mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-11-30 21:13:31 +00:00
Compare commits
353 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cb2dc526c | ||
|
|
f3c38ba62a | ||
|
|
29473f2d3b | ||
|
|
48654250e8 | ||
|
|
7aa24245b6 | ||
|
|
6070d74684 | ||
|
|
c3de3c4e35 | ||
|
|
5c513f3e50 | ||
|
|
5a980c6b89 | ||
|
|
85c075c5a6 | ||
|
|
f068afd16e | ||
|
|
ac71b0af64 | ||
|
|
5c515d6acd | ||
|
|
4585c3a94b | ||
|
|
cf2c27c961 | ||
|
|
f8e403025c | ||
|
|
46a1898be9 | ||
|
|
25fa7c07bc | ||
|
|
e7219e0eec | ||
|
|
45130fcffa | ||
|
|
5f75d4440d | ||
|
|
34210f63e3 | ||
|
|
5f60fd4922 | ||
|
|
47ef7dda55 | ||
|
|
0f3550a687 | ||
|
|
8f242f3535 | ||
|
|
1ce39e5394 | ||
|
|
cca7b912aa | ||
|
|
d939e32500 | ||
|
|
97ebe66db5 | ||
|
|
f437fc4541 | ||
|
|
6c65538450 | ||
|
|
d566a74df4 | ||
|
|
03e030a7d3 | ||
|
|
e738e1da9c | ||
|
|
972b3a6cbe | ||
|
|
96b4a3077e | ||
|
|
6b308e8a1e | ||
|
|
d0874cbc6f | ||
|
|
f106a51bf5 | ||
|
|
dc47dc5f81 | ||
|
|
dc81cffeea | ||
|
|
5766fcf4d8 | ||
|
|
c57a3b2cea | ||
|
|
0c1fa8e79b | ||
|
|
36cc91915c | ||
|
|
bb644fde31 | ||
|
|
269b54d382 | ||
|
|
a9115cc653 | ||
|
|
eeea7aee8b | ||
|
|
700089e381 | ||
|
|
932935557c | ||
|
|
2890a76cf2 | ||
|
|
4ac9b2e2b7 | ||
|
|
f92436f3f0 | ||
|
|
22d97cc99d | ||
|
|
305838573c | ||
|
|
cc7ad81d2f | ||
|
|
a694e57512 | ||
|
|
20be7fc67d | ||
|
|
54bfee414b | ||
|
|
bcfa47bd94 | ||
|
|
b784f68818 | ||
|
|
344ee54018 | ||
|
|
42e0f8f660 | ||
|
|
99b081887c | ||
|
|
dee8d0dae8 | ||
|
|
147dd5880e | ||
|
|
69991fff72 | ||
|
|
b881c84a52 | ||
|
|
de4e1f859f | ||
|
|
8e2a19540c | ||
|
|
855c596672 | ||
|
|
36d3c0937b | ||
|
|
d8fb1f78cf | ||
|
|
98fa7e0235 | ||
|
|
e4396fe2f9 | ||
|
|
1c117903f6 | ||
|
|
9e9dc39200 | ||
|
|
abd7e4e15c | ||
|
|
88ed9cd39e | ||
|
|
9666a8e78a | ||
|
|
271a3d90f8 | ||
|
|
01e291daf4 | ||
|
|
b7c0b45c15 | ||
|
|
0874e3c51c | ||
|
|
d39fa0363a | ||
|
|
369b08a9ae | ||
|
|
a872561b18 | ||
|
|
857608f8ef | ||
|
|
7a74ae566b | ||
|
|
f2c8724763 | ||
|
|
9a8dc4dbe5 | ||
|
|
01192dc637 | ||
|
|
957cbcc561 | ||
|
|
7eb6d093cf | ||
|
|
a23e544a9f | ||
|
|
845ea7a576 | ||
|
|
ae8fbf30e4 | ||
|
|
083e300ff5 | ||
|
|
ae4ebc0e36 | ||
|
|
3de385c902 | ||
|
|
5f3d4dba37 | ||
|
|
8acc7ddc25 | ||
|
|
c175f19142 | ||
|
|
ed6d25f3ea | ||
|
|
ab07d1321d | ||
|
|
a81e61bd70 | ||
|
|
d2d33619c2 | ||
|
|
fa464110c6 | ||
|
|
a5fa60e699 | ||
|
|
6db994852f | ||
|
|
0a68676957 | ||
|
|
9b82dd8f43 | ||
|
|
aac2c33fd2 | ||
|
|
0ebc703774 | ||
|
|
4615e20838 | ||
|
|
f4d28f282a | ||
|
|
1fe8ef17bd | ||
|
|
1665b65619 | ||
|
|
e1a946bb1d | ||
|
|
543ec7f071 | ||
|
|
bf40d2cb8d | ||
|
|
48ac40ea55 | ||
|
|
5a3f3c40fe | ||
|
|
d5bac311ff | ||
|
|
34a7c854ed | ||
|
|
6088afb38c | ||
|
|
5764c41d23 | ||
|
|
09444596ff | ||
|
|
ee15d90f9c | ||
|
|
f5b014dae9 | ||
|
|
ebb6090be9 | ||
|
|
7a4d31db60 | ||
|
|
2acf9ed5dc | ||
|
|
46df025200 | ||
|
|
43a363b5ab | ||
|
|
03688387d8 | ||
|
|
5060852918 | ||
|
|
57381b9782 | ||
|
|
6014c60e13 | ||
|
|
1b711d7b4b | ||
|
|
f761ba9746 | ||
|
|
20a795c5b5 | ||
|
|
0c80894c65 | ||
|
|
21844f0550 | ||
|
|
f7716ca45a | ||
|
|
de74714c77 | ||
|
|
4dfa83bd30 | ||
|
|
cb4dba8dc2 | ||
|
|
1d75b8f063 | ||
|
|
2a42c4e6df | ||
|
|
0ee6160bcd | ||
|
|
5826d2492b | ||
|
|
a643e20247 | ||
|
|
66dc680281 | ||
|
|
5e0965ead4 | ||
|
|
46f46c745e | ||
|
|
00bf620e35 | ||
|
|
46eef60d86 | ||
|
|
712379f4bb | ||
|
|
4c39c6fb39 | ||
|
|
fe836442ab | ||
|
|
9514806dbb | ||
|
|
4e6423ebc8 | ||
|
|
a97e598299 | ||
|
|
9c26b50aac | ||
|
|
3f2ddf5cc4 | ||
|
|
233b2bd7a4 | ||
|
|
0d35268efc | ||
|
|
d169220eb2 | ||
|
|
182d5ec9fb | ||
|
|
32958253b7 | ||
|
|
c011d56ce7 | ||
|
|
73d1921d42 | ||
|
|
7bb810e1e6 | ||
|
|
c90ac7b1e3 | ||
|
|
005e0c2bc6 | ||
|
|
808acb540e | ||
|
|
06626f910b | ||
|
|
a14e829f09 | ||
|
|
4002285882 | ||
|
|
d732d15ef6 | ||
|
|
812582d955 | ||
|
|
f3077c0bf1 | ||
|
|
32c70cbbad | ||
|
|
8934935e10 | ||
|
|
20c8a53712 | ||
|
|
b22970fef3 | ||
|
|
cf72394ef9 | ||
|
|
e6dbba7283 | ||
|
|
843b3b86b2 | ||
|
|
bd865b9f64 | ||
|
|
ae91cd2f92 | ||
|
|
0be7a5f9d0 | ||
|
|
e15bfa426a | ||
|
|
4198e4b07a | ||
|
|
03ee08ff67 | ||
|
|
ac4dd4c28b | ||
|
|
308e81a464 | ||
|
|
6f4240d931 | ||
|
|
847b45a431 | ||
|
|
7613ca78da | ||
|
|
5ec97d74ca | ||
|
|
74359a5542 | ||
|
|
0020f46dd8 | ||
|
|
c8631708b9 | ||
|
|
a6751b45c6 | ||
|
|
f48aeb5cec | ||
|
|
a5f25646c9 | ||
|
|
23cf1fd96f | ||
|
|
6f15521069 | ||
|
|
9d41e57c06 | ||
|
|
ea9a22df09 | ||
|
|
0d4fd6f214 | ||
|
|
87a6c20545 | ||
|
|
c375f4e4ce | ||
|
|
843a6d7320 | ||
|
|
98c54a3413 | ||
|
|
0439110938 | ||
|
|
8ce1e5fa3e | ||
|
|
ebaf6bcdc6 | ||
|
|
40d947bebc | ||
|
|
61d1c3848f | ||
|
|
e152ce179f | ||
|
|
7bbe387183 | ||
|
|
b1555ff03c | ||
|
|
e624499244 | ||
|
|
6a1976dec6 | ||
|
|
3db24c4344 | ||
|
|
883c09f255 | ||
|
|
ff24d80038 | ||
|
|
63cbc9c0b9 | ||
|
|
8056972a27 | ||
|
|
1759d46740 | ||
|
|
e4b7d2e45b | ||
|
|
41573cbee3 | ||
|
|
24ffc20bb8 | ||
|
|
e077849b66 | ||
|
|
375a9ef65b | ||
|
|
9bf90ab752 | ||
|
|
63ca473113 | ||
|
|
90c3481151 | ||
|
|
e36b08a7e5 | ||
|
|
e1f79170c3 | ||
|
|
68b5455e91 | ||
|
|
f28e75c7f4 | ||
|
|
6091adb28e | ||
|
|
d4657b335f | ||
|
|
7fee850902 | ||
|
|
648c168a66 | ||
|
|
f5c4b2c407 | ||
|
|
b592223d52 | ||
|
|
5cf118c6ee | ||
|
|
b25013c652 | ||
|
|
cf43861b11 | ||
|
|
b5fe8f8878 | ||
|
|
5e5068c7de | ||
|
|
624b51edfb | ||
|
|
a72f8e60c4 | ||
|
|
dec8ae50c9 | ||
|
|
0332d36a8e | ||
|
|
8444c7f82d | ||
|
|
ec3fc7447e | ||
|
|
20ec2800c9 | ||
|
|
6fbf43e860 | ||
|
|
697da38020 | ||
|
|
4bc65b43d2 | ||
|
|
910ec97fd1 | ||
|
|
40ed58ee8c | ||
|
|
c18d241c77 | ||
|
|
8b42908a5c | ||
|
|
6d32505a59 | ||
|
|
fe8a34c77d | ||
|
|
d12cafcca8 | ||
|
|
38a9c76ff0 | ||
|
|
d6c30b4a53 | ||
|
|
53a81daaf5 | ||
|
|
92081c99e3 | ||
|
|
d78020d2f5 | ||
|
|
fb1a9b440d | ||
|
|
0141ac46e3 | ||
|
|
d2bf6a8f86 | ||
|
|
1844e4c757 | ||
|
|
d407efe805 | ||
|
|
021e04d87a | ||
|
|
7844c9db34 | ||
|
|
355beb8394 | ||
|
|
d82eeba792 | ||
|
|
0396b05e58 | ||
|
|
9494a9eb37 | ||
|
|
8238f84ac7 | ||
|
|
1cf19b2a50 | ||
|
|
e8543fd2f8 | ||
|
|
c7f360e1fa | ||
|
|
a2b83f7f0c | ||
|
|
ae5689a403 | ||
|
|
c46af1d286 | ||
|
|
d17ba2168c | ||
|
|
80c14716eb | ||
|
|
8541fcd29b | ||
|
|
65d6acd7fb | ||
|
|
8b5f83d6b2 | ||
|
|
5e18891f4b | ||
|
|
74e0b85748 | ||
|
|
81d3495b65 | ||
|
|
d1959ca09f | ||
|
|
ec7a5ecf10 | ||
|
|
70b9ec99ba | ||
|
|
7147d79166 | ||
|
|
1dad9316bd | ||
|
|
872f7dcf48 | ||
|
|
02b450325e | ||
|
|
136bc4cbb9 | ||
|
|
dab49df9aa | ||
|
|
6286087f3e | ||
|
|
4ce7160f79 | ||
|
|
2913bf19b0 | ||
|
|
7bd6be6fd0 | ||
|
|
705daa286b | ||
|
|
614d06be66 | ||
|
|
dec3e9a7ce | ||
|
|
0017ac3373 | ||
|
|
ae34744578 | ||
|
|
76885058ef | ||
|
|
fccb007036 | ||
|
|
a9f8901bd5 | ||
|
|
8ae968b5be | ||
|
|
beffd45e4f | ||
|
|
4488d81e8d | ||
|
|
618cc8c5f1 | ||
|
|
3fb22a877e | ||
|
|
8759409b82 | ||
|
|
245647ae6a | ||
|
|
eb7d33ea07 | ||
|
|
3575b16def | ||
|
|
a6fb680be8 | ||
|
|
9e17df5544 | ||
|
|
683fde7be4 | ||
|
|
ee68ce92a2 | ||
|
|
8b4e38d795 | ||
|
|
4995202627 | ||
|
|
986b997a6a | ||
|
|
9a957af759 | ||
|
|
c5a0a96016 | ||
|
|
8715a6c0ac | ||
|
|
c9810095aa | ||
|
|
69eb888469 | ||
|
|
748347df9a | ||
|
|
aa4d49027c | ||
|
|
be7bbe6872 | ||
|
|
7df8284124 | ||
|
|
21ca630abd |
@@ -1,5 +1,7 @@
|
||||
export WEB_APP_URL="http://localhost:8000"
|
||||
export RELEASE_COOKIE="PDpbnyo6mEI_0T4ZsHH_ESmi1vT1toQ8PTc0vbfg5FIT4Ih-Lh98mw=="
|
||||
# Erlang node name for distributed Erlang (optional - defaults to wanderer@hostname)
|
||||
# export RELEASE_NODE="wanderer@localhost"
|
||||
export EVE_CLIENT_ID="<EVE_CLIENT_ID>"
|
||||
export EVE_CLIENT_SECRET="<EVE_CLIENT_SECRET>"
|
||||
export EVE_CLIENT_WITH_WALLET_ID="<EVE_CLIENT_WITH_WALLET_ID>"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
name: Build Docker Image
|
||||
name: Build Develop
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '**'
|
||||
branches:
|
||||
- develop
|
||||
|
||||
env:
|
||||
MIX_ENV: prod
|
||||
@@ -18,12 +18,85 @@ permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: 🛠 Build
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ github.ref == 'refs/heads/develop' && github.event_name == 'push' }}
|
||||
permissions:
|
||||
checks: write
|
||||
contents: write
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
pull-requests: write
|
||||
repository-projects: write
|
||||
strategy:
|
||||
matrix:
|
||||
otp: ["27"]
|
||||
elixir: ["1.17"]
|
||||
node-version: ["18.x"]
|
||||
outputs:
|
||||
commit_hash: ${{ steps.set-commit-develop.outputs.commit_hash }}
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Elixir
|
||||
uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: ${{matrix.otp}}
|
||||
elixir-version: ${{matrix.elixir}}
|
||||
# nix build would also work here because `todos` is the default package
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ssh-key: "${{ secrets.COMMIT_KEY }}"
|
||||
fetch-depth: 0
|
||||
- name: 😅 Cache deps
|
||||
id: cache-deps
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: cache-elixir-deps
|
||||
with:
|
||||
path: |
|
||||
deps
|
||||
key: ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles('**/mix.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-
|
||||
- name: 😅 Cache compiled build
|
||||
id: cache-build
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: cache-compiled-build
|
||||
with:
|
||||
path: |
|
||||
_build
|
||||
key: ${{ runner.os }}-build-${{ hashFiles('**/mix.lock') }}-${{ hashFiles( '**/lib/**/*.{ex,eex}', '**/config/*.exs', '**/mix.exs' ) }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ hashFiles('**/mix.lock') }}-
|
||||
${{ runner.os }}-build-
|
||||
# Step: Download project dependencies. If unchanged, uses
|
||||
# the cached version.
|
||||
- name: 🌐 Install dependencies
|
||||
run: mix deps.get --only "prod"
|
||||
|
||||
# Step: Compile the project treating any warnings as errors.
|
||||
# Customize this step if a different behavior is desired.
|
||||
- name: 🛠 Compiles without warnings
|
||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||
run: mix compile
|
||||
|
||||
- name: Set commit hash for develop
|
||||
id: set-commit-develop
|
||||
run: |
|
||||
echo "commit_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
docker:
|
||||
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
|
||||
@@ -37,6 +110,7 @@ jobs:
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
@@ -46,25 +120,9 @@ jobs:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ needs.build.outputs.commit_hash }}
|
||||
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: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ steps.get-latest-tag.outputs.tag }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Prepare Changelog
|
||||
run: |
|
||||
yes | cp -rf CHANGELOG.md priv/changelog/CHANGELOG.md
|
||||
sed -i '1i%{title: "Change Log"}\n\n---\n' priv/changelog/CHANGELOG.md
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
@@ -113,24 +171,6 @@ jobs:
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
- uses: markpatterson27/markdown-to-output@v1
|
||||
id: extract-changelog
|
||||
with:
|
||||
filepath: CHANGELOG.md
|
||||
|
||||
- name: Get content
|
||||
uses: 2428392/gh-truncate-string-action@v1.3.0
|
||||
id: get-content
|
||||
with:
|
||||
stringToTruncate: |
|
||||
📣 Wanderer new release available 🎉
|
||||
|
||||
**Version**: ${{ steps.get-latest-tag.outputs.tag }}
|
||||
|
||||
${{ steps.extract-changelog.outputs.body }}
|
||||
maxLength: 500
|
||||
truncationSymbol: "…"
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
@@ -161,9 +201,8 @@ jobs:
|
||||
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 }}
|
||||
type=raw,value=develop,enable=${{ github.ref == 'refs/heads/develop' }}
|
||||
type=raw,value=develop-{{sha}},enable=${{ github.ref == 'refs/heads/develop' }}
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/digests
|
||||
@@ -176,12 +215,20 @@ jobs:
|
||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
|
||||
|
||||
notify:
|
||||
name: 🏷 Notify about release
|
||||
name: 🏷 Notify about develop release
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [docker, merge]
|
||||
steps:
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@v5.3.0
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
content: ${{ needs.docker.outputs.release-notes }}
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL_DEV }}
|
||||
content: |
|
||||
📣 New develop release available 🚀
|
||||
|
||||
**Commit**: `${{ github.sha }}`
|
||||
**Status**: Development/Testing Release
|
||||
|
||||
Docker image: `wandererltd/community-edition:develop`
|
||||
|
||||
⚠️ This is an unstable development release for testing purposes.
|
||||
56
.github/workflows/build.yml
vendored
56
.github/workflows/build.yml
vendored
@@ -4,7 +4,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
|
||||
env:
|
||||
MIX_ENV: prod
|
||||
@@ -22,7 +21,7 @@ jobs:
|
||||
build:
|
||||
name: 🛠 Build
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') && github.event_name == 'push' }}
|
||||
if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }}
|
||||
permissions:
|
||||
checks: write
|
||||
contents: write
|
||||
@@ -37,7 +36,7 @@ jobs:
|
||||
elixir: ["1.17"]
|
||||
node-version: ["18.x"]
|
||||
outputs:
|
||||
commit_hash: ${{ steps.generate-changelog.outputs.commit_hash || steps.set-commit-develop.outputs.commit_hash }}
|
||||
commit_hash: ${{ steps.generate-changelog.outputs.commit_hash }}
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
@@ -91,7 +90,6 @@ jobs:
|
||||
|
||||
- name: Generate Changelog & Update Tag Version
|
||||
id: generate-changelog
|
||||
if: github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
git config --global user.name 'CI'
|
||||
git config --global user.email 'ci@users.noreply.github.com'
|
||||
@@ -102,15 +100,16 @@ jobs:
|
||||
|
||||
- name: Set commit hash for develop
|
||||
id: set-commit-develop
|
||||
if: github.ref == 'refs/heads/develop'
|
||||
run: |
|
||||
echo "commit_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
docker:
|
||||
name: 🛠 Build Docker Images
|
||||
if: github.ref == 'refs/heads/develop'
|
||||
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
|
||||
@@ -137,6 +136,17 @@ jobs:
|
||||
ref: ${{ needs.build.outputs.commit_hash }}
|
||||
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: Prepare Changelog
|
||||
run: |
|
||||
yes | cp -rf CHANGELOG.md priv/changelog/CHANGELOG.md
|
||||
sed -i '1i%{title: "Change Log"}\n\n---\n' priv/changelog/CHANGELOG.md
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
@@ -185,6 +195,24 @@ jobs:
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
- uses: markpatterson27/markdown-to-output@v1
|
||||
id: extract-changelog
|
||||
with:
|
||||
filepath: CHANGELOG.md
|
||||
|
||||
- name: Get content
|
||||
uses: 2428392/gh-truncate-string-action@v1.3.0
|
||||
id: get-content
|
||||
with:
|
||||
stringToTruncate: |
|
||||
📣 Wanderer new release available 🎉
|
||||
|
||||
**Version**: ${{ steps.get-latest-tag.outputs.tag }}
|
||||
|
||||
${{ steps.extract-changelog.outputs.body }}
|
||||
maxLength: 500
|
||||
truncationSymbol: "…"
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
@@ -215,8 +243,9 @@ jobs:
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=raw,value=develop,enable=${{ github.ref == 'refs/heads/develop' }}
|
||||
type=raw,value=develop-{{sha}},enable=${{ github.ref == 'refs/heads/develop' }}
|
||||
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
|
||||
@@ -259,3 +288,14 @@ jobs:
|
||||
## How to Promote?
|
||||
In order to promote this to prod, edit the draft and press **"Publish release"**.
|
||||
draft: true
|
||||
|
||||
notify:
|
||||
name: 🏷 Notify about release
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [docker, merge]
|
||||
steps:
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@v5.3.0
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
content: ${{ needs.docker.outputs.release-notes }}
|
||||
|
||||
187
.github/workflows/docker-arm.yml
vendored
187
.github/workflows/docker-arm.yml
vendored
@@ -1,187 +0,0 @@
|
||||
name: Build Docker ARM Image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '**'
|
||||
|
||||
env:
|
||||
MIX_ENV: prod
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REGISTRY_IMAGE: wandererltd/community-edition-arm
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
name: 🛠 Build Docker Images
|
||||
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
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
pull-requests: write
|
||||
repository-projects: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- linux/arm64
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
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: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ steps.get-latest-tag.outputs.tag }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Prepare Changelog
|
||||
run: |
|
||||
yes | cp -rf CHANGELOG.md priv/changelog/CHANGELOG.md
|
||||
sed -i '1i%{title: "Change Log"}\n\n---\n' priv/changelog/CHANGELOG.md
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.WANDERER_DOCKER_USER }}
|
||||
password: ${{ secrets.WANDERER_DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
push: true
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
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: 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
|
||||
with:
|
||||
filepath: CHANGELOG.md
|
||||
|
||||
- name: Get content
|
||||
uses: 2428392/gh-truncate-string-action@v1.3.0
|
||||
id: get-content
|
||||
with:
|
||||
stringToTruncate: |
|
||||
📣 Wanderer **ARM** release available 🎉
|
||||
|
||||
**Version**: :${{ steps.get-latest-tag.outputs.tag }}
|
||||
|
||||
${{ steps.extract-changelog.outputs.body }}
|
||||
maxLength: 500
|
||||
truncationSymbol: "…"
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- docker
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
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 }}
|
||||
|
||||
notify:
|
||||
name: 🏷 Notify about release
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [docker, merge]
|
||||
steps:
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@v5.3.0
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
content: ${{ needs.docker.outputs.release-notes }}
|
||||
110
.github/workflows/test.yml
vendored
110
.github/workflows/test.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
test:
|
||||
name: Test Suite
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
@@ -35,17 +35,17 @@ jobs:
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
|
||||
- name: Setup Elixir/OTP
|
||||
uses: erlef/setup-beam@v1
|
||||
with:
|
||||
elixir-version: ${{ env.ELIXIR_VERSION }}
|
||||
otp-version: ${{ env.OTP_VERSION }}
|
||||
|
||||
|
||||
- name: Cache Elixir dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
@@ -54,12 +54,12 @@ jobs:
|
||||
_build
|
||||
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
|
||||
restore-keys: ${{ runner.os }}-mix-
|
||||
|
||||
|
||||
- name: Install Elixir dependencies
|
||||
run: |
|
||||
mix deps.get
|
||||
mix deps.compile
|
||||
|
||||
|
||||
- name: Check code formatting
|
||||
id: format
|
||||
run: |
|
||||
@@ -71,42 +71,42 @@ jobs:
|
||||
echo "count=1" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
|
||||
- name: Compile code and capture warnings
|
||||
id: compile
|
||||
run: |
|
||||
# Capture compilation output
|
||||
output=$(mix compile 2>&1 || true)
|
||||
echo "$output" > compile_output.txt
|
||||
|
||||
|
||||
# Count warnings
|
||||
warning_count=$(echo "$output" | grep -c "warning:" || echo "0")
|
||||
|
||||
|
||||
# Check if compilation succeeded
|
||||
if mix compile > /dev/null 2>&1; then
|
||||
echo "status=✅ Success" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "status=❌ Failed" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
|
||||
echo "warnings=$warning_count" >> $GITHUB_OUTPUT
|
||||
echo "output<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$output" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
continue-on-error: true
|
||||
|
||||
|
||||
- name: Setup database
|
||||
run: |
|
||||
mix ecto.create
|
||||
mix ecto.migrate
|
||||
|
||||
|
||||
- name: Run tests with coverage
|
||||
id: tests
|
||||
run: |
|
||||
# Run tests with coverage
|
||||
output=$(mix test --cover 2>&1 || true)
|
||||
echo "$output" > test_output.txt
|
||||
|
||||
|
||||
# Parse test results
|
||||
if echo "$output" | grep -q "0 failures"; then
|
||||
echo "status=✅ All Passed" >> $GITHUB_OUTPUT
|
||||
@@ -115,16 +115,16 @@ jobs:
|
||||
echo "status=❌ Some Failed" >> $GITHUB_OUTPUT
|
||||
test_status="failed"
|
||||
fi
|
||||
|
||||
|
||||
# Extract test counts
|
||||
test_line=$(echo "$output" | grep -E "[0-9]+ tests?, [0-9]+ failures?" | head -1 || echo "0 tests, 0 failures")
|
||||
total_tests=$(echo "$test_line" | grep -o '[0-9]\+ tests\?' | grep -o '[0-9]\+' | head -1 || echo "0")
|
||||
failures=$(echo "$test_line" | grep -o '[0-9]\+ failures\?' | grep -o '[0-9]\+' | head -1 || echo "0")
|
||||
|
||||
|
||||
echo "total=$total_tests" >> $GITHUB_OUTPUT
|
||||
echo "failures=$failures" >> $GITHUB_OUTPUT
|
||||
echo "passed=$((total_tests - failures))" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
# Calculate success rate
|
||||
if [ "$total_tests" -gt 0 ]; then
|
||||
success_rate=$(echo "scale=1; ($total_tests - $failures) * 100 / $total_tests" | bc)
|
||||
@@ -132,26 +132,26 @@ jobs:
|
||||
success_rate="0"
|
||||
fi
|
||||
echo "success_rate=$success_rate" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
exit_code=$?
|
||||
echo "exit_code=$exit_code" >> $GITHUB_OUTPUT
|
||||
continue-on-error: true
|
||||
|
||||
|
||||
- name: Generate coverage report
|
||||
id: coverage
|
||||
run: |
|
||||
# Generate coverage report with GitHub format
|
||||
output=$(mix coveralls.github 2>&1 || true)
|
||||
echo "$output" > coverage_output.txt
|
||||
|
||||
|
||||
# Extract coverage percentage
|
||||
coverage=$(echo "$output" | grep -o '[0-9]\+\.[0-9]\+%' | head -1 | sed 's/%//' || echo "0")
|
||||
if [ -z "$coverage" ]; then
|
||||
coverage="0"
|
||||
fi
|
||||
|
||||
|
||||
echo "percentage=$coverage" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
# Determine status
|
||||
if (( $(echo "$coverage >= 80" | bc -l) )); then
|
||||
echo "status=✅ Excellent" >> $GITHUB_OUTPUT
|
||||
@@ -161,14 +161,14 @@ jobs:
|
||||
echo "status=❌ Needs Improvement" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
|
||||
- name: Run Credo analysis
|
||||
id: credo
|
||||
run: |
|
||||
# Run Credo and capture output
|
||||
output=$(mix credo --strict --format=json 2>&1 || true)
|
||||
echo "$output" > credo_output.txt
|
||||
|
||||
|
||||
# Try to parse JSON output
|
||||
if echo "$output" | jq . > /dev/null 2>&1; then
|
||||
issues=$(echo "$output" | jq '.issues | length' 2>/dev/null || echo "0")
|
||||
@@ -183,12 +183,12 @@ jobs:
|
||||
normal_issues="0"
|
||||
low_issues="0"
|
||||
fi
|
||||
|
||||
|
||||
echo "total_issues=$issues" >> $GITHUB_OUTPUT
|
||||
echo "high_issues=$high_issues" >> $GITHUB_OUTPUT
|
||||
echo "normal_issues=$normal_issues" >> $GITHUB_OUTPUT
|
||||
echo "low_issues=$low_issues" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
# Determine status
|
||||
if [ "$issues" -eq 0 ]; then
|
||||
echo "status=✅ Clean" >> $GITHUB_OUTPUT
|
||||
@@ -198,24 +198,24 @@ jobs:
|
||||
echo "status=❌ Needs Attention" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
|
||||
- name: Run Dialyzer analysis
|
||||
id: dialyzer
|
||||
run: |
|
||||
# Ensure PLT is built
|
||||
mix dialyzer --plt
|
||||
|
||||
|
||||
# Run Dialyzer and capture output
|
||||
output=$(mix dialyzer --format=github 2>&1 || true)
|
||||
echo "$output" > dialyzer_output.txt
|
||||
|
||||
|
||||
# Count warnings and errors
|
||||
warnings=$(echo "$output" | grep -c "warning:" || echo "0")
|
||||
errors=$(echo "$output" | grep -c "error:" || echo "0")
|
||||
|
||||
|
||||
echo "warnings=$warnings" >> $GITHUB_OUTPUT
|
||||
echo "errors=$errors" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
# Determine status
|
||||
if [ "$errors" -eq 0 ] && [ "$warnings" -eq 0 ]; then
|
||||
echo "status=✅ Clean" >> $GITHUB_OUTPUT
|
||||
@@ -225,7 +225,7 @@ jobs:
|
||||
echo "status=❌ Has Errors" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
|
||||
- name: Create test results summary
|
||||
id: summary
|
||||
run: |
|
||||
@@ -236,11 +236,11 @@ jobs:
|
||||
coverage_score=${{ steps.coverage.outputs.percentage }}
|
||||
credo_score=$(echo "scale=0; (100 - ${{ steps.credo.outputs.total_issues }} * 2)" | bc | sed 's/^-.*$/0/')
|
||||
dialyzer_score=$(echo "scale=0; (100 - ${{ steps.dialyzer.outputs.warnings }} * 2 - ${{ steps.dialyzer.outputs.errors }} * 10)" | bc | sed 's/^-.*$/0/')
|
||||
|
||||
|
||||
overall_score=$(echo "scale=1; ($format_score + $compile_score + $test_score + $coverage_score + $credo_score + $dialyzer_score) / 6" | bc)
|
||||
|
||||
|
||||
echo "overall_score=$overall_score" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
# Determine overall status
|
||||
if (( $(echo "$overall_score >= 90" | bc -l) )); then
|
||||
echo "overall_status=🌟 Excellent" >> $GITHUB_OUTPUT
|
||||
@@ -252,7 +252,7 @@ jobs:
|
||||
echo "overall_status=❌ Poor" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
|
||||
- name: Find existing PR comment
|
||||
if: github.event_name == 'pull_request'
|
||||
id: find_comment
|
||||
@@ -261,7 +261,7 @@ jobs:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: '## 🧪 Test Results Summary'
|
||||
|
||||
|
||||
- name: Create or update PR comment
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
@@ -271,11 +271,11 @@ jobs:
|
||||
edit-mode: replace
|
||||
body: |
|
||||
## 🧪 Test Results Summary
|
||||
|
||||
|
||||
**Overall Quality Score: ${{ steps.summary.outputs.overall_score }}%** ${{ steps.summary.outputs.overall_status }}
|
||||
|
||||
|
||||
### 📊 Metrics Dashboard
|
||||
|
||||
|
||||
| Category | Status | Count | Details |
|
||||
|----------|---------|-------|---------|
|
||||
| 📝 **Code Formatting** | ${{ steps.format.outputs.status }} | ${{ steps.format.outputs.count }} issues | `mix format --check-formatted` |
|
||||
@@ -284,50 +284,50 @@ jobs:
|
||||
| 📊 **Coverage** | ${{ steps.coverage.outputs.status }} | ${{ steps.coverage.outputs.percentage }}% | `mix coveralls` |
|
||||
| 🎯 **Credo** | ${{ steps.credo.outputs.status }} | ${{ steps.credo.outputs.total_issues }} issues | High: ${{ steps.credo.outputs.high_issues }}, Normal: ${{ steps.credo.outputs.normal_issues }}, Low: ${{ steps.credo.outputs.low_issues }} |
|
||||
| 🔍 **Dialyzer** | ${{ steps.dialyzer.outputs.status }} | ${{ steps.dialyzer.outputs.errors }} errors, ${{ steps.dialyzer.outputs.warnings }} warnings | `mix dialyzer` |
|
||||
|
||||
|
||||
### 🎯 Quality Gates
|
||||
|
||||
|
||||
Based on the project's quality thresholds:
|
||||
- **Compilation Warnings**: ${{ steps.compile.outputs.warnings }}/148 (limit: 148)
|
||||
- **Credo Issues**: ${{ steps.credo.outputs.total_issues }}/87 (limit: 87)
|
||||
- **Credo Issues**: ${{ steps.credo.outputs.total_issues }}/87 (limit: 87)
|
||||
- **Dialyzer Warnings**: ${{ steps.dialyzer.outputs.warnings }}/161 (limit: 161)
|
||||
- **Test Coverage**: ${{ steps.coverage.outputs.percentage }}%/50% (minimum: 50%)
|
||||
- **Test Failures**: ${{ steps.tests.outputs.failures }}/0 (limit: 0)
|
||||
|
||||
|
||||
<details>
|
||||
<summary>📈 Progress Toward Goals</summary>
|
||||
|
||||
|
||||
Target goals for the project:
|
||||
- ✨ **Zero compilation warnings** (currently: ${{ steps.compile.outputs.warnings }})
|
||||
- ✨ **≤10 Credo issues** (currently: ${{ steps.credo.outputs.total_issues }})
|
||||
- ✨ **Zero Dialyzer warnings** (currently: ${{ steps.dialyzer.outputs.warnings }})
|
||||
- ✨ **≥85% test coverage** (currently: ${{ steps.coverage.outputs.percentage }}%)
|
||||
- ✅ **Zero test failures** (currently: ${{ steps.tests.outputs.failures }})
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>🔧 Quick Actions</summary>
|
||||
|
||||
|
||||
To improve code quality:
|
||||
```bash
|
||||
# Fix formatting issues
|
||||
mix format
|
||||
|
||||
|
||||
# View detailed Credo analysis
|
||||
mix credo --strict
|
||||
|
||||
|
||||
# Check Dialyzer warnings
|
||||
mix dialyzer
|
||||
|
||||
|
||||
# Generate detailed coverage report
|
||||
mix coveralls.html
|
||||
```
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
🤖 *Auto-generated by GitHub Actions* • Updated: ${{ github.event.head_commit.timestamp }}
|
||||
|
||||
> **Note**: This comment will be updated automatically when new commits are pushed to this PR.
|
||||
|
||||
> **Note**: This comment will be updated automatically when new commits are pushed to this PR.
|
||||
|
||||
716
CHANGELOG.md
716
CHANGELOG.md
@@ -2,6 +2,722 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v1.89.0](https://github.com/wanderer-industries/wanderer/compare/v1.88.13...v1.89.0) (2025-11-30)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* removed unnecessary command
|
||||
|
||||
* rework wormholes reference
|
||||
|
||||
## [v1.88.13](https://github.com/wanderer-industries/wanderer/compare/v1.88.12...v1.88.13) (2025-11-29)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed tracking issues
|
||||
|
||||
## [v1.88.12](https://github.com/wanderer-industries/wanderer/compare/v1.88.11...v1.88.12) (2025-11-29)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed c4 -> ns connections auto size issues
|
||||
|
||||
## [v1.88.11](https://github.com/wanderer-industries/wanderer/compare/v1.88.10...v1.88.11) (2025-11-29)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.88.10](https://github.com/wanderer-industries/wanderer/compare/v1.88.9...v1.88.10) (2025-11-29)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed pings cleanup
|
||||
|
||||
## [v1.88.9](https://github.com/wanderer-industries/wanderer/compare/v1.88.8...v1.88.9) (2025-11-29)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed linked signatures cleanup
|
||||
|
||||
## [v1.88.8](https://github.com/wanderer-industries/wanderer/compare/v1.88.7...v1.88.8) (2025-11-28)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed pings issue
|
||||
|
||||
## [v1.88.7](https://github.com/wanderer-industries/wanderer/compare/v1.88.6...v1.88.7) (2025-11-28)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed tracking issues
|
||||
|
||||
## [v1.88.6](https://github.com/wanderer-industries/wanderer/compare/v1.88.5...v1.88.6) (2025-11-28)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed tracking issues
|
||||
|
||||
## [v1.88.5](https://github.com/wanderer-industries/wanderer/compare/v1.88.4...v1.88.5) (2025-11-28)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed env errors
|
||||
|
||||
## [v1.88.4](https://github.com/wanderer-industries/wanderer/compare/v1.88.3...v1.88.4) (2025-11-27)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* defensive check for undefined excluded systems
|
||||
|
||||
## [v1.88.3](https://github.com/wanderer-industries/wanderer/compare/v1.88.2...v1.88.3) (2025-11-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed env issues
|
||||
|
||||
## [v1.88.1](https://github.com/wanderer-industries/wanderer/compare/v1.88.0...v1.88.1) (2025-11-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* sse enable checkbox, and kills ticker
|
||||
|
||||
* apiv1 token auth and structure fixes
|
||||
|
||||
* removed ipv6 distribution env settings
|
||||
|
||||
* tests: updated tests
|
||||
|
||||
* tests: updated tests
|
||||
|
||||
* clean up id generation
|
||||
|
||||
* resolve issue with async event processing
|
||||
|
||||
## [v1.88.0](https://github.com/wanderer-industries/wanderer/compare/v1.87.0...v1.88.0) (2025-11-25)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Add zkb and eve who links for characters where it possibly was add
|
||||
|
||||
## [v1.87.0](https://github.com/wanderer-industries/wanderer/compare/v1.86.1...v1.87.0) (2025-11-25)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Add support markdown for system description
|
||||
|
||||
## [v1.86.1](https://github.com/wanderer-industries/wanderer/compare/v1.86.0...v1.86.1) (2025-11-25)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Add ability to see character passage direction in list of passages
|
||||
|
||||
## [v1.86.0](https://github.com/wanderer-industries/wanderer/compare/v1.85.5...v1.86.0) (2025-11-25)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* add date filter for character activity
|
||||
|
||||
## [v1.85.5](https://github.com/wanderer-industries/wanderer/compare/v1.85.4...v1.85.5) (2025-11-24)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed connections cleanup and rally points delete issues
|
||||
|
||||
## [v1.85.4](https://github.com/wanderer-industries/wanderer/compare/v1.85.3...v1.85.4) (2025-11-22)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: invalidate map characters every 1 hour for any missing/revoked permissions
|
||||
|
||||
## [v1.85.3](https://github.com/wanderer-industries/wanderer/compare/v1.85.2...v1.85.3) (2025-11-22)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed connection time status issues. fixed character alliance update issues
|
||||
|
||||
## [v1.85.2](https://github.com/wanderer-industries/wanderer/compare/v1.85.1...v1.85.2) (2025-11-20)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: increased API pool limits
|
||||
|
||||
## [v1.85.1](https://github.com/wanderer-industries/wanderer/compare/v1.85.0...v1.85.1) (2025-11-20)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: increased API pool limits
|
||||
|
||||
## [v1.85.0](https://github.com/wanderer-industries/wanderer/compare/v1.84.37...v1.85.0) (2025-11-19)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* core: added support for new ship types
|
||||
|
||||
## [v1.84.37](https://github.com/wanderer-industries/wanderer/compare/v1.84.36...v1.84.37) (2025-11-19)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* auth: fixed character auth issues
|
||||
|
||||
## [v1.84.36](https://github.com/wanderer-industries/wanderer/compare/v1.84.35...v1.84.36) (2025-11-19)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* fixed duplicated map slugs
|
||||
|
||||
## [v1.84.35](https://github.com/wanderer-industries/wanderer/compare/v1.84.34...v1.84.35) (2025-11-19)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* structure search / paste issues
|
||||
|
||||
## [v1.84.34](https://github.com/wanderer-industries/wanderer/compare/v1.84.33...v1.84.34) (2025-11-18)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed character tracking issues
|
||||
|
||||
## [v1.84.33](https://github.com/wanderer-industries/wanderer/compare/v1.84.32...v1.84.33) (2025-11-18)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed character tracking issues
|
||||
|
||||
## [v1.84.32](https://github.com/wanderer-industries/wanderer/compare/v1.84.31...v1.84.32) (2025-11-18)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed character tracking issues
|
||||
|
||||
## [v1.84.31](https://github.com/wanderer-industries/wanderer/compare/v1.84.30...v1.84.31) (2025-11-17)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed connactions validation logic
|
||||
|
||||
## [v1.84.30](https://github.com/wanderer-industries/wanderer/compare/v1.84.29...v1.84.30) (2025-11-17)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.84.29](https://github.com/wanderer-industries/wanderer/compare/v1.84.28...v1.84.29) (2025-11-17)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.84.28](https://github.com/wanderer-industries/wanderer/compare/v1.84.27...v1.84.28) (2025-11-17)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed ACL updates
|
||||
|
||||
## [v1.84.27](https://github.com/wanderer-industries/wanderer/compare/v1.84.26...v1.84.27) (2025-11-17)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: supported characters_updates for external events
|
||||
|
||||
* core: improved character tracking
|
||||
|
||||
* core: improved character tracking
|
||||
|
||||
* core: improved character location tracking
|
||||
|
||||
## [v1.84.26](https://github.com/wanderer-industries/wanderer/compare/v1.84.25...v1.84.26) (2025-11-16)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: disable character tracker pausing
|
||||
|
||||
## [v1.84.25](https://github.com/wanderer-industries/wanderer/compare/v1.84.24...v1.84.25) (2025-11-16)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: used upsert for adding map systems
|
||||
|
||||
## [v1.84.24](https://github.com/wanderer-industries/wanderer/compare/v1.84.23...v1.84.24) (2025-11-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed problem related with error if settings was removed and mapper crashed. Fixed settings reset.
|
||||
|
||||
## [v1.84.23](https://github.com/wanderer-industries/wanderer/compare/v1.84.22...v1.84.23) (2025-11-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed map pings cancel errors
|
||||
|
||||
## [v1.84.22](https://github.com/wanderer-industries/wanderer/compare/v1.84.21...v1.84.22) (2025-11-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed map initialization
|
||||
|
||||
## [v1.84.21](https://github.com/wanderer-industries/wanderer/compare/v1.84.20...v1.84.21) (2025-11-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed map characters adding
|
||||
|
||||
## [v1.84.20](https://github.com/wanderer-industries/wanderer/compare/v1.84.19...v1.84.20) (2025-11-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed map start issues
|
||||
|
||||
## [v1.84.19](https://github.com/wanderer-industries/wanderer/compare/v1.84.18...v1.84.19) (2025-11-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed map start issues
|
||||
|
||||
## [v1.84.18](https://github.com/wanderer-industries/wanderer/compare/v1.84.17...v1.84.18) (2025-11-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: added gracefull map poll recovery from saved state. added map slug unique checks
|
||||
|
||||
## [v1.84.17](https://github.com/wanderer-industries/wanderer/compare/v1.84.16...v1.84.17) (2025-11-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed activity tracking issues
|
||||
|
||||
## [v1.84.16](https://github.com/wanderer-industries/wanderer/compare/v1.84.15...v1.84.16) (2025-11-13)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: removed maps auto-start logic
|
||||
|
||||
## [v1.84.15](https://github.com/wanderer-industries/wanderer/compare/v1.84.14...v1.84.15) (2025-11-13)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed maps start/stop logic, added server downtime period support
|
||||
|
||||
## [v1.84.14](https://github.com/wanderer-industries/wanderer/compare/v1.84.13...v1.84.14) (2025-11-13)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed problem related with error if settings was removed and mapper crashed. Fixed settings reset.
|
||||
|
||||
## [v1.84.13](https://github.com/wanderer-industries/wanderer/compare/v1.84.12...v1.84.13) (2025-11-13)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.84.12](https://github.com/wanderer-industries/wanderer/compare/v1.84.11...v1.84.12) (2025-11-13)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.84.11](https://github.com/wanderer-industries/wanderer/compare/v1.84.10...v1.84.11) (2025-11-12)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* api and doc updates
|
||||
|
||||
## [v1.84.10](https://github.com/wanderer-industries/wanderer/compare/v1.84.9...v1.84.10) (2025-11-12)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: Fixed adding system on character dock
|
||||
|
||||
## [v1.84.9](https://github.com/wanderer-industries/wanderer/compare/v1.84.8...v1.84.9) (2025-11-12)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.84.8](https://github.com/wanderer-industries/wanderer/compare/v1.84.7...v1.84.8) (2025-11-12)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: added cleanup jobs for old system signatures & chain passages
|
||||
|
||||
## [v1.84.7](https://github.com/wanderer-industries/wanderer/compare/v1.84.6...v1.84.7) (2025-11-12)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* api and structure search fixes
|
||||
|
||||
## [v1.84.6](https://github.com/wanderer-industries/wanderer/compare/v1.84.5...v1.84.6) (2025-11-12)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: Added map slug uniqness checking while using API
|
||||
|
||||
## [v1.84.5](https://github.com/wanderer-industries/wanderer/compare/v1.84.4...v1.84.5) (2025-11-11)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: Added tracking for map & character event handling errors
|
||||
|
||||
## [v1.84.4](https://github.com/wanderer-industries/wanderer/compare/v1.84.3...v1.84.4) (2025-11-11)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed issue with updating system signatures
|
||||
|
||||
## [v1.84.3](https://github.com/wanderer-industries/wanderer/compare/v1.84.2...v1.84.3) (2025-11-11)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: fixed linked signature time status update
|
||||
|
||||
## [v1.84.2](https://github.com/wanderer-industries/wanderer/compare/v1.84.1...v1.84.2) (2025-11-10)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* api: fixed api for get/update map systems
|
||||
|
||||
* add index for map/systems api
|
||||
|
||||
## [v1.84.1](https://github.com/wanderer-industries/wanderer/compare/v1.84.0...v1.84.1) (2025-11-01)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed connection time status update issue
|
||||
|
||||
## [v1.84.0](https://github.com/wanderer-industries/wanderer/compare/v1.83.4...v1.84.0) (2025-10-29)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: ESI API rate limits support
|
||||
|
||||
## [v1.83.4](https://github.com/wanderer-industries/wanderer/compare/v1.83.3...v1.83.4) (2025-10-29)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed page reloads
|
||||
|
||||
## [v1.83.3](https://github.com/wanderer-industries/wanderer/compare/v1.83.2...v1.83.3) (2025-10-27)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed old map API for systems & added small QOL improvements
|
||||
|
||||
## [v1.83.2](https://github.com/wanderer-industries/wanderer/compare/v1.83.1...v1.83.2) (2025-10-22)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Connections: Set new connection time status based on to/from system class
|
||||
|
||||
## [v1.83.1](https://github.com/wanderer-industries/wanderer/compare/v1.83.0...v1.83.1) (2025-10-21)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Kills: Fixed zkb links (added following '/').
|
||||
|
||||
## [v1.83.0](https://github.com/wanderer-industries/wanderer/compare/v1.82.3...v1.83.0) (2025-10-21)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: Added map roles settings for copy/paste
|
||||
|
||||
* Core: Added map roles settings for copy/paste
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Copy-Paste restriction: support from FE side - fixed problem with incorrect disabling copy and paste buttons
|
||||
|
||||
* Map: Copy-Paste restriction: support from FE side - removed unnecessary constant
|
||||
|
||||
* Map: Copy-Paste restriction: support from FE side
|
||||
|
||||
* Core: Added Eve data downloaded files cleanup logic
|
||||
|
||||
## [v1.82.3](https://github.com/wanderer-industries/wanderer/compare/v1.82.2...v1.82.3) (2025-10-21)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix system static info - add source region for U319 from Null-sec
|
||||
|
||||
## [v1.82.2](https://github.com/wanderer-industries/wanderer/compare/v1.82.1...v1.82.2) (2025-10-21)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix system static info - for J012635 add D382; for J015092 - changed from J244, Z060 to N110, J244; for J000487 removed C008
|
||||
|
||||
## [v1.82.1](https://github.com/wanderer-industries/wanderer/compare/v1.82.0...v1.82.1) (2025-10-20)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed 'viewer' map access & characters tracking
|
||||
|
||||
## [v1.82.0](https://github.com/wanderer-industries/wanderer/compare/v1.81.15...v1.82.0) (2025-10-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: Added an ability to copy/paste selected map area between maps
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Add ability to copy and past systems (UI part)
|
||||
|
||||
## [v1.81.15](https://github.com/wanderer-industries/wanderer/compare/v1.81.14...v1.81.15) (2025-10-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed problem with commit - for correct restore deprecated data - change config key
|
||||
|
||||
## [v1.81.14](https://github.com/wanderer-industries/wanderer/compare/v1.81.13...v1.81.14) (2025-10-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed problem with commit - for correct restore deprecated data
|
||||
|
||||
## [v1.81.13](https://github.com/wanderer-industries/wanderer/compare/v1.81.12...v1.81.13) (2025-10-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed system select after tab switch
|
||||
|
||||
## [v1.81.12](https://github.com/wanderer-industries/wanderer/compare/v1.81.11...v1.81.12) (2025-10-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map events buffering on tab switch
|
||||
|
||||
## [v1.81.11](https://github.com/wanderer-industries/wanderer/compare/v1.81.10...v1.81.11) (2025-10-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signatures: Fixed EOL indication for un-splashed and signatures list
|
||||
|
||||
## [v1.81.10](https://github.com/wanderer-industries/wanderer/compare/v1.81.9...v1.81.10) (2025-10-13)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signatures: Rework for lazy signatures deletion
|
||||
|
||||
## [v1.81.9](https://github.com/wanderer-industries/wanderer/compare/v1.81.8...v1.81.9) (2025-10-12)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signatures: Fixed issue with wrong linked signatures deletions
|
||||
|
||||
## [v1.81.8](https://github.com/wanderer-industries/wanderer/compare/v1.81.7...v1.81.8) (2025-10-11)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix problem with restoring settings on widgets
|
||||
|
||||
## [v1.81.7](https://github.com/wanderer-industries/wanderer/compare/v1.81.6...v1.81.7) (2025-10-10)
|
||||
|
||||
|
||||
|
||||
55
Makefile
55
Makefile
@@ -30,10 +30,60 @@ format f:
|
||||
mix format
|
||||
|
||||
test t:
|
||||
mix test
|
||||
MIX_ENV=test mix test
|
||||
|
||||
# Run tests in 4 parallel partitions (useful for CI or faster local runs)
|
||||
test-parallel tp:
|
||||
@echo "Running tests in 4 parallel partitions..."
|
||||
@mkdir -p /tmp/wanderer_test_results
|
||||
@rm -f /tmp/wanderer_test_results/partition_*.txt /tmp/wanderer_test_results/exit_*.txt
|
||||
@for i in 1 2 3 4; do \
|
||||
(MIX_TEST_PARTITION=$$i MIX_ENV=test mix test --partitions 4 2>&1; echo $$? > /tmp/wanderer_test_results/exit_$$i.txt) | \
|
||||
tee /tmp/wanderer_test_results/partition_$$i.txt | sed "s/^/[P$$i] /" & \
|
||||
done; \
|
||||
wait
|
||||
@echo ""
|
||||
@echo "========================================"
|
||||
@echo " TEST RESULTS SUMMARY"
|
||||
@echo "========================================"
|
||||
@total_tests=0; total_failures=0; total_excluded=0; all_passed=true; \
|
||||
for i in 1 2 3 4; do \
|
||||
exit_code=$$(cat /tmp/wanderer_test_results/exit_$$i.txt 2>/dev/null || echo "1"); \
|
||||
if [ "$$exit_code" != "0" ]; then all_passed=false; fi; \
|
||||
summary=$$(grep -E "^[0-9]+ (tests?|doctest)" /tmp/wanderer_test_results/partition_$$i.txt | tail -1 || echo "No results"); \
|
||||
tests=$$(echo "$$summary" | grep -oE "^[0-9]+" || echo "0"); \
|
||||
failures=$$(echo "$$summary" | grep -oE "[0-9]+ failures?" | grep -oE "^[0-9]+" || echo "0"); \
|
||||
excluded=$$(echo "$$summary" | grep -oE "[0-9]+ excluded" | grep -oE "^[0-9]+" || echo "0"); \
|
||||
total_tests=$$((total_tests + tests)); \
|
||||
total_failures=$$((total_failures + failures)); \
|
||||
total_excluded=$$((total_excluded + excluded)); \
|
||||
if [ "$$exit_code" = "0" ]; then \
|
||||
echo "Partition $$i: ✓ $$summary"; \
|
||||
else \
|
||||
echo "Partition $$i: ✗ $$summary (exit code: $$exit_code)"; \
|
||||
fi; \
|
||||
done; \
|
||||
echo "========================================"; \
|
||||
echo "TOTAL: $$total_tests tests, $$total_failures failures, $$total_excluded excluded"; \
|
||||
echo "========================================"; \
|
||||
if [ "$$all_passed" = "true" ]; then \
|
||||
echo "✓ All partitions passed!"; \
|
||||
else \
|
||||
echo "✗ Some partitions failed. Details below:"; \
|
||||
echo ""; \
|
||||
for i in 1 2 3 4; do \
|
||||
exit_code=$$(cat /tmp/wanderer_test_results/exit_$$i.txt 2>/dev/null || echo "1"); \
|
||||
if [ "$$exit_code" != "0" ]; then \
|
||||
echo "======== PARTITION $$i FAILURES ========"; \
|
||||
grep -A 50 "Failures:" /tmp/wanderer_test_results/partition_$$i.txt 2>/dev/null || cat /tmp/wanderer_test_results/partition_$$i.txt; \
|
||||
echo ""; \
|
||||
fi; \
|
||||
done; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
coverage cover co:
|
||||
mix test --cover
|
||||
MIX_ENV=test mix test --cover
|
||||
|
||||
unit-tests ut:
|
||||
@echo "Running unit tests..."
|
||||
@@ -45,4 +95,3 @@ versions v:
|
||||
@cat .tool-versions
|
||||
@cat Aptfile
|
||||
@echo
|
||||
|
||||
|
||||
@@ -73,7 +73,9 @@ body > div:first-of-type {
|
||||
}
|
||||
|
||||
.maps_bg {
|
||||
background-image: url('../images/maps_bg.webp');
|
||||
/* OLD image */
|
||||
/* background-image: url('../images/maps_bg.webp'); */
|
||||
background-image: url('https://wanderer-industries.github.io/wanderer-assets/images/eve-screen-catalyst-expansion-bg.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
width: 100%;
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useMapperHandlers } from './useMapperHandlers';
|
||||
import { MapRootContent } from '@/hooks/Mapper/components/mapRootContent/MapRootContent.tsx';
|
||||
import { MapRootProvider } from '@/hooks/Mapper/mapRootProvider';
|
||||
import './common-styles/main.scss';
|
||||
import { ToastProvider } from '@/hooks/Mapper/ToastProvider.tsx';
|
||||
|
||||
const ErrorFallback = () => {
|
||||
return <div className="!z-100 absolute w-screen h-screen bg-transparent"></div>;
|
||||
@@ -39,13 +40,15 @@ export default function MapRoot({ hooks }) {
|
||||
|
||||
return (
|
||||
<PrimeReactProvider>
|
||||
<MapRootProvider fwdRef={providerRef} outCommand={handleCommand}>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}>
|
||||
<ReactFlowProvider>
|
||||
<MapRootContent />
|
||||
</ReactFlowProvider>
|
||||
</ErrorBoundary>
|
||||
</MapRootProvider>
|
||||
<ToastProvider>
|
||||
<MapRootProvider fwdRef={providerRef} outCommand={handleCommand}>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}>
|
||||
<ReactFlowProvider>
|
||||
<MapRootContent />
|
||||
</ReactFlowProvider>
|
||||
</ErrorBoundary>
|
||||
</MapRootProvider>
|
||||
</ToastProvider>
|
||||
</PrimeReactProvider>
|
||||
);
|
||||
}
|
||||
|
||||
31
assets/js/hooks/Mapper/ToastProvider.tsx
Normal file
31
assets/js/hooks/Mapper/ToastProvider.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React, { createContext, useContext, useRef } from 'react';
|
||||
import { Toast } from 'primereact/toast';
|
||||
import type { ToastMessage } from 'primereact/toast';
|
||||
|
||||
interface ToastContextValue {
|
||||
toastRef: React.RefObject<Toast>;
|
||||
show: (message: ToastMessage | ToastMessage[]) => void;
|
||||
}
|
||||
|
||||
const ToastContext = createContext<ToastContextValue | null>(null);
|
||||
|
||||
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const toastRef = useRef<Toast>(null);
|
||||
|
||||
const show = (message: ToastMessage | ToastMessage[]) => {
|
||||
toastRef.current?.show(message);
|
||||
};
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={{ toastRef, show }}>
|
||||
<Toast ref={toastRef} position="top-right" />
|
||||
{children}
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useToast = (): ToastContextValue => {
|
||||
const context = useContext(ToastContext);
|
||||
if (!context) throw new Error('useToast must be used within a ToastProvider');
|
||||
return context;
|
||||
};
|
||||
@@ -51,20 +51,8 @@ export const Characters = ({ data }: CharactersProps) => {
|
||||
['border-lime-600/70']: character.online,
|
||||
},
|
||||
)}
|
||||
title={character.tracking_paused ? `${character.name} - Tracking Paused (click to resume)` : character.name}
|
||||
title={character.name}
|
||||
>
|
||||
{character.tracking_paused && (
|
||||
<>
|
||||
<span
|
||||
className={clsx(
|
||||
'absolute flex flex-col p-[2px] top-[0px] left-[0px] w-[35px] h-[35px]',
|
||||
'text-yellow-500 text-[9px] z-10 bg-gray-800/40',
|
||||
'pi',
|
||||
PrimeIcons.PAUSE,
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{mainCharacterEveId === character.eve_id && (
|
||||
<span
|
||||
className={clsx(
|
||||
|
||||
@@ -118,7 +118,11 @@ export const useContextMenuSystemItems = ({
|
||||
});
|
||||
|
||||
if (isShowPingBtn) {
|
||||
return <WdMenuItem icon={iconClasses}>{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}</WdMenuItem>;
|
||||
return (
|
||||
<WdMenuItem icon={iconClasses} className="!ml-[-2px]">
|
||||
{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}
|
||||
</WdMenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -126,7 +130,7 @@ export const useContextMenuSystemItems = ({
|
||||
infoTitle="Locked. Ping can be set only for one system."
|
||||
infoClass="pi-lock text-stone-500 mr-[12px]"
|
||||
>
|
||||
<WdMenuItem disabled icon={iconClasses}>
|
||||
<WdMenuItem disabled icon={iconClasses} className="!ml-[-2px]">
|
||||
{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}
|
||||
</WdMenuItem>
|
||||
</MenuItemWithInfo>
|
||||
|
||||
@@ -2,25 +2,60 @@ import React, { RefObject, useMemo } from 'react';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { MenuItem } from 'primereact/menuitem';
|
||||
import { checkPermissions } from '@/hooks/Mapper/components/map/helpers';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { MenuItemWithInfo, WdMenuItem } from '@/hooks/Mapper/components/ui-kit';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export interface ContextMenuSystemMultipleProps {
|
||||
contextMenuRef: RefObject<ContextMenu>;
|
||||
onDeleteSystems(): void;
|
||||
onCopySystems(): void;
|
||||
}
|
||||
|
||||
export const ContextMenuSystemMultiple: React.FC<ContextMenuSystemMultipleProps> = ({
|
||||
contextMenuRef,
|
||||
onDeleteSystems,
|
||||
onCopySystems,
|
||||
}) => {
|
||||
const {
|
||||
data: { options, userPermissions },
|
||||
} = useMapRootState();
|
||||
|
||||
const items: MenuItem[] = useMemo(() => {
|
||||
const allowCopy = checkPermissions(userPermissions, options.allowed_copy_for);
|
||||
return [
|
||||
{
|
||||
label: 'Delete',
|
||||
icon: PrimeIcons.TRASH,
|
||||
icon: clsx(PrimeIcons.TRASH, 'text-red-400'),
|
||||
command: onDeleteSystems,
|
||||
},
|
||||
{ separator: true },
|
||||
{
|
||||
label: 'Copy',
|
||||
icon: PrimeIcons.COPY,
|
||||
command: onCopySystems,
|
||||
disabled: !allowCopy,
|
||||
template: () => {
|
||||
if (allowCopy) {
|
||||
return <WdMenuItem icon="pi pi-copy">Copy</WdMenuItem>;
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItemWithInfo
|
||||
infoTitle="Action is blocked because you don’t have permission to Copy."
|
||||
infoClass={clsx(PrimeIcons.QUESTION_CIRCLE, 'text-stone-500 mr-[12px]')}
|
||||
tooltipWrapperClassName="flex"
|
||||
>
|
||||
<WdMenuItem disabled icon="pi pi-copy">
|
||||
Copy
|
||||
</WdMenuItem>
|
||||
</MenuItemWithInfo>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
}, [onDeleteSystems]);
|
||||
}, [onCopySystems, onDeleteSystems, options, userPermissions]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -6,27 +6,34 @@ import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { encodeJsonToUriBase64 } from '@/hooks/Mapper/utils';
|
||||
import { useToast } from '@/hooks/Mapper/ToastProvider.tsx';
|
||||
|
||||
export const useContextMenuSystemMultipleHandlers = () => {
|
||||
const {
|
||||
data: { pings },
|
||||
data: { pings, connections },
|
||||
} = useMapRootState();
|
||||
|
||||
const { show } = useToast();
|
||||
|
||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||
const [systems, setSystems] = useState<Node<SolarSystemRawType>[]>();
|
||||
|
||||
const { deleteSystems } = useDeleteSystems();
|
||||
|
||||
const ping = useMemo(() => (pings.length === 1 ? pings[0] : undefined), [pings]);
|
||||
const refVars = useRef({ systems, ping, connections, deleteSystems });
|
||||
refVars.current = { systems, ping, connections, deleteSystems };
|
||||
|
||||
const handleSystemMultipleContext: NodeSelectionMouseHandler = (ev, systems_) => {
|
||||
const handleSystemMultipleContext = useCallback<NodeSelectionMouseHandler>((ev, systems_) => {
|
||||
setSystems(systems_);
|
||||
ev.preventDefault();
|
||||
ctxManager.next('ctxSysMult', contextMenuRef.current);
|
||||
contextMenuRef.current?.show(ev);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onDeleteSystems = useCallback(() => {
|
||||
const { systems, ping, deleteSystems } = refVars.current;
|
||||
|
||||
if (!systems) {
|
||||
return;
|
||||
}
|
||||
@@ -41,11 +48,34 @@ export const useContextMenuSystemMultipleHandlers = () => {
|
||||
}
|
||||
|
||||
deleteSystems(sysToDel);
|
||||
}, [deleteSystems, systems, ping]);
|
||||
}, []);
|
||||
|
||||
const onCopySystems = useCallback(async () => {
|
||||
const { systems, connections } = refVars.current;
|
||||
if (!systems) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connectionToCopy = connections.filter(
|
||||
c => systems.filter(s => [c.target, c.source].includes(s.id)).length == 2,
|
||||
);
|
||||
|
||||
await navigator.clipboard.writeText(
|
||||
encodeJsonToUriBase64({ systems: systems.map(x => x.data), connections: connectionToCopy }),
|
||||
);
|
||||
|
||||
show({
|
||||
severity: 'success',
|
||||
summary: 'Copied to clipboard',
|
||||
detail: `Successfully copied to clipboard - [${systems.length}] systems and [${connectionToCopy.length}] connections`,
|
||||
life: 3000,
|
||||
});
|
||||
}, [show]);
|
||||
|
||||
return {
|
||||
handleSystemMultipleContext,
|
||||
contextMenuRef,
|
||||
onDeleteSystems,
|
||||
onCopySystems,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { LayoutEventBlocker, TooltipPosition, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
import classes from './FastSystemActions.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import classes from './FastSystemActions.module.scss';
|
||||
|
||||
export interface FastSystemActionsProps {
|
||||
systemId: string;
|
||||
@@ -27,7 +27,7 @@ export const FastSystemActions = ({
|
||||
ref.current = { systemId, systemName, regionName, isWH };
|
||||
|
||||
const handleOpenZKB = useCallback(
|
||||
() => window.open(`https://zkillboard.com/system/${ref.current.systemId}`, '_blank'),
|
||||
() => window.open(`https://zkillboard.com/system/${ref.current.systemId}/`, '_blank'),
|
||||
[],
|
||||
);
|
||||
|
||||
|
||||
@@ -8,6 +8,4 @@ export type WaypointSetContextHandlerProps = {
|
||||
destination: string;
|
||||
};
|
||||
export type WaypointSetContextHandler = (props: WaypointSetContextHandlerProps) => void;
|
||||
export type NodeSelectionMouseHandler =
|
||||
| ((event: React.MouseEvent<Element, MouseEvent>, nodes: Node[]) => void)
|
||||
| undefined;
|
||||
export type NodeSelectionMouseHandler = (event: React.MouseEvent<Element, MouseEvent>, nodes: Node[]) => void;
|
||||
|
||||
@@ -120,7 +120,7 @@ const MapComp = ({
|
||||
useMapHandlers(refn, onSelectionChange);
|
||||
useUpdateNodes(nodes);
|
||||
|
||||
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem });
|
||||
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem, onCommand });
|
||||
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
|
||||
const { update } = useMapState();
|
||||
const { variant, gap, size, color } = useBackgroundVars(theme);
|
||||
|
||||
@@ -2,22 +2,70 @@ import React, { RefObject, useMemo } from 'react';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { MenuItem } from 'primereact/menuitem';
|
||||
import { PasteSystemsAndConnections } from '@/hooks/Mapper/components/map/components';
|
||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
import { checkPermissions } from '@/hooks/Mapper/components/map/helpers';
|
||||
import { MenuItemWithInfo, WdMenuItem } from '@/hooks/Mapper/components/ui-kit';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export interface ContextMenuRootProps {
|
||||
contextMenuRef: RefObject<ContextMenu>;
|
||||
pasteSystemsAndConnections: PasteSystemsAndConnections | undefined;
|
||||
onAddSystem(): void;
|
||||
onPasteSystemsAnsConnections(): void;
|
||||
}
|
||||
|
||||
export const ContextMenuRoot: React.FC<ContextMenuRootProps> = ({ contextMenuRef, onAddSystem }) => {
|
||||
export const ContextMenuRoot: React.FC<ContextMenuRootProps> = ({
|
||||
contextMenuRef,
|
||||
onAddSystem,
|
||||
onPasteSystemsAnsConnections,
|
||||
pasteSystemsAndConnections,
|
||||
}) => {
|
||||
const {
|
||||
data: { options, userPermissions },
|
||||
} = useMapState();
|
||||
|
||||
const items: MenuItem[] = useMemo(() => {
|
||||
const allowPaste = checkPermissions(userPermissions, options.allowed_paste_for);
|
||||
|
||||
return [
|
||||
{
|
||||
label: 'Add System',
|
||||
icon: PrimeIcons.PLUS,
|
||||
command: onAddSystem,
|
||||
},
|
||||
...(pasteSystemsAndConnections != null
|
||||
? [
|
||||
{
|
||||
icon: 'pi pi-clipboard',
|
||||
disabled: !allowPaste,
|
||||
command: onPasteSystemsAnsConnections,
|
||||
template: () => {
|
||||
if (allowPaste) {
|
||||
return (
|
||||
<WdMenuItem icon="pi pi-clipboard">
|
||||
Paste
|
||||
</WdMenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItemWithInfo
|
||||
infoTitle="Action is blocked because you don’t have permission to Paste."
|
||||
infoClass={clsx(PrimeIcons.QUESTION_CIRCLE, 'text-stone-500 mr-[12px]')}
|
||||
tooltipWrapperClassName="flex"
|
||||
>
|
||||
<WdMenuItem disabled icon="pi pi-clipboard">
|
||||
Paste
|
||||
</WdMenuItem>
|
||||
</MenuItemWithInfo>
|
||||
);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
}, [onAddSystem]);
|
||||
}, [userPermissions, options, onAddSystem, pasteSystemsAndConnections, onPasteSystemsAnsConnections]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,36 +1,76 @@
|
||||
import { useReactFlow, XYPosition } from 'reactflow';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { OnMapAddSystemCallback } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||
import { recenterSystemsByBounds } from '@/hooks/Mapper/helpers/recenterSystems.ts';
|
||||
import { OutCommand, OutCommandHandler, SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { decodeUriBase64ToJson } from '@/hooks/Mapper/utils';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { useReactFlow, XYPosition } from 'reactflow';
|
||||
|
||||
export type PasteSystemsAndConnections = {
|
||||
systems: SolarSystemRawType[];
|
||||
connections: SolarSystemConnection[];
|
||||
};
|
||||
|
||||
type UseContextMenuRootHandlers = {
|
||||
onAddSystem?: OnMapAddSystemCallback;
|
||||
onCommand?: OutCommandHandler;
|
||||
};
|
||||
|
||||
export const useContextMenuRootHandlers = ({ onAddSystem }: UseContextMenuRootHandlers = {}) => {
|
||||
export const useContextMenuRootHandlers = ({ onAddSystem, onCommand }: UseContextMenuRootHandlers = {}) => {
|
||||
const rf = useReactFlow();
|
||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||
const [position, setPosition] = useState<XYPosition | null>(null);
|
||||
const [pasteSystemsAndConnections, setPasteSystemsAndConnections] = useState<PasteSystemsAndConnections>();
|
||||
|
||||
const handleRootContext = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const handleRootContext = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
setPosition(rf.project({ x: e.clientX, y: e.clientY }));
|
||||
e.preventDefault();
|
||||
ctxManager.next('ctxRoot', contextMenuRef.current);
|
||||
contextMenuRef.current?.show(e);
|
||||
|
||||
try {
|
||||
const text = await navigator.clipboard.readText();
|
||||
const result = decodeUriBase64ToJson(text);
|
||||
setPasteSystemsAndConnections(result as PasteSystemsAndConnections);
|
||||
} catch (err) {
|
||||
setPasteSystemsAndConnections(undefined);
|
||||
// do nothing
|
||||
}
|
||||
};
|
||||
|
||||
const ref = useRef({ onAddSystem, position });
|
||||
ref.current = { onAddSystem, position };
|
||||
const ref = useRef({ onAddSystem, position, pasteSystemsAndConnections, onCommand });
|
||||
ref.current = { onAddSystem, position, pasteSystemsAndConnections, onCommand };
|
||||
|
||||
const onAddSystemCallback = useCallback(() => {
|
||||
ref.current.onAddSystem?.({ coordinates: position });
|
||||
}, [position]);
|
||||
|
||||
const onPasteSystemsAnsConnections = useCallback(async () => {
|
||||
const { pasteSystemsAndConnections, onCommand, position } = ref.current;
|
||||
if (!position || !onCommand || !pasteSystemsAndConnections) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { systems } = recenterSystemsByBounds(pasteSystemsAndConnections.systems);
|
||||
|
||||
await onCommand({
|
||||
type: OutCommand.manualPasteSystemsAndConnections,
|
||||
data: {
|
||||
systems: systems.map(({ position: srcPos, ...rest }) => ({
|
||||
position: { x: Math.round(srcPos.x + position.x), y: Math.round(srcPos.y + position.y) },
|
||||
...rest,
|
||||
})),
|
||||
connections: pasteSystemsAndConnections.connections,
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
handleRootContext,
|
||||
|
||||
pasteSystemsAndConnections,
|
||||
contextMenuRef,
|
||||
onAddSystem: onAddSystemCallback,
|
||||
onPasteSystemsAnsConnections,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@use "sass:color";
|
||||
@use '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
||||
@import '@/hooks/Mapper/components/map/styles/solar-system-node';
|
||||
@use '@/hooks/Mapper/components/map/styles/solar-system-node' as v;
|
||||
|
||||
@keyframes move-stripes {
|
||||
from {
|
||||
@@ -26,8 +26,8 @@
|
||||
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 color.adjust($pastel-blue, $lightness: -10%);
|
||||
box-shadow: 0 0 5px rgba(v.$dark-bg, 0.5);
|
||||
border: 1px solid color.adjust(v.$pastel-blue, $lightness: -10%);
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
@@ -99,7 +99,7 @@
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: $pastel-pink;
|
||||
border-color: v.$pastel-pink;
|
||||
box-shadow: 0 0 10px #9a1af1c2;
|
||||
}
|
||||
|
||||
@@ -113,11 +113,11 @@
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
|
||||
border-color: $neon-color-1;
|
||||
border-color: v.$neon-color-1;
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
$neon-color-3 0px,
|
||||
$neon-color-3 8px,
|
||||
v.$neon-color-3 0px,
|
||||
v.$neon-color-3 8px,
|
||||
transparent 8px,
|
||||
transparent 21px
|
||||
);
|
||||
@@ -146,7 +146,7 @@
|
||||
border: 1px solid var(--eve-solar-system-status-color-lookingFor-dark15);
|
||||
background-image: linear-gradient(275deg, #45ff8f2f, #457fff2f);
|
||||
&.selected {
|
||||
border-color: $pastel-pink;
|
||||
border-color: v.$pastel-pink;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,13 +347,13 @@
|
||||
.Handle {
|
||||
min-width: initial;
|
||||
min-height: initial;
|
||||
border: 1px solid $pastel-blue;
|
||||
border: 1px solid v.$pastel-blue;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
pointer-events: auto;
|
||||
|
||||
&.selected {
|
||||
border-color: $pastel-pink;
|
||||
border-color: v.$pastel-pink;
|
||||
}
|
||||
|
||||
&.HandleTop {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { InfoDrawer } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
|
||||
import classes from './UnsplashedSignature.module.scss';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { WORMHOLE_CLASS_STYLES, WORMHOLES_ADDITIONAL_INFO } from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import { useMemo } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { renderInfoColumn } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
||||
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
|
||||
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
|
||||
import clsx from 'clsx';
|
||||
import { useMemo } from 'react';
|
||||
import classes from './UnsplashedSignature.module.scss';
|
||||
|
||||
interface UnsplashedSignatureProps {
|
||||
signature: SystemSignature;
|
||||
@@ -35,7 +36,7 @@ export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) =>
|
||||
}, [customInfo]);
|
||||
|
||||
const isEOL = useMemo(() => {
|
||||
return customInfo?.isEOL;
|
||||
return customInfo?.time_status === TimeStatus._1h;
|
||||
}, [customInfo]);
|
||||
|
||||
const whClassStyle = useMemo(() => {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { UserPermission, UserPermissions } from '@/hooks/Mapper/types';
|
||||
|
||||
export const checkPermissions = (permissions: Partial<UserPermissions>, targetPermission: UserPermission) => {
|
||||
return targetPermission != null && permissions[targetPermission];
|
||||
};
|
||||
@@ -4,3 +4,4 @@ export * from './getSystemClassStyles';
|
||||
export * from './getShapeClass';
|
||||
export * from './getBackgroundClass';
|
||||
export * from './prepareUnsplashedChunks';
|
||||
export * from './checkPermissions';
|
||||
|
||||
@@ -14,8 +14,27 @@ export const useCommandsCharacters = () => {
|
||||
const ref = useRef({ update });
|
||||
ref.current = { update };
|
||||
|
||||
const charactersUpdated = useCallback((characters: CommandCharactersUpdated) => {
|
||||
ref.current.update(() => ({ characters: characters.slice() }));
|
||||
const charactersUpdated = useCallback((updatedCharacters: CommandCharactersUpdated) => {
|
||||
ref.current.update(state => {
|
||||
const existing = state.characters ?? [];
|
||||
// Put updatedCharacters into a map keyed by ID
|
||||
const updatedMap = new Map(updatedCharacters.map(c => [c.eve_id, c]));
|
||||
|
||||
// 1. Update existing characters when possible
|
||||
const merged = existing.map(character => {
|
||||
const updated = updatedMap.get(character.eve_id);
|
||||
if (updated) {
|
||||
updatedMap.delete(character.eve_id); // Mark as processed
|
||||
return { ...character, ...updated };
|
||||
}
|
||||
return character;
|
||||
});
|
||||
|
||||
// 2. Any remaining items in updatedMap are NEW characters → add them
|
||||
const newCharacters = Array.from(updatedMap.values());
|
||||
|
||||
return { characters: [...merged, ...newCharacters] };
|
||||
});
|
||||
}, []);
|
||||
|
||||
const characterAdded = useCallback((value: CommandCharacterAdded) => {
|
||||
|
||||
@@ -38,6 +38,8 @@ export const useMapInit = () => {
|
||||
user_characters,
|
||||
present_characters,
|
||||
hubs,
|
||||
options,
|
||||
user_permissions,
|
||||
}: CommandInit) => {
|
||||
const { update } = ref.current;
|
||||
|
||||
@@ -63,6 +65,14 @@ export const useMapInit = () => {
|
||||
updateData.hubs = hubs;
|
||||
}
|
||||
|
||||
if (options) {
|
||||
updateData.options = options;
|
||||
}
|
||||
|
||||
if (options) {
|
||||
updateData.userPermissions = user_permissions;
|
||||
}
|
||||
|
||||
if (systems) {
|
||||
updateData.systems = systems;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,13 @@ import { DEFAULT_WIDGETS } from '@/hooks/Mapper/components/mapInterface/constant
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export const MapInterface = () => {
|
||||
// const [items, setItems] = useState<WindowProps[]>(restoreWindowsFromLS);
|
||||
const { windowsSettings, updateWidgetSettings } = useMapRootState();
|
||||
|
||||
const items = useMemo(() => {
|
||||
if (Object.keys(windowsSettings).length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return windowsSettings.windows
|
||||
.map(x => {
|
||||
const content = DEFAULT_WIDGETS.find(y => y.id === x.id)?.content;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MarkdownComment } from '@/hooks/Mapper/components/mapInterface/components/Comments/components';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { CommentType } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CommentType } from '@/hooks/Mapper/types';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
export interface CommentsProps {}
|
||||
|
||||
@@ -14,7 +14,9 @@ export const Comments = ({}: CommentsProps) => {
|
||||
comments: { loadComments, comments, lastUpdateKey },
|
||||
} = useMapRootState();
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
const systemId = useMemo(() => {
|
||||
return +selectedSystems[0];
|
||||
}, [selectedSystems]);
|
||||
|
||||
const ref = useRef({ loadComments, systemId });
|
||||
ref.current = { loadComments, systemId };
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import classes from './MarkdownComment.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
InfoDrawer,
|
||||
@@ -49,7 +48,11 @@ export const MarkdownComment = ({ text, time, characterEveId, id }: MarkdownComm
|
||||
<>
|
||||
<InfoDrawer
|
||||
labelClassName="mb-[3px]"
|
||||
className={clsx(classes.MarkdownCommentRoot, 'p-1 bg-stone-700/20 ')}
|
||||
className={clsx(
|
||||
'p-1 bg-stone-700/20',
|
||||
'text-[12px] leading-[1.2] text-stone-300 break-words',
|
||||
'bg-gradient-to-r from-stone-600/40 via-stone-600/10 to-stone-600/0',
|
||||
)}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
title={
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
.CERoot {
|
||||
@apply border border-stone-400/30 rounded-[2px];
|
||||
|
||||
:global {
|
||||
.cm-content {
|
||||
@apply bg-stone-600/40;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,10 @@ import clsx from 'clsx';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { MarkdownEditor } from '@/hooks/Mapper/components/mapInterface/components/MarkdownEditor';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import classes from './CommentsEditor.module.scss';
|
||||
|
||||
export interface CommentsEditorProps {}
|
||||
|
||||
@@ -18,7 +19,9 @@ export const CommentsEditor = ({}: CommentsEditorProps) => {
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
const systemId = useMemo(() => {
|
||||
return +selectedSystems[0];
|
||||
}, [selectedSystems]);
|
||||
|
||||
const ref = useRef({ outCommand, systemId, textVal });
|
||||
ref.current = { outCommand, systemId, textVal };
|
||||
@@ -48,6 +51,7 @@ export const CommentsEditor = ({}: CommentsEditorProps) => {
|
||||
|
||||
return (
|
||||
<MarkdownEditor
|
||||
className={classes.CERoot}
|
||||
value={textVal}
|
||||
onChange={setTextVal}
|
||||
overlayContent={
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
.CERoot {
|
||||
@apply border border-stone-400/30 rounded-[2px];
|
||||
@apply border border-stone-500/30 rounded-[2px];
|
||||
|
||||
:global {
|
||||
.cm-content {
|
||||
@apply bg-stone-600/40;
|
||||
@apply bg-stone-950/70;
|
||||
}
|
||||
|
||||
.cm-scroller {
|
||||
|
||||
@@ -44,9 +44,17 @@ export interface MarkdownEditorProps {
|
||||
overlayContent?: ReactNode;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
height?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const MarkdownEditor = ({ value, onChange, overlayContent }: MarkdownEditorProps) => {
|
||||
export const MarkdownEditor = ({
|
||||
value,
|
||||
onChange,
|
||||
overlayContent,
|
||||
height = '70px',
|
||||
className,
|
||||
}: MarkdownEditorProps) => {
|
||||
const [hasShift, setHasShift] = useState(false);
|
||||
|
||||
const refData = useRef({ onChange });
|
||||
@@ -66,9 +74,9 @@ export const MarkdownEditor = ({ value, onChange, overlayContent }: MarkdownEdit
|
||||
<div className={clsx(classes.MarkdownEditor, 'relative')}>
|
||||
<CodeMirror
|
||||
value={value}
|
||||
height="70px"
|
||||
height={height}
|
||||
extensions={CODE_MIRROR_EXTENSIONS}
|
||||
className={classes.CERoot}
|
||||
className={clsx(classes.CERoot, className)}
|
||||
theme={oneDark}
|
||||
onChange={handleOnChange}
|
||||
placeholder="Start typing..."
|
||||
|
||||
@@ -9,11 +9,12 @@ import {
|
||||
} from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
|
||||
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
|
||||
import { SETTINGS_KEYS, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
|
||||
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { SETTINGS_KEYS, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
|
||||
import { useSystemSignaturesData } from '../../widgets/SystemSignatures/hooks/useSystemSignaturesData';
|
||||
|
||||
const K162_SIGNATURE_TYPE = WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME['K162'].shortName;
|
||||
|
||||
@@ -135,6 +136,11 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
||||
[data, setVisible],
|
||||
);
|
||||
|
||||
const { signatures } = useSystemSignaturesData({
|
||||
systemId: `${data.solar_system_source}`,
|
||||
settings: LINK_SIGNTATURE_SETTINGS,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!targetSystemDynamicInfo) {
|
||||
handleHide();
|
||||
@@ -152,10 +158,12 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
||||
>
|
||||
<SystemSignaturesContent
|
||||
systemId={`${data.solar_system_source}`}
|
||||
hideLinkedSignatures
|
||||
signatures={signatures}
|
||||
hasUnsupportedLanguage={false}
|
||||
settings={LINK_SIGNTATURE_SETTINGS}
|
||||
hideLinkedSignatures
|
||||
selectable
|
||||
onSelect={handleSelect}
|
||||
selectable={true}
|
||||
filterSignature={filterSignature}
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
@@ -8,8 +8,8 @@ import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { IconField } from 'primereact/iconfield';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { InputTextarea } from 'primereact/inputtextarea';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { MarkdownEditor } from '@/hooks/Mapper/components/mapInterface/components/MarkdownEditor';
|
||||
|
||||
interface SystemSettingsDialog {
|
||||
systemId: string;
|
||||
@@ -214,13 +214,9 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="username">Description</label>
|
||||
<InputTextarea
|
||||
autoResize
|
||||
rows={5}
|
||||
cols={30}
|
||||
value={description}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
/>
|
||||
<div className="h-[200px]">
|
||||
<MarkdownEditor value={description} onChange={e => setDescription(e)} height="180px" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { SystemSettingsDialog } from '@/hooks/Mapper/components/mapInterface/components/SystemSettingsDialog/SystemSettingsDialog.tsx';
|
||||
import { LayoutEventBlocker, SystemView, TooltipPosition, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { SystemInfoContent } from './SystemInfoContent';
|
||||
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { SystemSettingsDialog } from '@/hooks/Mapper/components/mapInterface/components/SystemSettingsDialog/SystemSettingsDialog.tsx';
|
||||
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons';
|
||||
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||
import { SystemInfoContent } from './SystemInfoContent';
|
||||
|
||||
export const SystemInfo = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
@@ -48,7 +48,7 @@ export const SystemInfo = () => {
|
||||
</div>
|
||||
|
||||
<LayoutEventBlocker className="flex gap-1 items-center">
|
||||
<a href={`https://zkillboard.com/system/${systemId}`} rel="noreferrer" target="_blank">
|
||||
<a href={`https://zkillboard.com/system/${systemId}/`} rel="noreferrer" target="_blank">
|
||||
<img src={ZKB_ICON} width="14" height="14" className="external-icon" />
|
||||
</a>
|
||||
<a href={`http://anoik.is/systems/${solarSystemName}`} rel="noreferrer" target="_blank">
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||
import { useMemo } from 'react';
|
||||
import { getSystemById, sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||
import { InfoDrawer, WHClassView, WHEffectView } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { InfoDrawer, MarkdownTextViewer, WHClassView, WHEffectView } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||
|
||||
interface SystemInfoContentProps {
|
||||
@@ -51,7 +51,7 @@ export const SystemInfoContent = ({ systemId }: SystemInfoContentProps) => {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="break-words">{description}</div>
|
||||
<MarkdownTextViewer>{description}</MarkdownTextViewer>
|
||||
</InfoDrawer>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,123 +1,16 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { SETTINGS_KEYS, SIGNATURE_WINDOW_ID, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks/useHotkey';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useSignatureUndo } from './hooks/useSignatureUndo';
|
||||
import { useSystemSignaturesData } from './hooks/useSystemSignaturesData';
|
||||
import { SystemSignaturesHeader } from './SystemSignatureHeader';
|
||||
import { SystemSignaturesContent } from './SystemSignaturesContent';
|
||||
import { SystemSignatureSettingsDialog } from './SystemSignatureSettingsDialog';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { SystemSignaturesHeader } from './SystemSignatureHeader';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks/useHotkey';
|
||||
import { getDeletionTimeoutMs } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
||||
import { SETTINGS_KEYS, SIGNATURE_WINDOW_ID, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
|
||||
|
||||
/**
|
||||
* Custom hook for managing pending signature deletions and undo countdown.
|
||||
*/
|
||||
function useSignatureUndo(
|
||||
systemId: string | undefined,
|
||||
settings: SignatureSettingsType,
|
||||
outCommand: OutCommandHandler,
|
||||
) {
|
||||
const [countdown, setCountdown] = useState<number>(0);
|
||||
const [pendingIds, setPendingIds] = useState<Set<string>>(new Set());
|
||||
const [deletedSignatures, setDeletedSignatures] = useState<ExtendedSystemSignature[]>([]);
|
||||
const intervalRef = useRef<number | null>(null);
|
||||
|
||||
const addDeleted = useCallback((signatures: ExtendedSystemSignature[]) => {
|
||||
const newIds = signatures.map(sig => sig.eve_id);
|
||||
setPendingIds(prev => {
|
||||
const next = new Set(prev);
|
||||
newIds.forEach(id => next.add(id));
|
||||
return next;
|
||||
});
|
||||
setDeletedSignatures(prev => [...prev, ...signatures]);
|
||||
}, []);
|
||||
|
||||
// Clear deleted signatures when system changes
|
||||
useEffect(() => {
|
||||
if (systemId) {
|
||||
setDeletedSignatures([]);
|
||||
setPendingIds(new Set());
|
||||
setCountdown(0);
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}
|
||||
}, [systemId]);
|
||||
|
||||
// kick off or clear countdown whenever pendingIds changes
|
||||
useEffect(() => {
|
||||
// clear any existing timer
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
|
||||
if (pendingIds.size === 0) {
|
||||
setCountdown(0);
|
||||
setDeletedSignatures([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// determine timeout from settings
|
||||
const timeoutMs = getDeletionTimeoutMs(settings);
|
||||
|
||||
// Ensure a minimum of 1 second for immediate deletion so the UI shows
|
||||
const effectiveTimeoutMs = timeoutMs === 0 ? 1000 : timeoutMs;
|
||||
|
||||
setCountdown(Math.ceil(effectiveTimeoutMs / 1000));
|
||||
|
||||
// start new interval
|
||||
intervalRef.current = window.setInterval(() => {
|
||||
setCountdown(prev => {
|
||||
if (prev <= 1) {
|
||||
clearInterval(intervalRef.current!);
|
||||
intervalRef.current = null;
|
||||
setPendingIds(new Set());
|
||||
setDeletedSignatures([]);
|
||||
return 0;
|
||||
}
|
||||
return prev - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [pendingIds, settings]);
|
||||
|
||||
// undo handler
|
||||
const handleUndo = useCallback(async () => {
|
||||
if (!systemId || pendingIds.size === 0) return;
|
||||
await outCommand({
|
||||
type: OutCommand.undoDeleteSignatures,
|
||||
data: { system_id: systemId, eve_ids: Array.from(pendingIds) },
|
||||
});
|
||||
setPendingIds(new Set());
|
||||
setDeletedSignatures([]);
|
||||
setCountdown(0);
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}, [systemId, pendingIds, outCommand]);
|
||||
|
||||
return {
|
||||
pendingIds,
|
||||
countdown,
|
||||
deletedSignatures,
|
||||
addDeleted,
|
||||
handleUndo,
|
||||
};
|
||||
}
|
||||
|
||||
export const SystemSignatures = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [sigCount, setSigCount] = useState(0);
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
|
||||
const {
|
||||
data: { selectedSystems },
|
||||
@@ -127,31 +20,6 @@ export const SystemSignatures = () => {
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
const isSystemSelected = useMemo(() => selectedSystems.length === 1, [selectedSystems.length]);
|
||||
const { pendingIds, countdown, deletedSignatures, addDeleted, handleUndo } = useSignatureUndo(
|
||||
systemId,
|
||||
settingsSignatures,
|
||||
outCommand,
|
||||
);
|
||||
|
||||
useHotkey(true, ['z', 'Z'], (event: KeyboardEvent) => {
|
||||
if (pendingIds.size > 0 && countdown > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleUndo();
|
||||
}
|
||||
});
|
||||
|
||||
const handleCountChange = useCallback((count: number) => {
|
||||
setSigCount(count);
|
||||
}, []);
|
||||
|
||||
const handleSettingsSave = useCallback(
|
||||
(newSettings: SignatureSettingsType) => {
|
||||
settingsSignaturesUpdate(newSettings);
|
||||
setVisible(false);
|
||||
},
|
||||
[settingsSignaturesUpdate],
|
||||
);
|
||||
|
||||
const handleLazyDeleteToggle = useCallback(
|
||||
(value: boolean) => {
|
||||
@@ -163,7 +31,42 @@ export const SystemSignatures = () => {
|
||||
[settingsSignaturesUpdate],
|
||||
);
|
||||
|
||||
const openSettings = useCallback(() => setVisible(true), []);
|
||||
const {
|
||||
signatures,
|
||||
selectedSignatures,
|
||||
setSelectedSignatures,
|
||||
handleDeleteSelected,
|
||||
handleSelectAll,
|
||||
handlePaste,
|
||||
hasUnsupportedLanguage,
|
||||
} = useSystemSignaturesData({
|
||||
systemId,
|
||||
settings: settingsSignatures,
|
||||
onLazyDeleteChange: handleLazyDeleteToggle,
|
||||
});
|
||||
|
||||
const sigCount = useMemo(() => signatures.length, [signatures]);
|
||||
const deletedSignatures = useMemo(() => signatures.filter(s => s.deleted), [signatures]);
|
||||
|
||||
const { countdown, handleUndo } = useSignatureUndo(systemId, settingsSignatures, deletedSignatures, outCommand);
|
||||
|
||||
useHotkey(true, ['z', 'Z'], (event: KeyboardEvent) => {
|
||||
if (deletedSignatures.length > 0 && countdown > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleUndo();
|
||||
}
|
||||
});
|
||||
|
||||
const handleSettingsSave = useCallback(
|
||||
(newSettings: SignatureSettingsType) => {
|
||||
settingsSignaturesUpdate(newSettings);
|
||||
setShowSettings(false);
|
||||
},
|
||||
[settingsSignaturesUpdate],
|
||||
);
|
||||
|
||||
const openSettings = useCallback(() => setShowSettings(true), []);
|
||||
|
||||
return (
|
||||
<Widget
|
||||
@@ -171,7 +74,7 @@ export const SystemSignatures = () => {
|
||||
<SystemSignaturesHeader
|
||||
sigCount={sigCount}
|
||||
lazyDeleteValue={settingsSignatures[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean}
|
||||
pendingCount={pendingIds.size}
|
||||
pendingCount={deletedSignatures.length}
|
||||
undoCountdown={countdown}
|
||||
onLazyDeleteChange={handleLazyDeleteToggle}
|
||||
onUndoClick={handleUndo}
|
||||
@@ -187,18 +90,21 @@ export const SystemSignatures = () => {
|
||||
) : (
|
||||
<SystemSignaturesContent
|
||||
systemId={systemId}
|
||||
signatures={signatures}
|
||||
selectedSignatures={selectedSignatures}
|
||||
onSelectSignatures={setSelectedSignatures}
|
||||
onDeleteSelected={handleDeleteSelected}
|
||||
onSelectAll={handleSelectAll}
|
||||
onPaste={handlePaste}
|
||||
hasUnsupportedLanguage={hasUnsupportedLanguage}
|
||||
settings={settingsSignatures}
|
||||
deletedSignatures={deletedSignatures}
|
||||
onLazyDeleteChange={handleLazyDeleteToggle}
|
||||
onCountChange={handleCountChange}
|
||||
onSignatureDeleted={addDeleted}
|
||||
/>
|
||||
)}
|
||||
|
||||
{visible && (
|
||||
{showSettings && (
|
||||
<SystemSignatureSettingsDialog
|
||||
settings={settingsSignatures}
|
||||
onCancel={() => setVisible(false)}
|
||||
onCancel={() => setShowSettings(false)}
|
||||
onSave={handleSettingsSave}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -33,34 +33,39 @@ import { useClipboard, useHotkey } from '@/hooks/Mapper/hooks';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { getSignatureRowClass } from '../helpers/rowStyles';
|
||||
import { useSystemSignaturesData } from '../hooks/useSystemSignaturesData';
|
||||
|
||||
const renderColIcon = (sig: SystemSignature) => renderIcon(sig);
|
||||
|
||||
interface SystemSignaturesContentProps {
|
||||
systemId: string;
|
||||
signatures: ExtendedSystemSignature[];
|
||||
selectedSignatures?: ExtendedSystemSignature[];
|
||||
onSelectSignatures?: (s: ExtendedSystemSignature[]) => void;
|
||||
onDeleteSelected?: () => Promise<void>;
|
||||
onSelectAll?: () => void;
|
||||
onPaste?: (clipboardString: string) => void;
|
||||
settings: SignatureSettingsType;
|
||||
hideLinkedSignatures?: boolean;
|
||||
hasUnsupportedLanguage?: boolean;
|
||||
selectable?: boolean;
|
||||
onSelect?: (signature: SystemSignature) => void;
|
||||
onLazyDeleteChange?: (value: boolean) => void;
|
||||
onCountChange?: (count: number) => void;
|
||||
filterSignature?: (signature: SystemSignature) => boolean;
|
||||
onSignatureDeleted?: (deletedSignatures: ExtendedSystemSignature[]) => void;
|
||||
deletedSignatures?: ExtendedSystemSignature[];
|
||||
}
|
||||
|
||||
export const SystemSignaturesContent = ({
|
||||
systemId,
|
||||
signatures,
|
||||
selectedSignatures,
|
||||
onSelectSignatures,
|
||||
onDeleteSelected,
|
||||
onSelectAll,
|
||||
onPaste,
|
||||
settings,
|
||||
hideLinkedSignatures,
|
||||
hasUnsupportedLanguage,
|
||||
selectable,
|
||||
onSelect,
|
||||
onLazyDeleteChange,
|
||||
onCountChange,
|
||||
filterSignature,
|
||||
onSignatureDeleted,
|
||||
deletedSignatures = [],
|
||||
}: SystemSignaturesContentProps) => {
|
||||
const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState<SystemSignature | null>(null);
|
||||
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
|
||||
@@ -79,32 +84,18 @@ export const SystemSignaturesContent = ({
|
||||
|
||||
const { clipboardContent, setClipboardContent } = useClipboard();
|
||||
|
||||
const {
|
||||
signatures,
|
||||
selectedSignatures,
|
||||
setSelectedSignatures,
|
||||
handleDeleteSelected,
|
||||
handleSelectAll,
|
||||
handlePaste,
|
||||
hasUnsupportedLanguage,
|
||||
} = useSystemSignaturesData({
|
||||
systemId,
|
||||
settings,
|
||||
onCountChange,
|
||||
onLazyDeleteChange,
|
||||
onSignatureDeleted,
|
||||
});
|
||||
const deletedSignatures = useMemo(() => signatures.filter(s => s.deleted), [signatures]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectable) return;
|
||||
if (!clipboardContent?.text) return;
|
||||
|
||||
handlePaste(clipboardContent.text);
|
||||
onPaste?.(clipboardContent.text);
|
||||
|
||||
setClipboardContent(null);
|
||||
}, [selectable, clipboardContent, handlePaste, setClipboardContent]);
|
||||
}, [selectable, clipboardContent, onPaste, setClipboardContent]);
|
||||
|
||||
useHotkey(true, ['a'], handleSelectAll);
|
||||
useHotkey(true, ['a'], () => onSelectAll?.());
|
||||
|
||||
useHotkey(false, ['Backspace', 'Delete'], (event: KeyboardEvent) => {
|
||||
const targetWindow = (event.target as HTMLHtmlElement)?.closest(`[data-window-id="${SIGNATURE_WINDOW_ID}"]`);
|
||||
@@ -117,7 +108,7 @@ export const SystemSignaturesContent = ({
|
||||
event.stopPropagation();
|
||||
|
||||
// Delete key should always immediately delete, never show pending deletions
|
||||
handleDeleteSelected();
|
||||
onDeleteSelected?.();
|
||||
});
|
||||
|
||||
const handleResize = useCallback(() => {
|
||||
@@ -152,9 +143,9 @@ export const SystemSignaturesContent = ({
|
||||
|
||||
selectable
|
||||
? onSelect?.(selectableSignatures[0])
|
||||
: setSelectedSignatures(selectableSignatures as ExtendedSystemSignature[]);
|
||||
: onSelectSignatures?.(selectableSignatures as ExtendedSystemSignature[]);
|
||||
},
|
||||
[onSelect, selectable, setSelectedSignatures, deletedSignatures],
|
||||
[onSelect, selectable, onSelectSignatures, deletedSignatures],
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -177,9 +168,6 @@ export const SystemSignaturesContent = ({
|
||||
);
|
||||
|
||||
const filteredSignatures = useMemo<ExtendedSystemSignature[]>(() => {
|
||||
// Get the set of deleted signature IDs for quick lookup
|
||||
const deletedIds = new Set(deletedSignatures.map(sig => sig.eve_id));
|
||||
|
||||
// Common filter function
|
||||
const shouldShowSignature = (sig: ExtendedSystemSignature): boolean => {
|
||||
if (filterSignature && !filterSignature(sig)) {
|
||||
@@ -213,24 +201,8 @@ export const SystemSignaturesContent = ({
|
||||
return settings[sig.kind] as boolean;
|
||||
};
|
||||
|
||||
// Filter active signatures, excluding any that are in the deleted list
|
||||
const activeSignatures = signatures.filter(sig => {
|
||||
// Skip if this signature is in the deleted list
|
||||
if (deletedIds.has(sig.eve_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return shouldShowSignature(sig);
|
||||
});
|
||||
|
||||
// Add deleted signatures with pending deletion flag, applying the same filters
|
||||
const deletedWithPendingFlag = deletedSignatures.filter(shouldShowSignature).map(sig => ({
|
||||
...sig,
|
||||
pendingDeletion: true,
|
||||
}));
|
||||
|
||||
return [...activeSignatures, ...deletedWithPendingFlag];
|
||||
}, [signatures, hideLinkedSignatures, settings, filterSignature, deletedSignatures]);
|
||||
return signatures.filter(sig => shouldShowSignature(sig));
|
||||
}, [signatures, hideLinkedSignatures, settings, filterSignature]);
|
||||
|
||||
const onRowMouseEnter = useCallback((e: DataTableRowMouseEvent) => {
|
||||
setHoveredSignature(e.data as SystemSignature);
|
||||
@@ -253,20 +225,18 @@ export const SystemSignaturesContent = ({
|
||||
|
||||
return getSignatureRowClass(
|
||||
rowData as ExtendedSystemSignature,
|
||||
refVars.current.selectedSignatures,
|
||||
refVars.current.selectedSignatures || [],
|
||||
refVars.current.settings[SETTINGS_KEYS.COLOR_BY_TYPE] as boolean,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleSortSettings = useCallback(
|
||||
(e: DataTableStateEvent) =>
|
||||
refVars.current.settingsSignaturesUpdate({
|
||||
...refVars.current.settingsSignatures,
|
||||
[SETTINGS_KEYS.SORT_FIELD]: e.sortField,
|
||||
[SETTINGS_KEYS.SORT_ORDER]: e.sortOrder,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const handleSortSettings = useCallback((e: DataTableStateEvent) => {
|
||||
refVars.current.settingsSignaturesUpdate({
|
||||
...refVars.current.settingsSignatures,
|
||||
[SETTINGS_KEYS.SORT_FIELD]: e.sortField,
|
||||
[SETTINGS_KEYS.SORT_ORDER]: e.sortOrder,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={tableRef} className="h-full">
|
||||
@@ -287,7 +257,7 @@ export const SystemSignaturesContent = ({
|
||||
value={filteredSignatures}
|
||||
size="small"
|
||||
selectionMode="multiple"
|
||||
selection={selectedSignatures}
|
||||
selection={selectedSignatures || []}
|
||||
metaKeySelection
|
||||
onSelectionChange={handleSelectSignatures}
|
||||
dataKey="eve_id"
|
||||
@@ -336,6 +306,8 @@ export const SystemSignaturesContent = ({
|
||||
style={{ maxWidth: nameColumnWidth }}
|
||||
hidden={isCompact || isMedium}
|
||||
body={renderInfoColumn}
|
||||
sortable
|
||||
sortField="name"
|
||||
/>
|
||||
{showDescriptionColumn && (
|
||||
<Column
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import { ExtendedSystemSignature, SignatureGroup } from '@/hooks/Mapper/types';
|
||||
import clsx from 'clsx';
|
||||
import { getRowBackgroundColor } from './getRowBackgroundColor';
|
||||
import classes from './rowStyles.module.scss';
|
||||
|
||||
@@ -20,7 +20,7 @@ export function getSignatureRowClass(
|
||||
return clsx([...baseCls, 'bg-violet-400/40 hover:bg-violet-300/40']);
|
||||
}
|
||||
|
||||
if (row.pendingDeletion) {
|
||||
if (row.deleted) {
|
||||
return clsx([...baseCls, 'bg-red-400/40 hover:bg-red-400/50']);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
||||
import { SignatureSettingsType } from '@/hooks/Mapper/constants/signatures.ts';
|
||||
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
export interface UseSystemSignaturesDataProps {
|
||||
systemId: string;
|
||||
settings: SignatureSettingsType;
|
||||
hideLinkedSignatures?: boolean;
|
||||
onCountChange?: (count: number) => void;
|
||||
onPendingChange?: (
|
||||
pending: React.MutableRefObject<Record<string, ExtendedSystemSignature>>,
|
||||
undo: () => void,
|
||||
) => void;
|
||||
onLazyDeleteChange?: (value: boolean) => void;
|
||||
deletionTiming?: number;
|
||||
}
|
||||
|
||||
export interface UseFetchingParams {
|
||||
systemId: string;
|
||||
settings: SignatureSettingsType;
|
||||
signaturesRef: React.MutableRefObject<ExtendedSystemSignature[]>;
|
||||
setSignatures: React.Dispatch<React.SetStateAction<ExtendedSystemSignature[]>>;
|
||||
pendingDeletionMapRef: React.MutableRefObject<Record<string, ExtendedSystemSignature>>;
|
||||
}
|
||||
|
||||
export interface UsePendingDeletionParams {
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { prepareUpdatePayload } from '../helpers';
|
||||
import { UsePendingDeletionParams } from './types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
export function usePendingDeletions({
|
||||
systemId,
|
||||
setSignatures,
|
||||
onPendingChange,
|
||||
}: Omit<UsePendingDeletionParams, 'deletionTiming'>) {
|
||||
const { outCommand } = useMapRootState();
|
||||
const pendingDeletionMapRef = useRef<Record<string, ExtendedSystemSignature>>({});
|
||||
|
||||
const processRemovedSignatures = useCallback(
|
||||
async (
|
||||
removed: ExtendedSystemSignature[],
|
||||
added: ExtendedSystemSignature[],
|
||||
updated: ExtendedSystemSignature[],
|
||||
) => {
|
||||
if (!removed.length) return;
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: prepareUpdatePayload(systemId, added, updated, removed),
|
||||
});
|
||||
},
|
||||
[systemId, outCommand],
|
||||
);
|
||||
|
||||
const clearPendingDeletions = useCallback(() => {
|
||||
pendingDeletionMapRef.current = {};
|
||||
setSignatures(prev => prev.map(x => (x.pendingDeletion ? { ...x, pendingDeletion: false } : x)));
|
||||
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
pendingDeletionMapRef,
|
||||
processRemovedSignatures,
|
||||
clearPendingDeletions,
|
||||
};
|
||||
}
|
||||
@@ -1,21 +1,27 @@
|
||||
import { useCallback } from 'react';
|
||||
import { SETTINGS_KEYS } from '@/hooks/Mapper/constants/signatures';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { ExtendedSystemSignature, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { prepareUpdatePayload, getActualSigs, mergeLocalPending } from '../helpers';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { getDeletionTimeoutMs } from '../constants';
|
||||
import { getActualSigs, prepareUpdatePayload } from '../helpers';
|
||||
import { UseFetchingParams } from './types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export const useSignatureFetching = ({
|
||||
systemId,
|
||||
signaturesRef,
|
||||
setSignatures,
|
||||
pendingDeletionMapRef,
|
||||
}: UseFetchingParams) => {
|
||||
export const useSignatureFetching = ({ systemId, settings, signaturesRef, setSignatures }: UseFetchingParams) => {
|
||||
const {
|
||||
data: { characters },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const deleteTimeout = useMemo(() => {
|
||||
const lazyDelete = settings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean;
|
||||
if (!lazyDelete) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return getDeletionTimeoutMs(settings);
|
||||
}, [settings]);
|
||||
|
||||
const handleGetSignatures = useCallback(async () => {
|
||||
if (!systemId) {
|
||||
setSignatures([]);
|
||||
@@ -32,24 +38,23 @@ export const useSignatureFetching = ({
|
||||
character_name: characters.find(c => c.eve_id === s.character_eve_id)?.name,
|
||||
})) as ExtendedSystemSignature[];
|
||||
|
||||
setSignatures(() => mergeLocalPending(pendingDeletionMapRef, extended));
|
||||
setSignatures(() => extended);
|
||||
}, [characters, systemId, outCommand]);
|
||||
|
||||
const handleUpdateSignatures = useCallback(
|
||||
async (newList: ExtendedSystemSignature[], updateOnly: boolean, skipUpdateUntouched?: boolean) => {
|
||||
const { added, updated, removed } = getActualSigs(
|
||||
signaturesRef.current,
|
||||
newList,
|
||||
updateOnly,
|
||||
skipUpdateUntouched,
|
||||
);
|
||||
const actualSigs = getActualSigs(signaturesRef.current, newList, updateOnly, skipUpdateUntouched);
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: prepareUpdatePayload(systemId, added, updated, removed),
|
||||
});
|
||||
const { added, updated, removed } = actualSigs;
|
||||
|
||||
if (updated.length !== 0 || added.length !== 0 || removed.length !== 0) {
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: { ...prepareUpdatePayload(systemId, added, updated, removed), deleteTimeout },
|
||||
});
|
||||
}
|
||||
},
|
||||
[systemId, outCommand, signaturesRef],
|
||||
[systemId, deleteTimeout, outCommand, signaturesRef],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
|
||||
import { ExtendedSystemSignature, OutCommandHandler } from '@/hooks/Mapper/types';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { getDeletionTimeoutMs } from '../constants';
|
||||
|
||||
/**
|
||||
* Custom hook for managing pending signature deletions and undo countdown.
|
||||
*/
|
||||
export function useSignatureUndo(
|
||||
systemId: string | undefined,
|
||||
settings: SignatureSettingsType,
|
||||
deletedSignatures: ExtendedSystemSignature[],
|
||||
outCommand: OutCommandHandler,
|
||||
) {
|
||||
const [countdown, setCountdown] = useState<number>(0);
|
||||
const intervalRef = useRef<number | null>(null);
|
||||
|
||||
// Clear deleted signatures when system changes
|
||||
useEffect(() => {
|
||||
if (systemId) {
|
||||
setCountdown(0);
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}
|
||||
}, [systemId]);
|
||||
|
||||
// kick off or clear countdown whenever pendingIds changes
|
||||
useEffect(() => {
|
||||
// clear any existing timer
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
|
||||
if (deletedSignatures.length === 0) {
|
||||
setCountdown(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// determine timeout from settings
|
||||
const timeoutMs = getDeletionTimeoutMs(settings);
|
||||
|
||||
// Ensure a minimum of 1 second for immediate deletion so the UI shows
|
||||
const effectiveTimeoutMs = timeoutMs === 0 ? 1000 : timeoutMs;
|
||||
|
||||
setCountdown(Math.ceil(effectiveTimeoutMs / 1000));
|
||||
|
||||
// start new interval
|
||||
intervalRef.current = window.setInterval(() => {
|
||||
setCountdown(prev => {
|
||||
if (prev <= 1) {
|
||||
clearInterval(intervalRef.current!);
|
||||
intervalRef.current = null;
|
||||
return 0;
|
||||
}
|
||||
return prev - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [deletedSignatures, settings]);
|
||||
|
||||
// undo handler
|
||||
const handleUndo = useCallback(async () => {
|
||||
if (!systemId || deletedSignatures.length === 0) return;
|
||||
await outCommand({
|
||||
type: OutCommand.undoDeleteSignatures,
|
||||
data: { system_id: systemId, eve_ids: deletedSignatures.map(s => s.eve_id) },
|
||||
});
|
||||
setCountdown(0);
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}, [systemId, deletedSignatures, outCommand]);
|
||||
|
||||
return {
|
||||
countdown,
|
||||
handleUndo,
|
||||
};
|
||||
}
|
||||
@@ -1,44 +1,29 @@
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { parseSignatures } from '@/hooks/Mapper/helpers';
|
||||
import { Commands, ExtendedSystemSignature, SignatureKind } from '@/hooks/Mapper/types';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import useRefState from 'react-usestateref';
|
||||
|
||||
import { getDeletionTimeoutMs } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { getActualSigs } from '../helpers';
|
||||
import { UseSystemSignaturesDataProps } from './types';
|
||||
import { usePendingDeletions } from './usePendingDeletions';
|
||||
import { useSignatureFetching } from './useSignatureFetching';
|
||||
import { SETTINGS_KEYS } from '@/hooks/Mapper/constants/signatures.ts';
|
||||
import { UseSystemSignaturesDataProps } from './types';
|
||||
import { useSignatureFetching } from './useSignatureFetching';
|
||||
|
||||
export const useSystemSignaturesData = ({
|
||||
systemId,
|
||||
settings,
|
||||
onCountChange,
|
||||
onPendingChange,
|
||||
onLazyDeleteChange,
|
||||
onSignatureDeleted,
|
||||
}: Omit<UseSystemSignaturesDataProps, 'deletionTiming'> & {
|
||||
onSignatureDeleted?: (deletedSignatures: ExtendedSystemSignature[]) => void;
|
||||
}) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
const [signatures, setSignatures, signaturesRef] = useRefState<ExtendedSystemSignature[]>([]);
|
||||
const [selectedSignatures, setSelectedSignatures] = useState<ExtendedSystemSignature[]>([]);
|
||||
const [hasUnsupportedLanguage, setHasUnsupportedLanguage] = useState<boolean>(false);
|
||||
|
||||
const { pendingDeletionMapRef, processRemovedSignatures, clearPendingDeletions } = usePendingDeletions({
|
||||
systemId,
|
||||
setSignatures,
|
||||
onPendingChange,
|
||||
});
|
||||
|
||||
const { handleGetSignatures, handleUpdateSignatures } = useSignatureFetching({
|
||||
systemId,
|
||||
settings,
|
||||
signaturesRef,
|
||||
setSignatures,
|
||||
pendingDeletionMapRef,
|
||||
});
|
||||
|
||||
const handlePaste = useCallback(
|
||||
@@ -67,40 +52,14 @@ export const useSystemSignaturesData = ({
|
||||
setHasUnsupportedLanguage(false);
|
||||
}
|
||||
|
||||
const currentNonPending = lazyDeleteValue
|
||||
? signaturesRef.current.filter(sig => !sig.pendingDeletion)
|
||||
: signaturesRef.current.filter(sig => !sig.pendingDeletion || !sig.pendingAddition);
|
||||
|
||||
const { added, updated, removed } = getActualSigs(currentNonPending, incomingSignatures, !lazyDeleteValue, false);
|
||||
|
||||
if (removed.length > 0) {
|
||||
await processRemovedSignatures(removed, added, updated);
|
||||
|
||||
// Show pending deletions if lazy deletion is enabled
|
||||
// The deletion timing controls how long the countdown lasts, not whether lazy delete is active
|
||||
if (onSignatureDeleted && lazyDeleteValue) {
|
||||
onSignatureDeleted(removed);
|
||||
}
|
||||
}
|
||||
|
||||
if (updated.length !== 0 || added.length !== 0) {
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
added,
|
||||
updated,
|
||||
removed: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
await handleUpdateSignatures(incomingSignatures, !lazyDeleteValue, false);
|
||||
|
||||
const keepLazy = settings[SETTINGS_KEYS.KEEP_LAZY_DELETE] as boolean;
|
||||
if (lazyDeleteValue && !keepLazy) {
|
||||
onLazyDeleteChange?.(false);
|
||||
}
|
||||
},
|
||||
[settings, signaturesRef, processRemovedSignatures, outCommand, systemId, onLazyDeleteChange, onSignatureDeleted],
|
||||
[settings, handleUpdateSignatures, onLazyDeleteChange],
|
||||
);
|
||||
|
||||
const handleDeleteSelected = useCallback(async () => {
|
||||
@@ -109,23 +68,15 @@ export const useSystemSignaturesData = ({
|
||||
const selectedIds = selectedSignatures.map(s => s.eve_id);
|
||||
const finalList = signatures.filter(s => !selectedIds.includes(s.eve_id));
|
||||
|
||||
// IMPORTANT: Send deletion to server BEFORE updating local state
|
||||
// Otherwise signaturesRef.current will be updated and getActualSigs won't detect removals
|
||||
await handleUpdateSignatures(finalList, false, true);
|
||||
|
||||
// Update local state after server call
|
||||
setSignatures(finalList);
|
||||
setSelectedSignatures([]);
|
||||
}, [handleUpdateSignatures, selectedSignatures, signatures, setSignatures]);
|
||||
|
||||
await handleUpdateSignatures(finalList, false, true);
|
||||
}, [handleUpdateSignatures, selectedSignatures, signatures]);
|
||||
|
||||
const handleSelectAll = useCallback(() => {
|
||||
setSelectedSignatures(signatures);
|
||||
}, [signatures]);
|
||||
|
||||
const undoPending = useCallback(() => {
|
||||
clearPendingDeletions();
|
||||
}, [clearPendingDeletions]);
|
||||
|
||||
useMapEventListener(event => {
|
||||
if (event.name === Commands.signaturesUpdated && String(event.data) === String(systemId)) {
|
||||
handleGetSignatures();
|
||||
@@ -136,18 +87,13 @@ export const useSystemSignaturesData = ({
|
||||
useEffect(() => {
|
||||
if (!systemId) {
|
||||
setSignatures([]);
|
||||
undoPending();
|
||||
return;
|
||||
}
|
||||
handleGetSignatures();
|
||||
}, [systemId]);
|
||||
|
||||
useEffect(() => {
|
||||
onCountChange?.(signatures.length);
|
||||
}, [signatures]);
|
||||
|
||||
return {
|
||||
signatures: signatures.filter(sig => !sig.deleted),
|
||||
signatures,
|
||||
selectedSignatures,
|
||||
setSelectedSignatures,
|
||||
handleDeleteSelected,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { SystemViewStandalone, TooltipPosition, WHClassView } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
|
||||
import { renderK162Type } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { renderName } from './renderName.tsx';
|
||||
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
|
||||
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo.ts';
|
||||
import clsx from 'clsx';
|
||||
import { renderName } from './renderName.tsx';
|
||||
|
||||
export const renderInfoColumn = (row: SystemSignature) => {
|
||||
if (!row.group || row.group === SignatureGroup.Wormhole) {
|
||||
@@ -18,7 +18,9 @@ export const renderInfoColumn = (row: SystemSignature) => {
|
||||
|
||||
return (
|
||||
<div className="flex justify-start items-center gap-[4px]">
|
||||
{customInfo.isEOL && (
|
||||
{row.temporary_name && <span className={clsx('text-[12px]')}>{row.temporary_name}</span>}
|
||||
|
||||
{customInfo.time_status === TimeStatus._1h && (
|
||||
<WdTooltipWrapper offset={5} position={TooltipPosition.top} content="Signature marked as EOL">
|
||||
<div className="pi pi-clock text-fuchsia-400 text-[11px] mr-[2px]"></div>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
@@ -30,10 +30,14 @@ export const SystemStructures: React.FC = () => {
|
||||
|
||||
const processClipboard = useCallback(
|
||||
(text: string) => {
|
||||
if (!systemId) {
|
||||
console.warn('Cannot update structures: no system selected');
|
||||
return;
|
||||
}
|
||||
const updated = processSnippetText(text, structures);
|
||||
handleUpdateStructures(updated);
|
||||
},
|
||||
[structures, handleUpdateStructures],
|
||||
[systemId, structures, handleUpdateStructures],
|
||||
);
|
||||
|
||||
const handlePaste = useCallback(
|
||||
|
||||
@@ -30,9 +30,6 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
|
||||
|
||||
const { outCommand } = useMapRootState();
|
||||
|
||||
const [prevQuery, setPrevQuery] = useState('');
|
||||
const [prevResults, setPrevResults] = useState<{ label: string; value: string }[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (structure) {
|
||||
setEditData(structure);
|
||||
@@ -46,34 +43,24 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
|
||||
// Searching corporation owners via auto-complete
|
||||
const searchOwners = useCallback(
|
||||
async (e: { query: string }) => {
|
||||
const newQuery = e.query.trim();
|
||||
if (!newQuery) {
|
||||
const query = e.query.trim();
|
||||
if (!query) {
|
||||
setOwnerSuggestions([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// If user typed more text but we have partial match in prevResults
|
||||
if (newQuery.startsWith(prevQuery) && prevResults.length > 0) {
|
||||
const filtered = prevResults.filter(item => item.label.toLowerCase().includes(newQuery.toLowerCase()));
|
||||
setOwnerSuggestions(filtered);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO fix it
|
||||
const { results = [] } = await outCommand({
|
||||
type: OutCommand.getCorporationNames,
|
||||
data: { search: newQuery },
|
||||
data: { search: query },
|
||||
});
|
||||
setOwnerSuggestions(results);
|
||||
setPrevQuery(newQuery);
|
||||
setPrevResults(results);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch owners:', err);
|
||||
setOwnerSuggestions([]);
|
||||
}
|
||||
},
|
||||
[prevQuery, prevResults, outCommand],
|
||||
[outCommand],
|
||||
);
|
||||
|
||||
const handleChange = (field: keyof StructureItem, val: string | Date) => {
|
||||
@@ -122,7 +109,6 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
|
||||
// fetch corporation ticker if we have an ownerId
|
||||
if (editData.ownerId) {
|
||||
try {
|
||||
// TODO fix it
|
||||
const { ticker } = await outCommand({
|
||||
type: OutCommand.getCorporationTicker,
|
||||
data: { corp_id: editData.ownerId },
|
||||
|
||||
@@ -56,6 +56,11 @@ export function useSystemStructures({ systemId, outCommand }: UseSystemStructure
|
||||
|
||||
const handleUpdateStructures = useCallback(
|
||||
async (newList: StructureItem[]) => {
|
||||
if (!systemId) {
|
||||
console.warn('Cannot update structures: systemId is undefined');
|
||||
return;
|
||||
}
|
||||
|
||||
const { added, updated, removed } = getActualStructures(structures, newList);
|
||||
|
||||
const sanitizedAdded = added.map(sanitizeIds);
|
||||
|
||||
@@ -31,7 +31,7 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
|
||||
storedSettings: { settingsKills },
|
||||
} = useMapRootState();
|
||||
|
||||
const excludedSystems = useStableValue(settingsKills.excludedSystems);
|
||||
const excludedSystems = useStableValue(settingsKills.excludedSystems ?? []);
|
||||
|
||||
const effectiveSystemIds = useMemo(() => {
|
||||
if (showAllVisible) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { MapContextMenu } from '@/hooks/Mapper/components/mapRootContent/compone
|
||||
import { useSkipContextMenu } from '@/hooks/Mapper/hooks/useSkipContextMenu';
|
||||
import { MapSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings';
|
||||
import { CharacterActivity } from '@/hooks/Mapper/components/mapRootContent/components/CharacterActivity';
|
||||
import { WormholeSignaturesDialog } from '@/hooks/Mapper/components/mapRootContent/components/WormholeSignaturesDialog';
|
||||
import { useCharacterActivityHandlers } from './hooks/useCharacterActivityHandlers';
|
||||
import { TrackingDialog } from '@/hooks/Mapper/components/mapRootContent/components/TrackingDialog';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
@@ -34,6 +35,7 @@ export const MapRootContent = ({}: MapRootContentProps) => {
|
||||
const [showOnTheMap, setShowOnTheMap] = useState(false);
|
||||
const [showMapSettings, setShowMapSettings] = useState(false);
|
||||
const [showTrackingDialog, setShowTrackingDialog] = useState(false);
|
||||
const [showWormholeList, setShowWormholeList] = useState(false);
|
||||
|
||||
/* Important Notice - this solution needs for use one instance of MapInterface */
|
||||
const mapInterface = isReady ? <MapInterface /> : null;
|
||||
@@ -41,6 +43,7 @@ export const MapRootContent = ({}: MapRootContentProps) => {
|
||||
const handleShowOnTheMap = useCallback(() => setShowOnTheMap(true), []);
|
||||
const handleShowMapSettings = useCallback(() => setShowMapSettings(true), []);
|
||||
const handleShowTrackingDialog = useCallback(() => setShowTrackingDialog(true), []);
|
||||
const handleShowWormholesReference = useCallback(() => setShowWormholeList(true), []);
|
||||
|
||||
useMapEventListener(event => {
|
||||
if (event.name === Commands.showTracking) {
|
||||
@@ -65,6 +68,7 @@ export const MapRootContent = ({}: MapRootContentProps) => {
|
||||
onShowOnTheMap={handleShowOnTheMap}
|
||||
onShowMapSettings={handleShowMapSettings}
|
||||
onShowTrackingDialog={handleShowTrackingDialog}
|
||||
onShowWormholesReference={handleShowWormholesReference}
|
||||
additionalContent={<PingsInterface hasLeftOffset />}
|
||||
/>
|
||||
</div>
|
||||
@@ -79,6 +83,7 @@ export const MapRootContent = ({}: MapRootContentProps) => {
|
||||
onShowOnTheMap={handleShowOnTheMap}
|
||||
onShowMapSettings={handleShowMapSettings}
|
||||
onShowTrackingDialog={handleShowTrackingDialog}
|
||||
onShowWormholesReference={handleShowWormholesReference}
|
||||
/>
|
||||
</div>
|
||||
</Topbar>
|
||||
@@ -93,6 +98,7 @@ export const MapRootContent = ({}: MapRootContentProps) => {
|
||||
{showTrackingDialog && (
|
||||
<TrackingDialog visible={showTrackingDialog} onHide={() => setShowTrackingDialog(false)} />
|
||||
)}
|
||||
<WormholeSignaturesDialog visible={showWormholeList} onHide={() => setShowWormholeList(false)} />
|
||||
|
||||
{hasOldSettings && <OldSettingsDialog />}
|
||||
</Layout>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { Menu } from 'primereact/menu';
|
||||
import { MenuItem } from 'primereact/menuitem';
|
||||
import { useState, useCallback, useRef, useMemo } from 'react';
|
||||
import { CharacterActivityContent } from '@/hooks/Mapper/components/mapRootContent/components/CharacterActivity/CharacterActivityContent.tsx';
|
||||
|
||||
interface CharacterActivityProps {
|
||||
@@ -6,17 +9,69 @@ interface CharacterActivityProps {
|
||||
onHide: () => void;
|
||||
}
|
||||
|
||||
const periodOptions = [
|
||||
{ value: 30, label: '30 Days' },
|
||||
{ value: 365, label: '1 Year' },
|
||||
{ value: null, label: 'All Time' },
|
||||
];
|
||||
|
||||
export const CharacterActivity = ({ visible, onHide }: CharacterActivityProps) => {
|
||||
const [selectedPeriod, setSelectedPeriod] = useState<number | null>(30);
|
||||
const menuRef = useRef<Menu>(null);
|
||||
|
||||
const handlePeriodChange = useCallback((days: number | null) => {
|
||||
setSelectedPeriod(days);
|
||||
}, []);
|
||||
|
||||
const menuItems: MenuItem[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: 'Period',
|
||||
items: periodOptions.map(option => ({
|
||||
label: option.label,
|
||||
icon: selectedPeriod === option.value ? 'pi pi-check' : undefined,
|
||||
command: () => handlePeriodChange(option.value),
|
||||
})),
|
||||
},
|
||||
],
|
||||
[selectedPeriod, handlePeriodChange],
|
||||
);
|
||||
|
||||
const selectedPeriodLabel = useMemo(
|
||||
() => periodOptions.find(opt => opt.value === selectedPeriod)?.label || 'All Time',
|
||||
[selectedPeriod],
|
||||
);
|
||||
|
||||
const headerIcons = (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="p-dialog-header-icon p-link"
|
||||
onClick={e => menuRef.current?.toggle(e)}
|
||||
aria-label="Filter options"
|
||||
>
|
||||
<span className="pi pi-bars" />
|
||||
</button>
|
||||
<Menu model={menuItems} popup ref={menuRef} />
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
header="Character Activity"
|
||||
header={
|
||||
<div className="flex items-center gap-2">
|
||||
<span>Character Activity</span>
|
||||
<span className="text-xs text-stone-400">({selectedPeriodLabel})</span>
|
||||
</div>
|
||||
}
|
||||
visible={visible}
|
||||
className="w-[550px] max-h-[90vh]"
|
||||
onHide={onHide}
|
||||
dismissableMask
|
||||
contentClassName="p-0 h-full flex flex-col"
|
||||
icons={headerIcons}
|
||||
>
|
||||
<CharacterActivityContent />
|
||||
<CharacterActivityContent selectedPeriod={selectedPeriod} />
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,16 +7,28 @@ import {
|
||||
} from '@/hooks/Mapper/components/mapRootContent/components/CharacterActivity/helpers.tsx';
|
||||
import { Column } from 'primereact/column';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMemo } from 'react';
|
||||
import { useMemo, useEffect } from 'react';
|
||||
import { useCharacterActivityHandlers } from '@/hooks/Mapper/components/mapRootContent/hooks/useCharacterActivityHandlers';
|
||||
|
||||
export const CharacterActivityContent = () => {
|
||||
interface CharacterActivityContentProps {
|
||||
selectedPeriod: number | null;
|
||||
}
|
||||
|
||||
export const CharacterActivityContent = ({ selectedPeriod }: CharacterActivityContentProps) => {
|
||||
const {
|
||||
data: { characterActivityData },
|
||||
} = useMapRootState();
|
||||
|
||||
const { handleShowActivity } = useCharacterActivityHandlers();
|
||||
|
||||
const activity = useMemo(() => characterActivityData?.activity || [], [characterActivityData]);
|
||||
const loading = useMemo(() => characterActivityData?.loading !== false, [characterActivityData]);
|
||||
|
||||
// Reload activity data when period changes
|
||||
useEffect(() => {
|
||||
handleShowActivity(selectedPeriod);
|
||||
}, [selectedPeriod, handleShowActivity]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full w-full">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
}
|
||||
|
||||
.SidebarOnTheMap {
|
||||
width: 400px;
|
||||
width: 500px;
|
||||
padding: 0 !important;
|
||||
|
||||
:global {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ConnectionType,
|
||||
OutCommand,
|
||||
Passage,
|
||||
PassageWithSourceTarget,
|
||||
SolarSystemConnection,
|
||||
} from '@/hooks/Mapper/types';
|
||||
import clsx from 'clsx';
|
||||
@@ -19,7 +20,7 @@ import { PassageCard } from './PassageCard';
|
||||
|
||||
const sortByDate = (a: string, b: string) => new Date(a).getTime() - new Date(b).getTime();
|
||||
|
||||
const itemTemplate = (item: Passage, options: VirtualScrollerTemplateOptions) => {
|
||||
const itemTemplate = (item: PassageWithSourceTarget, options: VirtualScrollerTemplateOptions) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(classes.CharacterRow, 'w-full box-border', {
|
||||
@@ -35,7 +36,7 @@ const itemTemplate = (item: Passage, options: VirtualScrollerTemplateOptions) =>
|
||||
};
|
||||
|
||||
export interface ConnectionPassagesContentProps {
|
||||
passages: Passage[];
|
||||
passages: PassageWithSourceTarget[];
|
||||
}
|
||||
|
||||
export const ConnectionPassages = ({ passages = [] }: ConnectionPassagesContentProps) => {
|
||||
@@ -113,6 +114,20 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
|
||||
[outCommand],
|
||||
);
|
||||
|
||||
const preparedPassages = useMemo(() => {
|
||||
if (!cnInfo) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return passages
|
||||
.sort((a, b) => sortByDate(b.inserted_at, a.inserted_at))
|
||||
.map<PassageWithSourceTarget>(x => ({
|
||||
...x,
|
||||
source: x.from ? cnInfo.target : cnInfo.source,
|
||||
target: x.from ? cnInfo.source : cnInfo.target,
|
||||
}));
|
||||
}, [cnInfo, passages]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedConnection) {
|
||||
return;
|
||||
@@ -145,12 +160,14 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
|
||||
<InfoDrawer title="Connection" rightSide>
|
||||
<div className="flex justify-end gap-2 items-center">
|
||||
<SystemView
|
||||
showCustomName
|
||||
systemId={cnInfo.source}
|
||||
className={clsx(classes.InfoTextSize, 'select-none text-center')}
|
||||
hideRegion
|
||||
/>
|
||||
<span className="pi pi-angle-double-right text-stone-500 text-[15px]"></span>
|
||||
<SystemView
|
||||
showCustomName
|
||||
systemId={cnInfo.target}
|
||||
className={clsx(classes.InfoTextSize, 'select-none text-center')}
|
||||
hideRegion
|
||||
@@ -184,7 +201,7 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
|
||||
{/* separator */}
|
||||
<div className="w-full h-px bg-neutral-800 px-0.5"></div>
|
||||
|
||||
<ConnectionPassages passages={passages} />
|
||||
<ConnectionPassages passages={preparedPassages} />
|
||||
</div>
|
||||
</Sidebar>
|
||||
);
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
&.ThreeColumns {
|
||||
grid-template-columns: auto 1fr auto;
|
||||
}
|
||||
|
||||
&.FourColumns {
|
||||
grid-template-columns: auto auto 1fr auto;
|
||||
}
|
||||
}
|
||||
|
||||
.CardBorderLeftIsOwn {
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import clsx from 'clsx';
|
||||
import classes from './PassageCard.module.scss';
|
||||
import { Passage } from '@/hooks/Mapper/types';
|
||||
import { TimeAgo } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { PassageWithSourceTarget } from '@/hooks/Mapper/types';
|
||||
import { SystemView, TimeAgo, TooltipPosition, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { ZKB_ICON } from '@/hooks/Mapper/icons';
|
||||
import { charEveWhoLink, charZKBLink } from '@/hooks/Mapper/helpers/linkHelpers.ts';
|
||||
|
||||
type PassageCardType = {
|
||||
// compact?: boolean;
|
||||
showShipName?: boolean;
|
||||
// showSystem?: boolean;
|
||||
// useSystemsCache?: boolean;
|
||||
} & Passage;
|
||||
} & PassageWithSourceTarget;
|
||||
|
||||
const SHIP_NAME_RX = /u'|'/g;
|
||||
export const getShipName = (name: string) => {
|
||||
@@ -25,7 +27,7 @@ export const getShipName = (name: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const PassageCard = ({ inserted_at, character: char, ship }: PassageCardType) => {
|
||||
export const PassageCard = ({ inserted_at, character: char, ship, source, target, from }: PassageCardType) => {
|
||||
const isOwn = false;
|
||||
|
||||
const insertedAt = useMemo(() => {
|
||||
@@ -33,11 +35,46 @@ export const PassageCard = ({ inserted_at, character: char, ship }: PassageCardT
|
||||
return date.toLocaleString();
|
||||
}, [inserted_at]);
|
||||
|
||||
const handleOpenZKB = useCallback(() => window.open(charZKBLink(char.eve_id), '_blank'), [char]);
|
||||
const handleOpenEveWho = useCallback(() => window.open(charEveWhoLink(char.eve_id), '_blank'), [char]);
|
||||
|
||||
return (
|
||||
<div className={clsx(classes.CharacterCard, 'w-full text-xs', 'flex flex-col box-border')}>
|
||||
<div className="flex flex-col justify-between px-2 py-1 gap-1">
|
||||
{/*here icon and other*/}
|
||||
<div className={clsx(classes.CharRow, classes.ThreeColumns)}>
|
||||
<div className={clsx(classes.CharRow, classes.FourColumns)}>
|
||||
<WdTooltipWrapper
|
||||
position={TooltipPosition.top}
|
||||
content={
|
||||
<div className="flex justify-between gap-2 items-center">
|
||||
<SystemView
|
||||
showCustomName
|
||||
systemId={source}
|
||||
className="select-none text-center !text-[12px]"
|
||||
hideRegion
|
||||
/>
|
||||
<span className="pi pi-angle-double-right text-stone-500 text-[15px]"></span>
|
||||
<SystemView
|
||||
showCustomName
|
||||
systemId={target}
|
||||
className="select-none text-center !text-[12px]"
|
||||
hideRegion
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'transition-all transform ease-in duration-200',
|
||||
'pi text-stone-500 text-[15px] w-[35px] h-[33px] !flex items-center justify-center border rounded-[6px]',
|
||||
{
|
||||
['pi-angle-double-right !text-orange-400 border-orange-400 hover:bg-orange-400/30']: from,
|
||||
['pi-angle-double-left !text-stone-500/70 border-stone-500/70 hover:bg-stone-500/30']: !from,
|
||||
},
|
||||
)}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
{/*portrait*/}
|
||||
<span
|
||||
className={clsx(classes.EveIcon, classes.CharIcon, 'wd-bg-default')}
|
||||
@@ -49,7 +86,7 @@ export const PassageCard = ({ inserted_at, character: char, ship }: PassageCardT
|
||||
{/*here name and ship name*/}
|
||||
<div className="grid gap-1 justify-between grid-cols-[max-content_1fr]">
|
||||
{/*char name*/}
|
||||
<div className="grid gap-1 grid-cols-[auto_1px_1fr]">
|
||||
<div className="grid gap-1 grid-cols-[auto_1px_1fr_auto]">
|
||||
<span
|
||||
className={clsx(classes.MaxWidth, 'text-ellipsis overflow-hidden whitespace-nowrap', {
|
||||
[classes.CardBorderLeftIsOwn]: isOwn,
|
||||
@@ -62,6 +99,21 @@ export const PassageCard = ({ inserted_at, character: char, ship }: PassageCardT
|
||||
<div className="h-3 border-r border-neutral-500 my-0.5"></div>
|
||||
{char.alliance_ticker && <span className="text-neutral-400">{char.alliance_ticker}</span>}
|
||||
{!char.alliance_ticker && <span className="text-neutral-400">{char.corporation_ticker}</span>}
|
||||
|
||||
<div className={clsx('flex gap-1 items-center h-full ml-[2px]')}>
|
||||
<WdImgButton
|
||||
width={16}
|
||||
height={16}
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Open zkillboard' }}
|
||||
source={ZKB_ICON}
|
||||
onClick={handleOpenZKB}
|
||||
/>
|
||||
<WdImgButton
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Open Eve Who' }}
|
||||
className={clsx('pi pi-user', '!text-[12px] relative top-[-1px]')}
|
||||
onClick={handleOpenEveWho}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/*ship name*/}
|
||||
|
||||
@@ -12,9 +12,15 @@ export interface MapContextMenuProps {
|
||||
onShowOnTheMap?: () => void;
|
||||
onShowMapSettings?: () => void;
|
||||
onShowTrackingDialog?: () => void;
|
||||
onShowWormholesReference?: () => void;
|
||||
}
|
||||
|
||||
export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings, onShowTrackingDialog }: MapContextMenuProps) => {
|
||||
export const MapContextMenu = ({
|
||||
onShowOnTheMap,
|
||||
onShowMapSettings,
|
||||
onShowTrackingDialog,
|
||||
onShowWormholesReference,
|
||||
}: MapContextMenuProps) => {
|
||||
const {
|
||||
outCommand,
|
||||
storedSettings: { setInterfaceSettings },
|
||||
@@ -52,6 +58,12 @@ export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings, onShowTracki
|
||||
command: onShowOnTheMap,
|
||||
visible: canTrackCharacters,
|
||||
},
|
||||
{
|
||||
label: 'Wormholes Ref.',
|
||||
icon: 'pi pi-bullseye',
|
||||
command: onShowWormholesReference,
|
||||
visible: canTrackCharacters,
|
||||
},
|
||||
{ separator: true, visible: true },
|
||||
{
|
||||
label: 'Settings',
|
||||
|
||||
@@ -10,9 +10,14 @@ import { useCallback } from 'react';
|
||||
import { TooltipPosition, WdButton, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { ConfirmPopup } from 'primereact/confirmpopup';
|
||||
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export const CommonSettings = () => {
|
||||
const { renderSettingItem } = useMapSettings();
|
||||
const {
|
||||
storedSettings: { resetSettings },
|
||||
} = useMapRootState();
|
||||
|
||||
const { cfShow, cfHide, cfVisible, cfRef } = useConfirmPopup();
|
||||
|
||||
const renderSettingsList = useCallback(
|
||||
@@ -22,7 +27,7 @@ export const CommonSettings = () => {
|
||||
[renderSettingItem],
|
||||
);
|
||||
|
||||
const handleResetSettings = () => {};
|
||||
const handleResetSettings = useCallback(() => resetSettings(), [resetSettings]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full gap-1">
|
||||
|
||||
@@ -64,7 +64,7 @@ export const ImportExport = () => {
|
||||
|
||||
// INFO: WE NOT SUPPORT MIGRATIONS FOR OLD FILES AND Clipboard
|
||||
const parsed = parseMapUserSettings(text);
|
||||
if (applySettings(applyMigrations(parsed))) {
|
||||
if (applySettings(applyMigrations(parsed) || createDefaultStoredSettings())) {
|
||||
toast.current?.show({
|
||||
severity: 'success',
|
||||
summary: 'Import',
|
||||
|
||||
@@ -35,7 +35,7 @@ export const ServerSettings = () => {
|
||||
|
||||
try {
|
||||
//INFO: INSTEAD CHECK WE WILL TRY TO APPLY MIGRATION
|
||||
applySettings(applyMigrations(JSON.parse(res.default_settings)));
|
||||
applySettings(applyMigrations(JSON.parse(res.default_settings)) || createDefaultStoredSettings());
|
||||
callToastSuccess(toast.current, 'Settings synchronized successfully');
|
||||
} catch (error) {
|
||||
applySettings(createDefaultStoredSettings());
|
||||
|
||||
@@ -14,6 +14,7 @@ interface RightBarProps {
|
||||
onShowOnTheMap?: () => void;
|
||||
onShowMapSettings?: () => void;
|
||||
onShowTrackingDialog?: () => void;
|
||||
onShowWormholesReference?: () => void;
|
||||
additionalContent?: ReactNode;
|
||||
}
|
||||
|
||||
@@ -21,6 +22,7 @@ export const RightBar = ({
|
||||
onShowOnTheMap,
|
||||
onShowMapSettings,
|
||||
onShowTrackingDialog,
|
||||
onShowWormholesReference,
|
||||
additionalContent,
|
||||
}: RightBarProps) => {
|
||||
const {
|
||||
@@ -90,6 +92,16 @@ export const RightBar = ({
|
||||
<i className="pi pi-hashtag"></i>
|
||||
</button>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
<WdTooltipWrapper content="Wormholes Reference" position={TooltipPosition.left}>
|
||||
<button
|
||||
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
|
||||
type="button"
|
||||
onClick={onShowWormholesReference}
|
||||
>
|
||||
<i className="pi pi-bullseye"></i>
|
||||
</button>
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { OutCommand, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { Controller, FormProvider, useForm } from 'react-hook-form';
|
||||
import {
|
||||
SignatureGroupContent,
|
||||
SignatureGroupSelect,
|
||||
} from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { SystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OutCommand, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { Controller, FormProvider, useForm } from 'react-hook-form';
|
||||
|
||||
type SystemSignaturePrepared = Omit<SystemSignature, 'linked_system'> & {
|
||||
linked_system: string;
|
||||
@@ -119,6 +119,7 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
|
||||
added: [],
|
||||
updated: [out],
|
||||
removed: [],
|
||||
deleteTimeout: 0,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@use "sass:color";
|
||||
@use '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
||||
@import '@/hooks/Mapper/components/map/styles/solar-system-node';
|
||||
@use '@/hooks/Mapper/components/map/styles/solar-system-node' as v;
|
||||
|
||||
:root {
|
||||
--rf-has-user-characters: #ffc75d;
|
||||
@@ -108,7 +108,7 @@
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: $pastel-pink;
|
||||
border-color: v.$pastel-pink;
|
||||
box-shadow: 0 0 10px #9a1af1c2;
|
||||
}
|
||||
|
||||
@@ -122,11 +122,11 @@
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
|
||||
border-color: $neon-color-1;
|
||||
border-color: v.$neon-color-1;
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
$neon-color-3 0px,
|
||||
$neon-color-3 8px,
|
||||
v.$neon-color-3 0px,
|
||||
v.$neon-color-3 8px,
|
||||
transparent 8px,
|
||||
transparent 21px
|
||||
);
|
||||
@@ -152,7 +152,7 @@
|
||||
&.eve-system-status-lookingFor {
|
||||
background-image: linear-gradient(275deg, #45ff8f2f, #457fff2f);
|
||||
&.selected {
|
||||
border-color: $pastel-pink;
|
||||
border-color: v.$pastel-pink;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createContext, useCallback, useContext, useRef, useState } from 'react';
|
||||
import { OutCommand, TrackingCharacter } from '@/hooks/Mapper/types';
|
||||
import { createContext, useCallback, useContext, useRef, useState, useEffect } from 'react';
|
||||
import { Commands, OutCommand, TrackingCharacter } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { IncomingEvent, WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||
import { CommandInCharactersTrackingInfo } from '@/hooks/Mapper/types/commandsIn.ts';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
|
||||
type DiffTrackingInfo = { characterId: string; tracked: boolean };
|
||||
|
||||
@@ -122,6 +123,14 @@ export const TrackingProvider = ({ children }: WithChildren) => {
|
||||
[outCommand],
|
||||
);
|
||||
|
||||
// Listen for refresh_tracking_data event (triggered when ACL members change)
|
||||
useMapEventListener(event => {
|
||||
if (event.name === Commands.refreshTrackingData) {
|
||||
loadTracking();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<TrackingContext.Provider
|
||||
value={{
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { DataTable } from 'primereact/datatable';
|
||||
import { Column } from 'primereact/column';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { WormholeDataRaw } from '@/hooks/Mapper/types';
|
||||
import { RespawnTag, WHClassView } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts';
|
||||
import { WORMHOLE_CLASS_STYLES, WORMHOLES_ADDITIONAL_INFO } from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import clsx from 'clsx';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { IconField } from 'primereact/iconfield';
|
||||
import { InputIcon } from 'primereact/inputicon';
|
||||
|
||||
const renderSpawns = (w: WormholeDataRaw) => (
|
||||
<div className="flex gap-1 flex-wrap">
|
||||
{w.src.map(s => {
|
||||
const group = s.split('-')[0];
|
||||
const info = WORMHOLES_ADDITIONAL_INFO[group];
|
||||
|
||||
if (!info) {
|
||||
return (
|
||||
<span
|
||||
key={s}
|
||||
className="px-[4px] py-[1px] rounded bg-stone-800 text-stone-300 text-xs border border-stone-700"
|
||||
>
|
||||
{s}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const cls = WORMHOLE_CLASS_STYLES[String(info.wormholeClassID)] || '';
|
||||
const label = `${info.shortName}`;
|
||||
return (
|
||||
<span
|
||||
key={s}
|
||||
className={clsx(cls, 'px-[4px] py-[1px] rounded text-xs border border-stone-700 bg-stone-900/40')}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderName = (w: WormholeDataRaw) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<WHClassView
|
||||
whClassName={w.name}
|
||||
noOffset
|
||||
useShortTitle
|
||||
classNameWh="overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderRespawn = (w: WormholeDataRaw) => (
|
||||
<div className="flex gap-1 flex-wrap">
|
||||
{w.respawn.map(r => (
|
||||
<RespawnTag key={r} value={r} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
export interface WormholeSignaturesDialogProps {
|
||||
visible: boolean;
|
||||
onHide: () => void;
|
||||
}
|
||||
|
||||
export const WormholeSignaturesDialog = ({ visible, onHide }: WormholeSignaturesDialogProps) => {
|
||||
const {
|
||||
data: { wormholes },
|
||||
} = useMapRootState();
|
||||
|
||||
const [filter, setFilter] = useState('');
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const q = filter.trim().toLowerCase();
|
||||
|
||||
if (!q) return wormholes;
|
||||
|
||||
return wormholes.filter(w => {
|
||||
const destInfo = WORMHOLES_ADDITIONAL_INFO[w.dest];
|
||||
const spawnsLabels = w.src
|
||||
.map(s => {
|
||||
const group = s.split('-')[0];
|
||||
const info = WORMHOLES_ADDITIONAL_INFO[group];
|
||||
if (!info) return s;
|
||||
return `${info.title} ${info.shortName}`.trim();
|
||||
})
|
||||
.join(' ');
|
||||
|
||||
return [
|
||||
w.name,
|
||||
destInfo?.title,
|
||||
destInfo?.shortName,
|
||||
spawnsLabels,
|
||||
String(w.total_mass),
|
||||
String(w.max_mass_per_jump),
|
||||
w.lifetime,
|
||||
w.respawn.join(','),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
.toLowerCase()
|
||||
.includes(q);
|
||||
});
|
||||
}, [wormholes, filter]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
header="Wormholes Reference"
|
||||
visible={visible}
|
||||
draggable={false}
|
||||
resizable={false}
|
||||
className="w-[950px] h-[600px]"
|
||||
onHide={onHide}
|
||||
contentClassName="!p-0 flex flex-col h-full"
|
||||
>
|
||||
<div className="p-3 flex items-center justify-between gap-2 border-b border-stone-800">
|
||||
<div className="font-semibold text-sm text-stone-200">Reference list of all wormhole types</div>
|
||||
<IconField iconPosition="right">
|
||||
<InputIcon
|
||||
className={clsx('pi pi-times', {
|
||||
['cursor-pointer text-stone-400 hover:text-stone-200']: filter,
|
||||
['text-stone-700 opacity-50 cursor-default']: !filter,
|
||||
})}
|
||||
onClick={() => filter && setFilter('')}
|
||||
role="button"
|
||||
aria-label="Clear search"
|
||||
aria-disabled={!filter}
|
||||
title={filter ? 'Clear' : 'Nothing to clear'}
|
||||
/>
|
||||
<InputText className="w-64" placeholder="Search" value={filter} onChange={e => setFilter(e.target.value)} />
|
||||
</IconField>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 p-3 overflow-x-hidden">
|
||||
<DataTable value={filtered} size="small" scrollable scrollHeight="flex" stripedRows>
|
||||
<Column header="Type" body={renderName} className="w-[160px]" bodyClassName="whitespace-normal break-words" />
|
||||
<Column header="Spawns In" body={renderSpawns} bodyClassName="whitespace-normal break-words text-[13px]" />
|
||||
<Column
|
||||
field="lifetime"
|
||||
header="Lifetime"
|
||||
className="w-[90px]"
|
||||
bodyClassName="whitespace-normal break-words text-[13px]"
|
||||
/>
|
||||
<Column
|
||||
header="Total Mass"
|
||||
className="w-[120px]"
|
||||
body={(w: WormholeDataRaw) => kgToTons(w.total_mass)}
|
||||
bodyClassName="whitespace-normal break-words text-[13px]"
|
||||
/>
|
||||
<Column
|
||||
header="Max/jump"
|
||||
className="w-[120px]"
|
||||
body={(w: WormholeDataRaw) => kgToTons(w.max_mass_per_jump)}
|
||||
bodyClassName="whitespace-normal break-words text-[13px]"
|
||||
/>
|
||||
<Column
|
||||
header="Respawn"
|
||||
className="w-[150px]"
|
||||
body={renderRespawn}
|
||||
bodyClassName="whitespace-normal break-words text-[13px]"
|
||||
/>
|
||||
</DataTable>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './WormholeSignaturesDialog';
|
||||
@@ -23,17 +23,17 @@ export const useCharacterActivityHandlers = () => {
|
||||
/**
|
||||
* Handle showing the character activity dialog
|
||||
*/
|
||||
const handleShowActivity = useCallback(() => {
|
||||
const handleShowActivity = useCallback((days?: number | null) => {
|
||||
// Update local state to show the dialog
|
||||
update(state => ({
|
||||
...state,
|
||||
showCharacterActivity: true,
|
||||
}));
|
||||
|
||||
// Send the command to the server
|
||||
// Send the command to the server with optional days parameter
|
||||
outCommand({
|
||||
type: OutCommand.showActivity,
|
||||
data: {},
|
||||
data: days !== undefined ? { days } : {},
|
||||
});
|
||||
}, [outCommand, update]);
|
||||
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import { Map, MAP_ROOT_ID } from '@/hooks/Mapper/components/map/Map.tsx';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { CommandSelectSystems, OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OnMapAddSystemCallback, OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||
import isEqual from 'lodash.isequal';
|
||||
import { ContextMenuSystem, useContextMenuSystemHandlers } from '@/hooks/Mapper/components/contexts';
|
||||
import { Map, MAP_ROOT_ID } from '@/hooks/Mapper/components/map/Map.tsx';
|
||||
import { OnMapAddSystemCallback, OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||
import {
|
||||
SystemCustomLabelDialog,
|
||||
SystemLinkSignatureDialog,
|
||||
SystemSettingsDialog,
|
||||
} from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { Connections } from '@/hooks/Mapper/components/mapRootContent/components/Connections';
|
||||
import { ContextMenuSystemMultiple, useContextMenuSystemMultipleHandlers } from '../contexts/ContextMenuSystemMultiple';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CommandSelectSystems, OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import isEqual from 'lodash.isequal';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Node, useReactFlow, Viewport, XYPosition } from 'reactflow';
|
||||
import { ContextMenuSystemMultiple, useContextMenuSystemMultipleHandlers } from '../contexts/ContextMenuSystemMultiple';
|
||||
|
||||
import { useCommandsSystems } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { emitMapEvent, useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { useCommandsSystems } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
|
||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
import { useCommonMapEventProcessor } from '@/hooks/Mapper/components/mapWrapper/hooks/useCommonMapEventProcessor.ts';
|
||||
import {
|
||||
AddSystemDialog,
|
||||
SearchOnSubmitCallback,
|
||||
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
|
||||
import { useHotkey } from '../../hooks/useHotkey';
|
||||
import { PingType } from '@/hooks/Mapper/types/ping.ts';
|
||||
import { SystemPingDialog } from '@/hooks/Mapper/components/mapInterface/components/SystemPingDialog';
|
||||
import { MiniMapPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { useCommonMapEventProcessor } from '@/hooks/Mapper/components/mapWrapper/hooks/useCommonMapEventProcessor.ts';
|
||||
import { MINIMAP_PLACEMENT_MAP } from '@/hooks/Mapper/constants.ts';
|
||||
import { MiniMapPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { PingType } from '@/hooks/Mapper/types/ping.ts';
|
||||
import type { PanelPosition } from '@reactflow/core';
|
||||
import { useHotkey } from '../../hooks/useHotkey';
|
||||
import { MINI_MAP_PLACEMENT_OFFSETS } from './constants.ts';
|
||||
|
||||
// TODO: INFO - this component needs for abstract work with Map instance
|
||||
@@ -106,7 +106,7 @@ export const MapWrapper = () => {
|
||||
|
||||
runCommand({
|
||||
name: Commands.selectSystems,
|
||||
data: { systems: selectedSystems } as CommandSelectSystems,
|
||||
data: { systems: selectedSystems, delay: 200 } as CommandSelectSystems,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
WdEveEntityPortrait,
|
||||
WdEveEntityPortraitSize,
|
||||
WdEveEntityPortraitType,
|
||||
WdImgButton,
|
||||
WdTooltipWrapper,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { SystemView } from '@/hooks/Mapper/components/ui-kit/SystemView';
|
||||
@@ -14,6 +15,8 @@ import { Commands } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback } from 'react';
|
||||
import classes from './CharacterCard.module.scss';
|
||||
import { ZKB_ICON } from '@/hooks/Mapper/icons';
|
||||
import { charEveWhoLink, charZKBLink } from '@/hooks/Mapper/helpers/linkHelpers.ts';
|
||||
|
||||
export type CharacterCardProps = {
|
||||
compact?: boolean;
|
||||
@@ -66,6 +69,9 @@ export const CharacterCard = ({
|
||||
const shipType = char.ship?.ship_type_info?.name;
|
||||
const locationShown = showSystem && char.location?.solar_system_id;
|
||||
|
||||
const handleOpenZKB = useCallback(() => window.open(charZKBLink(char.eve_id), '_blank'), [char]);
|
||||
const handleOpenEveWho = useCallback(() => window.open(charEveWhoLink(char.eve_id), '_blank'), [char]);
|
||||
|
||||
// INFO: Simple mode show only name and icon of ally/corp. By default it compact view
|
||||
if (simpleMode) {
|
||||
return (
|
||||
@@ -244,7 +250,24 @@ export const CharacterCard = ({
|
||||
{char.name}
|
||||
</span>
|
||||
{showTicker && <span className="flex-shrink-0 text-gray-400 ml-1">[{tickerText}]</span>}
|
||||
|
||||
<div className={clsx('flex gap-1 items-center h-full ml-[6px]')}>
|
||||
<WdImgButton
|
||||
width={16}
|
||||
height={16}
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Open zkillboard' }}
|
||||
source={ZKB_ICON}
|
||||
onClick={handleOpenZKB}
|
||||
className="min-w-[16px]"
|
||||
/>
|
||||
<WdImgButton
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Open Eve Who' }}
|
||||
className={clsx('pi pi-user', '!text-[12px] relative top-[-1px]')}
|
||||
onClick={handleOpenEveWho}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{locationShown ? (
|
||||
<div className="text-gray-300 text-xs overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
<SystemView
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
.MarkdownCommentRoot {
|
||||
border-left-width: 3px;
|
||||
|
||||
.MarkdownTextViewer {
|
||||
@apply text-[12px] leading-[1.2] text-stone-300 break-words;
|
||||
@apply bg-gradient-to-r from-stone-600/40 via-stone-600/10 to-stone-600/0;
|
||||
|
||||
.h1 {
|
||||
@apply text-[12px] font-normal m-0 p-0 border-none break-words whitespace-normal;
|
||||
@@ -56,6 +53,10 @@
|
||||
@apply font-bold text-green-400 break-words whitespace-normal;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
i, em {
|
||||
@apply italic text-pink-400 break-words whitespace-normal;
|
||||
}
|
||||
@@ -2,10 +2,16 @@ import Markdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkBreaks from 'remark-breaks';
|
||||
|
||||
import classes from './MarkdownTextViewer.module.scss';
|
||||
|
||||
const REMARK_PLUGINS = [remarkGfm, remarkBreaks];
|
||||
|
||||
type MarkdownTextViewerProps = { children: string };
|
||||
|
||||
export const MarkdownTextViewer = ({ children }: MarkdownTextViewerProps) => {
|
||||
return <Markdown remarkPlugins={REMARK_PLUGINS}>{children}</Markdown>;
|
||||
return (
|
||||
<div className={classes.MarkdownTextViewer}>
|
||||
<Markdown remarkPlugins={REMARK_PLUGINS}>{children}</Markdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,8 +4,17 @@ import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrap
|
||||
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit/WdTooltip';
|
||||
import clsx from 'clsx';
|
||||
|
||||
type MenuItemWithInfoProps = { infoTitle: ReactNode; infoClass?: string } & WithChildren;
|
||||
export const MenuItemWithInfo = ({ children, infoClass, infoTitle }: MenuItemWithInfoProps) => {
|
||||
type MenuItemWithInfoProps = {
|
||||
infoTitle: ReactNode;
|
||||
infoClass?: string;
|
||||
tooltipWrapperClassName?: string;
|
||||
} & WithChildren;
|
||||
export const MenuItemWithInfo = ({
|
||||
children,
|
||||
infoClass,
|
||||
infoTitle,
|
||||
tooltipWrapperClassName,
|
||||
}: MenuItemWithInfoProps) => {
|
||||
return (
|
||||
<div className="flex justify-between w-full h-full items-center">
|
||||
{children}
|
||||
@@ -13,6 +22,7 @@ export const MenuItemWithInfo = ({ children, infoClass, infoTitle }: MenuItemWit
|
||||
content={infoTitle}
|
||||
position={TooltipPosition.top}
|
||||
className="!opacity-100 !pointer-events-auto"
|
||||
wrapperClassName={tooltipWrapperClassName}
|
||||
>
|
||||
<div className={clsx('pi text-orange-400', infoClass)} />
|
||||
</WdTooltipWrapper>
|
||||
|
||||
20
assets/js/hooks/Mapper/components/ui-kit/RespawnTag.tsx
Normal file
20
assets/js/hooks/Mapper/components/ui-kit/RespawnTag.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Respawn } from '@/hooks/Mapper/types';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export const WORMHOLE_SPAWN_CLASSES_BG = {
|
||||
[Respawn.static]: 'bg-lime-400/80 text-stone-950',
|
||||
[Respawn.wandering]: 'bg-stone-800',
|
||||
[Respawn.reverse]: 'bg-blue-400 text-stone-950',
|
||||
};
|
||||
|
||||
type RespawnTagProps = { value: string };
|
||||
export const RespawnTag = ({ value }: RespawnTagProps) => (
|
||||
<span
|
||||
className={clsx(
|
||||
'px-[6px] py-[0px] rounded text-stone-300 text-[12px] font-[500] border border-stone-700',
|
||||
WORMHOLE_SPAWN_CLASSES_BG[value as Respawn],
|
||||
)}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
);
|
||||
@@ -1,13 +1,18 @@
|
||||
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||
import { WithChildren, WithClassName } from '@/hooks/Mapper/types/common.ts';
|
||||
import clsx from 'clsx';
|
||||
|
||||
type WdMenuItemProps = { icon?: string; disabled?: boolean } & WithChildren;
|
||||
export const WdMenuItem = ({ children, icon, disabled }: WdMenuItemProps) => {
|
||||
type WdMenuItemProps = { icon?: string; disabled?: boolean } & WithChildren & WithClassName;
|
||||
export const WdMenuItem = ({ children, icon, disabled, className }: WdMenuItemProps) => {
|
||||
return (
|
||||
<a
|
||||
className={clsx('flex gap-[6px] w-full h-full items-center px-[12px] !py-0 ml-[-2px]', 'p-menuitem-link', {
|
||||
'p-disabled': disabled,
|
||||
})}
|
||||
className={clsx(
|
||||
'flex gap-[6px] w-full h-full items-center px-[12px] !py-0',
|
||||
'p-menuitem-link',
|
||||
{
|
||||
'p-disabled': disabled,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{icon && <div className={clsx('min-w-[20px]', icon)}></div>}
|
||||
<div className="w-full">{children}</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ export type WdTooltipWrapperProps = {
|
||||
interactive?: boolean;
|
||||
smallPaddings?: boolean;
|
||||
tooltipClassName?: string;
|
||||
wrapperClassName?: string;
|
||||
} & Omit<HTMLProps<HTMLDivElement>, 'content' | 'size'> &
|
||||
Omit<TooltipProps, 'content'>;
|
||||
|
||||
@@ -26,6 +27,7 @@ export const WdTooltipWrapper = forwardRef<WdTooltipHandlers, WdTooltipWrapperPr
|
||||
smallPaddings,
|
||||
size,
|
||||
tooltipClassName,
|
||||
wrapperClassName,
|
||||
...props
|
||||
},
|
||||
forwardedRef,
|
||||
@@ -36,7 +38,7 @@ export const WdTooltipWrapper = forwardRef<WdTooltipHandlers, WdTooltipWrapperPr
|
||||
|
||||
return (
|
||||
<div className={clsx(classes.WdTooltipWrapperRoot, className)} {...props}>
|
||||
{targetSelector ? <>{children}</> : <div className={autoClass}>{children}</div>}
|
||||
{targetSelector ? <>{children}</> : <div className={clsx(autoClass, wrapperClassName)}>{children}</div>}
|
||||
|
||||
<WdTooltip
|
||||
ref={forwardedRef}
|
||||
|
||||
@@ -23,3 +23,4 @@ export * from './MenuItemWithInfo';
|
||||
export * from './MarkdownTextViewer.tsx';
|
||||
export * from './WdButton.tsx';
|
||||
export * from './constants.ts';
|
||||
export * from './RespawnTag';
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
import { PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export enum SESSION_KEY {
|
||||
viewPort = 'viewPort',
|
||||
windows = 'windows',
|
||||
windowsVisible = 'windowsVisible',
|
||||
routes = 'routes',
|
||||
}
|
||||
|
||||
export const SYSTEM_FOCUSED_LIFETIME = 10000;
|
||||
|
||||
export const GRADIENT_MENU_ACTIVE_CLASSES = 'bg-gradient-to-br from-transparent/10 to-fuchsia-300/10';
|
||||
|
||||
@@ -3,3 +3,4 @@ export * from './parseSignatures';
|
||||
export * from './getSystemById';
|
||||
export * from './getEveImageUrl';
|
||||
export * from './toastHelpers';
|
||||
export * from './recenterSystems';
|
||||
|
||||
2
assets/js/hooks/Mapper/helpers/linkHelpers.ts
Normal file
2
assets/js/hooks/Mapper/helpers/linkHelpers.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const charZKBLink = (characterId: string) => `https://zkillboard.com/character/${characterId}/`;
|
||||
export const charEveWhoLink = (characterId: string) => `https://evewho.com/character/${characterId}`;
|
||||
39
assets/js/hooks/Mapper/helpers/recenterSystems.ts
Normal file
39
assets/js/hooks/Mapper/helpers/recenterSystems.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { XYPosition } from 'reactflow';
|
||||
|
||||
export type WithPosition<T = unknown> = T & { position: XYPosition };
|
||||
|
||||
export const computeBoundsCenter = (items: Array<WithPosition>): XYPosition => {
|
||||
if (items.length === 0) return { x: 0, y: 0 };
|
||||
|
||||
let minX = Infinity;
|
||||
let maxX = -Infinity;
|
||||
let minY = Infinity;
|
||||
let maxY = -Infinity;
|
||||
|
||||
for (const { position } of items) {
|
||||
if (position.x < minX) minX = position.x;
|
||||
if (position.x > maxX) maxX = position.x;
|
||||
if (position.y < minY) minY = position.y;
|
||||
if (position.y > maxY) maxY = position.y;
|
||||
}
|
||||
|
||||
return {
|
||||
x: minX + (maxX - minX) / 2,
|
||||
y: minY + (maxY - minY) / 2,
|
||||
};
|
||||
};
|
||||
|
||||
/** Смещает все точки так, чтобы центр области стал (0,0) */
|
||||
export const recenterSystemsByBounds = <T extends WithPosition>(items: T[]): { center: XYPosition; systems: T[] } => {
|
||||
const center = computeBoundsCenter(items);
|
||||
|
||||
const systems = items.map(it => ({
|
||||
...it,
|
||||
position: {
|
||||
x: it.position.x - center.x,
|
||||
y: it.position.y - center.y,
|
||||
},
|
||||
}));
|
||||
|
||||
return { center, systems };
|
||||
};
|
||||
@@ -28,14 +28,17 @@ export const useEventBuffer = <T>(handler: UseEventBufferHandler<T>) => {
|
||||
eventTickRef.current = eventTick;
|
||||
|
||||
// @ts-ignore
|
||||
const handleEvent = useCallback(event => {
|
||||
if (!eventTickRef.current) {
|
||||
return;
|
||||
}
|
||||
const handleEvent = useCallback(
|
||||
event => {
|
||||
if (!eventTickRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
eventsBufferRef.current.push(event);
|
||||
eventTickRef.current();
|
||||
}, []);
|
||||
eventsBufferRef.current.push(event);
|
||||
eventTickRef.current();
|
||||
},
|
||||
[eventTickRef.current],
|
||||
);
|
||||
|
||||
return { handleEvent };
|
||||
};
|
||||
|
||||
@@ -6,9 +6,11 @@ import {
|
||||
MapUnionTypes,
|
||||
OutCommandHandler,
|
||||
SolarSystemConnection,
|
||||
StringBoolean,
|
||||
TrackingCharacter,
|
||||
UseCharactersCacheData,
|
||||
UseCommentsData,
|
||||
UserPermission,
|
||||
} from '@/hooks/Mapper/types';
|
||||
import { useCharactersCache, useComments, useMapRootHandlers } from '@/hooks/Mapper/mapRootProvider/hooks';
|
||||
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||
@@ -80,7 +82,16 @@ const INITIAL_DATA: MapRootData = {
|
||||
selectedSystems: [],
|
||||
selectedConnections: [],
|
||||
userPermissions: {},
|
||||
options: {},
|
||||
options: {
|
||||
allowed_copy_for: UserPermission.VIEW_SYSTEM,
|
||||
allowed_paste_for: UserPermission.VIEW_SYSTEM,
|
||||
layout: '',
|
||||
restrict_offline_showing: 'false',
|
||||
show_linked_signature_id: 'false',
|
||||
show_linked_signature_id_temp_name: 'false',
|
||||
show_temp_system_name: 'false',
|
||||
store_custom_labels: 'false',
|
||||
},
|
||||
isSubscriptionActive: false,
|
||||
linkSignatureToSystem: null,
|
||||
mainCharacterEveId: null,
|
||||
@@ -135,7 +146,7 @@ export interface MapRootContextProps {
|
||||
hasOldSettings: boolean;
|
||||
getSettingsForExport(): string | undefined;
|
||||
applySettings(settings: MapUserSettings): boolean;
|
||||
resetSettings(settings: MapUserSettings): void;
|
||||
resetSettings(): void;
|
||||
checkOldSettings(): void;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export const createWidgetSettings = <T>(settings: T) => {
|
||||
export const createDefaultStoredSettings = (): MapUserSettings => {
|
||||
return {
|
||||
version: STORED_SETTINGS_VERSION,
|
||||
migratedFromOld: true,
|
||||
migratedFromOld: false,
|
||||
killsWidget: createWidgetSettings(DEFAULT_KILLS_WIDGET_SETTINGS),
|
||||
localWidget: createWidgetSettings(DEFAULT_WIDGET_LOCAL_SETTINGS),
|
||||
widgets: createWidgetSettings(getDefaultWidgetProps()),
|
||||
|
||||
@@ -12,7 +12,7 @@ export const useCommandComments = () => {
|
||||
}, []);
|
||||
|
||||
const removeComment = useCallback((data: CommandCommentRemoved) => {
|
||||
ref.current.removeComment(data.solarSystemId.toString(), data.commentId);
|
||||
ref.current.removeComment(data.solarSystemId, data.commentId);
|
||||
}, []);
|
||||
|
||||
return { addComment, removeComment };
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import {
|
||||
CommandCharacterAdded,
|
||||
CommandCharacterRemoved,
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
CommandCharacterUpdated,
|
||||
CommandPresentCharacters,
|
||||
} from '@/hooks/Mapper/types';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
export const useCommandsCharacters = () => {
|
||||
const { update } = useMapRootState();
|
||||
@@ -14,8 +14,27 @@ export const useCommandsCharacters = () => {
|
||||
const ref = useRef({ update });
|
||||
ref.current = { update };
|
||||
|
||||
const charactersUpdated = useCallback((characters: CommandCharactersUpdated) => {
|
||||
ref.current.update(() => ({ characters: characters.slice() }));
|
||||
const charactersUpdated = useCallback((updatedCharacters: CommandCharactersUpdated) => {
|
||||
ref.current.update(state => {
|
||||
const existing = state.characters ?? [];
|
||||
// Put updatedCharacters into a map keyed by ID
|
||||
const updatedMap = new Map(updatedCharacters.map(c => [c.eve_id, c]));
|
||||
|
||||
// 1. Update existing characters when possible
|
||||
const merged = existing.map(character => {
|
||||
const updated = updatedMap.get(character.eve_id);
|
||||
if (updated) {
|
||||
updatedMap.delete(character.eve_id); // Mark as processed
|
||||
return { ...character, ...updated };
|
||||
}
|
||||
return character;
|
||||
});
|
||||
|
||||
// 2. Any remaining items in updatedMap are NEW characters → add them
|
||||
const newCharacters = Array.from(updatedMap.values());
|
||||
|
||||
return { characters: [...merged, ...newCharacters] };
|
||||
});
|
||||
}, []);
|
||||
|
||||
const characterAdded = useCallback((value: CommandCharacterAdded) => {
|
||||
|
||||
@@ -42,7 +42,7 @@ export const useActualizeRemoteMapSettings = ({
|
||||
}
|
||||
|
||||
try {
|
||||
applySettings(applyMigrations(JSON.parse(res.default_settings)));
|
||||
applySettings(applyMigrations(JSON.parse(res.default_settings) || createDefaultStoredSettings()));
|
||||
} catch (error) {
|
||||
applySettings(createDefaultStoredSettings());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { CommentSystem, CommentType, OutCommand, OutCommandHandler, UseCommentsData } from '@/hooks/Mapper/types';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
interface UseCommentsProps {
|
||||
outCommand: OutCommandHandler;
|
||||
@@ -8,12 +8,12 @@ interface UseCommentsProps {
|
||||
export const useComments = ({ outCommand }: UseCommentsProps): UseCommentsData => {
|
||||
const [lastUpdateKey, setLastUpdateKey] = useState(0);
|
||||
|
||||
const commentBySystemsRef = useRef<Map<string, CommentSystem>>(new Map());
|
||||
const commentBySystemsRef = useRef<Map<number, CommentSystem>>(new Map());
|
||||
|
||||
const ref = useRef({ outCommand });
|
||||
ref.current = { outCommand };
|
||||
|
||||
const loadComments = useCallback(async (systemId: string) => {
|
||||
const loadComments = useCallback(async (systemId: number) => {
|
||||
let cSystem = commentBySystemsRef.current.get(systemId);
|
||||
if (cSystem?.loading || cSystem?.loaded) {
|
||||
return;
|
||||
@@ -45,7 +45,7 @@ export const useComments = ({ outCommand }: UseCommentsProps): UseCommentsData =
|
||||
setLastUpdateKey(x => x + 1);
|
||||
}, []);
|
||||
|
||||
const addComment = useCallback((systemId: string, comment: CommentType) => {
|
||||
const addComment = useCallback((systemId: number, comment: CommentType) => {
|
||||
const cSystem = commentBySystemsRef.current.get(systemId);
|
||||
if (cSystem) {
|
||||
cSystem.comments.push(comment);
|
||||
@@ -61,8 +61,9 @@ export const useComments = ({ outCommand }: UseCommentsProps): UseCommentsData =
|
||||
setLastUpdateKey(x => x + 1);
|
||||
}, []);
|
||||
|
||||
const removeComment = useCallback((systemId: string, commentId: string) => {
|
||||
const removeComment = useCallback((systemId: number, commentId: string) => {
|
||||
const cSystem = commentBySystemsRef.current.get(systemId);
|
||||
console.log('cSystem', cSystem);
|
||||
if (!cSystem) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,127 +63,122 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
const { pingAdded, pingCancelled } = useCommandPings();
|
||||
const { characterActivityData, trackingCharactersData, userSettingsUpdated } = useCommandsActivity();
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => {
|
||||
return {
|
||||
command(type, data) {
|
||||
switch (type) {
|
||||
case Commands.init: // USED
|
||||
mapInit(data as CommandInit);
|
||||
break;
|
||||
case Commands.addSystems: // USED
|
||||
addSystems(data as CommandAddSystems);
|
||||
break;
|
||||
case Commands.updateSystems: // USED
|
||||
updateSystems(data as CommandUpdateSystems);
|
||||
break;
|
||||
case Commands.removeSystems: // USED
|
||||
removeSystems(data as CommandRemoveSystems);
|
||||
break;
|
||||
case Commands.addConnections: // USED
|
||||
addConnections(data as CommandAddConnections);
|
||||
break;
|
||||
case Commands.removeConnections: // USED
|
||||
removeConnections(data as CommandRemoveConnections);
|
||||
break;
|
||||
case Commands.updateConnection: // USED
|
||||
updateConnection(data as CommandUpdateConnection);
|
||||
break;
|
||||
case Commands.charactersUpdated: // USED
|
||||
charactersUpdated(data as CommandCharactersUpdated);
|
||||
break;
|
||||
case Commands.characterAdded: // USED
|
||||
characterAdded(data as CommandCharacterAdded);
|
||||
break;
|
||||
case Commands.characterRemoved: // USED
|
||||
characterRemoved(data as CommandCharacterRemoved);
|
||||
break;
|
||||
case Commands.characterUpdated: // USED
|
||||
characterUpdated(data as CommandCharacterUpdated);
|
||||
break;
|
||||
case Commands.presentCharacters: // USED
|
||||
presentCharacters(data as CommandPresentCharacters);
|
||||
break;
|
||||
case Commands.mapUpdated: // USED
|
||||
mapUpdated(data as CommandMapUpdated);
|
||||
break;
|
||||
case Commands.routes:
|
||||
mapRoutes(data as CommandRoutes);
|
||||
break;
|
||||
case Commands.userRoutes:
|
||||
mapUserRoutes(data as CommandRoutes);
|
||||
break;
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
command(type, data) {
|
||||
switch (type) {
|
||||
case Commands.init: // USED
|
||||
mapInit(data as CommandInit);
|
||||
break;
|
||||
case Commands.addSystems: // USED
|
||||
addSystems(data as CommandAddSystems);
|
||||
break;
|
||||
case Commands.updateSystems: // USED
|
||||
updateSystems(data as CommandUpdateSystems);
|
||||
break;
|
||||
case Commands.removeSystems: // USED
|
||||
removeSystems(data as CommandRemoveSystems);
|
||||
break;
|
||||
case Commands.addConnections: // USED
|
||||
addConnections(data as CommandAddConnections);
|
||||
break;
|
||||
case Commands.removeConnections: // USED
|
||||
removeConnections(data as CommandRemoveConnections);
|
||||
break;
|
||||
case Commands.updateConnection: // USED
|
||||
updateConnection(data as CommandUpdateConnection);
|
||||
break;
|
||||
case Commands.charactersUpdated: // USED
|
||||
charactersUpdated(data as CommandCharactersUpdated);
|
||||
break;
|
||||
case Commands.characterAdded: // USED
|
||||
characterAdded(data as CommandCharacterAdded);
|
||||
break;
|
||||
case Commands.characterRemoved: // USED
|
||||
characterRemoved(data as CommandCharacterRemoved);
|
||||
break;
|
||||
case Commands.characterUpdated: // USED
|
||||
characterUpdated(data as CommandCharacterUpdated);
|
||||
break;
|
||||
case Commands.presentCharacters: // USED
|
||||
presentCharacters(data as CommandPresentCharacters);
|
||||
break;
|
||||
case Commands.mapUpdated: // USED
|
||||
mapUpdated(data as CommandMapUpdated);
|
||||
break;
|
||||
case Commands.routes:
|
||||
mapRoutes(data as CommandRoutes);
|
||||
break;
|
||||
case Commands.userRoutes:
|
||||
mapUserRoutes(data as CommandRoutes);
|
||||
break;
|
||||
|
||||
case Commands.signaturesUpdated: // USED
|
||||
updateSystemSignatures(data as CommandSignaturesUpdated);
|
||||
break;
|
||||
case Commands.signaturesUpdated: // USED
|
||||
updateSystemSignatures(data as CommandSignaturesUpdated);
|
||||
break;
|
||||
|
||||
case Commands.linkSignatureToSystem: // USED
|
||||
setTimeout(() => {
|
||||
updateLinkSignatureToSystem(data as CommandLinkSignatureToSystem);
|
||||
}, 200);
|
||||
break;
|
||||
case Commands.linkSignatureToSystem: // USED
|
||||
setTimeout(() => {
|
||||
updateLinkSignatureToSystem(data as CommandLinkSignatureToSystem);
|
||||
}, 200);
|
||||
break;
|
||||
|
||||
case Commands.centerSystem: // USED
|
||||
// do nothing here
|
||||
break;
|
||||
case Commands.centerSystem: // USED
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.selectSystem: // USED
|
||||
// do nothing here
|
||||
break;
|
||||
case Commands.selectSystem: // USED
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.killsUpdated:
|
||||
// do nothing here
|
||||
break;
|
||||
case Commands.killsUpdated:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.detailedKillsUpdated:
|
||||
updateDetailedKills(data as Record<string, DetailedKill[]>);
|
||||
break;
|
||||
case Commands.detailedKillsUpdated:
|
||||
updateDetailedKills(data as Record<string, DetailedKill[]>);
|
||||
break;
|
||||
|
||||
case Commands.characterActivityData:
|
||||
characterActivityData(data as CommandCharacterActivityData);
|
||||
break;
|
||||
case Commands.characterActivityData:
|
||||
characterActivityData(data as CommandCharacterActivityData);
|
||||
break;
|
||||
|
||||
case Commands.trackingCharactersData:
|
||||
trackingCharactersData(data as CommandTrackingCharactersData);
|
||||
break;
|
||||
case Commands.trackingCharactersData:
|
||||
trackingCharactersData(data as CommandTrackingCharactersData);
|
||||
break;
|
||||
|
||||
case Commands.updateActivity:
|
||||
break;
|
||||
case Commands.updateActivity:
|
||||
break;
|
||||
|
||||
case Commands.updateTracking:
|
||||
break;
|
||||
case Commands.updateTracking:
|
||||
break;
|
||||
|
||||
case Commands.userSettingsUpdated:
|
||||
userSettingsUpdated(data as CommandUserSettingsUpdated);
|
||||
break;
|
||||
case Commands.userSettingsUpdated:
|
||||
userSettingsUpdated(data as CommandUserSettingsUpdated);
|
||||
break;
|
||||
|
||||
case Commands.systemCommentAdded:
|
||||
addComment(data as CommandCommentAdd);
|
||||
break;
|
||||
case Commands.systemCommentAdded:
|
||||
addComment(data as CommandCommentAdd);
|
||||
break;
|
||||
|
||||
case Commands.systemCommentRemoved:
|
||||
removeComment(data as CommandCommentRemoved);
|
||||
break;
|
||||
case Commands.systemCommentRemoved:
|
||||
removeComment(data as CommandCommentRemoved);
|
||||
break;
|
||||
|
||||
case Commands.pingAdded:
|
||||
pingAdded(data as CommandPingAdded);
|
||||
break;
|
||||
case Commands.pingAdded:
|
||||
pingAdded(data as CommandPingAdded);
|
||||
break;
|
||||
|
||||
case Commands.pingCancelled:
|
||||
pingCancelled(data as CommandPingCancelled);
|
||||
break;
|
||||
case Commands.pingCancelled:
|
||||
pingCancelled(data as CommandPingCancelled);
|
||||
break;
|
||||
default:
|
||||
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
|
||||
break;
|
||||
}
|
||||
|
||||
emitMapEvent({ name: type, data });
|
||||
},
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
emitMapEvent({ name: type, data });
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
||||
@@ -115,10 +115,15 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
|
||||
}
|
||||
|
||||
try {
|
||||
// here we try to restore settings
|
||||
let oldMapData;
|
||||
if (!currentMapUserSettings.migratedFromOld) {
|
||||
const allData = extractData(LS_KEY_LEGASY);
|
||||
oldMapData = allData?.[map_slug];
|
||||
}
|
||||
|
||||
// INFO: after migrations migratedFromOld always will be true
|
||||
const migratedResult = applyMigrations(
|
||||
!currentMapUserSettings.migratedFromOld ? extractData(LS_KEY_LEGASY) : currentMapUserSettings,
|
||||
);
|
||||
const migratedResult = applyMigrations(oldMapData ? oldMapData : currentMapUserSettings);
|
||||
|
||||
if (!migratedResult) {
|
||||
setIsReady(true);
|
||||
@@ -143,10 +148,6 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
|
||||
setHasOldSettings(!!(widgetsOld || interfaceSettings || widgetRoutes || widgetLocal || widgetKills || onTheMapOld));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
checkOldSettings();
|
||||
}, [checkOldSettings]);
|
||||
|
||||
const getSettingsForExport = useCallback(() => {
|
||||
const { map_slug } = ref.current;
|
||||
|
||||
@@ -161,6 +162,24 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
|
||||
applySettings(createDefaultStoredSettings());
|
||||
}, [applySettings]);
|
||||
|
||||
useEffect(() => {
|
||||
checkOldSettings();
|
||||
}, [checkOldSettings]);
|
||||
|
||||
// IN Case if in runtime someone clear settings
|
||||
useEffect(() => {
|
||||
if (Object.keys(windowsSettings).length !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
resetSettings();
|
||||
location.reload();
|
||||
}, [isReady, resetSettings, windowsSettings]);
|
||||
|
||||
return {
|
||||
isReady,
|
||||
hasOldSettings,
|
||||
|
||||
@@ -26,7 +26,7 @@ export const applyMigrations = (mapSettings: any) => {
|
||||
return { ...currentMapSettings, version: STORED_SETTINGS_VERSION, migratedFromOld: true };
|
||||
}
|
||||
|
||||
return;
|
||||
return currentMapSettings;
|
||||
}
|
||||
|
||||
const cmVersion = currentMapSettings.version || 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const STORED_SETTINGS_VERSION = 2;
|
||||
|
||||
export const LS_KEY_LEGASY = 'map-user-settings';
|
||||
export const LS_KEY = 'map-user-settings-v2';
|
||||
export const LS_KEY = 'map-user-settings-v3';
|
||||
|
||||
@@ -33,7 +33,6 @@ export type CharacterTypeRaw = {
|
||||
corporation_id: number;
|
||||
corporation_name: string;
|
||||
corporation_ticker: string;
|
||||
tracking_paused: boolean;
|
||||
};
|
||||
|
||||
export interface TrackingCharacter {
|
||||
@@ -69,4 +68,5 @@ export interface ActivitySummary {
|
||||
passages: number;
|
||||
connections: number;
|
||||
signatures: number;
|
||||
timestamp?: string;
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ export type CommentSystem = {
|
||||
};
|
||||
|
||||
export interface UseCommentsData {
|
||||
loadComments: (systemId: string) => Promise<void>;
|
||||
addComment: (systemId: string, comment: CommentType) => void;
|
||||
removeComment: (systemId: string, commentId: string) => void;
|
||||
comments: Map<string, CommentSystem>;
|
||||
loadComments: (systemId: number) => Promise<void>;
|
||||
addComment: (systemId: number, comment: CommentType) => void;
|
||||
removeComment: (systemId: number, commentId: string) => void;
|
||||
comments: Map<number, CommentSystem>;
|
||||
lastUpdateKey: number;
|
||||
}
|
||||
|
||||
@@ -6,11 +6,17 @@ export type PassageLimitedCharacterType = Pick<
|
||||
>;
|
||||
|
||||
export type Passage = {
|
||||
from: boolean;
|
||||
inserted_at: string; // Date
|
||||
ship: ShipTypeRaw;
|
||||
character: PassageLimitedCharacterType;
|
||||
};
|
||||
|
||||
export type PassageWithSourceTarget = {
|
||||
source: string;
|
||||
target: string;
|
||||
} & Passage;
|
||||
|
||||
export type ConnectionInfoOutput = {
|
||||
marl_eol_time: string;
|
||||
};
|
||||
|
||||
@@ -9,3 +9,4 @@ export * from './connectionPassages';
|
||||
export * from './permissions';
|
||||
export * from './comment';
|
||||
export * from './ping';
|
||||
export * from './options';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CommentType, PingData, SystemSignature, UserPermissions } from '@/hooks/Mapper/types';
|
||||
import { CommentType, MapOptions, PingData, SystemSignature, UserPermissions } from '@/hooks/Mapper/types';
|
||||
import { ActivitySummary, CharacterTypeRaw, TrackingCharacter } from '@/hooks/Mapper/types/character.ts';
|
||||
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
|
||||
import { DetailedKill, Kill } from '@/hooks/Mapper/types/kills.ts';
|
||||
@@ -38,6 +38,7 @@ export enum Commands {
|
||||
updateTracking = 'update_tracking',
|
||||
userSettingsUpdated = 'user_settings_updated',
|
||||
showTracking = 'show_tracking',
|
||||
refreshTrackingData = 'refresh_tracking_data',
|
||||
pingAdded = 'ping_added',
|
||||
pingCancelled = 'ping_cancelled',
|
||||
}
|
||||
@@ -74,6 +75,7 @@ export type Command =
|
||||
| Commands.updateActivity
|
||||
| Commands.updateTracking
|
||||
| Commands.showTracking
|
||||
| Commands.refreshTrackingData
|
||||
| Commands.pingAdded
|
||||
| Commands.pingCancelled;
|
||||
|
||||
@@ -94,7 +96,7 @@ export type CommandInit = {
|
||||
hubs: string[];
|
||||
user_hubs: string[];
|
||||
routes: RoutesList;
|
||||
options: Record<string, string | boolean>;
|
||||
options: MapOptions;
|
||||
reset?: boolean;
|
||||
is_subscription_active?: boolean;
|
||||
main_character_eve_id?: string | null;
|
||||
@@ -131,7 +133,7 @@ export type CommandLinkSignatureToSystem = {
|
||||
};
|
||||
export type CommandLinkSignaturesUpdated = number;
|
||||
export type CommandCommentAdd = {
|
||||
solarSystemId: string;
|
||||
solarSystemId: number;
|
||||
comment: CommentType;
|
||||
};
|
||||
export type CommandCommentRemoved = {
|
||||
@@ -145,6 +147,7 @@ export type CommandUserSettingsUpdated = {
|
||||
};
|
||||
|
||||
export type CommandShowTracking = null;
|
||||
export type CommandRefreshTrackingData = Record<string, never>;
|
||||
export type CommandUpdateActivity = {
|
||||
characterId: number;
|
||||
systemId: number;
|
||||
@@ -206,6 +209,7 @@ export interface CommandData {
|
||||
[Commands.systemCommentRemoved]: CommandCommentRemoved;
|
||||
[Commands.systemCommentsUpdated]: unknown;
|
||||
[Commands.showTracking]: CommandShowTracking;
|
||||
[Commands.refreshTrackingData]: CommandRefreshTrackingData;
|
||||
[Commands.pingAdded]: CommandPingAdded;
|
||||
[Commands.pingCancelled]: CommandPingCancelled;
|
||||
}
|
||||
@@ -247,6 +251,7 @@ export enum OutCommand {
|
||||
deleteSystems = 'delete_systems',
|
||||
manualAddSystem = 'manual_add_system',
|
||||
manualAddConnection = 'manual_add_connection',
|
||||
manualPasteSystemsAndConnections = 'manual_paste_systems_and_connections',
|
||||
manualDeleteConnection = 'manual_delete_connection',
|
||||
setAutopilotWaypoint = 'set_autopilot_waypoint',
|
||||
addSystem = 'add_system',
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CharacterTypeRaw } from '@/hooks/Mapper/types/character.ts';
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types/system.ts';
|
||||
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
|
||||
import { PingData, UserPermissions } from '@/hooks/Mapper/types';
|
||||
import { MapOptions, PingData, UserPermissions } from '@/hooks/Mapper/types';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
|
||||
|
||||
export type MapUnionTypes = {
|
||||
@@ -23,7 +23,7 @@ export type MapUnionTypes = {
|
||||
kills: Record<number, number>;
|
||||
connections: SolarSystemConnection[];
|
||||
userPermissions: Partial<UserPermissions>;
|
||||
options: Record<string, string | boolean>;
|
||||
options: MapOptions;
|
||||
isSubscriptionActive: boolean;
|
||||
|
||||
mainCharacterEveId: string | null;
|
||||
|
||||
14
assets/js/hooks/Mapper/types/options.ts
Normal file
14
assets/js/hooks/Mapper/types/options.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
||||
|
||||
export type StringBoolean = 'true' | 'false';
|
||||
|
||||
export type MapOptions = {
|
||||
allowed_copy_for: UserPermission;
|
||||
allowed_paste_for: UserPermission;
|
||||
layout: string;
|
||||
restrict_offline_showing: StringBoolean;
|
||||
show_linked_signature_id: StringBoolean;
|
||||
show_linked_signature_id_temp_name: StringBoolean;
|
||||
show_temp_system_name: StringBoolean;
|
||||
store_custom_labels: StringBoolean;
|
||||
};
|
||||
@@ -29,7 +29,7 @@ export type GroupType = {
|
||||
|
||||
export type SignatureCustomInfo = {
|
||||
k162Type?: string;
|
||||
isEOL?: boolean;
|
||||
time_status?: number;
|
||||
isCrit?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEventBuffer } from '@/hooks/Mapper/hooks';
|
||||
import usePageVisibility from '@/hooks/Mapper/hooks/usePageVisibility.ts';
|
||||
import debounce from 'lodash.debounce';
|
||||
|
||||
import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { RefObject, useCallback, useEffect, useRef } from 'react';
|
||||
@@ -16,23 +16,6 @@ export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRe
|
||||
const visibleRef = useRef(visible);
|
||||
visibleRef.current = visible;
|
||||
|
||||
// @ts-ignore
|
||||
const handleBufferedEvent = useCallback(({ type, body }) => {
|
||||
if (!visibleRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
handlerRefs.forEach(ref => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.current?.command(type, body);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { handleEvent: handleMapEvent } = useEventBuffer<any>(handleBufferedEvent);
|
||||
|
||||
// TODO - do not delete THIS code it needs for debug
|
||||
// const [record, setRecord] = useLocalStorageState<boolean>('record', {
|
||||
// defaultValue: false,
|
||||
@@ -73,6 +56,52 @@ export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRe
|
||||
[hooksRef.current],
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
const eventsBufferRef = useRef<{ type; body }[]>([]);
|
||||
|
||||
const eventTick = useCallback(
|
||||
debounce(() => {
|
||||
if (eventsBufferRef.current.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { type, body } = eventsBufferRef.current.shift()!;
|
||||
handlerRefs.forEach(ref => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.current?.command(type, body);
|
||||
});
|
||||
|
||||
// TODO - do not delete THIS code it needs for debug
|
||||
// console.log('JOipP', `Tick Buff`, eventsBufferRef.current.length);
|
||||
|
||||
if (eventsBufferRef.current.length > 0) {
|
||||
eventTick();
|
||||
}
|
||||
}, 10),
|
||||
[],
|
||||
);
|
||||
const eventTickRef = useRef(eventTick);
|
||||
eventTickRef.current = eventTick;
|
||||
|
||||
// @ts-ignore
|
||||
const handleMapEvent = useCallback(({ type, body }) => {
|
||||
// TODO - do not delete THIS code it needs for debug
|
||||
// const currentTime = +new Date();
|
||||
// const timeDiff = currentTime - prevEventTime;
|
||||
// prevEventTime = currentTime;
|
||||
// console.log('JOipP', `IN [${inIndex++}] [${timeDiff}] ${getFormattedTime()}`, { type, body });
|
||||
|
||||
if (!eventTickRef.current || !visibleRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
eventsBufferRef.current.push({ type, body });
|
||||
eventTickRef.current();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible && !wasHiddenOnce.current) {
|
||||
wasHiddenOnce.current = true;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user