mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-11-02 23:47:04 +00:00
Compare commits
260 Commits
audit-pagi
...
v1.65.11
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ec92b19fb | ||
|
|
630caa8686 | ||
|
|
497c3b2165 | ||
|
|
8823d06893 | ||
|
|
72a2bf50df | ||
|
|
15c1ed2011 | ||
|
|
3fb19663cc | ||
|
|
00cdbbc89c | ||
|
|
07f3cec16d | ||
|
|
e893e25ff4 | ||
|
|
1ad9a1eb13 | ||
|
|
d9288596e8 | ||
|
|
a2a642d9ce | ||
|
|
4f00007108 | ||
|
|
816ee77b6f | ||
|
|
26d470ecda | ||
|
|
3babd9d95e | ||
|
|
802e81b1cd | ||
|
|
41f0834c51 | ||
|
|
880de0b047 | ||
|
|
bbe7fda4e0 | ||
|
|
4fd214e328 | ||
|
|
92a9274dce | ||
|
|
8765d83083 | ||
|
|
a298152bc8 | ||
|
|
2b7abe5774 | ||
|
|
3e9241892e | ||
|
|
a8dcdcf339 | ||
|
|
6ea79a7960 | ||
|
|
2af562e313 | ||
|
|
40672f6a47 | ||
|
|
6d66ae3f50 | ||
|
|
94c89e0325 | ||
|
|
3670ef40a3 | ||
|
|
16d464fba5 | ||
|
|
0b7e0b9cd0 | ||
|
|
dd5fd114d2 | ||
|
|
6e53879344 | ||
|
|
af2bfd4d59 | ||
|
|
a4a34c8ba7 | ||
|
|
8c609f4fdf | ||
|
|
197f5b583f | ||
|
|
4eb4a03e59 | ||
|
|
5719469452 | ||
|
|
6e9d525890 | ||
|
|
e82805dd48 | ||
|
|
3d4e66d438 | ||
|
|
ffbc9f169a | ||
|
|
99650187e9 | ||
|
|
92699317cd | ||
|
|
0e48315803 | ||
|
|
868ec246bd | ||
|
|
0030a688c6 | ||
|
|
3ba8f51a2f | ||
|
|
04576b335c | ||
|
|
ea29aa176f | ||
|
|
9a9b7289ba | ||
|
|
5edcd5572a | ||
|
|
d601790864 | ||
|
|
3cd9b819f5 | ||
|
|
bf58d3ae93 | ||
|
|
d6c32e2d39 | ||
|
|
6914f75bb7 | ||
|
|
3adf3946b5 | ||
|
|
bdc4948afb | ||
|
|
331db10029 | ||
|
|
c036a157c8 | ||
|
|
2daf9e34d2 | ||
|
|
acf35f8c51 | ||
|
|
9155515082 | ||
|
|
558cd9b8b3 | ||
|
|
a0f02d0d2f | ||
|
|
9feb8492aa | ||
|
|
e5aa726899 | ||
|
|
93d1c28ccd | ||
|
|
b5ba9200bc | ||
|
|
699d866670 | ||
|
|
c3071344cb | ||
|
|
9e998dd2b6 | ||
|
|
c9accf6079 | ||
|
|
1b41a51004 | ||
|
|
3338dce900 | ||
|
|
1364779f81 | ||
|
|
b49d3423fc | ||
|
|
cccab2a985 | ||
|
|
1abaa90a7d | ||
|
|
6e1993ca8a | ||
|
|
171c821ac4 | ||
|
|
7ebf9186bf | ||
|
|
57d2f2baef | ||
|
|
0aee13878a | ||
|
|
f93ef0ca76 | ||
|
|
4ec03d8338 | ||
|
|
cb318aa6c6 | ||
|
|
733482cd5c | ||
|
|
3969d1287d | ||
|
|
1aa7854b0d | ||
|
|
7b27d4a1a7 | ||
|
|
24ddb8771f | ||
|
|
7134714245 | ||
|
|
96b320ac26 | ||
|
|
61235828ce | ||
|
|
1a27b21efe | ||
|
|
b88e121b30 | ||
|
|
4ba4119c2b | ||
|
|
91d1ca201c | ||
|
|
8bf063a228 | ||
|
|
4f53de39b1 | ||
|
|
8c3804f107 | ||
|
|
1be4ec2b90 | ||
|
|
8f0ed44b11 | ||
|
|
cbadfc4ac4 | ||
|
|
3d88ae4452 | ||
|
|
e57f565812 | ||
|
|
da2605ee03 | ||
|
|
07e2196eb4 | ||
|
|
6d99c54af7 | ||
|
|
2b7901e9a8 | ||
|
|
fb06dd1dbc | ||
|
|
d3b825529e | ||
|
|
ccf9c0db22 | ||
|
|
f8ba36b8be | ||
|
|
927c07bfd5 | ||
|
|
5bf9d99b3d | ||
|
|
7cad05342a | ||
|
|
867780e525 | ||
|
|
ff4f9a79c9 | ||
|
|
6699c36fb3 | ||
|
|
abd4556994 | ||
|
|
ccf0d17371 | ||
|
|
898584bbb6 | ||
|
|
6d7a267e39 | ||
|
|
9f656ca3cb | ||
|
|
d00caf1f4c | ||
|
|
a5e9d72bc5 | ||
|
|
8ac9047831 | ||
|
|
fede6451e2 | ||
|
|
9797ad380c | ||
|
|
33bc4a4d22 | ||
|
|
65509ace59 | ||
|
|
ea173f971a | ||
|
|
6378754c57 | ||
|
|
30fc972d78 | ||
|
|
c022b31c79 | ||
|
|
049b06bb39 | ||
|
|
e17d5213c0 | ||
|
|
dcf681941e | ||
|
|
1cd7d40405 | ||
|
|
fbd80ba2c7 | ||
|
|
88ab85bd04 | ||
|
|
78f98744fd | ||
|
|
9c9634a927 | ||
|
|
be47be626c | ||
|
|
2fbd3d8e19 | ||
|
|
d5c3d4c051 | ||
|
|
fac60f7ddd | ||
|
|
c371478c61 | ||
|
|
5911e29f34 | ||
|
|
c45c97c5d8 | ||
|
|
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 |
@@ -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
|
||||
|
||||
41
.github/workflows/build.yml
vendored
41
.github/workflows/build.yml
vendored
@@ -18,49 +18,8 @@ permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy-test:
|
||||
name: 🚀 Deploy to test env (fly.io)
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.base_ref == 'main' || (github.ref == 'refs/heads/main' && github.event_name == 'push') }}
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
- uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
|
||||
- name: 👀 Read app name
|
||||
uses: SebRollen/toml-action@v1.0.0
|
||||
id: app_name
|
||||
with:
|
||||
file: "fly.toml"
|
||||
field: "app"
|
||||
|
||||
- name: 🚀 Deploy Test
|
||||
run: flyctl deploy --remote-only --wait-timeout=300 --ha=false
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
|
||||
manual-approval:
|
||||
name: Manual Approval
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-test
|
||||
if: success()
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- name: Await Manual Approval
|
||||
uses: trstringer/manual-approval@v1
|
||||
with:
|
||||
secret: ${{ github.TOKEN }}
|
||||
approvers: DmitryPopov
|
||||
minimum-approvals: 1
|
||||
issue-title: "Manual Approval Required for Release"
|
||||
issue-body: "Please approve or deny the deployment."
|
||||
|
||||
build:
|
||||
name: 🛠 Build
|
||||
needs: manual-approval
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ (github.ref == 'refs/heads/main') && github.event_name == 'push' }}
|
||||
permissions:
|
||||
|
||||
593
CHANGELOG.md
593
CHANGELOG.md
@@ -2,6 +2,599 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v1.65.11](https://github.com/wanderer-industries/wanderer/compare/v1.65.10...v1.65.11) (2025-05-27)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed showing character ship
|
||||
|
||||
## [v1.65.10](https://github.com/wanderer-industries/wanderer/compare/v1.65.9...v1.65.10) (2025-05-27)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed sorting for characters in Local
|
||||
|
||||
* Map: Rally: fixed conflict style of status and rally
|
||||
|
||||
* Core: Fixed character token refresh
|
||||
|
||||
* Map: Add Rally point. Change placement of settings in Map User Settings. Add ability to placement minimap.
|
||||
|
||||
* Map: Routes - hide user routes btn from context if subscriptions is not active or widget is closed. Also now hidden widget will show again in place where it was on moment of hide (except cases when screen size has changed.
|
||||
|
||||
* Map: PINGS - Rally point first prototype
|
||||
|
||||
## [v1.65.9](https://github.com/wanderer-industries/wanderer/compare/v1.65.8...v1.65.9) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.65.8](https://github.com/wanderer-industries/wanderer/compare/v1.65.7...v1.65.8) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.65.7](https://github.com/wanderer-industries/wanderer/compare/v1.65.6...v1.65.7) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map character tracking issues
|
||||
|
||||
## [v1.65.6](https://github.com/wanderer-industries/wanderer/compare/v1.65.5...v1.65.6) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map character tracking issues
|
||||
|
||||
## [v1.65.5](https://github.com/wanderer-industries/wanderer/compare/v1.65.4...v1.65.5) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map character tracking issues
|
||||
|
||||
* Signature: Update restored signature character
|
||||
|
||||
## [v1.65.4](https://github.com/wanderer-industries/wanderer/compare/v1.65.3...v1.65.4) (2025-05-24)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signature: Force signature update even if there are no any changes
|
||||
|
||||
## [v1.65.3](https://github.com/wanderer-industries/wanderer/compare/v1.65.2...v1.65.3) (2025-05-23)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signature: Fixed signature clenup
|
||||
|
||||
## [v1.65.2](https://github.com/wanderer-industries/wanderer/compare/v1.65.1...v1.65.2) (2025-05-23)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signature: Fixed signature updates
|
||||
|
||||
## [v1.65.1](https://github.com/wanderer-industries/wanderer/compare/v1.65.0...v1.65.1) (2025-05-22)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added unsync map events timeout handling (force page refresh if outdated map events found)
|
||||
|
||||
## [v1.65.0](https://github.com/wanderer-industries/wanderer/compare/v1.64.8...v1.65.0) (2025-05-22)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* default connections from c1 holes to medium size
|
||||
|
||||
* support german and french signatures
|
||||
|
||||
* improve signature undo process
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* remove required id field from character schema
|
||||
|
||||
* update openapi spec response types
|
||||
|
||||
* fix issue with connection generation between k-space
|
||||
|
||||
* Signature: Fixed signatures updates
|
||||
|
||||
* update openapi spec for other apis
|
||||
|
||||
## [v1.64.8](https://github.com/wanderer-industries/wanderer/compare/v1.64.7...v1.64.8) (2025-05-20)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added unsync map events timeout handling (force page refresh if outdated map events found)
|
||||
|
||||
## [v1.64.7](https://github.com/wanderer-industries/wanderer/compare/v1.64.6...v1.64.7) (2025-05-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed connection EOL time refreshed every 2 minutes
|
||||
|
||||
## [v1.64.6](https://github.com/wanderer-industries/wanderer/compare/v1.64.5...v1.64.6) (2025-05-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added map hubs limits checking & a proper warning message shown
|
||||
|
||||
## [v1.64.5](https://github.com/wanderer-industries/wanderer/compare/v1.64.4...v1.64.5) (2025-05-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added character name update on re-auth
|
||||
|
||||
## [v1.64.4](https://github.com/wanderer-industries/wanderer/compare/v1.64.3...v1.64.4) (2025-05-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added 1 min timeout for ship and location updates on ESI API errors
|
||||
|
||||
## [v1.64.3](https://github.com/wanderer-industries/wanderer/compare/v1.64.2...v1.64.3) (2025-05-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed character tracking initialization logic & removed search caching
|
||||
|
||||
## [v1.64.2](https://github.com/wanderer-industries/wanderer/compare/v1.64.1...v1.64.2) (2025-05-13)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed tracking of ship & location for offline characters
|
||||
|
||||
## [v1.64.1](https://github.com/wanderer-industries/wanderer/compare/v1.64.0...v1.64.1) (2025-05-13)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed tracking stopped due to server errors
|
||||
|
||||
## [v1.64.0](https://github.com/wanderer-industries/wanderer/compare/v1.63.0...v1.64.0) (2025-05-13)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* api: add additional structure/signature methods (#365)
|
||||
|
||||
* api: add additional system/connections methods (#351)
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed EOL connections cleanup
|
||||
|
||||
* Core: Avoid Zarzakh system in routes widget
|
||||
|
||||
* remove repeat errors for token refresh (#375)
|
||||
|
||||
* updated openapi spec for character activity (#374)
|
||||
|
||||
* removed error from characters endpoint, and updated routes (#372)
|
||||
|
||||
* cleanup examples for system and connections (#370)
|
||||
|
||||
* remove error on websocket reconnect (#367)
|
||||
|
||||
## [v1.63.0](https://github.com/wanderer-industries/wanderer/compare/v1.62.4...v1.63.0) (2025-05-11)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: Updated map active characters page
|
||||
|
||||
## [v1.62.4](https://github.com/wanderer-industries/wanderer/compare/v1.62.3...v1.62.4) (2025-05-10)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map characters got untracked
|
||||
|
||||
## [v1.62.3](https://github.com/wanderer-industries/wanderer/compare/v1.62.2...v1.62.3) (2025-05-08)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map characters got untracked
|
||||
|
||||
## [v1.62.2](https://github.com/wanderer-industries/wanderer/compare/v1.62.1...v1.62.2) (2025-05-05)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed audit export API
|
||||
|
||||
## [v1.62.1](https://github.com/wanderer-industries/wanderer/compare/v1.62.0...v1.62.1) (2025-05-05)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.62.0](https://github.com/wanderer-industries/wanderer/compare/v1.61.2...v1.62.0) (2025-05-05)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: added user routes support
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed link signature modal crash afrer destination system removed
|
||||
|
||||
* Map: Change design for tags (#358)
|
||||
|
||||
* Map: Removed paywall restriction from public routes
|
||||
|
||||
* Core: Fixed issues with structures loading
|
||||
|
||||
* Map: Removed unnecessary logs
|
||||
|
||||
* Map: Add support user routes
|
||||
|
||||
* Map: Add support for User Routes on FE side.
|
||||
|
||||
* Map: Refactor Local - show ship name, change placement of ship name. Refactor On the Map - show corp and ally logo. Fixed problem with ellipsis at long character and ship names.
|
||||
|
||||
* Map: Refactored routes widget. Add loader for routes. Prepared for custom hubs
|
||||
|
||||
* Map: Refactor init and update of mapper
|
||||
|
||||
## [v1.61.2](https://github.com/wanderer-industries/wanderer/compare/v1.61.1...v1.61.2) (2025-04-29)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed main character checking & manual systems delete logic
|
||||
|
||||
## [v1.61.1](https://github.com/wanderer-industries/wanderer/compare/v1.61.0...v1.61.1) (2025-04-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed additional price calc for map sub updates
|
||||
|
||||
## [v1.61.0](https://github.com/wanderer-industries/wanderer/compare/v1.60.1...v1.61.0) (2025-04-24)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: force checking main character set for all map activity
|
||||
|
||||
## [v1.60.1](https://github.com/wanderer-industries/wanderer/compare/v1.60.0...v1.60.1) (2025-04-22)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Removed unnecessary code onFE part
|
||||
|
||||
* Map: Removed unnecessary debugger
|
||||
|
||||
* Map: Changed name for drifters systems. Fixed static info for Barbican.
|
||||
|
||||
## [v1.60.0](https://github.com/wanderer-industries/wanderer/compare/v1.59.11...v1.60.0) (2025-04-17)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* api: api showing character by user and main character (#334)
|
||||
|
||||
* Core: force map page reload after 30 mins of user inactivity (switched browser/tab)
|
||||
|
||||
* update character activity to use main character (#333)
|
||||
|
||||
## [v1.59.11](https://github.com/wanderer-industries/wanderer/compare/v1.59.10...v1.59.11) (2025-04-16)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed lifetime for A009 from 16h to 4.5h. Fixed problem with no appearing icon of shattered for Drifter wormholes. Fixed wanderings for Drifter wormholes. For system J011355 added static K346. For system J011824 added static K346. (#329)
|
||||
|
||||
## [v1.59.10](https://github.com/wanderer-industries/wanderer/compare/v1.59.9...v1.59.10) (2025-04-15)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.59.9](https://github.com/wanderer-industries/wanderer/compare/v1.59.8...v1.59.9) (2025-04-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed issues with map server manager
|
||||
|
||||
## [v1.59.8](https://github.com/wanderer-industries/wanderer/compare/v1.59.7...v1.59.8) (2025-04-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed issues with main character & tracking
|
||||
|
||||
## [v1.59.7](https://github.com/wanderer-industries/wanderer/compare/v1.59.6...v1.59.7) (2025-04-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed auto-select splashed systems
|
||||
|
||||
## [v1.59.6](https://github.com/wanderer-industries/wanderer/compare/v1.59.5...v1.59.6) (2025-04-13)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix icons of main, follow and shattered (#321)
|
||||
|
||||
## [v1.59.5](https://github.com/wanderer-industries/wanderer/compare/v1.59.4...v1.59.5) (2025-04-12)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signatures: avoid signatures delete on wrong buffer
|
||||
|
||||
## [v1.59.4](https://github.com/wanderer-industries/wanderer/compare/v1.59.3...v1.59.4) (2025-04-10)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.59.3](https://github.com/wanderer-industries/wanderer/compare/v1.59.2...v1.59.3) (2025-04-10)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.59.2](https://github.com/wanderer-industries/wanderer/compare/v1.59.1...v1.59.2) (2025-04-10)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: fixed connection validation
|
||||
|
||||
## [v1.59.1](https://github.com/wanderer-industries/wanderer/compare/v1.59.0...v1.59.1) (2025-03-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* doc: improve bot setup instructions (#309)
|
||||
|
||||
## [v1.59.0](https://github.com/wanderer-industries/wanderer/compare/v1.58.0...v1.59.0) (2025-03-23)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: added handling cases when wrong connections created
|
||||
|
||||
## [v1.58.0](https://github.com/wanderer-industries/wanderer/compare/v1.57.1...v1.58.0) (2025-03-22)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: Show online state on map characters page
|
||||
|
||||
* api: update character activity and api to allow date range (#299)
|
||||
|
||||
* api: update character activity and api to allow date range
|
||||
|
||||
## [v1.57.1](https://github.com/wanderer-industries/wanderer/compare/v1.57.0...v1.57.1) (2025-03-20)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.57.0](https://github.com/wanderer-industries/wanderer/compare/v1.56.6...v1.57.0) (2025-03-19)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* doc: update bot news (#294)
|
||||
|
||||
## [v1.56.6](https://github.com/wanderer-industries/wanderer/compare/v1.56.5...v1.56.6) (2025-03-19)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.56.5](https://github.com/wanderer-industries/wanderer/compare/v1.56.4...v1.56.5) (2025-03-19)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.56.4](https://github.com/wanderer-industries/wanderer/compare/v1.56.3...v1.56.4) (2025-03-19)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.56.3](https://github.com/wanderer-industries/wanderer/compare/v1.56.2...v1.56.3) (2025-03-19)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* cloak key error behavior (#288)
|
||||
|
||||
## [v1.56.2](https://github.com/wanderer-industries/wanderer/compare/v1.56.1...v1.56.2) (2025-03-18)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* show signature tooltip on top
|
||||
|
||||
## [v1.56.1](https://github.com/wanderer-industries/wanderer/compare/v1.56.0...v1.56.1) (2025-03-18)
|
||||
|
||||
|
||||
|
||||
|
||||
### 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)
|
||||
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -17,5 +17,6 @@ module.exports = {
|
||||
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
"linebreak-style": "off",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"endOfLine": "lf"
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
|
||||
@@ -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,67 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.p-autocomplete .p-autocomplete-multiple-container:not(.p-disabled).p-focus {
|
||||
box-shadow: 0 0 0 1px #335c7e;
|
||||
border-color: #335c7e;
|
||||
}
|
||||
|
||||
.p-inputtext:enabled:focus {
|
||||
box-shadow: 0 0 0 1px #335c7e;
|
||||
border-color: #335c7e;
|
||||
}
|
||||
|
||||
.p-inputtext:enabled:hover {
|
||||
border-color: #335c7e;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React, { RefObject } from 'react';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { PingType, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { useContextMenuSystemItems } from '@/hooks/Mapper/components/contexts/ContextMenuSystem/useContextMenuSystemItems.tsx';
|
||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
|
||||
export interface ContextMenuSystemProps {
|
||||
hubs: string[];
|
||||
userHubs: string[];
|
||||
contextMenuRef: RefObject<ContextMenu>;
|
||||
systemId: string | undefined;
|
||||
systems: SolarSystemRawType[];
|
||||
@@ -13,10 +14,12 @@ export interface ContextMenuSystemProps {
|
||||
onLockToggle(): void;
|
||||
onOpenSettings(): void;
|
||||
onHubToggle(): void;
|
||||
onUserHubToggle(): void;
|
||||
onSystemTag(val?: string): void;
|
||||
onSystemStatus(val: number): void;
|
||||
onSystemLabels(val: string): void;
|
||||
onCustomLabelDialog(): void;
|
||||
onTogglePing(type: PingType, solar_system_id: string, hasPing: boolean): void;
|
||||
onWaypointSet: WaypointSetContextHandler;
|
||||
}
|
||||
|
||||
@@ -25,7 +28,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,3 +1,4 @@
|
||||
export * from './useTagMenu';
|
||||
export * from './useStatusMenu';
|
||||
export * from './useLabelsMenu';
|
||||
export * from './useUserRoute';
|
||||
|
||||
@@ -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;
|
||||
}, []);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import { MapUserAddIcon, MapUserDeleteIcon } from '@/hooks/Mapper/icons';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { WidgetsIds } from '@/hooks/Mapper/components/mapInterface/constants.tsx';
|
||||
|
||||
interface UseUserRouteProps {
|
||||
systemId: string | undefined;
|
||||
userHubs: string[];
|
||||
onUserHubToggle(): void;
|
||||
}
|
||||
|
||||
export const useUserRoute = ({ userHubs, systemId, onUserHubToggle }: UseUserRouteProps) => {
|
||||
const {
|
||||
data: { isSubscriptionActive },
|
||||
windowsSettings,
|
||||
} = useMapRootState();
|
||||
|
||||
const ref = useRef({ userHubs, systemId, onUserHubToggle, isSubscriptionActive, windowsSettings });
|
||||
ref.current = { userHubs, systemId, onUserHubToggle, isSubscriptionActive, windowsSettings };
|
||||
|
||||
return useCallback(() => {
|
||||
const { userHubs, systemId, onUserHubToggle, isSubscriptionActive, windowsSettings } = ref.current;
|
||||
|
||||
const isVisibleUserRoutes = windowsSettings.visible.some(x => x === WidgetsIds.userRoutes);
|
||||
|
||||
if (!isSubscriptionActive || !isVisibleUserRoutes || !systemId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
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,
|
||||
},
|
||||
];
|
||||
}, [windowsSettings]);
|
||||
};
|
||||
@@ -5,22 +5,29 @@ import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
// import { PingType } from '@/hooks/Mapper/types/ping.ts';
|
||||
|
||||
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 +79,37 @@ 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 onTogglePingRally = useCallback(() => {
|
||||
// const { userHubs, system, outCommand } = ref.current;
|
||||
// if (!system) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// outCommand({
|
||||
// type: OutCommand.openPing,
|
||||
// data: {
|
||||
// solar_system_id: system,
|
||||
// type: PingType.Rally,
|
||||
// },
|
||||
// });
|
||||
// setSystem(undefined);
|
||||
// }, []);
|
||||
|
||||
const onSystemTag = useCallback((tag?: string) => {
|
||||
const { system, outCommand } = ref.current;
|
||||
if (!system) {
|
||||
@@ -104,7 +142,6 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
||||
setSystem(undefined);
|
||||
}, []);
|
||||
|
||||
|
||||
const onSystemStatus = useCallback((status: number) => {
|
||||
const { system, outCommand } = ref.current;
|
||||
if (!system) {
|
||||
@@ -177,6 +214,8 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
||||
onDeleteSystem,
|
||||
onLockToggle,
|
||||
onHubToggle,
|
||||
onUserHubToggle,
|
||||
// onTogglePingRally,
|
||||
onSystemTag,
|
||||
onSystemTemporaryName,
|
||||
onSystemStatus,
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { useLabelsMenu, useStatusMenu, useTagMenu } from '@/hooks/Mapper/components/contexts/ContextMenuSystem/hooks';
|
||||
import {
|
||||
useLabelsMenu,
|
||||
useStatusMenu,
|
||||
useTagMenu,
|
||||
useUserRoute,
|
||||
} from '@/hooks/Mapper/components/contexts/ContextMenuSystem/hooks';
|
||||
import { useMemo } from 'react';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import classes from './ContextMenuSystem.module.scss';
|
||||
@@ -9,11 +14,20 @@ 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 } from '@/hooks/Mapper/icons';
|
||||
import { PingType } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import clsx from 'clsx';
|
||||
import { MenuItem } from 'primereact/menuitem';
|
||||
import { MenuItemWithInfo, WdMenuItem } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
export const useContextMenuSystemItems = ({
|
||||
onDeleteSystem,
|
||||
onLockToggle,
|
||||
onHubToggle,
|
||||
onUserHubToggle,
|
||||
onTogglePing,
|
||||
onSystemTag,
|
||||
onSystemStatus,
|
||||
onSystemLabels,
|
||||
@@ -22,6 +36,7 @@ export const useContextMenuSystemItems = ({
|
||||
onWaypointSet,
|
||||
systemId,
|
||||
hubs,
|
||||
userHubs,
|
||||
systems,
|
||||
}: Omit<ContextMenuSystemProps, 'contextMenuRef'>) => {
|
||||
const getTags = useTagMenu(systems, systemId, onSystemTag);
|
||||
@@ -29,9 +44,31 @@ export const useContextMenuSystemItems = ({
|
||||
const getLabels = useLabelsMenu(systems, systemId, onSystemLabels, onCustomLabelDialog);
|
||||
const getWaypointMenu = useWaypointMenu(onWaypointSet);
|
||||
const canLockSystem = useMapCheckPermissions([UserPermission.LOCK_SYSTEM]);
|
||||
const canDeleteSystem = useMapCheckPermissions([UserPermission.DELETE_SYSTEM]);
|
||||
const getUserRoutes = useUserRoute({ userHubs, systemId, onUserHubToggle });
|
||||
|
||||
return useMemo(() => {
|
||||
const {
|
||||
data: { pings, isSubscriptionActive },
|
||||
} = useMapRootState();
|
||||
|
||||
const ping = useMemo(() => (pings.length === 1 ? pings[0] : undefined), [pings]);
|
||||
const isShowPingBtn = useMemo(() => {
|
||||
if (!isSubscriptionActive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pings.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return pings[0].solar_system_id === systemId;
|
||||
}, [isSubscriptionActive, pings, systemId]);
|
||||
|
||||
return useMemo((): MenuItem[] => {
|
||||
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
||||
const systemStaticInfo = getSystemStaticInfo(systemId)!;
|
||||
|
||||
const hasPing = ping?.solar_system_id === systemId;
|
||||
|
||||
if (!system || !systemId) {
|
||||
return [];
|
||||
@@ -44,9 +81,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,52 +94,97 @@ 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,
|
||||
},
|
||||
...(system.locked
|
||||
? canLockSystem
|
||||
? [
|
||||
{
|
||||
label: 'Unlock',
|
||||
icon: PrimeIcons.LOCK_OPEN,
|
||||
command: onLockToggle,
|
||||
},
|
||||
]
|
||||
: []
|
||||
: [
|
||||
...(canLockSystem
|
||||
? [
|
||||
{
|
||||
label: 'Lock',
|
||||
icon: PrimeIcons.LOCK,
|
||||
command: onLockToggle,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...getUserRoutes(),
|
||||
|
||||
{ separator: true },
|
||||
{
|
||||
command: () => onTogglePing(PingType.Rally, systemId, hasPing),
|
||||
disabled: !isShowPingBtn,
|
||||
template: () => {
|
||||
const iconClasses = clsx({
|
||||
'pi text-cyan-400 hero-signal': !hasPing,
|
||||
'pi text-red-400 hero-signal-slash': hasPing,
|
||||
});
|
||||
|
||||
if (isShowPingBtn) {
|
||||
return <WdMenuItem icon={iconClasses}>{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}</WdMenuItem>;
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItemWithInfo
|
||||
infoTitle="Locked. Ping can be set only for one system."
|
||||
infoClass="pi-lock text-stone-500 mr-[12px]"
|
||||
>
|
||||
<WdMenuItem disabled icon={iconClasses}>
|
||||
{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}
|
||||
</WdMenuItem>
|
||||
</MenuItemWithInfo>
|
||||
);
|
||||
},
|
||||
},
|
||||
...(canLockSystem
|
||||
? [
|
||||
{
|
||||
label: system.locked ? 'Unlock' : 'Lock',
|
||||
icon: system.locked ? PrimeIcons.LOCK_OPEN : PrimeIcons.LOCK,
|
||||
command: onLockToggle,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
...(canDeleteSystem && !system.locked
|
||||
? [
|
||||
{ separator: true },
|
||||
{
|
||||
label: 'Delete',
|
||||
icon: PrimeIcons.TRASH,
|
||||
command: onDeleteSystem,
|
||||
disabled: hasPing,
|
||||
template: () => {
|
||||
if (!hasPing) {
|
||||
return <WdMenuItem icon="text-red-400 pi pi-trash">Delete</WdMenuItem>;
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItemWithInfo
|
||||
infoTitle="Locked. System can not be deleted until ping set."
|
||||
infoClass="pi-lock text-stone-500 mr-[12px]"
|
||||
>
|
||||
<WdMenuItem disabled icon="text-red-400 pi pi-trash">
|
||||
Delete
|
||||
</WdMenuItem>
|
||||
</MenuItemWithInfo>
|
||||
);
|
||||
},
|
||||
},
|
||||
]),
|
||||
]
|
||||
: []),
|
||||
];
|
||||
}, [
|
||||
canLockSystem,
|
||||
systems,
|
||||
systemId,
|
||||
systems,
|
||||
getTags,
|
||||
getStatus,
|
||||
getLabels,
|
||||
getWaypointMenu,
|
||||
getUserRoutes,
|
||||
hubs,
|
||||
onHubToggle,
|
||||
onOpenSettings,
|
||||
canLockSystem,
|
||||
onLockToggle,
|
||||
canDeleteSystem,
|
||||
onDeleteSystem,
|
||||
onOpenSettings,
|
||||
onTogglePing,
|
||||
ping,
|
||||
isShowPingBtn,
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -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 { LayoutEventBlocker, TooltipPosition, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons';
|
||||
|
||||
import classes from './FastSystemActions.module.scss';
|
||||
import clsx from 'clsx';
|
||||
@@ -59,9 +59,21 @@ export const FastSystemActions = ({
|
||||
return (
|
||||
<LayoutEventBlocker className={clsx('flex px-2 gap-2 justify-between items-center h-full')}>
|
||||
<div className={clsx('flex gap-2 items-center h-full', classes.Links)}>
|
||||
<WdImgButton tooltip={{ content: 'Open zkillboard' }} source={ZKB_ICON} onClick={handleOpenZKB} />
|
||||
<WdImgButton tooltip={{ content: 'Open Anoikis' }} source={ANOIK_ICON} onClick={handleOpenAnoikis} />
|
||||
<WdImgButton tooltip={{ content: 'Open Dotlan' }} source={DOTLAN_ICON} onClick={handleOpenDotlan} />
|
||||
<WdImgButton
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Open zkillboard' }}
|
||||
source={ZKB_ICON}
|
||||
onClick={handleOpenZKB}
|
||||
/>
|
||||
<WdImgButton
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Open Anoikis' }}
|
||||
source={ANOIK_ICON}
|
||||
onClick={handleOpenAnoikis}
|
||||
/>
|
||||
<WdImgButton
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Open Dotlan' }}
|
||||
source={DOTLAN_ICON}
|
||||
onClick={handleOpenDotlan}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center pl-1">
|
||||
@@ -69,14 +81,14 @@ export const FastSystemActions = ({
|
||||
textSize={WdImageSize.off}
|
||||
className={PrimeIcons.COPY}
|
||||
onClick={copySystemNameToClipboard}
|
||||
tooltip={{ content: 'Copy system name' }}
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Copy system name' }}
|
||||
/>
|
||||
{showEdit && (
|
||||
<WdImgButton
|
||||
textSize={WdImageSize.off}
|
||||
className="pi pi-pen-to-square text-base"
|
||||
onClick={onOpenSettings}
|
||||
tooltip={{ content: 'Edit system name and description' }}
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Edit system name and description' }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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]);
|
||||
};
|
||||
|
||||
@@ -28,11 +28,12 @@ import {
|
||||
import { getBehaviorForTheme } from './helpers/getThemeBehavior';
|
||||
import { OnMapAddSystemCallback, OnMapSelectionChange } from './map.types';
|
||||
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
|
||||
import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { PingData, SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import clsx from 'clsx';
|
||||
import { useBackgroundVars } from './hooks/useBackgroundVars';
|
||||
import type { PanelPosition } from '@reactflow/core';
|
||||
|
||||
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
|
||||
|
||||
@@ -95,6 +96,8 @@ interface MapCompProps {
|
||||
isShowBackgroundPattern?: boolean;
|
||||
isSoftBackground?: boolean;
|
||||
theme?: string;
|
||||
pings: PingData[];
|
||||
minimapPlacement?: PanelPosition;
|
||||
}
|
||||
|
||||
const MapComp = ({
|
||||
@@ -112,6 +115,8 @@ const MapComp = ({
|
||||
isSoftBackground,
|
||||
theme,
|
||||
onAddSystem,
|
||||
pings,
|
||||
minimapPlacement = 'bottom-right',
|
||||
}: MapCompProps) => {
|
||||
const { getNodes } = useReactFlow();
|
||||
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
|
||||
@@ -206,8 +211,9 @@ const MapComp = ({
|
||||
...x,
|
||||
showKSpaceBG: showKSpaceBG,
|
||||
isThickConnections: isThickConnections,
|
||||
pings,
|
||||
}));
|
||||
}, [showKSpaceBG, isThickConnections, update]);
|
||||
}, [showKSpaceBG, isThickConnections, pings, update]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -270,7 +276,9 @@ const MapComp = ({
|
||||
// onlyRenderVisibleElements
|
||||
selectionMode={SelectionMode.Partial}
|
||||
>
|
||||
{isShowMinimap && <MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} />}
|
||||
{isShowMinimap && (
|
||||
<MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} position={minimapPlacement} />
|
||||
)}
|
||||
{isShowBackgroundPattern && <Background variant={variant} gap={gap} size={size} color={color} />}
|
||||
</ReactFlow>
|
||||
{/* <button className="z-auto btn btn-primary absolute top-20 right-20" onClick={handleGetPassages}>
|
||||
|
||||
@@ -38,6 +38,10 @@ const INITIAL_DATA: MapData = {
|
||||
systemSignatures: {} as Record<string, SystemSignature[]>,
|
||||
options: {} as Record<string, string | boolean>,
|
||||
isSubscriptionActive: false,
|
||||
mainCharacterEveId: null,
|
||||
followingCharacterEveId: null,
|
||||
userHubs: [],
|
||||
pings: [],
|
||||
};
|
||||
|
||||
export interface MapContextProps {
|
||||
@@ -53,7 +57,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,14 +1,15 @@
|
||||
import React, { useMemo } from 'react';
|
||||
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';
|
||||
|
||||
const ITEM_HEIGHT = 35;
|
||||
const MIN_TOOLTIP_HEIGHT = 40;
|
||||
|
||||
type TooltipSize = 'xs' | 'sm' | 'md' | 'lg';
|
||||
|
||||
type KillsBookmarkTooltipProps = {
|
||||
killsCount: number;
|
||||
killsActivityType: string | null;
|
||||
@@ -18,12 +19,14 @@ type KillsBookmarkTooltipProps = {
|
||||
} & WithChildren &
|
||||
WithClassName;
|
||||
|
||||
export const KillsCounter = ({ killsCount, systemId, className, children, size = 'xs' }: KillsBookmarkTooltipProps) => {
|
||||
const {
|
||||
isLoading,
|
||||
kills: detailedKills,
|
||||
systemNameMap,
|
||||
} = useKillsCounter({
|
||||
export const KillsCounter = ({
|
||||
killsCount,
|
||||
systemId,
|
||||
className,
|
||||
children,
|
||||
size = TooltipSize.xs,
|
||||
}: KillsBookmarkTooltipProps) => {
|
||||
const { isLoading, kills: detailedKills } = useKillsCounter({
|
||||
realSystemId: systemId,
|
||||
});
|
||||
|
||||
@@ -37,28 +40,24 @@ export const KillsCounter = ({ killsCount, systemId, className, children, size =
|
||||
}
|
||||
|
||||
// Calculate height based on number of kills, but ensure a minimum height
|
||||
const killsNeededHeight = limitedKills.length * ITEM_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));
|
||||
|
||||
const tooltipContent = (
|
||||
<div
|
||||
style={{
|
||||
width: '400px',
|
||||
height: `${tooltipHeight}px`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="flex-1 h-full">
|
||||
<SystemKillsContent kills={limitedKills} systemNameMap={systemNameMap} onlyOneSystem />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<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>;
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
||||
|
||||
$pastel-blue: #5a7d9a;
|
||||
$pastel-pink: #d291bc;
|
||||
$pastel-pink: rgb(30, 161, 255);
|
||||
$dark-bg: #2d2d2d;
|
||||
$text-color: #ffffff;
|
||||
$tooltip-bg: #202020;
|
||||
|
||||
$neon-color-1: rgb(27, 132, 236);
|
||||
$neon-color-3: rgba(27, 132, 236, 0.40);
|
||||
|
||||
@keyframes move-stripes {
|
||||
from {
|
||||
background-position: 0 0;
|
||||
}
|
||||
to {
|
||||
background-position: 30px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.RootCustomNode {
|
||||
display: flex;
|
||||
width: 130px;
|
||||
@@ -32,7 +44,7 @@ $tooltip-bg: #202020;
|
||||
&.Amarria,
|
||||
&.Gallente,
|
||||
&.Caldaria {
|
||||
&::before {
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -48,7 +60,7 @@ $tooltip-bg: #202020;
|
||||
}
|
||||
|
||||
&.Mataria {
|
||||
&::before {
|
||||
&::after {
|
||||
background-image: url('/images/mataria-180.png');
|
||||
opacity: 0.6;
|
||||
background-position-x: 1px;
|
||||
@@ -57,7 +69,7 @@ $tooltip-bg: #202020;
|
||||
}
|
||||
|
||||
&.Caldaria {
|
||||
&::before {
|
||||
&::after {
|
||||
background-image: url('/images/caldaria-180.png');
|
||||
opacity: 0.6;
|
||||
background-position-x: 1px;
|
||||
@@ -66,7 +78,7 @@ $tooltip-bg: #202020;
|
||||
}
|
||||
|
||||
&.Amarria {
|
||||
&::before {
|
||||
&::after {
|
||||
opacity: 0.45;
|
||||
background-image: url('/images/amarr-180.png');
|
||||
background-position-x: 0;
|
||||
@@ -75,7 +87,7 @@ $tooltip-bg: #202020;
|
||||
}
|
||||
|
||||
&.Gallente {
|
||||
&::before {
|
||||
&::after {
|
||||
opacity: 0.5;
|
||||
background-image: url('/images/gallente-180.png');
|
||||
background-position-x: 1px;
|
||||
@@ -88,6 +100,29 @@ $tooltip-bg: #202020;
|
||||
box-shadow: 0 0 10px #9a1af1c2;
|
||||
}
|
||||
|
||||
&.rally {
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
|
||||
border-color: $neon-color-1;
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
$neon-color-3 0px,
|
||||
$neon-color-3 8px,
|
||||
transparent 8px,
|
||||
transparent 21px
|
||||
);
|
||||
background-size: 30px 30px;
|
||||
animation: move-stripes 3s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
&.eve-system-status-home {
|
||||
border: 1px solid var(--eve-solar-system-status-color-home-dark30);
|
||||
background-image: linear-gradient(45deg, var(--eve-solar-system-status-color-background), transparent);
|
||||
@@ -355,3 +390,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';
|
||||
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}
|
||||
@@ -63,8 +71,11 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
|
||||
className={clsx(
|
||||
classes.RootCustomNode,
|
||||
nodeVars.regionClass && classes[nodeVars.regionClass],
|
||||
nodeVars.status !== undefined ? classes[STATUS_CLASSES[nodeVars.status]] : '',
|
||||
{ [classes.selected]: nodeVars.selected },
|
||||
nodeVars.status !== undefined && classes[STATUS_CLASSES[nodeVars.status]],
|
||||
{
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.rally]: nodeVars.isRally,
|
||||
},
|
||||
)}
|
||||
onMouseDownCapture={e => nodeVars.dbClick(e)}
|
||||
>
|
||||
@@ -82,7 +93,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
|
||||
|
||||
@@ -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}
|
||||
@@ -64,7 +71,10 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
|
||||
classes.RootCustomNode,
|
||||
nodeVars.regionClass && classes[nodeVars.regionClass],
|
||||
nodeVars.status !== undefined ? classes[STATUS_CLASSES[nodeVars.status]] : '',
|
||||
{ [classes.selected]: nodeVars.selected },
|
||||
{
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.rally]: nodeVars.isRally,
|
||||
},
|
||||
)}
|
||||
onMouseDownCapture={e => nodeVars.dbClick(e)}
|
||||
>
|
||||
@@ -109,23 +119,13 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
|
||||
|
||||
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
|
||||
{nodeVars.customName && (
|
||||
<div
|
||||
className={clsx(
|
||||
classes.CustomName,
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
|
||||
)}
|
||||
>
|
||||
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||
{nodeVars.customName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!nodeVars.isWormhole && !nodeVars.customName && (
|
||||
<div
|
||||
className={clsx(
|
||||
classes.RegionName,
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
|
||||
)}
|
||||
>
|
||||
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||
{nodeVars.regionName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -115,35 +115,18 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
||||
}, 500);
|
||||
break;
|
||||
|
||||
case Commands.pingAdded:
|
||||
case Commands.pingCancelled:
|
||||
case Commands.routes:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.signaturesUpdated:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.linkSignatureToSystem:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.detailedKillsUpdated:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.characterActivityData:
|
||||
break;
|
||||
|
||||
case Commands.trackingCharactersData:
|
||||
break;
|
||||
|
||||
case Commands.updateActivity:
|
||||
break;
|
||||
|
||||
case Commands.updateTracking:
|
||||
break;
|
||||
|
||||
case Commands.userSettingsUpdated:
|
||||
// do nothing
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -13,28 +13,26 @@ interface MapEvent {
|
||||
payload?: Kill[];
|
||||
}
|
||||
|
||||
export function useNodeKillsCount(
|
||||
systemId: number | string,
|
||||
initialKillsCount: number | null
|
||||
): number | null {
|
||||
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);
|
||||
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 true;
|
||||
}
|
||||
return false;
|
||||
}, [systemId]);
|
||||
return false;
|
||||
},
|
||||
[systemId],
|
||||
);
|
||||
|
||||
useMapEventListener(handleEvent);
|
||||
|
||||
|
||||
@@ -9,10 +9,11 @@ import { REGIONS_MAP, Spaces } from '@/hooks/Mapper/constants';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace';
|
||||
import { getSystemClassStyles } from '@/hooks/Mapper/components/map/helpers';
|
||||
import { sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||
import { CharacterTypeRaw, OutCommand, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { CharacterTypeRaw, OutCommand, PingType, 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';
|
||||
@@ -20,6 +21,45 @@ function getActivityType(count: number): string {
|
||||
return 'activityDanger';
|
||||
}
|
||||
|
||||
export interface SolarSystemNodeVars {
|
||||
id: string;
|
||||
selected: boolean;
|
||||
visible: boolean;
|
||||
isWormhole: boolean;
|
||||
classTitleColor: string | null;
|
||||
killsCount: number | null;
|
||||
killsActivityType: string | null;
|
||||
hasUserCharacters: boolean;
|
||||
showHandlers: boolean;
|
||||
regionClass: string | null;
|
||||
systemName: string;
|
||||
customName?: string | null;
|
||||
labelCustom: string | null;
|
||||
isShattered: boolean;
|
||||
tag?: string | null;
|
||||
status?: number;
|
||||
labelsInfo: LabelInfo[];
|
||||
dbClick: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
sortedStatics: Array<string | number>;
|
||||
effectName: string | null;
|
||||
regionName: string | null;
|
||||
solarSystemId: string;
|
||||
solarSystemName: string | null;
|
||||
locked: boolean;
|
||||
hubs: string[];
|
||||
name: string | null;
|
||||
isConnecting: boolean;
|
||||
hoverNodeId: string | null;
|
||||
charactersInSystem: Array<CharacterTypeRaw>;
|
||||
userCharacters: string[];
|
||||
unsplashedLeft: Array<SystemSignature>;
|
||||
unsplashedRight: Array<SystemSignature>;
|
||||
isThickConnections: boolean;
|
||||
isRally: boolean;
|
||||
classTitle: string | null;
|
||||
temporaryName?: string | null;
|
||||
}
|
||||
|
||||
const SpaceToClass: Record<string, string> = {
|
||||
[Spaces.Caldari]: 'Caldaria',
|
||||
[Spaces.Matar]: 'Mataria',
|
||||
@@ -40,11 +80,10 @@ export function useLocalCounter(nodeVars: SolarSystemNodeVars) {
|
||||
return { localCounterCharacters };
|
||||
}
|
||||
|
||||
export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarSystemNodeVars {
|
||||
export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarSystemNodeVars => {
|
||||
const { id, data, selected } = props;
|
||||
const {
|
||||
system_static_info,
|
||||
system_signatures,
|
||||
id: solar_system_id,
|
||||
locked,
|
||||
name,
|
||||
tag,
|
||||
@@ -54,23 +93,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';
|
||||
@@ -89,19 +131,17 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
visibleNodes,
|
||||
showKSpaceBG,
|
||||
isThickConnections,
|
||||
pings,
|
||||
},
|
||||
outCommand,
|
||||
} = useMapState();
|
||||
|
||||
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);
|
||||
@@ -121,7 +161,7 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
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(
|
||||
@@ -132,7 +172,7 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
const dbClick = useDoubleClick(() => {
|
||||
outCommand({
|
||||
type: OutCommand.openSettings,
|
||||
data: { system_id: solar_system_id.toString() },
|
||||
data: { system_id: solar_system_id },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -144,16 +184,21 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
const { systemName, computedTemporaryName, customName } = useSystemName({
|
||||
isTempSystemNameEnabled,
|
||||
temporary_name,
|
||||
solar_system_name: solar_system_name || '',
|
||||
isShowLinkedSigIdTempName,
|
||||
linkedSigPrefix,
|
||||
name,
|
||||
systemStaticInfo,
|
||||
});
|
||||
|
||||
const { unsplashedLeft, unsplashedRight } = useUnsplashedSignatures(systemSigs, isShowUnsplashedSignatures);
|
||||
|
||||
const hubsAsStrings = useMemo(() => hubs.map(item => item.toString()), [hubs]);
|
||||
|
||||
const isRally = useMemo(
|
||||
() => pings.find(x => x.solar_system_id === solar_system_id && x.type === PingType.Rally),
|
||||
[pings, solar_system_id],
|
||||
);
|
||||
|
||||
const nodeVars: SolarSystemNodeVars = {
|
||||
id,
|
||||
selected,
|
||||
@@ -190,45 +235,8 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
temporaryName: computedTemporaryName,
|
||||
regionName: region_name,
|
||||
solarSystemName: solar_system_name,
|
||||
isRally,
|
||||
};
|
||||
|
||||
return nodeVars;
|
||||
}
|
||||
|
||||
export interface SolarSystemNodeVars {
|
||||
id: string;
|
||||
selected: boolean;
|
||||
visible: boolean;
|
||||
isWormhole: boolean;
|
||||
classTitleColor: string | null;
|
||||
killsCount: number | null;
|
||||
killsActivityType: string | null;
|
||||
hasUserCharacters: boolean;
|
||||
showHandlers: boolean;
|
||||
regionClass: string | null;
|
||||
systemName: string;
|
||||
customName?: string | null;
|
||||
labelCustom: string | null;
|
||||
isShattered: boolean;
|
||||
tag?: string | null;
|
||||
status?: number;
|
||||
labelsInfo: LabelInfo[];
|
||||
dbClick: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
sortedStatics: Array<string | number>;
|
||||
effectName: string | null;
|
||||
regionName: string | null;
|
||||
solarSystemId: string;
|
||||
solarSystemName: string | null;
|
||||
locked: boolean;
|
||||
hubs: string[];
|
||||
name: string | null;
|
||||
isConnecting: boolean;
|
||||
hoverNodeId: string | null;
|
||||
charactersInSystem: Array<CharacterTypeRaw>;
|
||||
userCharacters: string[];
|
||||
unsplashedLeft: Array<SystemSignature>;
|
||||
unsplashedRight: Array<SystemSignature>;
|
||||
isThickConnections: boolean;
|
||||
classTitle: string | null;
|
||||
temporaryName?: string | null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
// useSystemName.ts
|
||||
import { useMemo } from 'react';
|
||||
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||
|
||||
interface UseSystemNameParams {
|
||||
isTempSystemNameEnabled: boolean;
|
||||
temporary_name?: string | null;
|
||||
solar_system_name: string;
|
||||
isShowLinkedSigIdTempName: boolean;
|
||||
linkedSigPrefix: string | null;
|
||||
name?: string | null;
|
||||
systemStaticInfo: SolarSystemStaticInfoRaw;
|
||||
}
|
||||
|
||||
export function useSystemName({
|
||||
export const useSystemName = ({
|
||||
isTempSystemNameEnabled,
|
||||
temporary_name,
|
||||
solar_system_name,
|
||||
isShowLinkedSigIdTempName,
|
||||
linkedSigPrefix,
|
||||
name,
|
||||
}: UseSystemNameParams) {
|
||||
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]);
|
||||
|
||||
@@ -32,6 +36,7 @@ export function useSystemName({
|
||||
if (isTempSystemNameEnabled && computedTemporaryName) {
|
||||
return computedTemporaryName;
|
||||
}
|
||||
|
||||
return solar_system_name;
|
||||
}, [isTempSystemNameEnabled, computedTemporaryName, solar_system_name]);
|
||||
|
||||
@@ -39,11 +44,13 @@ export function useSystemName({
|
||||
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 };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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,97 @@
|
||||
import classes from './MarkdownComment.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
InfoDrawer,
|
||||
MarkdownTextViewer,
|
||||
TimeAgo,
|
||||
TooltipPosition,
|
||||
WdImgButton,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
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 };
|
||||
|
||||
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>
|
||||
}
|
||||
>
|
||||
<MarkdownTextViewer>{text}</MarkdownTextViewer>
|
||||
</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,73 @@
|
||||
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 {}
|
||||
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
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';
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMemo } from 'react';
|
||||
import { RoutesList } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesList';
|
||||
|
||||
export const PingRoute = () => {
|
||||
const {
|
||||
data: { routes, pings, loadingPublicRoutes },
|
||||
} = useMapRootState();
|
||||
|
||||
const route = useMemo(() => {
|
||||
const [ping] = pings;
|
||||
if (!ping) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return routes?.routes.find(x => ping.solar_system_id === x.destination.toString()) ?? null;
|
||||
}, [routes, pings]);
|
||||
|
||||
const preparedRoute = useMemo(() => {
|
||||
if (!route) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...route,
|
||||
mapped_systems:
|
||||
route.systems?.map(solar_system_id =>
|
||||
routes?.systems_static_data.find(
|
||||
system_static_data => system_static_data.solar_system_id === solar_system_id,
|
||||
),
|
||||
) ?? [],
|
||||
};
|
||||
}, [route, routes?.systems_static_data]);
|
||||
|
||||
if (loadingPublicRoutes) {
|
||||
return <span className="m-0 text-[12px]">Loading...</span>;
|
||||
}
|
||||
|
||||
if (!preparedRoute || preparedRoute.origin === preparedRoute.destination) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="m-0 flex gap-2 items-center text-[12px]">
|
||||
{preparedRoute.has_connection && <div className="text-[12px]">{preparedRoute.systems?.length ?? 2}</div>}
|
||||
<RoutesList data={preparedRoute} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,284 @@
|
||||
import { Button } from 'primereact/button';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Toast } from 'primereact/toast';
|
||||
import clsx from 'clsx';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { Commands, OutCommand, PingType } from '@/hooks/Mapper/types';
|
||||
import {
|
||||
CharacterCardById,
|
||||
SystemView,
|
||||
TimeAgo,
|
||||
TooltipPosition,
|
||||
WdImgButton,
|
||||
WdImgButtonTooltip,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import useRefState from 'react-usestateref';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
import { ConfirmPopup } from 'primereact/confirmpopup';
|
||||
import { PingRoute } from '@/hooks/Mapper/components/mapInterface/components/PingsInterface/PingRoute.tsx';
|
||||
import { PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
const PING_PLACEMENT_MAP = {
|
||||
[PingsPlacement.rightTop]: 'top-right',
|
||||
[PingsPlacement.leftTop]: 'top-left',
|
||||
[PingsPlacement.rightBottom]: 'bottom-right',
|
||||
[PingsPlacement.leftBottom]: 'bottom-left',
|
||||
};
|
||||
|
||||
const PING_PLACEMENT_MAP_OFFSETS = {
|
||||
[PingsPlacement.rightTop]: { default: '!top-[56px]', withLeftMenu: '!top-[56px] !right-[64px]' },
|
||||
[PingsPlacement.rightBottom]: { default: '!bottom-[15px]', withLeftMenu: '!bottom-[15px] !right-[64px]' },
|
||||
[PingsPlacement.leftTop]: { default: '!top-[56px] !left-[64px]', withLeftMenu: '!top-[56px] !left-[64px]' },
|
||||
[PingsPlacement.leftBottom]: { default: '!left-[64px] !bottom-[15px]', withLeftMenu: '!bottom-[15px]' },
|
||||
};
|
||||
|
||||
const CLOSE_TOOLTIP_PROPS: WdImgButtonTooltip = {
|
||||
content: 'Hide',
|
||||
position: TooltipPosition.top,
|
||||
className: '!leading-[0]',
|
||||
};
|
||||
|
||||
const NAVIGATE_TOOLTIP_PROPS: WdImgButtonTooltip = {
|
||||
content: 'Navigate To',
|
||||
position: TooltipPosition.top,
|
||||
className: '!leading-[0]',
|
||||
};
|
||||
|
||||
const DELETE_TOOLTIP_PROPS: WdImgButtonTooltip = {
|
||||
content: 'Remove',
|
||||
position: TooltipPosition.top,
|
||||
className: '!leading-[0]',
|
||||
};
|
||||
|
||||
// const TOOLTIP_WAYPOINT_PROPS: WdImgButtonTooltip = {
|
||||
// content: 'Waypoint',
|
||||
// position: TooltipPosition.bottom,
|
||||
// className: '!leading-[0]',
|
||||
// };
|
||||
|
||||
const TITLES = {
|
||||
[PingType.Alert]: 'Alert',
|
||||
[PingType.Rally]: 'Rally Point',
|
||||
};
|
||||
|
||||
const ICONS = {
|
||||
[PingType.Alert]: 'pi-bell',
|
||||
[PingType.Rally]: 'pi-bell',
|
||||
};
|
||||
|
||||
export interface PingsInterfaceProps {
|
||||
hasLeftOffset?: boolean;
|
||||
}
|
||||
|
||||
// TODO: right now can be one ping. But in future will be multiple pings then:
|
||||
// 1. we will use this as container
|
||||
// 2. we will create PingInstance (which will contains ping Button and Toast
|
||||
// 3. ADD Context menu
|
||||
export const PingsInterface = ({ hasLeftOffset }: PingsInterfaceProps) => {
|
||||
const toast = useRef<Toast>(null);
|
||||
const [isShow, setIsShow, isShowRef] = useRefState(false);
|
||||
|
||||
const cpRemoveBtnRef = useRef<HTMLElement>();
|
||||
const [cpRemoveVisible, setCpRemoveVisible] = useState(false);
|
||||
|
||||
const {
|
||||
storedSettings: { interfaceSettings },
|
||||
data: { pings, selectedSystems },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const selectedSystem = useMemo(() => {
|
||||
if (selectedSystems.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return selectedSystems[0];
|
||||
}, [selectedSystems]);
|
||||
|
||||
const ping = useMemo(() => (pings.length === 1 ? pings[0] : null), [pings]);
|
||||
|
||||
const handleShowCP = useCallback(() => setCpRemoveVisible(true), []);
|
||||
const handleHideCP = useCallback(() => setCpRemoveVisible(false), []);
|
||||
|
||||
const navigateTo = useCallback(() => {
|
||||
if (!ping) {
|
||||
return;
|
||||
}
|
||||
|
||||
emitMapEvent({
|
||||
name: Commands.centerSystem,
|
||||
data: ping.solar_system_id?.toString(),
|
||||
});
|
||||
}, [ping]);
|
||||
|
||||
const removePing = useCallback(async () => {
|
||||
if (!ping) {
|
||||
return;
|
||||
}
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.cancelPing,
|
||||
data: { type: ping.type, solar_system_id: ping.solar_system_id },
|
||||
});
|
||||
}, [outCommand, ping]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ping) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tid = setTimeout(() => {
|
||||
toast.current?.replace({ severity: 'warn', detail: ping.message });
|
||||
setIsShow(true);
|
||||
}, 200);
|
||||
|
||||
return () => clearTimeout(tid);
|
||||
}, [ping]);
|
||||
|
||||
const handleClickShow = useCallback(() => {
|
||||
if (!ping) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isShowRef.current) {
|
||||
toast.current?.show({ severity: 'warn', detail: ping.message });
|
||||
setIsShow(true);
|
||||
return;
|
||||
}
|
||||
toast.current?.clear();
|
||||
setIsShow(false);
|
||||
}, [ping]);
|
||||
|
||||
const handleClickHide = useCallback(() => {
|
||||
toast.current?.clear();
|
||||
setIsShow(false);
|
||||
}, []);
|
||||
|
||||
const { placement, offsets } = useMemo(() => {
|
||||
const rawPlacement =
|
||||
interfaceSettings.pingsPlacement == null ? PingsPlacement.rightTop : interfaceSettings.pingsPlacement;
|
||||
|
||||
return {
|
||||
placement: PING_PLACEMENT_MAP[rawPlacement],
|
||||
offsets: PING_PLACEMENT_MAP_OFFSETS[rawPlacement],
|
||||
};
|
||||
}, [interfaceSettings]);
|
||||
|
||||
if (!ping) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isShowSelectedSystem = selectedSystem != null && selectedSystem !== ping.solar_system_id;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toast
|
||||
position={placement as never}
|
||||
className={clsx('!max-w-[initial] w-[500px]', hasLeftOffset ? offsets.withLeftMenu : offsets.default)}
|
||||
ref={toast}
|
||||
content={({ message }) => (
|
||||
<section
|
||||
className={clsx(
|
||||
'flex flex-col p-3 w-full border border-stone-800 shadow-md animate-fadeInDown rounded-[5px]',
|
||||
'bg-gradient-to-tr from-transparent to-sky-700/60 bg-stone-900/70',
|
||||
)}
|
||||
>
|
||||
<div className="flex gap-3">
|
||||
<i className={clsx('pi text-yellow-500 text-2xl', 'relative top-[2px]', ICONS[ping.type])}></i>
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<div className="m-0 font-semibold text-base text-white">{TITLES[ping.type]}</div>
|
||||
|
||||
<div className="flex gap-1 items-center">
|
||||
{isShowSelectedSystem && (
|
||||
<>
|
||||
<SystemView systemId={selectedSystem} />
|
||||
<span className="pi pi-angle-double-right text-[10px] relative top-[1px] text-stone-400" />
|
||||
</>
|
||||
)}
|
||||
<SystemView systemId={ping.solar_system_id} />
|
||||
{isShowSelectedSystem && (
|
||||
<WdImgButton
|
||||
className={clsx(PrimeIcons.QUESTION_CIRCLE, 'ml-[2px] relative top-[-2px] !text-[10px]')}
|
||||
tooltip={{
|
||||
position: TooltipPosition.top,
|
||||
content: (
|
||||
<div className="flex flex-col gap-1">
|
||||
The settings for the route are taken from the Routes settings and can be configured
|
||||
through them.
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-end">
|
||||
<CharacterCardById className="" characterId={ping.character_eve_id} simpleMode />
|
||||
<TimeAgo timestamp={ping.inserted_at.toString()} className="text-stone-400 text-[11px]" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedSystem != null && <PingRoute />}
|
||||
|
||||
<p className="m-0 text-[13px] text-stone-200 min-h-[20px] pr-[16px]">{message.detail}</p>
|
||||
</div>
|
||||
|
||||
<WdImgButton
|
||||
className={clsx(PrimeIcons.TIMES, 'hover:text-red-400 mt-[3px]')}
|
||||
tooltip={CLOSE_TOOLTIP_PROPS}
|
||||
onClick={handleClickHide}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/*Button bar*/}
|
||||
<div className="flex justify-end items-center gap-2 h-0 relative top-[-8px]">
|
||||
<WdImgButton
|
||||
className={clsx('pi-compass', 'hover:text-red-400 mt-[3px]')}
|
||||
tooltip={NAVIGATE_TOOLTIP_PROPS}
|
||||
onClick={navigateTo}
|
||||
/>
|
||||
|
||||
{/*@ts-ignore*/}
|
||||
<div ref={cpRemoveBtnRef}>
|
||||
<WdImgButton
|
||||
className={clsx('pi-trash', 'text-red-400 hover:text-red-300')}
|
||||
tooltip={DELETE_TOOLTIP_PROPS}
|
||||
onClick={handleShowCP}
|
||||
/>
|
||||
</div>
|
||||
{/* TODO ADD solar system menu*/}
|
||||
{/*<WdImgButton*/}
|
||||
{/* className={clsx('pi-map-marker', 'hover:text-red-400 mt-[3px]')}*/}
|
||||
{/* tooltip={TOOLTIP_WAYPOINT_PROPS}*/}
|
||||
{/* onClick={handleClickHide}*/}
|
||||
{/*/>*/}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
></Toast>
|
||||
|
||||
<Button
|
||||
icon="pi pi-bell"
|
||||
severity="warning"
|
||||
aria-label="Notification"
|
||||
size="small"
|
||||
className="w-[33px] h-[33px]"
|
||||
outlined
|
||||
onClick={handleClickShow}
|
||||
disabled={isShow}
|
||||
/>
|
||||
|
||||
<ConfirmPopup
|
||||
target={cpRemoveBtnRef.current}
|
||||
visible={cpRemoveVisible}
|
||||
onHide={handleHideCP}
|
||||
message="Are you sure you want to delete ping?"
|
||||
icon="pi pi-exclamation-triangle text-orange-400"
|
||||
accept={removePing}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './PingsInterface.tsx';
|
||||
@@ -1,26 +1,23 @@
|
||||
import { useCallback, useRef, useMemo } from 'react';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CommandLinkSignatureToSystem } from '@/hooks/Mapper/types';
|
||||
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
|
||||
import { SHOW_DESCRIPTION_COLUMN_SETTING } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatures';
|
||||
import {
|
||||
Setting,
|
||||
COSMIC_SIGNATURE,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
|
||||
import { SignatureGroup } from '@/hooks/Mapper/types';
|
||||
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo';
|
||||
import { getWhSize } from '@/hooks/Mapper/helpers/getWhSize';
|
||||
import { useSystemInfo } from '@/hooks/Mapper/components/hooks';
|
||||
import {
|
||||
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;
|
||||
|
||||
@@ -29,11 +26,11 @@ interface SystemLinkSignatureDialogProps {
|
||||
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 {
|
||||
@@ -52,7 +49,9 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
||||
ref.current = { outCommand };
|
||||
|
||||
// Get system info for the target system
|
||||
const { staticInfo: targetSystemInfo } = useSystemInfo({ systemId: `${data.solar_system_target}` });
|
||||
const { staticInfo: targetSystemInfo, dynamicInfo: targetSystemDynamicInfo } = useSystemInfo({
|
||||
systemId: `${data.solar_system_target}`,
|
||||
});
|
||||
|
||||
// Get the system class group for the target system
|
||||
const targetSystemClassGroup = useMemo(() => {
|
||||
@@ -163,6 +162,12 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
||||
[data, setVisible, wormholes],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!targetSystemDynamicInfo) {
|
||||
handleHide();
|
||||
}
|
||||
}, [targetSystemDynamicInfo]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
header="Select signature to link"
|
||||
@@ -175,7 +180,7 @@ 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}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { InputTextarea } from 'primereact/inputtextarea';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { PingType } from '@/hooks/Mapper/types/ping.ts';
|
||||
import { SystemView } from '@/hooks/Mapper/components/ui-kit';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const PING_TITLES = {
|
||||
[PingType.Rally]: 'RALLY',
|
||||
[PingType.Alert]: 'ALERT',
|
||||
};
|
||||
|
||||
interface SystemPingDialogProps {
|
||||
systemId: string;
|
||||
type: PingType;
|
||||
visible: boolean;
|
||||
setVisible: (visible: boolean) => void;
|
||||
}
|
||||
|
||||
export const SystemPingDialog = ({ systemId, type, visible, setVisible }: SystemPingDialogProps) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
|
||||
const [message, setMessage] = useState('');
|
||||
const inputRef = useRef<HTMLTextAreaElement>();
|
||||
|
||||
const ref = useRef({ message, outCommand, systemId, type });
|
||||
ref.current = { message, outCommand, systemId, type };
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
const { message, outCommand, systemId, type } = ref.current;
|
||||
|
||||
outCommand({
|
||||
type: OutCommand.addPing,
|
||||
data: {
|
||||
solar_system_id: systemId,
|
||||
type,
|
||||
message,
|
||||
},
|
||||
});
|
||||
setVisible(false);
|
||||
}, [setVisible]);
|
||||
|
||||
const onShow = useCallback(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
header={
|
||||
<div className="flex gap-1 text-[13px] items-center text-stone-300">
|
||||
<div>Ping:{` `}</div>
|
||||
<div
|
||||
className={clsx({
|
||||
['text-cyan-400']: type === PingType.Rally,
|
||||
})}
|
||||
>
|
||||
{PING_TITLES[type]}
|
||||
</div>
|
||||
<div className="text-[11px]">in</div> <SystemView systemId={systemId} className="relative top-[1px]" />
|
||||
</div>
|
||||
}
|
||||
visible={visible}
|
||||
draggable={false}
|
||||
style={{ width: '450px' }}
|
||||
onShow={onShow}
|
||||
onHide={() => {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
<form onSubmit={handleSave}>
|
||||
<div className="flex flex-col gap-3 px-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[11px]" htmlFor="username">
|
||||
Message
|
||||
</label>
|
||||
<InputTextarea
|
||||
// @ts-ignore
|
||||
ref={inputRef}
|
||||
autoResize
|
||||
rows={3}
|
||||
cols={30}
|
||||
value={message}
|
||||
onChange={e => setMessage(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button onClick={handleSave} size="small" severity="danger" label="Ping!"></Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './SystemPingDialog';
|
||||
@@ -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,14 +2,15 @@ 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;
|
||||
windowId?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
contentClassName?: string;
|
||||
} & WithChildren;
|
||||
|
||||
export const Widget = ({ label, children, windowId }: WidgetProps) => {
|
||||
export const Widget = ({ label, children, windowId, contentClassName }: WidgetProps) => {
|
||||
return (
|
||||
<div
|
||||
data-window-id={windowId}
|
||||
@@ -34,7 +35,7 @@ export const Widget = ({ label, children, windowId }: 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();
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from './Widget';
|
||||
export * from './SystemSettingsDialog';
|
||||
export * from './SystemCustomLabelDialog';
|
||||
export * from './SystemLinkSignatureDialog';
|
||||
export * from './PingsInterface';
|
||||
|
||||
@@ -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: 10 },
|
||||
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',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
|
||||
|
||||
export const sortCharacters = (a: CharacterTypeRaw & WithIsOwnCharacter, b: CharacterTypeRaw & WithIsOwnCharacter) => {
|
||||
if (a.online === b.online) {
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
|
||||
if (a.online !== b.online) {
|
||||
return a.online && !b.online ? -1 : 1;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
@@ -1,8 +1,8 @@
|
||||
import classes from './LocalCharactersItemTemplate.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import { CharacterCard } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { CharItemProps } from '@/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components';
|
||||
import { CharacterCard } from '@/hooks/Mapper/components/ui-kit';
|
||||
import clsx from 'clsx';
|
||||
import { VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
||||
import classes from './LocalCharactersItemTemplate.module.scss';
|
||||
|
||||
export type LocalCharactersItemTemplateProps = { showShipName: boolean } & CharItemProps &
|
||||
VirtualScrollerTemplateOptions;
|
||||
@@ -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 showShip {...options} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,79 +1,31 @@
|
||||
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 } 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));
|
||||
},
|
||||
},
|
||||
);
|
||||
// INFO: this component have imperative handler but now it not using.
|
||||
export const RoutesProvider = forwardRef<RoutesImperativeHandle, MapProviderProps>(
|
||||
({ children, ...props } /*, ref*/) => {
|
||||
// useImperativeHandle(ref, () => ({}));
|
||||
|
||||
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,16 +2,16 @@ import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import {
|
||||
LayoutEventBlocker,
|
||||
SystemViewStandalone,
|
||||
LoadingWrapper,
|
||||
SystemView,
|
||||
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 { getSystemById } from '@/hooks/Mapper/helpers/getSystemById.ts';
|
||||
|
||||
import { forwardRef, MouseEvent, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import classes from './RoutesWidget.module.scss';
|
||||
import { useLoadRoutes } from './hooks';
|
||||
import { RoutesList } from './RoutesList';
|
||||
import clsx from 'clsx';
|
||||
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
||||
@@ -25,7 +25,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,45 +39,31 @@ 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, loading } = useRouteProvider();
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
|
||||
const { loading } = useLoadRoutes();
|
||||
|
||||
const { systems: systemStatics, loadSystems, lastUpdateKey } = useLoadSystemStatic({ systems: hubs ?? [] });
|
||||
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers({
|
||||
outCommand,
|
||||
hubs,
|
||||
});
|
||||
|
||||
const preparedHubs = useMemo(() => {
|
||||
return hubs.map(x => {
|
||||
const sys = getSystemById(systems, x.toString());
|
||||
|
||||
return { ...systemStatics.get(parseInt(x))!, ...(sys && { customName: sys.name ?? '' }) };
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [hubs, systems, systemStatics, lastUpdateKey]);
|
||||
const { systems: systemStatics, loadSystems } = useLoadSystemStatic({ systems: hubs ?? [] });
|
||||
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers();
|
||||
|
||||
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 +86,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,12 +110,10 @@ 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)!;
|
||||
|
||||
// TODO do not delte this console log
|
||||
// TODO do not delete this console log
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('JOipP', `Check sys [${route.destination}]:`, sys);
|
||||
|
||||
@@ -132,15 +123,19 @@ 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
|
||||
key={route.destination}
|
||||
<SystemView
|
||||
systemId={route.destination.toString()}
|
||||
className={clsx('select-none text-center cursor-context-menu')}
|
||||
hideRegion
|
||||
compact
|
||||
{...sys}
|
||||
showCustomName
|
||||
/>
|
||||
</div>
|
||||
<div className="text-right pl-1">{route.has_connection ? route.systems?.length ?? 2 : ''}</div>
|
||||
@@ -151,7 +146,7 @@ export const RoutesWidgetContent = () => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</LoadingWrapper>
|
||||
|
||||
<ContextMenuSystemInfo
|
||||
hubs={hubs}
|
||||
@@ -165,15 +160,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 +183,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 +215,7 @@ export const RoutesWidgetComp = () => {
|
||||
className={PrimeIcons.SLIDERS_H}
|
||||
onClick={() => setRouteSettingsVisible(true)}
|
||||
tooltip={{
|
||||
position: TooltipPosition.top,
|
||||
content: 'Click here to open Routes settings',
|
||||
}}
|
||||
/>
|
||||
@@ -251,10 +236,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,8 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import {
|
||||
RoutesType,
|
||||
useRouteProvider,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
|
||||
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { LoadRoutesCommand } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
|
||||
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||
|
||||
function usePrevious<T>(value: T): T | undefined {
|
||||
const ref = useRef<T>();
|
||||
@@ -16,13 +14,25 @@ function usePrevious<T>(value: T): T | undefined {
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
export const useLoadRoutes = () => {
|
||||
type UseLoadRoutesProps = {
|
||||
loadRoutesCommand: LoadRoutesCommand;
|
||||
hubs: string[];
|
||||
routesList: RoutesList | undefined;
|
||||
data: RoutesType;
|
||||
deps?: unknown[];
|
||||
};
|
||||
|
||||
export const useLoadRoutes = ({
|
||||
data: routesSettings,
|
||||
loadRoutesCommand,
|
||||
hubs,
|
||||
routesList,
|
||||
deps = [],
|
||||
}: UseLoadRoutesProps) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { data: routesSettings } = useRouteProvider();
|
||||
|
||||
const {
|
||||
outCommand,
|
||||
data: { selectedSystems, hubs, systems, connections },
|
||||
data: { selectedSystems, systems, connections },
|
||||
} = useMapRootState();
|
||||
|
||||
const prevSys = usePrevious(systems);
|
||||
@@ -31,17 +41,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;
|
||||
@@ -61,7 +70,8 @@ export const useLoadRoutes = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
.map(x => routesSettings[x]),
|
||||
...deps,
|
||||
]);
|
||||
|
||||
return { loading, loadRoutes };
|
||||
return { loading, loadRoutes, setLoading };
|
||||
};
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
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;
|
||||
loading: boolean;
|
||||
|
||||
addHubCommand: AddHubCommand;
|
||||
toggleHubCommand: ToggleHubCommand;
|
||||
isRestricted?: boolean;
|
||||
};
|
||||
|
||||
export type RoutesProviderInnerProps = RoutesWidgetProps;
|
||||
|
||||
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,106 +0,0 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { SystemKillsContent } from './SystemKillsContent/SystemKillsContent';
|
||||
import { KillsHeader } from './components/SystemKillsHeader';
|
||||
import { useKillsWidgetSettings } from './hooks/useKillsWidgetSettings';
|
||||
import { useSystemKills } from './hooks/useSystemKills';
|
||||
import { KillsSettingsDialog } from './components/SystemKillsSettingsDialog';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace';
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
|
||||
export const SystemKills: React.FC = React.memo(() => {
|
||||
const {
|
||||
data: { selectedSystems, systems, isSubscriptionActive },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const [systemId] = selectedSystems || [];
|
||||
const [settingsDialogVisible, setSettingsDialogVisible] = useState(false);
|
||||
|
||||
const systemNameMap = useMemo(() => {
|
||||
const map: Record<string, string> = {};
|
||||
systems.forEach(sys => {
|
||||
map[sys.id] = sys.temporary_name || sys.name || '???';
|
||||
});
|
||||
return map;
|
||||
}, [systems]);
|
||||
|
||||
const systemBySolarSystemId = useMemo(() => {
|
||||
const map: Record<number, SolarSystemRawType> = {};
|
||||
systems.forEach(sys => {
|
||||
if (sys.system_static_info?.solar_system_id != null) {
|
||||
map[sys.system_static_info.solar_system_id] = sys;
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}, [systems]);
|
||||
|
||||
const [settings] = useKillsWidgetSettings();
|
||||
const visible = settings.showAll;
|
||||
|
||||
const { kills, isLoading, error } = useSystemKills({
|
||||
systemId,
|
||||
outCommand,
|
||||
showAllVisible: visible,
|
||||
sinceHours: settings.timeRange,
|
||||
});
|
||||
|
||||
const isNothingSelected = !systemId && !visible;
|
||||
const showLoading = isLoading && kills.length === 0;
|
||||
|
||||
const filteredKills = useMemo(() => {
|
||||
if (!settings.whOnly || !visible) return kills;
|
||||
return kills.filter(kill => {
|
||||
const system = systemBySolarSystemId[kill.solar_system_id];
|
||||
if (!system) {
|
||||
console.warn(`System with id ${kill.solar_system_id} not found.`);
|
||||
return false;
|
||||
}
|
||||
return isWormholeSpace(system.system_static_info.system_class);
|
||||
});
|
||||
}, [kills, settings.whOnly, systemBySolarSystemId, visible]);
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<Widget label={<KillsHeader systemId={systemId} onOpenSettings={() => setSettingsDialogVisible(true)} />}>
|
||||
{!isSubscriptionActive ? (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<span className="select-none text-center text-stone-400/80 text-sm">
|
||||
Kills available with 'Active' map subscription only (contact map administrators)
|
||||
</span>
|
||||
</div>
|
||||
) : isNothingSelected ? (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<span className="select-none text-center text-stone-400/80 text-sm">
|
||||
No system selected (or toggle "Show all systems")
|
||||
</span>
|
||||
</div>
|
||||
) : showLoading ? (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<span className="select-none text-center text-stone-400/80 text-sm">Loading Kills...</span>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<span className="select-none text-center text-red-400 text-sm">{error}</span>
|
||||
</div>
|
||||
) : !filteredKills || filteredKills.length === 0 ? (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<span className="select-none text-center text-stone-400/80 text-sm">No kills found</span>
|
||||
</div>
|
||||
) : (
|
||||
<SystemKillsContent
|
||||
kills={filteredKills}
|
||||
systemNameMap={systemNameMap}
|
||||
onlyOneSystem={!visible}
|
||||
timeRange={settings.timeRange}
|
||||
/>
|
||||
)}
|
||||
</Widget>
|
||||
|
||||
{settingsDialogVisible && <KillsSettingsDialog visible setVisible={setSettingsDialogVisible} />}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
SystemKills.displayName = 'SystemKills';
|
||||
@@ -1,37 +0,0 @@
|
||||
// Custom scrollbar styling is now handled by the global custom-scrollbar class
|
||||
.scrollerContent {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
// VirtualScroller specific styles that can't be handled with Tailwind
|
||||
.VirtualScroller {
|
||||
overflow: hidden !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
height: 100% !important;
|
||||
|
||||
// Target this specific VirtualScroller instance
|
||||
&:global(.p-virtualscroller) {
|
||||
height: 100% !important;
|
||||
|
||||
:global(.p-virtualscroller-content) {
|
||||
height: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix for PrimeReact VirtualScroller - these need to be global
|
||||
:global {
|
||||
.p-virtualscroller {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.p-virtualscroller-content {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
||||
import { VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
||||
import { KillRow } from './SystemKillsRow';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export function KillItemTemplate(
|
||||
systemNameMap: Record<string, string>,
|
||||
onlyOneSystem: boolean,
|
||||
kill: DetailedKill,
|
||||
options: VirtualScrollerTemplateOptions,
|
||||
) {
|
||||
const systemIdStr = String(kill.solar_system_id);
|
||||
const systemName = systemNameMap[systemIdStr] || `System ${systemIdStr}`;
|
||||
|
||||
return (
|
||||
<div style={{ height: `${options.props.itemSize}px` }} className={clsx({ 'bg-gray-900': options.odd })}>
|
||||
<KillRow killDetails={kill} systemName={systemName} onlyOneSystem={onlyOneSystem} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
||||
import { KillRowDetail } from './KillRowDetail.tsx';
|
||||
|
||||
export interface KillRowProps {
|
||||
killDetails: DetailedKill;
|
||||
systemName: string;
|
||||
onlyOneSystem?: boolean;
|
||||
}
|
||||
|
||||
const KillRowComponent: React.FC<KillRowProps> = ({ killDetails, systemName, onlyOneSystem = false }) => {
|
||||
return <KillRowDetail killDetails={killDetails} systemName={systemName} onlyOneSystem={onlyOneSystem} />;
|
||||
};
|
||||
|
||||
export const KillRow = React.memo(KillRowComponent);
|
||||
@@ -1 +0,0 @@
|
||||
export * from './SystemKills';
|
||||
@@ -15,7 +15,7 @@ export const SignatureView = ({ signature, showCharacterPortrait = false }: Sign
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="flex gap-2 items-center px-2">
|
||||
{renderIcon(signature)}
|
||||
<div>{signature?.eve_id}</div>
|
||||
<div>{groupDisplay}</div>
|
||||
|
||||
@@ -1,108 +1,113 @@
|
||||
import React from 'react';
|
||||
import { SystemView, WdCheckbox, WdImgButton, TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
|
||||
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 { InfoDrawer, LayoutEventBlocker } from '@/hooks/Mapper/components/ui-kit';
|
||||
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 = {
|
||||
systemId: string;
|
||||
isNotSelectedSystem: boolean;
|
||||
sigCount: number;
|
||||
isCompact: boolean;
|
||||
lazyDeleteValue: boolean;
|
||||
onLazyDeleteChange: (checked: boolean) => void;
|
||||
pendingCount: number;
|
||||
pendingTimeRemaining?: number; // Time remaining in ms
|
||||
undoCountdown?: number;
|
||||
onUndoClick: () => void;
|
||||
onSettingsClick: () => void;
|
||||
};
|
||||
|
||||
function HeaderImpl({
|
||||
systemId,
|
||||
isNotSelectedSystem,
|
||||
export const SystemSignaturesHeader = ({
|
||||
sigCount,
|
||||
isCompact,
|
||||
lazyDeleteValue,
|
||||
onLazyDeleteChange,
|
||||
pendingCount,
|
||||
pendingTimeRemaining,
|
||||
undoCountdown,
|
||||
onUndoClick,
|
||||
onSettingsClick,
|
||||
}: HeaderProps) {
|
||||
// Format time remaining as seconds
|
||||
const formatTimeRemaining = () => {
|
||||
if (!pendingTimeRemaining) return '';
|
||||
const seconds = Math.ceil(pendingTimeRemaining / 1000);
|
||||
return ` (${seconds}s remaining)`;
|
||||
};
|
||||
}: 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);
|
||||
|
||||
return (
|
||||
<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>
|
||||
<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>
|
||||
<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 deletions (${pendingCount})${undoCountdown && undoCountdown > 0 ? ` — ${undoCountdown}s left` : ''}`,
|
||||
}}
|
||||
onClick={onUndoClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
{pendingCount > 0 && (
|
||||
<WdImgButton
|
||||
className={PrimeIcons.UNDO}
|
||||
style={{ color: 'red' }}
|
||||
className={PrimeIcons.QUESTION_CIRCLE}
|
||||
tooltip={{
|
||||
content: `Undo pending changes (${pendingCount})${formatTimeRemaining()}`,
|
||||
position: TooltipPosition.top,
|
||||
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>
|
||||
),
|
||||
}}
|
||||
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>
|
||||
<WdImgButton className={PrimeIcons.SLIDERS_H} onClick={onSettingsClick} />
|
||||
</LayoutEventBlocker>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const SystemSignaturesHeader = React.memo(HeaderImpl);
|
||||
};
|
||||
|
||||
@@ -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,29 +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';
|
||||
import { Dropdown } from 'primereact/dropdown';
|
||||
|
||||
export type Setting = {
|
||||
key: string;
|
||||
name: string;
|
||||
value: boolean | number;
|
||||
isFilter?: boolean;
|
||||
options?: { label: string; value: number }[];
|
||||
};
|
||||
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -34,21 +23,18 @@ 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: typeof item.value === 'boolean' ? !item.value : item.value } : item,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const handleDropdownChange = (key: string, value: number) => {
|
||||
setSettings(prevState => prevState.map(item => (item.key === key ? { ...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(() => {
|
||||
@@ -56,17 +42,15 @@ export const SystemSignatureSettingsDialog = ({
|
||||
}, [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={setting.value}
|
||||
options={setting.options.map(opt => ({
|
||||
...opt,
|
||||
label: opt.label.split(' ')[0], // Just take the first part (e.g., "0s" from "Immediate (0s)")
|
||||
}))}
|
||||
onChange={e => handleDropdownChange(setting.key, e.value)}
|
||||
value={val}
|
||||
options={setting.options}
|
||||
onChange={e => handleSettingsChange(setting, e.value)}
|
||||
className="w-40"
|
||||
/>
|
||||
</div>
|
||||
@@ -77,8 +61,8 @@ export const SystemSignatureSettingsDialog = ({
|
||||
<PrettySwitchbox
|
||||
key={setting.key}
|
||||
label={setting.name}
|
||||
checked={!!setting.value}
|
||||
setChecked={() => handleSettingsChange(setting.key)}
|
||||
checked={!!val}
|
||||
setChecked={() => handleSettingsChange(setting)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -87,26 +71,24 @@ export const SystemSignatureSettingsDialog = ({
|
||||
<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(renderSetting)}</div>
|
||||
</TabPanel>
|
||||
<TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
|
||||
<div className="w-full h-full flex flex-col gap-1">
|
||||
{userSettings.filter(setting => !setting.options).map(renderSetting)}
|
||||
{userSettings.some(setting => setting.options) && (
|
||||
<div className="my-2 border-t border-stone-700/50"></div>
|
||||
)}
|
||||
{userSettings.filter(setting => setting.options).map(renderSetting)}
|
||||
</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,244 +1,173 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
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 { SystemSignatureSettingsDialog } from './SystemSignatureSettingsDialog';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks';
|
||||
import { SystemSignaturesHeader } from './SystemSignatureHeader';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks/useHotkey';
|
||||
import {
|
||||
COMPACT_MAX_WIDTH,
|
||||
DELETION_TIMING_DEFAULT,
|
||||
DELETION_TIMING_EXTENDED,
|
||||
DELETION_TIMING_IMMEDIATE,
|
||||
DELETION_TIMING_SETTING_KEY,
|
||||
} from './constants';
|
||||
import { renderHeaderLabel } from './renders';
|
||||
SETTINGS_KEYS,
|
||||
SETTINGS_VALUES,
|
||||
SIGNATURE_SETTING_STORE_KEY,
|
||||
SIGNATURE_WINDOW_ID,
|
||||
SignatureSettingsType,
|
||||
SIGNATURES_DELETION_TIMING,
|
||||
SIGNATURE_DELETION_TIMEOUTS,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers';
|
||||
|
||||
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v5_5';
|
||||
export const SIGNATURE_WINDOW_ID = 'system_signatures_window';
|
||||
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';
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const DELETION_TIMING_SETTING = DELETION_TIMING_SETTING_KEY;
|
||||
export const COLOR_BY_TYPE_SETTING = 'COLOR_BY_TYPE_SETTING';
|
||||
export const SHOW_CHARACTER_PORTRAIT_SETTING = 'SHOW_CHARACTER_PORTRAIT_SETTING';
|
||||
/**
|
||||
* Custom hook for managing pending signature deletions and undo countdown.
|
||||
*/
|
||||
function useSignatureUndo(
|
||||
systemId: string | undefined,
|
||||
settings: SignatureSettingsType,
|
||||
outCommand: OutCommandHandler,
|
||||
) {
|
||||
const [countdown, setCountdown] = useState<number>(0);
|
||||
const [pendingIds, setPendingIds] = useState<Set<string>>(new Set());
|
||||
const intervalRef = useRef<number | null>(null);
|
||||
|
||||
// Extend the Setting type to include options for dropdown settings
|
||||
type ExtendedSetting = Setting & {
|
||||
options?: { label: string; value: number }[];
|
||||
};
|
||||
|
||||
const SETTINGS: ExtendedSetting[] = [
|
||||
{ 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: SHOW_CHARACTER_PORTRAIT_SETTING, name: 'Show Character Portrait in Tooltip', 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: COLOR_BY_TYPE_SETTING, name: 'Color Signatures by Type', value: false, isFilter: false },
|
||||
{
|
||||
key: DELETION_TIMING_SETTING,
|
||||
name: 'Deletion Timing',
|
||||
value: DELETION_TIMING_DEFAULT,
|
||||
isFilter: false,
|
||||
options: [
|
||||
{ label: '0s', value: DELETION_TIMING_IMMEDIATE },
|
||||
{ label: '10s', value: DELETION_TIMING_DEFAULT },
|
||||
{ label: '30s', value: DELETION_TIMING_EXTENDED },
|
||||
],
|
||||
},
|
||||
|
||||
{ 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 },
|
||||
];
|
||||
|
||||
function getDefaultSettings(): ExtendedSetting[] {
|
||||
return [...SETTINGS];
|
||||
}
|
||||
|
||||
function getInitialSettings(): ExtendedSetting[] {
|
||||
const stored = localStorage.getItem(SIGNATURE_SETTINGS_KEY);
|
||||
if (stored) {
|
||||
try {
|
||||
const parsedSettings = JSON.parse(stored) as ExtendedSetting[];
|
||||
// Merge stored settings with default settings to ensure new settings are included
|
||||
const defaultSettings = getDefaultSettings();
|
||||
const mergedSettings = defaultSettings.map(defaultSetting => {
|
||||
const storedSetting = parsedSettings.find(s => s.key === defaultSetting.key);
|
||||
if (storedSetting) {
|
||||
// Keep the stored value but ensure options are from default settings
|
||||
return {
|
||||
...defaultSetting,
|
||||
value: storedSetting.value,
|
||||
};
|
||||
}
|
||||
return defaultSetting;
|
||||
});
|
||||
return mergedSettings;
|
||||
} catch (error) {
|
||||
console.error('Error parsing stored settings', error);
|
||||
}
|
||||
}
|
||||
return getDefaultSettings();
|
||||
}
|
||||
|
||||
export const SystemSignatures: React.FC = () => {
|
||||
const {
|
||||
data: { selectedSystems },
|
||||
} = useMapRootState();
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const [currentSettings, setCurrentSettings] = useState<ExtendedSetting[]>(getInitialSettings);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(currentSettings));
|
||||
}, [currentSettings]);
|
||||
|
||||
const [sigCount, setSigCount] = useState<number>(0);
|
||||
const [pendingSigs, setPendingSigs] = useState<SystemSignature[]>([]);
|
||||
const [minPendingTimeRemaining, setMinPendingTimeRemaining] = useState<number | undefined>(undefined);
|
||||
|
||||
const undoPendingFnRef = useRef<() => void>(() => {});
|
||||
|
||||
const handleSigCountChange = useCallback((count: number) => {
|
||||
setSigCount(count);
|
||||
}, []);
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
const isNotSelectedSystem = selectedSystems.length !== 1;
|
||||
|
||||
const lazyDeleteValue = useMemo(() => {
|
||||
const setting = currentSettings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING);
|
||||
return typeof setting?.value === 'boolean' ? setting.value : false;
|
||||
}, [currentSettings]);
|
||||
|
||||
const deletionTimingValue = useMemo(() => {
|
||||
const setting = currentSettings.find(setting => setting.key === DELETION_TIMING_SETTING);
|
||||
return typeof setting?.value === 'number' ? setting.value : DELETION_TIMING_IMMEDIATE;
|
||||
}, [currentSettings]);
|
||||
|
||||
const colorByTypeValue = useMemo(() => {
|
||||
const setting = currentSettings.find(setting => setting.key === COLOR_BY_TYPE_SETTING);
|
||||
return typeof setting?.value === 'boolean' ? setting.value : false;
|
||||
}, [currentSettings]);
|
||||
|
||||
const handleSettingsChange = useCallback((newSettings: Setting[]) => {
|
||||
setCurrentSettings(newSettings as ExtendedSetting[]);
|
||||
setVisible(false);
|
||||
}, []);
|
||||
|
||||
const handleLazyDeleteChange = useCallback((value: boolean) => {
|
||||
setCurrentSettings(prevSettings =>
|
||||
prevSettings.map(setting => (setting.key === LAZY_DELETE_SIGNATURES_SETTING ? { ...setting, value } : setting)),
|
||||
);
|
||||
}, []);
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const isCompact = useMaxWidth(containerRef, COMPACT_MAX_WIDTH);
|
||||
|
||||
useHotkey(true, ['z'], event => {
|
||||
if (pendingSigs.length > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
undoPendingFnRef.current();
|
||||
setPendingSigs([]);
|
||||
setMinPendingTimeRemaining(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
const handleUndoClick = useCallback(() => {
|
||||
undoPendingFnRef.current();
|
||||
setPendingSigs([]);
|
||||
setMinPendingTimeRemaining(undefined);
|
||||
}, []);
|
||||
|
||||
const handleSettingsButtonClick = useCallback(() => {
|
||||
setVisible(true);
|
||||
}, []);
|
||||
|
||||
const handlePendingChange = useCallback((newPending: SystemSignature[], newUndo: () => void) => {
|
||||
setPendingSigs(prev => {
|
||||
if (newPending.length === prev.length && newPending.every(np => prev.some(pp => pp.eve_id === np.eve_id))) {
|
||||
return prev;
|
||||
}
|
||||
return newPending;
|
||||
const addDeleted = useCallback((ids: string[]) => {
|
||||
setPendingIds(prev => {
|
||||
const next = new Set(prev);
|
||||
ids.forEach(id => next.add(id));
|
||||
return next;
|
||||
});
|
||||
undoPendingFnRef.current = newUndo;
|
||||
}, []);
|
||||
|
||||
// Calculate the minimum time remaining for any pending signature
|
||||
// kick off or clear countdown whenever pendingIds changes
|
||||
useEffect(() => {
|
||||
if (pendingSigs.length === 0) {
|
||||
setMinPendingTimeRemaining(undefined);
|
||||
// clear any existing timer
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
|
||||
if (pendingIds.size === 0) {
|
||||
setCountdown(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const calculateTimeRemaining = () => {
|
||||
const now = Date.now();
|
||||
let minTime: number | undefined = undefined;
|
||||
// determine timeout from settings
|
||||
const timingKey = Number(settings[SETTINGS_KEYS.DELETION_TIMING] ?? SIGNATURES_DELETION_TIMING.DEFAULT);
|
||||
const timeoutMs =
|
||||
Number(SIGNATURE_DELETION_TIMEOUTS[timingKey as keyof typeof SIGNATURE_DELETION_TIMEOUTS]) || 10000;
|
||||
setCountdown(Math.ceil(timeoutMs / 1000));
|
||||
|
||||
pendingSigs.forEach(sig => {
|
||||
const extendedSig = sig as unknown as { pendingUntil?: number };
|
||||
if (extendedSig.pendingUntil && (minTime === undefined || extendedSig.pendingUntil - now < minTime)) {
|
||||
minTime = extendedSig.pendingUntil - now;
|
||||
// start new interval
|
||||
intervalRef.current = window.setInterval(() => {
|
||||
setCountdown(prev => {
|
||||
if (prev <= 1) {
|
||||
clearInterval(intervalRef.current!);
|
||||
intervalRef.current = null;
|
||||
setPendingIds(new Set());
|
||||
return 0;
|
||||
}
|
||||
return prev - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
setMinPendingTimeRemaining(minTime && minTime > 0 ? minTime : undefined);
|
||||
return () => {
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [pendingIds, settings[SETTINGS_KEYS.DELETION_TIMING]]);
|
||||
|
||||
calculateTimeRemaining();
|
||||
const interval = setInterval(calculateTimeRemaining, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [pendingSigs]);
|
||||
// undo handler
|
||||
const handleUndo = useCallback(async () => {
|
||||
if (!systemId || pendingIds.size === 0) return;
|
||||
await outCommand({
|
||||
type: OutCommand.undoDeleteSignatures,
|
||||
data: { system_id: systemId, eve_ids: Array.from(pendingIds) },
|
||||
});
|
||||
setPendingIds(new Set());
|
||||
setCountdown(0);
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}, [systemId, pendingIds, outCommand]);
|
||||
|
||||
return {
|
||||
pendingIds,
|
||||
countdown,
|
||||
addDeleted,
|
||||
handleUndo,
|
||||
};
|
||||
}
|
||||
|
||||
export const SystemSignatures = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [sigCount, setSigCount] = useState(0);
|
||||
|
||||
const {
|
||||
data: { selectedSystems },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const [currentSettings, setCurrentSettings] = useLocalStorageState<SignatureSettingsType>(
|
||||
SIGNATURE_SETTING_STORE_KEY,
|
||||
{
|
||||
defaultValue: SETTINGS_VALUES,
|
||||
},
|
||||
);
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
const isSystemSelected = useMemo(() => selectedSystems.length === 1, [selectedSystems.length]);
|
||||
const { pendingIds, countdown, addDeleted, handleUndo } = useSignatureUndo(systemId, currentSettings, outCommand);
|
||||
|
||||
useHotkey(true, ['z', 'Z'], (event: KeyboardEvent) => {
|
||||
if (pendingIds.size > 0 && countdown > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleUndo();
|
||||
}
|
||||
});
|
||||
|
||||
const handleCountChange = useCallback((count: number) => {
|
||||
setSigCount(count);
|
||||
}, []);
|
||||
|
||||
const handleSettingsSave = useCallback(
|
||||
(newSettings: SignatureSettingsType) => {
|
||||
setCurrentSettings(newSettings);
|
||||
setVisible(false);
|
||||
},
|
||||
[setCurrentSettings],
|
||||
);
|
||||
|
||||
const handleLazyDeleteToggle = useCallback(
|
||||
(value: boolean) => {
|
||||
setCurrentSettings(prev => ({
|
||||
...prev,
|
||||
[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES]: value,
|
||||
}));
|
||||
},
|
||||
[setCurrentSettings],
|
||||
);
|
||||
|
||||
const openSettings = useCallback(() => setVisible(true), []);
|
||||
|
||||
return (
|
||||
<Widget
|
||||
label={
|
||||
<div ref={containerRef} className="w-full">
|
||||
{renderHeaderLabel({
|
||||
systemId,
|
||||
isNotSelectedSystem,
|
||||
isCompact,
|
||||
sigCount,
|
||||
lazyDeleteValue,
|
||||
pendingCount: pendingSigs.length,
|
||||
pendingTimeRemaining: minPendingTimeRemaining,
|
||||
onLazyDeleteChange: handleLazyDeleteChange,
|
||||
onUndoClick: handleUndoClick,
|
||||
onSettingsClick: handleSettingsButtonClick,
|
||||
})}
|
||||
</div>
|
||||
<SystemSignaturesHeader
|
||||
sigCount={sigCount}
|
||||
lazyDeleteValue={currentSettings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean}
|
||||
pendingCount={pendingIds.size}
|
||||
undoCountdown={countdown}
|
||||
onLazyDeleteChange={handleLazyDeleteToggle}
|
||||
onUndoClick={handleUndo}
|
||||
onSettingsClick={openSettings}
|
||||
/>
|
||||
}
|
||||
windowId={SIGNATURE_WINDOW_ID}
|
||||
>
|
||||
{isNotSelectedSystem ? (
|
||||
{!isSystemSelected ? (
|
||||
<div className="w-full h-full flex justify-center items-center select-none text-center text-stone-400/80 text-sm">
|
||||
System is not selected
|
||||
</div>
|
||||
@@ -246,18 +175,17 @@ export const SystemSignatures: React.FC = () => {
|
||||
<SystemSignaturesContent
|
||||
systemId={systemId}
|
||||
settings={currentSettings}
|
||||
onLazyDeleteChange={handleLazyDeleteChange}
|
||||
onCountChange={handleSigCountChange}
|
||||
onPendingChange={handlePendingChange}
|
||||
deletionTiming={deletionTimingValue}
|
||||
colorByType={colorByTypeValue}
|
||||
onLazyDeleteChange={handleLazyDeleteToggle}
|
||||
onCountChange={handleCountChange}
|
||||
onSignatureDeleted={addDeleted}
|
||||
/>
|
||||
)}
|
||||
|
||||
{visible && (
|
||||
<SystemSignatureSettingsDialog
|
||||
settings={currentSettings}
|
||||
onCancel={() => setVisible(false)}
|
||||
onSave={handleSettingsChange}
|
||||
onSave={handleSettingsSave}
|
||||
/>
|
||||
)}
|
||||
</Widget>
|
||||
|
||||
@@ -1,29 +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,
|
||||
getGroupIdByRawGroup,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants';
|
||||
import {
|
||||
SHOW_DESCRIPTION_COLUMN_SETTING,
|
||||
SHOW_UPDATED_COLUMN_SETTING,
|
||||
SHOW_CHARACTER_COLUMN_SETTING,
|
||||
SETTINGS_KEYS,
|
||||
SIGNATURE_WINDOW_ID,
|
||||
SHOW_CHARACTER_PORTRAIT_SETTING,
|
||||
} from '../SystemSignatures';
|
||||
SignatureSettingsType,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants';
|
||||
import { SignatureSettings } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings';
|
||||
import { TooltipPosition, WdTooltip, WdTooltipHandlers, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { ExtendedSystemSignature, SignatureGroup, SignatureKind, SystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
import { COSMIC_SIGNATURE } from '../SystemSignatureSettingsDialog';
|
||||
import {
|
||||
renderAddedTimeLeft,
|
||||
renderDescription,
|
||||
@@ -31,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;
|
||||
@@ -49,21 +51,17 @@ const SORT_DEFAULT_VALUES: SystemSignaturesSortSettings = {
|
||||
|
||||
interface SystemSignaturesContentProps {
|
||||
systemId: string;
|
||||
settings: { key: string; value: boolean | number }[];
|
||||
settings: SignatureSettingsType;
|
||||
hideLinkedSignatures?: boolean;
|
||||
selectable?: boolean;
|
||||
onSelect?: (signature: SystemSignature) => void;
|
||||
onLazyDeleteChange?: (value: boolean) => void;
|
||||
onCountChange?: (count: number) => void;
|
||||
onPendingChange?: (pending: ExtendedSystemSignature[], undo: () => void) => void;
|
||||
deletionTiming?: number;
|
||||
colorByType?: boolean;
|
||||
filterSignature?: (signature: SystemSignature) => boolean;
|
||||
onSignatureDeleted?: (deletedIds: string[]) => void;
|
||||
}
|
||||
|
||||
const headerInlineStyle = { padding: '2px', fontSize: '12px', lineHeight: '1.333' };
|
||||
|
||||
export function SystemSignaturesContent({
|
||||
export const SystemSignaturesContent = ({
|
||||
systemId,
|
||||
settings,
|
||||
hideLinkedSignatures,
|
||||
@@ -71,34 +69,43 @@ export function SystemSignaturesContent({
|
||||
onSelect,
|
||||
onLazyDeleteChange,
|
||||
onCountChange,
|
||||
onPendingChange,
|
||||
deletionTiming,
|
||||
colorByType,
|
||||
filterSignature,
|
||||
}: SystemSignaturesContentProps) {
|
||||
const { signatures, selectedSignatures, setSelectedSignatures, handleDeleteSelected, handleSelectAll, handlePaste } =
|
||||
useSystemSignaturesData({
|
||||
systemId,
|
||||
settings,
|
||||
onCountChange,
|
||||
onPendingChange,
|
||||
onLazyDeleteChange,
|
||||
deletionTiming,
|
||||
});
|
||||
onSignatureDeleted,
|
||||
}: 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 tableRef = useRef<HTMLDivElement>(null);
|
||||
const tooltipRef = useRef<WdTooltipHandlers>(null);
|
||||
const [hoveredSignature, setHoveredSignature] = useState<SystemSignature | null>(null);
|
||||
const {
|
||||
signatures,
|
||||
selectedSignatures,
|
||||
setSelectedSignatures,
|
||||
handleDeleteSelected,
|
||||
handleSelectAll,
|
||||
handlePaste,
|
||||
hasUnsupportedLanguage,
|
||||
} = useSystemSignaturesData({
|
||||
systemId,
|
||||
settings,
|
||||
onCountChange,
|
||||
onLazyDeleteChange,
|
||||
onSignatureDeleted,
|
||||
});
|
||||
|
||||
const isCompact = useMaxWidth(tableRef, COMPACT_MAX_WIDTH);
|
||||
const isMedium = useMaxWidth(tableRef, MEDIUM_MAX_WIDTH);
|
||||
|
||||
const { clipboardContent, setClipboardContent } = useClipboard();
|
||||
useEffect(() => {
|
||||
if (selectable) return;
|
||||
if (!clipboardContent?.text) return;
|
||||
@@ -109,6 +116,7 @@ export function SystemSignaturesContent({
|
||||
}, [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}"]`);
|
||||
|
||||
@@ -118,53 +126,52 @@ export function SystemSignaturesContent({
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (onSignatureDeleted && selectedSignatures.length > 0) {
|
||||
const deletedIds = selectedSignatures.map(s => s.eve_id);
|
||||
onSignatureDeleted(deletedIds);
|
||||
}
|
||||
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],
|
||||
[onSelect, selectable, setSelectedSignatures],
|
||||
);
|
||||
|
||||
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 showCharacterPortrait = settings.find(s => s.key === SHOW_CHARACTER_PORTRAIT_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 => {
|
||||
@@ -175,21 +182,61 @@ export function SystemSignaturesContent({
|
||||
if (hideLinkedSignatures && sig.linked_system) {
|
||||
return false;
|
||||
}
|
||||
const isCosmicSignature = sig.kind === COSMIC_SIGNATURE;
|
||||
|
||||
if (isCosmicSignature) {
|
||||
const showCosmic = settings.find(y => y.key === COSMIC_SIGNATURE)?.value;
|
||||
if (!showCosmic) return false;
|
||||
if (sig.group) {
|
||||
const preparedGroup = getGroupIdByRawGroup(sig.group);
|
||||
return enabledGroups.includes(preparedGroup);
|
||||
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],
|
||||
);
|
||||
|
||||
const mappedGroup = getGroupIdByRawGroup(sig.group);
|
||||
if (!mappedGroup) {
|
||||
return true; // If we can't determine the group, still show it
|
||||
}
|
||||
return enabledGroups.includes(mappedGroup);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return settings.find(y => y.key === sig.kind)?.value;
|
||||
}
|
||||
|
||||
return settings[sig.kind];
|
||||
});
|
||||
}, [signatures, hideLinkedSignatures, settings, enabledGroups, filterSignature]);
|
||||
}, [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">
|
||||
@@ -198,136 +245,130 @@ export function SystemSignaturesContent({
|
||||
No signatures
|
||||
</div>
|
||||
) : (
|
||||
<DataTable
|
||||
value={filteredSignatures}
|
||||
size="small"
|
||||
selectionMode="multiple"
|
||||
selection={selectedSignatures}
|
||||
metaKeySelection
|
||||
onSelectionChange={handleSelectSignatures}
|
||||
dataKey="eve_id"
|
||||
className="w-full select-none"
|
||||
resizableColumns={false}
|
||||
rowHover
|
||||
selectAll
|
||||
onRowDoubleClick={handleRowClick}
|
||||
sortField={sortSettings.sortField}
|
||||
sortOrder={sortSettings.sortOrder}
|
||||
onSort={e => setSortSettings({ sortField: e.sortField, sortOrder: e.sortOrder })}
|
||||
onRowMouseEnter={(e: DataTableRowMouseEvent) => {
|
||||
setHoveredSignature(e.data as SystemSignature);
|
||||
tooltipRef.current?.show(e.originalEvent);
|
||||
}}
|
||||
onRowMouseLeave={() => {
|
||||
setHoveredSignature(null);
|
||||
tooltipRef.current?.hide();
|
||||
}}
|
||||
rowClassName={rowData =>
|
||||
getSignatureRowClass(rowData as ExtendedSystemSignature, selectedSignatures, colorByType)
|
||||
}
|
||||
>
|
||||
<Column
|
||||
field="icon"
|
||||
header=""
|
||||
headerStyle={headerInlineStyle}
|
||||
body={sig => renderIcon(sig)}
|
||||
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
|
||||
/>
|
||||
<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 ?? ''}
|
||||
hidden={isCompact}
|
||||
sortable
|
||||
/>
|
||||
<Column
|
||||
field="info"
|
||||
header="Info"
|
||||
headerStyle={headerInlineStyle}
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: nameColumnWidth }}
|
||||
hidden={isCompact || isMedium}
|
||||
body={renderInfoColumn}
|
||||
/>
|
||||
{showDescriptionColumn && (
|
||||
<Column
|
||||
field="description"
|
||||
header="Description"
|
||||
headerStyle={headerInlineStyle}
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
hidden={isCompact}
|
||||
body={renderDescription}
|
||||
sortable
|
||||
/>
|
||||
<>
|
||||
{hasUnsupportedLanguage && (
|
||||
<div className="w-full flex justify-center items-center text-amber-500 text-xs p-1 bg-amber-950/20 border-b border-amber-800/30">
|
||||
<i className={PrimeIcons.EXCLAMATION_TRIANGLE + ' mr-1'} />
|
||||
Non-English signatures detected. Some signatures may not display correctly. Double-click to edit signature details.
|
||||
</div>
|
||||
)}
|
||||
<Column
|
||||
field="inserted_at"
|
||||
header="Added"
|
||||
headerStyle={headerInlineStyle}
|
||||
dataType="date"
|
||||
body={renderAddedTimeLeft}
|
||||
style={{ minWidth: 70, maxWidth: 80 }}
|
||||
bodyClassName="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 }}
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
/>
|
||||
)}
|
||||
|
||||
{showCharacterColumn && (
|
||||
<Column
|
||||
field="character_name"
|
||||
header="Character"
|
||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
></Column>
|
||||
)}
|
||||
|
||||
{!selectable && (
|
||||
<DataTable
|
||||
value={filteredSignatures}
|
||||
size="small"
|
||||
selectionMode="multiple"
|
||||
selection={selectedSignatures}
|
||||
metaKeySelection
|
||||
onSelectionChange={handleSelectSignatures}
|
||||
dataKey="eve_id"
|
||||
className="w-full select-none"
|
||||
resizableColumns={false}
|
||||
rowHover
|
||||
selectAll
|
||||
onRowDoubleClick={handleRowClick}
|
||||
sortField={sortSettings.sortField}
|
||||
sortOrder={sortSettings.sortOrder}
|
||||
onSort={handleSortSettings}
|
||||
onRowMouseEnter={onRowMouseEnter}
|
||||
onRowMouseLeave={onRowMouseLeave}
|
||||
// @ts-ignore
|
||||
rowClassName={getRowClassName}
|
||||
>
|
||||
<Column
|
||||
field="icon"
|
||||
header=""
|
||||
headerStyle={headerInlineStyle}
|
||||
body={() => (
|
||||
<div className="flex justify-end items-center gap-2 mr-[4px]">
|
||||
<WdTooltipWrapper content="Double-click a row to edit signature">
|
||||
<span className={PrimeIcons.PENCIL + ' text-[10px]'} />
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
)}
|
||||
body={renderColIcon}
|
||||
bodyClassName="p-0 px-1"
|
||||
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||
bodyClassName="p-0 pl-1 pr-2"
|
||||
/>
|
||||
)}
|
||||
</DataTable>
|
||||
<Column
|
||||
field="eve_id"
|
||||
header="Id"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: 72, minWidth: 72, width: 72 }}
|
||||
sortable
|
||||
/>
|
||||
<Column
|
||||
field="group"
|
||||
header="Group"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
|
||||
body={sig => sig.group ?? ''}
|
||||
hidden={isCompact}
|
||||
sortable
|
||||
/>
|
||||
<Column
|
||||
field="info"
|
||||
header="Info"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: nameColumnWidth }}
|
||||
hidden={isCompact || isMedium}
|
||||
body={renderInfoColumn}
|
||||
/>
|
||||
{showDescriptionColumn && (
|
||||
<Column
|
||||
field="description"
|
||||
header="Description"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
hidden={isCompact}
|
||||
body={renderDescription}
|
||||
sortable
|
||||
/>
|
||||
)}
|
||||
<Column
|
||||
field="inserted_at"
|
||||
header="Added"
|
||||
dataType="date"
|
||||
body={renderAddedTimeLeft}
|
||||
style={{ minWidth: 70, maxWidth: 80 }}
|
||||
bodyClassName="ssc-header text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
/>
|
||||
{showUpdatedColumn && (
|
||||
<Column
|
||||
field="updated_at"
|
||||
header="Updated"
|
||||
dataType="date"
|
||||
body={renderUpdatedTimeLeft}
|
||||
style={{ minWidth: 70, maxWidth: 80 }}
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
/>
|
||||
)}
|
||||
|
||||
{showCharacterColumn && (
|
||||
<Column
|
||||
field="character_name"
|
||||
header="Character"
|
||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
></Column>
|
||||
)}
|
||||
|
||||
{!selectable && (
|
||||
<Column
|
||||
header=""
|
||||
body={() => (
|
||||
<div className="flex justify-end items-center gap-2 mr-[4px]">
|
||||
<WdTooltipWrapper content="Double-click a row to edit signature">
|
||||
<span className={PrimeIcons.PENCIL + ' text-[10px]'} />
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
)}
|
||||
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||
bodyClassName="p-0 pl-1 pr-2"
|
||||
/>
|
||||
)}
|
||||
</DataTable>
|
||||
</>
|
||||
)}
|
||||
|
||||
<WdTooltip
|
||||
className="bg-stone-900/95 text-slate-50"
|
||||
ref={tooltipRef}
|
||||
position={TooltipPosition.top}
|
||||
content={
|
||||
hoveredSignature ? (
|
||||
<SignatureView signature={hoveredSignature} showCharacterPortrait={!!showCharacterPortrait} />
|
||||
<SignatureView signature={hoveredSignature} showCharacterPortrait={showCharacterPortrait} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
@@ -342,4 +383,4 @@ export function SystemSignaturesContent({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
import {
|
||||
GroupType,
|
||||
SignatureGroup,
|
||||
SignatureGroupDE,
|
||||
SignatureGroupENG,
|
||||
SignatureGroupFR,
|
||||
SignatureGroupRU,
|
||||
SignatureKind,
|
||||
SignatureKindDE,
|
||||
SignatureKindENG,
|
||||
SignatureKindFR,
|
||||
SignatureKindRU,
|
||||
} 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;
|
||||
|
||||
// Signature deletion timing options
|
||||
export const DELETION_TIMING_IMMEDIATE = 0;
|
||||
export const DELETION_TIMING_DEFAULT = 10000;
|
||||
export const DELETION_TIMING_EXTENDED = 30000;
|
||||
export const DELETION_TIMING_SETTING_KEY = 'DELETION_TIMING_SETTING';
|
||||
|
||||
export const COMPACT_MAX_WIDTH = 260;
|
||||
export const MEDIUM_MAX_WIDTH = 380;
|
||||
export const OTHER_COLUMNS_WIDTH = 276;
|
||||
@@ -46,42 +44,232 @@ export const GROUPS: Record<SignatureGroup, GroupType> = {
|
||||
[SignatureGroup.CosmicSignature]: { id: SignatureGroup.CosmicSignature, icon: '/icons/x_close14.png', w: 9, h: 9 },
|
||||
};
|
||||
|
||||
export const MAPPING_GROUP_TO_ENG = {
|
||||
// ENGLISH
|
||||
[SignatureGroupENG.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupENG.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupENG.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupENG.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupENG.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupENG.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupENG.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
|
||||
// RUSSIAN
|
||||
[SignatureGroupRU.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupRU.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupRU.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupRU.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupRU.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupRU.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupRU.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
export const LANGUAGE_GROUP_MAPPINGS = {
|
||||
EN: {
|
||||
[SignatureGroupENG.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupENG.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupENG.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupENG.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupENG.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupENG.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupENG.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
},
|
||||
RU: {
|
||||
[SignatureGroupRU.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupRU.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupRU.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupRU.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupRU.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupRU.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupRU.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
},
|
||||
FR: {
|
||||
[SignatureGroupFR.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupFR.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupFR.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupFR.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupFR.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupFR.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupFR.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
},
|
||||
DE: {
|
||||
[SignatureGroupDE.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupDE.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupDE.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupDE.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupDE.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupDE.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupDE.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
},
|
||||
};
|
||||
|
||||
export const MAPPING_TYPE_TO_ENG = {
|
||||
// ENGLISH
|
||||
[SignatureKindENG.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindENG.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindENG.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindENG.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindENG.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindENG.Drone]: SignatureKind.Drone,
|
||||
// Flatten the structure for backward compatibility
|
||||
export const MAPPING_GROUP_TO_ENG: Record<string, SignatureGroup> = (() => {
|
||||
const flattened: Record<string, SignatureGroup> = {};
|
||||
for (const [, mappings] of Object.entries(LANGUAGE_GROUP_MAPPINGS)) {
|
||||
Object.assign(flattened, mappings);
|
||||
}
|
||||
return flattened;
|
||||
})();
|
||||
|
||||
// RUSSIAN
|
||||
[SignatureKindRU.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindRU.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindRU.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindRU.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindRU.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindRU.Drone]: SignatureKind.Drone,
|
||||
export const getGroupIdByRawGroup = (val: string): SignatureGroup | undefined => {
|
||||
return MAPPING_GROUP_TO_ENG[val] || undefined;
|
||||
};
|
||||
|
||||
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: number | string | boolean }[];
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
// Replace the flat structure with a nested structure by language
|
||||
export const LANGUAGE_TYPE_MAPPINGS = {
|
||||
EN: {
|
||||
[SignatureKindENG.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindENG.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindENG.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindENG.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindENG.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindENG.Drone]: SignatureKind.Drone,
|
||||
[SignatureKindENG.Starbase]: SignatureKind.Starbase,
|
||||
},
|
||||
RU: {
|
||||
[SignatureKindRU.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindRU.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindRU.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindRU.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindRU.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindRU.Drone]: SignatureKind.Drone,
|
||||
[SignatureKindRU.Starbase]: SignatureKind.Starbase,
|
||||
},
|
||||
FR: {
|
||||
[SignatureKindFR.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindFR.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindFR.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindFR.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindFR.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindFR.Drone]: SignatureKind.Drone,
|
||||
[SignatureKindFR.Starbase]: SignatureKind.Starbase,
|
||||
},
|
||||
DE: {
|
||||
[SignatureKindDE.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindDE.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindDE.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindDE.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindDE.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindDE.Drone]: SignatureKind.Drone,
|
||||
[SignatureKindDE.Starbase]: SignatureKind.Starbase,
|
||||
},
|
||||
};
|
||||
|
||||
// Flatten the structure for backward compatibility
|
||||
export const MAPPING_TYPE_TO_ENG: Record<string, SignatureKind> = (() => {
|
||||
const flattened: Record<string, SignatureKind> = {};
|
||||
for (const [, mappings] of Object.entries(LANGUAGE_TYPE_MAPPINGS)) {
|
||||
Object.assign(flattened, mappings);
|
||||
}
|
||||
return flattened;
|
||||
})();
|
||||
|
||||
@@ -1,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;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { getState } from './getState';
|
||||
|
||||
/**
|
||||
@@ -22,6 +22,7 @@ export const getActualSigs = (
|
||||
|
||||
oldSignatures.forEach(oldSig => {
|
||||
const newSig = newSignatures.find(s => s.eve_id === oldSig.eve_id);
|
||||
|
||||
if (newSig) {
|
||||
const needUpgrade = getState(GROUPS_LIST, newSig) > getState(GROUPS_LIST, oldSig);
|
||||
const mergedSig = { ...oldSig };
|
||||
|
||||
@@ -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/40 transition hover:bg-lime-600/50';
|
||||
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/30 transition hover:bg-lime-700/40';
|
||||
return '[&_.ssc-header]:text-amber-500 [&_.ssc-header]:hover:text-amber-500 [&_.ssc-header]:font-bold';
|
||||
}
|
||||
|
||||
return '';
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { UNKNOWN_SIGNATURE_NAME } from '@/hooks/Mapper/helpers';
|
||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
export const getState = (_: string[], newSig: SystemSignature) => {
|
||||
let state = -1;
|
||||
if (!newSig.group) {
|
||||
if (!newSig.group || newSig.group === SignatureGroup.CosmicSignature) {
|
||||
state = 0;
|
||||
} else if (!newSig.name || newSig.name === '') {
|
||||
} else if (!newSig.name || newSig.name === '' || newSig.name === UNKNOWN_SIGNATURE_NAME) {
|
||||
state = 1;
|
||||
} else if (newSig.name !== '') {
|
||||
state = 2;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user