mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-11-09 19:04:26 +00:00
Compare commits
267 Commits
ui-issues-
...
v1.52.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c87cfb3c43 | ||
|
|
85cb9ccfa8 | ||
|
|
da2639786d | ||
|
|
3cf77da293 | ||
|
|
3dd7633194 | ||
|
|
ae7f4edf4a | ||
|
|
52eab28f27 | ||
|
|
6098d32bce | ||
|
|
1839834771 | ||
|
|
7cdfb87853 | ||
|
|
3d54783a3e | ||
|
|
f965461820 | ||
|
|
6d67f87d4b | ||
|
|
60697a50c2 | ||
|
|
778d23da06 | ||
|
|
0ee9a15d5d | ||
|
|
24bb902bb9 | ||
|
|
32fe6395a1 | ||
|
|
5f506bf4b2 | ||
|
|
0127ebfe46 | ||
|
|
8c5366fd9b | ||
|
|
dbcad892a9 | ||
|
|
6da3096db1 | ||
|
|
cd8efcd6e3 | ||
|
|
b52471ae5e | ||
|
|
438fecb61f | ||
|
|
70b589a359 | ||
|
|
cf7069b3b2 | ||
|
|
b2198e469e | ||
|
|
8ab337e8e7 | ||
|
|
51878ab503 | ||
|
|
401dfad298 | ||
|
|
18cff7d312 | ||
|
|
7896de00d6 | ||
|
|
3b079505c3 | ||
|
|
5b972b03e5 | ||
|
|
79b284c46d | ||
|
|
b29e57b3a4 | ||
|
|
c6f4baeee3 | ||
|
|
6d341be072 | ||
|
|
2437ec9c84 | ||
|
|
7e692b5805 | ||
|
|
01b7370ecd | ||
|
|
20ad8b07d7 | ||
|
|
cab1880fb0 | ||
|
|
78eefcd6a7 | ||
|
|
eec78d38a8 | ||
|
|
73f8b1f06b | ||
|
|
f96cb01860 | ||
|
|
6800be1bb6 | ||
|
|
143f0a5b3a | ||
|
|
b6495504f8 | ||
|
|
2f07ec1b74 | ||
|
|
7073a0e8e6 | ||
|
|
bb0d91a3c7 | ||
|
|
1cb12b97ba | ||
|
|
860d20dc66 | ||
|
|
a850071965 | ||
|
|
fc41573e70 | ||
|
|
97f1808fb5 | ||
|
|
d31046eebb | ||
|
|
a70fa50eab | ||
|
|
9a082c26f5 | ||
|
|
6af2dc1ed5 | ||
|
|
5fd1509d44 | ||
|
|
2448c0531b | ||
|
|
b685ea1013 | ||
|
|
55465688c8 | ||
|
|
ac3c7e0c44 | ||
|
|
2d6ab5646c | ||
|
|
67b373ac29 | ||
|
|
678169e6fa | ||
|
|
7ee3c8db82 | ||
|
|
304f4b01ab | ||
|
|
4af12c21b2 | ||
|
|
497da1e5f7 | ||
|
|
5bd968acae | ||
|
|
f74c20142c | ||
|
|
d4c40d7542 | ||
|
|
04f3fec0c0 | ||
|
|
cd0b4b0fc9 | ||
|
|
e7b115e6e6 | ||
|
|
dff8fc6396 | ||
|
|
afdaeb3d34 | ||
|
|
ac6053361e | ||
|
|
eb3e1ba3aa | ||
|
|
8468a9b5de | ||
|
|
5eafe59dcb | ||
|
|
b38bcaa8cf | ||
|
|
8a238a447d | ||
|
|
3731219216 | ||
|
|
73d5fd5f67 | ||
|
|
e8e4aed6d5 | ||
|
|
63571a462f | ||
|
|
606add4142 | ||
|
|
dac480b059 | ||
|
|
5f67cb1dd7 | ||
|
|
5886fff753 | ||
|
|
da2e12bdd1 | ||
|
|
05c3d20e56 | ||
|
|
4633d26517 | ||
|
|
30b0556d47 | ||
|
|
e094378dc5 | ||
|
|
0c48189503 | ||
|
|
a5c346627a | ||
|
|
4e526040bf | ||
|
|
869c25cd60 | ||
|
|
6aac698cd8 | ||
|
|
230016b90f | ||
|
|
4b1aef8dd9 | ||
|
|
d34509d7a0 | ||
|
|
fca98ec232 | ||
|
|
e2814e95bd | ||
|
|
68a3f84704 | ||
|
|
4bc76feefc | ||
|
|
da39a55fd0 | ||
|
|
ee3cf04cd4 | ||
|
|
d79e7fe2ff | ||
|
|
8de9fdef32 | ||
|
|
f51deeec2d | ||
|
|
a971c69a96 | ||
|
|
b7995f50de | ||
|
|
14997a2959 | ||
|
|
8fef6bcf82 | ||
|
|
1f82d23963 | ||
|
|
28317a2431 | ||
|
|
6aac496a57 | ||
|
|
ac9306b713 | ||
|
|
d55e804efa | ||
|
|
08407a5679 | ||
|
|
c37d175bec | ||
|
|
69c5326e72 | ||
|
|
305f63e11d | ||
|
|
698fd5e083 | ||
|
|
1af8342d30 | ||
|
|
68b59da78e | ||
|
|
e784a3f850 | ||
|
|
a45e2f3fc2 | ||
|
|
8a3d920c31 | ||
|
|
996d7c47bd | ||
|
|
8d2b9db430 | ||
|
|
423ce343c7 | ||
|
|
1c17912d9f | ||
|
|
6714eb5d9b | ||
|
|
1620e1fd21 | ||
|
|
859014874f | ||
|
|
ef44881f06 | ||
|
|
b0532325fa | ||
|
|
2c00bd426e | ||
|
|
6eccf2ac67 | ||
|
|
973a1e54b3 | ||
|
|
2b42b637df | ||
|
|
b950572818 | ||
|
|
e470a210f1 | ||
|
|
71ec2d413c | ||
|
|
9122412558 | ||
|
|
0ba5c963b4 | ||
|
|
39a0ce284f | ||
|
|
f9d580dbc0 | ||
|
|
5c41574328 | ||
|
|
f17d74c8b7 | ||
|
|
c88854c54c | ||
|
|
f3779961d6 | ||
|
|
d93fc29734 | ||
|
|
c67918aca5 | ||
|
|
a9f276c95a | ||
|
|
7cee4894a5 | ||
|
|
edf8bef813 | ||
|
|
2081218398 | ||
|
|
b100052453 | ||
|
|
71636e895e | ||
|
|
7ff9689b76 | ||
|
|
5a4d819622 | ||
|
|
3117d85648 | ||
|
|
114133ecd2 | ||
|
|
bf8a1197e4 | ||
|
|
54c06a1fc0 | ||
|
|
e77a42dfda | ||
|
|
f83b4a2ba7 | ||
|
|
d34e7b8d8a | ||
|
|
fa0c7f3c66 | ||
|
|
5f58645b41 | ||
|
|
7ae0ec7573 | ||
|
|
b1149cecaf | ||
|
|
8f28d2be65 | ||
|
|
d758b54ef8 | ||
|
|
58293b4dc4 | ||
|
|
f2083f4256 | ||
|
|
6c7bd5804e | ||
|
|
483ae21e89 | ||
|
|
fc36d51e24 | ||
|
|
f734565844 | ||
|
|
8c718ba181 | ||
|
|
8aaa2e7add | ||
|
|
c8d8734601 | ||
|
|
5c757e8255 | ||
|
|
22f608f302 | ||
|
|
82f90ef759 | ||
|
|
167c8eea6b | ||
|
|
d76079d4c7 | ||
|
|
bf9c4cda02 | ||
|
|
af00402546 | ||
|
|
a245842ca4 | ||
|
|
8ddd672f13 | ||
|
|
92f471c0b0 | ||
|
|
9e2a2c5b44 | ||
|
|
5f5d3df003 | ||
|
|
c66cc8868e | ||
|
|
0d6528ce4f | ||
|
|
34c385ac5f | ||
|
|
b6d12e73a9 | ||
|
|
1118858120 | ||
|
|
ae3a34d5bf | ||
|
|
43df42e49b | ||
|
|
e670f3bf03 | ||
|
|
c26a9404c5 | ||
|
|
c0fad4ca92 | ||
|
|
16dbf9378b | ||
|
|
4001fe5eac | ||
|
|
2992dd8f8b | ||
|
|
98a03d1e59 | ||
|
|
2088393c79 | ||
|
|
093042b88a | ||
|
|
e5ef35c186 | ||
|
|
1cd23d5efd | ||
|
|
ead5818a3f | ||
|
|
a5f66ada68 | ||
|
|
0919742853 | ||
|
|
f3efffd259 | ||
|
|
f85317983c | ||
|
|
76f709b768 | ||
|
|
e3b2356302 | ||
|
|
3d810211ee | ||
|
|
7453795dc5 | ||
|
|
9de7cd99ee | ||
|
|
51489c1aa5 | ||
|
|
25dd6de770 | ||
|
|
9727405194 | ||
|
|
2a825f5a02 | ||
|
|
908d249eb9 | ||
|
|
6cd119e8f4 | ||
|
|
9a59c8eb75 | ||
|
|
452c022d41 | ||
|
|
27e9bab82a | ||
|
|
edef860530 | ||
|
|
032cb63411 | ||
|
|
a1791ba578 | ||
|
|
3a69fd7786 | ||
|
|
8a90723c2e | ||
|
|
af2fc342c7 | ||
|
|
05ea2fcdbe | ||
|
|
6d4321fead | ||
|
|
3f6364c9ea | ||
|
|
0d11b12282 | ||
|
|
0796bcf7d0 | ||
|
|
0b5bec142a | ||
|
|
a5020b58f2 | ||
|
|
f039a74a8f | ||
|
|
0e6bb7390b | ||
|
|
b52b4eecca | ||
|
|
8186977d1d | ||
|
|
86adcfe4d7 | ||
|
|
ce2dd872c4 | ||
|
|
aadc53c90e | ||
|
|
cbc1b6b5c8 | ||
|
|
1aed7a9232 | ||
|
|
e0a37f7635 |
@@ -1,7 +1,12 @@
|
||||
{
|
||||
"name": "wanderer-dev",
|
||||
"dockerComposeFile": ["./docker-compose.yml"],
|
||||
"extensions": ["jakebecker.elixir-ls"],
|
||||
"extensions": [
|
||||
"jakebecker.elixir-ls",
|
||||
"JakeBecker.elixir-ls",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
],
|
||||
"service": "wanderer",
|
||||
"workspaceFolder": "/app",
|
||||
"shutdownAction": "stopCompose",
|
||||
|
||||
@@ -6,3 +6,6 @@ export EVE_CLIENT_WITH_WALLET_ID="<EVE_CLIENT_WITH_WALLET_ID>"
|
||||
export EVE_CLIENT_WITH_WALLET_SECRET="<EVE_CLIENT_WITH_WALLET_SECRET>"
|
||||
export GIT_SHA="1111"
|
||||
export WANDERER_INVITES="false"
|
||||
export WANDERER_PUBLIC_API_DISABLED="false"
|
||||
export WANDERER_CHARACTER_API_DISABLED="false"
|
||||
export WANDERER_ZKILL_PRELOAD_DISABLED="false"
|
||||
|
||||
122
.github/workflows/build.yml
vendored
122
.github/workflows/build.yml
vendored
@@ -1,8 +1,6 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -41,8 +39,28 @@ jobs:
|
||||
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:
|
||||
@@ -78,22 +96,23 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: 😅 Cache deps
|
||||
id: cache-deps
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: cache-elixir-deps
|
||||
with:
|
||||
path: deps
|
||||
key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
|
||||
path: |
|
||||
deps
|
||||
key: ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles('**/mix.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-mix-${{ env.cache-name }}-
|
||||
${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-
|
||||
- name: 😅 Cache compiled build
|
||||
id: cache-build
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: cache-compiled-build
|
||||
with:
|
||||
path: |
|
||||
**/_build
|
||||
_build
|
||||
key: ${{ runner.os }}-build-${{ hashFiles('**/mix.lock') }}-${{ hashFiles( '**/lib/**/*.{ex,eex}', '**/config/*.exs', '**/mix.exs' ) }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ hashFiles('**/mix.lock') }}-
|
||||
@@ -122,6 +141,9 @@ jobs:
|
||||
name: 🛠 Build Docker Images
|
||||
needs: build
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
release-tag: ${{ steps.get-latest-tag.outputs.tag }}
|
||||
release-notes: ${{ steps.get-content.outputs.string }}
|
||||
permissions:
|
||||
checks: write
|
||||
contents: write
|
||||
@@ -135,6 +157,7 @@ jobs:
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
@@ -183,15 +206,28 @@ jobs:
|
||||
push: true
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
tags: ${{ env.REGISTRY_IMAGE }}:latest,${{ env.REGISTRY_IMAGE }}:${{ steps.get-latest-tag.outputs.tag }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
outputs: type=image,"name=${{ env.REGISTRY_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
|
||||
build-args: |
|
||||
MIX_ENV=prod
|
||||
BUILD_METADATA=${{ steps.meta.outputs.json }}
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.build.outputs.digest }}
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
- uses: markpatterson27/markdown-to-output@v1
|
||||
id: extract-changelog
|
||||
@@ -211,16 +247,54 @@ jobs:
|
||||
maxLength: 500
|
||||
truncationSymbol: "…"
|
||||
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@v5.3.0
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- docker
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
content: ${{ steps.get-content.outputs.string }}
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.WANDERER_DOCKER_USER }}
|
||||
password: ${{ secrets.WANDERER_DOCKER_PASSWORD }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY_IMAGE }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{version}},value=${{ needs.docker.outputs.release-tag }}
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
|
||||
|
||||
create-release:
|
||||
name: 🏷 Create Release
|
||||
runs-on: ubuntu-22.04
|
||||
needs: docker
|
||||
needs: [docker, merge]
|
||||
if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }}
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
@@ -228,17 +302,11 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get Release Tag
|
||||
id: get-latest-tag
|
||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||
with:
|
||||
fallback: 1.0.0
|
||||
|
||||
- name: 🏷 Create Draft Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ steps.get-latest-tag.outputs.tag }}
|
||||
name: Release ${{ steps.get-latest-tag.outputs.tag }}
|
||||
tag_name: ${{ needs.docker.outputs.release-tag }}
|
||||
name: Release ${{ needs.docker.outputs.release-tag }}
|
||||
body: |
|
||||
## Info
|
||||
Commit ${{ github.sha }} was deployed to `staging`. [See code diff](${{ github.event.compare }}).
|
||||
@@ -248,3 +316,9 @@ jobs:
|
||||
## How to Promote?
|
||||
In order to promote this to prod, edit the draft and press **"Publish release"**.
|
||||
draft: true
|
||||
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@v5.3.0
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
content: ${{ needs.docker.outputs.release-notes }}
|
||||
|
||||
891
CHANGELOG.md
891
CHANGELOG.md
@@ -2,6 +2,897 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v1.52.2](https://github.com/wanderer-industries/wanderer/compare/v1.52.1...v1.52.2) (2025-02-21)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* prevent constant full signature widget rerender (#195)
|
||||
|
||||
## [v1.52.1](https://github.com/wanderer-industries/wanderer/compare/v1.52.0...v1.52.1) (2025-02-20)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* proper virtual scroller usage (#192)
|
||||
|
||||
* restore delete key functionality for nodes (#191)
|
||||
|
||||
## [v1.52.0](https://github.com/wanderer-industries/wanderer/compare/v1.51.3...v1.52.0) (2025-02-19)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: Added map characters view
|
||||
|
||||
## [v1.51.3](https://github.com/wanderer-industries/wanderer/compare/v1.51.2...v1.51.3) (2025-02-19)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* pending deletion working again (#185)
|
||||
|
||||
## [v1.51.2](https://github.com/wanderer-industries/wanderer/compare/v1.51.1...v1.51.2) (2025-02-18)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.51.1](https://github.com/wanderer-industries/wanderer/compare/v1.51.0...v1.51.1) (2025-02-18)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.51.0](https://github.com/wanderer-industries/wanderer/compare/v1.50.0...v1.51.0) (2025-02-17)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* add undo deletion for signatures (#155)
|
||||
|
||||
* add undo for signature deletion and addition
|
||||
|
||||
## [v1.50.0](https://github.com/wanderer-industries/wanderer/compare/v1.49.0...v1.50.0) (2025-02-17)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* allow addition of characters to acl without preregistration (#176)
|
||||
|
||||
## [v1.49.0](https://github.com/wanderer-industries/wanderer/compare/v1.48.1...v1.49.0) (2025-02-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* add api for acl management (#171)
|
||||
|
||||
## [v1.48.1](https://github.com/wanderer-industries/wanderer/compare/v1.48.0...v1.48.1) (2025-02-13)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.48.0](https://github.com/wanderer-industries/wanderer/compare/v1.47.6...v1.48.0) (2025-02-12)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* autosize local character tooltip and increase hover target (#165)
|
||||
|
||||
## [v1.47.6](https://github.com/wanderer-industries/wanderer/compare/v1.47.5...v1.47.6) (2025-02-12)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.47.5](https://github.com/wanderer-industries/wanderer/compare/v1.47.4...v1.47.5) (2025-02-12)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* sync kills count bookmark and the kills widget (#160)
|
||||
|
||||
* lazy load kills widget
|
||||
|
||||
## [v1.47.4](https://github.com/wanderer-industries/wanderer/compare/v1.47.3...v1.47.4) (2025-02-11)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.47.3](https://github.com/wanderer-industries/wanderer/compare/v1.47.2...v1.47.3) (2025-02-11)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.47.2](https://github.com/wanderer-industries/wanderer/compare/v1.47.1...v1.47.2) (2025-02-11)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* lazy load kills widget (#157)
|
||||
|
||||
* lazy load kills widget
|
||||
|
||||
* updates for eslint and pr feedback
|
||||
|
||||
## [v1.47.1](https://github.com/wanderer-industries/wanderer/compare/v1.47.0...v1.47.1) (2025-02-09)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Connections: Fixed connections auto-refresh after update
|
||||
|
||||
## [v1.47.0](https://github.com/wanderer-industries/wanderer/compare/v1.46.1...v1.47.0) (2025-02-09)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: Added check for active map subscription to using Map APIs
|
||||
|
||||
## [v1.46.1](https://github.com/wanderer-industries/wanderer/compare/v1.46.0...v1.46.1) (2025-02-09)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed a lot of design and architect issues after last milli⦠(#154)
|
||||
|
||||
* Map: Fixed a lot of design and architect issues after last million PRs
|
||||
|
||||
* Map: removed unnecessary hooks styles
|
||||
|
||||
## [v1.46.0](https://github.com/wanderer-industries/wanderer/compare/v1.45.5...v1.46.0) (2025-02-08)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Added WANDERER_RESTRICT_MAPS_CREATION env support
|
||||
|
||||
## [v1.45.5](https://github.com/wanderer-industries/wanderer/compare/v1.45.4...v1.45.5) (2025-02-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* restore styling for local characters list (#152)
|
||||
|
||||
## [v1.45.4](https://github.com/wanderer-industries/wanderer/compare/v1.45.3...v1.45.4) (2025-02-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* remove snap to grid customization (#153)
|
||||
|
||||
## [v1.45.3](https://github.com/wanderer-industries/wanderer/compare/v1.45.2...v1.45.3) (2025-02-05)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* color and formatting fixes for local character (#150)
|
||||
|
||||
## [v1.45.2](https://github.com/wanderer-industries/wanderer/compare/v1.45.1...v1.45.2) (2025-02-05)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* fix route list hover and on the map character list (#149)
|
||||
|
||||
* correct formatting for on the map character list
|
||||
|
||||
* fix hover for route list
|
||||
|
||||
## [v1.45.1](https://github.com/wanderer-industries/wanderer/compare/v1.45.0...v1.45.1) (2025-02-05)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* kill count subscript position on firefox, and remove kill filter for single system (#148)
|
||||
|
||||
## [v1.45.0](https://github.com/wanderer-industries/wanderer/compare/v1.44.9...v1.45.0) (2025-02-05)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* allow filtering of k-space kills (#147)
|
||||
|
||||
## [v1.44.9](https://github.com/wanderer-industries/wanderer/compare/v1.44.8...v1.44.9) (2025-02-04)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* improve local character header shrink behavior (#146)
|
||||
|
||||
## [v1.44.8](https://github.com/wanderer-industries/wanderer/compare/v1.44.7...v1.44.8) (2025-02-04)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: include external libraries in build
|
||||
|
||||
## [v1.44.7](https://github.com/wanderer-industries/wanderer/compare/v1.44.6...v1.44.7) (2025-02-04)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: include external libraries in build
|
||||
|
||||
## [v1.44.6](https://github.com/wanderer-industries/wanderer/compare/v1.44.5...v1.44.6) (2025-02-04)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.44.5](https://github.com/wanderer-industries/wanderer/compare/v1.44.4...v1.44.5) (2025-02-04)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* include category param in search cache key (#144)
|
||||
|
||||
## [v1.44.4](https://github.com/wanderer-industries/wanderer/compare/v1.44.3...v1.44.4) (2025-02-02)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.44.3](https://github.com/wanderer-industries/wanderer/compare/v1.44.2...v1.44.3) (2025-02-02)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* restored kills lightning bolt functionality (#143)
|
||||
|
||||
## [v1.44.2](https://github.com/wanderer-industries/wanderer/compare/v1.44.1...v1.44.2) (2025-02-02)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.44.1](https://github.com/wanderer-industries/wanderer/compare/v1.44.0...v1.44.1) (2025-02-01)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed problem with windows. (#140)
|
||||
|
||||
* Map: Fixed problem with windows.
|
||||
|
||||
* Core: Added min heigth for body
|
||||
|
||||
## [v1.44.0](https://github.com/wanderer-industries/wanderer/compare/v1.43.9...v1.44.0) (2025-02-01)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* add news post for zkill widget
|
||||
|
||||
* add zkill widget
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* design feedback patch
|
||||
|
||||
* removed unneeded event handler
|
||||
|
||||
## [v1.43.9](https://github.com/wanderer-industries/wanderer/compare/v1.43.8...v1.43.9) (2025-01-30)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Add discord link to 'Like' icon on main interface
|
||||
|
||||
## [v1.43.8](https://github.com/wanderer-industries/wanderer/compare/v1.43.7...v1.43.8) (2025-01-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Update shuttered constellations (required EVE DB data update on server).
|
||||
|
||||
## [v1.43.7](https://github.com/wanderer-industries/wanderer/compare/v1.43.6...v1.43.7) (2025-01-26)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.43.6](https://github.com/wanderer-industries/wanderer/compare/v1.43.5...v1.43.6) (2025-01-22)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Widgets: Fix widgets not visible on map
|
||||
|
||||
## [v1.43.5](https://github.com/wanderer-industries/wanderer/compare/v1.43.4...v1.43.5) (2025-01-22)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Audit: Fix signature added/removed system name
|
||||
|
||||
## [v1.43.4](https://github.com/wanderer-industries/wanderer/compare/v1.43.3...v1.43.4) (2025-01-21)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* improve structure widget styling (#127)
|
||||
|
||||
## [v1.43.3](https://github.com/wanderer-industries/wanderer/compare/v1.43.2...v1.43.3) (2025-01-21)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.43.2](https://github.com/wanderer-industries/wanderer/compare/v1.43.1...v1.43.2) (2025-01-21)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* prevent constraint error for follow/toggle (#132)
|
||||
|
||||
## [v1.43.1](https://github.com/wanderer-industries/wanderer/compare/v1.43.0...v1.43.1) (2025-01-20)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.43.0](https://github.com/wanderer-industries/wanderer/compare/v1.42.5...v1.43.0) (2025-01-20)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* add news post for structures widget (#131)
|
||||
|
||||
## [v1.42.5](https://github.com/wanderer-industries/wanderer/compare/v1.42.4...v1.42.5) (2025-01-20)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix link signatures on splash. Fix deleting connection on locked system remove.
|
||||
|
||||
## [v1.42.4](https://github.com/wanderer-industries/wanderer/compare/v1.42.3...v1.42.4) (2025-01-20)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Fix system statics list (required EVE DB data update). Add system name to signature added/removed audit log
|
||||
|
||||
## [v1.42.3](https://github.com/wanderer-industries/wanderer/compare/v1.42.2...v1.42.3) (2025-01-17)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* change structure tooltip to avoid paste confusion (#125)
|
||||
|
||||
* change structure tooltip to avoid paste confusion
|
||||
|
||||
* clarify use of evetime and use primereact calendar
|
||||
|
||||
## [v1.42.2](https://github.com/wanderer-industries/wanderer/compare/v1.42.1...v1.42.2) (2025-01-16)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.42.1](https://github.com/wanderer-industries/wanderer/compare/v1.42.0...v1.42.1) (2025-01-16)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Remove linked sig ID if system containing signature removed from map
|
||||
|
||||
## [v1.42.0](https://github.com/wanderer-industries/wanderer/compare/v1.41.0...v1.42.0) (2025-01-16)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Audit: Add 'Signatures added/removed' map audit events
|
||||
|
||||
## [v1.41.0](https://github.com/wanderer-industries/wanderer/compare/v1.40.7...v1.41.0) (2025-01-16)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Audit: Add 'ACL added/removed' map audit events
|
||||
|
||||
## [v1.40.7](https://github.com/wanderer-industries/wanderer/compare/v1.40.6...v1.40.7) (2025-01-15)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.40.6](https://github.com/wanderer-industries/wanderer/compare/v1.40.5...v1.40.6) (2025-01-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix follow mode
|
||||
|
||||
* center system is not selected text for structures (#122)
|
||||
|
||||
* Map: Fix system revert issues
|
||||
|
||||
* Map: Fix issues with splashing signatures select & sig ID in temp names
|
||||
|
||||
## [v1.40.5](https://github.com/wanderer-industries/wanderer/compare/v1.40.4...v1.40.5) (2025-01-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix follow mode
|
||||
|
||||
## [v1.40.4](https://github.com/wanderer-industries/wanderer/compare/v1.40.3...v1.40.4) (2025-01-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* center system is not selected text for structures (#122)
|
||||
|
||||
## [v1.40.3](https://github.com/wanderer-industries/wanderer/compare/v1.40.2...v1.40.3) (2025-01-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix system revert issues
|
||||
|
||||
## [v1.40.2](https://github.com/wanderer-industries/wanderer/compare/v1.40.1...v1.40.2) (2025-01-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix issues with splashing signatures select & sig ID in temp names
|
||||
|
||||
## [v1.40.1](https://github.com/wanderer-industries/wanderer/compare/v1.40.0...v1.40.1) (2025-01-14)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.40.0](https://github.com/wanderer-industries/wanderer/compare/v1.39.3...v1.40.0) (2025-01-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* add structure widget with timer and associated api
|
||||
|
||||
## [v1.39.3](https://github.com/wanderer-industries/wanderer/compare/v1.39.2...v1.39.3) (2025-01-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Add style of corners for windows. Add ability to reset widgets. A lot of refactoring
|
||||
|
||||
## [v1.39.2](https://github.com/wanderer-industries/wanderer/compare/v1.39.1...v1.39.2) (2025-01-13)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.39.1](https://github.com/wanderer-industries/wanderer/compare/v1.39.0...v1.39.1) (2025-01-13)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: New windows systems
|
||||
|
||||
* Map: Add new windows system and removed old
|
||||
|
||||
* Map: First prototype of windows
|
||||
|
||||
## [v1.39.0](https://github.com/wanderer-industries/wanderer/compare/v1.38.7...v1.39.0) (2025-01-13)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: Added option to show signature ID as system temporary name part
|
||||
|
||||
## [v1.38.7](https://github.com/wanderer-industries/wanderer/compare/v1.38.6...v1.38.7) (2025-01-12)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.38.6](https://github.com/wanderer-industries/wanderer/compare/v1.38.5...v1.38.6) (2025-01-12)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.38.5](https://github.com/wanderer-industries/wanderer/compare/v1.38.4...v1.38.5) (2025-01-12)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.38.4](https://github.com/wanderer-industries/wanderer/compare/v1.38.3...v1.38.4) (2025-01-12)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.38.3](https://github.com/wanderer-industries/wanderer/compare/v1.38.2...v1.38.3) (2025-01-12)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.38.2](https://github.com/wanderer-industries/wanderer/compare/v1.38.1...v1.38.2) (2025-01-11)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Fix connections remove timeouts
|
||||
|
||||
## [v1.38.1](https://github.com/wanderer-industries/wanderer/compare/v1.38.0...v1.38.1) (2025-01-10)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* restored default theme colors (#115)
|
||||
|
||||
## [v1.38.0](https://github.com/wanderer-industries/wanderer/compare/v1.37.9...v1.38.0) (2025-01-10)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: Ability to store/view audit logs up to 3 months
|
||||
|
||||
* Map: Inroduced Env settings for connection auto EOL/remove timeouts
|
||||
|
||||
## [v1.37.9](https://github.com/wanderer-industries/wanderer/compare/v1.37.8...v1.37.9) (2025-01-10)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* restore system status colors (#112)
|
||||
|
||||
* restore system status colors
|
||||
|
||||
## [v1.37.8](https://github.com/wanderer-industries/wanderer/compare/v1.37.7...v1.37.8) (2025-01-10)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* fix issue with newly added systems not adding a connection (#114)
|
||||
|
||||
* resolve issue with newly added systems not connecting
|
||||
|
||||
## [v1.37.7](https://github.com/wanderer-industries/wanderer/compare/v1.37.6...v1.37.7) (2025-01-10)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* support additional theme names
|
||||
|
||||
## [v1.37.6](https://github.com/wanderer-industries/wanderer/compare/v1.37.5...v1.37.6) (2025-01-09)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* support additional theme names
|
||||
|
||||
## [v1.37.5](https://github.com/wanderer-industries/wanderer/compare/v1.37.4...v1.37.5) (2025-01-09)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* restore node styling, simplify framework for new themes
|
||||
|
||||
## [v1.37.4](https://github.com/wanderer-industries/wanderer/compare/v1.37.3...v1.37.4) (2025-01-09)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed dbclick behaviour
|
||||
|
||||
## [v1.37.3](https://github.com/wanderer-industries/wanderer/compare/v1.37.2...v1.37.3) (2025-01-09)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed dbclick behaviour
|
||||
|
||||
## [v1.37.2](https://github.com/wanderer-industries/wanderer/compare/v1.37.1...v1.37.2) (2025-01-09)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.37.1](https://github.com/wanderer-industries/wanderer/compare/v1.37.0...v1.37.1) (2025-01-08)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* add back pathfinder theme font
|
||||
|
||||
## [v1.37.0](https://github.com/wanderer-industries/wanderer/compare/v1.36.2...v1.37.0) (2025-01-08)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* add theme selection and pathfinder theme
|
||||
|
||||
## [v1.36.2](https://github.com/wanderer-industries/wanderer/compare/v1.36.1...v1.36.2) (2025-01-08)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed pasting into Name, Custom Label and Description
|
||||
|
||||
## [v1.36.1](https://github.com/wanderer-industries/wanderer/compare/v1.36.0...v1.36.1) (2025-01-08)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Removed unnecessary comment
|
||||
|
||||
* Map: Add support RU signatures and fix filtering
|
||||
|
||||
## [v1.36.0](https://github.com/wanderer-industries/wanderer/compare/v1.35.0...v1.36.0) (2025-01-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* added static system info to api (#101)
|
||||
|
||||
* added static system info to api
|
||||
|
||||
|
||||
## [v1.35.0](https://github.com/wanderer-industries/wanderer/compare/v1.34.0...v1.35.0) (2025-01-07)
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: add "temporary system names" toggle (#86)
|
||||
|
||||
## [v1.34.0](https://github.com/wanderer-industries/wanderer/compare/v1.33.1...v1.34.0) (2025-01-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: api to allow systematic access to visible systems and tracked characters (#89)
|
||||
|
||||
* add limited api for system and tracked characters
|
||||
|
||||
## [v1.33.1](https://github.com/wanderer-industries/wanderer/compare/v1.33.0...v1.33.1) (2025-01-07)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.33.0](https://github.com/wanderer-industries/wanderer/compare/v1.32.7...v1.33.0) (2025-01-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: api to allow systematic access to visible systems and tracked characters (#89)
|
||||
|
||||
* add limited api for system and tracked characters
|
||||
|
||||
## [v1.32.7](https://github.com/wanderer-industries/wanderer/compare/v1.32.6...v1.32.7) (2025-01-06)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.32.6](https://github.com/wanderer-industries/wanderer/compare/v1.32.5...v1.32.6) (2025-01-06)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.32.5](https://github.com/wanderer-industries/wanderer/compare/v1.32.4...v1.32.5) (2025-01-04)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* map: prevent deselect on click to map (#96)
|
||||
|
||||
## [v1.32.4](https://github.com/wanderer-industries/wanderer/compare/v1.32.3...v1.32.4) (2025-01-02)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix 'Character Activity' modal
|
||||
|
||||
## [v1.32.3](https://github.com/wanderer-industries/wanderer/compare/v1.32.2...v1.32.3) (2025-01-02)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix 'Allow only tracked characters' saving
|
||||
|
||||
## [v1.32.2](https://github.com/wanderer-industries/wanderer/compare/v1.32.1...v1.32.2) (2025-01-02)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.32.1](https://github.com/wanderer-industries/wanderer/compare/v1.32.0...v1.32.1) (2024-12-25)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.32.0](https://github.com/wanderer-industries/wanderer/compare/v1.31.0...v1.32.0) (2024-12-24)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: Add search & update manual adding systems API
|
||||
|
||||
* Map: Add search & update manual adding systems API
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Added ability to add new system to routes via routes widget
|
||||
|
||||
* Map: Reworked add system to map
|
||||
|
||||
## [v1.31.0](https://github.com/wanderer-industries/wanderer/compare/v1.30.2...v1.31.0) (2024-12-20)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: Show tracking for new users by default. Auto link characters to account fix. Add character loading indicators.
|
||||
|
||||
## [v1.30.2](https://github.com/wanderer-industries/wanderer/compare/v1.30.1...v1.30.2) (2024-12-17)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed problem with ship size change.
|
||||
|
||||
## [v1.30.1](https://github.com/wanderer-industries/wanderer/compare/v1.30.0...v1.30.1) (2024-12-17)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Little rework Signatures header: change System Signatures to Signatures, and show selected system name instead.
|
||||
|
||||
* Map: update default size of connections
|
||||
|
||||
* Map: add ability set the size of wormhole and mark connection with label
|
||||
|
||||
## [v1.30.0](https://github.com/wanderer-industries/wanderer/compare/v1.29.5...v1.30.0) (2024-12-16)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: Fixed incorrect wrapping labels of checkboxes in System Signatures, Local and Routes. Also changed dotlan links for k-spacem now it leads to region map before, for wh all stay as it was. Added ability to chane to softer background and remove dots on background of map. Also some small design issues. #2
|
||||
|
||||
* Map: Fixed incorrect wrapping labels of checkboxes in System Signatures, Local and Routes. Also changed dotlan links for k-spacem now it leads to region map before, for wh all stay as it was. Added ability to chane to softer background and remove dots on background of map. Also some small design issues.
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: fixed U210, K346 for C4 shattered systems
|
||||
|
||||
* Map: fixed U210, K346 for shattered systems. Fixed mass of mediums chains. Fixed size of some capital chains from 3M to 3.3M. Based on https://whtype.info/ data.
|
||||
|
||||
* Map: removed unnecessary log
|
||||
|
||||
* Map: Uncomment what should not be commented
|
||||
|
||||
## [v1.29.5](https://github.com/wanderer-industries/wanderer/compare/v1.29.4...v1.29.5) (2024-12-14)
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ WORKDIR /app
|
||||
# set build ENV
|
||||
ENV MIX_ENV="prod"
|
||||
|
||||
# Set ERL_FLAGS for ARM compatibility
|
||||
ENV ERL_FLAGS="+JPperf true"
|
||||
|
||||
# install mix dependencies
|
||||
COPY mix.exs mix.lock ./
|
||||
RUN rm -Rf _build deps && mix deps.get --only $MIX_ENV
|
||||
|
||||
@@ -58,6 +58,7 @@ Now you can visit [`localhost:8000`](http://localhost:8000) from your browser.
|
||||
- `root@0d0a785313b6:/app# apt update`
|
||||
- `root@0d0a785313b6:/app# curl -sL https://deb.nodesource.com/setup_18.x | bash -`
|
||||
- `root@0d0a785313b6:/app# apt-get install nodejs inotify-tools -y`
|
||||
- `root@0d0a785313b6:/app# npm install -g yarn`
|
||||
- `root@0d0a785313b6:/app# mix setup`
|
||||
|
||||
- See how to run server in #Run section
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// import '@fontsource-variable/inter'
|
||||
// import '@fontsource-variable/jetbrains-mono'
|
||||
// import './lib/tailwind/index.css';
|
||||
import './css/app.css';
|
||||
|
||||
import './lib/phoenix';
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
@import 'primereact/resources/themes/arya-blue/theme.css' layer(primereact);
|
||||
/*@import 'primereact/resources/themes/bootstrap4-dark-blue/theme.css' layer(primereact);*/
|
||||
|
||||
@import '../js/hooks/Mapper/components/map/styles/index.scss';
|
||||
|
||||
@layer tailwind-base {
|
||||
@tailwind base;
|
||||
}
|
||||
@@ -23,6 +25,10 @@ body {
|
||||
width: 400px; /* As IE6 ignores !important it will set width as 400px; */
|
||||
}
|
||||
|
||||
body > div:first-of-type {
|
||||
min-height: 500px !important;
|
||||
}
|
||||
|
||||
.lending-normal {
|
||||
font-family: 'Shentox', 'Rogan', sans-serif !important;
|
||||
font-weight: 500;
|
||||
@@ -930,3 +936,66 @@ body {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.verticalTabsContainer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
}
|
||||
.verticalTabsContainer .p-tabview {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.verticalTabsContainer .p-tabview-panels {
|
||||
padding: 6px 1rem !important;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
}
|
||||
.verticalTabsContainer .p-tabview-nav-container {
|
||||
border-right: none;
|
||||
height: 100%;
|
||||
}
|
||||
.verticalTabsContainer .p-tabview-nav {
|
||||
flex-direction: column;
|
||||
width: 150px;
|
||||
min-height: 100%;
|
||||
border: none;
|
||||
}
|
||||
.verticalTabsContainer .p-tabview-nav li {
|
||||
width: 100%;
|
||||
border-right: 4px solid var(--surface-hover);
|
||||
background-color: var(--surface-card);
|
||||
transition:
|
||||
background-color 200ms,
|
||||
border-right-color 200ms;
|
||||
}
|
||||
.verticalTabsContainer .p-tabview-nav li:hover {
|
||||
background-color: var(--surface-hover);
|
||||
border-right: 4px solid var(--surface-100);
|
||||
}
|
||||
.verticalTabsContainer .p-tabview-nav li .p-tabview-nav-link {
|
||||
transition: color 200ms;
|
||||
justify-content: flex-end;
|
||||
padding: 10px;
|
||||
background-color: initial;
|
||||
border: none;
|
||||
color: var(--gray-400);
|
||||
border-radius: initial;
|
||||
font-weight: 400;
|
||||
margin: 0;
|
||||
}
|
||||
.verticalTabsContainer .p-tabview-nav li.p-tabview-selected {
|
||||
background-color: var(--surface-50);
|
||||
border-right: 4px solid var(--primary-color);
|
||||
}
|
||||
.verticalTabsContainer .p-tabview-nav li.p-tabview-selected .p-tabview-nav-link {
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.verticalTabsContainer .p-tabview-nav li.p-tabview-selected:hover {
|
||||
border-right: 4px solid var(--primary-color);
|
||||
}
|
||||
.verticalTabsContainer .p-tabview-panel {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@@ -108,3 +108,32 @@
|
||||
.p-dropdown-empty-message {
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
.p-autocomplete .p-autocomplete-multiple-container .p-autocomplete-token {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
/* Fixed sizes of Input switch */
|
||||
.p-inputswitch {
|
||||
width: 2.0rem;
|
||||
height: 1.15rem;
|
||||
|
||||
.p-inputswitch-slider:before {
|
||||
width: 0.8rem;
|
||||
height: 0.8rem;
|
||||
left: 0.14rem;
|
||||
margin-top: -0.385rem;
|
||||
}
|
||||
|
||||
&.p-highlight .p-inputswitch-slider:before {
|
||||
transform: translateX(0.8rem);
|
||||
}
|
||||
|
||||
&:not(.p-disabled):has(.p-inputswitch-input:hover) .p-inputswitch-slider {
|
||||
background: rgb(255 255 255 / 21%);
|
||||
}
|
||||
|
||||
&.p-highlight .p-inputswitch-slider {
|
||||
background: #966d3d;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
.active {
|
||||
background-color: rgba(98, 98, 98, 0.33);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.active {
|
||||
background-color: rgba(98, 98, 98, 0.33);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.active {
|
||||
background-color: rgba(98, 98, 98, 0.33);
|
||||
}
|
||||
@@ -88,6 +88,23 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
||||
setSystem(undefined);
|
||||
}, []);
|
||||
|
||||
const onSystemTemporaryName = useCallback((temporaryName?: string) => {
|
||||
const { system, outCommand } = ref.current;
|
||||
if (!system) {
|
||||
return;
|
||||
}
|
||||
|
||||
outCommand({
|
||||
type: OutCommand.updateSystemTemporaryName,
|
||||
data: {
|
||||
system_id: system,
|
||||
value: temporaryName ?? '',
|
||||
},
|
||||
});
|
||||
setSystem(undefined);
|
||||
}, []);
|
||||
|
||||
|
||||
const onSystemStatus = useCallback((status: number) => {
|
||||
const { system, outCommand } = ref.current;
|
||||
if (!system) {
|
||||
@@ -161,6 +178,7 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
||||
onLockToggle,
|
||||
onHubToggle,
|
||||
onSystemTag,
|
||||
onSystemTemporaryName,
|
||||
onSystemStatus,
|
||||
onSystemLabels,
|
||||
onOpenSettings,
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
.active {
|
||||
background-color: rgba(98, 98, 98, 0.33);
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './useSystemInfo';
|
||||
export * from './useGetOwnOnlineCharacters';
|
||||
export * from './useElementWidth';
|
||||
|
||||
43
assets/js/hooks/Mapper/components/hooks/useElementWidth.ts
Normal file
43
assets/js/hooks/Mapper/components/hooks/useElementWidth.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { useState, useLayoutEffect, RefObject } from 'react';
|
||||
|
||||
/**
|
||||
* useElementWidth
|
||||
*
|
||||
* A custom hook that accepts a ref to an HTML element and returns its current width.
|
||||
* It uses a ResizeObserver and window resize listener to update the width when necessary.
|
||||
*
|
||||
* @param ref - A RefObject pointing to an HTML element.
|
||||
* @returns The current width of the element.
|
||||
*/
|
||||
export function useElementWidth<T extends HTMLElement>(ref: RefObject<T>): number {
|
||||
const [width, setWidth] = useState<number>(0);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const updateWidth = () => {
|
||||
if (ref.current) {
|
||||
const newWidth = ref.current.getBoundingClientRect().width;
|
||||
if (newWidth > 0) {
|
||||
setWidth(newWidth);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
updateWidth(); // Initial measurement
|
||||
|
||||
const observer = new ResizeObserver(() => {
|
||||
const id = setTimeout(updateWidth, 100);
|
||||
return () => clearTimeout(id);
|
||||
});
|
||||
|
||||
if (ref.current) {
|
||||
observer.observe(ref.current);
|
||||
}
|
||||
window.addEventListener("resize", updateWidth);
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
window.removeEventListener("resize", updateWidth);
|
||||
};
|
||||
}, [ref]);
|
||||
|
||||
return width;
|
||||
}
|
||||
@@ -14,9 +14,6 @@ export const useSystemInfo = ({ systemId }: UseSystemInfoProps) => {
|
||||
|
||||
const { systems: systemStatics } = useLoadSystemStatic({ systems: [systemId] });
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('JOipP', `systemStatics`, systemStatics);
|
||||
|
||||
return useMemo(() => {
|
||||
const staticInfo = systemStatics.get(parseInt(systemId));
|
||||
const dynamicInfo = getSystemById(systems, systemId);
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
.MapRoot {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.BackgroundAlternateColor {
|
||||
background-color: var(--rf-bg-color, #0C0A09);
|
||||
|
||||
&.BackgroundAlternateColor {
|
||||
background-color: var(--rf-soft-bg-color, #171717);
|
||||
--rf-node-bg-color: var(--rf-node-soft-bg-color, #202020);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect } from 'react';
|
||||
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useMemo } from 'react';
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
ConnectionMode,
|
||||
Edge,
|
||||
EdgeChange,
|
||||
MiniMap,
|
||||
Node,
|
||||
NodeChange,
|
||||
@@ -16,25 +16,24 @@ import ReactFlow, {
|
||||
} from 'reactflow';
|
||||
import 'reactflow/dist/style.css';
|
||||
import classes from './Map.module.scss';
|
||||
import './styles/neon-theme.scss';
|
||||
import './styles/eve-common.scss';
|
||||
import { MapProvider, useMapState } from './MapProvider';
|
||||
import { useNodesState, useEdgesState, useMapHandlers, useUpdateNodes } from './hooks';
|
||||
import { useEdgesState, useMapHandlers, useNodesState, useUpdateNodes } from './hooks';
|
||||
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import {
|
||||
ContextMenuConnection,
|
||||
ContextMenuRoot,
|
||||
SolarSystemEdge,
|
||||
SolarSystemNode,
|
||||
useContextMenuConnectionHandlers,
|
||||
useContextMenuRootHandlers,
|
||||
} from './components';
|
||||
import { OnMapSelectionChange } from './map.types';
|
||||
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 { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import clsx from 'clsx';
|
||||
import { useBackgroundVars } from './hooks/useBackgroundVars';
|
||||
|
||||
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
|
||||
|
||||
@@ -76,12 +75,6 @@ const initialEdges = [
|
||||
},
|
||||
];
|
||||
|
||||
const nodeTypes = {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
custom: SolarSystemNode,
|
||||
} as never;
|
||||
|
||||
const edgeTypes = {
|
||||
floating: SolarSystemEdge,
|
||||
};
|
||||
@@ -92,6 +85,7 @@ interface MapCompProps {
|
||||
onSelectionChange: OnMapSelectionChange;
|
||||
onManualDelete(systems: string[]): void;
|
||||
onConnectionInfoClick?(e: SolarSystemConnection): void;
|
||||
onAddSystem?: OnMapAddSystemCallback;
|
||||
onSelectionContextMenu?: NodeSelectionMouseHandler;
|
||||
minimapClasses?: string;
|
||||
isShowMinimap?: boolean;
|
||||
@@ -100,6 +94,7 @@ interface MapCompProps {
|
||||
isThickConnections?: boolean;
|
||||
isShowBackgroundPattern?: boolean;
|
||||
isSoftBackground?: boolean;
|
||||
theme?: string;
|
||||
}
|
||||
|
||||
const MapComp = ({
|
||||
@@ -116,16 +111,26 @@ const MapComp = ({
|
||||
isThickConnections,
|
||||
isShowBackgroundPattern,
|
||||
isSoftBackground,
|
||||
theme,
|
||||
onAddSystem,
|
||||
}: MapCompProps) => {
|
||||
const { getNode } = useReactFlow();
|
||||
const { getNode, getNodes } = useReactFlow();
|
||||
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
|
||||
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges);
|
||||
|
||||
useMapHandlers(refn, onSelectionChange);
|
||||
useUpdateNodes(nodes);
|
||||
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers();
|
||||
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem });
|
||||
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
|
||||
const { update } = useMapState();
|
||||
const { variant, gap, size, color } = useBackgroundVars(theme);
|
||||
const { isPanAndDrag, nodeComponent, connectionMode } = getBehaviorForTheme(theme || 'default');
|
||||
|
||||
const nodeTypes = useMemo(() => {
|
||||
return {
|
||||
custom: nodeComponent,
|
||||
};
|
||||
}, [nodeComponent]);
|
||||
|
||||
const onConnect: OnConnect = useCallback(
|
||||
params => {
|
||||
@@ -184,6 +189,12 @@ const MapComp = ({
|
||||
(changes: NodeChange[]) => {
|
||||
const systemsIdsToRemove: string[] = [];
|
||||
|
||||
// prevents single node deselection on background / same node click
|
||||
// allows deseletion of all nodes if multiple are currently selected
|
||||
if (changes.length === 1 && changes[0].type == 'select' && changes[0].selected === false) {
|
||||
changes[0].selected = getNodes().filter(node => node.selected).length === 1;
|
||||
}
|
||||
|
||||
const nextChanges = changes.reduce((acc, change) => {
|
||||
if (change.type !== 'remove') {
|
||||
return [...acc, change];
|
||||
@@ -208,7 +219,7 @@ const MapComp = ({
|
||||
|
||||
onNodesChange(nextChanges);
|
||||
},
|
||||
[getNode, onManualDelete, onNodesChange],
|
||||
[getNode, getNodes, onManualDelete, onNodesChange],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -221,7 +232,7 @@ const MapComp = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={clsx(classes.MapRoot, { ['bg-neutral-900']: isSoftBackground })}>
|
||||
<div className={clsx(classes.MapRoot, { [classes.BackgroundAlternateColor]: isSoftBackground })}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
@@ -233,7 +244,7 @@ const MapComp = ({
|
||||
defaultViewport={getViewPortFromStore()}
|
||||
edgeTypes={edgeTypes}
|
||||
nodeTypes={nodeTypes}
|
||||
connectionMode={ConnectionMode.Loose}
|
||||
connectionMode={connectionMode}
|
||||
snapToGrid
|
||||
nodeDragThreshold={10}
|
||||
onNodeDragStop={handleDragStop}
|
||||
@@ -241,6 +252,10 @@ const MapComp = ({
|
||||
onConnectStart={() => update({ isConnecting: true })}
|
||||
onConnectEnd={() => update({ isConnecting: false })}
|
||||
onNodeMouseEnter={(_, node) => update({ hoverNodeId: node.id })}
|
||||
onPaneClick={event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}}
|
||||
// onKeyUp=
|
||||
onNodeMouseLeave={() => update({ hoverNodeId: null })}
|
||||
onEdgeClick={(_, t) => {
|
||||
@@ -262,13 +277,19 @@ const MapComp = ({
|
||||
maxZoom={1.5}
|
||||
elevateNodesOnSelect
|
||||
deleteKeyCode={['Delete']}
|
||||
{...(isPanAndDrag
|
||||
? {
|
||||
selectionOnDrag: true,
|
||||
panOnDrag: [2],
|
||||
}
|
||||
: {})}
|
||||
// TODO need create clear example with problem with that flag
|
||||
// if system is not visible edge not drawing (and any render in Custom node is not happening)
|
||||
// onlyRenderVisibleElements
|
||||
selectionMode={SelectionMode.Partial}
|
||||
>
|
||||
{isShowMinimap && <MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} />}
|
||||
{isShowBackgroundPattern && <Background />}
|
||||
{isShowBackgroundPattern && <Background variant={variant} gap={gap} size={size} color={color} />}
|
||||
</ReactFlow>
|
||||
{/* <button className="z-auto btn btn-primary absolute top-20 right-20" onClick={handleGetPassages}>
|
||||
Test // DON NOT REMOVE
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { MapUnionTypes } from '@/hooks/Mapper/types';
|
||||
import { MapUnionTypes, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
|
||||
|
||||
export type MapData = MapUnionTypes & {
|
||||
@@ -9,6 +9,7 @@ export type MapData = MapUnionTypes & {
|
||||
visibleNodes: Set<string>;
|
||||
showKSpaceBG: boolean;
|
||||
isThickConnections: boolean;
|
||||
linkedSigEveId: string;
|
||||
};
|
||||
|
||||
interface MapProviderProps {
|
||||
@@ -29,10 +30,14 @@ const INITIAL_DATA: MapData = {
|
||||
isConnecting: false,
|
||||
connections: [],
|
||||
hoverNodeId: null,
|
||||
linkedSigEveId: '',
|
||||
visibleNodes: new Set(),
|
||||
showKSpaceBG: false,
|
||||
isThickConnections: false,
|
||||
userPermissions: {},
|
||||
systemSignatures: {} as Record<string, SystemSignature[]>,
|
||||
options: {} as Record<string, string | boolean>,
|
||||
isSubscriptionActive: false,
|
||||
};
|
||||
|
||||
export interface MapContextProps {
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
||||
|
||||
.ConnectionTimeEOL {
|
||||
background-image: linear-gradient(207deg, transparent, #7452c3e3);
|
||||
background-image: linear-gradient(207deg, transparent, var(--conn-time-eol));
|
||||
}
|
||||
|
||||
.ConnectionFrigate {
|
||||
background-image: linear-gradient(207deg, transparent, #325d88);
|
||||
background-image: linear-gradient(207deg, transparent, var(--conn-frigate));
|
||||
}
|
||||
|
||||
.ConnectionSave {
|
||||
background-image: linear-gradient(207deg, transparent, rgba(155, 102, 45, 0.85));
|
||||
background-image: linear-gradient(207deg, transparent, var(--conn-save));
|
||||
}
|
||||
|
||||
.SelectedItem {
|
||||
background-color: rgba(98, 98, 98, 0.33);
|
||||
background-color: var(--selected-item-bg);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,14 @@ import { Edge } from '@reactflow/core/dist/esm/types/edges';
|
||||
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import clsx from 'clsx';
|
||||
import classes from './ContextMenuConnection.module.scss';
|
||||
import { MASS_STATE_NAMES, MASS_STATE_NAMES_ORDER } from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import {
|
||||
MASS_STATE_NAMES,
|
||||
MASS_STATE_NAMES_ORDER,
|
||||
SHIP_SIZES_NAMES,
|
||||
SHIP_SIZES_NAMES_ORDER,
|
||||
SHIP_SIZES_NAMES_SHORT,
|
||||
SHIP_SIZES_SIZE,
|
||||
} from '@/hooks/Mapper/components/map/constants.ts';
|
||||
|
||||
export interface ContextMenuConnectionProps {
|
||||
contextMenuRef: RefObject<ContextMenu>;
|
||||
@@ -48,10 +55,6 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
|
||||
icon: PrimeIcons.CLOCK,
|
||||
command: onChangeTimeState,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(isWormhole
|
||||
? [
|
||||
{
|
||||
label: `Frigate`,
|
||||
className: clsx({
|
||||
@@ -60,13 +63,9 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
|
||||
icon: PrimeIcons.CLOUD,
|
||||
command: () =>
|
||||
onChangeShipSizeStatus(
|
||||
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.normal : ShipSizeStatus.small,
|
||||
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.large : ShipSizeStatus.small,
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(isWormhole
|
||||
? [
|
||||
{
|
||||
label: `Save mass`,
|
||||
className: clsx({
|
||||
@@ -75,19 +74,40 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
|
||||
icon: PrimeIcons.LOCK,
|
||||
command: () => onToggleMassSave(!edge.data?.locked),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(isWormhole && !isFrigateSize
|
||||
? [
|
||||
...(!isFrigateSize
|
||||
? [
|
||||
{
|
||||
label: `Mass status`,
|
||||
icon: PrimeIcons.CHART_PIE,
|
||||
items: MASS_STATE_NAMES_ORDER.map(x => ({
|
||||
label: MASS_STATE_NAMES[x],
|
||||
className: clsx({
|
||||
[classes.SelectedItem]: edge.data?.mass_status === x,
|
||||
}),
|
||||
command: () => onChangeMassState(x),
|
||||
})),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
{
|
||||
label: `Mass status`,
|
||||
icon: PrimeIcons.CHART_PIE,
|
||||
items: MASS_STATE_NAMES_ORDER.map(x => ({
|
||||
label: MASS_STATE_NAMES[x],
|
||||
label: `Ship Size`,
|
||||
icon: PrimeIcons.CLOUD,
|
||||
items: SHIP_SIZES_NAMES_ORDER.map(x => ({
|
||||
label: (
|
||||
<div className="grid grid-cols-[20px_120px_1fr_40px] gap-2 items-center">
|
||||
<div className="text-[12px] font-bold text-stone-400">{SHIP_SIZES_NAMES_SHORT[x]}</div>
|
||||
<div>{SHIP_SIZES_NAMES[x]}</div>
|
||||
<div></div>
|
||||
<div className="flex justify-end whitespace-nowrap text-[12px] font-bold text-stone-500">
|
||||
{SHIP_SIZES_SIZE[x]} t.
|
||||
</div>
|
||||
</div>
|
||||
) as unknown as string, // TODO my lovely kostyl
|
||||
className: clsx({
|
||||
[classes.SelectedItem]: edge.data?.mass_status === x,
|
||||
[classes.SelectedItem]: edge.data?.ship_size_type === x,
|
||||
}),
|
||||
command: () => onChangeMassState(x),
|
||||
command: () => onChangeShipSizeStatus(x),
|
||||
})),
|
||||
},
|
||||
]
|
||||
@@ -98,7 +118,7 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
|
||||
command: onDeleteConnection,
|
||||
},
|
||||
];
|
||||
}, [edge, onChangeTimeState, onDeleteConnection, onChangeMassState, onChangeShipSizeStatus]);
|
||||
}, [edge, onChangeTimeState, onDeleteConnection, onChangeShipSizeStatus, onToggleMassSave, onChangeMassState]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -97,14 +97,16 @@ export const useContextMenuConnectionHandlers = () => {
|
||||
},
|
||||
});
|
||||
|
||||
outCommand({
|
||||
type: OutCommand.updateConnectionMassStatus,
|
||||
data: {
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
value: MassState.normal,
|
||||
},
|
||||
});
|
||||
if (status === ShipSizeStatus.small) {
|
||||
outCommand({
|
||||
type: OutCommand.updateConnectionMassStatus,
|
||||
data: {
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
value: MassState.normal,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onToggleMassSave = useCallback((locked: boolean) => {
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { useReactFlow, XYPosition } from 'reactflow';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { useMapState } from '../../MapProvider.tsx';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { OnMapAddSystemCallback } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||
|
||||
export const useContextMenuRootHandlers = () => {
|
||||
type UseContextMenuRootHandlers = {
|
||||
onAddSystem?: OnMapAddSystemCallback;
|
||||
};
|
||||
|
||||
export const useContextMenuRootHandlers = ({ onAddSystem }: UseContextMenuRootHandlers = {}) => {
|
||||
const rf = useReactFlow();
|
||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||
const { outCommand } = useMapState();
|
||||
const [position, setPosition] = useState<XYPosition | null>(null);
|
||||
|
||||
const handleRootContext = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
@@ -18,14 +20,17 @@ export const useContextMenuRootHandlers = () => {
|
||||
contextMenuRef.current?.show(e);
|
||||
};
|
||||
|
||||
const onAddSystem = () => {
|
||||
outCommand({ type: OutCommand.manualAddSystem, data: { coordinates: position } });
|
||||
};
|
||||
const ref = useRef({ onAddSystem, position });
|
||||
ref.current = { onAddSystem, position };
|
||||
|
||||
const onAddSystemCallback = useCallback(() => {
|
||||
ref.current.onAddSystem?.({ coordinates: position });
|
||||
}, [position]);
|
||||
|
||||
return {
|
||||
handleRootContext,
|
||||
|
||||
contextMenuRef,
|
||||
onAddSystem,
|
||||
onAddSystem: onAddSystemCallback,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,23 +1,47 @@
|
||||
@import "@/hooks/Mapper/components/map/styles/neon-variables";
|
||||
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
||||
|
||||
.react-flow__edge.selected {
|
||||
.EdgePathBack {
|
||||
stroke: $pastel-yellow;
|
||||
.EdgePathBack {
|
||||
fill: none;
|
||||
stroke: #80a5c5;
|
||||
stroke-width: 3px;
|
||||
|
||||
&.TimeCrit {
|
||||
stroke: #f11ab2;
|
||||
stroke-width: 4px;
|
||||
}
|
||||
|
||||
&.Hovered {
|
||||
stroke: #b5c8d9;
|
||||
|
||||
&.TimeCrit {
|
||||
stroke: #ef7dce;
|
||||
}
|
||||
}
|
||||
|
||||
&.Tick {
|
||||
stroke-width: 5px;
|
||||
|
||||
&.TimeCrit {
|
||||
stroke-width: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&.Gate {
|
||||
stroke: #9aff40;
|
||||
}
|
||||
}
|
||||
|
||||
.EdgePathFront {
|
||||
fill: none;
|
||||
|
||||
stroke: #2c3844;
|
||||
stroke-width: 2px;
|
||||
|
||||
&.MassVerge:not(&.Frigate) {
|
||||
stroke: #af2900;
|
||||
stroke: #af0000;
|
||||
}
|
||||
|
||||
&.MassHalf:not(&.Frigate) {
|
||||
stroke: #a85f00;
|
||||
stroke: #ffd700;
|
||||
}
|
||||
|
||||
&.Frigate {
|
||||
@@ -54,46 +78,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
.EdgePathBack {
|
||||
fill: none;
|
||||
|
||||
stroke: #80a5c5;
|
||||
stroke-width: 3px;
|
||||
|
||||
&.TimeCrit {
|
||||
stroke: #f11ab2;
|
||||
stroke-width: 4px;
|
||||
}
|
||||
|
||||
&.Hovered {
|
||||
stroke: #b5c8d9;
|
||||
|
||||
&.TimeCrit {
|
||||
stroke: #ef7dce;
|
||||
}
|
||||
}
|
||||
|
||||
&.Tick {
|
||||
stroke-width: 5px;
|
||||
|
||||
&.TimeCrit {
|
||||
stroke-width: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&.Gate {
|
||||
stroke: #9aff40;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ClickPath {
|
||||
fill: none;
|
||||
stroke: none;
|
||||
stroke-width: 8px;
|
||||
}
|
||||
|
||||
.LinkLabel{
|
||||
.Handle {
|
||||
border: 1px solid var(--pastel-blue);
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
z-index: 1001;
|
||||
|
||||
&.Tick {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
}
|
||||
|
||||
&.Right {
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.LinkLabel {
|
||||
font-size: 9px;
|
||||
line-height: 10px;
|
||||
padding: 2px 4px;
|
||||
@@ -109,22 +116,3 @@
|
||||
height: 8px;
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.Handle {
|
||||
min-width: initial;
|
||||
min-height: initial;
|
||||
border: 1px solid #5a7d9a;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
z-index: 1001;
|
||||
|
||||
&.Tick {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
|
||||
&.Right {
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeS
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
import { SHIP_SIZES_DESCRIPTION, SHIP_SIZES_NAMES_SHORT } from '@/hooks/Mapper/components/map/constants.ts';
|
||||
|
||||
const MAP_TRANSLATES: Record<string, string> = {
|
||||
[Position.Top]: 'translate(-48%, 0%)',
|
||||
@@ -30,6 +31,14 @@ const MAP_OFFSETS: Record<string, { x: number; y: number }> = {
|
||||
[Position.Right]: { x: 0, y: 0 },
|
||||
};
|
||||
|
||||
export const SHIP_SIZES_COLORS = {
|
||||
[ShipSizeStatus.small]: 'bg-indigo-400',
|
||||
[ShipSizeStatus.medium]: 'bg-cyan-500',
|
||||
[ShipSizeStatus.large]: '',
|
||||
[ShipSizeStatus.freight]: 'bg-lime-400',
|
||||
[ShipSizeStatus.capital]: 'bg-red-400',
|
||||
};
|
||||
|
||||
export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }: EdgeProps<SolarSystemConnection>) => {
|
||||
const sourceNode = useStore(useCallback(store => store.nodeInternals.get(source), [source]));
|
||||
const targetNode = useStore(useCallback(store => store.nodeInternals.get(target), [target]));
|
||||
@@ -121,7 +130,7 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
|
||||
/>
|
||||
|
||||
<div
|
||||
className="absolute flex items-center gap-1"
|
||||
className="absolute flex items-center gap-1 pointer-events-none"
|
||||
style={{
|
||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||
}}
|
||||
@@ -137,6 +146,19 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
|
||||
<span className={clsx(PrimeIcons.LOCK, classes.icon)} />
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
|
||||
{isWormhole && data.ship_size_type !== ShipSizeStatus.large && (
|
||||
<WdTooltipWrapper
|
||||
content={SHIP_SIZES_DESCRIPTION[data.ship_size_type]}
|
||||
className={clsx(
|
||||
classes.LinkLabel,
|
||||
'pointer-events-auto rounded opacity-100 cursor-auto text-neutral-900 font-bold',
|
||||
SHIP_SIZES_COLORS[data.ship_size_type],
|
||||
)}
|
||||
>
|
||||
{SHIP_SIZES_NAMES_SHORT[data.ship_size_type]}
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
</div>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
.KillsBookmark {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 8px;
|
||||
font-weight: 700;
|
||||
border: 0;
|
||||
border-radius: 5px 5px 0 0;
|
||||
padding: 4px 3px;
|
||||
|
||||
}
|
||||
|
||||
.KillsBookmarkWithIcon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: -2px;
|
||||
text-shadow: 0 0 3px #000;
|
||||
padding-right: 2px;
|
||||
height: 8px;
|
||||
font-size: 8px;
|
||||
line-height: 12px;
|
||||
font-weight: 700;
|
||||
text-size-adjust: 100%;
|
||||
|
||||
.pi {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 9px;
|
||||
font-family: var(--rf-node-font-family, inherit) !important;
|
||||
font-weight: var(--rf-node-font-weight, inherit) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.TooltipContainer {
|
||||
background-color: #1a1a1a;
|
||||
color: #fff;
|
||||
padding: 3px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 2px;
|
||||
pointer-events: auto;
|
||||
max-width: 500px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { SystemKillsContent } from '../../../mapInterface/widgets/SystemKills/SystemKillsContent/SystemKillsContent';
|
||||
import { useKillsCounter } from '../../hooks/useKillsCounter';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { WithChildren, WithClassName } from '@/hooks/Mapper/types/common';
|
||||
|
||||
type TooltipSize = 'xs' | 'sm' | 'md' | 'lg';
|
||||
|
||||
type KillsBookmarkTooltipProps = {
|
||||
killsCount: number;
|
||||
killsActivityType: string | null;
|
||||
systemId: string;
|
||||
className?: string;
|
||||
size?: TooltipSize;
|
||||
} & WithChildren &
|
||||
WithClassName;
|
||||
|
||||
export const KillsCounter = ({ killsCount, systemId, className, children, size = 'xs' }: KillsBookmarkTooltipProps) => {
|
||||
const { isLoading, kills: detailedKills, systemNameMap } = useKillsCounter({ realSystemId: systemId });
|
||||
|
||||
if (!killsCount || detailedKills.length === 0 || !systemId || isLoading) return null;
|
||||
|
||||
const tooltipContent = (
|
||||
<div style={{ width: '100%', minWidth: '300px', overflow: 'hidden' }}>
|
||||
<SystemKillsContent
|
||||
kills={detailedKills}
|
||||
systemNameMap={systemNameMap}
|
||||
onlyOneSystem={true}
|
||||
autoSize={true}
|
||||
limit={killsCount}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<WdTooltipWrapper content={tooltipContent} className={className} size={size} interactive={true}>
|
||||
{children}
|
||||
</WdTooltipWrapper>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
.TooltipActive {
|
||||
pointer-events: auto !important;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.hoverTarget {
|
||||
padding: 0.5rem;
|
||||
margin: -0.5rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.localCounter {
|
||||
mix-blend-mode: screen;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
color: var(--rf-node-local-counter);
|
||||
|
||||
&.hasUserCharacters {
|
||||
color: var(--rf-has-user-characters);
|
||||
}
|
||||
|
||||
& > i {
|
||||
font-size: 9px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
& > span {
|
||||
font-size: 9px;
|
||||
line-height: 9px;
|
||||
font-weight: var(--rf-local-counter-font-weight, 500);
|
||||
|
||||
@-moz-document url-prefix() {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Pathfinder {
|
||||
.localCounter {
|
||||
@-moz-document url-prefix() {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
& > span {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { useMemo } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit/WdTooltip';
|
||||
import { CharItemProps, LocalCharactersList } from '../../../mapInterface/widgets/LocalCharacters/components';
|
||||
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';
|
||||
|
||||
interface LocalCounterProps {
|
||||
localCounterCharacters: Array<CharItemProps>;
|
||||
hasUserCharacters: boolean;
|
||||
showIcon?: boolean;
|
||||
}
|
||||
|
||||
export const LocalCounter = ({ localCounterCharacters, hasUserCharacters, showIcon = true }: LocalCounterProps) => {
|
||||
const [settings] = useLocalCharacterWidgetSettings();
|
||||
const itemTemplate = useLocalCharactersItemTemplate(settings.showShipName);
|
||||
const theme = useTheme();
|
||||
|
||||
const pilotTooltipContent = useMemo(() => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
minWidth: '300px',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<LocalCharactersList items={localCounterCharacters} itemTemplate={itemTemplate} itemSize={26} autoSize={true} />
|
||||
</div>
|
||||
);
|
||||
}, [localCounterCharacters, itemTemplate]);
|
||||
|
||||
if (localCounterCharacters.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(classes.TooltipActive, {
|
||||
[classes.Pathfinder]: theme === AvailableThemes.pathfinder,
|
||||
})}
|
||||
>
|
||||
<WdTooltipWrapper content={pilotTooltipContent} position={TooltipPosition.right} offset={0} interactive={true}>
|
||||
<div className={clsx(classes.hoverTarget)}>
|
||||
<div
|
||||
className={clsx(classes.localCounter, {
|
||||
[classes.hasUserCharacters]: hasUserCharacters,
|
||||
})}
|
||||
>
|
||||
{showIcon && <i className="pi pi-users" />}
|
||||
<span>{localCounterCharacters.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,324 +0,0 @@
|
||||
import { memo, useMemo } from 'react';
|
||||
import { Handle, Position, WrapNodeProps } from 'reactflow';
|
||||
import { MapSolarSystemType } from '../../map.types';
|
||||
import classes from './SolarSystemNode.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
import {
|
||||
EFFECT_BACKGROUND_STYLES,
|
||||
LABELS_INFO,
|
||||
LABELS_ORDER,
|
||||
MARKER_BOOKMARK_BG_STYLES,
|
||||
STATUS_CLASSES,
|
||||
} from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||
import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp';
|
||||
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
|
||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
import { getSystemClassStyles, prepareUnsplashedChunks } from '@/hooks/Mapper/components/map/helpers';
|
||||
import { sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { useDoubleClick } from '@/hooks/Mapper/hooks/useDoubleClick.ts';
|
||||
import { REGIONS_MAP, Spaces } from '@/hooks/Mapper/constants';
|
||||
|
||||
const SpaceToClass: Record<string, string> = {
|
||||
[Spaces.Caldari]: classes.Caldaria,
|
||||
[Spaces.Matar]: classes.Mataria,
|
||||
[Spaces.Amarr]: classes.Amarria,
|
||||
[Spaces.Gallente]: classes.Gallente,
|
||||
};
|
||||
|
||||
const sortedLabels = (labels: string[]) => {
|
||||
if (!labels) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return LABELS_ORDER.filter(x => labels.includes(x)).map(x => LABELS_INFO[x]);
|
||||
};
|
||||
|
||||
export const getActivityType = (count: number) => {
|
||||
if (count <= 5) {
|
||||
return 'activityNormal';
|
||||
}
|
||||
|
||||
if (count <= 30) {
|
||||
return 'activityWarn';
|
||||
}
|
||||
|
||||
return 'activityDanger';
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarSystemType>) => {
|
||||
const { interfaceSettings } = useMapRootState();
|
||||
const { isShowUnsplashedSignatures } = interfaceSettings;
|
||||
|
||||
const {
|
||||
system_class,
|
||||
security,
|
||||
class_title,
|
||||
solar_system_id,
|
||||
statics,
|
||||
effect_name,
|
||||
region_name,
|
||||
region_id,
|
||||
is_shattered,
|
||||
solar_system_name,
|
||||
} = data.system_static_info;
|
||||
|
||||
const signatures = data.system_signatures;
|
||||
|
||||
const { locked, name, tag, status, labels, id } = data || {};
|
||||
|
||||
const customName = solar_system_name !== name ? name : undefined;
|
||||
|
||||
const {
|
||||
data: {
|
||||
characters,
|
||||
presentCharacters,
|
||||
wormholesData,
|
||||
hubs,
|
||||
kills,
|
||||
userCharacters,
|
||||
isConnecting,
|
||||
hoverNodeId,
|
||||
visibleNodes,
|
||||
showKSpaceBG,
|
||||
isThickConnections,
|
||||
},
|
||||
outCommand,
|
||||
} = useMapState();
|
||||
|
||||
const visible = useMemo(() => visibleNodes.has(id), [id, visibleNodes]);
|
||||
|
||||
const charactersInSystem = useMemo(() => {
|
||||
return characters.filter(c => c.location?.solar_system_id === solar_system_id).filter(c => c.online);
|
||||
// eslint-disable-next-line
|
||||
}, [characters, presentCharacters, solar_system_id]);
|
||||
|
||||
const isWormhole = isWormholeSpace(system_class);
|
||||
const classTitleColor = useMemo(
|
||||
() => getSystemClassStyles({ systemClass: system_class, security }),
|
||||
[security, system_class],
|
||||
);
|
||||
const sortedStatics = useMemo(() => sortWHClasses(wormholesData, statics), [wormholesData, statics]);
|
||||
const lebM = useMemo(() => new LabelsManager(labels ?? ''), [labels]);
|
||||
const labelsInfo = useMemo(() => sortedLabels(lebM.list), [lebM]);
|
||||
const labelCustom = useMemo(() => lebM.customLabel, [lebM]);
|
||||
|
||||
const killsCount = useMemo(() => {
|
||||
const systemKills = kills[solar_system_id];
|
||||
if (!systemKills) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return systemKills;
|
||||
}, [kills, solar_system_id]);
|
||||
|
||||
const hasUserCharacters = useMemo(() => {
|
||||
return charactersInSystem.some(x => userCharacters.includes(x.eve_id));
|
||||
}, [charactersInSystem, userCharacters]);
|
||||
|
||||
const dbClick = useDoubleClick(() => {
|
||||
outCommand({
|
||||
type: OutCommand.openSettings,
|
||||
data: {
|
||||
system_id: solar_system_id.toString(),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const showHandlers = isConnecting || hoverNodeId === id;
|
||||
|
||||
const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
|
||||
const regionClass = showKSpaceBG ? SpaceToClass[space] : null;
|
||||
|
||||
const [unsplashedLeft, unsplashedRight] = useMemo(() => {
|
||||
if (!isShowUnsplashedSignatures) {
|
||||
return [[], []];
|
||||
}
|
||||
|
||||
return prepareUnsplashedChunks(
|
||||
signatures
|
||||
.filter(s => s.group === 'Wormhole' && !s.linked_system)
|
||||
.map(s => ({
|
||||
eve_id: s.eve_id,
|
||||
type: s.type,
|
||||
custom_info: s.custom_info,
|
||||
})),
|
||||
);
|
||||
}, [isShowUnsplashedSignatures, signatures]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{visible && (
|
||||
<div className={classes.Bookmarks}>
|
||||
{labelCustom !== '' && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
|
||||
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] ">{labelCustom}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{is_shattered && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered)}>
|
||||
<span className={clsx('pi pi-chart-pie', classes.icon)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{killsCount && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[getActivityType(killsCount)])}>
|
||||
<div className={clsx(classes.BookmarkWithIcon)}>
|
||||
<span className={clsx(PrimeIcons.BOLT, classes.icon)} />
|
||||
<span className={clsx(classes.text)}>{killsCount}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{labelsInfo.map(x => (
|
||||
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
||||
{x.shortName}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={clsx(classes.RootCustomNode, regionClass, classes[STATUS_CLASSES[status]], {
|
||||
[classes.selected]: selected,
|
||||
})}
|
||||
>
|
||||
{visible && (
|
||||
<>
|
||||
<div className={classes.HeadRow}>
|
||||
<div className={clsx(classes.classTitle, classTitleColor, '[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]')}>
|
||||
{class_title ?? '-'}
|
||||
</div>
|
||||
{tag != null && tag !== '' && (
|
||||
<div className={clsx(classes.TagTitle, 'text-sky-400 font-medium')}>{tag}</div>
|
||||
)}
|
||||
<div
|
||||
className={clsx(
|
||||
classes.classSystemName,
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
|
||||
)}
|
||||
>
|
||||
{solar_system_name}
|
||||
</div>
|
||||
|
||||
{isWormhole && (
|
||||
<div className={classes.statics}>
|
||||
{sortedStatics.map(x => (
|
||||
<WormholeClassComp key={x} id={x} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{effect_name !== null && isWormhole && (
|
||||
<div className={clsx(classes.effect, EFFECT_BACKGROUND_STYLES[effect_name])}></div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
|
||||
{customName && (
|
||||
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-blue-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||
{customName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isWormhole && !customName && (
|
||||
<div
|
||||
className={clsx(
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-stone-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
|
||||
)}
|
||||
>
|
||||
{region_name}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isWormhole && !customName && <div />}
|
||||
|
||||
<div className="flex items-center justify-end">
|
||||
<div className="flex gap-1 items-center">
|
||||
{locked && <i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem', fontWeight: 'bold' }}></i>}
|
||||
|
||||
{hubs.includes(solar_system_id.toString()) && (
|
||||
<i className={PrimeIcons.MAP_MARKER} style={{ fontSize: '0.45rem', fontWeight: 'bold' }}></i>
|
||||
)}
|
||||
|
||||
{charactersInSystem.length > 0 && (
|
||||
<div className={clsx(classes.localCounter, { ['text-amber-300']: hasUserCharacters })}>
|
||||
<i className="pi pi-users" style={{ fontSize: '0.50rem' }}></i>
|
||||
<span className="font-sans">{charactersInSystem.length}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{visible && isShowUnsplashedSignatures && (
|
||||
<div className={classes.Unsplashed}>
|
||||
{unsplashedLeft.map(x => (
|
||||
<UnsplashedSignature key={x.sig_id} signature={x} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{visible && isShowUnsplashedSignatures && (
|
||||
<div className={clsx([classes.Unsplashed, classes['Unsplashed--right']])}>
|
||||
{unsplashedRight.map(x => (
|
||||
<UnsplashedSignature key={x.sig_id} signature={x} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div onMouseDownCapture={dbClick} className={classes.Handlers}>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleTop, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Top}
|
||||
id="a"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleRight, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Right}
|
||||
id="b"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleBottom, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Bottom}
|
||||
id="c"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleLeft, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Left}
|
||||
id="d"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -2,27 +2,30 @@
|
||||
|
||||
$pastel-blue: #5a7d9a;
|
||||
$pastel-pink: #d291bc;
|
||||
$pastel-green: #88b04b;
|
||||
$pastel-yellow: #ffdd59;
|
||||
$dark-bg: #2d2d2d;
|
||||
$text-color: #ffffff;
|
||||
$tooltip-bg: #202020; // Dark background for tooltips
|
||||
$tooltip-bg: #202020;
|
||||
|
||||
.RootCustomNode {
|
||||
display: flex;
|
||||
width: 130px;
|
||||
height: 34px;
|
||||
|
||||
font-family: var(--rf-node-font-family, inherit) !important;
|
||||
font-weight: var(--rf-node-font-weight, inherit) !important;
|
||||
|
||||
flex-direction: column;
|
||||
padding: 2px 6px;
|
||||
font-size: 10px;
|
||||
|
||||
background-color: $tooltip-bg;
|
||||
background-color: var(--rf-node-bg-color, #202020) !important;
|
||||
color: var(--rf-text-color, #ffffff);
|
||||
|
||||
box-shadow: 0 0 5px rgba($dark-bg, 0.5);
|
||||
border: 1px solid darken($pastel-blue, 10%);
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
z-index: 3;
|
||||
overflow: hidden;
|
||||
|
||||
&.Mataria,
|
||||
@@ -85,51 +88,40 @@ $tooltip-bg: #202020; // Dark background for tooltips
|
||||
box-shadow: 0 0 10px #9a1af1c2;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
background-color: $tooltip-bg;
|
||||
color: $text-color;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid $pastel-pink;
|
||||
}
|
||||
|
||||
&.eve-system-status-home {
|
||||
border: 1px solid darken($eve-solar-system-status-color-home, 30%);
|
||||
background-image: linear-gradient(275deg, $eve-solar-system-status-friendly, transparent);
|
||||
|
||||
border: 1px solid var(--eve-solar-system-status-color-home-dark30);
|
||||
background-image: linear-gradient(45deg, var(--eve-solar-system-status-color-background), transparent);
|
||||
&.selected {
|
||||
border-color: $eve-solar-system-status-color-home;
|
||||
border-color: var(--eve-solar-system-status-color-home);
|
||||
}
|
||||
}
|
||||
|
||||
&.eve-system-status-friendly {
|
||||
border: 1px solid darken($eve-solar-system-status-color-friendly, 20%);
|
||||
background-image: linear-gradient(275deg, darken($eve-solar-system-status-friendly, 30%), transparent);
|
||||
|
||||
border: 1px solid var(--eve-solar-system-status-color-friendly-dark20);
|
||||
background-image: linear-gradient(275deg, var(--eve-solar-system-status-friendly-dark30), transparent);
|
||||
&.selected {
|
||||
border-color: darken($eve-solar-system-status-color-friendly, 5%);
|
||||
border-color: var(--eve-solar-system-status-color-friendly-dark5);
|
||||
}
|
||||
}
|
||||
|
||||
&.eve-system-status-lookingFor {
|
||||
border: 1px solid darken($eve-solar-system-status-color-lookingFor, 15%);
|
||||
border: 1px solid var(--eve-solar-system-status-color-lookingFor-dark15);
|
||||
background-image: linear-gradient(275deg, #45ff8f2f, #457fff2f);
|
||||
|
||||
&.selected {
|
||||
border-color: $pastel-pink;
|
||||
}
|
||||
}
|
||||
|
||||
&.eve-system-status-warning {
|
||||
background-image: linear-gradient(275deg, $eve-solar-system-status-warning, transparent);
|
||||
background-image: linear-gradient(275deg, var(--eve-solar-system-status-warning), transparent);
|
||||
}
|
||||
|
||||
&.eve-system-status-dangerous {
|
||||
background-image: linear-gradient(275deg, $eve-solar-system-status-dangerous, transparent);
|
||||
background-image: linear-gradient(275deg, var(--eve-solar-system-status-dangerous), transparent);
|
||||
}
|
||||
|
||||
&.eve-system-status-target {
|
||||
background-image: linear-gradient(275deg, $eve-solar-system-status-target, transparent);
|
||||
background-image: linear-gradient(275deg, var(--eve-solar-system-status-target), transparent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,8 +146,6 @@ $tooltip-bg: #202020; // Dark background for tooltips
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
|
||||
//background-color: #833ca4;
|
||||
|
||||
&:not(:first-child) {
|
||||
box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
@@ -242,26 +232,17 @@ $tooltip-bg: #202020; // Dark background for tooltips
|
||||
|
||||
.TagTitle {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
font-weight: 500;
|
||||
text-shadow: 0 0 2px rgba(231, 146, 52, 0.73);
|
||||
|
||||
color: #ffb01d;
|
||||
color: var(--rf-tag-color, #38bdf8);
|
||||
}
|
||||
|
||||
/* Firefox kostyl */
|
||||
@-moz-document url-prefix() {
|
||||
.classSystemName {
|
||||
font-family: inherit !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.classSystemName {
|
||||
//font-weight: bold;
|
||||
}
|
||||
|
||||
.solarSystemName {
|
||||
}
|
||||
}
|
||||
|
||||
.BottomRow {
|
||||
@@ -270,22 +251,23 @@ $tooltip-bg: #202020; // Dark background for tooltips
|
||||
align-items: center;
|
||||
height: 19px;
|
||||
|
||||
.localCounter {
|
||||
display: flex;
|
||||
//align-items: center;
|
||||
gap: 2px;
|
||||
|
||||
& > i {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
.hasLocalCounter {
|
||||
margin-right: 2px;
|
||||
&.countAbove9 {
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
& > span {
|
||||
font-size: 9px;
|
||||
line-height: 9px;
|
||||
font-weight: 500;
|
||||
//margin-top: 1px;
|
||||
}
|
||||
.lockIcon {
|
||||
font-size: 0.45rem;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mapMarker {
|
||||
font-size: 0.45rem;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,7 +298,8 @@ $tooltip-bg: #202020; // Dark background for tooltips
|
||||
|
||||
.Handlers {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
z-index: 4;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
@@ -329,6 +312,7 @@ $tooltip-bg: #202020; // Dark background for tooltips
|
||||
border: 1px solid $pastel-blue;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
pointer-events: auto;
|
||||
|
||||
&.selected {
|
||||
border-color: $pastel-pink;
|
||||
@@ -0,0 +1,209 @@
|
||||
import { memo } from 'react';
|
||||
import { MapSolarSystemType } from '../../map.types';
|
||||
import { Handle, NodeProps, Position } from 'reactflow';
|
||||
import clsx from 'clsx';
|
||||
import classes from './SolarSystemNodeDefault.module.scss';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { useLocalCounter, useSolarSystemNode, useNodeKillsCount } from '../../hooks/useSolarSystemLogic';
|
||||
import {
|
||||
EFFECT_BACKGROUND_STYLES,
|
||||
MARKER_BOOKMARK_BG_STYLES,
|
||||
STATUS_CLASSES,
|
||||
} from '@/hooks/Mapper/components/map/constants';
|
||||
import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp';
|
||||
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
|
||||
import { LocalCounter } from './SolarSystemLocalCounter';
|
||||
import { KillsCounter } from './SolarSystemKillsCounter';
|
||||
|
||||
export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>) => {
|
||||
const nodeVars = useSolarSystemNode(props);
|
||||
const { localCounterCharacters } = useLocalCounter(nodeVars);
|
||||
const localKillsCount = useNodeKillsCount(nodeVars.solarSystemId, nodeVars.killsCount);
|
||||
|
||||
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>
|
||||
)}
|
||||
|
||||
{localKillsCount && localKillsCount > 0 && nodeVars.solarSystemId && (
|
||||
<KillsCounter
|
||||
killsCount={localKillsCount}
|
||||
systemId={nodeVars.solarSystemId}
|
||||
size="lg"
|
||||
killsActivityType={nodeVars.killsActivityType}
|
||||
className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}
|
||||
>
|
||||
<div className={clsx(classes.BookmarkWithIcon)}>
|
||||
<span className={clsx(PrimeIcons.BOLT, classes.icon)} />
|
||||
<span className={clsx(classes.text)}>{nodeVars.killsCount}</span>
|
||||
</div>
|
||||
</KillsCounter>
|
||||
)}
|
||||
|
||||
{nodeVars.labelsInfo.map(x => (
|
||||
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
||||
{x.shortName}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={clsx(
|
||||
classes.RootCustomNode,
|
||||
nodeVars.regionClass && classes[nodeVars.regionClass],
|
||||
nodeVars.status !== undefined ? classes[STATUS_CLASSES[nodeVars.status]] : '',
|
||||
{ [classes.selected]: nodeVars.selected },
|
||||
)}
|
||||
onMouseDownCapture={e => nodeVars.dbClick(e)}
|
||||
>
|
||||
{nodeVars.visible && (
|
||||
<>
|
||||
<div className={classes.HeadRow}>
|
||||
<div
|
||||
className={clsx(
|
||||
classes.classTitle,
|
||||
nodeVars.classTitleColor,
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]',
|
||||
)}
|
||||
>
|
||||
{nodeVars.classTitle ?? '-'}
|
||||
</div>
|
||||
|
||||
{nodeVars.tag != null && nodeVars.tag !== '' && (
|
||||
<div className={clsx(classes.TagTitle, 'text-sky-400 font-medium')}>{nodeVars.tag}</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={clsx(
|
||||
classes.classSystemName,
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
|
||||
)}
|
||||
>
|
||||
{nodeVars.systemName}
|
||||
</div>
|
||||
|
||||
{nodeVars.isWormhole && (
|
||||
<div className={classes.statics}>
|
||||
{nodeVars.sortedStatics.map(whClass => (
|
||||
<WormholeClassComp key={String(whClass)} id={String(whClass)} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nodeVars.effectName !== null && nodeVars.isWormhole && (
|
||||
<div className={clsx(classes.effect, EFFECT_BACKGROUND_STYLES[nodeVars.effectName])} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
|
||||
{nodeVars.customName && (
|
||||
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-blue-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||
{nodeVars.customName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!nodeVars.isWormhole && !nodeVars.customName && (
|
||||
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-stone-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||
{nodeVars.regionName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nodeVars.isWormhole && !nodeVars.customName && <div />}
|
||||
|
||||
<div className="flex items-center gap-1 justify-end">
|
||||
<div className={clsx('flex items-center gap-1')}>
|
||||
{nodeVars.locked && <i className={clsx(PrimeIcons.LOCK, classes.lockIcon)} />}
|
||||
{nodeVars.hubs.includes(nodeVars.solarSystemId) && (
|
||||
<i className={clsx(PrimeIcons.MAP_MARKER, classes.mapMarker)} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<LocalCounter
|
||||
hasUserCharacters={nodeVars.hasUserCharacters}
|
||||
localCounterCharacters={localCounterCharacters}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{nodeVars.visible && (
|
||||
<>
|
||||
{nodeVars.unsplashedLeft.length > 0 && (
|
||||
<div className={classes.Unsplashed}>
|
||||
{nodeVars.unsplashedLeft.map(sig => (
|
||||
<UnsplashedSignature key={sig.eve_id} signature={sig} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nodeVars.unsplashedRight.length > 0 && (
|
||||
<div className={clsx(classes.Unsplashed, classes['Unsplashed--right'])}>
|
||||
{nodeVars.unsplashedRight.map(sig => (
|
||||
<UnsplashedSignature key={sig.eve_id} signature={sig} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className={classes.Handlers}>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleTop, {
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Top}
|
||||
id="a"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleRight, {
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Right}
|
||||
id="b"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleBottom, {
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Bottom}
|
||||
id="c"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleLeft, {
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Left}
|
||||
id="d"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
SolarSystemNodeDefault.displayName = 'SolarSystemNodeDefault';
|
||||
@@ -0,0 +1,20 @@
|
||||
@import './SolarSystemNodeDefault.module.scss';
|
||||
|
||||
/* ---------------------------------------------
|
||||
Only override what's different from the base
|
||||
Currently none required
|
||||
---------------------------------------------- */
|
||||
|
||||
.RootCustomNode {
|
||||
&.eve-system-status-home {
|
||||
border: 1px solid var(--eve-solar-system-status-color-home-dark30);
|
||||
background-image: linear-gradient(
|
||||
275deg,
|
||||
var(--eve-solar-system-status-home),
|
||||
transparent
|
||||
);
|
||||
&.selected {
|
||||
border-color: var(--eve-solar-system-status-color-home);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
import { memo } from 'react';
|
||||
import { MapSolarSystemType } from '../../map.types';
|
||||
import { Handle, NodeProps, Position } from 'reactflow';
|
||||
import clsx from 'clsx';
|
||||
import classes from './SolarSystemNodeTheme.module.scss';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks/useSolarSystemLogic';
|
||||
import {
|
||||
EFFECT_BACKGROUND_STYLES,
|
||||
MARKER_BOOKMARK_BG_STYLES,
|
||||
STATUS_CLASSES,
|
||||
} from '@/hooks/Mapper/components/map/constants';
|
||||
import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp';
|
||||
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
|
||||
import { LocalCounter } from './SolarSystemLocalCounter';
|
||||
import { KillsCounter } from './SolarSystemKillsCounter';
|
||||
|
||||
export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>) => {
|
||||
const nodeVars = useSolarSystemNode(props);
|
||||
const { localCounterCharacters } = useLocalCounter(nodeVars);
|
||||
const localKillsCount = useNodeKillsCount(nodeVars.solarSystemId, nodeVars.killsCount);
|
||||
|
||||
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>
|
||||
)}
|
||||
|
||||
{localKillsCount && localKillsCount > 0 && nodeVars.solarSystemId && (
|
||||
<KillsCounter
|
||||
killsCount={localKillsCount}
|
||||
systemId={nodeVars.solarSystemId}
|
||||
size="lg"
|
||||
killsActivityType={nodeVars.killsActivityType}
|
||||
className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}
|
||||
>
|
||||
<div className={clsx(classes.BookmarkWithIcon)}>
|
||||
<span className={clsx(PrimeIcons.BOLT, classes.icon)} />
|
||||
<span className={clsx(classes.text)}>{nodeVars.killsCount}</span>
|
||||
</div>
|
||||
</KillsCounter>
|
||||
)}
|
||||
|
||||
{nodeVars.labelsInfo.map(x => (
|
||||
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
||||
{x.shortName}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={clsx(
|
||||
classes.RootCustomNode,
|
||||
nodeVars.regionClass && classes[nodeVars.regionClass],
|
||||
nodeVars.status !== undefined ? classes[STATUS_CLASSES[nodeVars.status]] : '',
|
||||
{ [classes.selected]: nodeVars.selected },
|
||||
)}
|
||||
onMouseDownCapture={e => nodeVars.dbClick(e)}
|
||||
>
|
||||
{nodeVars.visible && (
|
||||
<>
|
||||
<div className={classes.HeadRow}>
|
||||
<div
|
||||
className={clsx(
|
||||
classes.classTitle,
|
||||
nodeVars.classTitleColor,
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]',
|
||||
)}
|
||||
>
|
||||
{nodeVars.classTitle ?? '-'}
|
||||
</div>
|
||||
|
||||
{nodeVars.tag != null && nodeVars.tag !== '' && (
|
||||
<div className={clsx(classes.TagTitle)}>{nodeVars.tag}</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={clsx(
|
||||
classes.classSystemName,
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap',
|
||||
)}
|
||||
>
|
||||
{nodeVars.systemName}
|
||||
</div>
|
||||
|
||||
{nodeVars.isWormhole && (
|
||||
<div className={classes.statics}>
|
||||
{nodeVars.sortedStatics.map(whClass => (
|
||||
<WormholeClassComp key={String(whClass)} id={String(whClass)} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nodeVars.effectName !== null && nodeVars.isWormhole && (
|
||||
<div className={clsx(classes.effect, EFFECT_BACKGROUND_STYLES[nodeVars.effectName])} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<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',
|
||||
)}
|
||||
>
|
||||
{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',
|
||||
)}
|
||||
>
|
||||
{nodeVars.regionName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nodeVars.isWormhole && !nodeVars.customName && <div />}
|
||||
|
||||
<div className="flex items-center gap-1 justify-end">
|
||||
<div className={clsx('flex items-center gap-1')}>
|
||||
{nodeVars.locked && <i className={clsx(PrimeIcons.LOCK, classes.lockIcon)} />}
|
||||
{nodeVars.hubs.includes(nodeVars.solarSystemId) && (
|
||||
<i className={clsx(PrimeIcons.MAP_MARKER, classes.mapMarker)} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<LocalCounter
|
||||
hasUserCharacters={nodeVars.hasUserCharacters}
|
||||
localCounterCharacters={localCounterCharacters}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{nodeVars.visible && (
|
||||
<>
|
||||
{nodeVars.unsplashedLeft.length > 0 && (
|
||||
<div className={classes.Unsplashed}>
|
||||
{nodeVars.unsplashedLeft.map(sig => (
|
||||
<UnsplashedSignature key={sig.eve_id} signature={sig} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nodeVars.unsplashedRight.length > 0 && (
|
||||
<div className={clsx(classes.Unsplashed, classes['Unsplashed--right'])}>
|
||||
{nodeVars.unsplashedRight.map(sig => (
|
||||
<UnsplashedSignature key={sig.eve_id} signature={sig} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className={classes.Handlers}>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleTop, {
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Top}
|
||||
id="a"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleRight, {
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Right}
|
||||
id="b"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleBottom, {
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Bottom}
|
||||
id="c"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleLeft, {
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Left}
|
||||
id="d"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
SolarSystemNodeTheme.displayName = 'SolarSystemNodeTheme';
|
||||
@@ -1 +1,2 @@
|
||||
export * from './SolarSystemNode';
|
||||
export * from './SolarSystemNodeDefault';
|
||||
export * from './SolarSystemNodeTheme';
|
||||
|
||||
@@ -9,10 +9,14 @@
|
||||
width: 13px;
|
||||
height: 4px;
|
||||
border-radius: 4px;
|
||||
color: #ffffff;
|
||||
color: var(--text-color);
|
||||
font-size: 8px;
|
||||
text-align: center;
|
||||
font-weight: bolder;
|
||||
display: block;
|
||||
}
|
||||
|
||||
& > .Eol {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import { WORMHOLE_CLASS_STYLES, WORMHOLES_ADDITIONAL_INFO } from '@/hooks/Mapper
|
||||
import { useMemo } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { renderInfoColumn } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
||||
|
||||
import { k162Types } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
|
||||
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
|
||||
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo.ts';
|
||||
|
||||
interface UnsplashedSignatureProps {
|
||||
signature: SystemSignature;
|
||||
@@ -22,17 +22,22 @@ export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) =>
|
||||
const whData = useMemo(() => wormholesData[signature.type], [signature.type, wormholesData]);
|
||||
const whClass = useMemo(() => (whData ? WORMHOLES_ADDITIONAL_INFO[whData.dest] : null), [whData]);
|
||||
|
||||
const k162TypeOption = useMemo(() => {
|
||||
if (!signature.custom_info) {
|
||||
return null;
|
||||
}
|
||||
const customInfo = JSON.parse(signature.custom_info);
|
||||
if (!customInfo.k162Type) {
|
||||
return null;
|
||||
}
|
||||
return k162Types.find(x => x.value === customInfo.k162Type);
|
||||
const customInfo = useMemo(() => {
|
||||
return parseSignatureCustomInfo(signature.custom_info);
|
||||
}, [signature]);
|
||||
|
||||
const k162TypeOption = useMemo(() => {
|
||||
if (!customInfo?.k162Type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return K162_TYPES_MAP[customInfo.k162Type];
|
||||
}, [customInfo]);
|
||||
|
||||
const isEOL = useMemo(() => {
|
||||
return customInfo?.isEOL;
|
||||
}, [customInfo]);
|
||||
|
||||
const whClassStyle = useMemo(() => {
|
||||
if (signature.type === 'K162' && k162TypeOption) {
|
||||
const k162Data = wormholesData[k162TypeOption.whClassName];
|
||||
@@ -45,19 +50,19 @@ export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) =>
|
||||
return (
|
||||
<WdTooltipWrapper
|
||||
className={clsx(classes.Signature)}
|
||||
// @ts-ignore
|
||||
content={
|
||||
(
|
||||
<div className="flex flex-col gap-1">
|
||||
<InfoDrawer title={<b className="text-slate-50">{signature.eve_id}</b>}>
|
||||
{renderInfoColumn(signature)}
|
||||
</InfoDrawer>
|
||||
</div>
|
||||
) as React.ReactNode
|
||||
<div className="flex flex-col gap-1">
|
||||
<InfoDrawer title={<b className="text-slate-50">{signature.eve_id}</b>}>
|
||||
{renderInfoColumn(signature)}
|
||||
</InfoDrawer>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className={clsx(classes.Box, whClassStyle)}>
|
||||
<svg width="13" height="4" viewBox="0 0 13 4" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="13" height="4" rx="2" className={whClassStyle} fill="currentColor" />
|
||||
<svg width="13" height="8" viewBox="0 0 13 8" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="1" width="13" height="4" rx="2" className={whClassStyle} fill="currentColor" />
|
||||
{isEOL && <rect x="4" width="5" height="6" rx="1" className={clsx(classes.Eol)} fill="#a153ac" />}
|
||||
</svg>
|
||||
</div>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConnectionType, MassState } from '@/hooks/Mapper/types';
|
||||
import { ConnectionType, MassState, ShipSizeStatus } from '@/hooks/Mapper/types';
|
||||
|
||||
export enum SOLAR_SYSTEM_CLASS_IDS {
|
||||
ccp1 = -1,
|
||||
@@ -727,16 +727,41 @@ export const MASS_STATE_NAMES = {
|
||||
[MassState.verge]: 'Verge of collapse',
|
||||
};
|
||||
|
||||
// export const SHIP_SIZES_NAMES_ORDER = [
|
||||
// ShipSizeStatus.small,
|
||||
// ShipSizeStatus.normal,
|
||||
// // ShipSizeStatus.large,
|
||||
// // ShipSizeStatus.capital,
|
||||
// ];
|
||||
//
|
||||
// export const SHIP_SIZES_NAMES = {
|
||||
// [ShipSizeStatus.small]: 'Frigate',
|
||||
// [ShipSizeStatus.normal]: 'Normal',
|
||||
// // [ShipSizeStatus.large]: 'Normal',
|
||||
// // [ShipSizeStatus.capital]: 'Normal',
|
||||
// };
|
||||
export const SHIP_SIZES_NAMES_ORDER = [
|
||||
ShipSizeStatus.small,
|
||||
ShipSizeStatus.medium,
|
||||
ShipSizeStatus.large,
|
||||
ShipSizeStatus.freight,
|
||||
ShipSizeStatus.capital,
|
||||
];
|
||||
|
||||
export const SHIP_SIZES_NAMES = {
|
||||
[ShipSizeStatus.small]: 'Frigate',
|
||||
[ShipSizeStatus.medium]: 'Medium',
|
||||
[ShipSizeStatus.large]: 'Normal',
|
||||
[ShipSizeStatus.freight]: 'Huge',
|
||||
[ShipSizeStatus.capital]: 'Capital',
|
||||
};
|
||||
export const SHIP_SIZES_SIZE = {
|
||||
[ShipSizeStatus.small]: '5K',
|
||||
[ShipSizeStatus.medium]: '62K',
|
||||
[ShipSizeStatus.large]: '375K',
|
||||
[ShipSizeStatus.freight]: '1M',
|
||||
[ShipSizeStatus.capital]: '2M',
|
||||
};
|
||||
|
||||
export const SHIP_SIZES_DESCRIPTION = {
|
||||
[ShipSizeStatus.small]: 'Frigate wormhole - up to Destroyer | 5K t.',
|
||||
[ShipSizeStatus.medium]: 'Cruise wormhole - up to Battlecruiser | 62K t.',
|
||||
[ShipSizeStatus.large]: 'Large wormhole - up to Battleship | 375K t.',
|
||||
[ShipSizeStatus.freight]: 'Huge wormhole - up to Freighter | 1M t.',
|
||||
[ShipSizeStatus.capital]: 'Capital wormhole - up to Capital | 2M t.',
|
||||
};
|
||||
|
||||
export const SHIP_SIZES_NAMES_SHORT = {
|
||||
[ShipSizeStatus.small]: 'S',
|
||||
[ShipSizeStatus.medium]: 'M',
|
||||
[ShipSizeStatus.large]: 'L',
|
||||
[ShipSizeStatus.freight]: 'H',
|
||||
[ShipSizeStatus.capital]: 'XL',
|
||||
};
|
||||
|
||||
@@ -10,5 +10,6 @@ export const convertSystem2Node = (sys: SolarSystemRawType): Node => {
|
||||
position: sys.position,
|
||||
data: sys,
|
||||
draggable: !sys.locked,
|
||||
deletable: !sys.locked,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { SolarSystemNodeDefault, SolarSystemNodeTheme } from '../components/SolarSystemNode';
|
||||
import type { NodeProps } from 'reactflow';
|
||||
import type { ComponentType } from 'react';
|
||||
import { MapSolarSystemType } from '../map.types';
|
||||
import { ConnectionMode } from 'reactflow';
|
||||
|
||||
export type SolarSystemNodeComponent = ComponentType<NodeProps<MapSolarSystemType>>;
|
||||
|
||||
interface ThemeBehavior {
|
||||
isPanAndDrag: boolean;
|
||||
nodeComponent: SolarSystemNodeComponent;
|
||||
connectionMode: ConnectionMode;
|
||||
}
|
||||
|
||||
const THEME_BEHAVIORS: {
|
||||
[key: string]: ThemeBehavior;
|
||||
} = {
|
||||
default: {
|
||||
isPanAndDrag: false,
|
||||
nodeComponent: SolarSystemNodeDefault,
|
||||
connectionMode: ConnectionMode.Loose,
|
||||
},
|
||||
pathfinder: {
|
||||
isPanAndDrag: true,
|
||||
nodeComponent: SolarSystemNodeTheme,
|
||||
connectionMode: ConnectionMode.Loose,
|
||||
},
|
||||
};
|
||||
|
||||
export function getBehaviorForTheme(themeName: string) {
|
||||
return THEME_BEHAVIORS[themeName] ?? THEME_BEHAVIORS.default;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export const useMapUpdateSystems = () => {
|
||||
return newSystem;
|
||||
});
|
||||
|
||||
update({ systems: out });
|
||||
update({ systems: out }, true);
|
||||
},
|
||||
[rf, update],
|
||||
);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { BackgroundVariant } from 'reactflow';
|
||||
|
||||
export function useBackgroundVars(themeName?: string) {
|
||||
const [variant, setVariant] = useState<BackgroundVariant>(BackgroundVariant.Dots);
|
||||
const [gap, setGap] = useState<number>(16);
|
||||
const [size, setSize] = useState<number>(1);
|
||||
const [color, setColor] = useState('#81818b');
|
||||
const [snapSize, setSnapSize] = useState<number>(25);
|
||||
|
||||
useEffect(() => {
|
||||
// match any element whose entire `class` attribute ends with "-theme"
|
||||
let themeEl = document.querySelector('[class$="-theme"]');
|
||||
|
||||
// If none is found, fall back to the <html> element
|
||||
if (!themeEl) {
|
||||
themeEl = document.documentElement;
|
||||
}
|
||||
|
||||
const style = getComputedStyle(themeEl as HTMLElement);
|
||||
|
||||
const rawVariant = style.getPropertyValue('--rf-bg-variant').replace(/['"]/g, '').trim().toLowerCase();
|
||||
let finalVariant: BackgroundVariant = BackgroundVariant.Dots;
|
||||
|
||||
if (rawVariant === 'lines') {
|
||||
finalVariant = BackgroundVariant.Lines;
|
||||
} else if (rawVariant === 'cross') {
|
||||
finalVariant = BackgroundVariant.Cross;
|
||||
}
|
||||
|
||||
const cssVarGap = style.getPropertyValue('--rf-bg-gap');
|
||||
const cssVarSize = style.getPropertyValue('--rf-bg-size');
|
||||
const cssVarSnapSize = style.getPropertyValue('--rf-snap-size');
|
||||
const cssColor = style.getPropertyValue('--rf-bg-pattern-color');
|
||||
|
||||
const gapNum = parseInt(cssVarGap, 10) || 16;
|
||||
const sizeNum = parseInt(cssVarSize, 10) || 1;
|
||||
const snapSize = parseInt(cssVarSnapSize, 10) || 25; //react-flow default
|
||||
|
||||
setVariant(finalVariant);
|
||||
setGap(gapNum);
|
||||
setSize(sizeNum);
|
||||
setColor(cssColor);
|
||||
setSnapSize(snapSize);
|
||||
}, [themeName]);
|
||||
|
||||
return { variant, gap, size, color, snapSize };
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useSystemKills } from '../../mapInterface/widgets/SystemKills/hooks/useSystemKills';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
interface UseKillsCounterProps {
|
||||
realSystemId: string;
|
||||
}
|
||||
|
||||
export function useKillsCounter({ realSystemId }: UseKillsCounterProps) {
|
||||
const { data: mapData, outCommand } = useMapRootState();
|
||||
const { systems } = mapData;
|
||||
|
||||
const systemNameMap = useMemo(() => {
|
||||
const m: Record<string, string> = {};
|
||||
systems.forEach(sys => {
|
||||
m[sys.id] = sys.temporary_name || sys.name || '???';
|
||||
});
|
||||
return m;
|
||||
}, [systems]);
|
||||
|
||||
const { kills: allKills, isLoading } = useSystemKills({
|
||||
systemId: realSystemId,
|
||||
outCommand,
|
||||
showAllVisible: false,
|
||||
});
|
||||
|
||||
const filteredKills = useMemo(() => {
|
||||
if (!allKills || allKills.length === 0) return [];
|
||||
|
||||
return [...allKills]
|
||||
.sort((a, b) => {
|
||||
const aTime = a.kill_time ? new Date(a.kill_time).getTime() : 0;
|
||||
const bTime = b.kill_time ? new Date(b.kill_time).getTime() : 0;
|
||||
return bTime - aTime;
|
||||
})
|
||||
.slice(0, 10);
|
||||
}, [allKills]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
kills: filteredKills,
|
||||
systemNameMap,
|
||||
};
|
||||
}
|
||||
@@ -70,7 +70,7 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
||||
setTimeout(() => addConnections(data as CommandAddConnections), 100);
|
||||
break;
|
||||
case Commands.removeConnections:
|
||||
removeConnections(data as CommandRemoveConnections);
|
||||
setTimeout(() => removeConnections(data as CommandRemoveConnections), 100);
|
||||
break;
|
||||
case Commands.charactersUpdated:
|
||||
charactersUpdated(data as CommandCharactersUpdated);
|
||||
@@ -127,6 +127,10 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.detailedKillsUpdated:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`Map handlers: Unknown command: ${type}`, data);
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { MapSolarSystemType } from '../map.types';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider';
|
||||
import { useDoubleClick } from '@/hooks/Mapper/hooks/useDoubleClick';
|
||||
import { REGIONS_MAP, Spaces } from '@/hooks/Mapper/constants';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace';
|
||||
import { getSystemClassStyles, prepareUnsplashedChunks } from '@/hooks/Mapper/components/map/helpers';
|
||||
import { sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager';
|
||||
import { CharacterTypeRaw, Commands, OutCommand, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { LABELS_INFO, LABELS_ORDER } from '@/hooks/Mapper/components/map/constants';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
|
||||
export type LabelInfo = {
|
||||
id: string;
|
||||
shortName: string;
|
||||
};
|
||||
|
||||
export type UnsplashedSignatureType = SystemSignature & { sig_id: string };
|
||||
|
||||
function getActivityType(count: number): string {
|
||||
if (count <= 5) return 'activityNormal';
|
||||
if (count <= 30) return 'activityWarn';
|
||||
return 'activityDanger';
|
||||
}
|
||||
|
||||
const SpaceToClass: Record<string, string> = {
|
||||
[Spaces.Caldari]: 'Caldaria',
|
||||
[Spaces.Matar]: 'Mataria',
|
||||
[Spaces.Amarr]: 'Amarria',
|
||||
[Spaces.Gallente]: 'Gallente',
|
||||
};
|
||||
|
||||
function sortedLabels(labels: string[]): LabelInfo[] {
|
||||
if (!labels) return [];
|
||||
return LABELS_ORDER.filter(x => labels.includes(x)).map(x => LABELS_INFO[x] as LabelInfo);
|
||||
}
|
||||
|
||||
export function useLocalCounter(nodeVars: SolarSystemNodeVars) {
|
||||
const localCounterCharacters = useMemo(() => {
|
||||
return nodeVars.charactersInSystem
|
||||
.map(char => ({
|
||||
...char,
|
||||
compact: true,
|
||||
isOwn: nodeVars.userCharacters.includes(char.eve_id),
|
||||
}))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}, [nodeVars.charactersInSystem, nodeVars.userCharacters]);
|
||||
return { localCounterCharacters };
|
||||
}
|
||||
|
||||
export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarSystemNodeVars {
|
||||
const { id, data, selected } = props;
|
||||
const {
|
||||
system_static_info,
|
||||
system_signatures,
|
||||
locked,
|
||||
name,
|
||||
tag,
|
||||
status,
|
||||
labels,
|
||||
temporary_name,
|
||||
linked_sig_eve_id: linkedSigEveId = '',
|
||||
} = data;
|
||||
|
||||
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();
|
||||
|
||||
const { isShowUnsplashedSignatures } = interfaceSettings;
|
||||
const isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
|
||||
const isShowLinkedSigId = useMapGetOption('show_linked_signature_id') === 'true';
|
||||
const isShowLinkedSigIdTempName = useMapGetOption('show_linked_signature_id_temp_name') === 'true';
|
||||
|
||||
const {
|
||||
data: {
|
||||
characters,
|
||||
wormholesData,
|
||||
hubs,
|
||||
kills,
|
||||
userCharacters,
|
||||
isConnecting,
|
||||
hoverNodeId,
|
||||
visibleNodes,
|
||||
showKSpaceBG,
|
||||
isThickConnections,
|
||||
},
|
||||
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 charactersInSystem = useMemo(() => {
|
||||
return characters.filter(c => c.location?.solar_system_id === solar_system_id && c.online);
|
||||
}, [characters, solar_system_id]);
|
||||
|
||||
const isWormhole = isWormholeSpace(system_class);
|
||||
|
||||
const classTitleColor = useMemo(
|
||||
() => getSystemClassStyles({ systemClass: system_class, security }),
|
||||
[security, system_class],
|
||||
);
|
||||
|
||||
const sortedStatics = useMemo(() => sortWHClasses(wormholesData, statics), [wormholesData, statics]);
|
||||
|
||||
const linkedSigPrefix = useMemo(() => (linkedSigEveId ? linkedSigEveId.split('-')[0] : null), [linkedSigEveId]);
|
||||
|
||||
const labelsManager = useMemo(() => new LabelsManager(labels ?? ''), [labels]);
|
||||
const labelsInfo = useMemo(() => sortedLabels(labelsManager.list), [labelsManager]);
|
||||
const labelCustom = useMemo(() => {
|
||||
if (isShowLinkedSigId && linkedSigPrefix) {
|
||||
return labelsManager.customLabel ? `${linkedSigPrefix}・${labelsManager.customLabel}` : linkedSigPrefix;
|
||||
}
|
||||
return labelsManager.customLabel;
|
||||
}, [linkedSigPrefix, isShowLinkedSigId, labelsManager]);
|
||||
|
||||
const killsCount = useMemo(() => kills[solar_system_id] ?? null, [kills, solar_system_id]);
|
||||
const killsActivityType = killsCount ? getActivityType(killsCount) : null;
|
||||
|
||||
const hasUserCharacters = useMemo(() => {
|
||||
return charactersInSystem.some(x => userCharacters.includes(x.eve_id));
|
||||
}, [charactersInSystem, userCharacters]);
|
||||
|
||||
const dbClick = useDoubleClick(() => {
|
||||
outCommand({
|
||||
type: OutCommand.openSettings,
|
||||
data: { system_id: solar_system_id.toString() },
|
||||
});
|
||||
});
|
||||
|
||||
const showHandlers = isConnecting || hoverNodeId === id;
|
||||
|
||||
const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
|
||||
const regionClass = showKSpaceBG ? SpaceToClass[space] : null;
|
||||
|
||||
const computedTemporaryName = useMemo(() => {
|
||||
if (!isTempSystemNameEnabled) {
|
||||
return '';
|
||||
}
|
||||
if (isShowLinkedSigIdTempName && linkedSigPrefix) {
|
||||
return temporary_name ? `${linkedSigPrefix}・${temporary_name}` : `${linkedSigPrefix}・${solar_system_name}`;
|
||||
}
|
||||
return temporary_name;
|
||||
}, [isShowLinkedSigIdTempName, isTempSystemNameEnabled, linkedSigPrefix, solar_system_name, temporary_name]);
|
||||
|
||||
const systemName = useMemo(() => {
|
||||
if (isTempSystemNameEnabled && computedTemporaryName) {
|
||||
return computedTemporaryName;
|
||||
}
|
||||
return solar_system_name;
|
||||
}, [isTempSystemNameEnabled, solar_system_name, computedTemporaryName]);
|
||||
|
||||
const customName = useMemo(() => {
|
||||
if (isTempSystemNameEnabled && computedTemporaryName && name) {
|
||||
return name;
|
||||
}
|
||||
if (solar_system_name !== name && name) {
|
||||
return name;
|
||||
}
|
||||
return null;
|
||||
}, [isTempSystemNameEnabled, computedTemporaryName, name, solar_system_name]);
|
||||
|
||||
const [unsplashedLeft, unsplashedRight] = useMemo(() => {
|
||||
if (!isShowUnsplashedSignatures) {
|
||||
return [[], []];
|
||||
}
|
||||
return prepareUnsplashedChunks(
|
||||
systemSigs
|
||||
.filter(s => s.group === 'Wormhole' && !s.linked_system)
|
||||
.map(s => ({
|
||||
eve_id: s.eve_id,
|
||||
type: s.type,
|
||||
custom_info: s.custom_info,
|
||||
kind: s.kind,
|
||||
name: s.name,
|
||||
group: s.group,
|
||||
})) as UnsplashedSignatureType[],
|
||||
);
|
||||
}, [isShowUnsplashedSignatures, systemSigs]);
|
||||
|
||||
// Ensure hubs are always strings.
|
||||
const hubsAsStrings = useMemo(() => hubs.map(item => item.toString()), [hubs]);
|
||||
|
||||
const nodeVars: SolarSystemNodeVars = {
|
||||
id,
|
||||
selected,
|
||||
visible,
|
||||
isWormhole,
|
||||
classTitleColor,
|
||||
killsCount,
|
||||
killsActivityType,
|
||||
hasUserCharacters,
|
||||
userCharacters,
|
||||
showHandlers,
|
||||
regionClass,
|
||||
systemName,
|
||||
customName,
|
||||
labelCustom,
|
||||
isShattered: is_shattered,
|
||||
tag,
|
||||
status,
|
||||
labelsInfo,
|
||||
dbClick,
|
||||
sortedStatics,
|
||||
effectName: effect_name,
|
||||
regionName: region_name,
|
||||
solarSystemId: solar_system_id.toString(),
|
||||
solarSystemName: solar_system_name,
|
||||
locked,
|
||||
hubs: hubsAsStrings,
|
||||
name: name,
|
||||
isConnecting,
|
||||
hoverNodeId,
|
||||
charactersInSystem,
|
||||
unsplashedLeft,
|
||||
unsplashedRight,
|
||||
isThickConnections,
|
||||
classTitle: class_title,
|
||||
temporaryName: computedTemporaryName,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export function useNodeKillsCount(systemId: number | string, initialKillsCount: number | null): number | null {
|
||||
const [killsCount, setKillsCount] = useState<number | null>(initialKillsCount);
|
||||
|
||||
useEffect(() => {
|
||||
setKillsCount(initialKillsCount);
|
||||
}, [initialKillsCount]);
|
||||
|
||||
useMapEventListener(event => {
|
||||
if (event.name === Commands.killsUpdated && event.data?.toString() === systemId.toString()) {
|
||||
//@ts-ignore
|
||||
if (event.payload && typeof event.payload.kills === 'number') {
|
||||
// @ts-ignore
|
||||
setKillsCount(event.payload.kills);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return killsCount;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types/system';
|
||||
import { SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||
import { XYPosition } from 'reactflow';
|
||||
|
||||
export type MapSolarSystemType = Omit<SolarSystemRawType, 'position'>;
|
||||
|
||||
@@ -7,3 +8,5 @@ export type OnMapSelectionChange = (event: {
|
||||
systems: string[];
|
||||
connections: Pick<SolarSystemConnection, 'source' | 'target'>[];
|
||||
}) => void;
|
||||
|
||||
export type OnMapAddSystemCallback = (props: { coordinates: XYPosition | null }) => void;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
@import './eve-common-variables';
|
||||
@import './eve-common';
|
||||
|
||||
.default-theme {
|
||||
--rf-bg-color: #0C0A09;
|
||||
--rf-soft-bg-color: #171717;
|
||||
|
||||
--rf-node-bg-color: #202020;
|
||||
--rf-node-soft-bg-color: #202020;
|
||||
--rf-text-color: #ffffff;
|
||||
--rf-tag-color: #38BDF8;
|
||||
--rf-region-name: #D6D3D1;
|
||||
--rf-custom-name: #93C5FD;
|
||||
--rf-node-font-family: 'Shentox', 'Rogan', sans-serif !important;
|
||||
--rf-node-font-weight: 500;
|
||||
|
||||
--rf-bg-variant: "dots";
|
||||
--rf-bg-gap: 15;
|
||||
--rf-bg-size: 1;
|
||||
--rf-bg-pattern-color: #81818a;
|
||||
|
||||
--pastel-blue: #5a7d9a;
|
||||
--pastel-pink: #d291bc;
|
||||
--pastel-green: #88b04b;
|
||||
--pastel-yellow: #ffdd59;
|
||||
|
||||
--dark-bg: #2d2d2d;
|
||||
--text-color: #ffffff;
|
||||
--tooltip-bg: #202020;
|
||||
|
||||
--window-corner: #72716f;
|
||||
|
||||
--rf-local-counter-font-weight: 500;
|
||||
--rf-node-local-counter: inherit;
|
||||
--rf-has-user-characters: #ffc75d;
|
||||
}
|
||||
@@ -1,78 +1,122 @@
|
||||
$eve-link-color-default: #333;
|
||||
$eve-link-color-top-mass-0: #333;
|
||||
$eve-link-color-top-mass-1: #5a4520;
|
||||
$eve-link-color-top-mass-2: #672c2c;
|
||||
$eve-link-color-middle-mass-0: #333;
|
||||
$eve-link-color-middle-mass-1: #333;
|
||||
$eve-link-color-middle-mass-2: #333;
|
||||
$eve-link-color-middle-time-0: #5c5c5c;
|
||||
$eve-link-color-middle-time-1: #ff00cd;
|
||||
$eve-link-color-middle-time-1-border: #99f3ff;
|
||||
|
||||
$eve-link-color-top-mass-1-time-1: #796300;
|
||||
$eve-link-color-top-mass-2-time-1: #8c1717;
|
||||
$eve-link-color-temp: orange;
|
||||
$friendlyBase: #3bbd39;
|
||||
$friendlyAlpha: #3bbd3952;
|
||||
$friendlyDark20: darken($friendlyBase, 20%);
|
||||
$friendlyDark30: darken($friendlyBase, 30%);
|
||||
$friendlyDark5: darken($friendlyBase, 5%);
|
||||
|
||||
$eve-effect-pulsar: #40aef5;
|
||||
$eve-effect-magnetar: #f058f8;
|
||||
$eve-effect-wolfRayet: #ef7843;
|
||||
$eve-effect-blackHole: #1b1b1b;
|
||||
$eve-effect-cataclysmicVariable: #ffea90;
|
||||
$eve-effect-redGiant: #fd3c3c;
|
||||
$eve-effect-dazhLiminalityLocus: #ff6464;
|
||||
$eve-effect-imperialStellarObservatory: #6991ce;
|
||||
$eve-effect-stateStellarObservatory: #6991ce;
|
||||
$eve-effect-republicStellarObservatory: #6991ce;
|
||||
$eve-effect-federalStellarObservatory: #6991ce;
|
||||
$lookingForBase: #43c2fd;
|
||||
$lookingForAlpha: rgba(67, 176, 253, 0.48);
|
||||
$lookingForDark15: darken($lookingForBase, 15%);
|
||||
|
||||
$eve-wh-type-color-high: #5dffd2;
|
||||
$eve-wh-type-color-low: #f79400;
|
||||
$eve-wh-type-color-null: #fc3c3c;
|
||||
$eve-wh-type-color-c1: #69bfce;
|
||||
$eve-wh-type-color-c2: #6991ce;
|
||||
$eve-wh-type-color-c3: #a8cb70;
|
||||
$eve-wh-type-color-c4: #e39c68;
|
||||
$eve-wh-type-color-c5: #de8686;
|
||||
$eve-wh-type-color-c6: #e76363;
|
||||
$eve-wh-type-color-c13: #988cb5;
|
||||
$eve-wh-type-color-drifter: #ff44f6;
|
||||
$eve-wh-type-color-thera: #ffffff;
|
||||
$eve-wh-type-color-zarzakh: #212121;
|
||||
$homeBase: rgb(179, 253, 67);
|
||||
$homeAlpha: rgba(186, 248, 48, 0.32);
|
||||
$homeBackground: #a0fa5636;
|
||||
$homeDark30: darken($homeBase, 30%);
|
||||
|
||||
$eve-security-color-10: #2c74df;
|
||||
$eve-security-color-09: #3998e8;
|
||||
$eve-security-color-08: #4dcbf5;
|
||||
$eve-security-color-07: #60d8a2;
|
||||
$eve-security-color-06: #71e454;
|
||||
$eve-security-color-05: #f2fc81;
|
||||
$eve-security-color-04: #d96c07;
|
||||
$eve-security-color-03: #cb440f;
|
||||
$eve-security-color-02: #b91117;
|
||||
$eve-security-color-01: #732020;
|
||||
$eve-security-color-00: #8b3263;
|
||||
$eve-security-color-m-01: #8b3263;
|
||||
$eve-security-color-m-02: #8b3263;
|
||||
$eve-security-color-m-03: #8b3263;
|
||||
$eve-security-color-m-04: #8b3263;
|
||||
$eve-security-color-m-05: #8b3263;
|
||||
$eve-security-color-m-06: #8b3263;
|
||||
$eve-security-color-m-07: #8b3263;
|
||||
$eve-security-color-m-08: #8b3263;
|
||||
$eve-security-color-m-09: #8b3263;
|
||||
$eve-security-color-m-10: #8b3263;
|
||||
:root {
|
||||
--pastel-blue: #5a7d9a;
|
||||
--pastel-pink: #d291bc;
|
||||
--pastel-green: #88b04b;
|
||||
--pastel-yellow: #ffdd59;
|
||||
--dark-bg: #2d2d2d;
|
||||
--text-color: #ffffff;
|
||||
--tooltip-bg: #202020;
|
||||
|
||||
$eve-solar-system-status-unknown: transparent;
|
||||
$eve-solar-system-status-friendly: #3bbd3952;
|
||||
$eve-solar-system-status-warning: #906518a6;
|
||||
$eve-solar-system-status-target: #b439ff6b;
|
||||
$eve-solar-system-status-dangerous: #d54040;
|
||||
$eve-solar-system-status-lookingFor: rgba(67, 176, 253, 0.48);
|
||||
$eve-solar-system-status-home: rgb(197, 253, 67);
|
||||
--pastel-blue-darken10: #4f6b86;
|
||||
--pastel-blue-lighten10: #6da3af;
|
||||
--pastel-pink-darken10: #bb7ca9;
|
||||
--pastel-pink-lighten10: #e0a6cb;
|
||||
--pastel-green-darken10: #79a244;
|
||||
--pastel-green-lighten10: #99cf52;
|
||||
--pastel-yellow-darken10: #e6c44f;
|
||||
--pastel-yellow-lighten10: #ffe874;
|
||||
|
||||
$eve-solar-system-status-color-unknown: transparent;
|
||||
$eve-solar-system-status-color-friendly: #3bbd39;
|
||||
$eve-solar-system-status-color-warning: #ffb93b;
|
||||
$eve-solar-system-status-color-target: #b439ff;
|
||||
$eve-solar-system-status-color-dangerous: #d54040;
|
||||
$eve-solar-system-status-color-lookingFor: #43c2fd;
|
||||
$eve-solar-system-status-color-home: rgb(197, 253, 67);
|
||||
--eve-link-color-default: #333;
|
||||
--eve-link-color-top-mass-0: #333;
|
||||
--eve-link-color-top-mass-1: #5a4520;
|
||||
--eve-link-color-top-mass-2: #672c2c;
|
||||
--eve-link-color-middle-mass-0: #333;
|
||||
--eve-link-color-middle-mass-1: #333;
|
||||
--eve-link-color-middle-mass-2: #333;
|
||||
--eve-link-color-middle-time-0: #5c5c5c;
|
||||
--eve-link-color-middle-time-1: #ff00cd;
|
||||
--eve-link-color-middle-time-1-border: #99f3ff;
|
||||
--eve-link-color-top-mass-1-time-1: #796300;
|
||||
--eve-link-color-top-mass-2-time-1: #8c1717;
|
||||
--eve-link-color-temp: orange;
|
||||
|
||||
--eve-effect-pulsar: #40aef5;
|
||||
--eve-effect-magnetar: #f058f8;
|
||||
--eve-effect-wolfRayet: #ef7843;
|
||||
--eve-effect-blackHole: #1b1b1b;
|
||||
--eve-effect-cataclysmicVariable: #ffea90;
|
||||
--eve-effect-redGiant: #fd3c3c;
|
||||
--eve-effect-dazhLiminalityLocus: #ff6464;
|
||||
--eve-effect-imperialStellarObservatory: #6991ce;
|
||||
--eve-effect-stateStellarObservatory: #6991ce;
|
||||
--eve-effect-republicStellarObservatory: #6991ce;
|
||||
--eve-effect-federalStellarObservatory: #6991ce;
|
||||
|
||||
--eve-wh-type-color-high: #5dffd2;
|
||||
--eve-wh-type-color-low: #f79400;
|
||||
--eve-wh-type-color-null: #fc3c3c;
|
||||
--eve-wh-type-color-c1: #69bfce;
|
||||
--eve-wh-type-color-c2: #6991ce;
|
||||
--eve-wh-type-color-c3: #a8cb70;
|
||||
--eve-wh-type-color-c4: #e39c68;
|
||||
--eve-wh-type-color-c5: #de8686;
|
||||
--eve-wh-type-color-c6: #e76363;
|
||||
--eve-wh-type-color-c13: #988cb5;
|
||||
--eve-wh-type-color-drifter: #ff44f6;
|
||||
--eve-wh-type-color-thera: #ffffff;
|
||||
--eve-wh-type-color-zarzakh: #212121;
|
||||
|
||||
--eve-security-color-10: #2c74df;
|
||||
--eve-security-color-09: #3998e8;
|
||||
--eve-security-color-08: #4dcbf5;
|
||||
--eve-security-color-07: #60d8a2;
|
||||
--eve-security-color-06: #71e454;
|
||||
--eve-security-color-05: #f2fc81;
|
||||
--eve-security-color-04: #d96c07;
|
||||
--eve-security-color-03: #cb440f;
|
||||
--eve-security-color-02: #b91117;
|
||||
--eve-security-color-01: #732020;
|
||||
--eve-security-color-00: #8b3263;
|
||||
--eve-security-color-m-01: #8b3263;
|
||||
--eve-security-color-m-02: #8b3263;
|
||||
--eve-security-color-m-03: #8b3263;
|
||||
--eve-security-color-m-04: #8b3263;
|
||||
--eve-security-color-m-05: #8b3263;
|
||||
--eve-security-color-m-06: #8b3263;
|
||||
--eve-security-color-m-07: #8b3263;
|
||||
--eve-security-color-m-08: #8b3263;
|
||||
--eve-security-color-m-09: #8b3263;
|
||||
--eve-security-color-m-10: #8b3263;
|
||||
|
||||
--eve-solar-system-status-unknown: transparent;
|
||||
--eve-solar-system-status-color-unknown: transparent;
|
||||
--eve-solar-system-status-home: #{$homeAlpha};
|
||||
--eve-solar-system-status-color-home: #{$homeBase};
|
||||
--eve-solar-system-status-color-background: #{$homeBackground};
|
||||
--eve-solar-system-status-color-home-dark30: #{$homeDark30};
|
||||
--eve-solar-system-status-friendly: #{$friendlyAlpha};
|
||||
--eve-solar-system-status-color-friendly: #{$friendlyBase};
|
||||
--eve-solar-system-status-friendly-dark30: #{$friendlyDark30};
|
||||
--eve-solar-system-status-color-friendly-dark20: #{$friendlyDark20};
|
||||
--eve-solar-system-status-color-friendly-dark5: #{$friendlyDark5};
|
||||
--eve-solar-system-status-lookingFor: #{$lookingForAlpha};
|
||||
--eve-solar-system-status-color-lookingFor: #{$lookingForBase};
|
||||
--eve-solar-system-status-color-lookingFor-dark15: #{$lookingForDark15};
|
||||
--eve-solar-system-status-warning: #906518a6;
|
||||
--eve-solar-system-status-color-warning: #ffb93b;
|
||||
--eve-solar-system-status-target: #b439ff6b;
|
||||
--eve-solar-system-status-color-target: #b439ff;
|
||||
--eve-solar-system-status-dangerous: #d54040;
|
||||
--eve-solar-system-status-color-dangerous: #d54040;
|
||||
|
||||
--conn-time-eol: #7452c3e3;
|
||||
--conn-frigate: #325d88;
|
||||
--conn-save: rgba(155, 102, 45, 0.85);
|
||||
--selected-item-bg: rgba(98, 98, 98, 0.33);
|
||||
}
|
||||
|
||||
@@ -1,535 +1,504 @@
|
||||
@import "eve-common-variables";
|
||||
@import './eve-common-variables';
|
||||
|
||||
|
||||
.eve-wh-effect-color-pulsar {
|
||||
fill: $eve-effect-pulsar;
|
||||
background-color: $eve-effect-pulsar;
|
||||
fill: var(--eve-effect-pulsar);
|
||||
background-color: var(--eve-effect-pulsar);
|
||||
}
|
||||
|
||||
.eve-wh-effect-color-magnetar {
|
||||
fill: $eve-effect-magnetar;
|
||||
background-color: $eve-effect-magnetar;
|
||||
fill: var(--eve-effect-magnetar);
|
||||
background-color: var(--eve-effect-magnetar);
|
||||
}
|
||||
|
||||
.eve-wh-effect-color-wolfRayet {
|
||||
fill: $eve-effect-wolfRayet;
|
||||
background-color: $eve-effect-wolfRayet;
|
||||
fill: var(--eve-effect-wolfRayet);
|
||||
background-color: var(--eve-effect-wolfRayet);
|
||||
}
|
||||
|
||||
.eve-wh-effect-color-blackHole {
|
||||
fill: $eve-effect-blackHole;
|
||||
background-color: $eve-effect-blackHole;
|
||||
box-shadow: 0 0 8px rgba(255 255 255 / 33);
|
||||
fill: var(--eve-effect-blackHole);
|
||||
background-color: var(--eve-effect-blackHole);
|
||||
box-shadow: 0 0 8px rgba(255, 255, 255, 0.33);
|
||||
}
|
||||
|
||||
.eve-wh-effect-color-cataclysmicVariable {
|
||||
fill: $eve-effect-cataclysmicVariable;
|
||||
background-color: $eve-effect-cataclysmicVariable;
|
||||
fill: var(--eve-effect-cataclysmicVariable);
|
||||
background-color: var(--eve-effect-cataclysmicVariable);
|
||||
}
|
||||
|
||||
.eve-wh-effect-color-redGiant {
|
||||
fill: $eve-effect-redGiant;
|
||||
background-color: $eve-effect-redGiant;
|
||||
fill: var(--eve-effect-redGiant);
|
||||
background-color: var(--eve-effect-redGiant);
|
||||
}
|
||||
|
||||
.text-eve-wh-effect-color-pulsar {
|
||||
color: $eve-effect-pulsar;
|
||||
color: var(--eve-effect-pulsar);
|
||||
}
|
||||
|
||||
.text-eve-wh-effect-color-magnetar {
|
||||
color: $eve-effect-magnetar;
|
||||
color: var(--eve-effect-magnetar);
|
||||
}
|
||||
|
||||
.text-eve-wh-effect-color-wolfRayet {
|
||||
color: $eve-effect-wolfRayet;
|
||||
color: var(--eve-effect-wolfRayet);
|
||||
}
|
||||
|
||||
.text-eve-wh-effect-color-blackHole {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.text-eve-wh-effect-color-cataclysmicVariable {
|
||||
color: $eve-effect-cataclysmicVariable;
|
||||
color: var(--eve-effect-cataclysmicVariable);
|
||||
}
|
||||
|
||||
.text-eve-wh-effect-color-redGiant {
|
||||
color: $eve-effect-redGiant;
|
||||
color: var(--eve-effect-redGiant);
|
||||
}
|
||||
|
||||
.text-eve-wh-effect-color-dazhLiminalityLocus {
|
||||
color: $eve-effect-dazhLiminalityLocus;
|
||||
color: var(--eve-effect-dazhLiminalityLocus);
|
||||
}
|
||||
|
||||
.text-eve-wh-effect-color-imperialStellarObservatory {
|
||||
color: $eve-effect-imperialStellarObservatory;
|
||||
color: var(--eve-effect-imperialStellarObservatory);
|
||||
}
|
||||
|
||||
.text-eve-wh-effect-color-stateStellarObservatory {
|
||||
color: $eve-effect-stateStellarObservatory;
|
||||
color: var(--eve-effect-stateStellarObservatory);
|
||||
}
|
||||
|
||||
.text-eve-wh-effect-color-republicStellarObservatory {
|
||||
color: $eve-effect-republicStellarObservatory;
|
||||
color: var(--eve-effect-republicStellarObservatory);
|
||||
}
|
||||
|
||||
.text-eve-wh-effect-color-federalStellarObservatory {
|
||||
color: $eve-effect-federalStellarObservatory;
|
||||
color: var(--eve-effect-federalStellarObservatory);
|
||||
}
|
||||
|
||||
/* Security color classes */
|
||||
.eve-security-color-10 {
|
||||
color: $eve-security-color-10 !important;
|
||||
fill: $eve-security-color-10;
|
||||
color: var(--eve-security-color-10) !important;
|
||||
fill: var(--eve-security-color-10);
|
||||
}
|
||||
|
||||
.eve-security-color-09 {
|
||||
color: $eve-security-color-09 !important;
|
||||
fill: $eve-security-color-09;
|
||||
color: var(--eve-security-color-09) !important;
|
||||
fill: var(--eve-security-color-09);
|
||||
}
|
||||
|
||||
.eve-security-color-08 {
|
||||
color: $eve-security-color-08 !important;
|
||||
fill: $eve-security-color-08;
|
||||
color: var(--eve-security-color-08) !important;
|
||||
fill: var(--eve-security-color-08);
|
||||
}
|
||||
|
||||
.eve-security-color-07 {
|
||||
color: $eve-security-color-07 !important;
|
||||
fill: $eve-security-color-07;
|
||||
color: var(--eve-security-color-07) !important;
|
||||
fill: var(--eve-security-color-07);
|
||||
}
|
||||
|
||||
.eve-security-color-06 {
|
||||
color: $eve-security-color-06 !important;
|
||||
fill: $eve-security-color-06;
|
||||
color: var(--eve-security-color-06) !important;
|
||||
fill: var(--eve-security-color-06);
|
||||
}
|
||||
|
||||
.eve-security-color-05 {
|
||||
color: $eve-security-color-05 !important;
|
||||
fill: $eve-security-color-05;
|
||||
color: var(--eve-security-color-05) !important;
|
||||
fill: var(--eve-security-color-05);
|
||||
}
|
||||
|
||||
.eve-security-color-04 {
|
||||
color: $eve-security-color-04 !important;
|
||||
fill: $eve-security-color-04;
|
||||
color: var(--eve-security-color-04) !important;
|
||||
fill: var(--eve-security-color-04);
|
||||
}
|
||||
|
||||
.eve-security-color-03 {
|
||||
color: $eve-security-color-03 !important;
|
||||
fill: $eve-security-color-03;
|
||||
color: var(--eve-security-color-03) !important;
|
||||
fill: var(--eve-security-color-03);
|
||||
}
|
||||
|
||||
.eve-security-color-02 {
|
||||
color: $eve-security-color-02 !important;
|
||||
fill: $eve-security-color-02;
|
||||
color: var(--eve-security-color-02) !important;
|
||||
fill: var(--eve-security-color-02);
|
||||
}
|
||||
|
||||
.eve-security-color-01 {
|
||||
color: $eve-security-color-01 !important;
|
||||
fill: $eve-security-color-01;
|
||||
color: var(--eve-security-color-01) !important;
|
||||
fill: var(--eve-security-color-01);
|
||||
}
|
||||
|
||||
.eve-security-color-00 {
|
||||
color: $eve-security-color-00 !important;
|
||||
fill: $eve-security-color-00;
|
||||
color: var(--eve-security-color-00) !important;
|
||||
fill: var(--eve-security-color-00);
|
||||
}
|
||||
|
||||
.eve-security-color-m-01 {
|
||||
color: $eve-security-color-m-01 !important;
|
||||
fill: $eve-security-color-m-01;
|
||||
color: var(--eve-security-color-m-01) !important;
|
||||
fill: var(--eve-security-color-m-01);
|
||||
}
|
||||
|
||||
.eve-security-color-m-02 {
|
||||
color: $eve-security-color-m-02 !important;
|
||||
fill: $eve-security-color-m-02;
|
||||
color: var(--eve-security-color-m-02) !important;
|
||||
fill: var(--eve-security-color-m-02);
|
||||
}
|
||||
|
||||
.eve-security-color-m-03 {
|
||||
color: $eve-security-color-m-03 !important;
|
||||
fill: $eve-security-color-m-03;
|
||||
color: var(--eve-security-color-m-03) !important;
|
||||
fill: var(--eve-security-color-m-03);
|
||||
}
|
||||
|
||||
.eve-security-color-m-04 {
|
||||
color: $eve-security-color-m-04 !important;
|
||||
fill: $eve-security-color-m-04;
|
||||
color: var(--eve-security-color-m-04) !important;
|
||||
fill: var(--eve-security-color-m-04);
|
||||
}
|
||||
|
||||
.eve-security-color-m-05 {
|
||||
color: $eve-security-color-m-05 !important;
|
||||
fill: $eve-security-color-m-05;
|
||||
color: var(--eve-security-color-m-05) !important;
|
||||
fill: var(--eve-security-color-m-05);
|
||||
}
|
||||
|
||||
.eve-security-color-m-06 {
|
||||
color: $eve-security-color-m-06 !important;
|
||||
fill: $eve-security-color-m-06;
|
||||
color: var(--eve-security-color-m-06) !important;
|
||||
fill: var(--eve-security-color-m-06);
|
||||
}
|
||||
|
||||
.eve-security-color-m-07 {
|
||||
color: $eve-security-color-m-07 !important;
|
||||
fill: $eve-security-color-m-07;
|
||||
color: var(--eve-security-color-m-07) !important;
|
||||
fill: var(--eve-security-color-m-07);
|
||||
}
|
||||
|
||||
.eve-security-color-m-08 {
|
||||
color: $eve-security-color-m-08 !important;
|
||||
fill: $eve-security-color-m-08;
|
||||
color: var(--eve-security-color-m-08) !important;
|
||||
fill: var(--eve-security-color-m-08);
|
||||
}
|
||||
|
||||
.eve-security-color-m-09 {
|
||||
color: $eve-security-color-m-09 !important;
|
||||
fill: $eve-security-color-m-09;
|
||||
color: var(--eve-security-color-m-09) !important;
|
||||
fill: var(--eve-security-color-m-09);
|
||||
}
|
||||
|
||||
.eve-security-color-m-10 {
|
||||
color: $eve-security-color-m-10 !important;
|
||||
fill: $eve-security-color-m-10;
|
||||
color: var(--eve-security-color-m-10) !important;
|
||||
fill: var(--eve-security-color-m-10);
|
||||
}
|
||||
|
||||
/* Security backgrounds */
|
||||
.eve-security-background-10 {
|
||||
background-color: $eve-security-color-10;
|
||||
fill: $eve-security-color-10;
|
||||
background-color: var(--eve-security-color-10);
|
||||
fill: var(--eve-security-color-10);
|
||||
}
|
||||
|
||||
.eve-security-background-09 {
|
||||
background-color: $eve-security-color-09;
|
||||
fill: $eve-security-color-09;
|
||||
background-color: var(--eve-security-color-09);
|
||||
fill: var(--eve-security-color-09);
|
||||
}
|
||||
|
||||
.eve-security-background-08 {
|
||||
background-color: $eve-security-color-08;
|
||||
fill: $eve-security-color-08;
|
||||
background-color: var(--eve-security-color-08);
|
||||
fill: var(--eve-security-color-08);
|
||||
}
|
||||
|
||||
.eve-security-background-07 {
|
||||
background-color: $eve-security-color-07;
|
||||
fill: $eve-security-color-07;
|
||||
background-color: var(--eve-security-color-07);
|
||||
fill: var(--eve-security-color-07);
|
||||
}
|
||||
|
||||
.eve-security-background-06 {
|
||||
background-color: $eve-security-color-06;
|
||||
fill: $eve-security-color-06;
|
||||
background-color: var(--eve-security-color-06);
|
||||
fill: var(--eve-security-color-06);
|
||||
}
|
||||
|
||||
.eve-security-background-05 {
|
||||
background-color: $eve-security-color-05;
|
||||
fill: $eve-security-color-05;
|
||||
background-color: var(--eve-security-color-05);
|
||||
fill: var(--eve-security-color-05);
|
||||
}
|
||||
|
||||
.eve-security-background-04 {
|
||||
background-color: $eve-security-color-04;
|
||||
fill: $eve-security-color-04;
|
||||
background-color: var(--eve-security-color-04);
|
||||
fill: var(--eve-security-color-04);
|
||||
}
|
||||
|
||||
.eve-security-background-03 {
|
||||
background-color: $eve-security-color-03;
|
||||
fill: $eve-security-color-03;
|
||||
background-color: var(--eve-security-color-03);
|
||||
fill: var(--eve-security-color-03);
|
||||
}
|
||||
|
||||
.eve-security-background-02 {
|
||||
background-color: $eve-security-color-02;
|
||||
fill: $eve-security-color-02;
|
||||
background-color: var(--eve-security-color-02);
|
||||
fill: var(--eve-security-color-02);
|
||||
}
|
||||
|
||||
.eve-security-background-01 {
|
||||
background-color: $eve-security-color-01;
|
||||
fill: $eve-security-color-01;
|
||||
background-color: var(--eve-security-color-01);
|
||||
fill: var(--eve-security-color-01);
|
||||
}
|
||||
|
||||
.eve-security-background-00 {
|
||||
background-color: $eve-security-color-00;
|
||||
fill: $eve-security-color-00;
|
||||
background-color: var(--eve-security-color-00);
|
||||
fill: var(--eve-security-color-00);
|
||||
}
|
||||
|
||||
.eve-security-background-m-01 {
|
||||
background-color: $eve-security-color-m-01;
|
||||
fill: $eve-security-color-m-01;
|
||||
background-color: var(--eve-security-color-m-01);
|
||||
fill: var(--eve-security-color-m-01);
|
||||
}
|
||||
|
||||
.eve-security-background-m-02 {
|
||||
background-color: $eve-security-color-m-02;
|
||||
fill: $eve-security-color-m-02;
|
||||
background-color: var(--eve-security-color-m-02);
|
||||
fill: var(--eve-security-color-m-02);
|
||||
}
|
||||
|
||||
.eve-security-background-m-03 {
|
||||
background-color: $eve-security-color-m-03;
|
||||
fill: $eve-security-color-m-03;
|
||||
background-color: var(--eve-security-color-m-03);
|
||||
fill: var(--eve-security-color-m-03);
|
||||
}
|
||||
|
||||
.eve-security-background-m-04 {
|
||||
background-color: $eve-security-color-m-04;
|
||||
fill: $eve-security-color-m-04;
|
||||
background-color: var(--eve-security-color-m-04);
|
||||
fill: var(--eve-security-color-m-04);
|
||||
}
|
||||
|
||||
.eve-security-background-m-05 {
|
||||
background-color: $eve-security-color-m-05;
|
||||
fill: $eve-security-color-m-05;
|
||||
background-color: var(--eve-security-color-m-05);
|
||||
fill: var(--eve-security-color-m-05);
|
||||
}
|
||||
|
||||
.eve-security-background-m-06 {
|
||||
background-color: $eve-security-color-m-06;
|
||||
fill: $eve-security-color-m-06;
|
||||
background-color: var(--eve-security-color-m-06);
|
||||
fill: var(--eve-security-color-m-06);
|
||||
}
|
||||
|
||||
.eve-security-background-m-07 {
|
||||
background-color: $eve-security-color-m-07;
|
||||
fill: $eve-security-color-m-07;
|
||||
background-color: var(--eve-security-color-m-07);
|
||||
fill: var(--eve-security-color-m-07);
|
||||
}
|
||||
|
||||
.eve-security-background-m-08 {
|
||||
background-color: $eve-security-color-m-08;
|
||||
fill: $eve-security-color-m-08;
|
||||
background-color: var(--eve-security-color-m-08);
|
||||
fill: var(--eve-security-color-m-08);
|
||||
}
|
||||
|
||||
.eve-security-background-m-09 {
|
||||
background-color: $eve-security-color-m-09;
|
||||
fill: $eve-security-color-m-09;
|
||||
background-color: var(--eve-security-color-m-09);
|
||||
fill: var(--eve-security-color-m-09);
|
||||
}
|
||||
|
||||
.eve-security-background-m-10 {
|
||||
background-color: $eve-security-color-m-10;
|
||||
fill: $eve-security-color-m-10;
|
||||
background-color: var(--eve-security-color-m-10);
|
||||
fill: var(--eve-security-color-m-10);
|
||||
}
|
||||
|
||||
/* WH Type color classes */
|
||||
.eve-wh-type-color-high {
|
||||
color: $eve-wh-type-color-high;
|
||||
fill: $eve-wh-type-color-high;
|
||||
color: var(--eve-wh-type-color-high) !important;
|
||||
fill: var(--eve-wh-type-color-high);
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.eve-wh-type-color-low {
|
||||
color: $eve-wh-type-color-low;
|
||||
fill: $eve-wh-type-color-low;
|
||||
color: var(--eve-wh-type-color-low) !important;
|
||||
fill: var(--eve-wh-type-color-low);
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.eve-wh-type-color-null {
|
||||
color: $eve-wh-type-color-null;
|
||||
fill: $eve-wh-type-color-null;
|
||||
color: var(--eve-wh-type-color-null) !important;
|
||||
fill: var(--eve-wh-type-color-null);
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.eve-wh-type-color-c1 {
|
||||
color: $eve-wh-type-color-c1 !important;
|
||||
fill: $eve-wh-type-color-c1;
|
||||
color: var(--eve-wh-type-color-c1) !important;
|
||||
fill: var(--eve-wh-type-color-c1);
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.eve-wh-type-color-c2 {
|
||||
color: $eve-wh-type-color-c2 !important;
|
||||
fill: $eve-wh-type-color-c2;
|
||||
color: var(--eve-wh-type-color-c2) !important;
|
||||
fill: var(--eve-wh-type-color-c2);
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.eve-wh-type-color-c3 {
|
||||
color: $eve-wh-type-color-c3 !important;
|
||||
fill: $eve-wh-type-color-c3;
|
||||
color: var(--eve-wh-type-color-c3) !important;
|
||||
fill: var(--eve-wh-type-color-c3);
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.eve-wh-type-color-c4 {
|
||||
color: $eve-wh-type-color-c4 !important;
|
||||
fill: $eve-wh-type-color-c4;
|
||||
color: var(--eve-wh-type-color-c4) !important;
|
||||
fill: var(--eve-wh-type-color-c4);
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.eve-wh-type-color-c5 {
|
||||
color: $eve-wh-type-color-c5 !important;
|
||||
fill: $eve-wh-type-color-c5;
|
||||
color: var(--eve-wh-type-color-c5) !important;
|
||||
fill: var(--eve-wh-type-color-c5);
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.eve-wh-type-color-c6 {
|
||||
color: $eve-wh-type-color-c6 !important;
|
||||
fill: $eve-wh-type-color-c6;
|
||||
color: var(--eve-wh-type-color-c6) !important;
|
||||
fill: var(--eve-wh-type-color-c6);
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.eve-wh-type-color-c13 {
|
||||
color: $eve-wh-type-color-c13 !important;
|
||||
fill: $eve-wh-type-color-c13;
|
||||
color: var(--eve-wh-type-color-c13) !important;
|
||||
fill: var(--eve-wh-type-color-c13);
|
||||
}
|
||||
|
||||
.eve-wh-type-color-drifter {
|
||||
color: $eve-wh-type-color-drifter !important;
|
||||
fill: $eve-wh-type-color-drifter;
|
||||
color: var(--eve-wh-type-color-drifter) !important;
|
||||
fill: var(--eve-wh-type-color-drifter);
|
||||
}
|
||||
|
||||
.eve-wh-type-color-thera {
|
||||
color: $eve-wh-type-color-thera !important;
|
||||
fill: $eve-wh-type-color-thera;
|
||||
color: var(--eve-wh-type-color-thera) !important;
|
||||
fill: var(--eve-wh-type-color-thera);
|
||||
}
|
||||
|
||||
/* WH Type backgrounds */
|
||||
.eve-wh-type-background-high {
|
||||
background-color: $eve-wh-type-color-high;
|
||||
background-color: var(--eve-wh-type-color-high);
|
||||
}
|
||||
|
||||
.eve-wh-type-background-low {
|
||||
background-color: $eve-wh-type-color-low;
|
||||
background-color: var(--eve-wh-type-color-low);
|
||||
}
|
||||
|
||||
.eve-wh-type-background-null {
|
||||
background-color: $eve-wh-type-color-null;
|
||||
background-color: var(--eve-wh-type-color-null);
|
||||
}
|
||||
|
||||
.eve-wh-type-background-c1 {
|
||||
background-color: $eve-wh-type-color-c1;
|
||||
background-color: var(--eve-wh-type-color-c1);
|
||||
}
|
||||
|
||||
.eve-wh-type-background-c2 {
|
||||
background-color: $eve-wh-type-color-c2;
|
||||
background-color: var(--eve-wh-type-color-c2);
|
||||
}
|
||||
|
||||
.eve-wh-type-background-c3 {
|
||||
background-color: $eve-wh-type-color-c3;
|
||||
background-color: var(--eve-wh-type-color-c3);
|
||||
}
|
||||
|
||||
.eve-wh-type-background-c4 {
|
||||
background-color: $eve-wh-type-color-c4;
|
||||
background-color: var(--eve-wh-type-color-c4);
|
||||
}
|
||||
|
||||
.eve-wh-type-background-c5 {
|
||||
background-color: $eve-wh-type-color-c5;
|
||||
background-color: var(--eve-wh-type-color-c5);
|
||||
}
|
||||
|
||||
.eve-wh-type-background-c6 {
|
||||
background-color: $eve-wh-type-color-c6;
|
||||
background-color: var(--eve-wh-type-color-c6);
|
||||
}
|
||||
|
||||
.eve-wh-type-background-c13 {
|
||||
background-color: $eve-wh-type-color-c13;
|
||||
background-color: var(--eve-wh-type-color-c13);
|
||||
}
|
||||
|
||||
.eve-wh-type-background-drifter {
|
||||
background-color: $eve-wh-type-color-drifter;
|
||||
background-color: var(--eve-wh-type-color-drifter);
|
||||
}
|
||||
|
||||
.eve-wh-type-background-thera {
|
||||
background-color: $eve-wh-type-color-thera;
|
||||
background-color: var(--eve-wh-type-color-thera);
|
||||
}
|
||||
|
||||
.eve-wh-type-background-zarzakh {
|
||||
background-color: $eve-wh-type-color-zarzakh;
|
||||
background-color: var(--eve-wh-type-color-zarzakh);
|
||||
}
|
||||
|
||||
/* Kind color classes */
|
||||
.eve-kind-color-high {
|
||||
color: $eve-wh-type-color-high;
|
||||
fill: $eve-wh-type-color-high;
|
||||
color: var(--eve-wh-type-color-high);
|
||||
fill: var(--eve-wh-type-color-high);
|
||||
}
|
||||
|
||||
.eve-kind-color-low {
|
||||
color: $eve-wh-type-color-low;
|
||||
fill: $eve-wh-type-color-low;
|
||||
color: var(--eve-wh-type-color-low);
|
||||
fill: var(--eve-wh-type-color-low);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.eve-kind-color-null {
|
||||
color: $eve-wh-type-color-null;
|
||||
fill: $eve-wh-type-color-null;
|
||||
color: var(--eve-wh-type-color-null);
|
||||
fill: var(--eve-wh-type-color-null);
|
||||
}
|
||||
|
||||
.eve-kind-color-wh {
|
||||
color: $eve-wh-type-color-c6;
|
||||
fill: $eve-wh-type-color-c6;
|
||||
color: var(--eve-wh-type-color-c6);
|
||||
fill: var(--eve-wh-type-color-c6);
|
||||
}
|
||||
|
||||
.eve-kind-color-thera {
|
||||
color: $eve-wh-type-color-thera;
|
||||
fill: $eve-wh-type-color-thera;
|
||||
color: var(--eve-wh-type-color-thera);
|
||||
fill: var(--eve-wh-type-color-thera);
|
||||
}
|
||||
|
||||
.eve-kind-color-abyss {
|
||||
color: $eve-wh-type-color-c6;
|
||||
fill: $eve-wh-type-color-c6;
|
||||
color: var(--eve-wh-type-color-c6);
|
||||
fill: var(--eve-wh-type-color-c6);
|
||||
}
|
||||
|
||||
.eve-kind-color-penalty {
|
||||
color: $eve-wh-type-color-c6;
|
||||
fill: $eve-wh-type-color-c6;
|
||||
color: var(--eve-wh-type-color-c6);
|
||||
fill: var(--eve-wh-type-color-c6);
|
||||
}
|
||||
|
||||
.eve-kind-color-pochven {
|
||||
color: $eve-wh-type-color-c6;
|
||||
fill: $eve-wh-type-color-c6;
|
||||
color: var(--eve-wh-type-color-c6);
|
||||
fill: var(--eve-wh-type-color-c6);
|
||||
}
|
||||
|
||||
.eve-kind-color-zarzakh {
|
||||
color: $eve-wh-type-color-zarzakh;
|
||||
fill: $eve-wh-type-color-zarzakh;
|
||||
color: var(--eve-wh-type-color-zarzakh);
|
||||
fill: var(--eve-wh-type-color-zarzakh);
|
||||
}
|
||||
|
||||
/* Kind backgrounds */
|
||||
.eve-kind-background-high {
|
||||
background-color: $eve-wh-type-color-high;
|
||||
background-color: var(--eve-wh-type-color-high);
|
||||
}
|
||||
|
||||
.eve-kind-background-low {
|
||||
background-color: $eve-wh-type-color-low;
|
||||
background-color: var(--eve-wh-type-color-low);
|
||||
}
|
||||
|
||||
.eve-kind-background-null {
|
||||
background-color: $eve-wh-type-color-null;
|
||||
background-color: var(--eve-wh-type-color-null);
|
||||
}
|
||||
|
||||
.eve-kind-background-wh {
|
||||
background-color: $eve-wh-type-color-c6;
|
||||
background-color: var(--eve-wh-type-color-c6);
|
||||
}
|
||||
|
||||
.eve-kind-background-thera {
|
||||
background-color: $eve-wh-type-color-thera;
|
||||
background-color: var(--eve-wh-type-color-thera);
|
||||
}
|
||||
|
||||
.eve-kind-background-abyss {
|
||||
background-color: $eve-wh-type-color-c6;
|
||||
background-color: var(--eve-wh-type-color-c6);
|
||||
}
|
||||
|
||||
.eve-kind-background-penalty {
|
||||
background-color: $eve-wh-type-color-c6;
|
||||
background-color: var(--eve-wh-type-color-c6);
|
||||
}
|
||||
|
||||
.eve-kind-background-pochven {
|
||||
background-color: $eve-wh-type-color-c6;
|
||||
background-color: var(--eve-wh-type-color-c6);
|
||||
}
|
||||
|
||||
.eve-kind-background-zarzakh {
|
||||
background-color: $eve-wh-type-color-zarzakh;
|
||||
background-color: var(--eve-wh-type-color-zarzakh);
|
||||
}
|
||||
|
||||
/* System status color classes */
|
||||
.eve-system-status-color-clear {
|
||||
color: $eve-solar-system-status-color-unknown;
|
||||
color: var(--eve-solar-system-status-color-unknown);
|
||||
}
|
||||
|
||||
.eve-system-status-color-home {
|
||||
color: $eve-solar-system-status-color-home;
|
||||
color: var(--eve-solar-system-status-color-home);
|
||||
}
|
||||
|
||||
.eve-system-status-color-friendly {
|
||||
color: $eve-solar-system-status-color-friendly;
|
||||
color: var(--eve-solar-system-status-color-friendly);
|
||||
}
|
||||
|
||||
.eve-system-status-color-lookingFor {
|
||||
color: $eve-solar-system-status-color-lookingFor;
|
||||
color: var(--eve-solar-system-status-color-lookingFor);
|
||||
}
|
||||
|
||||
.eve-system-status-color-warning {
|
||||
color: $eve-solar-system-status-color-warning;
|
||||
color: var(--eve-solar-system-status-color-warning);
|
||||
}
|
||||
|
||||
.eve-system-status-color-target {
|
||||
color: $eve-solar-system-status-color-target;
|
||||
color: var(--eve-solar-system-status-color-target);
|
||||
}
|
||||
.eve-system-status-color-dangerous {
|
||||
color: var(--eve-solar-system-status-color-dangerous);
|
||||
}
|
||||
|
||||
.eve-system-status-color-dangerous {
|
||||
color: $eve-solar-system-status-color-dangerous;
|
||||
.eve-system-status-clear {
|
||||
background-color: var(--eve-solar-system-status-unknown);
|
||||
}
|
||||
.eve-system-status-home {
|
||||
background-color: var(--eve-solar-system-status-home);
|
||||
}
|
||||
.eve-system-status-friendly {
|
||||
background-color: var(--eve-solar-system-status-friendly);
|
||||
}
|
||||
.eve-system-status-lookingFor {
|
||||
background-color: var(--eve-solar-system-status-lookingFor);
|
||||
}
|
||||
.eve-system-status-warning {
|
||||
background-color: var(--eve-solar-system-status-warning);
|
||||
}
|
||||
.eve-system-status-target {
|
||||
background-color: var(--eve-solar-system-status-target);
|
||||
}
|
||||
.eve-system-status-dangerous {
|
||||
background-color: var(--eve-solar-system-status-dangerous);
|
||||
}
|
||||
|
||||
.eve-system-status-clear {
|
||||
background-color: var(--eve-solar-system-status-unknown);
|
||||
color: var(--eve-solar-system-status-color-unknown);
|
||||
}
|
||||
|
||||
.eve-system-status-home {
|
||||
background-color: var(--eve-solar-system-status-home);
|
||||
color: var(--eve-solar-system-status-color-home);
|
||||
}
|
||||
|
||||
.eve-system-status-friendly {
|
||||
background-color: var(--eve-solar-system-status-friendly);
|
||||
color: var(--eve-solar-system-status-color-friendly);
|
||||
}
|
||||
|
||||
.eve-system-status-lookingFor {
|
||||
background-color: var(--eve-solar-system-status-lookingFor);
|
||||
color: var(--eve-solar-system-status-color-lookingFor);
|
||||
}
|
||||
|
||||
.eve-system-status-warning {
|
||||
background-color: var(--eve-solar-system-status-warning);
|
||||
color: var(--eve-solar-system-status-color-warning);
|
||||
}
|
||||
|
||||
.eve-system-status-target {
|
||||
background-color: var(--eve-solar-system-status-target);
|
||||
color: var(--eve-solar-system-status-color-target);
|
||||
}
|
||||
|
||||
.eve-system-status-dangerous {
|
||||
background-color: var(--eve-solar-system-status-dangerous);
|
||||
color: var(--eve-solar-system-status-color-dangerous);
|
||||
}
|
||||
|
||||
.wd-route-system-shape-triangle {
|
||||
clip-path: polygon(50% 0, 0 100%, 100% 100%);
|
||||
}
|
||||
|
||||
.wd-route-system-shape-circle {
|
||||
border-radius: 40%;
|
||||
}
|
||||
|
||||
/* Some additional background classes */
|
||||
.wd-marker-bookmark-color-shattered {
|
||||
background-color: #833ca4;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
|
||||
.wd-marker-bookmark-color-custom {
|
||||
background-color: #282828;
|
||||
border: 1px solid #4c4c4c;
|
||||
@@ -572,3 +541,49 @@
|
||||
.wd-marker-bookmark-color-danger {
|
||||
background-color: #d10600;
|
||||
}
|
||||
|
||||
.react-flow {
|
||||
color: var(--text-color);
|
||||
|
||||
&__pane {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
&__minimap {
|
||||
background-color: rgba(66, 66, 66, 1);
|
||||
opacity: 0.7;
|
||||
border: 1px solid #2f2f2f;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__minimap-mask {
|
||||
fill: rgba(28, 28, 28, 0.75);
|
||||
}
|
||||
|
||||
&__controls {
|
||||
filter: brightness(1.5);
|
||||
}
|
||||
|
||||
&__minimap-node {
|
||||
fill: #ffb03a;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
2
assets/js/hooks/Mapper/components/map/styles/index.scss
Normal file
2
assets/js/hooks/Mapper/components/map/styles/index.scss
Normal file
@@ -0,0 +1,2 @@
|
||||
@import './default-theme.scss';
|
||||
@import './pathfinder-theme.scss';
|
||||
@@ -1,74 +0,0 @@
|
||||
$pastel-blue: #5a7d9a;
|
||||
$pastel-pink: #d291bc;
|
||||
$pastel-green: #88b04b;
|
||||
$pastel-yellow: #ffdd59;
|
||||
$dark-bg: #2d2d2d;
|
||||
$text-color: #ffffff;
|
||||
$tooltip-bg: #202020;
|
||||
|
||||
.react-flow {
|
||||
// background-color: $dark-bg;
|
||||
color: $text-color;
|
||||
|
||||
&__node {
|
||||
//cursor: auto;
|
||||
}
|
||||
|
||||
&__pane {
|
||||
cursor: auto;
|
||||
}
|
||||
//&__edge {
|
||||
// stroke: $pastel-pink;
|
||||
// stroke-width: 2px;
|
||||
//
|
||||
// &.selected {
|
||||
// stroke: $pastel-yellow;
|
||||
// }
|
||||
//}
|
||||
|
||||
&__handle {
|
||||
//background-color: $pastel-green;
|
||||
//box-shadow: 0 0 5px rgba($pastel-green, 0.5);
|
||||
}
|
||||
|
||||
&__minimap {
|
||||
background-color: rgba(66, 66, 66, 1);
|
||||
opacity: 0.7;
|
||||
//backdrop-filter: blur(5px);
|
||||
border: 1px solid #2f2f2f;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__minimap-mask {
|
||||
fill: rgba(28, 28, 28, 0.75);
|
||||
}
|
||||
|
||||
&__controls {
|
||||
filter: brightness(1.5);
|
||||
}
|
||||
|
||||
&__minimap-node {
|
||||
fill: #ffb03a;
|
||||
}
|
||||
}
|
||||
|
||||
.context-menu-active {
|
||||
background-color: rgba(131, 131, 131, 0.33);
|
||||
}
|
||||
|
||||
.p-dialog {
|
||||
.p-dialog-header {
|
||||
height: 40px;
|
||||
padding: 1rem;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
|
||||
.p-dialog-title {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
.p-dialog-header-icons {
|
||||
align-self: initial !important;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
$pastel-blue: #5a7d9a;
|
||||
$pastel-pink: #d291bc;
|
||||
$pastel-green: #88b04b;
|
||||
$pastel-yellow: #ffdd59;
|
||||
$dark-bg: #2d2d2d;
|
||||
$text-color: #ffffff;
|
||||
$tooltip-bg: #202020;
|
||||
@@ -0,0 +1,54 @@
|
||||
@import './eve-common-variables';
|
||||
@import './eve-common';
|
||||
@import url('https://fonts.googleapis.com/css2?family=Oxygen:wght@300;400;700&display=swap');
|
||||
|
||||
$homeBase: rgb(197, 253, 67);
|
||||
$homeAlpha: rgba(197, 253, 67, 0.32);
|
||||
$homeDark30: darken($homeBase, 30%);
|
||||
|
||||
.pathfinder-theme {
|
||||
/* -- Override values from the default theme -- */
|
||||
--rf-bg-color: #000000;
|
||||
--rf-soft-bg-color: #282828;
|
||||
--rf-node-soft-bg-color: #313335;
|
||||
--rf-node-font-weight: bold;
|
||||
--rf-text-color: #adadad;
|
||||
--rf-region-name: var(--rf-text-color);
|
||||
--rf-custom-name: var(--rf-text-color);
|
||||
--rf-bg-variant: "lines";
|
||||
--rf-bg-gap: 34;
|
||||
--rf-snap-size: 17;
|
||||
--rf-bg-pattern-color: #313131;
|
||||
--rf-local-counter-font-weight: 700;
|
||||
|
||||
/* Additional node-specific overrides */
|
||||
--rf-node-line-height: normal;
|
||||
--rf-node-font-family: 'Oxygen', sans-serif;
|
||||
--rf-tag-color: #fbbf24;
|
||||
|
||||
/* -- theme-specific variables -- */
|
||||
--eve-effect-pulsar: #428bca;
|
||||
--eve-effect-magnetar: #e06fdf;
|
||||
--eve-effect-wolfRayet: #e28a0d;
|
||||
--eve-effect-blackHole: #000000;
|
||||
--eve-effect-cataclysmicVariable: #ffffbb;
|
||||
--eve-effect-redGiant: #d9534f;
|
||||
|
||||
--eve-wh-type-color-high: #5cb85c;
|
||||
--eve-wh-type-color-low: #e28a0d;
|
||||
--eve-wh-type-color-null: #d9534f;
|
||||
--eve-wh-type-color-c1: #428bca;
|
||||
--eve-wh-type-color-c2: #428bca;
|
||||
--eve-wh-type-color-c3: #e28a0d;
|
||||
--eve-wh-type-color-c4: #e28a0d;
|
||||
--eve-wh-type-color-c5: #d9534f;
|
||||
--eve-wh-type-color-c6: #d9534f;
|
||||
--eve-wh-type-color-c13: #7986cb;
|
||||
--eve-wh-type-color-drifter: #44aa82;
|
||||
|
||||
--rf-node-local-counter: #5cb85c;
|
||||
--rf-has-user-characters: #ffc75d;
|
||||
|
||||
--eve-solar-system-status-home: #{$homeAlpha};
|
||||
--eve-solar-system-status-color-home: #{$homeBase};
|
||||
}
|
||||
@@ -1,78 +1,30 @@
|
||||
import 'react-grid-layout/css/styles.css';
|
||||
import 'react-resizable/css/styles.css';
|
||||
import { WidgetGridItem, WidgetsGrid } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import {
|
||||
LocalCharacters,
|
||||
RoutesWidget,
|
||||
SystemInfo,
|
||||
SystemSignatures,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets';
|
||||
import { useState } from 'react';
|
||||
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
|
||||
// import { debounce } from 'lodash/debounce';
|
||||
|
||||
const DEFAULT_WINDOWS = [
|
||||
{
|
||||
name: 'info',
|
||||
rightOffset: 5,
|
||||
width: 5,
|
||||
height: 4,
|
||||
item: () => <SystemInfo />,
|
||||
},
|
||||
{
|
||||
name: 'local',
|
||||
rightOffset: 5,
|
||||
topOffset: 4,
|
||||
width: 5,
|
||||
height: 4,
|
||||
item: () => <LocalCharacters />,
|
||||
},
|
||||
{ name: 'signatures', width: 8, height: 4, topOffset: 8, rightOffset: 12, item: () => <SystemSignatures /> },
|
||||
{
|
||||
name: 'routes',
|
||||
rightOffset: 0,
|
||||
topOffset: 8,
|
||||
width: 5,
|
||||
height: 6,
|
||||
item: () => <RoutesWidget />,
|
||||
},
|
||||
];
|
||||
|
||||
const saveWindowsToLS = (toSaveItems: WidgetGridItem[]) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const out = toSaveItems.map(({ item, ...rest }) => rest);
|
||||
localStorage.setItem(SESSION_KEY.windows, JSON.stringify(out));
|
||||
};
|
||||
|
||||
const restoreWindowsFromLS = (): WidgetGridItem[] => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const raw = localStorage.getItem(SESSION_KEY.windows);
|
||||
if (!raw) {
|
||||
console.warn('No windows found in local storage!!');
|
||||
return DEFAULT_WINDOWS;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-debugger
|
||||
const out = (JSON.parse(raw) as Omit<WidgetGridItem, 'item'>[])
|
||||
.filter(x => DEFAULT_WINDOWS.find(def => def.name === x.name))
|
||||
.map(x => {
|
||||
const windowItem = DEFAULT_WINDOWS.find(def => def.name === x.name)?.item;
|
||||
return { ...x, item: windowItem! };
|
||||
});
|
||||
|
||||
return out;
|
||||
};
|
||||
import { useMemo } from 'react';
|
||||
import { WindowManager } from '@/hooks/Mapper/components/ui-kit/WindowManager';
|
||||
import { DEFAULT_WIDGETS } from '@/hooks/Mapper/components/mapInterface/constants.tsx';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export const MapInterface = () => {
|
||||
const [items, setItems] = useState<WidgetGridItem[]>(restoreWindowsFromLS);
|
||||
// const [items, setItems] = useState<WindowProps[]>(restoreWindowsFromLS);
|
||||
const { windowsSettings, updateWidgetSettings } = useMapRootState();
|
||||
|
||||
const items = useMemo(() => {
|
||||
return windowsSettings.windows
|
||||
.map(x => {
|
||||
const content = DEFAULT_WIDGETS.find(y => y.id === x.id)?.content;
|
||||
return {
|
||||
...x,
|
||||
content: content!,
|
||||
};
|
||||
})
|
||||
.filter(x => windowsSettings.visible.some(j => x.id === j));
|
||||
}, [windowsSettings]);
|
||||
|
||||
return (
|
||||
<WidgetsGrid
|
||||
items={items}
|
||||
onChange={x => {
|
||||
saveWindowsToLS(x);
|
||||
setItems(x);
|
||||
}}
|
||||
<WindowManager
|
||||
windows={items}
|
||||
viewPort={windowsSettings.viewPort}
|
||||
dragSelector=".react-grid-dragHandleExample"
|
||||
onChange={updateWidgetSettings}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
.SearchItem {
|
||||
& > * {
|
||||
font-size: 13px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.SearchItemEffect {
|
||||
font-weight: initial !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { IconField } from 'primereact/iconfield';
|
||||
import { AutoComplete } from 'primereact/autocomplete';
|
||||
import { OutCommand, SearchSystemItem } from '@/hooks/Mapper/types';
|
||||
import { SystemViewStandalone, WHClassView, WHEffectView } from '@/hooks/Mapper/components/ui-kit';
|
||||
import classes from './AddSystemDialog.module.scss';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||
import { sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||
|
||||
export type SearchOnSubmitCallback = (item: SearchSystemItem) => void;
|
||||
|
||||
interface AddSystemDialogProps {
|
||||
title?: string;
|
||||
visible: boolean;
|
||||
setVisible: (visible: boolean) => void;
|
||||
onSubmit?: SearchOnSubmitCallback;
|
||||
excludedSystems?: number[];
|
||||
}
|
||||
|
||||
export const AddSystemDialog = ({
|
||||
title = 'Add system',
|
||||
visible,
|
||||
setVisible,
|
||||
onSubmit,
|
||||
excludedSystems = [],
|
||||
}: AddSystemDialogProps) => {
|
||||
const {
|
||||
outCommand,
|
||||
data: { wormholesData },
|
||||
} = useMapRootState();
|
||||
|
||||
const inputRef = useRef<any>();
|
||||
const onShow = useCallback(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const [filteredItems, setFilteredItems] = useState<SearchSystemItem[]>([]);
|
||||
const [selectedItem, setSelectedItem] = useState<SearchSystemItem[] | null>(null);
|
||||
|
||||
const searchItems = useCallback(
|
||||
async (event: { query: string }) => {
|
||||
if (event.query.length < 2) {
|
||||
setFilteredItems([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const query = event.query;
|
||||
|
||||
if (query.length === 0) {
|
||||
setFilteredItems([]);
|
||||
} else {
|
||||
try {
|
||||
const result = await outCommand({
|
||||
type: OutCommand.searchSystems,
|
||||
data: {
|
||||
text: query,
|
||||
},
|
||||
});
|
||||
|
||||
let prepared = (result.systems as SearchSystemItem[]).sort((a, b) => {
|
||||
const amatch = a.label.indexOf(query);
|
||||
const bmatch = b.label.indexOf(query);
|
||||
return amatch - bmatch;
|
||||
});
|
||||
|
||||
if (excludedSystems) {
|
||||
prepared = prepared.filter(x => !excludedSystems.includes(x.system_static_info.solar_system_id));
|
||||
}
|
||||
|
||||
setFilteredItems(prepared);
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
setFilteredItems([]);
|
||||
}
|
||||
}
|
||||
},
|
||||
[excludedSystems, outCommand],
|
||||
);
|
||||
|
||||
const ref = useRef({ onSubmit, selectedItem });
|
||||
ref.current = { onSubmit, selectedItem };
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
const { onSubmit, selectedItem } = ref.current;
|
||||
setFilteredItems([]);
|
||||
setSelectedItem([]);
|
||||
|
||||
if (!selectedItem) {
|
||||
setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
onSubmit?.(selectedItem[0]);
|
||||
setVisible(false);
|
||||
}, [setVisible]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
header={title}
|
||||
visible={visible}
|
||||
draggable={false}
|
||||
style={{ width: '520px' }}
|
||||
onShow={onShow}
|
||||
onHide={() => {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-3 px-1.5">
|
||||
<div className="flex flex-col gap-2 py-3.5">
|
||||
<div className="flex flex-col gap-1">
|
||||
<IconField>
|
||||
<AutoComplete
|
||||
ref={inputRef}
|
||||
multiple
|
||||
showEmptyMessage
|
||||
scrollHeight="300px"
|
||||
value={selectedItem}
|
||||
suggestions={filteredItems}
|
||||
completeMethod={searchItems}
|
||||
onChange={e => {
|
||||
setSelectedItem(e.value.length < 2 ? e.value : [e.value[e.value.length - 1]]);
|
||||
}}
|
||||
emptyMessage="Not found any system..."
|
||||
placeholder="Type here..."
|
||||
field="label"
|
||||
id="value"
|
||||
className="w-full"
|
||||
itemTemplate={(item: SearchSystemItem) => {
|
||||
const { security, system_class, effect_power, effect_name, statics } = item.system_static_info;
|
||||
const sortedStatics = sortWHClasses(wormholesData, statics);
|
||||
const isWH = isWormholeSpace(system_class);
|
||||
|
||||
return (
|
||||
<div className={clsx('flex gap-1.5', classes.SearchItem)}>
|
||||
<SystemViewStandalone
|
||||
security={security}
|
||||
system_class={system_class}
|
||||
solar_system_id={item.value}
|
||||
class_title={item.class_title}
|
||||
solar_system_name={item.label}
|
||||
region_name={item.region_name}
|
||||
/>
|
||||
|
||||
{effect_name && isWH && (
|
||||
<WHEffectView
|
||||
effectName={effect_name}
|
||||
effectPower={effect_power}
|
||||
className={classes.SearchItemEffect}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isWH && (
|
||||
<div className="flex gap-1 grow justify-between">
|
||||
<div></div>
|
||||
<div className="flex gap-1">
|
||||
{sortedStatics.map(x => (
|
||||
<WHClassView key={x} whClassName={x} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
selectedItemTemplate={(item: SearchSystemItem) => (
|
||||
<SystemViewStandalone
|
||||
security={item.system_static_info.security}
|
||||
system_class={item.system_static_info.system_class}
|
||||
solar_system_id={item.value}
|
||||
class_title={item.class_title}
|
||||
solar_system_name={item.label}
|
||||
region_name={item.region_name}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</IconField>
|
||||
|
||||
<span className="text-[12px] text-stone-400 ml-1">*to search type at least 2 symbols.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
outlined
|
||||
disabled={!selectedItem || selectedItem.length !== 1}
|
||||
size="small"
|
||||
label="Submit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './AddSystemDialog';
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
@@ -58,7 +58,7 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
||||
<Dialog
|
||||
header="Select signature to link"
|
||||
visible
|
||||
draggable={false}
|
||||
draggable={true}
|
||||
style={{ width: '500px' }}
|
||||
onHide={handleHide}
|
||||
contentClassName="!p-0"
|
||||
|
||||
@@ -3,6 +3,7 @@ import { InputTextarea } from 'primereact/inputtextarea';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
@@ -22,30 +23,21 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
|
||||
|
||||
const system = getSystemById(systems, systemId);
|
||||
|
||||
const [name, setName] = useState('');
|
||||
const [label, setLabel] = useState('');
|
||||
const [temporaryName, setTemporaryName] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const inputRef = useRef<HTMLInputElement>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!system) {
|
||||
return;
|
||||
}
|
||||
|
||||
const labels = new LabelsManager(system.labels || '');
|
||||
|
||||
setName(system.name || '');
|
||||
setLabel(labels.customLabel);
|
||||
setDescription(system.description || '');
|
||||
}, [system]);
|
||||
|
||||
const ref = useRef({ name, description, label, outCommand, systemId, system });
|
||||
ref.current = { name, description, label, outCommand, systemId, system };
|
||||
const ref = useRef({ name, description, temporaryName, label, outCommand, systemId, system });
|
||||
ref.current = { name, description, label, temporaryName, outCommand, systemId, system };
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
const { name, description, label, outCommand, systemId, system } = ref.current;
|
||||
const { name, description, label, temporaryName, outCommand, systemId, system } = ref.current;
|
||||
|
||||
const outLabel = new LabelsManager(system?.labels ?? '');
|
||||
outLabel.updateCustomLabel(label);
|
||||
@@ -58,6 +50,14 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
},
|
||||
});
|
||||
|
||||
outCommand({
|
||||
type: OutCommand.updateSystemTemporaryName,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
value: temporaryName,
|
||||
},
|
||||
});
|
||||
|
||||
outCommand({
|
||||
type: OutCommand.updateSystemName,
|
||||
data: {
|
||||
@@ -93,6 +93,21 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9\-[\](){}]/g, '');
|
||||
}, []);
|
||||
|
||||
// Attention: this effect should be call only on mount.
|
||||
useEffect(() => {
|
||||
const { system } = ref.current;
|
||||
if (!system) {
|
||||
return;
|
||||
}
|
||||
|
||||
const labels = new LabelsManager(system.labels || '');
|
||||
|
||||
setName(system.name || '');
|
||||
setLabel(labels.customLabel);
|
||||
setTemporaryName(system.temporary_name || '');
|
||||
setDescription(system.description || '');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
header="System settings"
|
||||
@@ -167,6 +182,35 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
</IconField>
|
||||
</div>
|
||||
|
||||
{isTempSystemNameEnabled && (
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="username">Temporary Name</label>
|
||||
|
||||
<IconField>
|
||||
{temporaryName !== '' && (
|
||||
<WdImgButton
|
||||
className="pi pi-trash text-red-400"
|
||||
textSize={WdImageSize.large}
|
||||
tooltip={{
|
||||
content: 'Remove temporary name',
|
||||
className: 'pi p-input-icon',
|
||||
position: TooltipPosition.top,
|
||||
}}
|
||||
onClick={() => setTemporaryName('')}
|
||||
/>
|
||||
)}
|
||||
<InputText
|
||||
id="temporaryName"
|
||||
aria-describedby="temporaryName"
|
||||
autoComplete="off"
|
||||
value={temporaryName}
|
||||
maxLength={10}
|
||||
onChange={e => setTemporaryName(e.target.value)}
|
||||
/>
|
||||
</IconField>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="username">Description</label>
|
||||
<InputTextarea
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
.GridLayoutWrapper {
|
||||
width: 100%;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.GridLayout {
|
||||
width: 100%;
|
||||
height: 100% !important;
|
||||
pointer-events: none;
|
||||
|
||||
& > div {
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
||||
:global {
|
||||
.react-resizable-handle::after {
|
||||
border-color: #696969 !important;
|
||||
}
|
||||
|
||||
.react-grid-placeholder {
|
||||
background-color: rgba(147, 147, 147, 0.3);
|
||||
//filter: blur(5px);
|
||||
border: 2px dashed #b6b6b6;
|
||||
}
|
||||
|
||||
.react-grid-item {
|
||||
transition-property: none !important;
|
||||
}
|
||||
|
||||
.react-grid-item.cssTransforms {
|
||||
transition-property: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import classes from './WidgetsGrid.module.scss';
|
||||
import { ItemCallback, Layouts, Responsive, WidthProvider } from 'react-grid-layout';
|
||||
import clsx from 'clsx';
|
||||
import usePageVisibility from '@/hooks/Mapper/hooks/usePageVisibility.ts';
|
||||
|
||||
const ResponsiveGridLayout = WidthProvider(Responsive);
|
||||
|
||||
const colSize = 50;
|
||||
const initState = { breakpoints: 100, cols: 2 };
|
||||
|
||||
export type WidgetGridItem = {
|
||||
rightOffset?: number;
|
||||
leftOffset?: number;
|
||||
topOffset?: number;
|
||||
width: number;
|
||||
height: number;
|
||||
name: string;
|
||||
item: () => React.ReactNode;
|
||||
};
|
||||
|
||||
export interface WidgetsGridProps {
|
||||
items: WidgetGridItem[];
|
||||
onChange: (items: WidgetGridItem[]) => void;
|
||||
}
|
||||
|
||||
export const WidgetsGrid = ({ items, onChange }: WidgetsGridProps) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [, setKey] = useState(0);
|
||||
const [callRerenderOfGrid, setCallRerenderOfGrid] = useState(0);
|
||||
|
||||
const isTabVisible = usePageVisibility();
|
||||
|
||||
const refAll = useRef({
|
||||
isReady: false,
|
||||
layouts: {
|
||||
lg: [
|
||||
// { i: 'a', w: 4, h: 16, x: 22, y: 0 },
|
||||
// { i: 'b', w: 5, h: 10, x: 17, y: 0 },
|
||||
],
|
||||
} as Layouts,
|
||||
breakpoints: { lg: 100, md: 0, sm: 0, xs: 0, xxs: 0 },
|
||||
cols: { lg: 26, md: 0, sm: 0, xs: 0, xxs: 0 },
|
||||
containerWidth: 0,
|
||||
colsPrev: 26,
|
||||
needPostProcess: false,
|
||||
items: [...items],
|
||||
});
|
||||
|
||||
// TODO
|
||||
// 1. onLayoutChange (original) not calling when we change x of any widget
|
||||
// 2. setKey need no call rerender for update props
|
||||
const onLayoutChange: ItemCallback = (newItems, _, newItem) => {
|
||||
const updatedItems = newItems.map(item => {
|
||||
const toLeft = (item.x + item.w / 2) / refAll.current.cols.lg <= 0.5;
|
||||
const original = refAll.current.items.find(x => x.name === item.i)!;
|
||||
|
||||
return {
|
||||
...original,
|
||||
width: item.w,
|
||||
height: item.h,
|
||||
leftOffset: toLeft ? item.x : undefined,
|
||||
rightOffset: !toLeft ? refAll.current.cols.lg - (item.x + item.w) : undefined,
|
||||
topOffset: item.y,
|
||||
};
|
||||
});
|
||||
|
||||
const sortedItems = [
|
||||
...updatedItems.filter(x => x.name !== newItem.i),
|
||||
updatedItems.find(x => x.name === newItem.i)!,
|
||||
];
|
||||
|
||||
refAll.current.layouts = {
|
||||
lg: [...newItems.filter(x => x.i !== newItem.i), newItem],
|
||||
};
|
||||
|
||||
onChange(sortedItems);
|
||||
setKey(x => x + 1);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refAll.current.items = [...items];
|
||||
setKey(x => x + 1);
|
||||
}, [items]);
|
||||
|
||||
// TODO
|
||||
// 1. Unknown why but if we set layout and cols both instantly it not help...
|
||||
// 1.2 it means that we should make report... until we will send new key on window resize
|
||||
useEffect(() => {
|
||||
const updateItems = () => {
|
||||
if (!containerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { width } = containerRef.current.getBoundingClientRect();
|
||||
const newColsCount = (width - (width % colSize)) / colSize;
|
||||
|
||||
refAll.current.layouts = {
|
||||
lg: refAll.current.items.map(({ name, width, height, rightOffset, leftOffset, topOffset = 0 }) => {
|
||||
return {
|
||||
i: name,
|
||||
x: rightOffset != null ? newColsCount - width - rightOffset : leftOffset ?? 0,
|
||||
y: topOffset,
|
||||
w: width,
|
||||
h: height,
|
||||
};
|
||||
}),
|
||||
};
|
||||
refAll.current.cols = { lg: newColsCount, md: 0, sm: 0, xs: 0, xxs: 0 };
|
||||
};
|
||||
|
||||
const updateContainerWidth = () => {
|
||||
if (!containerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { width } = containerRef.current.getBoundingClientRect();
|
||||
|
||||
refAll.current.containerWidth = width;
|
||||
const newColsCount = (width - (width % colSize)) / colSize;
|
||||
|
||||
if (width <= 100 || refAll.current.cols.lg === newColsCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!refAll.current.isReady) {
|
||||
updateItems();
|
||||
setCallRerenderOfGrid(x => x + 1);
|
||||
refAll.current.isReady = true;
|
||||
return;
|
||||
}
|
||||
|
||||
refAll.current.layouts = {
|
||||
lg: refAll.current.layouts.lg.map(lgEl => {
|
||||
const toLeft = (lgEl.x + lgEl.w / 2) / refAll.current.cols.lg <= 0.5;
|
||||
const next = {
|
||||
...lgEl,
|
||||
x: toLeft ? lgEl.x : newColsCount - (refAll.current.cols.lg - lgEl.x),
|
||||
};
|
||||
return next;
|
||||
}),
|
||||
};
|
||||
|
||||
refAll.current.cols = { lg: newColsCount, md: 0, sm: 0, xs: 0, xxs: 0 };
|
||||
setCallRerenderOfGrid(x => x + 1);
|
||||
};
|
||||
|
||||
setTimeout(() => updateContainerWidth(), 100);
|
||||
|
||||
const withRerender = () => {
|
||||
updateContainerWidth();
|
||||
setCallRerenderOfGrid(x => x + 1);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', withRerender);
|
||||
return () => {
|
||||
window.removeEventListener('resize', withRerender);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const isNotSet = initState.cols === refAll.current.cols.lg;
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className={clsx(classes.GridLayoutWrapper, 'relative p-4')}>
|
||||
{!isNotSet && isTabVisible && (
|
||||
<ResponsiveGridLayout
|
||||
key={callRerenderOfGrid}
|
||||
className={classes.GridLayout}
|
||||
layouts={refAll.current.layouts}
|
||||
breakpoints={refAll.current.breakpoints}
|
||||
cols={refAll.current.cols}
|
||||
rowHeight={30}
|
||||
width={refAll.current.containerWidth}
|
||||
preventCollision={true}
|
||||
compactType={null}
|
||||
allowOverlap
|
||||
onDragStop={onLayoutChange}
|
||||
onResizeStop={onLayoutChange}
|
||||
// onResizeStart={onLayoutChange}
|
||||
// onDragStart={onLayoutChange}
|
||||
isBounded
|
||||
containerPadding={[0, 0]}
|
||||
resizeHandles={['sw', 'se']}
|
||||
draggableHandle=".react-grid-dragHandleExample"
|
||||
>
|
||||
{refAll.current.items.map(x => (
|
||||
<div key={x.name} className="grid-item">
|
||||
{x.item()}
|
||||
</div>
|
||||
))}
|
||||
</ResponsiveGridLayout>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './WidgetsGrid';
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './Widget';
|
||||
export * from './WidgetsGrid';
|
||||
export * from './SystemSettingsDialog';
|
||||
export * from './SystemCustomLabelDialog';
|
||||
export * from './SystemLinkSignatureDialog';
|
||||
|
||||
105
assets/js/hooks/Mapper/components/mapInterface/constants.tsx
Normal file
105
assets/js/hooks/Mapper/components/mapInterface/constants.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { WindowProps } from '@/hooks/Mapper/components/ui-kit/WindowManager/types.ts';
|
||||
import {
|
||||
LocalCharacters,
|
||||
RoutesWidget,
|
||||
SystemInfo,
|
||||
SystemSignatures,
|
||||
SystemStructures,
|
||||
SystemKills,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets';
|
||||
|
||||
export const CURRENT_WINDOWS_VERSION = 8;
|
||||
export const WINDOWS_LOCAL_STORE_KEY = 'windows:settings:v2';
|
||||
|
||||
export enum WidgetsIds {
|
||||
info = 'info',
|
||||
signatures = 'signatures',
|
||||
local = 'local',
|
||||
routes = 'routes',
|
||||
structures = 'structures',
|
||||
kills = 'kills',
|
||||
}
|
||||
|
||||
export const STORED_VISIBLE_WIDGETS_DEFAULT = [
|
||||
WidgetsIds.info,
|
||||
WidgetsIds.local,
|
||||
WidgetsIds.routes,
|
||||
WidgetsIds.signatures,
|
||||
];
|
||||
|
||||
export const DEFAULT_WIDGETS: WindowProps[] = [
|
||||
{
|
||||
id: WidgetsIds.info,
|
||||
position: { x: 10, y: 10 },
|
||||
size: { width: 250, height: 200 },
|
||||
zIndex: 0,
|
||||
content: () => <SystemInfo />,
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.signatures,
|
||||
position: { x: 10, y: 220 },
|
||||
size: { width: 250, height: 300 },
|
||||
zIndex: 0,
|
||||
content: () => <SystemSignatures />,
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.local,
|
||||
position: { x: 270, y: 10 },
|
||||
size: { width: 250, height: 510 },
|
||||
zIndex: 0,
|
||||
content: () => <LocalCharacters />,
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.routes,
|
||||
position: { x: 10, y: 530 },
|
||||
size: { width: 510, height: 200 },
|
||||
zIndex: 0,
|
||||
content: () => <RoutesWidget />,
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.structures,
|
||||
position: { x: 10, y: 730 },
|
||||
size: { width: 510, height: 200 },
|
||||
zIndex: 0,
|
||||
content: () => <SystemStructures />,
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.kills,
|
||||
position: { x: 270, y: 730 },
|
||||
size: { width: 510, height: 200 },
|
||||
zIndex: 0,
|
||||
content: () => <SystemKills />,
|
||||
},
|
||||
];
|
||||
|
||||
type WidgetsCheckboxesType = {
|
||||
id: WidgetsIds;
|
||||
label: string;
|
||||
}[];
|
||||
|
||||
export const WIDGETS_CHECKBOXES_PROPS: WidgetsCheckboxesType = [
|
||||
{
|
||||
id: WidgetsIds.info,
|
||||
label: 'System Info',
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.signatures,
|
||||
label: 'Signatures',
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.local,
|
||||
label: 'Local',
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.routes,
|
||||
label: 'Routes',
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.structures,
|
||||
label: 'Structures',
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.kills,
|
||||
label: 'Kills',
|
||||
},
|
||||
];
|
||||
@@ -1,13 +1,3 @@
|
||||
.VirtualScroller {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.CharacterRow {
|
||||
//border-left-width: 1px;
|
||||
|
||||
&.CardBorderLeftIsOwn {
|
||||
border-left-color: rgb(251 146 60 / 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,130 +1,71 @@
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
||||
import clsx from 'clsx';
|
||||
import classes from './LocalCharacters.module.scss';
|
||||
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
|
||||
import { CharacterCard, LayoutEventBlocker, WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { sortCharacters } from '@/hooks/Mapper/components/mapInterface/helpers/sortCharacters.ts';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
import { sortCharacters } from '@/hooks/Mapper/components/mapInterface/helpers/sortCharacters';
|
||||
import { useMapCheckPermissions, useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
|
||||
type CharItemProps = {
|
||||
compact: boolean;
|
||||
} & CharacterTypeRaw &
|
||||
WithIsOwnCharacter;
|
||||
|
||||
const useItemTemplate = () => {
|
||||
const {
|
||||
data: { presentCharacters },
|
||||
} = useMapRootState();
|
||||
|
||||
return useCallback(
|
||||
(char: CharItemProps, options: VirtualScrollerTemplateOptions) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(classes.CharacterRow, 'w-full box-border', {
|
||||
'surface-hover': options.odd,
|
||||
['border-b border-gray-600 border-opacity-20']: !options.last,
|
||||
['bg-green-500 hover:bg-green-700 transition duration-300 bg-opacity-10 hover:bg-opacity-10']: char.online,
|
||||
})}
|
||||
style={{ height: options.props.itemSize + 'px' }}
|
||||
>
|
||||
<CharacterCard showShipName {...char} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
// eslint-disable-next-line
|
||||
[presentCharacters],
|
||||
);
|
||||
};
|
||||
|
||||
type WindowLocalSettingsType = {
|
||||
compact: boolean;
|
||||
showOffline: boolean;
|
||||
version: number;
|
||||
};
|
||||
|
||||
const STORED_DEFAULT_VALUES: WindowLocalSettingsType = {
|
||||
compact: true,
|
||||
showOffline: false,
|
||||
version: 0,
|
||||
};
|
||||
import { UserPermission } from '@/hooks/Mapper/types/permissions';
|
||||
import { LocalCharactersList } from './components/LocalCharactersList';
|
||||
import { useLocalCharactersItemTemplate } from './hooks/useLocalCharacters';
|
||||
import { useLocalCharacterWidgetSettings } from './hooks/useLocalWidgetSettings';
|
||||
import { LocalCharactersHeader } from './components/LocalCharactersHeader';
|
||||
import classes from './LocalCharacters.module.scss';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export const LocalCharacters = () => {
|
||||
const {
|
||||
data: { characters, userCharacters, selectedSystems, presentCharacters },
|
||||
data: { characters, userCharacters, selectedSystems },
|
||||
} = useMapRootState();
|
||||
|
||||
const [settings, setSettings] = useLocalStorageState<WindowLocalSettingsType>('window:local:settings', {
|
||||
defaultValue: STORED_DEFAULT_VALUES,
|
||||
});
|
||||
|
||||
const [settings, setSettings] = useLocalCharacterWidgetSettings();
|
||||
const [systemId] = selectedSystems;
|
||||
|
||||
const restrictOfflineShowing = useMapGetOption('restrict_offline_showing');
|
||||
const isAdminOrManager = useMapCheckPermissions([UserPermission.MANAGE_MAP]);
|
||||
|
||||
const showOffline = useMemo(
|
||||
() => !restrictOfflineShowing || isAdminOrManager,
|
||||
[isAdminOrManager, restrictOfflineShowing],
|
||||
);
|
||||
|
||||
const itemTemplate = useItemTemplate();
|
||||
|
||||
const sorted = useMemo(() => {
|
||||
const sorted = characters
|
||||
const filtered = characters
|
||||
.filter(x => x.location?.solar_system_id?.toString() === systemId)
|
||||
.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id), compact: settings.compact }))
|
||||
.map(x => ({
|
||||
...x,
|
||||
isOwn: userCharacters.includes(x.eve_id),
|
||||
compact: settings.compact,
|
||||
showShipName: settings.showShipName,
|
||||
}))
|
||||
.sort(sortCharacters);
|
||||
|
||||
if (!showOffline || !settings.showOffline) {
|
||||
return sorted.filter(c => c.online);
|
||||
return filtered.filter(c => c.online);
|
||||
}
|
||||
|
||||
return sorted;
|
||||
// eslint-disable-next-line
|
||||
}, [showOffline, characters, settings.showOffline, settings.compact, systemId, userCharacters, presentCharacters]);
|
||||
return filtered;
|
||||
}, [
|
||||
characters,
|
||||
systemId,
|
||||
userCharacters,
|
||||
settings.compact,
|
||||
settings.showOffline,
|
||||
settings.showShipName,
|
||||
showOffline,
|
||||
]);
|
||||
|
||||
const isNobodyHere = sorted.length === 0;
|
||||
const isNotSelectedSystem = selectedSystems.length !== 1;
|
||||
const showList = sorted.length > 0 && selectedSystems.length === 1;
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const compact = useMaxWidth(ref, 145);
|
||||
const itemTemplate = useLocalCharactersItemTemplate(settings.showShipName);
|
||||
|
||||
return (
|
||||
<Widget
|
||||
label={
|
||||
<div className="flex justify-between items-center text-xs w-full" ref={ref}>
|
||||
<span className="select-none">Local{showList ? ` [${sorted.length}]` : ''}</span>
|
||||
<LayoutEventBlocker className="flex items-center gap-2">
|
||||
{showOffline && (
|
||||
<WdTooltipWrapper content="Show offline characters in system">
|
||||
<WdCheckbox
|
||||
size="xs"
|
||||
labelSide="left"
|
||||
label={compact ? '' : 'Show offline'}
|
||||
value={settings.showOffline}
|
||||
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300"
|
||||
onChange={() => setSettings(() => ({ ...settings, showOffline: !settings.showOffline }))}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
|
||||
<span
|
||||
className={clsx('w-4 h-4 cursor-pointer', {
|
||||
['hero-bars-2']: settings.compact,
|
||||
['hero-bars-3']: !settings.compact,
|
||||
})}
|
||||
onClick={() => setSettings(() => ({ ...settings, compact: !settings.compact }))}
|
||||
></span>
|
||||
</LayoutEventBlocker>
|
||||
</div>
|
||||
<LocalCharactersHeader
|
||||
sortedCount={sorted.length}
|
||||
showList={showList}
|
||||
showOffline={showOffline}
|
||||
settings={settings}
|
||||
setSettings={setSettings}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{isNotSelectedSystem && (
|
||||
@@ -132,23 +73,20 @@ export const LocalCharacters = () => {
|
||||
System is not selected
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isNobodyHere && !isNotSelectedSystem && (
|
||||
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">
|
||||
Nobody here
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showList && (
|
||||
<VirtualScroller
|
||||
<LocalCharactersList
|
||||
items={sorted}
|
||||
itemSize={settings.compact ? 26 : 41}
|
||||
itemTemplate={itemTemplate}
|
||||
className={clsx(
|
||||
classes.VirtualScroller,
|
||||
containerClassName={clsx(
|
||||
'w-full h-full overflow-x-hidden overflow-y-auto custom-scrollbar select-none',
|
||||
classes.VirtualScroller,
|
||||
)}
|
||||
autoSize={false}
|
||||
/>
|
||||
)}
|
||||
</Widget>
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import React, { useRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth';
|
||||
import { LayoutEventBlocker, TooltipPosition, WdCheckbox, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
interface LocalCharactersHeaderProps {
|
||||
sortedCount: number;
|
||||
showList: boolean;
|
||||
showOffline: boolean;
|
||||
settings: {
|
||||
compact: boolean;
|
||||
showOffline: boolean;
|
||||
showShipName: boolean;
|
||||
};
|
||||
setSettings: (fn: (prev: any) => any) => void;
|
||||
}
|
||||
|
||||
export const LocalCharactersHeader: React.FC<LocalCharactersHeaderProps> = ({
|
||||
sortedCount,
|
||||
showList,
|
||||
showOffline,
|
||||
settings,
|
||||
setSettings,
|
||||
}) => {
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
const compactOffline = useMaxWidth(headerRef, 145);
|
||||
const compactShipName = useMaxWidth(headerRef, 195);
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center text-xs justify-between" ref={headerRef}>
|
||||
<div className="flex-shrink-0 select-none mr-2">Local{showList ? ` [${sortedCount}]` : ''}</div>
|
||||
<LayoutEventBlocker className="flex items-center gap-2 justify-end">
|
||||
<div className="flex items-center gap-2">
|
||||
{showOffline && (
|
||||
<WdTooltipWrapper content="Show offline characters in system" position={TooltipPosition.top}>
|
||||
<WdCheckbox
|
||||
size="xs"
|
||||
labelSide="left"
|
||||
label={compactOffline ? '' : 'Offline'}
|
||||
value={settings.showOffline}
|
||||
onChange={() => setSettings((prev: any) => ({ ...prev, showOffline: !prev.showOffline }))}
|
||||
classNameLabel={clsx('whitespace-nowrap text-stone-400 hover:text-stone-200 transition duration-300', {
|
||||
truncate: compactOffline,
|
||||
})}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
|
||||
{settings.compact && (
|
||||
<WdTooltipWrapper content="Show ship name in compact rows" position={TooltipPosition.top}>
|
||||
<WdCheckbox
|
||||
size="xs"
|
||||
labelSide="left"
|
||||
label={compactShipName ? '' : 'Ship name'}
|
||||
value={settings.showShipName}
|
||||
onChange={() => setSettings((prev: any) => ({ ...prev, showShipName: !prev.showShipName }))}
|
||||
classNameLabel={clsx('whitespace-nowrap text-stone-400 hover:text-stone-200 transition duration-300', {
|
||||
truncate: compactShipName,
|
||||
})}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
</div>
|
||||
<WdTooltipWrapper content="Enable compact mode" position={TooltipPosition.top}>
|
||||
<span
|
||||
className={clsx('w-4 h-4 min-w-[1rem] cursor-pointer', {
|
||||
'hero-bars-2': settings.compact,
|
||||
'hero-bars-3': !settings.compact,
|
||||
})}
|
||||
onClick={() => setSettings((prev: any) => ({ ...prev, compact: !prev.compact }))}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
</LayoutEventBlocker>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
.CharacterRow {
|
||||
&.CardBorderLeftIsOwn {
|
||||
border-left-color: rgb(251 146 60 / 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
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 { VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
||||
|
||||
export type LocalCharactersItemTemplateProps = { showShipName: boolean } & CharItemProps &
|
||||
VirtualScrollerTemplateOptions;
|
||||
|
||||
export const LocalCharactersItemTemplate = ({ showShipName, ...options }: LocalCharactersItemTemplateProps) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
classes.CharacterRow,
|
||||
'box-border flex items-center w-full whitespace-nowrap overflow-hidden text-ellipsis min-w-[0px]',
|
||||
{
|
||||
'surface-hover': options.odd,
|
||||
'border-b border-gray-600 border-opacity-20': !options.last,
|
||||
'bg-green-500 hover:bg-green-700 transition duration-300 bg-opacity-10 hover:bg-opacity-10': options.online,
|
||||
},
|
||||
)}
|
||||
style={{ height: `${options.props.itemSize}px` }}
|
||||
>
|
||||
<CharacterCard showShipName={showShipName} {...options} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './LocalCharactersItemTemplate.tsx';
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
||||
import clsx from 'clsx';
|
||||
import { CharItemProps } from './types';
|
||||
|
||||
export type LocalCharactersListProps = {
|
||||
items: Array<CharItemProps>;
|
||||
itemSize: number;
|
||||
itemTemplate: (char: CharItemProps, options: VirtualScrollerTemplateOptions) => React.ReactNode;
|
||||
containerClassName?: string;
|
||||
style?: React.CSSProperties;
|
||||
autoSize?: boolean;
|
||||
};
|
||||
|
||||
export const LocalCharactersList = ({
|
||||
items,
|
||||
itemSize,
|
||||
itemTemplate,
|
||||
containerClassName,
|
||||
style = {},
|
||||
autoSize = false,
|
||||
}: LocalCharactersListProps) => {
|
||||
const computedHeight = autoSize ? `${Math.max(items.length, 1) * itemSize}px` : style.height || '100%';
|
||||
|
||||
const localStyle: React.CSSProperties = {
|
||||
...style,
|
||||
height: computedHeight,
|
||||
width: '100%',
|
||||
boxSizing: 'border-box',
|
||||
overflowX: 'hidden',
|
||||
};
|
||||
|
||||
return (
|
||||
<VirtualScroller
|
||||
items={items}
|
||||
itemSize={itemSize}
|
||||
orientation="vertical"
|
||||
className={clsx('w-full h-full', containerClassName)}
|
||||
itemTemplate={itemTemplate}
|
||||
autoSize={autoSize}
|
||||
style={localStyle}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './LocalCharactersItemTemplate';
|
||||
export * from './LocalCharactersList';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,6 @@
|
||||
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
|
||||
|
||||
export type CharItemProps = {
|
||||
compact: boolean;
|
||||
} & CharacterTypeRaw &
|
||||
WithIsOwnCharacter;
|
||||
@@ -0,0 +1,12 @@
|
||||
import { useCallback } from 'react';
|
||||
import { VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
||||
import { CharItemProps, LocalCharactersItemTemplate } from '../components';
|
||||
|
||||
export function useLocalCharactersItemTemplate(showShipName: boolean) {
|
||||
return useCallback(
|
||||
(char: CharItemProps, options: VirtualScrollerTemplateOptions) => (
|
||||
<LocalCharactersItemTemplate {...char} {...options} showShipName={showShipName} />
|
||||
),
|
||||
[showShipName],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
|
||||
export interface LocalCharacterWidgetSettings {
|
||||
compact: boolean;
|
||||
showOffline: boolean;
|
||||
version: number;
|
||||
showShipName: boolean;
|
||||
}
|
||||
|
||||
export const LOCAL_CHARACTER_WIDGET_DEFAULT: LocalCharacterWidgetSettings = {
|
||||
compact: true,
|
||||
showOffline: false,
|
||||
version: 0,
|
||||
showShipName: false,
|
||||
};
|
||||
|
||||
export function useLocalCharacterWidgetSettings() {
|
||||
return useLocalStorageState<LocalCharacterWidgetSettings>('kills:widget:settings', {
|
||||
defaultValue: LOCAL_CHARACTER_WIDGET_DEFAULT,
|
||||
});
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
.RouteSystem {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #ffffff;
|
||||
|
||||
cursor: pointer;
|
||||
transition: opacity 200ms;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import classes from './RoutesList.module.scss';
|
||||
import { Route, SystemStaticInfoShort } from '@/hooks/Mapper/types/routes.ts';
|
||||
import clsx from 'clsx';
|
||||
import { SystemViewStandalone, WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { SystemViewStandalone, TooltipPosition, WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { getBackgroundClass, getShapeClass } from '@/hooks/Mapper/components/map/helpers';
|
||||
import { MouseEvent, useCallback, useRef, useState } from 'react';
|
||||
import { Commands } from '@/hooks/Mapper/types';
|
||||
@@ -46,9 +46,11 @@ export const RouteSystem = ({
|
||||
<>
|
||||
<WdTooltip
|
||||
ref={tooltipRef}
|
||||
position={TooltipPosition.top}
|
||||
// targetSelector={`.tooltip-route-sys_${destination}_${solar_system_id}`}
|
||||
content={() => (
|
||||
<SystemViewStandalone
|
||||
className="mx-[4px]"
|
||||
security={security}
|
||||
system_class={system_class}
|
||||
class_title={class_title}
|
||||
@@ -63,8 +65,8 @@ export const RouteSystem = ({
|
||||
tooltipRef.current?.show(e);
|
||||
onMouseEnter?.(solar_system_id);
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
tooltipRef.current?.hide(e);
|
||||
onMouseLeave={() => {
|
||||
tooltipRef.current?.hide();
|
||||
onMouseLeave?.();
|
||||
}}
|
||||
onContextMenu={handleContext}
|
||||
|
||||
@@ -21,6 +21,11 @@ import { RoutesProvider, useRouteProvider } from './RoutesProvider.tsx';
|
||||
import { ContextMenuSystemInfo, useContextMenuSystemInfoHandlers } from '@/hooks/Mapper/components/contexts';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import {
|
||||
AddSystemDialog,
|
||||
SearchOnSubmitCallback,
|
||||
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
|
||||
const sortByDist = (a: Route, b: Route) => {
|
||||
const distA = a.has_connection ? a.systems?.length || 0 : Infinity;
|
||||
@@ -163,6 +168,12 @@ export const RoutesWidgetContent = () => {
|
||||
export const RoutesWidgetComp = () => {
|
||||
const [routeSettingsVisible, setRouteSettingsVisible] = useState(false);
|
||||
const { data, update } = useRouteProvider();
|
||||
const {
|
||||
data: { hubs = [] },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const preparedHubs = useMemo(() => hubs.map(x => parseInt(x)), [hubs]);
|
||||
|
||||
const isSecure = data.path_type === 'secure';
|
||||
const handleSecureChange = useCallback(() => {
|
||||
@@ -173,7 +184,24 @@ export const RoutesWidgetComp = () => {
|
||||
}, [data, update]);
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const compact = useMaxWidth(ref, 155);
|
||||
const compact = useMaxWidth(ref, 170);
|
||||
const [openAddSystem, setOpenAddSystem] = useState<boolean>(false);
|
||||
|
||||
const onAddSystem = useCallback(() => setOpenAddSystem(true), []);
|
||||
|
||||
const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback(
|
||||
async item => {
|
||||
if (preparedHubs.includes(item.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.addHub,
|
||||
data: { system_id: item.value },
|
||||
});
|
||||
},
|
||||
[hubs, outCommand],
|
||||
);
|
||||
|
||||
return (
|
||||
<Widget
|
||||
@@ -181,23 +209,44 @@ export const RoutesWidgetComp = () => {
|
||||
<div className="flex justify-between items-center text-xs w-full" ref={ref}>
|
||||
<span className="select-none">Routes</span>
|
||||
<LayoutEventBlocker className="flex items-center gap-2">
|
||||
<WdTooltipWrapper content="Show shortest route">
|
||||
<WdImgButton
|
||||
className={PrimeIcons.PLUS_CIRCLE}
|
||||
onClick={onAddSystem}
|
||||
tooltip={{
|
||||
content: 'Click here to add new system to routes',
|
||||
}}
|
||||
/>
|
||||
|
||||
<WdTooltipWrapper content="Show shortest route" position={TooltipPosition.top}>
|
||||
<WdCheckbox
|
||||
size="xs"
|
||||
labelSide="left"
|
||||
label={compact ? '' : 'Show shortest'}
|
||||
value={!isSecure}
|
||||
onChange={handleSecureChange}
|
||||
classNameLabel={clsx('text-red-400')}
|
||||
classNameLabel="text-red-400 whitespace-nowrap"
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
<WdImgButton className={PrimeIcons.SLIDERS_H} onClick={() => setRouteSettingsVisible(true)} />
|
||||
<WdImgButton
|
||||
className={PrimeIcons.SLIDERS_H}
|
||||
onClick={() => setRouteSettingsVisible(true)}
|
||||
tooltip={{
|
||||
content: 'Click here to open Routes settings',
|
||||
}}
|
||||
/>
|
||||
</LayoutEventBlocker>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<RoutesWidgetContent />
|
||||
<RoutesSettingsDialog visible={routeSettingsVisible} setVisible={setRouteSettingsVisible} />
|
||||
|
||||
<AddSystemDialog
|
||||
title="Add system to routes"
|
||||
visible={openAddSystem}
|
||||
setVisible={() => setOpenAddSystem(false)}
|
||||
onSubmit={handleSubmitAddSystem}
|
||||
/>
|
||||
</Widget>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { SystemKillsContent } from './SystemKillsContent/SystemKillsContent';
|
||||
import { KillsHeader } from './components/SystemKillsHeader';
|
||||
import { useKillsWidgetSettings } from './hooks/useKillsWidgetSettings';
|
||||
import { useSystemKills } from './hooks/useSystemKills';
|
||||
import { KillsSettingsDialog } from './components/SystemKillsSettingsDialog';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace';
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
|
||||
export const SystemKills: React.FC = React.memo(() => {
|
||||
const {
|
||||
data: { selectedSystems, systems, isSubscriptionActive },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const [systemId] = selectedSystems || [];
|
||||
const [settingsDialogVisible, setSettingsDialogVisible] = useState(false);
|
||||
|
||||
const systemNameMap = useMemo(() => {
|
||||
const map: Record<string, string> = {};
|
||||
systems.forEach(sys => {
|
||||
map[sys.id] = sys.temporary_name || sys.name || '???';
|
||||
});
|
||||
return map;
|
||||
}, [systems]);
|
||||
|
||||
const systemBySolarSystemId = useMemo(() => {
|
||||
const map: Record<number, SolarSystemRawType> = {};
|
||||
systems.forEach(sys => {
|
||||
if (sys.system_static_info?.solar_system_id != null) {
|
||||
map[sys.system_static_info.solar_system_id] = sys;
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}, [systems]);
|
||||
|
||||
const [settings] = useKillsWidgetSettings();
|
||||
const visible = settings.showAll;
|
||||
|
||||
const { kills, isLoading, error } = useSystemKills({
|
||||
systemId,
|
||||
outCommand,
|
||||
showAllVisible: visible,
|
||||
});
|
||||
|
||||
const isNothingSelected = !systemId && !visible;
|
||||
const showLoading = isLoading && kills.length === 0;
|
||||
|
||||
const filteredKills = useMemo(() => {
|
||||
if (!settings.whOnly || !visible) return kills;
|
||||
return kills.filter(kill => {
|
||||
const system = systemBySolarSystemId[kill.solar_system_id];
|
||||
if (!system) {
|
||||
console.warn(`System with id ${kill.solar_system_id} not found.`);
|
||||
return false;
|
||||
}
|
||||
return isWormholeSpace(system.system_static_info.system_class);
|
||||
});
|
||||
}, [kills, settings.whOnly, systemBySolarSystemId, visible]);
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col min-h-0">
|
||||
<div className="flex flex-col flex-1 min-h-0">
|
||||
<Widget label={<KillsHeader systemId={systemId} onOpenSettings={() => setSettingsDialogVisible(true)} />}>
|
||||
{!isSubscriptionActive ? (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<span className="select-none text-center text-stone-400/80 text-sm">
|
||||
Kills available with 'Active' map subscription only (contact map administrators)
|
||||
</span>
|
||||
</div>
|
||||
) : isNothingSelected ? (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<span className="select-none text-center text-stone-400/80 text-sm">
|
||||
No system selected (or toggle “Show all systems”)
|
||||
</span>
|
||||
</div>
|
||||
) : showLoading ? (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<span className="select-none text-center text-stone-400/80 text-sm">Loading Kills...</span>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<span className="select-none text-center text-red-400 text-sm">{error}</span>
|
||||
</div>
|
||||
) : !filteredKills || filteredKills.length === 0 ? (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<span className="select-none text-center text-stone-400/80 text-sm">No kills found</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full h-full" style={{ height: '100%' }}>
|
||||
<SystemKillsContent
|
||||
kills={filteredKills}
|
||||
systemNameMap={systemNameMap}
|
||||
onlyOneSystem={!visible}
|
||||
timeRange={settings.timeRange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Widget>
|
||||
</div>
|
||||
|
||||
{settingsDialogVisible && <KillsSettingsDialog visible setVisible={setSettingsDialogVisible} />}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
SystemKills.displayName = 'SystemKills';
|
||||
@@ -0,0 +1,14 @@
|
||||
.wrapper {
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.scrollerContent {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.VirtualScroller {
|
||||
height: 100% !important;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
||||
import { VirtualScroller } from 'primereact/virtualscroller';
|
||||
import { useSystemKillsItemTemplate } from '../hooks/useSystemKillsItemTemplate';
|
||||
import classes from './SystemKillsContent.module.scss';
|
||||
|
||||
export const ITEM_HEIGHT = 35;
|
||||
|
||||
export interface SystemKillsContentProps {
|
||||
kills: DetailedKill[];
|
||||
systemNameMap: Record<string, string>;
|
||||
onlyOneSystem?: boolean;
|
||||
autoSize?: boolean;
|
||||
timeRange?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export const SystemKillsContent: React.FC<SystemKillsContentProps> = ({
|
||||
kills,
|
||||
systemNameMap,
|
||||
onlyOneSystem = false,
|
||||
autoSize = false,
|
||||
timeRange = 4,
|
||||
limit,
|
||||
}) => {
|
||||
const processedKills = useMemo(() => {
|
||||
const sortedKills = kills
|
||||
.filter(k => k.kill_time)
|
||||
.sort((a, b) => new Date(b.kill_time!).getTime() - new Date(a.kill_time!).getTime());
|
||||
|
||||
if (limit !== undefined) {
|
||||
return sortedKills.slice(0, limit);
|
||||
} else {
|
||||
const now = Date.now();
|
||||
const cutoff = now - timeRange * 60 * 60 * 1000;
|
||||
return sortedKills.filter(k => new Date(k.kill_time!).getTime() >= cutoff);
|
||||
}
|
||||
}, [kills, timeRange, limit]);
|
||||
|
||||
const computedHeight = autoSize ? Math.max(processedKills.length, 1) * ITEM_HEIGHT : undefined;
|
||||
const scrollerHeight = autoSize ? `${computedHeight}px` : '100%';
|
||||
|
||||
const itemTemplate = useSystemKillsItemTemplate(systemNameMap, onlyOneSystem);
|
||||
|
||||
return (
|
||||
<div className={clsx('w-full h-full', classes.wrapper)}>
|
||||
<VirtualScroller
|
||||
items={processedKills}
|
||||
itemSize={ITEM_HEIGHT}
|
||||
itemTemplate={itemTemplate}
|
||||
autoSize={autoSize}
|
||||
scrollWidth="100%"
|
||||
style={{ height: scrollerHeight }}
|
||||
className={clsx('w-full h-full custom-scrollbar select-none', {
|
||||
[classes.VirtualScroller]: !autoSize,
|
||||
})}
|
||||
pt={{
|
||||
content: {
|
||||
className: classes.scrollerContent,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SystemKillsContent;
|
||||
@@ -0,0 +1,20 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
.killRowContainer {
|
||||
@apply flex items-center whitespace-nowrap overflow-hidden;
|
||||
&:not(:last-child) {
|
||||
@apply border-b border-stone-800;
|
||||
}
|
||||
@apply bg-transparent transition-all hover:bg-stone-900 hover:border-stone-700;
|
||||
}
|
||||
|
||||
.killRowImage {
|
||||
@apply border border-stone-800 rounded-[4px] object-contain;
|
||||
}
|
||||
|
||||
.attackerCountLabel {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
font-size: 10px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.attackerCountLabelCompact {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
font-size: 0.6rem;
|
||||
line-height: 1;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
padding: 1px 2px;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
||||
import {
|
||||
formatISK,
|
||||
formatTimeMixed,
|
||||
zkillLink,
|
||||
getAttackerSubscript,
|
||||
buildVictimImageUrls,
|
||||
buildAttackerImageUrls,
|
||||
getPrimaryLogoAndTooltip,
|
||||
getAttackerPrimaryImageAndTooltip,
|
||||
} from '../helpers';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
||||
import classes from './KillRowDetail.module.scss';
|
||||
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
export interface CompactKillRowProps {
|
||||
killDetails: DetailedKill;
|
||||
systemName: string;
|
||||
onlyOneSystem: boolean;
|
||||
}
|
||||
|
||||
export const KillRowDetail: React.FC<CompactKillRowProps> = ({ killDetails, systemName, onlyOneSystem }) => {
|
||||
const {
|
||||
killmail_id = 0,
|
||||
// Victim data
|
||||
victim_char_name = 'Unknown Pilot',
|
||||
victim_alliance_ticker = '',
|
||||
victim_corp_ticker = '',
|
||||
victim_ship_name = 'Unknown Ship',
|
||||
victim_corp_name = '',
|
||||
victim_alliance_name = '',
|
||||
victim_char_id = 0,
|
||||
victim_corp_id = 0,
|
||||
victim_alliance_id = 0,
|
||||
victim_ship_type_id = 0,
|
||||
// Attacker data
|
||||
final_blow_char_id = 0,
|
||||
final_blow_char_name = '',
|
||||
final_blow_alliance_ticker = '',
|
||||
final_blow_alliance_name = '',
|
||||
final_blow_alliance_id = 0,
|
||||
final_blow_corp_ticker = '',
|
||||
final_blow_corp_id = 0,
|
||||
final_blow_corp_name = '',
|
||||
final_blow_ship_type_id = 0,
|
||||
kill_time = '',
|
||||
total_value = 0,
|
||||
} = killDetails || {};
|
||||
|
||||
const attackerIsNpc = final_blow_char_id === 0;
|
||||
|
||||
// Define victim affiliation ticker.
|
||||
const victimAffiliationTicker = victim_alliance_ticker || victim_corp_ticker || 'No Ticker';
|
||||
|
||||
const killValueFormatted = total_value != null && total_value > 0 ? `${formatISK(total_value)} ISK` : null;
|
||||
const killTimeAgo = kill_time ? formatTimeMixed(kill_time) : '0h ago';
|
||||
|
||||
const attackerSubscript = getAttackerSubscript(killDetails);
|
||||
|
||||
const { victimCorpLogoUrl, victimAllianceLogoUrl, victimShipUrl } = buildVictimImageUrls({
|
||||
victim_char_id,
|
||||
victim_ship_type_id,
|
||||
victim_corp_id,
|
||||
victim_alliance_id,
|
||||
});
|
||||
|
||||
const { attackerCorpLogoUrl, attackerAllianceLogoUrl } = buildAttackerImageUrls({
|
||||
final_blow_char_id,
|
||||
final_blow_corp_id,
|
||||
final_blow_alliance_id,
|
||||
});
|
||||
|
||||
const { url: victimPrimaryLogoUrl, tooltip: victimPrimaryTooltip } = getPrimaryLogoAndTooltip(
|
||||
victimAllianceLogoUrl,
|
||||
victimCorpLogoUrl,
|
||||
victim_alliance_name,
|
||||
victim_corp_name,
|
||||
'Victim',
|
||||
);
|
||||
|
||||
const { url: attackerPrimaryImageUrl, tooltip: attackerPrimaryTooltip } = getAttackerPrimaryImageAndTooltip(
|
||||
attackerIsNpc,
|
||||
attackerAllianceLogoUrl,
|
||||
attackerCorpLogoUrl,
|
||||
final_blow_alliance_name,
|
||||
final_blow_corp_name,
|
||||
final_blow_ship_type_id,
|
||||
);
|
||||
|
||||
// Define attackerTicker to use the alliance ticker if available, otherwise the corp ticker.
|
||||
const attackerTicker = attackerIsNpc ? '' : final_blow_alliance_ticker || final_blow_corp_ticker || '';
|
||||
|
||||
// For the attacker image link: if the attacker is not an NPC, link to the character page; otherwise, link to the kill page.
|
||||
const attackerLink = attackerIsNpc ? zkillLink('kill', killmail_id) : zkillLink('character', final_blow_char_id);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'h-10 flex items-center border-b border-stone-800',
|
||||
'text-xs whitespace-nowrap overflow-hidden leading-none',
|
||||
)}
|
||||
>
|
||||
{/* Victim Section */}
|
||||
<div className="flex items-center gap-1">
|
||||
{victimShipUrl && (
|
||||
<div className="relative shrink-0 w-8 h-8 overflow-hidden">
|
||||
<a
|
||||
href={zkillLink('kill', killmail_id)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block w-full h-full"
|
||||
>
|
||||
<img
|
||||
src={victimShipUrl}
|
||||
alt="Victim Ship"
|
||||
className={clsx(classes.killRowImage, 'w-full h-full object-contain')}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
{victimPrimaryLogoUrl && (
|
||||
<WdTooltipWrapper content={victimPrimaryTooltip} position={TooltipPosition.top}>
|
||||
<a
|
||||
href={zkillLink('kill', killmail_id)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="relative block shrink-0 w-8 h-8 overflow-hidden"
|
||||
>
|
||||
<img
|
||||
src={victimPrimaryLogoUrl}
|
||||
alt="Victim Primary Logo"
|
||||
className={clsx(classes.killRowImage, 'w-full h-full object-contain')}
|
||||
/>
|
||||
</a>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col ml-2 flex-1 min-w-0 overflow-hidden leading-[1rem]">
|
||||
<div className="truncate text-stone-200">
|
||||
{victim_char_name}
|
||||
<span className="text-stone-400"> / {victimAffiliationTicker}</span>
|
||||
</div>
|
||||
<div className="truncate text-stone-300">
|
||||
{victim_ship_name}
|
||||
{killValueFormatted && (
|
||||
<>
|
||||
<span className="ml-1 text-stone-400">/</span>
|
||||
<span className="ml-1 text-green-400">{killValueFormatted}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center ml-auto gap-2">
|
||||
<div className="flex flex-col items-end flex-1 min-w-0 overflow-hidden text-right leading-[1rem]">
|
||||
{!attackerIsNpc && (final_blow_char_name || attackerTicker) && (
|
||||
<div className="truncate text-stone-200">
|
||||
{final_blow_char_name}
|
||||
{!attackerIsNpc && attackerTicker && <span className="ml-1 text-stone-400">/ {attackerTicker}</span>}
|
||||
</div>
|
||||
)}
|
||||
<div className="truncate text-stone-400">
|
||||
{!onlyOneSystem && systemName ? (
|
||||
<>
|
||||
{systemName} / <span className="ml-1 text-red-400">{killTimeAgo}</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-red-400">{killTimeAgo}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{attackerPrimaryImageUrl && (
|
||||
<WdTooltipWrapper content={attackerPrimaryTooltip} position={TooltipPosition.top}>
|
||||
<a
|
||||
href={attackerLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="relative block shrink-0 w-8 h-8 overflow-hidden"
|
||||
>
|
||||
<img
|
||||
src={attackerPrimaryImageUrl}
|
||||
alt={attackerIsNpc ? 'NPC Ship' : 'Attacker Primary Logo'}
|
||||
className={clsx(classes.killRowImage, 'w-full h-full object-contain')}
|
||||
/>
|
||||
{attackerSubscript && (
|
||||
<span
|
||||
className={clsx(
|
||||
classes.attackerCountLabel,
|
||||
attackerSubscript.cssClass,
|
||||
'text-[0.6rem] leading-none px-[2px]',
|
||||
)}
|
||||
>
|
||||
{attackerSubscript.label}
|
||||
</span>
|
||||
)}
|
||||
</a>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
import React, { useRef } from 'react';
|
||||
import {
|
||||
LayoutEventBlocker,
|
||||
SystemView,
|
||||
TooltipPosition,
|
||||
WdCheckbox,
|
||||
WdImgButton,
|
||||
WdTooltipWrapper,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { useKillsWidgetSettings } from '../hooks/useKillsWidgetSettings';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
|
||||
|
||||
interface KillsHeaderProps {
|
||||
systemId?: string;
|
||||
onOpenSettings: () => void;
|
||||
}
|
||||
|
||||
export const KillsHeader: React.FC<KillsHeaderProps> = ({ systemId, onOpenSettings }) => {
|
||||
const [settings, setSettings] = useKillsWidgetSettings();
|
||||
const { showAll } = settings;
|
||||
|
||||
const onToggleShowAllVisible = () => {
|
||||
setSettings(prev => ({ ...prev, showAll: !prev.showAll }));
|
||||
};
|
||||
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
const compact = useMaxWidth(headerRef, 150);
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center justify-between text-xs" ref={headerRef}>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="text-stone-400">
|
||||
Kills
|
||||
{systemId && !showAll && ' in '}
|
||||
</div>
|
||||
{systemId && !showAll && <SystemView systemId={systemId} className="select-none text-center" hideRegion />}
|
||||
</div>
|
||||
|
||||
<LayoutEventBlocker className="flex items-center gap-2 justify-end">
|
||||
<div className="flex items-center gap-2">
|
||||
<WdTooltipWrapper content="Show all systems" position={TooltipPosition.top}>
|
||||
<WdCheckbox
|
||||
size="xs"
|
||||
labelSide="left"
|
||||
label={compact ? 'All' : 'Show all systems'}
|
||||
value={showAll}
|
||||
onChange={onToggleShowAllVisible}
|
||||
classNameLabel="whitespace-nowrap text-stone-400 hover:text-stone-200 transition duration-300"
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
<WdImgButton
|
||||
className={PrimeIcons.SLIDERS_H}
|
||||
onClick={onOpenSettings}
|
||||
tooltip={{
|
||||
content: 'Open Kills Settings',
|
||||
position: TooltipPosition.top,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</LayoutEventBlocker>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
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);
|
||||
@@ -0,0 +1,163 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { Button } from 'primereact/button';
|
||||
import { WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { useKillsWidgetSettings } from '../hooks/useKillsWidgetSettings';
|
||||
import {
|
||||
AddSystemDialog,
|
||||
SearchOnSubmitCallback,
|
||||
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
|
||||
import { SystemView, TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
interface KillsSettingsDialogProps {
|
||||
visible: boolean;
|
||||
setVisible: (visible: boolean) => void;
|
||||
}
|
||||
|
||||
export const KillsSettingsDialog: React.FC<KillsSettingsDialogProps> = ({ visible, setVisible }) => {
|
||||
const [globalSettings, setGlobalSettings] = useKillsWidgetSettings();
|
||||
const localRef = useRef({
|
||||
showAll: globalSettings.showAll,
|
||||
whOnly: globalSettings.whOnly,
|
||||
excludedSystems: globalSettings.excludedSystems || [],
|
||||
timeRange: globalSettings.timeRange,
|
||||
});
|
||||
|
||||
const [, forceRender] = useState(0);
|
||||
const [addSystemDialogVisible, setAddSystemDialogVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
localRef.current = {
|
||||
showAll: globalSettings.showAll,
|
||||
whOnly: globalSettings.whOnly,
|
||||
excludedSystems: globalSettings.excludedSystems || [],
|
||||
timeRange: globalSettings.timeRange,
|
||||
};
|
||||
forceRender(n => n + 1);
|
||||
}
|
||||
}, [visible, globalSettings]);
|
||||
|
||||
const handleWHChange = useCallback((checked: boolean) => {
|
||||
localRef.current = {
|
||||
...localRef.current,
|
||||
whOnly: checked,
|
||||
};
|
||||
forceRender(n => n + 1);
|
||||
}, []);
|
||||
|
||||
const handleTimeRangeChange = useCallback((newTimeRange: number) => {
|
||||
localRef.current = {
|
||||
...localRef.current,
|
||||
timeRange: newTimeRange,
|
||||
};
|
||||
forceRender(n => n + 1);
|
||||
}, []);
|
||||
|
||||
const handleRemoveSystem = useCallback((sysId: number) => {
|
||||
localRef.current = {
|
||||
...localRef.current,
|
||||
excludedSystems: localRef.current.excludedSystems.filter(id => id !== sysId),
|
||||
};
|
||||
forceRender(n => n + 1);
|
||||
}, []);
|
||||
|
||||
const handleAddSystemSubmit: SearchOnSubmitCallback = useCallback(item => {
|
||||
if (localRef.current.excludedSystems.includes(item.value)) {
|
||||
return;
|
||||
}
|
||||
localRef.current = {
|
||||
...localRef.current,
|
||||
excludedSystems: [...localRef.current.excludedSystems, item.value],
|
||||
};
|
||||
forceRender(n => n + 1);
|
||||
}, []);
|
||||
|
||||
const handleApply = useCallback(() => {
|
||||
setGlobalSettings(prev => ({
|
||||
...prev,
|
||||
...localRef.current,
|
||||
}));
|
||||
setVisible(false);
|
||||
}, [setGlobalSettings, setVisible]);
|
||||
|
||||
const handleHide = useCallback(() => {
|
||||
setVisible(false);
|
||||
}, [setVisible]);
|
||||
|
||||
const localData = localRef.current;
|
||||
const excluded = localData.excludedSystems || [];
|
||||
const timeRangeOptions = [4, 12, 24];
|
||||
|
||||
return (
|
||||
<Dialog header="Kills Settings" visible={visible} style={{ width: '440px' }} draggable={false} onHide={handleHide}>
|
||||
<div className="flex flex-col gap-3 p-2.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="kills-wormhole-only-mode"
|
||||
checked={localData.whOnly}
|
||||
onChange={e => handleWHChange(e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="kills-wormhole-only-mode" className="cursor-pointer">
|
||||
Only show wormhole kills
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-sm">Time Range:</span>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{timeRangeOptions.map(option => (
|
||||
<label key={option} className="cursor-pointer flex items-center gap-1">
|
||||
<input
|
||||
type="radio"
|
||||
name="timeRange"
|
||||
value={option}
|
||||
checked={localData.timeRange === option}
|
||||
onChange={() => handleTimeRangeChange(option)}
|
||||
/>
|
||||
<span className="text-sm">{option} Hours</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Excluded Systems */}
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm text-stone-400">Excluded Systems</label>
|
||||
<WdImgButton
|
||||
className={PrimeIcons.PLUS_CIRCLE}
|
||||
onClick={() => setAddSystemDialogVisible(true)}
|
||||
tooltip={{ content: 'Add system to excluded list' }}
|
||||
/>
|
||||
</div>
|
||||
{excluded.length === 0 && <div className="text-stone-500 text-xs italic">No systems excluded.</div>}
|
||||
{excluded.map(sysId => (
|
||||
<div key={sysId} className="flex items-center justify-between border-b border-stone-600 py-1 px-1 text-xs">
|
||||
<SystemView systemId={sysId.toString()} hideRegion />
|
||||
<WdImgButton
|
||||
className={PrimeIcons.TRASH}
|
||||
onClick={() => handleRemoveSystem(sysId)}
|
||||
tooltip={{ content: 'Remove from excluded', position: TooltipPosition.top }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end mt-4">
|
||||
<Button onClick={handleApply} label="Apply" outlined size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AddSystemDialog
|
||||
title="Add system to kills exclude list"
|
||||
visible={addSystemDialogVisible}
|
||||
setVisible={() => setAddSystemDialogVisible(false)}
|
||||
onSubmit={handleAddSystemSubmit}
|
||||
excludedSystems={excluded}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './linkHelpers';
|
||||
export * from './killRowUtils';
|
||||
@@ -0,0 +1,47 @@
|
||||
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
||||
|
||||
/** Returns "5m ago", "3h ago", "2.5d ago", etc. */
|
||||
export function formatTimeMixed(killTime: string): string {
|
||||
const killDate = new Date(killTime);
|
||||
const diffMs = Date.now() - killDate.getTime();
|
||||
const diffHours = diffMs / (1000 * 60 * 60);
|
||||
|
||||
if (diffHours < 1) {
|
||||
const mins = Math.round(diffHours * 60);
|
||||
return `${mins}m ago`;
|
||||
} else if (diffHours < 24) {
|
||||
const hours = Math.round(diffHours);
|
||||
return `${hours}h ago`;
|
||||
} else {
|
||||
const days = diffHours / 24;
|
||||
const roundedDays = days.toFixed(1);
|
||||
return `${roundedDays}d ago`;
|
||||
}
|
||||
}
|
||||
|
||||
/** Formats integer ISK values into k/M/B/T. */
|
||||
export function formatISK(value: number): string {
|
||||
if (value >= 1_000_000_000_000) {
|
||||
return `${(value / 1_000_000_000_000).toFixed(2)}T`;
|
||||
} else if (value >= 1_000_000_000) {
|
||||
return `${(value / 1_000_000_000).toFixed(2)}B`;
|
||||
} else if (value >= 1_000_000) {
|
||||
return `${(value / 1_000_000).toFixed(2)}M`;
|
||||
} else if (value >= 1_000) {
|
||||
return `${(value / 1_000).toFixed(2)}k`;
|
||||
}
|
||||
return Math.round(value).toString();
|
||||
}
|
||||
|
||||
export function getAttackerSubscript(kill: DetailedKill) {
|
||||
if (kill.npc) {
|
||||
return { label: 'npc', cssClass: 'text-purple-400' };
|
||||
}
|
||||
const count = kill.attacker_count ?? 0;
|
||||
if (count === 1) {
|
||||
return { label: 'solo', cssClass: 'text-green-400' };
|
||||
} else if (count > 1) {
|
||||
return { label: String(count), cssClass: 'text-white' };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
const ZKILL_URL = 'https://zkillboard.com';
|
||||
const BASE_IMAGE_URL = 'https://images.evetech.net';
|
||||
|
||||
export function zkillLink(type: 'kill' | 'character' | 'corporation' | 'alliance', id?: number | null): string {
|
||||
if (!id) return `${ZKILL_URL}`;
|
||||
if (type === 'kill') return `${ZKILL_URL}/kill/${id}/`;
|
||||
if (type === 'character') return `${ZKILL_URL}/character/${id}/`;
|
||||
if (type === 'corporation') return `${ZKILL_URL}/corporation/${id}/`;
|
||||
if (type === 'alliance') return `${ZKILL_URL}/alliance/${id}/`;
|
||||
return `${ZKILL_URL}`;
|
||||
}
|
||||
|
||||
export function eveImageUrl(
|
||||
category: 'characters' | 'corporations' | 'alliances' | 'types',
|
||||
id?: number | null,
|
||||
variation: string = 'icon',
|
||||
size?: number,
|
||||
): string | null {
|
||||
if (!id || id <= 0) {
|
||||
return null;
|
||||
}
|
||||
let url = `${BASE_IMAGE_URL}/${category}/${id}/${variation}`;
|
||||
if (size) {
|
||||
url += `?size=${size}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
export function buildVictimImageUrls(args: {
|
||||
victim_char_id?: number | null;
|
||||
victim_ship_type_id?: number | null;
|
||||
victim_corp_id?: number | null;
|
||||
victim_alliance_id?: number | null;
|
||||
}) {
|
||||
const { victim_char_id, victim_ship_type_id, victim_corp_id, victim_alliance_id } = args;
|
||||
|
||||
const victimPortraitUrl = eveImageUrl('characters', victim_char_id, 'portrait', 64);
|
||||
const victimShipUrl = eveImageUrl('types', victim_ship_type_id, 'render', 64);
|
||||
const victimCorpLogoUrl = eveImageUrl('corporations', victim_corp_id, 'logo', 32);
|
||||
const victimAllianceLogoUrl = eveImageUrl('alliances', victim_alliance_id, 'logo', 32);
|
||||
|
||||
return {
|
||||
victimPortraitUrl,
|
||||
victimShipUrl,
|
||||
victimCorpLogoUrl,
|
||||
victimAllianceLogoUrl,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildAttackerShipUrl(final_blow_ship_type_id?: number | null): string | null {
|
||||
return eveImageUrl('types', final_blow_ship_type_id, 'render', 64);
|
||||
}
|
||||
|
||||
export function buildAttackerImageUrls(args: {
|
||||
final_blow_char_id?: number | null;
|
||||
final_blow_corp_id?: number | null;
|
||||
final_blow_alliance_id?: number | null;
|
||||
}) {
|
||||
const { final_blow_char_id, final_blow_corp_id, final_blow_alliance_id } = args;
|
||||
|
||||
const attackerPortraitUrl = eveImageUrl('characters', final_blow_char_id, 'portrait', 64);
|
||||
const attackerCorpLogoUrl = eveImageUrl('corporations', final_blow_corp_id, 'logo', 32);
|
||||
const attackerAllianceLogoUrl = eveImageUrl('alliances', final_blow_alliance_id, 'logo', 32);
|
||||
|
||||
return {
|
||||
attackerPortraitUrl,
|
||||
attackerCorpLogoUrl,
|
||||
attackerAllianceLogoUrl,
|
||||
};
|
||||
}
|
||||
|
||||
export function getPrimaryLogoAndTooltip(
|
||||
allianceUrl: string | null,
|
||||
corpUrl: string | null,
|
||||
allianceName: string,
|
||||
corpName: string,
|
||||
fallback: string,
|
||||
) {
|
||||
let url: string | null = null;
|
||||
let tooltip = '';
|
||||
|
||||
if (allianceUrl) {
|
||||
url = allianceUrl;
|
||||
tooltip = allianceName || fallback;
|
||||
} else if (corpUrl) {
|
||||
url = corpUrl;
|
||||
tooltip = corpName || fallback;
|
||||
}
|
||||
|
||||
return { url, tooltip };
|
||||
}
|
||||
|
||||
export function getAttackerPrimaryImageAndTooltip(
|
||||
isNpc: boolean,
|
||||
allianceUrl: string | null,
|
||||
corpUrl: string | null,
|
||||
allianceName: string,
|
||||
corpName: string,
|
||||
finalBlowShipTypeId: number | null,
|
||||
npcFallback: string = 'NPC Attacker',
|
||||
) {
|
||||
if (isNpc) {
|
||||
const shipUrl = buildAttackerShipUrl(finalBlowShipTypeId);
|
||||
return {
|
||||
url: shipUrl,
|
||||
tooltip: npcFallback,
|
||||
};
|
||||
}
|
||||
|
||||
return getPrimaryLogoAndTooltip(allianceUrl, corpUrl, allianceName, corpName, 'Attacker');
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { useMemo, useCallback } from 'react';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
|
||||
export interface KillsWidgetSettings {
|
||||
showAll: boolean;
|
||||
whOnly: boolean;
|
||||
excludedSystems: number[];
|
||||
version: number;
|
||||
timeRange: number;
|
||||
}
|
||||
|
||||
export const DEFAULT_KILLS_WIDGET_SETTINGS: KillsWidgetSettings = {
|
||||
showAll: false,
|
||||
whOnly: true,
|
||||
excludedSystems: [],
|
||||
version: 2,
|
||||
timeRange: 1,
|
||||
};
|
||||
|
||||
function mergeWithDefaults(settings?: Partial<KillsWidgetSettings>): KillsWidgetSettings {
|
||||
if (!settings) {
|
||||
return DEFAULT_KILLS_WIDGET_SETTINGS;
|
||||
}
|
||||
|
||||
return {
|
||||
...DEFAULT_KILLS_WIDGET_SETTINGS,
|
||||
...settings,
|
||||
excludedSystems: Array.isArray(settings.excludedSystems) ? settings.excludedSystems : [],
|
||||
};
|
||||
}
|
||||
|
||||
export function useKillsWidgetSettings() {
|
||||
const [rawValue, setRawValue] = useLocalStorageState<KillsWidgetSettings | undefined>('kills:widget:settings');
|
||||
|
||||
const value = useMemo<KillsWidgetSettings>(() => {
|
||||
return mergeWithDefaults(rawValue);
|
||||
}, [rawValue]);
|
||||
|
||||
const setValue = useCallback(
|
||||
(newVal: KillsWidgetSettings | ((prev: KillsWidgetSettings) => KillsWidgetSettings)) => {
|
||||
setRawValue(prev => {
|
||||
const mergedPrev = mergeWithDefaults(prev);
|
||||
|
||||
const nextUnmerged = typeof newVal === 'function' ? newVal(mergedPrev) : newVal;
|
||||
|
||||
return mergeWithDefaults(nextUnmerged);
|
||||
});
|
||||
},
|
||||
[setRawValue],
|
||||
);
|
||||
|
||||
return [value, setValue] as const;
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
import { useCallback, useMemo, useState, useEffect, useRef } from 'react';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useKillsWidgetSettings } from './useKillsWidgetSettings';
|
||||
|
||||
interface UseSystemKillsProps {
|
||||
systemId?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
outCommand: (payload: any) => Promise<any>;
|
||||
showAllVisible?: boolean;
|
||||
sinceHours?: number;
|
||||
}
|
||||
|
||||
function combineKills(existing: DetailedKill[], incoming: DetailedKill[], sinceHours: number): DetailedKill[] {
|
||||
const cutoff = Date.now() - sinceHours * 60 * 60 * 1000;
|
||||
const byId: Record<string, DetailedKill> = {};
|
||||
|
||||
for (const kill of [...existing, ...incoming]) {
|
||||
if (!kill.kill_time) {
|
||||
continue;
|
||||
}
|
||||
const killTimeMs = new Date(kill.kill_time).valueOf();
|
||||
|
||||
if (killTimeMs >= cutoff) {
|
||||
byId[kill.killmail_id] = kill;
|
||||
}
|
||||
}
|
||||
|
||||
return Object.values(byId);
|
||||
}
|
||||
|
||||
export function useSystemKills({ systemId, outCommand, showAllVisible = false, sinceHours = 24 }: UseSystemKillsProps) {
|
||||
const { data, update } = useMapRootState();
|
||||
const { detailedKills = {}, systems = [] } = data;
|
||||
|
||||
const [settings] = useKillsWidgetSettings();
|
||||
const excludedSystems = settings.excludedSystems;
|
||||
|
||||
// When showing all visible kills, filter out excluded systems;
|
||||
// when showAllVisible is false, ignore the exclusion filter.
|
||||
const effectiveSystemIds = useMemo(() => {
|
||||
if (showAllVisible) {
|
||||
return systems.map(s => s.id).filter(id => !excludedSystems.includes(Number(id)));
|
||||
}
|
||||
return systems.map(s => s.id);
|
||||
}, [systems, excludedSystems, showAllVisible]);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const didFallbackFetch = useRef(Object.keys(detailedKills).length !== 0);
|
||||
|
||||
const mergeKillsIntoGlobal = useCallback(
|
||||
(killsMap: Record<string, DetailedKill[]>) => {
|
||||
update(prev => {
|
||||
const oldMap = prev.detailedKills ?? {};
|
||||
const updated: Record<string, DetailedKill[]> = { ...oldMap };
|
||||
|
||||
for (const [sid, newKills] of Object.entries(killsMap)) {
|
||||
const existing = updated[sid] ?? [];
|
||||
const combined = combineKills(existing, newKills, sinceHours);
|
||||
updated[sid] = combined;
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
detailedKills: updated,
|
||||
};
|
||||
});
|
||||
},
|
||||
[update, sinceHours],
|
||||
);
|
||||
|
||||
const fetchKills = useCallback(
|
||||
async (forceFallback = false) => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
let eventType: OutCommand;
|
||||
let requestData: Record<string, unknown>;
|
||||
|
||||
if (showAllVisible || forceFallback) {
|
||||
eventType = OutCommand.getSystemsKills;
|
||||
requestData = {
|
||||
system_ids: effectiveSystemIds,
|
||||
since_hours: sinceHours,
|
||||
};
|
||||
} else if (systemId) {
|
||||
eventType = OutCommand.getSystemKills;
|
||||
requestData = {
|
||||
system_id: systemId,
|
||||
since_hours: sinceHours,
|
||||
};
|
||||
} else {
|
||||
// If there's no system and not showing all, do nothing
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const resp = await outCommand({
|
||||
type: eventType,
|
||||
data: requestData,
|
||||
});
|
||||
|
||||
// Single system => `resp.kills`
|
||||
if (resp?.kills) {
|
||||
const arr = resp.kills as DetailedKill[];
|
||||
const sid = systemId ?? 'unknown';
|
||||
mergeKillsIntoGlobal({ [sid]: arr });
|
||||
}
|
||||
// multiple systems => `resp.systems_kills`
|
||||
else if (resp?.systems_kills) {
|
||||
mergeKillsIntoGlobal(resp.systems_kills as Record<string, DetailedKill[]>);
|
||||
} else {
|
||||
console.warn('[useSystemKills] Unexpected kills response =>', resp);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[useSystemKills] Failed to fetch kills:', err);
|
||||
setError(err instanceof Error ? err.message : 'Error fetching kills');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[showAllVisible, systemId, outCommand, effectiveSystemIds, sinceHours, mergeKillsIntoGlobal],
|
||||
);
|
||||
|
||||
const debouncedFetchKills = useMemo(
|
||||
() =>
|
||||
debounce(fetchKills, 500, {
|
||||
leading: true,
|
||||
trailing: false,
|
||||
}),
|
||||
[fetchKills],
|
||||
);
|
||||
|
||||
const finalKills = useMemo(() => {
|
||||
if (showAllVisible) {
|
||||
return effectiveSystemIds.flatMap(sid => detailedKills[sid] ?? []);
|
||||
} else if (systemId) {
|
||||
return detailedKills[systemId] ?? [];
|
||||
} else if (didFallbackFetch.current) {
|
||||
// if we already did a fallback, we may have data for multiple systems
|
||||
return effectiveSystemIds.flatMap(sid => detailedKills[sid] ?? []);
|
||||
}
|
||||
return [];
|
||||
}, [showAllVisible, systemId, effectiveSystemIds, detailedKills]);
|
||||
|
||||
const effectiveIsLoading = isLoading && finalKills.length === 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (!systemId && !showAllVisible && !didFallbackFetch.current) {
|
||||
didFallbackFetch.current = true;
|
||||
// Cancel any queued debounced calls, then do the fallback.
|
||||
debouncedFetchKills.cancel();
|
||||
fetchKills(true); // forceFallback => fetch as though showAllVisible is true
|
||||
}
|
||||
}, [systemId, showAllVisible, debouncedFetchKills, fetchKills]);
|
||||
|
||||
useEffect(() => {
|
||||
if (effectiveSystemIds.length === 0) return;
|
||||
|
||||
if (showAllVisible || systemId) {
|
||||
debouncedFetchKills();
|
||||
// Clean up the debounce on unmount or changes
|
||||
return () => debouncedFetchKills.cancel();
|
||||
}
|
||||
}, [showAllVisible, systemId, effectiveSystemIds, debouncedFetchKills]);
|
||||
|
||||
const refetch = useCallback(() => {
|
||||
debouncedFetchKills.cancel();
|
||||
fetchKills(); // immediate (non-debounced) call
|
||||
}, [debouncedFetchKills, fetchKills]);
|
||||
|
||||
return {
|
||||
kills: finalKills,
|
||||
isLoading: effectiveIsLoading,
|
||||
error,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// useSystemKillsItemTemplate.tsx
|
||||
import { useCallback } from 'react';
|
||||
import { VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
||||
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
||||
import { KillItemTemplate } from '../components/KillItemTemplate';
|
||||
|
||||
export function useSystemKillsItemTemplate(systemNameMap: Record<string, string>, onlyOneSystem: boolean) {
|
||||
return useCallback(
|
||||
(kill: DetailedKill, options: VirtualScrollerTemplateOptions) =>
|
||||
KillItemTemplate(systemNameMap, onlyOneSystem, kill, options),
|
||||
[systemNameMap, onlyOneSystem],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './SystemKills';
|
||||
@@ -0,0 +1,96 @@
|
||||
import React from 'react';
|
||||
import { SystemView, WdCheckbox, WdImgButton, TooltipPosition } 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';
|
||||
|
||||
export type HeaderProps = {
|
||||
systemId: string;
|
||||
isNotSelectedSystem: boolean;
|
||||
sigCount: number;
|
||||
isCompact: boolean;
|
||||
lazyDeleteValue: boolean;
|
||||
onLazyDeleteChange: (checked: boolean) => void;
|
||||
pendingCount: number;
|
||||
onUndoClick: () => void;
|
||||
onSettingsClick: () => void;
|
||||
};
|
||||
|
||||
function HeaderImpl({
|
||||
systemId,
|
||||
isNotSelectedSystem,
|
||||
sigCount,
|
||||
isCompact,
|
||||
lazyDeleteValue,
|
||||
onLazyDeleteChange,
|
||||
pendingCount,
|
||||
onUndoClick,
|
||||
onSettingsClick,
|
||||
}: HeaderProps) {
|
||||
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>
|
||||
|
||||
<LayoutEventBlocker className="flex gap-2.5">
|
||||
<WdTooltipWrapper content="Enable Lazy delete">
|
||||
<WdCheckbox
|
||||
size="xs"
|
||||
labelSide="left"
|
||||
label={isCompact ? '' : 'Lazy delete'}
|
||||
value={lazyDeleteValue}
|
||||
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300 whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
onChange={(event: CheckboxChangeEvent) => onLazyDeleteChange(!!event.checked)}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
{pendingCount > 0 && (
|
||||
<WdImgButton
|
||||
className={PrimeIcons.UNDO}
|
||||
style={{ color: 'red' }}
|
||||
tooltip={{ content: `Undo pending changes (${pendingCount})` }}
|
||||
onClick={onUndoClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
<WdImgButton
|
||||
className={PrimeIcons.QUESTION_CIRCLE}
|
||||
tooltip={{
|
||||
position: TooltipPosition.left,
|
||||
content: (
|
||||
<div className="flex flex-col gap-1">
|
||||
<InfoDrawer title={<b className="text-slate-50">How to add/update signature?</b>}>
|
||||
In game you need to select one or more signatures <br /> in the list in{' '}
|
||||
<b className="text-sky-500">Probe scanner</b>. <br /> Use next hotkeys:
|
||||
<br />
|
||||
<b className="text-sky-500">Shift + LMB</b> or <b className="text-sky-500">Ctrl + LMB</b>
|
||||
<br /> or <b className="text-sky-500">Ctrl + A</b> for select all
|
||||
<br /> and then use <b className="text-sky-500">Ctrl + C</b>, after you need to go <br />
|
||||
here, select Solar system and paste it with <b className="text-sky-500">Ctrl + V</b>
|
||||
</InfoDrawer>
|
||||
<InfoDrawer title={<b className="text-slate-50">How to select?</b>}>
|
||||
For selecting any signature, click on it <br /> with hotkeys{' '}
|
||||
<b className="text-sky-500">Shift + LMB</b> or <b className="text-sky-500">Ctrl + LMB</b>
|
||||
</InfoDrawer>
|
||||
<InfoDrawer title={<b className="text-slate-50">How to delete?</b>}>
|
||||
To delete any signature, first select it <br /> and then press <b className="text-sky-500">Del</b>
|
||||
</InfoDrawer>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<WdImgButton className={PrimeIcons.SLIDERS_H} onClick={onSettingsClick} />
|
||||
</LayoutEventBlocker>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const SystemSignaturesHeader = React.memo(HeaderImpl);
|
||||
@@ -1,45 +1,38 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import {
|
||||
InfoDrawer,
|
||||
LayoutEventBlocker,
|
||||
TooltipPosition,
|
||||
WdImgButton,
|
||||
WdCheckbox,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { SystemSignaturesContent } from './SystemSignaturesContent';
|
||||
import {
|
||||
Setting,
|
||||
SystemSignatureSettingsDialog,
|
||||
COSMIC_SIGNATURE,
|
||||
COSMIC_ANOMALY,
|
||||
COSMIC_SIGNATURE,
|
||||
DEPLOYABLE,
|
||||
STRUCTURE,
|
||||
STARBASE,
|
||||
SHIP,
|
||||
DRONE,
|
||||
Setting,
|
||||
SHIP,
|
||||
STARBASE,
|
||||
STRUCTURE,
|
||||
SystemSignatureSettingsDialog,
|
||||
} from './SystemSignatureSettingsDialog';
|
||||
import { SignatureGroup } from '@/hooks/Mapper/types';
|
||||
|
||||
import React, { useCallback, useEffect, useState, useMemo, useRef } from 'react';
|
||||
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
|
||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CheckboxChangeEvent } from 'primereact/checkbox';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks';
|
||||
import { COMPACT_MAX_WIDTH } from './constants';
|
||||
import { renderHeaderLabel } from './renders';
|
||||
|
||||
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v5_2';
|
||||
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v5_5';
|
||||
export const SHOW_DESCRIPTION_COLUMN_SETTING = 'show_description_column_setting';
|
||||
export const SHOW_UPDATED_COLUMN_SETTING = 'SHOW_UPDATED_COLUMN_SETTING';
|
||||
export const SHOW_CHARACTER_COLUMN_SETTING = 'SHOW_CHARACTER_COLUMN_SETTING';
|
||||
export const LAZY_DELETE_SIGNATURES_SETTING = 'LAZY_DELETE_SIGNATURES_SETTING';
|
||||
export const KEEP_LAZY_DELETE_SETTING = 'KEEP_LAZY_DELETE_ENABLED_SETTING';
|
||||
|
||||
const settings: Setting[] = [
|
||||
const SETTINGS: Setting[] = [
|
||||
{ key: SHOW_UPDATED_COLUMN_SETTING, name: 'Show Updated Column', value: false, isFilter: false },
|
||||
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: false, isFilter: false },
|
||||
{ key: SHOW_CHARACTER_COLUMN_SETTING, name: 'Show Character Column', value: false, isFilter: false },
|
||||
{ key: LAZY_DELETE_SIGNATURES_SETTING, name: 'Lazy Delete Signatures', value: false, isFilter: false },
|
||||
{ key: KEEP_LAZY_DELETE_SETTING, name: 'Keep "Lazy Delete" Enabled', value: false, isFilter: false },
|
||||
|
||||
{ key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true, isFilter: true },
|
||||
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true, isFilter: true },
|
||||
{ key: DEPLOYABLE, name: 'Show Deployables', value: true, isFilter: true },
|
||||
@@ -55,101 +48,107 @@ const settings: Setting[] = [
|
||||
{ key: SignatureGroup.CombatSite, name: 'Show Combat Sites', value: true, isFilter: true },
|
||||
];
|
||||
|
||||
const defaultSettings = () => {
|
||||
return [...settings];
|
||||
};
|
||||
function getDefaultSettings(): Setting[] {
|
||||
return [...SETTINGS];
|
||||
}
|
||||
|
||||
export const SystemSignatures = () => {
|
||||
export const SystemSignatures: React.FC = () => {
|
||||
const {
|
||||
data: { selectedSystems },
|
||||
} = useMapRootState();
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [settings, setSettings] = useState<Setting[]>(defaultSettings);
|
||||
|
||||
const [currentSettings, setCurrentSettings] = useState<Setting[]>(() => {
|
||||
const stored = localStorage.getItem(SIGNATURE_SETTINGS_KEY);
|
||||
if (stored) {
|
||||
try {
|
||||
return JSON.parse(stored) as Setting[];
|
||||
} catch (error) {
|
||||
console.error('Error parsing stored settings', error);
|
||||
}
|
||||
}
|
||||
return getDefaultSettings();
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(currentSettings));
|
||||
}, [currentSettings]);
|
||||
|
||||
const [sigCount, setSigCount] = useState<number>(0);
|
||||
const [pendingSigs, setPendingSigs] = useState<SystemSignature[]>([]);
|
||||
|
||||
const undoPendingFnRef = useRef<() => void>(() => {});
|
||||
|
||||
const handleSigCountChange = useCallback((count: number) => {
|
||||
setSigCount(count);
|
||||
}, []);
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
|
||||
const isNotSelectedSystem = selectedSystems.length !== 1;
|
||||
|
||||
const lazyDeleteValue = useMemo(() => {
|
||||
return settings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)!.value;
|
||||
}, [settings]);
|
||||
const lazyDeleteValue = useMemo(
|
||||
() => currentSettings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)?.value || false,
|
||||
[currentSettings]
|
||||
);
|
||||
|
||||
const handleSettingsChange = useCallback((settings: Setting[]) => {
|
||||
setSettings(settings);
|
||||
localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(settings));
|
||||
const handleSettingsChange = useCallback((newSettings: Setting[]) => {
|
||||
setCurrentSettings(newSettings);
|
||||
setVisible(false);
|
||||
}, []);
|
||||
|
||||
const handleLazyDeleteChange = useCallback((value: boolean) => {
|
||||
setSettings(settings => {
|
||||
const lazyDelete = settings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)!;
|
||||
lazyDelete.value = value;
|
||||
localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(settings));
|
||||
return [...settings];
|
||||
});
|
||||
setCurrentSettings(prevSettings =>
|
||||
prevSettings.map(setting => (setting.key === LAZY_DELETE_SIGNATURES_SETTING ? { ...setting, value } : setting))
|
||||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const restoredSettings = localStorage.getItem(SIGNATURE_SETTINGS_KEY);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const isCompact = useMaxWidth(containerRef, COMPACT_MAX_WIDTH);
|
||||
|
||||
if (restoredSettings) {
|
||||
setSettings(JSON.parse(restoredSettings));
|
||||
useHotkey(true, ['z'], event => {
|
||||
if (pendingSigs.length > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
undoPendingFnRef.current();
|
||||
setPendingSigs([]);
|
||||
}
|
||||
});
|
||||
|
||||
const handleUndoClick = useCallback(() => {
|
||||
undoPendingFnRef.current();
|
||||
setPendingSigs([]);
|
||||
}, []);
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const compact = useMaxWidth(ref, 260);
|
||||
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;
|
||||
});
|
||||
undoPendingFnRef.current = newUndo;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Widget
|
||||
label={
|
||||
<div className="flex justify-between items-center text-xs w-full h-full" ref={ref}>
|
||||
<div className="flex gap-1 whitespace-nowrap text-ellipsis overflow-hidden">System Signatures</div>
|
||||
|
||||
<LayoutEventBlocker className="flex gap-2.5">
|
||||
<WdTooltipWrapper content="Enable Lazy delete">
|
||||
<WdCheckbox
|
||||
size="xs"
|
||||
labelSide="left"
|
||||
label={compact ? '' : 'Lazy delete'}
|
||||
value={lazyDeleteValue}
|
||||
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300 whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
onChange={(event: CheckboxChangeEvent) => handleLazyDeleteChange(!!event.checked)}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
<WdImgButton
|
||||
className={PrimeIcons.QUESTION_CIRCLE}
|
||||
tooltip={{
|
||||
position: TooltipPosition.left,
|
||||
// @ts-ignore
|
||||
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 select one or more signatures <br /> in 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 select any signature need click on that, <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>}>
|
||||
For delete any signature first of all you need select before
|
||||
<br /> and then use <b className="text-sky-500">Del</b>
|
||||
</InfoDrawer>
|
||||
</div>
|
||||
) as React.ReactNode,
|
||||
}}
|
||||
/>
|
||||
<WdImgButton className={PrimeIcons.SLIDERS_H} onClick={() => setVisible(true)} />
|
||||
</LayoutEventBlocker>
|
||||
<div ref={containerRef} className="w-full">
|
||||
{renderHeaderLabel({
|
||||
systemId,
|
||||
isNotSelectedSystem,
|
||||
isCompact,
|
||||
sigCount,
|
||||
lazyDeleteValue,
|
||||
pendingCount: pendingSigs.length,
|
||||
onLazyDeleteChange: handleLazyDeleteChange,
|
||||
onUndoClick: handleUndoClick,
|
||||
onSettingsClick: handleSettingsButtonClick,
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
@@ -158,11 +157,17 @@ export const SystemSignatures = () => {
|
||||
System is not selected
|
||||
</div>
|
||||
) : (
|
||||
<SystemSignaturesContent systemId={systemId} settings={settings} onLazyDeleteChange={handleLazyDeleteChange} />
|
||||
<SystemSignaturesContent
|
||||
systemId={systemId}
|
||||
settings={currentSettings}
|
||||
onLazyDeleteChange={handleLazyDeleteChange}
|
||||
onCountChange={handleSigCountChange}
|
||||
onPendingChange={handlePendingChange}
|
||||
/>
|
||||
)}
|
||||
{visible && (
|
||||
<SystemSignatureSettingsDialog
|
||||
settings={settings}
|
||||
settings={currentSettings}
|
||||
onCancel={() => setVisible(false)}
|
||||
onSave={handleSettingsChange}
|
||||
/>
|
||||
@@ -170,3 +175,5 @@ export const SystemSignatures = () => {
|
||||
</Widget>
|
||||
);
|
||||
};
|
||||
|
||||
export default SystemSignatures;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
.TableRowCompact {
|
||||
height: 8px;
|
||||
max-height: 8px;
|
||||
font-size: 12px !important;
|
||||
line-height: 8px;
|
||||
}
|
||||
|
||||
.Table {
|
||||
|
||||
}
|
||||
@@ -1,26 +1,27 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { parseSignatures } from '@/hooks/Mapper/helpers';
|
||||
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
|
||||
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
||||
import { DataTable, DataTableRowClickEvent, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable';
|
||||
import { Column } from 'primereact/column';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import useRefState from 'react-usestateref';
|
||||
import { Setting } from '../SystemSignatureSettingsDialog';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
|
||||
import { useClipboard } from '@/hooks/Mapper/hooks/useClipboard';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
|
||||
import classes from './SystemSignaturesContent.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
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 {
|
||||
getActualSigs,
|
||||
getRowColorByTimeLeft,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers';
|
||||
COMPACT_MAX_WIDTH,
|
||||
GROUPS_LIST,
|
||||
MEDIUM_MAX_WIDTH,
|
||||
OTHER_COLUMNS_WIDTH,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants';
|
||||
import {
|
||||
KEEP_LAZY_DELETE_SETTING,
|
||||
LAZY_DELETE_SIGNATURES_SETTING,
|
||||
SHOW_DESCRIPTION_COLUMN_SETTING,
|
||||
SHOW_UPDATED_COLUMN_SETTING,
|
||||
SHOW_CHARACTER_COLUMN_SETTING,
|
||||
} from '../SystemSignatures';
|
||||
import { COSMIC_SIGNATURE } from '../SystemSignatureSettingsDialog';
|
||||
import {
|
||||
renderAddedTimeLeft,
|
||||
renderDescription,
|
||||
@@ -28,18 +29,12 @@ import {
|
||||
renderInfoColumn,
|
||||
renderUpdatedTimeLeft,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { SignatureSettings } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { COSMIC_SIGNATURE } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
|
||||
import {
|
||||
SHOW_DESCRIPTION_COLUMN_SETTING,
|
||||
SHOW_UPDATED_COLUMN_SETTING,
|
||||
LAZY_DELETE_SIGNATURES_SETTING,
|
||||
KEEP_LAZY_DELETE_SETTING,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures';
|
||||
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';
|
||||
|
||||
type SystemSignaturesSortSettings = {
|
||||
sortField: string;
|
||||
sortOrder: SortOrder;
|
||||
@@ -52,390 +47,288 @@ const SORT_DEFAULT_VALUES: SystemSignaturesSortSettings = {
|
||||
|
||||
interface SystemSignaturesContentProps {
|
||||
systemId: string;
|
||||
settings: Setting[];
|
||||
settings: { key: string; value: boolean }[];
|
||||
hideLinkedSignatures?: boolean;
|
||||
selectable?: boolean;
|
||||
onSelect?: (signature: SystemSignature) => void;
|
||||
onLazyDeleteChange?: (value: boolean) => void;
|
||||
onCountChange?: (count: number) => void;
|
||||
onPendingChange?: (pending: ExtendedSystemSignature[], undo: () => void) => void;
|
||||
}
|
||||
export const SystemSignaturesContent = ({
|
||||
|
||||
const headerInlineStyle = { padding: '2px', fontSize: '12px', lineHeight: '1.333' };
|
||||
|
||||
export function SystemSignaturesContent({
|
||||
systemId,
|
||||
settings,
|
||||
hideLinkedSignatures,
|
||||
selectable,
|
||||
onSelect,
|
||||
onLazyDeleteChange,
|
||||
}: SystemSignaturesContentProps) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
|
||||
const [signatures, setSignatures, signaturesRef] = useRefState<SystemSignature[]>([]);
|
||||
const [selectedSignatures, setSelectedSignatures] = useState<SystemSignature[]>([]);
|
||||
const [nameColumnWidth, setNameColumnWidth] = useState('auto');
|
||||
const [selectedSignature, setSelectedSignature] = useState<SystemSignature | null>(null);
|
||||
|
||||
const [hoveredSig, setHoveredSig] = useState<SystemSignature | null>(null);
|
||||
|
||||
const [sortSettings, setSortSettings] = useLocalStorageState<SystemSignaturesSortSettings>('window:signatures:sort', {
|
||||
defaultValue: SORT_DEFAULT_VALUES,
|
||||
});
|
||||
|
||||
const tableRef = useRef<HTMLDivElement>(null);
|
||||
const compact = useMaxWidth(tableRef, 260);
|
||||
const medium = useMaxWidth(tableRef, 380);
|
||||
const refData = useRef({ selectable });
|
||||
refData.current = { selectable };
|
||||
|
||||
const tooltipRef = useRef<WdTooltipHandlers>(null);
|
||||
|
||||
const { clipboardContent, setClipboardContent } = useClipboard();
|
||||
|
||||
const lazyDeleteValue = useMemo(() => {
|
||||
return settings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)?.value ?? false;
|
||||
}, [settings]);
|
||||
|
||||
const keepLazyDeleteValue = useMemo(() => {
|
||||
return settings.find(setting => setting.key === KEEP_LAZY_DELETE_SETTING)?.value ?? false;
|
||||
}, [settings]);
|
||||
|
||||
const handleResize = useCallback(() => {
|
||||
if (tableRef.current) {
|
||||
const tableWidth = tableRef.current.offsetWidth;
|
||||
const otherColumnsWidth = 276;
|
||||
const availableWidth = tableWidth - otherColumnsWidth;
|
||||
setNameColumnWidth(`${availableWidth}px`);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const groupSettings = useMemo(() => settings.filter(s => (GROUPS_LIST as string[]).includes(s.key)), [settings]);
|
||||
const showDescriptionColumn = useMemo(
|
||||
() => settings.find(s => s.key === SHOW_DESCRIPTION_COLUMN_SETTING)?.value,
|
||||
[settings],
|
||||
);
|
||||
|
||||
const showUpdatedColumn = useMemo(() => settings.find(s => s.key === SHOW_UPDATED_COLUMN_SETTING)?.value, [settings]);
|
||||
|
||||
const filteredSignatures = useMemo(() => {
|
||||
return signatures
|
||||
.filter(x => {
|
||||
if (hideLinkedSignatures && !!x.linked_system) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isCosmicSignature = x.kind === COSMIC_SIGNATURE;
|
||||
|
||||
if (isCosmicSignature) {
|
||||
const showCosmicSignatures = settings.find(y => y.key === COSMIC_SIGNATURE)?.value;
|
||||
if (showCosmicSignatures) {
|
||||
return !x.group || groupSettings.find(y => y.key === x.group)?.value;
|
||||
} else {
|
||||
return !!x.group && groupSettings.find(y => y.key === x.group)?.value;
|
||||
}
|
||||
}
|
||||
|
||||
return settings.find(y => y.key === x.kind)?.value;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return new Date(b.updated_at || 0).getTime() - new Date(a.updated_at || 0).getTime();
|
||||
});
|
||||
}, [signatures, settings, groupSettings, hideLinkedSignatures]);
|
||||
|
||||
const handleGetSignatures = useCallback(async () => {
|
||||
const { signatures } = await outCommand({
|
||||
type: OutCommand.getSignatures,
|
||||
data: { system_id: systemId },
|
||||
onCountChange,
|
||||
onPendingChange,
|
||||
}: SystemSignaturesContentProps) {
|
||||
const { signatures, selectedSignatures, setSelectedSignatures, handleDeleteSelected, handleSelectAll, handlePaste } =
|
||||
useSystemSignaturesData({
|
||||
systemId,
|
||||
settings,
|
||||
onCountChange,
|
||||
onPendingChange,
|
||||
onLazyDeleteChange,
|
||||
});
|
||||
|
||||
setSignatures(signatures);
|
||||
}, [outCommand, systemId]);
|
||||
|
||||
const handleUpdateSignatures = useCallback(
|
||||
async (newSignatures: SystemSignature[], updateOnly: boolean, skipUpdateUntouched?: boolean) => {
|
||||
const { added, updated, removed } = getActualSigs(
|
||||
signaturesRef.current,
|
||||
newSignatures,
|
||||
updateOnly,
|
||||
skipUpdateUntouched,
|
||||
);
|
||||
|
||||
const { signatures: updatedSignatures } = await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
added,
|
||||
updated,
|
||||
removed,
|
||||
},
|
||||
});
|
||||
|
||||
setSignatures(() => updatedSignatures);
|
||||
setSelectedSignatures([]);
|
||||
},
|
||||
[outCommand, systemId],
|
||||
const [sortSettings, setSortSettings] = useLocalStorageState<{ sortField: string; sortOrder: SortOrder }>(
|
||||
'window:signatures:sort',
|
||||
{ defaultValue: SORT_DEFAULT_VALUES },
|
||||
);
|
||||
|
||||
const handleDeleteSelected = useCallback(
|
||||
async (e: KeyboardEvent) => {
|
||||
if (selectable) {
|
||||
return;
|
||||
}
|
||||
if (selectedSignatures.length === 0) {
|
||||
return;
|
||||
}
|
||||
const tableRef = useRef<HTMLDivElement>(null);
|
||||
const tooltipRef = useRef<WdTooltipHandlers>(null);
|
||||
const [hoveredSignature, setHoveredSignature] = useState<SystemSignature | null>(null);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const isCompact = useMaxWidth(tableRef, COMPACT_MAX_WIDTH);
|
||||
const isMedium = useMaxWidth(tableRef, MEDIUM_MAX_WIDTH);
|
||||
|
||||
const selectedSignaturesEveIds = selectedSignatures.map(x => x.eve_id);
|
||||
await handleUpdateSignatures(
|
||||
signatures.filter(x => !selectedSignaturesEveIds.includes(x.eve_id)),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
},
|
||||
[handleUpdateSignatures, selectable, signatures, selectedSignatures],
|
||||
);
|
||||
|
||||
const handleSelectAll = useCallback(() => {
|
||||
setSelectedSignatures(signatures);
|
||||
}, [signatures]);
|
||||
|
||||
const handleSelectSignatures = useCallback(
|
||||
// TODO still will be good to define types if we use typescript
|
||||
// @ts-ignore
|
||||
e => {
|
||||
if (selectable) {
|
||||
onSelect?.(e.value);
|
||||
} else {
|
||||
setSelectedSignatures(e.value);
|
||||
}
|
||||
},
|
||||
[onSelect, selectable],
|
||||
);
|
||||
|
||||
const handlePaste = async (clipboardContent: string) => {
|
||||
const newSignatures = parseSignatures(
|
||||
clipboardContent,
|
||||
settings.map(x => x.key),
|
||||
);
|
||||
|
||||
handleUpdateSignatures(newSignatures, !lazyDeleteValue);
|
||||
|
||||
if (lazyDeleteValue && !keepLazyDeleteValue) {
|
||||
onLazyDeleteChange?.(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEnterRow = useCallback(
|
||||
(e: DataTableRowMouseEvent) => {
|
||||
setHoveredSig(filteredSignatures[e.index]);
|
||||
tooltipRef.current?.show(e.originalEvent);
|
||||
},
|
||||
[filteredSignatures],
|
||||
);
|
||||
|
||||
const handleLeaveRow = useCallback((e: DataTableRowMouseEvent) => {
|
||||
tooltipRef.current?.hide(e.originalEvent);
|
||||
setHoveredSig(null);
|
||||
}, []);
|
||||
const lazyDeleteEnabled = settings.find(s => s.key === LAZY_DELETE_SIGNATURES_SETTING)?.value ?? false;
|
||||
const keepLazyDeleteEnabled = settings.find(s => s.key === KEEP_LAZY_DELETE_SETTING)?.value ?? false;
|
||||
|
||||
const { clipboardContent, setClipboardContent } = useClipboard();
|
||||
useEffect(() => {
|
||||
if (refData.current.selectable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!clipboardContent?.text) {
|
||||
return;
|
||||
}
|
||||
if (selectable) return;
|
||||
if (!clipboardContent?.text) return;
|
||||
|
||||
handlePaste(clipboardContent.text);
|
||||
|
||||
if (lazyDeleteEnabled && !keepLazyDeleteEnabled) {
|
||||
onLazyDeleteChange?.(false);
|
||||
}
|
||||
setClipboardContent(null);
|
||||
}, [clipboardContent, selectable, lazyDeleteValue, keepLazyDeleteValue]);
|
||||
}, [
|
||||
selectable,
|
||||
clipboardContent,
|
||||
handlePaste,
|
||||
setClipboardContent,
|
||||
lazyDeleteEnabled,
|
||||
keepLazyDeleteEnabled,
|
||||
onLazyDeleteChange,
|
||||
]);
|
||||
|
||||
useHotkey(true, ['a'], handleSelectAll);
|
||||
useHotkey(false, ['Backspace', 'Delete'], handleDeleteSelected);
|
||||
|
||||
useEffect(() => {
|
||||
if (!systemId) {
|
||||
setSignatures([]);
|
||||
return;
|
||||
}
|
||||
|
||||
handleGetSignatures();
|
||||
}, [systemId]);
|
||||
|
||||
useMapEventListener(event => {
|
||||
switch (event.name) {
|
||||
case Commands.signaturesUpdated:
|
||||
if (event.data?.toString() !== systemId.toString()) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleGetSignatures();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new ResizeObserver(handleResize);
|
||||
if (tableRef.current) {
|
||||
observer.observe(tableRef.current);
|
||||
}
|
||||
|
||||
handleResize(); // Call on mount to set initial width
|
||||
|
||||
return () => {
|
||||
if (tableRef.current) {
|
||||
observer.unobserve(tableRef.current);
|
||||
}
|
||||
};
|
||||
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`);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (!tableRef.current) return;
|
||||
const observer = new ResizeObserver(handleResize);
|
||||
observer.observe(tableRef.current);
|
||||
handleResize();
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [handleResize]);
|
||||
|
||||
const renderToolbar = (/*row: SystemSignature*/) => {
|
||||
return (
|
||||
<div className="flex justify-end items-center gap-2 mr-[4px]">
|
||||
<WdTooltipWrapper content="To Edit Signature do double click">
|
||||
<span className={clsx(PrimeIcons.PENCIL, 'text-[10px]')}></span>
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState<SystemSignature | null>(null);
|
||||
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
|
||||
|
||||
const handleRowClick = (e: DataTableRowClickEvent) => {
|
||||
setSelectedSignature(e.data as SystemSignature);
|
||||
setSelectedSignatureForDialog(e.data as SystemSignature);
|
||||
setShowSignatureSettings(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={tableRef} className={'h-full '}>
|
||||
{filteredSignatures.length === 0 ? (
|
||||
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">
|
||||
No signatures
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* @ts-ignore */}
|
||||
<DataTable
|
||||
className={classes.Table}
|
||||
value={filteredSignatures}
|
||||
size="small"
|
||||
selectionMode={selectable ? 'single' : 'multiple'}
|
||||
selection={selectedSignatures}
|
||||
metaKeySelection
|
||||
onSelectionChange={handleSelectSignatures}
|
||||
dataKey="eve_id"
|
||||
tableClassName="w-full select-none"
|
||||
resizableColumns={false}
|
||||
onRowDoubleClick={handleRowClick}
|
||||
rowHover
|
||||
selectAll
|
||||
sortField={sortSettings.sortField}
|
||||
sortOrder={sortSettings.sortOrder}
|
||||
onSort={event => setSortSettings(() => ({ sortField: event.sortField, sortOrder: event.sortOrder }))}
|
||||
onRowMouseEnter={compact || medium ? handleEnterRow : undefined}
|
||||
onRowMouseLeave={compact || medium ? handleLeaveRow : undefined}
|
||||
rowClassName={row => {
|
||||
if (selectedSignatures.some(x => x.eve_id === row.eve_id)) {
|
||||
return clsx(classes.TableRowCompact, 'bg-amber-500/50 hover:bg-amber-500/70 transition duration-200');
|
||||
}
|
||||
|
||||
const dateClass = getRowColorByTimeLeft(row.inserted_at ? new Date(row.inserted_at) : undefined);
|
||||
if (!dateClass) {
|
||||
return clsx(classes.TableRowCompact, 'hover:bg-purple-400/20 transition duration-200');
|
||||
}
|
||||
|
||||
return clsx(classes.TableRowCompact, dateClass);
|
||||
}}
|
||||
>
|
||||
<Column
|
||||
bodyClassName="p-0 px-1"
|
||||
field="group"
|
||||
body={x => renderIcon(x)}
|
||||
style={{ maxWidth: 26, minWidth: 26, width: 26, height: 25 }}
|
||||
></Column>
|
||||
|
||||
<Column
|
||||
field="eve_id"
|
||||
header="Id"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: 72, minWidth: 72, width: 72 }}
|
||||
sortable
|
||||
></Column>
|
||||
<Column
|
||||
field="group"
|
||||
header="Group"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
hidden={compact}
|
||||
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
|
||||
sortable
|
||||
></Column>
|
||||
<Column
|
||||
field="info"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
body={renderInfoColumn}
|
||||
style={{ maxWidth: nameColumnWidth }}
|
||||
hidden={compact || medium}
|
||||
></Column>
|
||||
{showDescriptionColumn && (
|
||||
<Column
|
||||
field="description"
|
||||
header="Description"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
body={renderDescription}
|
||||
hidden={compact}
|
||||
sortable
|
||||
></Column>
|
||||
)}
|
||||
|
||||
<Column
|
||||
field="inserted_at"
|
||||
header="Added"
|
||||
dataType="date"
|
||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
body={renderAddedTimeLeft}
|
||||
sortable
|
||||
></Column>
|
||||
|
||||
{showUpdatedColumn && (
|
||||
<Column
|
||||
field="updated_at"
|
||||
header="Updated"
|
||||
dataType="date"
|
||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
body={renderUpdatedTimeLeft}
|
||||
sortable
|
||||
></Column>
|
||||
)}
|
||||
|
||||
{!selectable && (
|
||||
<Column
|
||||
bodyClassName="p-0 pl-1 pr-2"
|
||||
field="group"
|
||||
body={renderToolbar}
|
||||
// headerClassName={headerClasses}
|
||||
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||
></Column>
|
||||
)}
|
||||
</DataTable>
|
||||
</>
|
||||
)}
|
||||
<WdTooltip
|
||||
className="bg-stone-900/95 text-slate-50"
|
||||
ref={tooltipRef}
|
||||
content={hoveredSig ? <SignatureView {...hoveredSig} /> : null}
|
||||
/>
|
||||
|
||||
{showSignatureSettings && (
|
||||
<SignatureSettings
|
||||
systemId={systemId}
|
||||
show
|
||||
onHide={() => setShowSignatureSettings(false)}
|
||||
signatureData={selectedSignature}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
const handleSelectSignatures = useCallback(
|
||||
(e: { value: SystemSignature[] }) => {
|
||||
if (selectable) {
|
||||
onSelect?.(e.value[0]);
|
||||
} else {
|
||||
setSelectedSignatures(e.value as ExtendedSystemSignature[]);
|
||||
}
|
||||
},
|
||||
[selectable, onSelect, 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 enabledGroups = settings
|
||||
.filter(s => GROUPS_LIST.includes(s.key as SignatureGroup) && s.value === true)
|
||||
.map(s => s.key);
|
||||
|
||||
const filteredSignatures = useMemo<ExtendedSystemSignature[]>(() => {
|
||||
return signatures.filter(sig => {
|
||||
if (hideLinkedSignatures && sig.linked_system) {
|
||||
return false;
|
||||
}
|
||||
if (sig.kind === COSMIC_SIGNATURE) {
|
||||
const showCosmic = settings.find(y => y.key === COSMIC_SIGNATURE)?.value;
|
||||
if (!showCosmic) return false;
|
||||
if (sig.group) {
|
||||
return enabledGroups.includes(sig.group);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return settings.find(y => y.key === sig.kind)?.value;
|
||||
}
|
||||
});
|
||||
}, [signatures, hideLinkedSignatures, settings, enabledGroups]);
|
||||
|
||||
return (
|
||||
<div ref={tableRef} className="h-full">
|
||||
{filteredSignatures.length === 0 ? (
|
||||
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">
|
||||
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={
|
||||
isCompact || isMedium
|
||||
? (e: DataTableRowMouseEvent) => {
|
||||
setHoveredSignature(filteredSignatures[e.index]);
|
||||
tooltipRef.current?.show(e.originalEvent);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onRowMouseLeave={
|
||||
isCompact || isMedium
|
||||
? () => {
|
||||
setHoveredSignature(null);
|
||||
tooltipRef.current?.hide();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
rowClassName={rowData => getSignatureRowClass(rowData as ExtendedSystemSignature, selectedSignatures)}
|
||||
>
|
||||
<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
|
||||
/>
|
||||
)}
|
||||
<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 && (
|
||||
<Column
|
||||
header=""
|
||||
headerStyle={headerInlineStyle}
|
||||
body={() => (
|
||||
<div className="flex justify-end items-center gap-2 mr-[4px]">
|
||||
<WdTooltipWrapper content="Double-click a row to edit signature">
|
||||
<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}
|
||||
content={hoveredSignature ? <SignatureView {...hoveredSignature} /> : null}
|
||||
/>
|
||||
|
||||
{showSignatureSettings && (
|
||||
<SignatureSettings
|
||||
systemId={systemId}
|
||||
show
|
||||
onHide={() => setShowSignatureSettings(false)}
|
||||
signatureData={selectedSignatureForDialog || undefined}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user