Compare commits

..

76 Commits

Author SHA1 Message Date
CI
32fe6395a1 chore: release version v1.50.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / Manual Approval (push) Blocked by required conditions
Build / 🛠 Build (1.17, 18.x, 27) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-02-17 00:09:15 +00:00
guarzo
5f506bf4b2 feat: allow addition of characters to acl without preregistration (#176) 2025-02-17 03:52:47 +04:00
CI
0127ebfe46 chore: release version v1.49.0
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-02-15 08:37:43 +00:00
guarzo
8c5366fd9b feat: add api for acl management (#171) 2025-02-15 12:16:42 +04:00
CI
dbcad892a9 chore: release version v1.48.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-02-13 17:40:15 +00:00
Dmitry Popov
6da3096db1 chore: release version v1.48.0 2025-02-13 18:04:28 +01:00
CI
cd8efcd6e3 chore: release version v1.48.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / Manual Approval (push) Blocked by required conditions
Build / 🛠 Build (1.17, 18.x, 27) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-02-12 22:46:57 +00:00
Dmitry Popov
b52471ae5e chore: release version v1.47.6 2025-02-12 23:37:54 +01:00
Dmitry Popov
438fecb61f chore: release version v1.47.6 2025-02-12 23:07:45 +01:00
guarzo
70b589a359 System Kills cleanup (#166)
* fix: styling and count of kills tooltip
2025-02-13 01:23:36 +04:00
guarzo
cf7069b3b2 feat: autosize local character tooltip and increase hover target (#165) 2025-02-13 01:11:40 +04:00
CI
b2198e469e chore: release version v1.47.6
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / Manual Approval (push) Blocked by required conditions
Build / 🛠 Build (1.17, 18.x, 27) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-02-12 09:47:56 +00:00
Dmitry Popov
8ab337e8e7 chore: release version v1.47.5 2025-02-12 10:17:35 +01:00
CI
51878ab503 chore: release version v1.47.5 2025-02-12 07:55:37 +00:00
guarzo
401dfad298 fix: sync kills count bookmark and the kills widget (#160)
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / Manual Approval (push) Blocked by required conditions
Build / 🛠 Build (1.17, 18.x, 27) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64/v8) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
* fix: lazy load kills widget
2025-02-12 03:15:16 +04:00
CI
18cff7d312 chore: release version v1.47.4
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / Manual Approval (push) Blocked by required conditions
Build / 🛠 Build (1.17, 18.x, 27) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64/v8) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-02-11 11:10:34 +00:00
Dmitry Popov
7896de00d6 chore: release version v1.47.3 2025-02-11 12:02:42 +01:00
CI
3b079505c3 chore: release version v1.47.3 2025-02-11 10:20:28 +00:00
Dmitry Popov
5b972b03e5 Revert "fix: lazy load kills widget (#157)" (#158)
This reverts commit b29e57b3a4.
2025-02-11 14:20:01 +04:00
CI
79b284c46d chore: release version v1.47.2 2025-02-11 08:31:23 +00:00
guarzo
b29e57b3a4 fix: lazy load kills widget (#157)
* fix: lazy load kills widget

* fix: updates for eslint and pr feedback
2025-02-11 12:28:24 +04:00
CI
c6f4baeee3 chore: release version v1.47.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64/v8) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-02-09 16:27:54 +00:00
Dmitry Popov
6d341be072 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-02-09 17:27:27 +01:00
Dmitry Popov
2437ec9c84 fix(Connections): Fixed connections auto-refresh after update 2025-02-09 17:27:22 +01:00
CI
7e692b5805 chore: release version v1.47.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64/v8) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-02-09 10:13:17 +00:00
Dmitry Popov
01b7370ecd feat(Map): Added check for active map subscription to using Map APIs 2025-02-09 11:12:43 +01:00
CI
20ad8b07d7 chore: release version v1.46.1 2025-02-09 09:36:55 +00:00
Aleksei Chichenkov
cab1880fb0 fix(Map): Fixed a lot of design and architect issues after last milli… (#154)
* fix(Map): Fixed a lot of design and architect issues after last million PRs

* fix(Map): removed unnecessary hooks styles

---------

Co-authored-by: achichenkov <aleksei.chichenkov@telleqt.ai>
2025-02-09 13:36:25 +04:00
CI
78eefcd6a7 chore: release version v1.46.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64/v8) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-02-08 11:03:09 +00:00
Dmitry Popov
eec78d38a8 feat: Added WANDERER_RESTRICT_MAPS_CREATION env support
Restrict maps creation for any registered users, allow for server admins
only.
2025-02-08 12:02:38 +01:00
CI
73f8b1f06b chore: release version v1.45.5
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64/v8) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-02-07 19:16:01 +00:00
guarzo
f96cb01860 fix: restore styling for local characters list (#152) 2025-02-07 23:15:20 +04:00
CI
6800be1bb6 chore: release version v1.45.4 2025-02-07 19:15:01 +00:00
guarzo
143f0a5b3a fix: remove snap to grid customization (#153) 2025-02-07 23:14:29 +04:00
CI
b6495504f8 chore: release version v1.45.3
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64/v8) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-02-05 17:22:36 +00:00
guarzo
2f07ec1b74 fix: color and formatting fixes for local character (#150) 2025-02-05 21:22:10 +04:00
CI
7073a0e8e6 chore: release version v1.45.2
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64/v8) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-02-05 15:55:30 +00:00
guarzo
bb0d91a3c7 fix: fix route list hover and on the map character list (#149)
* fix: correct formatting for on the map character list

* fix: fix hover for route list
2025-02-05 19:55:05 +04:00
CI
1cb12b97ba chore: release version v1.45.1 2025-02-05 14:59:57 +00:00
guarzo
860d20dc66 fix: kill count subscript position on firefox, and remove kill filter for single system (#148) 2025-02-05 18:59:30 +04:00
CI
a850071965 chore: release version v1.45.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64/v8) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-02-05 07:02:17 +00:00
guarzo
fc41573e70 feat: allow filtering of k-space kills (#147) 2025-02-05 07:01:46 +00:00
CI
97f1808fb5 chore: release version v1.44.9
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64/v8) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-02-04 21:00:56 +00:00
guarzo
d31046eebb fix: improve local character header shrink behavior (#146) 2025-02-04 21:00:30 +00:00
CI
a70fa50eab chore: release version v1.44.8 2025-02-04 19:32:13 +00:00
Dmitry Popov
9a082c26f5 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-02-04 20:31:26 +01:00
Dmitry Popov
6af2dc1ed5 fix(Core): include external libraries in build 2025-02-04 20:31:21 +01:00
CI
5fd1509d44 chore: release version v1.44.7 2025-02-04 19:21:18 +00:00
Dmitry Popov
2448c0531b fix(Core): include external libraries in build 2025-02-04 20:20:34 +01:00
CI
b685ea1013 chore: release version v1.44.6 2025-02-04 17:19:49 +00:00
guarzo
55465688c8 Add hover tooltips for local counter and kills bookmark (#130)
* feat: add local pilots and kills display on hover
2025-02-04 21:19:13 +04:00
CI
ac3c7e0c44 chore: release version v1.44.5 2025-02-04 17:12:10 +00:00
guarzo
2d6ab5646c fix: include category param in search cache key (#144) 2025-02-04 21:11:41 +04:00
CI
67b373ac29 chore: release version v1.44.4
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-02-02 21:45:53 +00:00
Dmitry Popov
678169e6fa chore: release version v1.44.0 2025-02-02 22:44:55 +01:00
Dmitry Popov
7ee3c8db82 Merge branch 'main' into zkill_subs 2025-02-02 22:25:00 +01:00
Dmitry Popov
304f4b01ab chore: release version v1.44.0 2025-02-02 22:24:47 +01:00
CI
4af12c21b2 chore: release version v1.44.3 2025-02-02 21:18:47 +00:00
guarzo
497da1e5f7 fix: restored kills lightning bolt functionality (#143) 2025-02-03 01:18:21 +04:00
CI
5bd968acae chore: release version v1.44.2 2025-02-02 19:54:11 +00:00
guarzo
f74c20142c Add api for visible system kill information (#133)
* feat: api for zkill information
2025-02-02 23:53:40 +04:00
CI
d4c40d7542 chore: release version v1.44.1
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-02-01 20:34:37 +00:00
Aleksei Chichenkov
04f3fec0c0 fix(Map): Fixed problem with windows. (#140)
* fix(Map): Fixed problem with windows.

* fix(Core): Added min heigth for body

---------

Co-authored-by: achichenkov <aleksei.chichenkov@telleqt.ai>
Co-authored-by: Dmitry Popov <dmitriypopovsamara@gmail.com>
2025-02-02 00:34:08 +04:00
CI
cd0b4b0fc9 chore: release version v1.44.0 2025-02-01 15:22:11 +00:00
Aleksei Chichenkov
e7b115e6e6 Merge pull request #124 from guarzo/guarzo/zkill
feat: add zkill widget
2025-02-01 18:21:40 +03:00
Gustav
dff8fc6396 refactor: additional design feedback improvements 2025-01-31 15:00:46 -07:00
Gustav
afdaeb3d34 fix: design feedback patch 2025-01-31 15:00:46 -07:00
Gustav
ac6053361e fix: removed unneeded event handler 2025-01-31 15:00:46 -07:00
Gustav
eb3e1ba3aa feat: add news post for zkill widget 2025-01-31 15:00:46 -07:00
Gustav
8468a9b5de feat: add zkill widget 2025-01-31 15:00:46 -07:00
CI
5eafe59dcb chore: release version v1.43.9
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-01-30 21:55:35 +00:00
Dmitry Popov
b38bcaa8cf fix(Core): Add discord link to 'Like' icon on main interface 2025-01-30 22:55:04 +01:00
CI
8a238a447d chore: release version v1.43.8
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-01-26 16:21:18 +00:00
Dmitry Popov
3731219216 fix(Core): Update shuttered constellations (required EVE DB data update on server). 2025-01-26 17:20:28 +01:00
CI
73d5fd5f67 chore: release version v1.43.7 2025-01-26 16:12:32 +00:00
Dmitry Popov
e8e4aed6d5 Signature EOL status support (#136)
* feat(Map): Added an ability to mark signature as EOL

* chore: release version v1.39.1

* fix(Map): Add correct styles for switch

* fix(Map): Refactor signatures code. Add ability to set EOL for signature marked as EOL

* feat(Map): Added EOL status for unsplashed signatures. Show precise time for connection passages on hover.

---------

Co-authored-by: achichenkov <aleksei.chichenkov@telleqt.ai>
2025-01-26 20:12:07 +04:00
148 changed files with 6792 additions and 1541 deletions

View File

@@ -1,7 +1,12 @@
{
"name": "wanderer-dev",
"dockerComposeFile": ["./docker-compose.yml"],
"extensions": ["jakebecker.elixir-ls"],
"extensions": [
"jakebecker.elixir-ls",
"JakeBecker.elixir-ls",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
],
"service": "wanderer",
"workspaceFolder": "/app",
"shutdownAction": "stopCompose",

View File

@@ -7,3 +7,5 @@ export EVE_CLIENT_WITH_WALLET_SECRET="<EVE_CLIENT_WITH_WALLET_SECRET>"
export GIT_SHA="1111"
export WANDERER_INVITES="false"
export WANDERER_PUBLIC_API_DISABLED="false"
export WANDERER_CHARACTER_API_DISABLED="false"
export WANDERER_ZKILL_PRELOAD_DISABLED="false"

View File

@@ -1,8 +1,6 @@
name: Build
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
push:
branches:
- main
@@ -41,8 +39,28 @@ jobs:
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
manual-approval:
name: Manual Approval
runs-on: ubuntu-latest
needs: deploy-test
if: success()
permissions:
issues: write
steps:
- name: Await Manual Approval
uses: trstringer/manual-approval@v1
with:
secret: ${{ github.TOKEN }}
approvers: DmitryPopov
minimum-approvals: 1
issue-title: "Manual Approval Required for Release"
issue-body: "Please approve or deny the deployment."
build:
name: 🛠 Build
needs: manual-approval
runs-on: ubuntu-22.04
if: ${{ (github.ref == 'refs/heads/main') && github.event_name == 'push' }}
permissions:

View File

@@ -2,6 +2,288 @@
<!-- changelog -->
## [v1.50.0](https://github.com/wanderer-industries/wanderer/compare/v1.49.0...v1.50.0) (2025-02-17)
### Features:
* allow addition of characters to acl without preregistration (#176)
## [v1.49.0](https://github.com/wanderer-industries/wanderer/compare/v1.48.1...v1.49.0) (2025-02-15)
### Features:
* add api for acl management (#171)
## [v1.48.1](https://github.com/wanderer-industries/wanderer/compare/v1.48.0...v1.48.1) (2025-02-13)
## [v1.48.0](https://github.com/wanderer-industries/wanderer/compare/v1.47.6...v1.48.0) (2025-02-12)
### Features:
* autosize local character tooltip and increase hover target (#165)
## [v1.47.6](https://github.com/wanderer-industries/wanderer/compare/v1.47.5...v1.47.6) (2025-02-12)
## [v1.47.5](https://github.com/wanderer-industries/wanderer/compare/v1.47.4...v1.47.5) (2025-02-12)
### Bug Fixes:
* sync kills count bookmark and the kills widget (#160)
* lazy load kills widget
## [v1.47.4](https://github.com/wanderer-industries/wanderer/compare/v1.47.3...v1.47.4) (2025-02-11)
## [v1.47.3](https://github.com/wanderer-industries/wanderer/compare/v1.47.2...v1.47.3) (2025-02-11)
## [v1.47.2](https://github.com/wanderer-industries/wanderer/compare/v1.47.1...v1.47.2) (2025-02-11)
### Bug Fixes:
* lazy load kills widget (#157)
* lazy load kills widget
* updates for eslint and pr feedback
## [v1.47.1](https://github.com/wanderer-industries/wanderer/compare/v1.47.0...v1.47.1) (2025-02-09)
### Bug Fixes:
* Connections: Fixed connections auto-refresh after update
## [v1.47.0](https://github.com/wanderer-industries/wanderer/compare/v1.46.1...v1.47.0) (2025-02-09)
### Features:
* Map: Added check for active map subscription to using Map APIs
## [v1.46.1](https://github.com/wanderer-industries/wanderer/compare/v1.46.0...v1.46.1) (2025-02-09)
### Bug Fixes:
* Map: Fixed a lot of design and architect issues after last milli… (#154)
* Map: Fixed a lot of design and architect issues after last million PRs
* Map: removed unnecessary hooks styles
## [v1.46.0](https://github.com/wanderer-industries/wanderer/compare/v1.45.5...v1.46.0) (2025-02-08)
### Features:
* Added WANDERER_RESTRICT_MAPS_CREATION env support
## [v1.45.5](https://github.com/wanderer-industries/wanderer/compare/v1.45.4...v1.45.5) (2025-02-07)
### Bug Fixes:
* restore styling for local characters list (#152)
## [v1.45.4](https://github.com/wanderer-industries/wanderer/compare/v1.45.3...v1.45.4) (2025-02-07)
### Bug Fixes:
* remove snap to grid customization (#153)
## [v1.45.3](https://github.com/wanderer-industries/wanderer/compare/v1.45.2...v1.45.3) (2025-02-05)
### Bug Fixes:
* color and formatting fixes for local character (#150)
## [v1.45.2](https://github.com/wanderer-industries/wanderer/compare/v1.45.1...v1.45.2) (2025-02-05)
### Bug Fixes:
* fix route list hover and on the map character list (#149)
* correct formatting for on the map character list
* fix hover for route list
## [v1.45.1](https://github.com/wanderer-industries/wanderer/compare/v1.45.0...v1.45.1) (2025-02-05)
### Bug Fixes:
* kill count subscript position on firefox, and remove kill filter for single system (#148)
## [v1.45.0](https://github.com/wanderer-industries/wanderer/compare/v1.44.9...v1.45.0) (2025-02-05)
### Features:
* allow filtering of k-space kills (#147)
## [v1.44.9](https://github.com/wanderer-industries/wanderer/compare/v1.44.8...v1.44.9) (2025-02-04)
### Bug Fixes:
* improve local character header shrink behavior (#146)
## [v1.44.8](https://github.com/wanderer-industries/wanderer/compare/v1.44.7...v1.44.8) (2025-02-04)
### Bug Fixes:
* Core: include external libraries in build
## [v1.44.7](https://github.com/wanderer-industries/wanderer/compare/v1.44.6...v1.44.7) (2025-02-04)
### Bug Fixes:
* Core: include external libraries in build
## [v1.44.6](https://github.com/wanderer-industries/wanderer/compare/v1.44.5...v1.44.6) (2025-02-04)
## [v1.44.5](https://github.com/wanderer-industries/wanderer/compare/v1.44.4...v1.44.5) (2025-02-04)
### Bug Fixes:
* include category param in search cache key (#144)
## [v1.44.4](https://github.com/wanderer-industries/wanderer/compare/v1.44.3...v1.44.4) (2025-02-02)
## [v1.44.3](https://github.com/wanderer-industries/wanderer/compare/v1.44.2...v1.44.3) (2025-02-02)
### Bug Fixes:
* restored kills lightning bolt functionality (#143)
## [v1.44.2](https://github.com/wanderer-industries/wanderer/compare/v1.44.1...v1.44.2) (2025-02-02)
## [v1.44.1](https://github.com/wanderer-industries/wanderer/compare/v1.44.0...v1.44.1) (2025-02-01)
### Bug Fixes:
* Map: Fixed problem with windows. (#140)
* Map: Fixed problem with windows.
* Core: Added min heigth for body
## [v1.44.0](https://github.com/wanderer-industries/wanderer/compare/v1.43.9...v1.44.0) (2025-02-01)
### Features:
* add news post for zkill widget
* add zkill widget
### Bug Fixes:
* design feedback patch
* removed unneeded event handler
## [v1.43.9](https://github.com/wanderer-industries/wanderer/compare/v1.43.8...v1.43.9) (2025-01-30)
### Bug Fixes:
* Core: Add discord link to 'Like' icon on main interface
## [v1.43.8](https://github.com/wanderer-industries/wanderer/compare/v1.43.7...v1.43.8) (2025-01-26)
### Bug Fixes:
* Core: Update shuttered constellations (required EVE DB data update on server).
## [v1.43.7](https://github.com/wanderer-industries/wanderer/compare/v1.43.6...v1.43.7) (2025-01-26)
## [v1.43.6](https://github.com/wanderer-industries/wanderer/compare/v1.43.5...v1.43.6) (2025-01-22)

View File

@@ -58,6 +58,7 @@ Now you can visit [`localhost:8000`](http://localhost:8000) from your browser.
- `root@0d0a785313b6:/app# apt update`
- `root@0d0a785313b6:/app# curl -sL https://deb.nodesource.com/setup_18.x | bash -`
- `root@0d0a785313b6:/app# apt-get install nodejs inotify-tools -y`
- `root@0d0a785313b6:/app# npm install -g yarn`
- `root@0d0a785313b6:/app# mix setup`
- See how to run server in #Run section

View File

@@ -1,6 +1,3 @@
// import '@fontsource-variable/inter'
// import '@fontsource-variable/jetbrains-mono'
// import './lib/tailwind/index.css';
import './css/app.css';
import './lib/phoenix';

View File

@@ -25,6 +25,10 @@ body {
width: 400px; /* As IE6 ignores !important it will set width as 400px; */
}
body > div:first-of-type {
min-height: 500px !important;
}
.lending-normal {
font-family: 'Shentox', 'Rogan', sans-serif !important;
font-weight: 500;
@@ -932,3 +936,66 @@ body {
width: 16px;
height: 16px;
}
.verticalTabsContainer {
display: flex;
width: 100%;
min-height: 300px;
}
.verticalTabsContainer .p-tabview {
width: 100%;
display: flex;
align-items: flex-start;
}
.verticalTabsContainer .p-tabview-panels {
padding: 6px 1rem !important;
flex-grow: 1;
height: 100%;
}
.verticalTabsContainer .p-tabview-nav-container {
border-right: none;
height: 100%;
}
.verticalTabsContainer .p-tabview-nav {
flex-direction: column;
width: 150px;
min-height: 100%;
border: none;
}
.verticalTabsContainer .p-tabview-nav li {
width: 100%;
border-right: 4px solid var(--surface-hover);
background-color: var(--surface-card);
transition:
background-color 200ms,
border-right-color 200ms;
}
.verticalTabsContainer .p-tabview-nav li:hover {
background-color: var(--surface-hover);
border-right: 4px solid var(--surface-100);
}
.verticalTabsContainer .p-tabview-nav li .p-tabview-nav-link {
transition: color 200ms;
justify-content: flex-end;
padding: 10px;
background-color: initial;
border: none;
color: var(--gray-400);
border-radius: initial;
font-weight: 400;
margin: 0;
}
.verticalTabsContainer .p-tabview-nav li.p-tabview-selected {
background-color: var(--surface-50);
border-right: 4px solid var(--primary-color);
}
.verticalTabsContainer .p-tabview-nav li.p-tabview-selected .p-tabview-nav-link {
font-weight: 600;
color: var(--primary-color);
}
.verticalTabsContainer .p-tabview-nav li.p-tabview-selected:hover {
border-right: 4px solid var(--primary-color);
}
.verticalTabsContainer .p-tabview-panel {
flex-grow: 1;
}

View File

@@ -1,3 +0,0 @@
.active {
background-color: rgba(98, 98, 98, 0.33);
}

View File

@@ -1,3 +0,0 @@
.active {
background-color: rgba(98, 98, 98, 0.33);
}

View File

@@ -1,3 +0,0 @@
.active {
background-color: rgba(98, 98, 98, 0.33);
}

View File

@@ -1,3 +0,0 @@
.active {
background-color: rgba(98, 98, 98, 0.33);
}

View File

@@ -1,2 +1,3 @@
export * from './useSystemInfo';
export * from './useGetOwnOnlineCharacters';
export * from './useElementWidth';

View File

@@ -0,0 +1,43 @@
import { useState, useLayoutEffect, RefObject } from 'react';
/**
* useElementWidth
*
* A custom hook that accepts a ref to an HTML element and returns its current width.
* It uses a ResizeObserver and window resize listener to update the width when necessary.
*
* @param ref - A RefObject pointing to an HTML element.
* @returns The current width of the element.
*/
export function useElementWidth<T extends HTMLElement>(ref: RefObject<T>): number {
const [width, setWidth] = useState<number>(0);
useLayoutEffect(() => {
const updateWidth = () => {
if (ref.current) {
const newWidth = ref.current.getBoundingClientRect().width;
if (newWidth > 0) {
setWidth(newWidth);
}
}
};
updateWidth(); // Initial measurement
const observer = new ResizeObserver(() => {
const id = setTimeout(updateWidth, 100);
return () => clearTimeout(id);
});
if (ref.current) {
observer.observe(ref.current);
}
window.addEventListener("resize", updateWidth);
return () => {
observer.disconnect();
window.removeEventListener("resize", updateWidth);
};
}, [ref]);
return width;
}

View File

@@ -84,7 +84,6 @@ interface MapCompProps {
onCommand: OutCommandHandler;
onSelectionChange: OnMapSelectionChange;
onManualDelete(systems: string[]): void;
canRemoveConnection?(connectionId: string): boolean;
onConnectionInfoClick?(e: SolarSystemConnection): void;
onAddSystem?: OnMapAddSystemCallback;
onSelectionContextMenu?: NodeSelectionMouseHandler;
@@ -114,9 +113,8 @@ const MapComp = ({
isSoftBackground,
theme,
onAddSystem,
canRemoveConnection,
}: MapCompProps) => {
const { getEdge, getNode, getNodes } = useReactFlow();
const { getNode, getNodes } = useReactFlow();
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges);
@@ -128,7 +126,6 @@ const MapComp = ({
const { variant, gap, size, color } = useBackgroundVars(theme);
const { isPanAndDrag, nodeComponent, connectionMode } = getBehaviorForTheme(theme || 'default');
// You can create nodeTypes dynamically based on the node component
const nodeTypes = useMemo(() => {
return {
custom: nodeComponent,
@@ -225,40 +222,6 @@ const MapComp = ({
[getNode, getNodes, onManualDelete, onNodesChange],
);
const handleEdgesChange = useCallback(
(changes: EdgeChange[]) => {
const nextChanges = changes.reduce((acc, change) => {
if (change.type !== 'remove') {
return [...acc, change];
}
if (canRemoveConnection?.(change.id)) {
return [...acc, change];
}
const edge = getEdge(change.id);
if (!edge) {
return [...acc, change];
}
const sourceNode = getNode(edge.source);
const targetNode = getNode(edge.target);
if (!sourceNode || !targetNode) {
return [...acc, change];
}
if (sourceNode.data.locked || targetNode.data.locked) {
return acc;
}
return [...acc, change];
}, [] as EdgeChange[]);
onEdgesChange(nextChanges);
},
[getEdge, getNode, onEdgesChange],
);
useEffect(() => {
update(x => ({
...x,
@@ -274,7 +237,7 @@ const MapComp = ({
nodes={nodes}
edges={edges}
onNodesChange={handleNodesChange}
onEdgesChange={handleEdgesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
// TODO we need save into session all of this
// and on any action do either

View File

@@ -1,6 +1,6 @@
import React, { createContext, useContext } from 'react';
import { OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
import { MapUnionTypes } from '@/hooks/Mapper/types';
import { MapUnionTypes, SystemSignature } from '@/hooks/Mapper/types';
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
export type MapData = MapUnionTypes & {
@@ -30,10 +30,14 @@ const INITIAL_DATA: MapData = {
isConnecting: false,
connections: [],
hoverNodeId: null,
linkedSigEveId: '',
visibleNodes: new Set(),
showKSpaceBG: false,
isThickConnections: false,
userPermissions: {},
systemSignatures: {} as Record<string, SystemSignature[]>,
options: {} as Record<string, string | boolean>,
is_subscription_active: false,
};
export interface MapContextProps {

View File

@@ -0,0 +1,48 @@
.KillsBookmark {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 8px;
font-weight: 700;
border: 0;
border-radius: 5px 5px 0 0;
padding: 4px 3px;
}
.KillsBookmarkWithIcon {
display: flex;
align-items: center;
justify-content: center;
margin-top: -2px;
text-shadow: 0 0 3px #000;
padding-right: 2px;
height: 8px;
font-size: 8px;
line-height: 12px;
font-weight: 700;
text-size-adjust: 100%;
.pi {
font-size: 9px;
}
.text {
font-size: 9px;
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
}
.TooltipContainer {
background-color: #1a1a1a;
color: #fff;
padding: 3px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
border-radius: 2px;
pointer-events: auto;
max-width: 500px;
max-height: 300px;
overflow-y: auto;
overflow-x: hidden;
}

View File

@@ -0,0 +1,39 @@
import { SystemKillsContent } from '../../../mapInterface/widgets/SystemKills/SystemKillsContent/SystemKillsContent';
import { useKillsCounter } from '../../hooks/useKillsCounter';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { WithChildren, WithClassName } from '@/hooks/Mapper/types/common';
type TooltipSize = 'xs' | 'sm' | 'md' | 'lg';
type KillsBookmarkTooltipProps = {
killsCount: number;
killsActivityType: string | null;
systemId: string;
className?: string;
size?: TooltipSize;
} & WithChildren &
WithClassName;
export const KillsCounter = ({ killsCount, systemId, className, children, size = 'xs' }: KillsBookmarkTooltipProps) => {
const { isLoading, kills: detailedKills, systemNameMap } = useKillsCounter({ realSystemId: systemId });
if (!killsCount || detailedKills.length === 0 || !systemId || isLoading) return null;
const tooltipContent = (
<div style={{ width: '100%', minWidth: '300px', overflow: 'hidden' }}>
<SystemKillsContent
kills={detailedKills}
systemNameMap={systemNameMap}
onlyOneSystem={true}
autoSize={true}
limit={killsCount}
/>
</div>
);
return (
<WdTooltipWrapper content={tooltipContent} className={className} size={size} interactive={true}>
{children}
</WdTooltipWrapper>
);
};

View File

@@ -0,0 +1,54 @@
.TooltipActive {
pointer-events: auto !important;
position: relative;
z-index: 3;
}
.hoverTarget {
padding: 0.5rem;
margin: -0.5rem;
display: inline-block;
}
.localCounter {
mix-blend-mode: screen;
display: flex;
align-items: center;
gap: 1px;
position: relative;
top: 1px;
color: var(--rf-node-local-counter);
&.hasUserCharacters {
color: var(--rf-has-user-characters);
}
& > i {
font-size: 9px;
position: relative;
}
& > span {
font-size: 9px;
line-height: 9px;
font-weight: var(--rf-local-counter-font-weight, 500);
@-moz-document url-prefix() {
position: relative;
top: -1px;
}
}
}
.Pathfinder {
.localCounter {
@-moz-document url-prefix() {
top: 0;
}
& > span {
position: relative;
top: -1px;
}
}
}

View File

@@ -0,0 +1,61 @@
import { useMemo } from 'react';
import clsx from 'clsx';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit/WdTooltip';
import { CharItemProps, LocalCharactersList } from '../../../mapInterface/widgets/LocalCharacters/components';
import { useLocalCharactersItemTemplate } from '../../../mapInterface/widgets/LocalCharacters/hooks/useLocalCharacters';
import { useLocalCharacterWidgetSettings } from '../../../mapInterface/widgets/LocalCharacters/hooks/useLocalWidgetSettings';
import classes from './SolarSystemLocalCounter.module.scss';
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider';
import { useTheme } from '@/hooks/Mapper/hooks/useTheme.ts';
interface LocalCounterProps {
localCounterCharacters: Array<CharItemProps>;
hasUserCharacters: boolean;
showIcon?: boolean;
}
export const LocalCounter = ({ localCounterCharacters, hasUserCharacters, showIcon = true }: LocalCounterProps) => {
const [settings] = useLocalCharacterWidgetSettings();
const itemTemplate = useLocalCharactersItemTemplate(settings.showShipName);
const theme = useTheme();
const pilotTooltipContent = useMemo(() => {
return (
<div
style={{
width: '100%',
minWidth: '300px',
overflow: 'hidden',
}}
>
<LocalCharactersList items={localCounterCharacters} itemTemplate={itemTemplate} itemSize={26} autoSize={true} />
</div>
);
}, [localCounterCharacters, itemTemplate]);
if (localCounterCharacters.length === 0) {
return null;
}
return (
<div
className={clsx(classes.TooltipActive, {
[classes.Pathfinder]: theme === AvailableThemes.pathfinder,
})}
>
<WdTooltipWrapper content={pilotTooltipContent} position={TooltipPosition.right} offset={0} interactive={true}>
<div className={clsx(classes.hoverTarget)}>
<div
className={clsx(classes.localCounter, {
[classes.hasUserCharacters]: hasUserCharacters,
})}
>
{showIcon && <i className="pi pi-users" />}
<span>{localCounterCharacters.length}</span>
</div>
</div>
</WdTooltipWrapper>
</div>
);
};

View File

@@ -2,34 +2,30 @@
$pastel-blue: #5a7d9a;
$pastel-pink: #d291bc;
$pastel-green: #88b04b;
$pastel-yellow: #ffdd59;
$dark-bg: #2d2d2d;
$text-color: #ffffff;
$tooltip-bg: #202020;
$node-bg-color: #202020;
$node-soft-bg-color: #202020;
$text-color: #ffffff;
$tag-color: #38BDF8;
$region-name: #D6D3D1;
$custom-name: #93C5FD;
.RootCustomNode {
display: flex;
width: 130px;
height: 34px;
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
flex-direction: column;
padding: 2px 6px;
font-size: 10px;
background-color: $tooltip-bg;
background-color: var(--rf-node-bg-color, #202020) !important;
color: var(--rf-text-color, #ffffff);
box-shadow: 0 0 5px rgba($dark-bg, 0.5);
border: 1px solid darken($pastel-blue, 10%);
border-radius: 5px;
position: relative;
z-index: 1;
z-index: 3;
overflow: hidden;
&.Mataria,
@@ -92,21 +88,9 @@ $custom-name: #93C5FD;
box-shadow: 0 0 10px #9a1af1c2;
}
&.tooltip {
background-color: $tooltip-bg;
color: $text-color;
padding: 5px 10px;
border-radius: 3px;
border: 1px solid $pastel-pink;
}
&.eve-system-status-home {
border: 1px solid var(--eve-solar-system-status-color-home-dark30);
background-image: linear-gradient(
275deg,
var(--eve-solar-system-status-home),
transparent
);
background-image: linear-gradient(45deg, var(--eve-solar-system-status-color-background), transparent);
&.selected {
border-color: var(--eve-solar-system-status-color-home);
}
@@ -114,11 +98,7 @@ $custom-name: #93C5FD;
&.eve-system-status-friendly {
border: 1px solid var(--eve-solar-system-status-color-friendly-dark20);
background-image: linear-gradient(
275deg,
var(--eve-solar-system-status-friendly-dark30),
transparent
);
background-image: linear-gradient(275deg, var(--eve-solar-system-status-friendly-dark30), transparent);
&.selected {
border-color: var(--eve-solar-system-status-color-friendly-dark5);
}
@@ -133,27 +113,15 @@ $custom-name: #93C5FD;
}
&.eve-system-status-warning {
background-image: linear-gradient(
275deg,
var(--eve-solar-system-status-warning),
transparent
);
background-image: linear-gradient(275deg, var(--eve-solar-system-status-warning), transparent);
}
&.eve-system-status-dangerous {
background-image: linear-gradient(
275deg,
var(--eve-solar-system-status-dangerous),
transparent
);
background-image: linear-gradient(275deg, var(--eve-solar-system-status-dangerous), transparent);
}
&.eve-system-status-target {
background-image: linear-gradient(
275deg,
var(--eve-solar-system-status-target),
transparent
);
background-image: linear-gradient(275deg, var(--eve-solar-system-status-target), transparent);
}
}
@@ -178,8 +146,6 @@ $custom-name: #93C5FD;
padding-left: 3px;
padding-right: 3px;
//background-color: #833ca4;
&:not(:first-child) {
box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3);
}
@@ -266,26 +232,17 @@ $custom-name: #93C5FD;
.TagTitle {
font-size: 11px;
font-weight: medium;
font-weight: 500;
text-shadow: 0 0 2px rgba(231, 146, 52, 0.73);
color: var(--rf-tag-color, #38BDF8);
color: var(--rf-tag-color, #38bdf8);
}
/* Firefox kostyl */
@-moz-document url-prefix() {
.classSystemName {
font-family: inherit !important;
font-weight: bold;
}
}
.classSystemName {
//font-weight: bold;
}
.solarSystemName {
}
}
.BottomRow {
@@ -294,22 +251,23 @@ $custom-name: #93C5FD;
align-items: center;
height: 19px;
.localCounter {
display: flex;
//align-items: center;
gap: 2px;
& > i {
position: relative;
top: 1px;
.hasLocalCounter {
margin-right: 2px;
&.countAbove9 {
margin-right: 1.5rem;
}
}
& > span {
font-size: 9px;
line-height: 9px;
font-weight: 500;
//margin-top: 1px;
}
.lockIcon {
font-size: 0.45rem;
font-weight: bold;
position: relative;
}
.mapMarker {
font-size: 0.45rem;
font-weight: bold;
position: relative;
}
}
@@ -340,7 +298,8 @@ $custom-name: #93C5FD;
.Handlers {
position: absolute;
z-index: 2;
z-index: 4;
pointer-events: none;
top: 0;
left: 0;
width: 100%;
@@ -353,6 +312,7 @@ $custom-name: #93C5FD;
border: 1px solid $pastel-blue;
width: 5px;
height: 5px;
pointer-events: auto;
&.selected {
border-color: $pastel-pink;

View File

@@ -1,20 +1,24 @@
import { memo } from 'react';
import { MapSolarSystemType } from '../../map.types';
import { Handle, Position, NodeProps } from 'reactflow';
import { Handle, NodeProps, Position } from 'reactflow';
import clsx from 'clsx';
import classes from './SolarSystemNodeDefault.module.scss';
import { PrimeIcons } from 'primereact/api';
import { useSolarSystemNode } from '../../hooks/useSolarSystemNode';
import { useLocalCounter, useSolarSystemNode, useNodeKillsCount } from '../../hooks/useSolarSystemLogic';
import {
EFFECT_BACKGROUND_STYLES,
MARKER_BOOKMARK_BG_STYLES,
STATUS_CLASSES,
EFFECT_BACKGROUND_STYLES,
} from '@/hooks/Mapper/components/map/constants';
import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp';
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
import { LocalCounter } from './SolarSystemLocalCounter';
import { KillsCounter } from './SolarSystemKillsCounter';
export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>) => {
const nodeVars = useSolarSystemNode(props);
const { localCounterCharacters } = useLocalCounter(nodeVars);
const localKillsCount = useNodeKillsCount(nodeVars.solarSystemId, nodeVars.killsCount);
return (
<>
@@ -22,7 +26,7 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
<div className={classes.Bookmarks}>
{nodeVars.labelCustom !== '' && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] ">{nodeVars.labelCustom}</span>
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]">{nodeVars.labelCustom}</span>
</div>
)}
@@ -32,13 +36,19 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
</div>
)}
{nodeVars.killsCount && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}>
{localKillsCount && localKillsCount > 0 && nodeVars.solarSystemId && (
<KillsCounter
killsCount={localKillsCount}
systemId={nodeVars.solarSystemId}
size="lg"
killsActivityType={nodeVars.killsActivityType}
className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}
>
<div className={clsx(classes.BookmarkWithIcon)}>
<span className={clsx(PrimeIcons.BOLT, classes.icon)} />
<span className={clsx(classes.text)}>{nodeVars.killsCount}</span>
</div>
</div>
</KillsCounter>
)}
{nodeVars.labelsInfo.map(x => (
@@ -53,11 +63,10 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
className={clsx(
classes.RootCustomNode,
nodeVars.regionClass && classes[nodeVars.regionClass],
classes[STATUS_CLASSES[nodeVars.status]],
{
[classes.selected]: nodeVars.selected,
},
nodeVars.status !== undefined ? classes[STATUS_CLASSES[nodeVars.status]] : '',
{ [classes.selected]: nodeVars.selected },
)}
onMouseDownCapture={e => nodeVars.dbClick(e)}
>
{nodeVars.visible && (
<>
@@ -88,7 +97,7 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
{nodeVars.isWormhole && (
<div className={classes.statics}>
{nodeVars.sortedStatics.map(whClass => (
<WormholeClassComp key={whClass} id={whClass} />
<WormholeClassComp key={String(whClass)} id={String(whClass)} />
))}
</div>
)}
@@ -113,27 +122,18 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
{nodeVars.isWormhole && !nodeVars.customName && <div />}
<div className="flex items-center justify-end">
<div className="flex gap-1 items-center">
{nodeVars.locked && (
<i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem', fontWeight: 'bold' }} />
)}
{nodeVars.hubs.includes(nodeVars.solarSystemId.toString()) && (
<i className={PrimeIcons.MAP_MARKER} style={{ fontSize: '0.45rem', fontWeight: 'bold' }} />
)}
{nodeVars.charactersInSystem.length > 0 && (
<div
className={clsx(classes.localCounter, {
['text-amber-300']: nodeVars.hasUserCharacters,
})}
>
<i className="pi pi-users" style={{ fontSize: '0.50rem' }} />
<span className="font-sans">{nodeVars.charactersInSystem.length}</span>
</div>
<div className="flex items-center gap-1 justify-end">
<div className={clsx('flex items-center gap-1')}>
{nodeVars.locked && <i className={clsx(PrimeIcons.LOCK, classes.lockIcon)} />}
{nodeVars.hubs.includes(nodeVars.solarSystemId) && (
<i className={clsx(PrimeIcons.MAP_MARKER, classes.mapMarker)} />
)}
</div>
<LocalCounter
hasUserCharacters={nodeVars.hasUserCharacters}
localCounterCharacters={localCounterCharacters}
/>
</div>
</div>
</>
@@ -145,7 +145,7 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
{nodeVars.unsplashedLeft.length > 0 && (
<div className={classes.Unsplashed}>
{nodeVars.unsplashedLeft.map(sig => (
<UnsplashedSignature key={sig.sig_id} signature={sig} />
<UnsplashedSignature key={sig.eve_id} signature={sig} />
))}
</div>
)}
@@ -153,14 +153,14 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
{nodeVars.unsplashedRight.length > 0 && (
<div className={clsx(classes.Unsplashed, classes['Unsplashed--right'])}>
{nodeVars.unsplashedRight.map(sig => (
<UnsplashedSignature key={sig.sig_id} signature={sig} />
<UnsplashedSignature key={sig.eve_id} signature={sig} />
))}
</div>
)}
</>
)}
<div onMouseDownCapture={nodeVars.dbClick} className={classes.Handlers}>
<div className={classes.Handlers}>
<Handle
type="source"
className={clsx(classes.Handle, classes.HandleTop, {

View File

@@ -1,91 +1,20 @@
@import './SolarSystemNodeDefault.module.scss';
/* ---------------------------
Only override what's different
--------------------------- */
/* ---------------------------------------------
Only override what's different from the base
Currently none required
---------------------------------------------- */
/* 1) .RootCustomNode:
- new background-color using CSS var
- plus color, font-family, and font-weight */
.RootCustomNode {
background-color: var(--rf-node-bg-color, #202020) !important;
color: var(--rf-text-color, #ffffff);
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
/* 2) .Bookmarks:
- add var-based font family/weight
*/
.Bookmarks {
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
/* 3) .HeadRow, .classTitle, .classSystemName:
- add new references to var-based font family/weight
*/
.HeadRow {
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
.classTitle {
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
@-moz-document url-prefix() {
.classSystemName {
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
}
.classSystemName {
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
}
/* 4) .BottomRow:
- introduces .tagTitle, .regionName, .customName, .localCounter
referencing new CSS variables */
.BottomRow {
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
.tagTitle {
font-size: 11px;
font-weight: medium;
text-shadow: 0 0 2px rgba(231, 146, 52, 0.73);
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
color: var(--rf-tag-color, #38BDF8);
}
.regionName {
color: var(--rf-region-name, #D6D3D1);
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
.customName {
color: var(--rf-custom-name, #93C5FD);
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
.localCounter {
display: flex;
color: var(--rf-has-user-characters, #fbbf24);
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
gap: 2px;
.hasUserCharacters {
color: var(--rf-has-user-characters, #fbbf24);
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
&.eve-system-status-home {
border: 1px solid var(--eve-solar-system-status-color-home-dark30);
background-image: linear-gradient(
275deg,
var(--eve-solar-system-status-home),
transparent
);
&.selected {
border-color: var(--eve-solar-system-status-color-home);
}
}
}

View File

@@ -1,20 +1,24 @@
import { memo } from 'react';
import { MapSolarSystemType } from '../../map.types';
import { Handle, Position, NodeProps } from 'reactflow';
import { Handle, NodeProps, Position } from 'reactflow';
import clsx from 'clsx';
import classes from './SolarSystemNodeTheme.module.scss';
import { PrimeIcons } from 'primereact/api';
import { useSolarSystemNode } from '../../hooks/useSolarSystemNode';
import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks/useSolarSystemLogic';
import {
EFFECT_BACKGROUND_STYLES,
MARKER_BOOKMARK_BG_STYLES,
STATUS_CLASSES,
EFFECT_BACKGROUND_STYLES,
} from '@/hooks/Mapper/components/map/constants';
import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp';
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
import { LocalCounter } from './SolarSystemLocalCounter';
import { KillsCounter } from './SolarSystemKillsCounter';
export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>) => {
const nodeVars = useSolarSystemNode(props);
const { localCounterCharacters } = useLocalCounter(nodeVars);
const localKillsCount = useNodeKillsCount(nodeVars.solarSystemId, nodeVars.killsCount);
return (
<>
@@ -22,7 +26,7 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
<div className={classes.Bookmarks}>
{nodeVars.labelCustom !== '' && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] ">{nodeVars.labelCustom}</span>
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]">{nodeVars.labelCustom}</span>
</div>
)}
@@ -32,13 +36,19 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
</div>
)}
{nodeVars.killsCount && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}>
{localKillsCount && localKillsCount > 0 && nodeVars.solarSystemId && (
<KillsCounter
killsCount={localKillsCount}
systemId={nodeVars.solarSystemId}
size="lg"
killsActivityType={nodeVars.killsActivityType}
className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}
>
<div className={clsx(classes.BookmarkWithIcon)}>
<span className={clsx(PrimeIcons.BOLT, classes.icon)} />
<span className={clsx(classes.text)}>{nodeVars.killsCount}</span>
</div>
</div>
</KillsCounter>
)}
{nodeVars.labelsInfo.map(x => (
@@ -53,11 +63,10 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
className={clsx(
classes.RootCustomNode,
nodeVars.regionClass && classes[nodeVars.regionClass],
classes[STATUS_CLASSES[nodeVars.status]],
{
[classes.selected]: nodeVars.selected,
},
nodeVars.status !== undefined ? classes[STATUS_CLASSES[nodeVars.status]] : '',
{ [classes.selected]: nodeVars.selected },
)}
onMouseDownCapture={e => nodeVars.dbClick(e)}
>
{nodeVars.visible && (
<>
@@ -88,7 +97,7 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
{nodeVars.isWormhole && (
<div className={classes.statics}>
{nodeVars.sortedStatics.map(whClass => (
<WormholeClassComp key={whClass} id={whClass} />
<WormholeClassComp key={String(whClass)} id={String(whClass)} />
))}
</div>
)}
@@ -123,27 +132,18 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
{nodeVars.isWormhole && !nodeVars.customName && <div />}
<div className="flex items-center justify-end">
<div className="flex gap-1 items-center">
{nodeVars.locked && (
<i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem', fontWeight: 'bold' }} />
)}
{nodeVars.hubs.includes(nodeVars.solarSystemId.toString()) && (
<i className={PrimeIcons.MAP_MARKER} style={{ fontSize: '0.45rem', fontWeight: 'bold' }} />
)}
{nodeVars.charactersInSystem.length > 0 && (
<div
className={clsx(classes.localCounter, {
[classes.hasUserCharacters]: nodeVars.hasUserCharacters,
})}
>
<i className="pi pi-users" style={{ fontSize: '0.50rem' }} />
<span className="font-sans">{nodeVars.charactersInSystem.length}</span>
</div>
<div className="flex items-center gap-1 justify-end">
<div className={clsx('flex items-center gap-1')}>
{nodeVars.locked && <i className={clsx(PrimeIcons.LOCK, classes.lockIcon)} />}
{nodeVars.hubs.includes(nodeVars.solarSystemId) && (
<i className={clsx(PrimeIcons.MAP_MARKER, classes.mapMarker)} />
)}
</div>
<LocalCounter
hasUserCharacters={nodeVars.hasUserCharacters}
localCounterCharacters={localCounterCharacters}
/>
</div>
</div>
</>
@@ -155,7 +155,7 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
{nodeVars.unsplashedLeft.length > 0 && (
<div className={classes.Unsplashed}>
{nodeVars.unsplashedLeft.map(sig => (
<UnsplashedSignature key={sig.sig_id} signature={sig} />
<UnsplashedSignature key={sig.eve_id} signature={sig} />
))}
</div>
)}
@@ -163,14 +163,14 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
{nodeVars.unsplashedRight.length > 0 && (
<div className={clsx(classes.Unsplashed, classes['Unsplashed--right'])}>
{nodeVars.unsplashedRight.map(sig => (
<UnsplashedSignature key={sig.sig_id} signature={sig} />
<UnsplashedSignature key={sig.eve_id} signature={sig} />
))}
</div>
)}
</>
)}
<div onMouseDownCapture={nodeVars.dbClick} className={classes.Handlers}>
<div className={classes.Handlers}>
<Handle
type="source"
className={clsx(classes.Handle, classes.HandleTop, {

View File

@@ -10,5 +10,6 @@ export const convertSystem2Node = (sys: SolarSystemRawType): Node => {
position: sys.position,
data: sys,
draggable: !sys.locked,
deletable: !sys.locked,
};
};

View File

@@ -6,6 +6,7 @@ export function useBackgroundVars(themeName?: string) {
const [gap, setGap] = useState<number>(16);
const [size, setSize] = useState<number>(1);
const [color, setColor] = useState('#81818b');
const [snapSize, setSnapSize] = useState<number>(25);
useEffect(() => {
// match any element whose entire `class` attribute ends with "-theme"
@@ -29,16 +30,19 @@ export function useBackgroundVars(themeName?: string) {
const cssVarGap = style.getPropertyValue('--rf-bg-gap');
const cssVarSize = style.getPropertyValue('--rf-bg-size');
const cssVarSnapSize = style.getPropertyValue('--rf-snap-size');
const cssColor = style.getPropertyValue('--rf-bg-pattern-color');
const gapNum = parseInt(cssVarGap, 10) || 16;
const sizeNum = parseInt(cssVarSize, 10) || 1;
const snapSize = parseInt(cssVarSnapSize, 10) || 25; //react-flow default
setVariant(finalVariant);
setGap(gapNum);
setSize(sizeNum);
setColor(cssColor);
setSnapSize(snapSize);
}, [themeName]);
return { variant, gap, size, color };
return { variant, gap, size, color, snapSize };
}

View File

@@ -0,0 +1,44 @@
import { useMemo } from 'react';
import { useSystemKills } from '../../mapInterface/widgets/SystemKills/hooks/useSystemKills';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
interface UseKillsCounterProps {
realSystemId: string;
}
export function useKillsCounter({ realSystemId }: UseKillsCounterProps) {
const { data: mapData, outCommand } = useMapRootState();
const { systems } = mapData;
const systemNameMap = useMemo(() => {
const m: Record<string, string> = {};
systems.forEach(sys => {
m[sys.id] = sys.temporary_name || sys.name || '???';
});
return m;
}, [systems]);
const { kills: allKills, isLoading } = useSystemKills({
systemId: realSystemId,
outCommand,
showAllVisible: false,
});
const filteredKills = useMemo(() => {
if (!allKills || allKills.length === 0) return [];
return [...allKills]
.sort((a, b) => {
const aTime = a.kill_time ? new Date(a.kill_time).getTime() : 0;
const bTime = b.kill_time ? new Date(b.kill_time).getTime() : 0;
return bTime - aTime;
})
.slice(0, 10);
}, [allKills]);
return {
isLoading,
kills: filteredKills,
systemNameMap,
};
}

View File

@@ -127,6 +127,10 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
// do nothing here
break;
case Commands.detailedKillsUpdated:
// do nothing here
break;
default:
console.warn(`Map handlers: Unknown command: ${type}`, data);
break;

View File

@@ -1,4 +1,4 @@
import { useMemo } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { MapSolarSystemType } from '../map.types';
import { NodeProps } from 'reactflow';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
@@ -10,10 +10,18 @@ import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormhol
import { getSystemClassStyles, prepareUnsplashedChunks } from '@/hooks/Mapper/components/map/helpers';
import { sortWHClasses } from '@/hooks/Mapper/helpers';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager';
import { CharacterTypeRaw, OutCommand } from '@/hooks/Mapper/types';
import { CharacterTypeRaw, Commands, OutCommand, SystemSignature } from '@/hooks/Mapper/types';
import { LABELS_INFO, LABELS_ORDER } from '@/hooks/Mapper/components/map/constants';
import { useMapEventListener } from '@/hooks/Mapper/events';
function getActivityType(count: number) {
export type LabelInfo = {
id: string;
shortName: string;
};
export type UnsplashedSignatureType = SystemSignature & { sig_id: string };
function getActivityType(count: number): string {
if (count <= 5) return 'activityNormal';
if (count <= 30) return 'activityWarn';
return 'activityDanger';
@@ -26,12 +34,25 @@ const SpaceToClass: Record<string, string> = {
[Spaces.Gallente]: 'Gallente',
};
function sortedLabels(labels: string[]) {
function sortedLabels(labels: string[]): LabelInfo[] {
if (!labels) return [];
return LABELS_ORDER.filter(x => labels.includes(x)).map(x => LABELS_INFO[x]);
return LABELS_ORDER.filter(x => labels.includes(x)).map(x => LABELS_INFO[x] as LabelInfo);
}
export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>) {
export function useLocalCounter(nodeVars: SolarSystemNodeVars) {
const localCounterCharacters = useMemo(() => {
return nodeVars.charactersInSystem
.map(char => ({
...char,
compact: true,
isOwn: nodeVars.userCharacters.includes(char.eve_id),
}))
.sort((a, b) => a.name.localeCompare(b.name));
}, [nodeVars.charactersInSystem, nodeVars.userCharacters]);
return { localCounterCharacters };
}
export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarSystemNodeVars {
const { id, data, selected } = props;
const {
system_static_info,
@@ -71,7 +92,6 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>) {
const {
data: {
characters,
presentCharacters,
wormholesData,
hubs,
kills,
@@ -87,15 +107,14 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>) {
const visible = useMemo(() => visibleNodes.has(id), [id, visibleNodes]);
const systemSignatures = useMemo(
const systemSigs = useMemo(
() => mapSystemSignatures[solar_system_id] || system_signatures,
[system_signatures, solar_system_id, mapSystemSignatures],
);
const charactersInSystem = useMemo(() => {
return characters.filter(c => c.location?.solar_system_id === solar_system_id).filter(c => c.online);
// eslint-disable-next-line
}, [characters, presentCharacters, solar_system_id]);
return characters.filter(c => c.location?.solar_system_id === solar_system_id && c.online);
}, [characters, solar_system_id]);
const isWormhole = isWormholeSpace(system_class);
@@ -136,52 +155,64 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>) {
const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
const regionClass = showKSpaceBG ? SpaceToClass[space] : null;
const temporaryName = useMemo(() => {
const computedTemporaryName = useMemo(() => {
if (!isTempSystemNameEnabled) {
return '';
}
if (isShowLinkedSigIdTempName && linkedSigPrefix) {
return temporary_name ? `${linkedSigPrefix}${temporary_name}` : `${linkedSigPrefix}${solar_system_name}`;
}
return temporary_name;
}, [isShowLinkedSigIdTempName, isTempSystemNameEnabled, linkedSigPrefix, solar_system_name, temporary_name]);
const systemName = useMemo(() => {
if (isTempSystemNameEnabled && temporaryName) {
return temporaryName;
if (isTempSystemNameEnabled && computedTemporaryName) {
return computedTemporaryName;
}
return solar_system_name;
}, [isTempSystemNameEnabled, solar_system_name, temporaryName]);
}, [isTempSystemNameEnabled, solar_system_name, computedTemporaryName]);
const customName = (isTempSystemNameEnabled && temporaryName && name) || (solar_system_name !== name && name) || null;
const customName = useMemo(() => {
if (isTempSystemNameEnabled && computedTemporaryName && name) {
return name;
}
if (solar_system_name !== name && name) {
return name;
}
return null;
}, [isTempSystemNameEnabled, computedTemporaryName, name, solar_system_name]);
const [unsplashedLeft, unsplashedRight] = useMemo(() => {
if (!isShowUnsplashedSignatures) {
return [[], []];
}
return prepareUnsplashedChunks(
systemSignatures
systemSigs
.filter(s => s.group === 'Wormhole' && !s.linked_system)
.map(s => ({
eve_id: s.eve_id,
type: s.type,
custom_info: s.custom_info,
})),
kind: s.kind,
name: s.name,
group: s.group,
})) as UnsplashedSignatureType[],
);
}, [isShowUnsplashedSignatures, systemSignatures]);
}, [isShowUnsplashedSignatures, systemSigs]);
const nodeVars = {
// Ensure hubs are always strings.
const hubsAsStrings = useMemo(() => hubs.map(item => item.toString()), [hubs]);
const nodeVars: SolarSystemNodeVars = {
id,
selected,
visible,
isWormhole,
classTitleColor,
killsCount,
killsActivityType,
hasUserCharacters,
userCharacters,
showHandlers,
regionClass,
systemName,
@@ -195,10 +226,10 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>) {
sortedStatics,
effectName: effect_name,
regionName: region_name,
solarSystemId: solar_system_id,
solarSystemId: solar_system_id.toString(),
solarSystemName: solar_system_name,
locked,
hubs,
hubs: hubsAsStrings,
name: name,
isConnecting,
hoverNodeId,
@@ -207,7 +238,7 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>) {
unsplashedRight,
isThickConnections,
classTitle: class_title,
temporaryName: temporary_name,
temporaryName: computedTemporaryName,
};
return nodeVars;
@@ -230,25 +261,45 @@ export interface SolarSystemNodeVars {
isShattered: boolean;
tag?: string | null;
status?: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
labelsInfo: Array<any>;
dbClick: (event?: void) => void;
labelsInfo: LabelInfo[];
dbClick: (event: React.MouseEvent<HTMLDivElement>) => void;
sortedStatics: Array<string | number>;
effectName: string | null;
regionName: string | null;
solarSystemId: number;
solarSystemId: string;
solarSystemName: string | null;
locked: boolean;
hubs: string[] | number[];
hubs: string[];
name: string | null;
isConnecting: boolean;
hoverNodeId: string | null;
charactersInSystem: Array<CharacterTypeRaw>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
unsplashedLeft: Array<any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
unsplashedRight: Array<any>;
userCharacters: string[];
unsplashedLeft: Array<SystemSignature>;
unsplashedRight: Array<SystemSignature>;
isThickConnections: boolean;
classTitle: string | null;
temporaryName?: string | null;
}
export function useNodeKillsCount(systemId: number | string, initialKillsCount: number | null): number | null {
const [killsCount, setKillsCount] = useState<number | null>(initialKillsCount);
useEffect(() => {
setKillsCount(initialKillsCount);
}, [initialKillsCount]);
useMapEventListener(event => {
if (event.name === Commands.killsUpdated && event.data?.toString() === systemId.toString()) {
//@ts-ignore
if (event.payload && typeof event.payload.kills === 'number') {
// @ts-ignore
setKillsCount(event.payload.kills);
}
return true;
}
return false;
});
return killsCount;
}

View File

@@ -11,7 +11,8 @@
--rf-tag-color: #38BDF8;
--rf-region-name: #D6D3D1;
--rf-custom-name: #93C5FD;
--rf-node-font-family: 'Shentox', 'Rogan', sans-serif !important;
--rf-node-font-weight: 500;
--rf-bg-variant: "dots";
--rf-bg-gap: 15;
@@ -28,4 +29,8 @@
--tooltip-bg: #202020;
--window-corner: #72716f;
--rf-local-counter-font-weight: 500;
--rf-node-local-counter: inherit;
--rf-has-user-characters: #ffc75d;
}

View File

@@ -1,19 +1,19 @@
$friendlyBase: #3bbd39;
$friendlyAlpha: #3bbd3952;
$friendlyBase: #3bbd39;
$friendlyAlpha: #3bbd3952;
$friendlyDark20: darken($friendlyBase, 20%);
$friendlyDark30: darken($friendlyBase, 30%);
$friendlyDark5: darken($friendlyBase, 5%);
$lookingForBase: #43c2fd;
$lookingForBase: #43c2fd;
$lookingForAlpha: rgba(67, 176, 253, 0.48);
$lookingForDark15: darken($lookingForBase, 15%);
$homeBase: rgb(197, 253, 67);
$homeAlpha: rgba(197, 253, 67, 0.32);
$homeBase: rgb(179, 253, 67);
$homeAlpha: rgba(186, 248, 48, 0.32);
$homeBackground: #a0fa5636;
$homeDark30: darken($homeBase, 30%);
:root {
--pastel-blue: #5a7d9a;
--pastel-pink: #d291bc;
@@ -98,6 +98,7 @@ $homeDark30: darken($homeBase, 30%);
--eve-solar-system-status-color-unknown: transparent;
--eve-solar-system-status-home: #{$homeAlpha};
--eve-solar-system-status-color-home: #{$homeBase};
--eve-solar-system-status-color-background: #{$homeBackground};
--eve-solar-system-status-color-home-dark30: #{$homeDark30};
--eve-solar-system-status-friendly: #{$friendlyAlpha};
--eve-solar-system-status-color-friendly: #{$friendlyBase};

View File

@@ -2,24 +2,31 @@
@import './eve-common';
@import url('https://fonts.googleapis.com/css2?family=Oxygen:wght@300;400;700&display=swap');
$homeBase: rgb(197, 253, 67);
$homeAlpha: rgba(197, 253, 67, 0.32);
$homeDark30: darken($homeBase, 30%);
.pathfinder-theme {
/* -- Override values from the default theme -- */
--rf-bg-color: #000000;
--rf-soft-bg-color: #282828;
--rf-node-bg-color: #202020;
--rf-node-soft-bg-color: #313335;
--rf-node-font-weight: bold;
--rf-text-color: #adadad;
--rf-region-name: var(--rf-text-color);
--rf-custom-name: var(--rf-text-color);
--tooltip-bg: #202020;
--rf-bg-variant: "lines";
--rf-bg-gap: 32;
--rf-bg-size: 1;
--rf-bg-gap: 34;
--rf-snap-size: 17;
--rf-bg-pattern-color: #313131;
--rf-local-counter-font-weight: 700;
/* Additional node-specific overrides */
--rf-node-line-height: normal;
--rf-node-font-family: 'Oxygen', sans-serif;
--rf-tag-color: #fbbf24;
/* -- theme-specific variables -- */
--eve-effect-pulsar: #428bca;
--eve-effect-magnetar: #e06fdf;
--eve-effect-wolfRayet: #e28a0d;
@@ -39,13 +46,9 @@
--eve-wh-type-color-c13: #7986cb;
--eve-wh-type-color-drifter: #44aa82;
--rf-node-font-weight: bold;
--rf-node-line-height: normal;
--rf-node-font-family: 'Oxygen', sans-serif;
--rf-node-text-color: var(--pf-text-color);
--rf-node-local-counter: #5cb85c;
--rf-has-user-characters: #ffc75d;
--rf-tag-color: #fbbf24;
--rf-has-user-characters: #5cb85c;
--window-corner: #72716f;
--eve-solar-system-status-home: #{$homeAlpha};
--eve-solar-system-status-color-home: #{$homeBase};
}

View File

@@ -1,5 +1,3 @@
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
import { useMemo } from 'react';
import { WindowManager } from '@/hooks/Mapper/components/ui-kit/WindowManager';
import { DEFAULT_WIDGETS } from '@/hooks/Mapper/components/mapInterface/constants.tsx';
@@ -21,5 +19,12 @@ export const MapInterface = () => {
.filter(x => windowsSettings.visible.some(j => x.id === j));
}, [windowsSettings]);
return <WindowManager windows={items} dragSelector=".react-grid-dragHandleExample" onChange={updateWidgetSettings} />;
return (
<WindowManager
windows={items}
viewPort={windowsSettings.viewPort}
dragSelector=".react-grid-dragHandleExample"
onChange={updateWidgetSettings}
/>
);
};

View File

@@ -5,6 +5,7 @@ import {
SystemInfo,
SystemSignatures,
SystemStructures,
SystemKills,
} from '@/hooks/Mapper/components/mapInterface/widgets';
export const CURRENT_WINDOWS_VERSION = 8;
@@ -16,6 +17,7 @@ export enum WidgetsIds {
local = 'local',
routes = 'routes',
structures = 'structures',
kills = 'kills',
}
export const STORED_VISIBLE_WIDGETS_DEFAULT = [
@@ -61,6 +63,13 @@ export const DEFAULT_WIDGETS: WindowProps[] = [
zIndex: 0,
content: () => <SystemStructures />,
},
{
id: WidgetsIds.kills,
position: { x: 270, y: 730 },
size: { width: 510, height: 200 },
zIndex: 0,
content: () => <SystemKills />,
},
];
type WidgetsCheckboxesType = {
@@ -89,4 +98,8 @@ export const WIDGETS_CHECKBOXES_PROPS: WidgetsCheckboxesType = [
id: WidgetsIds.structures,
label: 'Structures',
},
{
id: WidgetsIds.kills,
label: 'Kills',
},
];

View File

@@ -1,13 +1,3 @@
.VirtualScroller {
height: 100% !important;
}
.CharacterRow {
//border-left-width: 1px;
&.CardBorderLeftIsOwn {
border-left-color: rgb(251 146 60 / 1)
}
}

View File

@@ -1,130 +1,71 @@
import { useCallback, useMemo, useRef } from 'react';
import { useMemo } from 'react';
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import clsx from 'clsx';
import classes from './LocalCharacters.module.scss';
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
import { CharacterCard, LayoutEventBlocker, WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
import { sortCharacters } from '@/hooks/Mapper/components/mapInterface/helpers/sortCharacters.ts';
import useLocalStorageState from 'use-local-storage-state';
import { sortCharacters } from '@/hooks/Mapper/components/mapInterface/helpers/sortCharacters';
import { useMapCheckPermissions, useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
type CharItemProps = {
compact: boolean;
} & CharacterTypeRaw &
WithIsOwnCharacter;
const useItemTemplate = () => {
const {
data: { presentCharacters },
} = useMapRootState();
return useCallback(
(char: CharItemProps, options: VirtualScrollerTemplateOptions) => {
return (
<div
className={clsx(classes.CharacterRow, 'w-full box-border', {
'surface-hover': options.odd,
['border-b border-gray-600 border-opacity-20']: !options.last,
['bg-green-500 hover:bg-green-700 transition duration-300 bg-opacity-10 hover:bg-opacity-10']: char.online,
})}
style={{ height: options.props.itemSize + 'px' }}
>
<CharacterCard showShipName {...char} />
</div>
);
},
// eslint-disable-next-line
[presentCharacters],
);
};
type WindowLocalSettingsType = {
compact: boolean;
showOffline: boolean;
version: number;
};
const STORED_DEFAULT_VALUES: WindowLocalSettingsType = {
compact: true,
showOffline: false,
version: 0,
};
import { UserPermission } from '@/hooks/Mapper/types/permissions';
import { LocalCharactersList } from './components/LocalCharactersList';
import { useLocalCharactersItemTemplate } from './hooks/useLocalCharacters';
import { useLocalCharacterWidgetSettings } from './hooks/useLocalWidgetSettings';
import { LocalCharactersHeader } from './components/LocalCharactersHeader';
import classes from './LocalCharacters.module.scss';
import clsx from 'clsx';
export const LocalCharacters = () => {
const {
data: { characters, userCharacters, selectedSystems, presentCharacters },
data: { characters, userCharacters, selectedSystems },
} = useMapRootState();
const [settings, setSettings] = useLocalStorageState<WindowLocalSettingsType>('window:local:settings', {
defaultValue: STORED_DEFAULT_VALUES,
});
const [settings, setSettings] = useLocalCharacterWidgetSettings();
const [systemId] = selectedSystems;
const restrictOfflineShowing = useMapGetOption('restrict_offline_showing');
const isAdminOrManager = useMapCheckPermissions([UserPermission.MANAGE_MAP]);
const showOffline = useMemo(
() => !restrictOfflineShowing || isAdminOrManager,
[isAdminOrManager, restrictOfflineShowing],
);
const itemTemplate = useItemTemplate();
const sorted = useMemo(() => {
const sorted = characters
const filtered = characters
.filter(x => x.location?.solar_system_id?.toString() === systemId)
.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id), compact: settings.compact }))
.map(x => ({
...x,
isOwn: userCharacters.includes(x.eve_id),
compact: settings.compact,
showShipName: settings.showShipName,
}))
.sort(sortCharacters);
if (!showOffline || !settings.showOffline) {
return sorted.filter(c => c.online);
return filtered.filter(c => c.online);
}
return sorted;
// eslint-disable-next-line
}, [showOffline, characters, settings.showOffline, settings.compact, systemId, userCharacters, presentCharacters]);
return filtered;
}, [
characters,
systemId,
userCharacters,
settings.compact,
settings.showOffline,
settings.showShipName,
showOffline,
]);
const isNobodyHere = sorted.length === 0;
const isNotSelectedSystem = selectedSystems.length !== 1;
const showList = sorted.length > 0 && selectedSystems.length === 1;
const ref = useRef<HTMLDivElement>(null);
const compact = useMaxWidth(ref, 145);
const itemTemplate = useLocalCharactersItemTemplate(settings.showShipName);
return (
<Widget
label={
<div className="flex justify-between items-center text-xs w-full" ref={ref}>
<span className="select-none">Local{showList ? ` [${sorted.length}]` : ''}</span>
<LayoutEventBlocker className="flex items-center gap-2">
{showOffline && (
<WdTooltipWrapper content="Show offline characters in system">
<WdCheckbox
size="xs"
labelSide="left"
label={compact ? '' : 'Show offline'}
value={settings.showOffline}
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300"
onChange={() => setSettings(() => ({ ...settings, showOffline: !settings.showOffline }))}
/>
</WdTooltipWrapper>
)}
<span
className={clsx('w-4 h-4 cursor-pointer', {
['hero-bars-2']: settings.compact,
['hero-bars-3']: !settings.compact,
})}
onClick={() => setSettings(() => ({ ...settings, compact: !settings.compact }))}
></span>
</LayoutEventBlocker>
</div>
<LocalCharactersHeader
sortedCount={sorted.length}
showList={showList}
showOffline={showOffline}
settings={settings}
setSettings={setSettings}
/>
}
>
{isNotSelectedSystem && (
@@ -132,23 +73,20 @@ export const LocalCharacters = () => {
System is not selected
</div>
)}
{isNobodyHere && !isNotSelectedSystem && (
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">
Nobody here
</div>
)}
{showList && (
<VirtualScroller
<LocalCharactersList
items={sorted}
itemSize={settings.compact ? 26 : 41}
itemTemplate={itemTemplate}
className={clsx(
classes.VirtualScroller,
containerClassName={clsx(
'w-full h-full overflow-x-hidden overflow-y-auto custom-scrollbar select-none',
classes.VirtualScroller,
)}
autoSize={false}
/>
)}
</Widget>

View File

@@ -0,0 +1,76 @@
import React, { useRef } from 'react';
import clsx from 'clsx';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth';
import { LayoutEventBlocker, TooltipPosition, WdCheckbox, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
interface LocalCharactersHeaderProps {
sortedCount: number;
showList: boolean;
showOffline: boolean;
settings: {
compact: boolean;
showOffline: boolean;
showShipName: boolean;
};
setSettings: (fn: (prev: any) => any) => void;
}
export const LocalCharactersHeader: React.FC<LocalCharactersHeaderProps> = ({
sortedCount,
showList,
showOffline,
settings,
setSettings,
}) => {
const headerRef = useRef<HTMLDivElement>(null);
const compactOffline = useMaxWidth(headerRef, 145);
const compactShipName = useMaxWidth(headerRef, 195);
return (
<div className="flex w-full items-center text-xs justify-between" ref={headerRef}>
<div className="flex-shrink-0 select-none mr-2">Local{showList ? ` [${sortedCount}]` : ''}</div>
<LayoutEventBlocker className="flex items-center gap-2 justify-end">
<div className="flex items-center gap-2">
{showOffline && (
<WdTooltipWrapper content="Show offline characters in system" position={TooltipPosition.top}>
<WdCheckbox
size="xs"
labelSide="left"
label={compactOffline ? '' : 'Offline'}
value={settings.showOffline}
onChange={() => setSettings((prev: any) => ({ ...prev, showOffline: !prev.showOffline }))}
classNameLabel={clsx('whitespace-nowrap text-stone-400 hover:text-stone-200 transition duration-300', {
truncate: compactOffline,
})}
/>
</WdTooltipWrapper>
)}
{settings.compact && (
<WdTooltipWrapper content="Show ship name in compact rows" position={TooltipPosition.top}>
<WdCheckbox
size="xs"
labelSide="left"
label={compactShipName ? '' : 'Ship name'}
value={settings.showShipName}
onChange={() => setSettings((prev: any) => ({ ...prev, showShipName: !prev.showShipName }))}
classNameLabel={clsx('whitespace-nowrap text-stone-400 hover:text-stone-200 transition duration-300', {
truncate: compactShipName,
})}
/>
</WdTooltipWrapper>
)}
</div>
<WdTooltipWrapper content="Enable compact mode" position={TooltipPosition.top}>
<span
className={clsx('w-4 h-4 min-w-[1rem] cursor-pointer', {
'hero-bars-2': settings.compact,
'hero-bars-3': !settings.compact,
})}
onClick={() => setSettings((prev: any) => ({ ...prev, compact: !prev.compact }))}
/>
</WdTooltipWrapper>
</LayoutEventBlocker>
</div>
);
};

View File

@@ -0,0 +1,6 @@
.CharacterRow {
&.CardBorderLeftIsOwn {
border-left-color: rgb(251 146 60 / 1)
}
}

View File

@@ -0,0 +1,27 @@
import classes from './LocalCharactersItemTemplate.module.scss';
import clsx from 'clsx';
import { CharacterCard } from '@/hooks/Mapper/components/ui-kit';
import { CharItemProps } from '@/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components';
import { VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
export type LocalCharactersItemTemplateProps = { showShipName: boolean } & CharItemProps &
VirtualScrollerTemplateOptions;
export const LocalCharactersItemTemplate = ({ showShipName, ...options }: LocalCharactersItemTemplateProps) => {
return (
<div
className={clsx(
classes.CharacterRow,
'box-border flex items-center w-full whitespace-nowrap overflow-hidden text-ellipsis min-w-[0px]',
{
'surface-hover': options.odd,
'border-b border-gray-600 border-opacity-20': !options.last,
'bg-green-500 hover:bg-green-700 transition duration-300 bg-opacity-10 hover:bg-opacity-10': options.online,
},
)}
style={{ height: `${options.props.itemSize}px` }}
>
<CharacterCard showShipName={showShipName} {...options} />
</div>
);
};

View File

@@ -0,0 +1 @@
export * from './LocalCharactersItemTemplate.tsx';

View File

@@ -0,0 +1,44 @@
import React from 'react';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import clsx from 'clsx';
import { CharItemProps } from './types';
export type LocalCharactersListProps = {
items: Array<CharItemProps>;
itemSize: number;
itemTemplate: (char: CharItemProps, options: VirtualScrollerTemplateOptions) => React.ReactNode;
containerClassName?: string;
style?: React.CSSProperties;
autoSize?: boolean;
};
export const LocalCharactersList = ({
items,
itemSize,
itemTemplate,
containerClassName,
style = {},
autoSize = false,
}: LocalCharactersListProps) => {
const computedHeight = autoSize ? `${Math.max(items.length, 1) * itemSize}px` : style.height || '100%';
const localStyle: React.CSSProperties = {
...style,
height: computedHeight,
width: '100%',
boxSizing: 'border-box',
overflowX: 'hidden',
};
return (
<VirtualScroller
items={items}
itemSize={itemSize}
orientation="vertical"
className={clsx('w-full h-full', containerClassName)}
itemTemplate={itemTemplate}
autoSize={autoSize}
style={localStyle}
/>
);
};

View File

@@ -0,0 +1,3 @@
export * from './LocalCharactersItemTemplate';
export * from './LocalCharactersList';
export * from './types';

View File

@@ -0,0 +1,6 @@
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
export type CharItemProps = {
compact: boolean;
} & CharacterTypeRaw &
WithIsOwnCharacter;

View File

@@ -0,0 +1,12 @@
import { useCallback } from 'react';
import { VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import { CharItemProps, LocalCharactersItemTemplate } from '../components';
export function useLocalCharactersItemTemplate(showShipName: boolean) {
return useCallback(
(char: CharItemProps, options: VirtualScrollerTemplateOptions) => (
<LocalCharactersItemTemplate {...char} {...options} showShipName={showShipName} />
),
[showShipName],
);
}

View File

@@ -0,0 +1,21 @@
import useLocalStorageState from 'use-local-storage-state';
export interface LocalCharacterWidgetSettings {
compact: boolean;
showOffline: boolean;
version: number;
showShipName: boolean;
}
export const LOCAL_CHARACTER_WIDGET_DEFAULT: LocalCharacterWidgetSettings = {
compact: true,
showOffline: false,
version: 0,
showShipName: false,
};
export function useLocalCharacterWidgetSettings() {
return useLocalStorageState<LocalCharacterWidgetSettings>('kills:widget:settings', {
defaultValue: LOCAL_CHARACTER_WIDGET_DEFAULT,
});
}

View File

@@ -1,7 +1,7 @@
import classes from './RoutesList.module.scss';
import { Route, SystemStaticInfoShort } from '@/hooks/Mapper/types/routes.ts';
import clsx from 'clsx';
import { SystemViewStandalone, WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit';
import { SystemViewStandalone, TooltipPosition, WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit';
import { getBackgroundClass, getShapeClass } from '@/hooks/Mapper/components/map/helpers';
import { MouseEvent, useCallback, useRef, useState } from 'react';
import { Commands } from '@/hooks/Mapper/types';
@@ -46,9 +46,11 @@ export const RouteSystem = ({
<>
<WdTooltip
ref={tooltipRef}
position={TooltipPosition.top}
// targetSelector={`.tooltip-route-sys_${destination}_${solar_system_id}`}
content={() => (
<SystemViewStandalone
className="mx-[4px]"
security={security}
system_class={system_class}
class_title={class_title}
@@ -63,8 +65,8 @@ export const RouteSystem = ({
tooltipRef.current?.show(e);
onMouseEnter?.(solar_system_id);
}}
onMouseLeave={e => {
tooltipRef.current?.hide(e);
onMouseLeave={() => {
tooltipRef.current?.hide();
onMouseLeave?.();
}}
onContextMenu={handleContext}

View File

@@ -184,7 +184,7 @@ export const RoutesWidgetComp = () => {
}, [data, update]);
const ref = useRef<HTMLDivElement>(null);
const compact = useMaxWidth(ref, 155);
const compact = useMaxWidth(ref, 170);
const [openAddSystem, setOpenAddSystem] = useState<boolean>(false);
const onAddSystem = useCallback(() => setOpenAddSystem(true), []);
@@ -217,14 +217,14 @@ export const RoutesWidgetComp = () => {
}}
/>
<WdTooltipWrapper content="Show shortest route">
<WdTooltipWrapper content="Show shortest route" position={TooltipPosition.top}>
<WdCheckbox
size="xs"
labelSide="left"
label={compact ? '' : 'Show shortest'}
value={!isSecure}
onChange={handleSecureChange}
classNameLabel={clsx('text-red-400')}
classNameLabel="text-red-400 whitespace-nowrap"
/>
</WdTooltipWrapper>
<WdImgButton

View File

@@ -0,0 +1,109 @@
import React, { useMemo, useState } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
import { SystemKillsContent } from './SystemKillsContent/SystemKillsContent';
import { KillsHeader } from './components/SystemKillsHeader';
import { useKillsWidgetSettings } from './hooks/useKillsWidgetSettings';
import { useSystemKills } from './hooks/useSystemKills';
import { KillsSettingsDialog } from './components/SystemKillsSettingsDialog';
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace';
import { SolarSystemRawType } from '@/hooks/Mapper/types';
export const SystemKills: React.FC = React.memo(() => {
const {
data: { selectedSystems, systems, isSubscriptionActive },
outCommand,
} = useMapRootState();
const [systemId] = selectedSystems || [];
const [settingsDialogVisible, setSettingsDialogVisible] = useState(false);
const systemNameMap = useMemo(() => {
const map: Record<string, string> = {};
systems.forEach(sys => {
map[sys.id] = sys.temporary_name || sys.name || '???';
});
return map;
}, [systems]);
const systemBySolarSystemId = useMemo(() => {
const map: Record<number, SolarSystemRawType> = {};
systems.forEach(sys => {
if (sys.system_static_info?.solar_system_id != null) {
map[sys.system_static_info.solar_system_id] = sys;
}
});
return map;
}, [systems]);
const [settings] = useKillsWidgetSettings();
const visible = settings.showAll;
const { kills, isLoading, error } = useSystemKills({
systemId,
outCommand,
showAllVisible: visible,
});
const isNothingSelected = !systemId && !visible;
const showLoading = isLoading && kills.length === 0;
const filteredKills = useMemo(() => {
if (!settings.whOnly || !visible) return kills;
return kills.filter(kill => {
const system = systemBySolarSystemId[kill.solar_system_id];
if (!system) {
console.warn(`System with id ${kill.solar_system_id} not found.`);
return false;
}
return isWormholeSpace(system.system_static_info.system_class);
});
}, [kills, settings.whOnly, systemBySolarSystemId, visible]);
return (
<div className="h-full flex flex-col min-h-0">
<div className="flex flex-col flex-1 min-h-0">
<Widget label={<KillsHeader systemId={systemId} onOpenSettings={() => setSettingsDialogVisible(true)} />}>
{!isSubscriptionActive ? (
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-stone-400/80 text-sm">
Kills available with &#39;Active&#39; map subscription only (contact map administrators)
</span>
</div>
) : isNothingSelected ? (
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-stone-400/80 text-sm">
No system selected (or toggle Show all systems)
</span>
</div>
) : showLoading ? (
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-stone-400/80 text-sm">Loading Kills...</span>
</div>
) : error ? (
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-red-400 text-sm">{error}</span>
</div>
) : !filteredKills || filteredKills.length === 0 ? (
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-stone-400/80 text-sm">No kills found</span>
</div>
) : (
<div className="w-full h-full" style={{ height: '100%' }}>
<SystemKillsContent
kills={filteredKills}
systemNameMap={systemNameMap}
onlyOneSystem={!visible}
timeRange={settings.timeRange}
/>
</div>
)}
</Widget>
</div>
{settingsDialogVisible && <KillsSettingsDialog visible setVisible={setSettingsDialogVisible} />}
</div>
);
});
SystemKills.displayName = 'SystemKills';

View File

@@ -0,0 +1,14 @@
.wrapper {
overflow-x: hidden;
box-sizing: border-box;
}
.scrollerContent {
width: 100%;
box-sizing: border-box;
overflow-x: hidden;
}
.VirtualScroller {
height: 100% !important;
}

View File

@@ -0,0 +1,91 @@
import React, { useMemo, useRef, useEffect, useState } from 'react';
import clsx from 'clsx';
import { DetailedKill } from '@/hooks/Mapper/types/kills';
import { VirtualScroller } from 'primereact/virtualscroller';
import { useSystemKillsItemTemplate } from '../hooks/useSystemKillsItemTemplate';
import classes from './SystemKillsContent.module.scss';
export const ITEM_HEIGHT = 35;
export const CONTENT_MARGINS = 5;
export interface SystemKillsContentProps {
kills: DetailedKill[];
systemNameMap: Record<string, string>;
onlyOneSystem?: boolean;
autoSize?: boolean;
timeRange?: number;
limit?: number;
}
export const SystemKillsContent: React.FC<SystemKillsContentProps> = ({
kills,
systemNameMap,
onlyOneSystem = false,
autoSize = false,
timeRange = 4,
limit,
}) => {
const processedKills = useMemo(() => {
const sortedKills = kills
.filter(k => k.kill_time)
.sort((a, b) => new Date(b.kill_time!).getTime() - new Date(a.kill_time!).getTime());
if (limit !== undefined) {
return sortedKills.slice(0, limit);
} else {
const now = Date.now();
const cutoff = now - timeRange * 60 * 60 * 1000;
return sortedKills.filter(k => new Date(k.kill_time!).getTime() >= cutoff);
}
}, [kills, timeRange, limit]);
const computedHeight = autoSize ? Math.max(processedKills.length, 1) * ITEM_HEIGHT + CONTENT_MARGINS : undefined;
const containerRef = useRef<HTMLDivElement>(null);
const scrollerRef = useRef<VirtualScroller | null>(null);
const [containerHeight, setContainerHeight] = useState<number>(0);
useEffect(() => {
if (!autoSize && containerRef.current) {
const measure = () => {
const newHeight = containerRef.current?.clientHeight || 0;
setContainerHeight(newHeight);
};
measure();
const observer = new ResizeObserver(measure);
observer.observe(containerRef.current);
window.addEventListener('resize', measure);
return () => {
observer.disconnect();
window.removeEventListener('resize', measure);
};
}
}, [autoSize]);
const itemTemplate = useSystemKillsItemTemplate(systemNameMap, onlyOneSystem);
const scrollerHeight = autoSize ? `${computedHeight}px` : containerHeight ? `${containerHeight}px` : '100%';
return (
<div ref={autoSize ? undefined : containerRef} className={clsx('w-full h-full', classes.wrapper)}>
<VirtualScroller
ref={autoSize ? undefined : scrollerRef}
items={processedKills}
itemSize={ITEM_HEIGHT}
itemTemplate={itemTemplate}
autoSize={autoSize}
scrollWidth="100%"
style={{ height: scrollerHeight }}
className={clsx('w-full h-full custom-scrollbar select-none overflow-x-hidden overflow-y-auto', {
[classes.VirtualScroller]: !autoSize,
})}
pt={{
content: {
className: classes.scrollerContent,
},
}}
/>
</div>
);
};

View File

@@ -0,0 +1,20 @@
import { DetailedKill } from '@/hooks/Mapper/types/kills';
import { VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import { KillRow } from './SystemKillsRow';
import clsx from 'clsx';
export function KillItemTemplate(
systemNameMap: Record<string, string>,
onlyOneSystem: boolean,
kill: DetailedKill,
options: VirtualScrollerTemplateOptions,
) {
const systemIdStr = String(kill.solar_system_id);
const systemName = systemNameMap[systemIdStr] || `System ${systemIdStr}`;
return (
<div style={{ height: `${options.props.itemSize}px` }} className={clsx({ 'bg-gray-900': options.odd })}>
<KillRow killDetails={kill} systemName={systemName} onlyOneSystem={onlyOneSystem} />
</div>
);
}

View File

@@ -0,0 +1,30 @@
.killRowContainer {
@apply flex items-center whitespace-nowrap overflow-hidden;
&:not(:last-child) {
@apply border-b border-stone-800;
}
@apply bg-transparent transition-all hover:bg-stone-900 hover:border-stone-700;
}
.killRowImage {
@apply border border-stone-800 rounded-[4px] object-contain;
}
.attackerCountLabel {
position: absolute;
bottom: 0;
right: 0;
font-size: 10px;
padding: 0 2px;
}
.attackerCountLabelCompact {
position: absolute;
left: 0;
bottom: 0;
font-size: 0.6rem;
line-height: 1;
background-color: rgba(0, 0, 0, 0.7);
padding: 1px 2px;
pointer-events: none;
}

View File

@@ -0,0 +1,203 @@
import React from 'react';
import clsx from 'clsx';
import { DetailedKill } from '@/hooks/Mapper/types/kills';
import {
formatISK,
formatTimeMixed,
zkillLink,
getAttackerSubscript,
buildVictimImageUrls,
buildAttackerImageUrls,
getPrimaryLogoAndTooltip,
getAttackerPrimaryImageAndTooltip,
} from '../helpers';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
import classes from './KillRowDetail.module.scss';
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
export interface CompactKillRowProps {
killDetails: DetailedKill;
systemName: string;
onlyOneSystem: boolean;
}
export const KillRowDetail: React.FC<CompactKillRowProps> = ({ killDetails, systemName, onlyOneSystem }) => {
const {
killmail_id = 0,
// Victim data
victim_char_name = 'Unknown Pilot',
victim_alliance_ticker = '',
victim_corp_ticker = '',
victim_ship_name = 'Unknown Ship',
victim_corp_name = '',
victim_alliance_name = '',
victim_char_id = 0,
victim_corp_id = 0,
victim_alliance_id = 0,
victim_ship_type_id = 0,
// Attacker data
final_blow_char_id = 0,
final_blow_char_name = '',
final_blow_alliance_ticker = '',
final_blow_alliance_name = '',
final_blow_alliance_id = 0,
final_blow_corp_ticker = '',
final_blow_corp_id = 0,
final_blow_corp_name = '',
final_blow_ship_type_id = 0,
kill_time = '',
total_value = 0,
} = killDetails || {};
const attackerIsNpc = final_blow_char_id === 0;
// Define victim affiliation ticker.
const victimAffiliationTicker = victim_alliance_ticker || victim_corp_ticker || 'No Ticker';
const killValueFormatted = total_value != null && total_value > 0 ? `${formatISK(total_value)} ISK` : null;
const killTimeAgo = kill_time ? formatTimeMixed(kill_time) : '0h ago';
const attackerSubscript = getAttackerSubscript(killDetails);
const { victimCorpLogoUrl, victimAllianceLogoUrl, victimShipUrl } = buildVictimImageUrls({
victim_char_id,
victim_ship_type_id,
victim_corp_id,
victim_alliance_id,
});
const { attackerCorpLogoUrl, attackerAllianceLogoUrl } = buildAttackerImageUrls({
final_blow_char_id,
final_blow_corp_id,
final_blow_alliance_id,
});
const { url: victimPrimaryLogoUrl, tooltip: victimPrimaryTooltip } = getPrimaryLogoAndTooltip(
victimAllianceLogoUrl,
victimCorpLogoUrl,
victim_alliance_name,
victim_corp_name,
'Victim',
);
const { url: attackerPrimaryImageUrl, tooltip: attackerPrimaryTooltip } = getAttackerPrimaryImageAndTooltip(
attackerIsNpc,
attackerAllianceLogoUrl,
attackerCorpLogoUrl,
final_blow_alliance_name,
final_blow_corp_name,
final_blow_ship_type_id,
);
// Define attackerTicker to use the alliance ticker if available, otherwise the corp ticker.
const attackerTicker = attackerIsNpc ? '' : final_blow_alliance_ticker || final_blow_corp_ticker || '';
// For the attacker image link: if the attacker is not an NPC, link to the character page; otherwise, link to the kill page.
const attackerLink = attackerIsNpc ? zkillLink('kill', killmail_id) : zkillLink('character', final_blow_char_id);
return (
<div
className={clsx(
'h-10 flex items-center border-b border-stone-800',
'text-xs whitespace-nowrap overflow-hidden leading-none',
)}
>
{/* Victim Section */}
<div className="flex items-center gap-1">
{victimShipUrl && (
<div className="relative shrink-0 w-8 h-8 overflow-hidden">
<a
href={zkillLink('kill', killmail_id)}
target="_blank"
rel="noopener noreferrer"
className="block w-full h-full"
>
<img
src={victimShipUrl}
alt="Victim Ship"
className={clsx(classes.killRowImage, 'w-full h-full object-contain')}
/>
</a>
</div>
)}
{victimPrimaryLogoUrl && (
<WdTooltipWrapper content={victimPrimaryTooltip} position={TooltipPosition.top}>
<a
href={zkillLink('kill', killmail_id)}
target="_blank"
rel="noopener noreferrer"
className="relative block shrink-0 w-8 h-8 overflow-hidden"
>
<img
src={victimPrimaryLogoUrl}
alt="Victim Primary Logo"
className={clsx(classes.killRowImage, 'w-full h-full object-contain')}
/>
</a>
</WdTooltipWrapper>
)}
</div>
<div className="flex flex-col ml-2 flex-1 min-w-0 overflow-hidden leading-[1rem]">
<div className="truncate text-stone-200">
{victim_char_name}
<span className="text-stone-400"> / {victimAffiliationTicker}</span>
</div>
<div className="truncate text-stone-300">
{victim_ship_name}
{killValueFormatted && (
<>
<span className="ml-1 text-stone-400">/</span>
<span className="ml-1 text-green-400">{killValueFormatted}</span>
</>
)}
</div>
</div>
<div className="flex items-center ml-auto gap-2">
<div className="flex flex-col items-end flex-1 min-w-0 overflow-hidden text-right leading-[1rem]">
{!attackerIsNpc && (final_blow_char_name || attackerTicker) && (
<div className="truncate text-stone-200">
{final_blow_char_name}
{!attackerIsNpc && attackerTicker && <span className="ml-1 text-stone-400">/ {attackerTicker}</span>}
</div>
)}
<div className="truncate text-stone-400">
{!onlyOneSystem && systemName ? (
<>
{systemName} / <span className="ml-1 text-red-400">{killTimeAgo}</span>
</>
) : (
<span className="text-red-400">{killTimeAgo}</span>
)}
</div>
</div>
{attackerPrimaryImageUrl && (
<WdTooltipWrapper content={attackerPrimaryTooltip} position={TooltipPosition.top}>
<a
href={attackerLink}
target="_blank"
rel="noopener noreferrer"
className="relative block shrink-0 w-8 h-8 overflow-hidden"
>
<img
src={attackerPrimaryImageUrl}
alt={attackerIsNpc ? 'NPC Ship' : 'Attacker Primary Logo'}
className={clsx(classes.killRowImage, 'w-full h-full object-contain')}
/>
{attackerSubscript && (
<span
className={clsx(
classes.attackerCountLabel,
attackerSubscript.cssClass,
'text-[0.6rem] leading-none px-[2px]',
)}
>
{attackerSubscript.label}
</span>
)}
</a>
</WdTooltipWrapper>
)}
</div>
</div>
);
};

View File

@@ -0,0 +1,65 @@
import React, { useRef } from 'react';
import {
LayoutEventBlocker,
SystemView,
TooltipPosition,
WdCheckbox,
WdImgButton,
WdTooltipWrapper,
} from '@/hooks/Mapper/components/ui-kit';
import { useKillsWidgetSettings } from '../hooks/useKillsWidgetSettings';
import { PrimeIcons } from 'primereact/api';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
interface KillsHeaderProps {
systemId?: string;
onOpenSettings: () => void;
}
export const KillsHeader: React.FC<KillsHeaderProps> = ({ systemId, onOpenSettings }) => {
const [settings, setSettings] = useKillsWidgetSettings();
const { showAll } = settings;
const onToggleShowAllVisible = () => {
setSettings(prev => ({ ...prev, showAll: !prev.showAll }));
};
const headerRef = useRef<HTMLDivElement>(null);
const compact = useMaxWidth(headerRef, 150);
return (
<div className="flex w-full items-center justify-between text-xs" ref={headerRef}>
<div className="flex items-center gap-1">
<div className="text-stone-400">
Kills
{systemId && !showAll && ' in '}
</div>
{systemId && !showAll && <SystemView systemId={systemId} className="select-none text-center" hideRegion />}
</div>
<LayoutEventBlocker className="flex items-center gap-2 justify-end">
<div className="flex items-center gap-2">
<WdTooltipWrapper content="Show all systems" position={TooltipPosition.top}>
<WdCheckbox
size="xs"
labelSide="left"
label={compact ? 'All' : 'Show all systems'}
value={showAll}
onChange={onToggleShowAllVisible}
classNameLabel="whitespace-nowrap text-stone-400 hover:text-stone-200 transition duration-300"
/>
</WdTooltipWrapper>
<WdImgButton
className={PrimeIcons.SLIDERS_H}
onClick={onOpenSettings}
tooltip={{
content: 'Open Kills Settings',
position: TooltipPosition.top,
}}
/>
</div>
</LayoutEventBlocker>
</div>
);
};

View File

@@ -0,0 +1,15 @@
import React from 'react';
import { DetailedKill } from '@/hooks/Mapper/types/kills';
import { KillRowDetail } from './KillRowDetail.tsx';
export interface KillRowProps {
killDetails: DetailedKill;
systemName: string;
onlyOneSystem?: boolean;
}
const KillRowComponent: React.FC<KillRowProps> = ({ killDetails, systemName, onlyOneSystem = false }) => {
return <KillRowDetail killDetails={killDetails} systemName={systemName} onlyOneSystem={onlyOneSystem} />;
};
export const KillRow = React.memo(KillRowComponent);

View File

@@ -0,0 +1,163 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Dialog } from 'primereact/dialog';
import { Button } from 'primereact/button';
import { WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { PrimeIcons } from 'primereact/api';
import { useKillsWidgetSettings } from '../hooks/useKillsWidgetSettings';
import {
AddSystemDialog,
SearchOnSubmitCallback,
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
import { SystemView, TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
interface KillsSettingsDialogProps {
visible: boolean;
setVisible: (visible: boolean) => void;
}
export const KillsSettingsDialog: React.FC<KillsSettingsDialogProps> = ({ visible, setVisible }) => {
const [globalSettings, setGlobalSettings] = useKillsWidgetSettings();
const localRef = useRef({
showAll: globalSettings.showAll,
whOnly: globalSettings.whOnly,
excludedSystems: globalSettings.excludedSystems || [],
timeRange: globalSettings.timeRange,
});
const [, forceRender] = useState(0);
const [addSystemDialogVisible, setAddSystemDialogVisible] = useState(false);
useEffect(() => {
if (visible) {
localRef.current = {
showAll: globalSettings.showAll,
whOnly: globalSettings.whOnly,
excludedSystems: globalSettings.excludedSystems || [],
timeRange: globalSettings.timeRange,
};
forceRender(n => n + 1);
}
}, [visible, globalSettings]);
const handleWHChange = useCallback((checked: boolean) => {
localRef.current = {
...localRef.current,
whOnly: checked,
};
forceRender(n => n + 1);
}, []);
const handleTimeRangeChange = useCallback((newTimeRange: number) => {
localRef.current = {
...localRef.current,
timeRange: newTimeRange,
};
forceRender(n => n + 1);
}, []);
const handleRemoveSystem = useCallback((sysId: number) => {
localRef.current = {
...localRef.current,
excludedSystems: localRef.current.excludedSystems.filter(id => id !== sysId),
};
forceRender(n => n + 1);
}, []);
const handleAddSystemSubmit: SearchOnSubmitCallback = useCallback(item => {
if (localRef.current.excludedSystems.includes(item.value)) {
return;
}
localRef.current = {
...localRef.current,
excludedSystems: [...localRef.current.excludedSystems, item.value],
};
forceRender(n => n + 1);
}, []);
const handleApply = useCallback(() => {
setGlobalSettings(prev => ({
...prev,
...localRef.current,
}));
setVisible(false);
}, [setGlobalSettings, setVisible]);
const handleHide = useCallback(() => {
setVisible(false);
}, [setVisible]);
const localData = localRef.current;
const excluded = localData.excludedSystems || [];
const timeRangeOptions = [4, 12, 24];
return (
<Dialog header="Kills Settings" visible={visible} style={{ width: '440px' }} draggable={false} onHide={handleHide}>
<div className="flex flex-col gap-3 p-2.5">
<div className="flex items-center gap-2">
<input
type="checkbox"
id="kills-wormhole-only-mode"
checked={localData.whOnly}
onChange={e => handleWHChange(e.target.checked)}
/>
<label htmlFor="kills-wormhole-only-mode" className="cursor-pointer">
Only show wormhole kills
</label>
</div>
<div className="flex flex-col gap-1">
<span className="text-sm">Time Range:</span>
<div className="flex flex-wrap gap-2">
{timeRangeOptions.map(option => (
<label key={option} className="cursor-pointer flex items-center gap-1">
<input
type="radio"
name="timeRange"
value={option}
checked={localData.timeRange === option}
onChange={() => handleTimeRangeChange(option)}
/>
<span className="text-sm">{option} Hours</span>
</label>
))}
</div>
</div>
{/* Excluded Systems */}
<div className="flex flex-col gap-1">
<div className="flex items-center justify-between">
<label className="text-sm text-stone-400">Excluded Systems</label>
<WdImgButton
className={PrimeIcons.PLUS_CIRCLE}
onClick={() => setAddSystemDialogVisible(true)}
tooltip={{ content: 'Add system to excluded list' }}
/>
</div>
{excluded.length === 0 && <div className="text-stone-500 text-xs italic">No systems excluded.</div>}
{excluded.map(sysId => (
<div key={sysId} className="flex items-center justify-between border-b border-stone-600 py-1 px-1 text-xs">
<SystemView systemId={sysId.toString()} hideRegion />
<WdImgButton
className={PrimeIcons.TRASH}
onClick={() => handleRemoveSystem(sysId)}
tooltip={{ content: 'Remove from excluded', position: TooltipPosition.top }}
/>
</div>
))}
</div>
<div className="flex gap-2 justify-end mt-4">
<Button onClick={handleApply} label="Apply" outlined size="small" />
</div>
</div>
<AddSystemDialog
title="Add system to kills exclude list"
visible={addSystemDialogVisible}
setVisible={() => setAddSystemDialogVisible(false)}
onSubmit={handleAddSystemSubmit}
excludedSystems={excluded}
/>
</Dialog>
);
};

View File

@@ -0,0 +1,2 @@
export * from './linkHelpers';
export * from './killRowUtils';

View File

@@ -0,0 +1,47 @@
import { DetailedKill } from '@/hooks/Mapper/types/kills';
/** Returns "5m ago", "3h ago", "2.5d ago", etc. */
export function formatTimeMixed(killTime: string): string {
const killDate = new Date(killTime);
const diffMs = Date.now() - killDate.getTime();
const diffHours = diffMs / (1000 * 60 * 60);
if (diffHours < 1) {
const mins = Math.round(diffHours * 60);
return `${mins}m ago`;
} else if (diffHours < 24) {
const hours = Math.round(diffHours);
return `${hours}h ago`;
} else {
const days = diffHours / 24;
const roundedDays = days.toFixed(1);
return `${roundedDays}d ago`;
}
}
/** Formats integer ISK values into k/M/B/T. */
export function formatISK(value: number): string {
if (value >= 1_000_000_000_000) {
return `${(value / 1_000_000_000_000).toFixed(2)}T`;
} else if (value >= 1_000_000_000) {
return `${(value / 1_000_000_000).toFixed(2)}B`;
} else if (value >= 1_000_000) {
return `${(value / 1_000_000).toFixed(2)}M`;
} else if (value >= 1_000) {
return `${(value / 1_000).toFixed(2)}k`;
}
return Math.round(value).toString();
}
export function getAttackerSubscript(kill: DetailedKill) {
if (kill.npc) {
return { label: 'npc', cssClass: 'text-purple-400' };
}
const count = kill.attacker_count ?? 0;
if (count === 1) {
return { label: 'solo', cssClass: 'text-green-400' };
} else if (count > 1) {
return { label: String(count), cssClass: 'text-white' };
}
return null;
}

View File

@@ -0,0 +1,111 @@
const ZKILL_URL = 'https://zkillboard.com';
const BASE_IMAGE_URL = 'https://images.evetech.net';
export function zkillLink(type: 'kill' | 'character' | 'corporation' | 'alliance', id?: number | null): string {
if (!id) return `${ZKILL_URL}`;
if (type === 'kill') return `${ZKILL_URL}/kill/${id}/`;
if (type === 'character') return `${ZKILL_URL}/character/${id}/`;
if (type === 'corporation') return `${ZKILL_URL}/corporation/${id}/`;
if (type === 'alliance') return `${ZKILL_URL}/alliance/${id}/`;
return `${ZKILL_URL}`;
}
export function eveImageUrl(
category: 'characters' | 'corporations' | 'alliances' | 'types',
id?: number | null,
variation: string = 'icon',
size?: number,
): string | null {
if (!id || id <= 0) {
return null;
}
let url = `${BASE_IMAGE_URL}/${category}/${id}/${variation}`;
if (size) {
url += `?size=${size}`;
}
return url;
}
export function buildVictimImageUrls(args: {
victim_char_id?: number | null;
victim_ship_type_id?: number | null;
victim_corp_id?: number | null;
victim_alliance_id?: number | null;
}) {
const { victim_char_id, victim_ship_type_id, victim_corp_id, victim_alliance_id } = args;
const victimPortraitUrl = eveImageUrl('characters', victim_char_id, 'portrait', 64);
const victimShipUrl = eveImageUrl('types', victim_ship_type_id, 'render', 64);
const victimCorpLogoUrl = eveImageUrl('corporations', victim_corp_id, 'logo', 32);
const victimAllianceLogoUrl = eveImageUrl('alliances', victim_alliance_id, 'logo', 32);
return {
victimPortraitUrl,
victimShipUrl,
victimCorpLogoUrl,
victimAllianceLogoUrl,
};
}
export function buildAttackerShipUrl(final_blow_ship_type_id?: number | null): string | null {
return eveImageUrl('types', final_blow_ship_type_id, 'render', 64);
}
export function buildAttackerImageUrls(args: {
final_blow_char_id?: number | null;
final_blow_corp_id?: number | null;
final_blow_alliance_id?: number | null;
}) {
const { final_blow_char_id, final_blow_corp_id, final_blow_alliance_id } = args;
const attackerPortraitUrl = eveImageUrl('characters', final_blow_char_id, 'portrait', 64);
const attackerCorpLogoUrl = eveImageUrl('corporations', final_blow_corp_id, 'logo', 32);
const attackerAllianceLogoUrl = eveImageUrl('alliances', final_blow_alliance_id, 'logo', 32);
return {
attackerPortraitUrl,
attackerCorpLogoUrl,
attackerAllianceLogoUrl,
};
}
export function getPrimaryLogoAndTooltip(
allianceUrl: string | null,
corpUrl: string | null,
allianceName: string,
corpName: string,
fallback: string,
) {
let url: string | null = null;
let tooltip = '';
if (allianceUrl) {
url = allianceUrl;
tooltip = allianceName || fallback;
} else if (corpUrl) {
url = corpUrl;
tooltip = corpName || fallback;
}
return { url, tooltip };
}
export function getAttackerPrimaryImageAndTooltip(
isNpc: boolean,
allianceUrl: string | null,
corpUrl: string | null,
allianceName: string,
corpName: string,
finalBlowShipTypeId: number | null,
npcFallback: string = 'NPC Attacker',
) {
if (isNpc) {
const shipUrl = buildAttackerShipUrl(finalBlowShipTypeId);
return {
url: shipUrl,
tooltip: npcFallback,
};
}
return getPrimaryLogoAndTooltip(allianceUrl, corpUrl, allianceName, corpName, 'Attacker');
}

View File

@@ -0,0 +1,53 @@
import { useMemo, useCallback } from 'react';
import useLocalStorageState from 'use-local-storage-state';
export interface KillsWidgetSettings {
showAll: boolean;
whOnly: boolean;
excludedSystems: number[];
version: number;
timeRange: number;
}
export const DEFAULT_KILLS_WIDGET_SETTINGS: KillsWidgetSettings = {
showAll: false,
whOnly: true,
excludedSystems: [],
version: 2,
timeRange: 1,
};
function mergeWithDefaults(settings?: Partial<KillsWidgetSettings>): KillsWidgetSettings {
if (!settings) {
return DEFAULT_KILLS_WIDGET_SETTINGS;
}
return {
...DEFAULT_KILLS_WIDGET_SETTINGS,
...settings,
excludedSystems: Array.isArray(settings.excludedSystems) ? settings.excludedSystems : [],
};
}
export function useKillsWidgetSettings() {
const [rawValue, setRawValue] = useLocalStorageState<KillsWidgetSettings | undefined>('kills:widget:settings');
const value = useMemo<KillsWidgetSettings>(() => {
return mergeWithDefaults(rawValue);
}, [rawValue]);
const setValue = useCallback(
(newVal: KillsWidgetSettings | ((prev: KillsWidgetSettings) => KillsWidgetSettings)) => {
setRawValue(prev => {
const mergedPrev = mergeWithDefaults(prev);
const nextUnmerged = typeof newVal === 'function' ? newVal(mergedPrev) : newVal;
return mergeWithDefaults(nextUnmerged);
});
},
[setRawValue],
);
return [value, setValue] as const;
}

View File

@@ -0,0 +1,183 @@
import { useCallback, useMemo, useState, useEffect, useRef } from 'react';
import debounce from 'lodash.debounce';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
import { DetailedKill } from '@/hooks/Mapper/types/kills';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useKillsWidgetSettings } from './useKillsWidgetSettings';
interface UseSystemKillsProps {
systemId?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
outCommand: (payload: any) => Promise<any>;
showAllVisible?: boolean;
sinceHours?: number;
}
function combineKills(existing: DetailedKill[], incoming: DetailedKill[], sinceHours: number): DetailedKill[] {
const cutoff = Date.now() - sinceHours * 60 * 60 * 1000;
const byId: Record<string, DetailedKill> = {};
for (const kill of [...existing, ...incoming]) {
if (!kill.kill_time) {
continue;
}
const killTimeMs = new Date(kill.kill_time).valueOf();
if (killTimeMs >= cutoff) {
byId[kill.killmail_id] = kill;
}
}
return Object.values(byId);
}
export function useSystemKills({ systemId, outCommand, showAllVisible = false, sinceHours = 24 }: UseSystemKillsProps) {
const { data, update } = useMapRootState();
const { detailedKills = {}, systems = [] } = data;
const [settings] = useKillsWidgetSettings();
const excludedSystems = settings.excludedSystems;
// When showing all visible kills, filter out excluded systems;
// when showAllVisible is false, ignore the exclusion filter.
const effectiveSystemIds = useMemo(() => {
if (showAllVisible) {
return systems.map(s => s.id).filter(id => !excludedSystems.includes(Number(id)));
}
return systems.map(s => s.id);
}, [systems, excludedSystems, showAllVisible]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const didFallbackFetch = useRef(Object.keys(detailedKills).length !== 0);
const mergeKillsIntoGlobal = useCallback(
(killsMap: Record<string, DetailedKill[]>) => {
update(prev => {
const oldMap = prev.detailedKills ?? {};
const updated: Record<string, DetailedKill[]> = { ...oldMap };
for (const [sid, newKills] of Object.entries(killsMap)) {
const existing = updated[sid] ?? [];
const combined = combineKills(existing, newKills, sinceHours);
updated[sid] = combined;
}
return {
...prev,
detailedKills: updated,
};
});
},
[update, sinceHours],
);
const fetchKills = useCallback(
async (forceFallback = false) => {
setIsLoading(true);
setError(null);
try {
let eventType: OutCommand;
let requestData: Record<string, unknown>;
if (showAllVisible || forceFallback) {
eventType = OutCommand.getSystemsKills;
requestData = {
system_ids: effectiveSystemIds,
since_hours: sinceHours,
};
} else if (systemId) {
eventType = OutCommand.getSystemKills;
requestData = {
system_id: systemId,
since_hours: sinceHours,
};
} else {
// If there's no system and not showing all, do nothing
setIsLoading(false);
return;
}
const resp = await outCommand({
type: eventType,
data: requestData,
});
// Single system => `resp.kills`
if (resp?.kills) {
const arr = resp.kills as DetailedKill[];
const sid = systemId ?? 'unknown';
mergeKillsIntoGlobal({ [sid]: arr });
}
// multiple systems => `resp.systems_kills`
else if (resp?.systems_kills) {
mergeKillsIntoGlobal(resp.systems_kills as Record<string, DetailedKill[]>);
} else {
console.warn('[useSystemKills] Unexpected kills response =>', resp);
}
} catch (err) {
console.error('[useSystemKills] Failed to fetch kills:', err);
setError(err instanceof Error ? err.message : 'Error fetching kills');
} finally {
setIsLoading(false);
}
},
[showAllVisible, systemId, outCommand, effectiveSystemIds, sinceHours, mergeKillsIntoGlobal],
);
const debouncedFetchKills = useMemo(
() =>
debounce(fetchKills, 500, {
leading: true,
trailing: false,
}),
[fetchKills],
);
const finalKills = useMemo(() => {
if (showAllVisible) {
return effectiveSystemIds.flatMap(sid => detailedKills[sid] ?? []);
} else if (systemId) {
return detailedKills[systemId] ?? [];
} else if (didFallbackFetch.current) {
// if we already did a fallback, we may have data for multiple systems
return effectiveSystemIds.flatMap(sid => detailedKills[sid] ?? []);
}
return [];
}, [showAllVisible, systemId, effectiveSystemIds, detailedKills]);
const effectiveIsLoading = isLoading && finalKills.length === 0;
useEffect(() => {
if (!systemId && !showAllVisible && !didFallbackFetch.current) {
didFallbackFetch.current = true;
// Cancel any queued debounced calls, then do the fallback.
debouncedFetchKills.cancel();
fetchKills(true); // forceFallback => fetch as though showAllVisible is true
}
}, [systemId, showAllVisible, debouncedFetchKills, fetchKills]);
useEffect(() => {
if (effectiveSystemIds.length === 0) return;
if (showAllVisible || systemId) {
debouncedFetchKills();
// Clean up the debounce on unmount or changes
return () => debouncedFetchKills.cancel();
}
}, [showAllVisible, systemId, effectiveSystemIds, debouncedFetchKills]);
const refetch = useCallback(() => {
debouncedFetchKills.cancel();
fetchKills(); // immediate (non-debounced) call
}, [debouncedFetchKills, fetchKills]);
return {
kills: finalKills,
isLoading: effectiveIsLoading,
error,
refetch,
};
}

View File

@@ -0,0 +1,13 @@
// useSystemKillsItemTemplate.tsx
import { useCallback } from 'react';
import { VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import { DetailedKill } from '@/hooks/Mapper/types/kills';
import { KillItemTemplate } from '../components/KillItemTemplate';
export function useSystemKillsItemTemplate(systemNameMap: Record<string, string>, onlyOneSystem: boolean) {
return useCallback(
(kill: DetailedKill, options: VirtualScrollerTemplateOptions) =>
KillItemTemplate(systemNameMap, onlyOneSystem, kill, options),
[systemNameMap, onlyOneSystem],
);
}

View File

@@ -0,0 +1 @@
export * from './SystemKills';

View File

@@ -3,3 +3,4 @@ export * from './SystemInfo';
export * from './RoutesWidget';
export * from './SystemSignatures';
export * from './SystemStructures';
export * from './SystemKills';

View File

@@ -3,7 +3,12 @@ import { Dialog } from 'primereact/dialog';
import { useCallback, useMemo, useState } from 'react';
import { TabPanel, TabView } from 'primereact/tabview';
import { PrettySwitchbox } from './components';
import { InterfaceStoredSettingsProps, useMapRootState, InterfaceStoredSettings } from '@/hooks/Mapper/mapRootProvider';
import {
InterfaceStoredSettingsProps,
useMapRootState,
InterfaceStoredSettings,
AvailableThemes
} from '@/hooks/Mapper/mapRootProvider';
import { OutCommand } from '@/hooks/Mapper/types';
import { Dropdown } from 'primereact/dropdown';
import { WidgetsSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components/WidgetsSettings/WidgetsSettings.tsx';
@@ -112,8 +117,8 @@ const UI_CHECKBOXES_PROPS: SettingsListItem[] = [
];
const THEME_OPTIONS = [
{ label: 'Default', value: 'default' },
{ label: 'Pathfinder', value: 'pathfinder' },
{ label: 'Default', value: AvailableThemes.default },
{ label: 'Pathfinder', value: AvailableThemes.pathfinder },
];
const THEME_SETTING: SettingsListItem = {

View File

@@ -33,7 +33,7 @@ export const MapWrapper = () => {
const {
update,
outCommand,
data: { selectedConnections, selectedSystems, hubs, systems, connections, linkSignatureToSystem },
data: { selectedConnections, selectedSystems, hubs, systems, linkSignatureToSystem },
interfaceSettings: {
isShowMenu,
isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
@@ -56,8 +56,8 @@ export const MapWrapper = () => {
const [openAddSystem, setOpenAddSystem] = useState<XYPosition | null>(null);
const [selectedConnection, setSelectedConnection] = useState<SolarSystemConnection | null>(null);
const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems, connections, deleteSystems });
ref.current = { selectedConnections, selectedSystems, systemContextProps, systems, connections, deleteSystems };
const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems });
ref.current = { selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems };
useMapEventListener(event => {
runCommand(event);
@@ -125,11 +125,6 @@ export const MapWrapper = () => {
setOpenAddSystem(coordinates);
}, []);
const canRemoveConnection = useCallback((connectionId: string) => {
const { connections } = ref.current;
return !connections.some(x => x.id === connectionId);
}, []);
const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback(
async item => {
if (ref.current.systems.some(x => x.system_static_info.solar_system_id === item.value)) {
@@ -166,7 +161,6 @@ export const MapWrapper = () => {
isSoftBackground={isSoftBackground}
theme={theme}
onAddSystem={onAddSystem}
canRemoveConnection={canRemoveConnection}
/>
{openSettings != null && (

View File

@@ -15,7 +15,7 @@
border-style: solid;
border-color: #272727;
background-color: rgba(0, 0, 0, 0);
border-radius: 3px;
border-radius: 0 !important;
}
.CharName {
@@ -26,7 +26,10 @@
}
}
.CharIcon {}
.CharIcon {
border-radius: 0 !important;
border: 1px solid #2b2b2b;
}
.CharRow {
display: grid;

View File

@@ -3,13 +3,13 @@ import clsx from 'clsx';
import classes from './CharacterCard.module.scss';
import { SystemView } from '@/hooks/Mapper/components/ui-kit/SystemView';
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
import { Commands } from '@/hooks/Mapper/types/mapHandlers';
import { emitMapEvent } from '@/hooks/Mapper/events';
type CharacterCardProps = {
compact?: boolean;
showShipName?: boolean;
showSystem?: boolean;
showShipName?: boolean;
useSystemsCache?: boolean;
} & CharacterTypeRaw &
WithIsOwnCharacter;
@@ -18,16 +18,16 @@ const SHIP_NAME_RX = /u'|'/g;
export const getShipName = (name: string) => {
return name
.replace(SHIP_NAME_RX, '')
.replace(/\\u([\dA-Fa-f]{4})/g, (_, grp) => {
return String.fromCharCode(parseInt(grp, 16));
})
.replace(/\\x([\dA-Fa-f]{2})/g, (_, grp) => {
return String.fromCharCode(parseInt(grp, 16));
});
.replace(/\\u([\dA-Fa-f]{4})/g, (_, grp) =>
String.fromCharCode(parseInt(grp, 16))
)
.replace(/\\x([\dA-Fa-f]{2})/g, (_, grp) =>
String.fromCharCode(parseInt(grp, 16))
);
};
export const CharacterCard = ({
compact,
compact = false,
isOwn,
showSystem,
showShipName,
@@ -41,57 +41,105 @@ export const CharacterCard = ({
});
}, [char]);
return (
<div className={clsx(classes.CharacterCard, 'w-full text-xs', 'flex flex-col box-border')} onClick={handleSelect}>
<div className="flex px-2 py-1 gap-1">
{!compact && (
<span
className={clsx(classes.EveIcon, classes.CharIcon, 'wd-bg-default')}
style={{ backgroundImage: `url(https://images.evetech.net/characters/${char.eve_id}/portrait)` }}
const shipNameText = char.ship?.ship_name ? getShipName(char.ship.ship_name) : '';
const tickerText = char.alliance_id ? char.alliance_ticker : char.corporation_ticker;
const shipType = char.ship?.ship_type_info?.name;
const locationShown = showSystem && char.location?.solar_system_id;
if (compact) {
return (
<div
className={clsx(classes.CharacterCard, 'w-full text-xs box-border')}
onClick={handleSelect}
>
<div className="w-full px-2 py-1 flex items-center gap-2" style={{ minWidth: 0 }}>
<img
src={`https://images.evetech.net/characters/${char.eve_id}/portrait`}
alt={`${char.name} portrait`}
style={{
width: '18px',
height: '18px',
borderRadius: 0,
flexShrink: 0,
border: '1px solid #2b2b2b',
}}
/>
)}
<div className="flex flex-col flex-grow">
<div
className={clsx(classes.CharRow, 'w-full', {
[classes.TwoColumns]: !char.ship,
[classes.ThreeColumns]: char.ship,
})}
>
<span
className={clsx(classes.CharName, 'text-ellipsis overflow-hidden whitespace-nowrap', {
[classes.CardBorderLeftIsOwn]: isOwn,
})}
title={char.name}
>
{char.name}
</span>
{char.alliance_id && <span className="text-gray-400">[{char.alliance_ticker}]</span>}
{!char.alliance_id && <span className="text-gray-400">[{char.corporation_ticker}]</span>}
{char.ship?.ship_type_info && (
<div
className="flex-grow text-ellipsis overflow-hidden whitespace-nowrap"
title={char.ship.ship_type_info.name}
>
{char.ship.ship_type_info.name}
</div>
)}
</div>
{showShipName && !compact && char.ship?.ship_name && (
<div className="grid w-full">
<span className="text-ellipsis overflow-hidden whitespace-nowrap">
{getShipName(char.ship.ship_name)}
<div className="flex flex-grow overflow-hidden text-left" style={{ minWidth: 0 }}>
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
<span className={clsx(isOwn ? 'text-orange-400' : 'text-gray-200')}>
{char.name}
</span>{" "}
<span className="text-gray-400">
{(!locationShown && showShipName && shipNameText)
? `- ${shipNameText}`
: `[${tickerText}]`}
</span>
</div>
)}
{showSystem && !compact && char.location?.solar_system_id && (
<SystemView systemId={char.location.solar_system_id.toString()} useSystemsCache={useSystemsCache} />
</div>
{shipType && (
<div
className="text-gray-300 overflow-hidden text-ellipsis whitespace-nowrap flex-shrink-0"
style={{ maxWidth: '120px' }}
title={shipType}
>
{shipType}
</div>
)}
</div>
</div>
</div>
);
);
} else {
return (
<div
className={clsx(classes.CharacterCard, 'w-full text-xs box-border')}
onClick={handleSelect}
>
<div className="w-full px-2 py-1 flex items-center gap-2" style={{ minWidth: 0 }}>
<span
className={clsx(classes.EveIcon, classes.CharIcon, 'wd-bg-default')}
style={{
backgroundImage: `url(https://images.evetech.net/characters/${char.eve_id}/portrait)`,
minWidth: '33px',
minHeight: '33px',
width: '33px',
height: '33px',
}}
/>
<div className="flex flex-col flex-grow overflow-hidden" style={{ minWidth: 0 }}>
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
<span className={clsx(isOwn ? 'text-orange-400' : 'text-gray-200')}>
{char.name}
</span>{" "}
<span className="text-gray-400">[{tickerText}]</span>
</div>
{locationShown ? (
<div className="text-gray-300 text-xs overflow-hidden text-ellipsis whitespace-nowrap">
<SystemView
systemId={char?.location?.solar_system_id?.toString() || ''}
useSystemsCache={useSystemsCache}
/>
</div>
) : (
shipNameText && (
<div className="text-gray-300 text-xs overflow-hidden text-ellipsis whitespace-nowrap">
{shipNameText}
</div>
)
)}
</div>
{shipType && (
<div className="flex-shrink-0 self-start">
<div
className="text-gray-300 overflow-hidden text-ellipsis whitespace-nowrap"
style={{ maxWidth: '200px' }}
title={shipType}
>
{shipType}
</div>
</div>
)}
</div>
</div>
);
}
};

View File

@@ -2,12 +2,12 @@ import classes from './WdCheckbox.module.scss';
import { Checkbox, CheckboxChangeEvent } from 'primereact/checkbox';
import { WithClassName } from '@/hooks/Mapper/types/common';
import clsx from 'clsx';
import { useMemo } from 'react';
import React, { useMemo } from 'react';
let counter = 0;
export interface WdCheckboxProps {
label: string;
label: React.ReactNode | string;
classNameLabel?: string;
value: boolean;
labelSide?: 'left' | 'right';

View File

@@ -0,0 +1,67 @@
import React from 'react';
import clsx from 'clsx';
import { WdCheckbox, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
/**
* Display modes for the responsive checkbox.
*
* - "full": show the full label (e.g. "Show offline" or "Show ship name")
* - "abbr": show the abbreviated label (e.g. "Offline" or "Ship name")
* - "checkbox": show only the checkbox (no text)
* - "hide": do not render the checkbox at all
*/
export type WdDisplayMode = 'full' | 'abbr' | 'checkbox' | 'hide';
export interface WdResponsiveCheckboxProps {
tooltipContent: string;
size: 'xs' | 'normal' | 'm';
labelFull: string;
labelAbbreviated: string;
value: boolean;
onChange: () => void;
classNameLabel?: string;
containerClassName?: string;
labelSide?: 'left' | 'right';
displayMode: WdDisplayMode;
}
export const WdResponsiveCheckbox: React.FC<WdResponsiveCheckboxProps> = ({
tooltipContent,
size,
labelFull,
labelAbbreviated,
value,
onChange,
classNameLabel,
containerClassName,
labelSide = 'left',
displayMode,
}) => {
if (displayMode === 'hide') {
return null;
}
const label =
displayMode === 'full'
? labelFull
: displayMode === 'abbr'
? labelAbbreviated
: displayMode === 'checkbox'
? ''
: labelFull;
const checkbox = (
<div className={clsx('min-w-0', containerClassName)}>
<WdCheckbox
size={size}
labelSide={labelSide}
label={label}
value={value}
classNameLabel={classNameLabel}
onChange={onChange}
/>
</div>
);
return tooltipContent ? <WdTooltipWrapper content={tooltipContent}>{checkbox}</WdTooltipWrapper> : checkbox;
};

View File

@@ -0,0 +1 @@
export * from './WdResponsiveCheckbox';

View File

@@ -1,20 +1,8 @@
import React, {
ForwardedRef,
forwardRef,
MouseEvent,
MouseEventHandler,
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState,
} from 'react';
import React, { ForwardedRef, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import classes from './WdTooltip.module.scss';
import clsx from 'clsx';
import debounce from 'lodash.debounce';
import { WithClassName } from '@/hooks/Mapper/types/common.ts';
import classes from './WdTooltip.module.scss';
export enum TooltipPosition {
default = 'default',
@@ -24,16 +12,12 @@ export enum TooltipPosition {
bottom = 'bottom',
}
export interface TooltipProps {
export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'content'> {
position?: TooltipPosition;
offset?: number;
content: (() => React.ReactNode) | React.ReactNode;
targetSelector?: string;
}
export interface WdTooltipHandlers {
show: MouseEventHandler;
hide: MouseEventHandler;
interactive?: boolean;
}
export interface OffsetPosition {
@@ -41,26 +25,52 @@ export interface OffsetPosition {
left: number;
}
// eslint-disable-next-line react/display-name
export const WdTooltip = forwardRef((props: TooltipProps & WithClassName, ref: ForwardedRef<WdTooltipHandlers>) => {
const { content, targetSelector, position: tPosition = TooltipPosition.default, className, offset = 5 } = props;
export interface WdTooltipHandlers {
show: (e?: React.MouseEvent) => void;
hide: () => void;
getIsMouseInside: () => boolean;
}
interface TriggerInfo {
clientX: number;
clientY: number;
rect: DOMRect;
}
const LEAVE_DELAY = 100;
export const WdTooltip = forwardRef(function WdTooltip(
{
content,
targetSelector,
position: tPosition = TooltipPosition.default,
offset = 5,
interactive = false,
className,
...restProps
}: TooltipProps,
ref: ForwardedRef<WdTooltipHandlers>,
) {
// Always initialize position so we never have a null value.
const [visible, setVisible] = useState(false);
const [position, setPosition] = useState<OffsetPosition | null>(null);
const [ev, setEv] = useState<MouseEvent>();
const [pos, setPos] = useState<OffsetPosition | null>(null);
const tooltipRef = useRef<HTMLDivElement>(null);
const calcTooltipPosition = useCallback(({ x, y }: { x: number; y: number }) => {
let newLeft = x;
let newTop = y;
const [isMouseInsideTooltip, setIsMouseInsideTooltip] = useState(false);
if (!tooltipRef.current) {
return { left: newLeft, top: newTop };
}
const [triggerInfo, setTriggerInfo] = useState<TriggerInfo | null>(null);
const hideTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const calcTooltipPosition = useCallback(({ x, y }: { x: number; y: number }) => {
if (!tooltipRef.current) return { left: x, top: y };
const tooltipWidth = tooltipRef.current.offsetWidth;
const tooltipHeight = tooltipRef.current.offsetHeight;
let newLeft = x;
let newTop = y;
if (newLeft < 0) {
newLeft = 10;
}
@@ -69,141 +79,218 @@ export const WdTooltip = forwardRef((props: TooltipProps & WithClassName, ref: F
newTop = 10;
}
if (newLeft + tooltipWidth + 10 > window.innerWidth) {
const rightEdge = newLeft + tooltipWidth + 10;
if (rightEdge > window.innerWidth) {
newLeft = window.innerWidth - tooltipWidth - 10;
}
if (newTop + tooltipHeight + 10 > window.innerHeight) {
const bottomEdge = newTop + tooltipHeight + 10;
if (bottomEdge > window.innerHeight) {
newTop = window.innerHeight - tooltipHeight - 10;
}
return { left: newLeft, top: newTop };
}, []);
useEffect(() => {
if (!tooltipRef.current || !ev) {
const scheduleHide = useCallback(() => {
if (!interactive) {
setVisible(false);
setPos(null);
return;
}
if (!hideTimeoutRef.current) {
hideTimeoutRef.current = setTimeout(() => {
setVisible(false);
setPos(null);
}, LEAVE_DELAY);
}
}, [interactive]);
const { clientX, clientY, target } = ev;
useImperativeHandle(ref, () => ({
show: (e?: React.MouseEvent) => {
if (hideTimeoutRef.current) {
clearTimeout(hideTimeoutRef.current);
hideTimeoutRef.current = null;
}
if (e) {
// Use e.currentTarget (or fallback to e.target) to determine the trigger element.
const triggerEl = (e.currentTarget as HTMLElement) || (e.target as HTMLElement);
if (triggerEl) {
const rect = triggerEl.getBoundingClientRect();
setTriggerInfo({ clientX: e.clientX, clientY: e.clientY, rect });
setPos(calcTooltipPosition({ x: e.clientX, y: e.clientY }));
}
}
setVisible(true);
},
hide: () => {
if (hideTimeoutRef.current) {
clearTimeout(hideTimeoutRef.current);
}
setVisible(false);
setPos(null);
},
getIsMouseInside: () => isMouseInsideTooltip,
}));
const targetBounds = (target as HTMLElement).getBoundingClientRect();
const tooltipBounds = tooltipRef.current.getBoundingClientRect();
useEffect(() => {
if (!tooltipRef.current || !triggerInfo) return;
let offsetX = clientX;
let offsetY = clientY;
const tooltipEl = tooltipRef.current;
const { rect } = triggerInfo;
let x = triggerInfo.clientX;
let y = triggerInfo.clientY;
if (tPosition === TooltipPosition.left) {
offsetX = targetBounds.left - tooltipBounds.width - offset;
offsetY = targetBounds.y + targetBounds.height / 2 - tooltipBounds.height / 2;
const tooltipBounds = tooltipEl.getBoundingClientRect();
x = rect.left - tooltipBounds.width - offset;
y = rect.top + rect.height / 2 - tooltipBounds.height / 2;
if (offsetX <= 0) {
offsetX = targetBounds.left + targetBounds.width + offset;
if (x <= 0) {
x = rect.left + rect.width + offset;
}
setPosition(calcTooltipPosition({ x: offsetX, y: offsetY }));
setPos(calcTooltipPosition({ x, y }));
return;
}
if (tPosition === TooltipPosition.right) {
offsetX = targetBounds.left + targetBounds.width + offset;
offsetY = targetBounds.y + targetBounds.height / 2 - tooltipBounds.height / 2;
setPosition(calcTooltipPosition({ x: offsetX, y: offsetY }));
x = rect.left + rect.width + offset;
y = rect.top + rect.height / 2 - tooltipEl.offsetHeight / 2;
setPos(calcTooltipPosition({ x, y }));
return;
}
if (tPosition === TooltipPosition.top) {
offsetY = targetBounds.top - tooltipBounds.height - offset;
offsetX = targetBounds.x + targetBounds.width / 2 - tooltipBounds.width / 2;
setPosition(calcTooltipPosition({ x: offsetX, y: offsetY }));
x = rect.left + rect.width / 2 - tooltipEl.offsetWidth / 2;
y = rect.top - tooltipEl.offsetHeight - offset;
setPos(calcTooltipPosition({ x, y }));
return;
}
// default case
setPosition(calcTooltipPosition({ x: clientX, y: clientY }));
}, [calcTooltipPosition, ev, tPosition, offset]);
if (tPosition === TooltipPosition.bottom) {
x = rect.left + rect.width / 2 - tooltipEl.offsetWidth / 2;
y = rect.bottom + offset;
setPos(calcTooltipPosition({ x, y }));
return;
}
useImperativeHandle(ref, () => ({
show: e => {
setEv(e);
setVisible(true);
setPosition(null);
},
hide: () => {
setVisible(false);
},
}));
// Default case: use stored coordinates.
setPos(calcTooltipPosition({ x, y }));
}, [calcTooltipPosition, triggerInfo, tPosition, offset]);
useEffect(() => {
if (targetSelector == null) {
return;
}
if (!targetSelector) return;
const handleMouseMove = (e: MouseEvent) => {
const targetElement = e.target as HTMLElement;
if (!targetElement) {
setVisible(false);
const handleMouseMove = (evt: MouseEvent) => {
const targetEl = evt.target as HTMLElement | null;
if (!targetEl) {
scheduleHide();
return;
}
const nodesFound = [...(targetElement?.parentElement?.querySelectorAll(targetSelector) ?? [])];
const triggerEl = targetEl.closest(targetSelector);
const insideTooltip = interactive && tooltipRef.current?.contains(targetEl);
if (!nodesFound.includes(targetElement)) {
setVisible(false);
if (!triggerEl && !insideTooltip) {
scheduleHide();
return;
}
if (hideTimeoutRef.current) {
clearTimeout(hideTimeoutRef.current);
hideTimeoutRef.current = null;
}
setVisible(true);
if (tooltipRef.current) {
const { clientX, clientY } = e;
const tooltipWidth = tooltipRef.current.offsetWidth;
const tooltipHeight = tooltipRef.current.offsetHeight;
let newLeft = clientX + 10;
let newTop = clientY + 10;
if (newLeft + tooltipWidth + 10 > window.innerWidth) {
newLeft = window.innerWidth - tooltipWidth - 10;
if (triggerEl && tooltipRef.current) {
const rect = triggerEl.getBoundingClientRect();
const tooltipEl = tooltipRef.current;
let x = evt.clientX;
let y = evt.clientY;
switch (tPosition) {
case TooltipPosition.left:
x = rect.left - tooltipEl.offsetWidth - offset;
y = rect.top + rect.height / 2 - tooltipEl.offsetHeight / 2;
if (x <= 0) {
x = rect.left + rect.width + offset;
}
break;
case TooltipPosition.right:
x = rect.left + rect.width + offset;
y = rect.top + rect.height / 2 - tooltipEl.offsetHeight / 2;
break;
case TooltipPosition.top:
x = rect.left + rect.width / 2 - tooltipEl.offsetWidth / 2;
y = rect.top - tooltipEl.offsetHeight - offset;
break;
case TooltipPosition.bottom:
x = rect.left + rect.width / 2 - tooltipEl.offsetWidth / 2;
y = rect.bottom + offset;
break;
}
if (newTop + tooltipHeight + 10 > window.innerHeight) {
newTop = window.innerHeight - tooltipHeight - 10;
}
setPosition({ top: newTop, left: newLeft });
setPos(calcTooltipPosition({ x, y }));
}
};
const deb = debounce(handleMouseMove, 10);
const debounced = debounce(handleMouseMove, 15);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
document.addEventListener('mousemove', deb);
document.addEventListener('mousemove', debounced);
return () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
document.removeEventListener('mousemove', deb);
document.removeEventListener('mousemove', debounced);
debounced.cancel();
};
}, [targetSelector]);
}, [targetSelector, interactive, tPosition, offset, calcTooltipPosition, scheduleHide]);
useEffect(() => {
return () => {
if (hideTimeoutRef.current) {
clearTimeout(hideTimeoutRef.current);
}
};
}, []);
if (!visible) return null;
return createPortal(
visible && (
<div
ref={tooltipRef}
className={clsx(
classes.tooltip,
'pointer-events-none',
'absolute px-2 py-2',
'border rounded border-green-300 border-opacity-10 bg-stone-900 bg-opacity-90',
{ ['invisible']: position === null },
className,
)}
style={{
top: position?.top ?? 0,
left: position?.left ?? 0,
zIndex: 10000,
}}
>
{typeof content === 'function' ? content() : content}
</div>
),
<div
ref={tooltipRef}
className={clsx(
classes.tooltip,
interactive ? 'pointer-events-auto' : 'pointer-events-none',
'absolute p-1 border rounded-sm border-green-300 border-opacity-10 bg-stone-900 bg-opacity-90',
className,
pos === null ? 'invisible' : '',
)}
style={{
top: pos?.top ?? 0,
left: pos?.left ?? 0,
zIndex: 10000,
}}
onMouseEnter={() => {
if (interactive && hideTimeoutRef.current) {
clearTimeout(hideTimeoutRef.current);
hideTimeoutRef.current = null;
}
setIsMouseInsideTooltip(true);
}}
onMouseLeave={() => {
setIsMouseInsideTooltip(false);
if (interactive) {
scheduleHide();
}
}}
{...restProps}
>
{typeof content === 'function' ? content() : content}
</div>,
document.body,
);
});
WdTooltip.displayName = 'WdTooltip';

View File

@@ -1,3 +1,25 @@
/* WdTooltipWrapper.module.scss */
.WdTooltipWrapperRoot {
display: inline-block;
}
.wdTooltipSizeXs {
font-size: 0.7rem;
max-width: 150px;
}
.wdTooltipSizeSm {
font-size: 0.8rem;
max-width: 200px;
}
.wdTooltipSizeMd {
font-size: 0.9rem;
max-width: 250px;
}
.wdTooltipSizeLg {
font-size: 1rem !important;
min-width: 350px;
}

View File

@@ -1,49 +1,54 @@
import React, { HTMLProps, MouseEventHandler, useCallback, useRef } from 'react';
import classes from './WdTooltipWrapper.module.scss';
import { WithChildren, WithClassName } from '@/hooks/Mapper/types/common.ts';
import { TooltipProps, WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit';
import { forwardRef, HTMLProps, ReactNode, useMemo } from 'react';
import clsx from 'clsx';
import { WdTooltip, WdTooltipHandlers, TooltipProps } from '@/hooks/Mapper/components/ui-kit';
import classes from './WdTooltipWrapper.module.scss';
type TooltipSize = 'xs' | 'sm' | 'md' | 'lg';
export type WdTooltipWrapperProps = {
content?: (() => React.ReactNode) | React.ReactNode;
} & WithChildren &
WithClassName &
HTMLProps<HTMLDivElement> &
content?: (() => ReactNode) | ReactNode;
size?: TooltipSize;
interactive?: boolean;
} & Omit<HTMLProps<HTMLDivElement>, 'content' | 'size'> &
Omit<TooltipProps, 'content'>;
export const WdTooltipWrapper = ({
className,
children,
content,
offset,
position,
targetSelector,
...props
}: WdTooltipWrapperProps) => {
const tooltipRef = useRef<WdTooltipHandlers>(null);
const handleShowDeleteTooltip: MouseEventHandler = useCallback(e => tooltipRef.current?.show(e), []);
const handleHideDeleteTooltip: MouseEventHandler = useCallback(e => tooltipRef.current?.hide(e), []);
export const WdTooltipWrapper = forwardRef<WdTooltipHandlers, WdTooltipWrapperProps>(
({ className, children, content, offset, position, targetSelector, interactive, size, ...props }, forwardedRef) => {
const suffix = useMemo(() => Math.random().toString(36).slice(2, 7), []);
const autoClass = `wdTooltipAutoTrigger-${suffix}`;
const finalTargetSelector = targetSelector || `.${autoClass}`;
return (
<>
<div
className={clsx(classes.WdTooltipWrapperRoot, className)}
{...props}
{...(content && {
onMouseEnter: handleShowDeleteTooltip,
onMouseLeave: handleHideDeleteTooltip,
})}
>
{children}
return (
<div className={clsx(classes.WdTooltipWrapperRoot, className)} {...props}>
{targetSelector ? <>{children}</> : <div className={autoClass}>{children}</div>}
<WdTooltip
ref={forwardedRef}
offset={offset}
position={position}
content={content}
interactive={interactive}
targetSelector={finalTargetSelector}
className={size ? sizeClass(size) : undefined}
/>
</div>
<WdTooltip
ref={tooltipRef}
offset={offset}
position={position}
content={content}
targetSelector={targetSelector}
/>
</>
);
};
);
},
);
WdTooltipWrapper.displayName = 'WdTooltipWrapper';
function sizeClass(size: TooltipSize) {
switch (size) {
case 'xs':
return classes.wdTooltipSizeXs;
case 'sm':
return classes.wdTooltipSizeSm;
case 'md':
return classes.wdTooltipSizeMd;
case 'lg':
return classes.wdTooltipSizeLg;
default:
return undefined;
}
}

View File

@@ -76,14 +76,22 @@ export const WindowWrapper = ({ onResize, onDrag, ...window }: WindowWrapperProp
</div>
);
};
export type ViewPortProps = { w: number; h: number };
export type WindowsManagerOnChange = (props: { windows: WindowProps[]; viewPort: ViewPortProps }) => void;
type WindowManagerProps = {
windows: WindowProps[];
viewPort?: ViewPortProps;
dragSelector?: string;
onChange?(windows: WindowProps[]): void;
onChange?: WindowsManagerOnChange;
};
export const WindowManager: React.FC<WindowManagerProps> = ({ windows: initialWindows, dragSelector, onChange }) => {
export const WindowManager: React.FC<WindowManagerProps> = ({
windows: initialWindows,
viewPort,
dragSelector,
onChange,
}) => {
const [windows, setWindows] = useState(
initialWindows.map((window, index) => ({
...window,
@@ -91,6 +99,16 @@ export const WindowManager: React.FC<WindowManagerProps> = ({ windows: initialWi
})),
);
const refPrevSize = useRef({ w: 0, h: 0 });
useEffect(() => {
if (!viewPort) {
return;
}
refPrevSize.current = viewPort;
}, [viewPort]);
useEffect(() => {
setWindows(initialWindows.slice(0));
}, [initialWindows]);
@@ -102,14 +120,15 @@ export const WindowManager: React.FC<WindowManagerProps> = ({ windows: initialWi
const startMousePositionRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
const startWindowStateRef = useRef<{ x: number; y: number; width: number; height: number }>(DefaultWindowState);
const ref = useRef({ windows, onChange });
ref.current = { windows, onChange };
const refPrevSize = useRef({ w: 0, h: 0 });
const ref = useRef({ windows, viewPort, onChange });
ref.current = { windows, viewPort, onChange };
const onDebouncedChange = useMemo(() => {
return debounce(() => {
ref.current.onChange?.(ref.current.windows);
ref.current.onChange?.({
windows: ref.current.windows,
viewPort: refPrevSize.current,
});
}, 20);
}, []);
@@ -336,7 +355,7 @@ export const WindowManager: React.FC<WindowManagerProps> = ({ windows: initialWi
// Handle resize of the container and reposition windows
useEffect(() => {
if (containerRef.current) {
if (ref.current.viewPort == null && containerRef.current) {
refPrevSize.current = { w: containerRef.current.clientWidth, h: containerRef.current.clientHeight };
}
@@ -384,10 +403,14 @@ export const WindowManager: React.FC<WindowManagerProps> = ({ windows: initialWi
next.position.y = container.clientHeight - next.size.height - SNAP_GAP;
}
if (next.position.y < 0) {
if (next.position.y < SNAP_GAP) {
next.position.y = 0;
}
if (next.position.x < SNAP_GAP) {
next.position.x = SNAP_GAP;
}
return next;
});
});

View File

@@ -11,3 +11,5 @@ export * from './WdImgButton';
export * from './WdTooltip';
export * from './WdCheckbox';
export * from './TimeAgo';
export * from './WdTooltipWrapper';
export * from './WdResponsiveCheckBox';

View File

@@ -0,0 +1,7 @@
import { AvailableThemes, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
export const useTheme = (): AvailableThemes => {
const { interfaceSettings } = useMapRootState();
return interfaceSettings.theme;
};

View File

@@ -1,21 +1,27 @@
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
import { createContext, Dispatch, ForwardedRef, forwardRef, SetStateAction, useContext, useEffect } from 'react';
import { MapUnionTypes, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
import {
CommandLinkSignatureToSystem,
MapUnionTypes,
OutCommandHandler,
SolarSystemConnection,
} from '@/hooks/Mapper/types';
import { useMapRootHandlers } from '@/hooks/Mapper/mapRootProvider/hooks';
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
import useLocalStorageState from 'use-local-storage-state';
import {
ToggleWidgetVisibility,
UpdateWidgetSettingsFunc,
useStoreWidgets,
WindowStoreInfo,
} from '@/hooks/Mapper/mapRootProvider/hooks/useStoreWidgets.ts';
import { CommandLinkSignatureToSystem } from '@/hooks/Mapper/types';
import { WindowsManagerOnChange } from '@/hooks/Mapper/components/ui-kit/WindowManager';
import { DetailedKill } from '../types/kills';
export type MapRootData = MapUnionTypes & {
selectedSystems: string[];
selectedConnections: Pick<SolarSystemConnection, 'source' | 'target'>[];
linkSignatureToSystem: CommandLinkSignatureToSystem | null;
detailedKills: Record<string, DetailedKill[]>;
};
const INITIAL_DATA: MapRootData = {
@@ -31,14 +37,20 @@ const INITIAL_DATA: MapRootData = {
routes: undefined,
kills: [],
connections: [],
detailedKills: {},
selectedSystems: [],
selectedConnections: [],
userPermissions: {},
options: {},
isSubscriptionActive: false,
linkSignatureToSystem: null,
};
export enum AvailableThemes {
default = 'default',
pathfinder = 'pathfinder',
}
export enum InterfaceStoredSettingsProps {
isShowMenu = 'isShowMenu',
isShowMinimap = 'isShowMinimap',
@@ -58,7 +70,7 @@ export type InterfaceStoredSettings = {
isShowUnsplashedSignatures: boolean;
isShowBackgroundPattern: boolean;
isSoftBackground: boolean;
theme: string;
theme: AvailableThemes;
};
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
@@ -69,7 +81,7 @@ export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
isShowUnsplashedSignatures: false,
isShowBackgroundPattern: true,
isSoftBackground: false,
theme: 'default',
theme: AvailableThemes.default,
};
export interface MapRootContextProps {
@@ -80,7 +92,7 @@ export interface MapRootContextProps {
setInterfaceSettings: Dispatch<SetStateAction<InterfaceStoredSettings>>;
windowsSettings: WindowStoreInfo;
toggleWidgetVisibility: ToggleWidgetVisibility;
updateWidgetSettings: UpdateWidgetSettingsFunc;
updateWidgetSettings: WindowsManagerOnChange;
resetWidgets: () => void;
}

View File

@@ -10,17 +10,18 @@ import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoa
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { emitMapEvent } from '@/hooks/Mapper/events';
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
import { DetailedKill } from '@/hooks/Mapper/types/kills';
export const useCommandsSystems = () => {
const {
update,
data: { systems, systemSignatures },
data: { systems, systemSignatures, detailedKills },
outCommand,
} = useMapRootState();
const { addSystemStatic } = useLoadSystemStatic({ systems: [] });
const ref = useRef({ systems, systemSignatures, update, addSystemStatic });
ref.current = { systems, systemSignatures, update, addSystemStatic };
const ref = useRef({ systems, systemSignatures, update, addSystemStatic, detailedKills });
ref.current = { systems, systemSignatures, update, addSystemStatic, detailedKills };
const addSystems = useCallback((systemsToAdd: CommandAddSystems) => {
const { update, addSystemStatic, systems } = ref.current;
@@ -84,5 +85,23 @@ export const useCommandsSystems = () => {
update({ linkSignatureToSystem: command }, true);
}, []);
return { addSystems, removeSystems, updateSystems, updateSystemSignatures, updateLinkSignatureToSystem };
const updateDetailedKills = useCallback((newKillsMap: Record<string, DetailedKill[]>) => {
const { update, detailedKills } = ref.current;
const updated = { ...detailedKills };
for (const [systemId, killsArr] of Object.entries(newKillsMap)) {
updated[systemId] = killsArr;
}
update({ detailedKills: updated }, true);
}, []);
return {
addSystems,
removeSystems,
updateSystems,
updateSystemSignatures,
updateLinkSignatureToSystem,
updateDetailedKills,
};
};

View File

@@ -21,6 +21,7 @@ export const useMapInit = () => {
hubs,
user_permissions,
options,
is_subscription_active,
}: CommandInit) => {
const updateData: Partial<MapRootData> = {};
@@ -65,6 +66,8 @@ export const useMapInit = () => {
updateData.options = options;
}
updateData.isSubscriptionActive = is_subscription_active;
if (system_static_infos) {
system_static_infos.forEach(static_info => {
addSystemStatic(static_info);

View File

@@ -7,6 +7,7 @@ import {
CommandCharactersUpdated,
CommandCharacterUpdated,
CommandInit,
CommandLinkSignatureToSystem,
CommandMapUpdated,
CommandPresentCharacters,
CommandRemoveConnections,
@@ -29,11 +30,18 @@ import {
} from './api';
import { emitMapEvent } from '@/hooks/Mapper/events';
import { DetailedKill } from '../../types/kills';
export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
const mapInit = useMapInit();
const { addSystems, removeSystems, updateSystems, updateSystemSignatures, updateLinkSignatureToSystem } =
useCommandsSystems();
const {
addSystems,
removeSystems,
updateSystems,
updateSystemSignatures,
updateLinkSignatureToSystem,
updateDetailedKills,
} = useCommandsSystems();
const { addConnections, removeConnections, updateConnection } = useCommandsConnections();
const { charactersUpdated, characterAdded, characterRemoved, characterUpdated, presentCharacters } =
useCommandsCharacters();
@@ -111,6 +119,10 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
// do nothing here
break;
case Commands.detailedKillsUpdated:
updateDetailedKills(data as Record<string, DetailedKill[]>);
break;
default:
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
break;

View File

@@ -8,13 +8,14 @@ import {
} from '@/hooks/Mapper/components/mapInterface/constants.tsx';
import { WindowProps } from '@/hooks/Mapper/components/ui-kit/WindowManager/types.ts';
import { useCallback, useEffect, useRef } from 'react';
import { SNAP_GAP } from '@/hooks/Mapper/components/ui-kit/WindowManager';
import { SNAP_GAP, WindowsManagerOnChange } from '@/hooks/Mapper/components/ui-kit/WindowManager';
export type StoredWindowProps = Omit<WindowProps, 'content'>;
export type WindowStoreInfo = {
version: number;
windows: StoredWindowProps[];
visible: WidgetsIds[];
viewPort?: { w: number; h: number } | undefined;
};
export type UpdateWidgetSettingsFunc = (widgets: WindowProps[]) => void;
export type ToggleWidgetVisibility = (widgetId: WidgetsIds) => void;
@@ -33,7 +34,7 @@ export const useStoreWidgets = () => {
const ref = useRef({ windowsSettings, setWindowsSettings });
ref.current = { windowsSettings, setWindowsSettings };
const updateWidgetSettings: UpdateWidgetSettingsFunc = useCallback(newWindows => {
const updateWidgetSettings: WindowsManagerOnChange = useCallback(({ windows, viewPort }) => {
const { setWindowsSettings } = ref.current;
setWindowsSettings(({ version, visible /*, windows*/ }: WindowStoreInfo) => {
@@ -41,13 +42,14 @@ export const useStoreWidgets = () => {
version,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
windows: DEFAULT_WIDGETS.map(({ content, ...x }) => {
const windowProp = newWindows.find(j => j.id === x.id);
const windowProp = windows.find(j => j.id === x.id);
if (windowProp) {
return windowProp;
}
return x;
}),
viewPort,
visible,
};
});
@@ -92,7 +94,7 @@ export const useStoreWidgets = () => {
return;
}
const { version, windows, visible } = JSON.parse(raw) as WindowStoreInfo;
const { version, windows, visible, viewPort } = JSON.parse(raw) as WindowStoreInfo;
if (!version || CURRENT_WINDOWS_VERSION > version) {
setWindowsSettings(getDefaultWidgetProps());
}
@@ -104,6 +106,7 @@ export const useStoreWidgets = () => {
version: CURRENT_WINDOWS_VERSION,
windows: out as WindowProps[],
visible,
viewPort,
});
}, []);

View File

@@ -2,3 +2,37 @@ export type Kill = {
solar_system_id: number;
kills: number;
};
export interface DetailedKill {
killmail_id: number;
solar_system_id: number;
kill_time?: string;
zkb?: Record<string, unknown>;
victim_char_id?: number | null;
victim_char_name?: string;
victim_corp_id?: number | null;
victim_corp_ticker?: string;
victim_corp_name?: string;
victim_alliance_id?: number | null;
victim_alliance_ticker?: string;
victim_alliance_name?: string;
victim_ship_type_id?: number | null;
victim_ship_name?: string;
final_blow_char_id?: number | null;
final_blow_char_name?: string;
final_blow_corp_id?: number | null;
final_blow_corp_ticker?: string;
final_blow_corp_name?: string;
final_blow_alliance_id?: number | null;
final_blow_alliance_ticker?: string;
final_blow_alliance_name?: string;
final_blow_ship_type_id?: number | null;
final_blow_ship_name?: string;
attacker_count?: number | null;
total_value?: number | null;
npc?: boolean;
}

View File

@@ -3,8 +3,8 @@ import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
import { WormholeDataRaw } from '@/hooks/Mapper/types/wormholes.ts';
import { CharacterTypeRaw } from '@/hooks/Mapper/types/character.ts';
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
import { Kill } from '@/hooks/Mapper/types/kills.ts';
import { UserPermissions } from '@/hooks/Mapper/types';
import { DetailedKill, Kill } from '@/hooks/Mapper/types/kills.ts';
import { SignatureGroup, UserPermissions } from '@/hooks/Mapper/types';
export enum Commands {
init = 'init',
@@ -21,6 +21,7 @@ export enum Commands {
updateConnection = 'update_connection',
mapUpdated = 'map_updated',
killsUpdated = 'kills_updated',
detailedKillsUpdated = 'detailed_kills_updated',
routes = 'routes',
centerSystem = 'center_system',
selectSystem = 'select_system',
@@ -43,6 +44,7 @@ export type Command =
| Commands.updateConnection
| Commands.mapUpdated
| Commands.killsUpdated
| Commands.detailedKillsUpdated
| Commands.routes
| Commands.selectSystem
| Commands.centerSystem
@@ -76,9 +78,11 @@ export type CommandCharacterRemoved = CharacterTypeRaw;
export type CommandCharacterUpdated = CharacterTypeRaw;
export type CommandPresentCharacters = string[];
export type CommandUpdateConnection = SolarSystemConnection;
export type CommandSignaturesUpdated = string;
export type CommandMapUpdated = Partial<CommandInit>;
export type CommandRoutes = RoutesList;
export type CommandKillsUpdated = Kill[];
export type CommandDetailedKillsUpdated = Record<string, DetailedKill[]>;
export type CommandSelectSystem = string | undefined;
export type CommandCenterSystem = string | undefined;
export type CommandLinkSignatureToSystem = {
@@ -103,6 +107,7 @@ export interface CommandData {
[Commands.mapUpdated]: CommandMapUpdated;
[Commands.routes]: CommandRoutes;
[Commands.killsUpdated]: CommandKillsUpdated;
[Commands.detailedKillsUpdated]: CommandDetailedKillsUpdated;
[Commands.selectSystem]: CommandSelectSystem;
[Commands.centerSystem]: CommandCenterSystem;
[Commands.linkSignatureToSystem]: CommandLinkSignatureToSystem;
@@ -151,6 +156,8 @@ export enum OutCommand {
linkSignatureToSystem = 'link_signature_to_system',
getCorporationNames = 'get_corporation_names',
getCorporationTicker = 'get_corporation_ticker',
getSystemKills = 'get_system_kills',
getSystemsKills = 'get_systems_kills',
// Only UI commands
openSettings = 'open_settings',
@@ -161,4 +168,5 @@ export enum OutCommand {
searchSystems = 'search_systems',
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type OutCommandHandler = <T = any>(event: { type: OutCommand; data: any }) => Promise<T>;

View File

@@ -22,4 +22,5 @@ export type MapUnionTypes = {
connections: SolarSystemConnection[];
userPermissions: Partial<UserPermissions>;
options: Record<string, string | boolean>;
isSubscriptionActive: boolean;
};

View File

@@ -1,6 +1,7 @@
import { XYPosition } from 'reactflow';
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
import { DetailedKill } from './kills';
export enum SolarSystemStaticInfoRawNames {
regionId = 'region_id',

View File

@@ -25,10 +25,11 @@
"primeflex": "^3.3.1",
"primeicons": "^7.0.0",
"primereact": "^10.6.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.0.13",
"react-event-hook": "^3.1.2",
"react-flow-renderer": "^10.3.17",
"react-grid-layout": "^1.3.4",
"react-hook-form": "^7.53.1",
"react-usestateref": "^1.0.9",
"reactflow": "^11.11.4",
@@ -42,12 +43,11 @@
"@tailwindcss/typography": "^0.5.13",
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.isequal": "^4.5.8",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.1",
"@types/react-grid-layout": "^1.3.4",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-react": "^4.3.0",
"@vitejs/plugin-react": "^4.3.3",
"@vitejs/plugin-react-refresh": "^1.3.6",
"autoprefixer": "^10.4.19",
"child_process": "^1.0.2",
@@ -82,5 +82,6 @@
},
"keywords": [],
"author": "",
"license": "ISC"
"license": "ISC",
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,24 +1,10 @@
import cdn from 'vite-plugin-cdn-import';
import path from 'path';
import react from '@vitejs/plugin-react';
export default {
publicDir: './static',
plugins: [
cdn({
modules: [
{
name: 'react',
var: 'React',
path: `umd/react.production.min.js`,
},
{
name: 'react-dom',
var: 'ReactDOM',
path: `umd/react-dom.production.min.js`,
},
],
}),
],
plugins: [react()],
build: {
target: 'es2018',
format: 'esm',
@@ -27,13 +13,8 @@ export default {
emptyOutDir: true,
assetsInlineLimit: 0,
rollupOptions: {
external: ['react', 'react-dom'],
input: ['app.tsx'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name][extname]',

View File

@@ -28,12 +28,26 @@
"@babel/highlight" "^7.24.2"
picocolors "^1.0.0"
"@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.2":
version "7.26.2"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
dependencies:
"@babel/helper-validator-identifier" "^7.25.9"
js-tokens "^4.0.0"
picocolors "^1.0.0"
"@babel/compat-data@^7.23.5":
version "7.24.4"
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz"
integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==
"@babel/core@^7.14.8", "@babel/core@^7.24.5":
"@babel/compat-data@^7.26.5":
version "7.26.5"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.5.tgz#df93ac37f4417854130e21d72c66ff3d4b897fc7"
integrity sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==
"@babel/core@^7.14.8":
version "7.24.5"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz"
integrity sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==
@@ -54,6 +68,27 @@
json5 "^2.2.3"
semver "^6.3.1"
"@babel/core@^7.26.0":
version "7.26.7"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.7.tgz#0439347a183b97534d52811144d763a17f9d2b24"
integrity sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==
dependencies:
"@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.26.2"
"@babel/generator" "^7.26.5"
"@babel/helper-compilation-targets" "^7.26.5"
"@babel/helper-module-transforms" "^7.26.0"
"@babel/helpers" "^7.26.7"
"@babel/parser" "^7.26.7"
"@babel/template" "^7.25.9"
"@babel/traverse" "^7.26.7"
"@babel/types" "^7.26.7"
convert-source-map "^2.0.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.3"
semver "^6.3.1"
"@babel/generator@^7.24.5":
version "7.24.5"
resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz"
@@ -64,6 +99,17 @@
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^2.5.1"
"@babel/generator@^7.26.5":
version "7.26.5"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.5.tgz#e44d4ab3176bbcaf78a5725da5f1dc28802a9458"
integrity sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==
dependencies:
"@babel/parser" "^7.26.5"
"@babel/types" "^7.26.5"
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^3.0.2"
"@babel/helper-compilation-targets@^7.23.6":
version "7.23.6"
resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz"
@@ -75,6 +121,17 @@
lru-cache "^5.1.1"
semver "^6.3.1"
"@babel/helper-compilation-targets@^7.26.5":
version "7.26.5"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8"
integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==
dependencies:
"@babel/compat-data" "^7.26.5"
"@babel/helper-validator-option" "^7.25.9"
browserslist "^4.24.0"
lru-cache "^5.1.1"
semver "^6.3.1"
"@babel/helper-environment-visitor@^7.22.20":
version "7.22.20"
resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz"
@@ -102,6 +159,14 @@
dependencies:
"@babel/types" "^7.24.0"
"@babel/helper-module-imports@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715"
integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==
dependencies:
"@babel/traverse" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/helper-module-transforms@^7.24.5":
version "7.24.5"
resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz"
@@ -113,11 +178,25 @@
"@babel/helper-split-export-declaration" "^7.24.5"
"@babel/helper-validator-identifier" "^7.24.5"
"@babel/helper-module-transforms@^7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae"
integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==
dependencies:
"@babel/helper-module-imports" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@babel/traverse" "^7.25.9"
"@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.24.5":
version "7.24.5"
resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz"
integrity sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==
"@babel/helper-plugin-utils@^7.25.9":
version "7.26.5"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35"
integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==
"@babel/helper-simple-access@^7.24.5":
version "7.24.5"
resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz"
@@ -137,16 +216,31 @@
resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz"
integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==
"@babel/helper-string-parser@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c"
integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
"@babel/helper-validator-identifier@^7.24.5":
version "7.24.5"
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz"
integrity sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==
"@babel/helper-validator-identifier@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
"@babel/helper-validator-option@^7.23.5":
version "7.23.5"
resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz"
integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==
"@babel/helper-validator-option@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72"
integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==
"@babel/helpers@^7.24.5":
version "7.24.5"
resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz"
@@ -156,6 +250,14 @@
"@babel/traverse" "^7.24.5"
"@babel/types" "^7.24.5"
"@babel/helpers@^7.26.7":
version "7.26.7"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.7.tgz#fd1d2a7c431b6e39290277aacfd8367857c576a4"
integrity sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==
dependencies:
"@babel/template" "^7.25.9"
"@babel/types" "^7.26.7"
"@babel/highlight@^7.24.2":
version "7.24.5"
resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz"
@@ -171,20 +273,41 @@
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz"
integrity sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==
"@babel/plugin-transform-react-jsx-self@^7.14.5", "@babel/plugin-transform-react-jsx-self@^7.24.5":
"@babel/parser@^7.25.9", "@babel/parser@^7.26.5", "@babel/parser@^7.26.7":
version "7.26.7"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.7.tgz#e114cd099e5f7d17b05368678da0fb9f69b3385c"
integrity sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==
dependencies:
"@babel/types" "^7.26.7"
"@babel/plugin-transform-react-jsx-self@^7.14.5":
version "7.24.5"
resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.5.tgz"
integrity sha512-RtCJoUO2oYrYwFPtR1/jkoBEcFuI1ae9a9IMxeyAVa3a1Ap4AnxmyIKG2b2FaJKqkidw/0cxRbWN+HOs6ZWd1w==
dependencies:
"@babel/helper-plugin-utils" "^7.24.5"
"@babel/plugin-transform-react-jsx-source@^7.14.5", "@babel/plugin-transform-react-jsx-source@^7.24.1":
"@babel/plugin-transform-react-jsx-self@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz#c0b6cae9c1b73967f7f9eb2fca9536ba2fad2858"
integrity sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==
dependencies:
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/plugin-transform-react-jsx-source@^7.14.5":
version "7.24.1"
resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.1.tgz"
integrity sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA==
dependencies:
"@babel/helper-plugin-utils" "^7.24.0"
"@babel/plugin-transform-react-jsx-source@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz#4c6b8daa520b5f155b5fb55547d7c9fa91417503"
integrity sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==
dependencies:
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/runtime@^7.12.5":
version "7.25.0"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz"
@@ -215,6 +338,15 @@
"@babel/parser" "^7.24.0"
"@babel/types" "^7.24.0"
"@babel/template@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016"
integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==
dependencies:
"@babel/code-frame" "^7.25.9"
"@babel/parser" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/traverse@^7.24.5":
version "7.24.5"
resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz"
@@ -231,6 +363,19 @@
debug "^4.3.1"
globals "^11.1.0"
"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.7":
version "7.26.7"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.7.tgz#99a0a136f6a75e7fb8b0a1ace421e0b25994b8bb"
integrity sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==
dependencies:
"@babel/code-frame" "^7.26.2"
"@babel/generator" "^7.26.5"
"@babel/parser" "^7.26.7"
"@babel/template" "^7.25.9"
"@babel/types" "^7.26.7"
debug "^4.3.1"
globals "^11.1.0"
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.24.0", "@babel/types@^7.24.5":
version "7.24.5"
resolved "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz"
@@ -240,6 +385,14 @@
"@babel/helper-validator-identifier" "^7.24.5"
to-fast-properties "^2.0.0"
"@babel/types@^7.25.9", "@babel/types@^7.26.5", "@babel/types@^7.26.7":
version "7.26.7"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.7.tgz#5e2b89c0768e874d4d061961f3a5a153d71dc17a"
integrity sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==
dependencies:
"@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@esbuild/aix-ppc64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
@@ -951,19 +1104,10 @@
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz"
integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==
"@types/react-dom@18.2.1":
version "18.2.1"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.1.tgz"
integrity sha512-8QZEV9+Kwy7tXFmjJrp3XUKQSs9LTnE0KnoUb0YCguWBiNW0Yfb2iBMYZ08WPg35IR6P3Z0s00B15SwZnO26+w==
dependencies:
"@types/react" "*"
"@types/react-grid-layout@^1.3.4":
version "1.3.5"
resolved "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.5.tgz"
integrity sha512-WH/po1gcEcoR6y857yAnPGug+ZhkF4PaTUxgAbwfeSH/QOgVSakKHBXoPGad/sEznmkiaK3pqHk+etdWisoeBQ==
dependencies:
"@types/react" "*"
"@types/react-dom@^18.3.1":
version "18.3.5"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.5.tgz#45f9f87398c5dcea085b715c58ddcf1faf65f716"
integrity sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==
"@types/react-transition-group@^4.4.1":
version "4.4.10"
@@ -972,7 +1116,7 @@
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@18.2.0":
"@types/react@*":
version "18.2.0"
resolved "https://registry.npmjs.org/@types/react/-/react-18.2.0.tgz"
integrity sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==
@@ -981,6 +1125,14 @@
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@^18.3.12":
version "18.3.18"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.18.tgz#9b382c4cd32e13e463f97df07c2ee3bbcd26904b"
integrity sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/resize-observer-browser@^0.1.7":
version "0.1.11"
resolved "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.11.tgz"
@@ -1098,14 +1250,14 @@
"@rollup/pluginutils" "^4.1.1"
react-refresh "^0.10.0"
"@vitejs/plugin-react@^4.3.0":
version "4.3.1"
resolved "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz"
integrity sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==
"@vitejs/plugin-react@^4.3.3":
version "4.3.4"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz#c64be10b54c4640135a5b28a2432330e88ad7c20"
integrity sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==
dependencies:
"@babel/core" "^7.24.5"
"@babel/plugin-transform-react-jsx-self" "^7.24.5"
"@babel/plugin-transform-react-jsx-source" "^7.24.1"
"@babel/core" "^7.26.0"
"@babel/plugin-transform-react-jsx-self" "^7.25.9"
"@babel/plugin-transform-react-jsx-source" "^7.25.9"
"@types/babel__core" "^7.20.5"
react-refresh "^0.14.2"
@@ -1324,6 +1476,16 @@ browserslist@^4.22.2, browserslist@^4.23.0:
node-releases "^2.0.14"
update-browserslist-db "^1.0.13"
browserslist@^4.24.0:
version "4.24.4"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b"
integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==
dependencies:
caniuse-lite "^1.0.30001688"
electron-to-chromium "^1.5.73"
node-releases "^2.0.19"
update-browserslist-db "^1.1.1"
call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7:
version "1.0.7"
resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz"
@@ -1346,9 +1508,14 @@ camelcase-css@^2.0.1:
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599:
version "1.0.30001600"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz"
integrity sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==
version "1.0.30001696"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz"
integrity sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==
caniuse-lite@^1.0.30001688:
version "1.0.30001697"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001697.tgz#040bbbb54463c4b4b3377c716b34a322d16e6fc7"
integrity sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==
chalk@^2.4.2:
version "2.4.2"
@@ -1401,12 +1568,7 @@ cliui@^8.0.1:
strip-ansi "^6.0.1"
wrap-ansi "^7.0.0"
clsx@^1.1.1:
version "1.2.1"
resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
clsx@^2.0.0, clsx@^2.1.1:
clsx@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz"
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
@@ -1660,6 +1822,11 @@ electron-to-chromium@^1.4.668:
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.722.tgz"
integrity sha512-5nLE0TWFFpZ80Crhtp4pIp8LXCztjYX41yUcV6b+bKR2PqzjskTMOOlBi1VjBHlvHwS+4gar7kNKOrsbsewEZQ==
electron-to-chromium@^1.5.73:
version "1.5.91"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.91.tgz#cf5567f6853062493242133aefd4dc8dc8440abd"
integrity sha512-sNSHHyq048PFmZY4S90ax61q+gLCs0X0YmcOII9wG9S2XwbVr+h4VW2wWhnbp/Eys3cCwTxVF292W3qPaxIapQ==
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"
@@ -1820,6 +1987,11 @@ escalade@^3.1.1:
resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz"
integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==
escalade@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
@@ -1989,11 +2161,6 @@ fast-diff@^1.1.2:
resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz"
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
fast-equals@^4.0.3:
version "4.0.3"
resolved "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz"
integrity sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==
fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.2:
version "3.3.2"
resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz"
@@ -2597,6 +2764,11 @@ jsesc@^2.5.1:
resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
jsesc@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
json-buffer@3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz"
@@ -2814,6 +2986,11 @@ node-releases@^2.0.14:
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz"
integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==
node-releases@^2.0.19:
version "2.0.19"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314"
integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
@@ -2975,6 +3152,11 @@ picocolors@^1, picocolors@^1.0.0:
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picocolors@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
@@ -3139,7 +3321,7 @@ primereact@^10.6.5:
"@types/react-transition-group" "^4.4.1"
react-transition-group "^4.4.1"
prop-types@15.x, prop-types@^15.6.2, prop-types@^15.8.1:
prop-types@^15.6.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -3166,13 +3348,13 @@ react-dom@18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"
react-draggable@^4.0.3, react-draggable@^4.4.5:
version "4.4.6"
resolved "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz"
integrity sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==
react-dom@^18.3.1:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
dependencies:
clsx "^1.1.1"
prop-types "^15.8.1"
loose-envify "^1.1.0"
scheduler "^0.23.2"
react-error-boundary@^4.0.13:
version "4.0.13"
@@ -3200,18 +3382,6 @@ react-flow-renderer@^10.3.17:
d3-zoom "^3.0.0"
zustand "^3.7.2"
react-grid-layout@^1.3.4:
version "1.4.4"
resolved "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.4.4.tgz"
integrity sha512-7+Lg8E8O8HfOH5FrY80GCIR1SHTn2QnAYKh27/5spoz+OHhMmEhU/14gIkRzJOtympDPaXcVRX/nT1FjmeOUmQ==
dependencies:
clsx "^2.0.0"
fast-equals "^4.0.3"
prop-types "^15.8.1"
react-draggable "^4.4.5"
react-resizable "^3.0.5"
resize-observer-polyfill "^1.5.1"
react-hook-form@^7.53.1:
version "7.53.1"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.1.tgz#3f2cd1ed2b3af99416a4ac674da2d526625add67"
@@ -3232,14 +3402,6 @@ react-refresh@^0.14.2:
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz"
integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==
react-resizable@^3.0.5:
version "3.0.5"
resolved "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz"
integrity sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==
dependencies:
prop-types "15.x"
react-draggable "^4.0.3"
react-transition-group@^4.4.1:
version "4.4.5"
resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz"
@@ -3262,6 +3424,13 @@ react@18.2.0:
dependencies:
loose-envify "^1.1.0"
react@^18.3.1:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
dependencies:
loose-envify "^1.1.0"
reactflow@^11.11.4:
version "11.11.4"
resolved "https://registry.yarnpkg.com/reactflow/-/reactflow-11.11.4.tgz#e3593e313420542caed81aecbd73fb9bc6576653"
@@ -3321,11 +3490,6 @@ require-directory@^2.1.1:
resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz"
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
@@ -3445,6 +3609,13 @@ scheduler@^0.23.0:
dependencies:
loose-envify "^1.1.0"
scheduler@^0.23.2:
version "0.23.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
dependencies:
loose-envify "^1.1.0"
semver@^6.3.1:
version "6.3.1"
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
@@ -3801,6 +3972,14 @@ update-browserslist-db@^1.0.13:
escalade "^3.1.1"
picocolors "^1.0.0"
update-browserslist-db@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz#97e9c96ab0ae7bcac08e9ae5151d26e6bc6b5580"
integrity sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==
dependencies:
escalade "^3.2.0"
picocolors "^1.1.1"
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz"

View File

@@ -53,6 +53,16 @@ public_api_disabled =
|> get_var_from_path_or_env("WANDERER_PUBLIC_API_DISABLED", "false")
|> String.to_existing_atom()
character_api_disabled =
config_dir
|> get_var_from_path_or_env("WANDERER_CHARACTER_API_DISABLED", "false")
|> String.to_existing_atom()
zkill_preload_disabled =
config_dir
|> get_var_from_path_or_env("WANDERER_ZKILL_PRELOAD_DISABLED", "false")
|> String.to_existing_atom()
map_subscriptions_enabled =
config_dir
|> get_var_from_path_or_env("WANDERER_MAP_SUBSCRIPTIONS_ENABLED", "false")
@@ -102,6 +112,11 @@ admins =
admins -> admins |> String.split(",")
end
restrict_maps_creation =
config_dir
|> get_var_from_path_or_env("WANDERER_RESTRICT_MAPS_CREATION", "false")
|> String.to_existing_atom()
config :wanderer_app,
web_app_url: web_app_url,
git_sha: System.get_env("GIT_SHA", "111"),
@@ -113,11 +128,14 @@ config :wanderer_app,
corp_id: System.get_env("WANDERER_CORP_ID", "-1") |> String.to_integer(),
corp_wallet: System.get_env("WANDERER_CORP_WALLET", ""),
public_api_disabled: public_api_disabled,
character_api_disabled: character_api_disabled,
zkill_preload_disabled: zkill_preload_disabled,
map_subscriptions_enabled: map_subscriptions_enabled,
map_connection_auto_expire_hours: map_connection_auto_expire_hours,
map_connection_auto_eol_hours: map_connection_auto_eol_hours,
map_connection_eol_expire_timeout_mins: map_connection_eol_expire_timeout_mins,
wallet_tracking_enabled: wallet_tracking_enabled,
restrict_maps_creation: restrict_maps_creation,
subscription_settings: %{
plans: [
%{

View File

@@ -12,7 +12,6 @@ defmodule WandererApp.Api.AccessList do
code_interface do
define(:create, action: :create)
define(:available, action: :available)
define(:new, action: :new)
define(:read, action: :read)
@@ -39,7 +38,8 @@ defmodule WandererApp.Api.AccessList do
end
create :new do
accept [:name, :description, :owner_id]
# Added :api_key to the accepted attributes
accept [:name, :description, :owner_id, :api_key]
primary?(true)
argument :owner_id, :uuid, allow_nil?: false
@@ -48,7 +48,7 @@ defmodule WandererApp.Api.AccessList do
end
update :update do
accept [:name, :description, :owner_id]
accept [:name, :description, :owner_id, :api_key]
primary?(true)
end
@@ -68,6 +68,10 @@ defmodule WandererApp.Api.AccessList do
allow_nil? true
end
attribute :api_key, :string do
allow_nil? true
end
create_timestamp(:inserted_at)
update_timestamp(:updated_at)
end

View File

@@ -127,7 +127,6 @@ defmodule WandererApp.Api.Map do
update :update_api_key do
accept [:public_api_key]
end
end
attributes do

View File

@@ -13,39 +13,48 @@ defmodule WandererApp.Application do
WandererAppWeb.Telemetry,
WandererApp.Vault,
WandererApp.Repo,
{Phoenix.PubSub, name: WandererApp.PubSub, adapter_name: Phoenix.PubSub.PG2},
{Finch, name: WandererApp.Finch},
{
Finch,
name: WandererApp.Finch,
pools: %{
default: [
size: 25, # number of connections per pool
count: 2, # number of pools (so total 50 connections)
]
}
},
WandererApp.Cache,
Supervisor.child_spec({Cachex, name: :system_static_info_cache},
id: :system_static_info_cache_worker
),
Supervisor.child_spec({Cachex, name: :ship_types_cache},
id: :ship_types_cache_worker
),
Supervisor.child_spec({Cachex, name: :character_cache}, id: :character_cache_worker),
Supervisor.child_spec({Cachex, name: :map_cache}, id: :map_cache_worker),
Supervisor.child_spec({Cachex, name: :character_state_cache},
id: :character_state_cache_worker
),
Supervisor.child_spec({Cachex, name: :system_static_info_cache}, id: :system_static_info_cache_worker),
Supervisor.child_spec({Cachex, name: :ship_types_cache}, id: :ship_types_cache_worker),
Supervisor.child_spec({Cachex, name: :character_cache}, id: :character_cache_worker),
Supervisor.child_spec({Cachex, name: :map_cache}, id: :map_cache_worker),
Supervisor.child_spec({Cachex, name: :character_state_cache}, id: :character_state_cache_worker),
WandererApp.Scheduler,
{Registry, keys: :unique, name: WandererApp.MapRegistry},
{Registry, keys: :unique, name: WandererApp.Character.TrackerRegistry},
{PartitionSupervisor,
child_spec: DynamicSupervisor, name: WandererApp.Map.DynamicSupervisors},
{PartitionSupervisor,
child_spec: DynamicSupervisor, name: WandererApp.Character.DynamicSupervisors},
{PartitionSupervisor, child_spec: DynamicSupervisor, name: WandererApp.Map.DynamicSupervisors},
{PartitionSupervisor, child_spec: DynamicSupervisor, name: WandererApp.Character.DynamicSupervisors},
WandererApp.Zkb.Supervisor,
WandererApp.Server.ServerStatusTracker,
WandererApp.Server.TheraDataFetcher,
WandererApp.Character.TrackerManager,
WandererApp.Map.Manager,
WandererApp.Map.ZkbDataFetcher,
WandererAppWeb.Presence,
WandererAppWeb.Endpoint
] ++ maybe_start_corp_wallet_tracker(WandererApp.Env.map_subscriptions_enabled?())
]
++ maybe_start_corp_wallet_tracker(WandererApp.Env.map_subscriptions_enabled?())
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: WandererApp.Supervisor]
Supervisor.start_link(children, opts)
@@ -59,8 +68,6 @@ defmodule WandererApp.Application do
end
end
# Tell Phoenix to update the endpoint configuration
# whenever the application is updated.
@impl true
def config_change(changed, _new, removed) do
WandererAppWeb.Endpoint.config_change(changed, removed)
@@ -72,5 +79,6 @@ defmodule WandererApp.Application do
WandererApp.StartCorpWalletTrackerTask
]
defp maybe_start_corp_wallet_tracker(_), do: []
defp maybe_start_corp_wallet_tracker(_),
do: []
end

View File

@@ -71,7 +71,7 @@ defmodule WandererApp.Character.Tracker do
{:ok, %{eve_id: eve_id}} = WandererApp.Character.get_character(character_id)
case WandererApp.Esi.get_character_info(eve_id) do
{:ok, info} ->
{:ok, _info} ->
{:ok, character_state} = WandererApp.Character.get_character_state(character_id)
update = maybe_update_corporation(character_state, eve_id |> String.to_integer())

View File

@@ -71,7 +71,7 @@ defmodule WandererApp.Character.TransactionsTracker do
@impl true
def handle_info(:shutdown, %Impl{} = state) do
Logger.debug("Shutting down character transaction tracker: #{inspect(state.character_id)}")
Logger.debug(fn -> "Shutting down character transaction tracker: #{inspect(state.character_id)}" end)
{:stop, :normal, state}
end

View File

@@ -238,7 +238,7 @@ defmodule WandererApp.Character.TransactionsTracker.Impl do
Phoenix.PubSub.broadcast(
WandererApp.PubSub,
"corporation",
{:transactions, character.corporation_id, transactions}
{:transactions, character.corporation_id, transactions |> Enum.sort_by(& &1.date, :desc)}
)
end

View File

@@ -11,6 +11,8 @@ defmodule WandererApp.Env do
def invites, do: get_key(:invites, false)
def map_subscriptions_enabled?, do: get_key(:map_subscriptions_enabled, false)
def public_api_disabled?, do: get_key(:public_api_disabled, false)
def character_api_disabled?, do: get_key(:character_api_disabled, false)
def zkill_preload_disabled?, do: get_key(:zkill_preload_disabled, false)
def wallet_tracking_enabled?, do: get_key(:wallet_tracking_enabled, false)
def admins, do: get_key(:admins, [])
def admin_username, do: get_key(:admin_username)
@@ -19,6 +21,12 @@ defmodule WandererApp.Env do
def corp_eve_id, do: get_key(:corp_id, -1)
def subscription_settings, do: get_key(:subscription_settings)
@decorate cacheable(
cache: WandererApp.Cache,
key: "restrict_maps_creation"
)
def restrict_maps_creation?, do: get_key(:restrict_maps_creation, false)
@decorate cacheable(
cache: WandererApp.Cache,
key: "map-connection-auto-expire-hours"
@@ -39,4 +47,12 @@ defmodule WandererApp.Env do
do: get_key(:map_connection_eol_expire_timeout_mins)
def get_key(key, default \\ nil), do: Application.get_env(@app, key, default)
@doc """
A single map containing environment variables
made available to react
"""
def to_client_env do
%{detailedKillsDisabled: zkill_preload_disabled?()}
end
end

View File

@@ -24,6 +24,9 @@ defmodule WandererApp.Esi do
defdelegate find_routes(map_id, origin, hubs, routes_settings), to: WandererApp.Esi.ApiClient
defdelegate search(character_eve_id, opts \\ []), to: WandererApp.Esi.ApiClient
defdelegate get_killmail(killmail_id, killmail_hash, opts \\ []), to: WandererApp.Esi.ApiClient
defdelegate set_autopilot_waypoint(
add_to_beginning,
clear_other_waypoints,

Some files were not shown because too many files have changed in this diff Show More