mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-11-30 21:13:31 +00:00
Compare commits
215 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5ea4d6129 | ||
|
|
9d50bfedbd | ||
|
|
b03410083c | ||
|
|
a314b1e448 | ||
|
|
e8a51a85c4 | ||
|
|
d4074f966c | ||
|
|
1413b41824 | ||
|
|
379c1edec3 | ||
|
|
58b5bade9e | ||
|
|
71aee4cd3e | ||
|
|
10bab0cfa1 | ||
|
|
698350b0f7 | ||
|
|
a97cf25031 | ||
|
|
8302d088bd | ||
|
|
64788e73de | ||
|
|
114fd471e8 | ||
|
|
b24a3120d3 | ||
|
|
c5f93b3d0a | ||
|
|
79290e4721 | ||
|
|
984e126f23 | ||
|
|
cd1ad31aed | ||
|
|
1e3f6cf9e7 | ||
|
|
9c6ccd9a8a | ||
|
|
681ba21d39 | ||
|
|
aef62189ee | ||
|
|
09f70ac817 | ||
|
|
1eacb22143 | ||
|
|
8524bad377 | ||
|
|
9d899243d1 | ||
|
|
9acf20a639 | ||
|
|
71ef6b2e82 | ||
|
|
5e34d95dd2 | ||
|
|
25a809c064 | ||
|
|
f760498150 | ||
|
|
328301a375 | ||
|
|
f28e7ebbbb | ||
|
|
bfa84af71e | ||
|
|
42cd1ba976 | ||
|
|
88cba866fd | ||
|
|
af2876a84b | ||
|
|
c5b15bfa78 | ||
|
|
85f00a63c2 | ||
|
|
05f427bcd7 | ||
|
|
69f4c41534 | ||
|
|
30b9239a8b | ||
|
|
2061a83c59 | ||
|
|
24e723de07 | ||
|
|
27b5694885 | ||
|
|
4093f28cee | ||
|
|
08aaf2f2dd | ||
|
|
5a927e5ba5 | ||
|
|
10fafcf59f | ||
|
|
be87591801 | ||
|
|
086d4378d3 | ||
|
|
e982275905 | ||
|
|
77c02703e9 | ||
|
|
0ef27d4f95 | ||
|
|
5edc27744e | ||
|
|
02ff887fee | ||
|
|
3a30eeb59f | ||
|
|
79af8fb601 | ||
|
|
f9f00faa0e | ||
|
|
a3c41e84e4 | ||
|
|
7f21f33351 | ||
|
|
568f682cee | ||
|
|
901c4c8ca4 | ||
|
|
3dbba97f9c | ||
|
|
3475620267 | ||
|
|
8936a5e5d8 | ||
|
|
719e34f9bc | ||
|
|
df955ff8b0 | ||
|
|
4dc74022b4 | ||
|
|
c2b7d07208 | ||
|
|
21e76a6f05 | ||
|
|
89c0d6fad6 | ||
|
|
b74d15b1ee | ||
|
|
10313438bf | ||
|
|
a764217948 | ||
|
|
d81d6fb937 | ||
|
|
4c0f7ab7f9 | ||
|
|
1c48945a96 | ||
|
|
850901f62f | ||
|
|
4822854e30 | ||
|
|
f580538331 | ||
|
|
0d70c555e6 | ||
|
|
c5f6cf0080 | ||
|
|
6ff7b3bc9a | ||
|
|
346c2c65b0 | ||
|
|
a6445fd500 | ||
|
|
358e43e508 | ||
|
|
20c5ba6b63 | ||
|
|
661658a6e8 | ||
|
|
0a6f224ed3 | ||
|
|
e7bb29693f | ||
|
|
32dfd50461 | ||
|
|
bfec385dce | ||
|
|
04278f99d7 | ||
|
|
9d3db19dc1 | ||
|
|
3953e33f37 | ||
|
|
611fdd56d0 | ||
|
|
36a4fd0f35 | ||
|
|
488984a988 | ||
|
|
1b183d6e58 | ||
|
|
3dcb6d30b5 | ||
|
|
1d11788f89 | ||
|
|
d3822128ab | ||
|
|
6ac7836505 | ||
|
|
5796fccae4 | ||
|
|
22ab6e544c | ||
|
|
5e735ab8bd | ||
|
|
450139402d | ||
|
|
1e0d4f1fde | ||
|
|
7e9b1af3a3 | ||
|
|
30b90cd4be | ||
|
|
f28affa222 | ||
|
|
2ae7b187b8 | ||
|
|
ad037be1f4 | ||
|
|
9e31542c5f | ||
|
|
aae6ed0cb3 | ||
|
|
7ea2ecb8e9 | ||
|
|
852fc28896 | ||
|
|
fcdab79802 | ||
|
|
bb0e06589f | ||
|
|
d65b72c4dd | ||
|
|
24a3d5b3de | ||
|
|
2814b46941 | ||
|
|
6ffc25448d | ||
|
|
d9a82f7c9f | ||
|
|
9a8106947e | ||
|
|
e21ca79ea1 | ||
|
|
dd579caeac | ||
|
|
ddf8a4b9fb | ||
|
|
3c04f4194e | ||
|
|
1e0de841eb | ||
|
|
c9c88cf0a6 | ||
|
|
fd1e124166 | ||
|
|
aa2bee258a | ||
|
|
12d696dc07 | ||
|
|
b33772a1d6 | ||
|
|
b41f89d7de | ||
|
|
e8ca213b74 | ||
|
|
7620b8d493 | ||
|
|
23306fd9e3 | ||
|
|
a68ccdca50 | ||
|
|
771e432546 | ||
|
|
20bdbfb81a | ||
|
|
90729b436c | ||
|
|
8ea4e97bbf | ||
|
|
17e12e9263 | ||
|
|
4cb3d021f4 | ||
|
|
97cec2e127 | ||
|
|
9c4294659c | ||
|
|
5b8526db96 | ||
|
|
54cce9e9fb | ||
|
|
54d1691d19 | ||
|
|
4d4431868e | ||
|
|
cdae69d346 | ||
|
|
3e86baabd8 | ||
|
|
6ec92b19fb | ||
|
|
630caa8686 | ||
|
|
497c3b2165 | ||
|
|
8823d06893 | ||
|
|
72a2bf50df | ||
|
|
15c1ed2011 | ||
|
|
3fb19663cc | ||
|
|
00cdbbc89c | ||
|
|
07f3cec16d | ||
|
|
e893e25ff4 | ||
|
|
1ad9a1eb13 | ||
|
|
d9288596e8 | ||
|
|
a2a642d9ce | ||
|
|
4f00007108 | ||
|
|
816ee77b6f | ||
|
|
26d470ecda | ||
|
|
3babd9d95e | ||
|
|
802e81b1cd | ||
|
|
41f0834c51 | ||
|
|
880de0b047 | ||
|
|
bbe7fda4e0 | ||
|
|
4fd214e328 | ||
|
|
92a9274dce | ||
|
|
8765d83083 | ||
|
|
a298152bc8 | ||
|
|
2b7abe5774 | ||
|
|
3e9241892e | ||
|
|
a8dcdcf339 | ||
|
|
6ea79a7960 | ||
|
|
2af562e313 | ||
|
|
40672f6a47 | ||
|
|
6d66ae3f50 | ||
|
|
94c89e0325 | ||
|
|
3670ef40a3 | ||
|
|
16d464fba5 | ||
|
|
0b7e0b9cd0 | ||
|
|
dd5fd114d2 | ||
|
|
6e53879344 | ||
|
|
af2bfd4d59 | ||
|
|
5719469452 | ||
|
|
6e9d525890 | ||
|
|
e82805dd48 | ||
|
|
5edcd5572a | ||
|
|
3cd9b819f5 | ||
|
|
6914f75bb7 | ||
|
|
3adf3946b5 | ||
|
|
c036a157c8 | ||
|
|
acf35f8c51 | ||
|
|
9155515082 | ||
|
|
61235828ce | ||
|
|
927c07bfd5 | ||
|
|
d00caf1f4c | ||
|
|
a5e9d72bc5 | ||
|
|
8ac9047831 | ||
|
|
65509ace59 | ||
|
|
ea173f971a | ||
|
|
c45c97c5d8 |
41
.github/workflows/build.yml
vendored
41
.github/workflows/build.yml
vendored
@@ -18,49 +18,8 @@ permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy-test:
|
||||
name: 🚀 Deploy to test env (fly.io)
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.base_ref == 'main' || (github.ref == 'refs/heads/main' && github.event_name == 'push') }}
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
- uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
|
||||
- name: 👀 Read app name
|
||||
uses: SebRollen/toml-action@v1.0.0
|
||||
id: app_name
|
||||
with:
|
||||
file: "fly.toml"
|
||||
field: "app"
|
||||
|
||||
- name: 🚀 Deploy Test
|
||||
run: flyctl deploy --remote-only --wait-timeout=300 --ha=false
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
|
||||
manual-approval:
|
||||
name: Manual Approval
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-test
|
||||
if: success()
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- name: Await Manual Approval
|
||||
uses: trstringer/manual-approval@v1
|
||||
with:
|
||||
secret: ${{ github.TOKEN }}
|
||||
approvers: DmitryPopov
|
||||
minimum-approvals: 1
|
||||
issue-title: "Manual Approval Required for Release"
|
||||
issue-body: "Please approve or deny the deployment."
|
||||
|
||||
build:
|
||||
name: 🛠 Build
|
||||
needs: manual-approval
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ (github.ref == 'refs/heads/main') && github.event_name == 'push' }}
|
||||
permissions:
|
||||
|
||||
500
CHANGELOG.md
500
CHANGELOG.md
@@ -2,6 +2,506 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v1.68.6](https://github.com/wanderer-industries/wanderer/compare/v1.68.5...v1.68.6) (2025-06-10)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.68.5](https://github.com/wanderer-industries/wanderer/compare/v1.68.4...v1.68.5) (2025-06-10)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed updating map options
|
||||
|
||||
## [v1.68.4](https://github.com/wanderer-industries/wanderer/compare/v1.68.3...v1.68.4) (2025-06-10)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.68.3](https://github.com/wanderer-industries/wanderer/compare/v1.68.2...v1.68.3) (2025-06-09)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.68.2](https://github.com/wanderer-industries/wanderer/compare/v1.68.1...v1.68.2) (2025-06-09)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed character auth with wallet (on characters page)
|
||||
|
||||
## [v1.68.1](https://github.com/wanderer-industries/wanderer/compare/v1.68.0...v1.68.1) (2025-06-09)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed auth from welcome page if invites disabled
|
||||
|
||||
## [v1.68.0](https://github.com/wanderer-industries/wanderer/compare/v1.67.5...v1.68.0) (2025-06-09)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: Added invites store support
|
||||
|
||||
## [v1.67.5](https://github.com/wanderer-industries/wanderer/compare/v1.67.4...v1.67.5) (2025-06-08)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added back ARM docker image build
|
||||
|
||||
## [v1.67.4](https://github.com/wanderer-industries/wanderer/compare/v1.67.3...v1.67.4) (2025-06-08)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed issue with system splash updates
|
||||
|
||||
## [v1.67.3](https://github.com/wanderer-industries/wanderer/compare/v1.67.2...v1.67.3) (2025-06-08)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed issue with system splash updates
|
||||
|
||||
## [v1.67.2](https://github.com/wanderer-industries/wanderer/compare/v1.67.1...v1.67.2) (2025-06-08)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.67.1](https://github.com/wanderer-industries/wanderer/compare/v1.67.0...v1.67.1) (2025-06-08)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.67.0](https://github.com/wanderer-industries/wanderer/compare/v1.66.25...v1.67.0) (2025-06-08)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: Added support for WANDERER_CHARACTER_TRACKING_PAUSE_DISABLED env variable to pause inactive character trackers
|
||||
|
||||
## [v1.66.25](https://github.com/wanderer-industries/wanderer/compare/v1.66.24...v1.66.25) (2025-06-08)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Disabled kills fetching based on env settings
|
||||
|
||||
## [v1.66.24](https://github.com/wanderer-industries/wanderer/compare/v1.66.23...v1.66.24) (2025-06-08)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.66.23](https://github.com/wanderer-industries/wanderer/compare/v1.66.22...v1.66.23) (2025-06-08)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.66.22](https://github.com/wanderer-industries/wanderer/compare/v1.66.21...v1.66.22) (2025-06-07)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.66.21](https://github.com/wanderer-industries/wanderer/compare/v1.66.20...v1.66.21) (2025-06-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed kills fetching based on env settings
|
||||
|
||||
## [v1.66.20](https://github.com/wanderer-industries/wanderer/compare/v1.66.19...v1.66.20) (2025-06-07)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.66.19](https://github.com/wanderer-industries/wanderer/compare/v1.66.18...v1.66.19) (2025-06-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added check for offline characters timeouts
|
||||
|
||||
## [v1.66.18](https://github.com/wanderer-industries/wanderer/compare/v1.66.17...v1.66.18) (2025-06-07)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.66.17](https://github.com/wanderer-industries/wanderer/compare/v1.66.16...v1.66.17) (2025-06-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Increased tracking pause timeout for offline characters up to 10 hours
|
||||
|
||||
## [v1.66.16](https://github.com/wanderer-industries/wanderer/compare/v1.66.15...v1.66.16) (2025-06-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Increased tracking pause timeout for offline characters up to 10 hours
|
||||
|
||||
## [v1.66.15](https://github.com/wanderer-industries/wanderer/compare/v1.66.14...v1.66.15) (2025-06-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added back arm docker image build
|
||||
|
||||
## [v1.66.14](https://github.com/wanderer-industries/wanderer/compare/v1.66.13...v1.66.14) (2025-06-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: fixed online updates
|
||||
|
||||
## [v1.66.13](https://github.com/wanderer-industries/wanderer/compare/v1.66.12...v1.66.13) (2025-06-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: fixed location tracking issues
|
||||
|
||||
## [v1.66.12](https://github.com/wanderer-industries/wanderer/compare/v1.66.11...v1.66.12) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.66.11](https://github.com/wanderer-industries/wanderer/compare/v1.66.10...v1.66.11) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: fixed refresh character tokens
|
||||
|
||||
## [v1.66.10](https://github.com/wanderer-industries/wanderer/compare/v1.66.9...v1.66.10) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.66.9](https://github.com/wanderer-industries/wanderer/compare/v1.66.8...v1.66.9) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.66.8](https://github.com/wanderer-industries/wanderer/compare/v1.66.7...v1.66.8) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* fixed disable detailed kills env check
|
||||
|
||||
## [v1.66.7](https://github.com/wanderer-industries/wanderer/compare/v1.66.6...v1.66.7) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* fixed disable detailed kills env check
|
||||
|
||||
## [v1.66.6](https://github.com/wanderer-industries/wanderer/compare/v1.66.5...v1.66.6) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* respect error limits for ESI APIs
|
||||
|
||||
## [v1.66.5](https://github.com/wanderer-industries/wanderer/compare/v1.66.4...v1.66.5) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* respect error limits for ESI APIs
|
||||
|
||||
## [v1.66.4](https://github.com/wanderer-industries/wanderer/compare/v1.66.3...v1.66.4) (2025-06-06)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* respect error limits for ESI APIs
|
||||
|
||||
## [v1.66.3](https://github.com/wanderer-industries/wanderer/compare/v1.66.2...v1.66.3) (2025-06-05)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.66.2](https://github.com/wanderer-industries/wanderer/compare/v1.66.1...v1.66.2) (2025-06-05)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.66.1](https://github.com/wanderer-industries/wanderer/compare/v1.66.0...v1.66.1) (2025-06-05)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* remove bugs with signature deletion
|
||||
|
||||
## [v1.66.0](https://github.com/wanderer-industries/wanderer/compare/v1.65.24...v1.66.0) (2025-06-05)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* show deleted signatures during undo timer
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* remove callbacks from effect dependencies
|
||||
|
||||
## [v1.65.24](https://github.com/wanderer-industries/wanderer/compare/v1.65.23...v1.65.24) (2025-06-04)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed errors duration check to reduce requests amount to ESI
|
||||
|
||||
## [v1.65.23](https://github.com/wanderer-industries/wanderer/compare/v1.65.22...v1.65.23) (2025-06-04)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added back arm docker image build
|
||||
|
||||
## [v1.65.22](https://github.com/wanderer-industries/wanderer/compare/v1.65.21...v1.65.22) (2025-06-04)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fix character tracking issues
|
||||
|
||||
## [v1.65.21](https://github.com/wanderer-industries/wanderer/compare/v1.65.20...v1.65.21) (2025-06-01)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fix connection pool errors
|
||||
|
||||
## [v1.65.20](https://github.com/wanderer-industries/wanderer/compare/v1.65.19...v1.65.20) (2025-06-01)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: fix waypoint set timeout errors
|
||||
|
||||
## [v1.65.19](https://github.com/wanderer-industries/wanderer/compare/v1.65.18...v1.65.19) (2025-06-01)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: fix updating character online
|
||||
|
||||
## [v1.65.18](https://github.com/wanderer-industries/wanderer/compare/v1.65.17...v1.65.18) (2025-05-30)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: fix updating systems and connections
|
||||
|
||||
## [v1.65.17](https://github.com/wanderer-industries/wanderer/compare/v1.65.16...v1.65.17) (2025-05-29)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: fix updating systems and connections
|
||||
|
||||
* Comments: fix error loading comments
|
||||
|
||||
## [v1.65.16](https://github.com/wanderer-industries/wanderer/compare/v1.65.15...v1.65.16) (2025-05-29)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Allow lock systems for members
|
||||
|
||||
## [v1.65.15](https://github.com/wanderer-industries/wanderer/compare/v1.65.14...v1.65.15) (2025-05-28)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.65.14](https://github.com/wanderer-industries/wanderer/compare/v1.65.13...v1.65.14) (2025-05-28)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.65.13](https://github.com/wanderer-industries/wanderer/compare/v1.65.12...v1.65.13) (2025-05-28)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signatures: small wh size is now passed from signature to connection
|
||||
|
||||
## [v1.65.12](https://github.com/wanderer-industries/wanderer/compare/v1.65.11...v1.65.12) (2025-05-27)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed showing character ship
|
||||
|
||||
## [v1.65.11](https://github.com/wanderer-industries/wanderer/compare/v1.65.10...v1.65.11) (2025-05-27)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed showing character ship
|
||||
|
||||
## [v1.65.10](https://github.com/wanderer-industries/wanderer/compare/v1.65.9...v1.65.10) (2025-05-27)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed sorting for characters in Local
|
||||
|
||||
* Map: Rally: fixed conflict style of status and rally
|
||||
|
||||
* Core: Fixed character token refresh
|
||||
|
||||
* Map: Add Rally point. Change placement of settings in Map User Settings. Add ability to placement minimap.
|
||||
|
||||
* Map: Routes - hide user routes btn from context if subscriptions is not active or widget is closed. Also now hidden widget will show again in place where it was on moment of hide (except cases when screen size has changed.
|
||||
|
||||
* Map: PINGS - Rally point first prototype
|
||||
|
||||
## [v1.65.9](https://github.com/wanderer-industries/wanderer/compare/v1.65.8...v1.65.9) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.65.8](https://github.com/wanderer-industries/wanderer/compare/v1.65.7...v1.65.8) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.65.7](https://github.com/wanderer-industries/wanderer/compare/v1.65.6...v1.65.7) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map character tracking issues
|
||||
|
||||
## [v1.65.6](https://github.com/wanderer-industries/wanderer/compare/v1.65.5...v1.65.6) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map character tracking issues
|
||||
|
||||
## [v1.65.5](https://github.com/wanderer-industries/wanderer/compare/v1.65.4...v1.65.5) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map character tracking issues
|
||||
|
||||
* Signature: Update restored signature character
|
||||
|
||||
## [v1.65.4](https://github.com/wanderer-industries/wanderer/compare/v1.65.3...v1.65.4) (2025-05-24)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signature: Force signature update even if there are no any changes
|
||||
|
||||
## [v1.65.3](https://github.com/wanderer-industries/wanderer/compare/v1.65.2...v1.65.3) (2025-05-23)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signature: Fixed signature clenup
|
||||
|
||||
## [v1.65.2](https://github.com/wanderer-industries/wanderer/compare/v1.65.1...v1.65.2) (2025-05-23)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signature: Fixed signature updates
|
||||
|
||||
## [v1.65.1](https://github.com/wanderer-industries/wanderer/compare/v1.65.0...v1.65.1) (2025-05-22)
|
||||
|
||||
|
||||
|
||||
@@ -17,5 +17,6 @@ module.exports = {
|
||||
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
"linebreak-style": "off",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"endOfLine": "lf"
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
|
||||
@@ -198,3 +198,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.p-autocomplete .p-autocomplete-multiple-container:not(.p-disabled).p-focus {
|
||||
box-shadow: 0 0 0 1px #335c7e;
|
||||
border-color: #335c7e;
|
||||
}
|
||||
|
||||
.p-inputtext:enabled:focus {
|
||||
box-shadow: 0 0 0 1px #335c7e;
|
||||
border-color: #335c7e;
|
||||
}
|
||||
|
||||
.p-inputtext:enabled:hover {
|
||||
border-color: #335c7e;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { useCallback } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { useAutoAnimate } from '@formkit/auto-animate/react';
|
||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { CharacterTypeRaw } from '@/hooks/Mapper/types';
|
||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import classes from './Characters.module.scss';
|
||||
import { isDocked } from '@/hooks/Mapper/helpers/isDocked.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CharacterTypeRaw } from '@/hooks/Mapper/types';
|
||||
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { useAutoAnimate } from '@formkit/auto-animate/react';
|
||||
import clsx from 'clsx';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import classes from './Characters.module.scss';
|
||||
interface CharactersProps {
|
||||
data: CharacterTypeRaw[];
|
||||
}
|
||||
@@ -17,13 +16,22 @@ export const Characters = ({ data }: CharactersProps) => {
|
||||
const [parent] = useAutoAnimate();
|
||||
|
||||
const {
|
||||
outCommand,
|
||||
data: { mainCharacterEveId, followingCharacterEveId },
|
||||
} = useMapRootState();
|
||||
|
||||
const handleSelect = useCallback((character: CharacterTypeRaw) => {
|
||||
const handleSelect = useCallback(async (character: CharacterTypeRaw) => {
|
||||
if (!character) {
|
||||
return;
|
||||
}
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.startTracking,
|
||||
data: { character_eve_id: character.eve_id },
|
||||
});
|
||||
emitMapEvent({
|
||||
name: Commands.centerSystem,
|
||||
data: character?.location?.solar_system_id?.toString(),
|
||||
data: character.location?.solar_system_id?.toString(),
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -37,14 +45,26 @@ export const Characters = ({ data }: CharactersProps) => {
|
||||
className={clsx(
|
||||
'overflow-hidden relative',
|
||||
'flex w-[35px] h-[35px] rounded-[4px] border-[1px] border-solid bg-transparent cursor-pointer',
|
||||
'transition-colors duration-250',
|
||||
'transition-colors duration-250 hover:bg-stone-300/90',
|
||||
{
|
||||
['border-stone-800/90']: !character.online,
|
||||
['border-lime-600/70']: character.online,
|
||||
},
|
||||
)}
|
||||
title={character.name}
|
||||
title={character.tracking_paused ? `${character.name} - Tracking Paused (click to resume)` : character.name}
|
||||
>
|
||||
{character.tracking_paused && (
|
||||
<>
|
||||
<span
|
||||
className={clsx(
|
||||
'absolute flex flex-col p-[2px] top-[0px] left-[0px] w-[35px] h-[35px]',
|
||||
'text-yellow-500 text-[9px] z-10 bg-gray-800/40',
|
||||
'pi',
|
||||
PrimeIcons.PAUSE,
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{mainCharacterEveId === character.eve_id && (
|
||||
<span
|
||||
className={clsx(
|
||||
@@ -55,6 +75,7 @@ export const Characters = ({ data }: CharactersProps) => {
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{followingCharacterEveId === character.eve_id && (
|
||||
<span
|
||||
className={clsx(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { RefObject } from 'react';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { PingType, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { useContextMenuSystemItems } from '@/hooks/Mapper/components/contexts/ContextMenuSystem/useContextMenuSystemItems.tsx';
|
||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface ContextMenuSystemProps {
|
||||
onSystemStatus(val: number): void;
|
||||
onSystemLabels(val: string): void;
|
||||
onCustomLabelDialog(): void;
|
||||
onTogglePing(type: PingType, solar_system_id: string, hasPing: boolean): void;
|
||||
onWaypointSet: WaypointSetContextHandler;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './useTagMenu';
|
||||
export * from './useStatusMenu';
|
||||
export * from './useLabelsMenu';
|
||||
export * from './useUserRoute';
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { MapUserAddIcon, MapUserDeleteIcon } from '@/hooks/Mapper/icons';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { WidgetsIds } from '@/hooks/Mapper/components/mapInterface/constants.tsx';
|
||||
|
||||
interface UseUserRouteProps {
|
||||
systemId: string | undefined;
|
||||
userHubs: string[];
|
||||
onUserHubToggle(): void;
|
||||
}
|
||||
|
||||
export const useUserRoute = ({ userHubs, systemId, onUserHubToggle }: UseUserRouteProps) => {
|
||||
const {
|
||||
data: { isSubscriptionActive },
|
||||
windowsSettings,
|
||||
} = useMapRootState();
|
||||
|
||||
const ref = useRef({ userHubs, systemId, onUserHubToggle, isSubscriptionActive, windowsSettings });
|
||||
ref.current = { userHubs, systemId, onUserHubToggle, isSubscriptionActive, windowsSettings };
|
||||
|
||||
return useCallback(() => {
|
||||
const { userHubs, systemId, onUserHubToggle, isSubscriptionActive, windowsSettings } = ref.current;
|
||||
|
||||
const isVisibleUserRoutes = windowsSettings.visible.some(x => x === WidgetsIds.userRoutes);
|
||||
|
||||
if (!isSubscriptionActive || !isVisibleUserRoutes || !systemId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: !userHubs.includes(systemId) ? 'Add User Route' : 'Remove User Route',
|
||||
icon: !userHubs.includes(systemId) ? (
|
||||
<MapUserAddIcon className="mr-1 relative left-[-2px]" />
|
||||
) : (
|
||||
<MapUserDeleteIcon className="mr-1 relative left-[-2px]" />
|
||||
),
|
||||
command: onUserHubToggle,
|
||||
},
|
||||
];
|
||||
}, [windowsSettings]);
|
||||
};
|
||||
@@ -5,6 +5,7 @@ import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
// import { PingType } from '@/hooks/Mapper/types/ping.ts';
|
||||
|
||||
interface UseContextMenuSystemHandlersProps {
|
||||
hubs: string[];
|
||||
@@ -93,6 +94,22 @@ export const useContextMenuSystemHandlers = ({
|
||||
setSystem(undefined);
|
||||
}, []);
|
||||
|
||||
// const onTogglePingRally = useCallback(() => {
|
||||
// const { userHubs, system, outCommand } = ref.current;
|
||||
// if (!system) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// outCommand({
|
||||
// type: OutCommand.openPing,
|
||||
// data: {
|
||||
// solar_system_id: system,
|
||||
// type: PingType.Rally,
|
||||
// },
|
||||
// });
|
||||
// setSystem(undefined);
|
||||
// }, []);
|
||||
|
||||
const onSystemTag = useCallback((tag?: string) => {
|
||||
const { system, outCommand } = ref.current;
|
||||
if (!system) {
|
||||
@@ -198,6 +215,7 @@ export const useContextMenuSystemHandlers = ({
|
||||
onLockToggle,
|
||||
onHubToggle,
|
||||
onUserHubToggle,
|
||||
// onTogglePingRally,
|
||||
onSystemTag,
|
||||
onSystemTemporaryName,
|
||||
onSystemStatus,
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { useLabelsMenu, useStatusMenu, useTagMenu } from '@/hooks/Mapper/components/contexts/ContextMenuSystem/hooks';
|
||||
import {
|
||||
useLabelsMenu,
|
||||
useStatusMenu,
|
||||
useTagMenu,
|
||||
useUserRoute,
|
||||
} from '@/hooks/Mapper/components/contexts/ContextMenuSystem/hooks';
|
||||
import { useMemo } from 'react';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import classes from './ContextMenuSystem.module.scss';
|
||||
@@ -10,13 +15,19 @@ import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api
|
||||
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||
import { MapAddIcon, MapDeleteIcon, MapUserAddIcon, MapUserDeleteIcon } from '@/hooks/Mapper/icons';
|
||||
import { MapAddIcon, MapDeleteIcon } from '@/hooks/Mapper/icons';
|
||||
import { PingType } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import clsx from 'clsx';
|
||||
import { MenuItem } from 'primereact/menuitem';
|
||||
import { MenuItemWithInfo, WdMenuItem } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
export const useContextMenuSystemItems = ({
|
||||
onDeleteSystem,
|
||||
onLockToggle,
|
||||
onHubToggle,
|
||||
onUserHubToggle,
|
||||
onTogglePing,
|
||||
onSystemTag,
|
||||
onSystemStatus,
|
||||
onSystemLabels,
|
||||
@@ -33,11 +44,33 @@ export const useContextMenuSystemItems = ({
|
||||
const getLabels = useLabelsMenu(systems, systemId, onSystemLabels, onCustomLabelDialog);
|
||||
const getWaypointMenu = useWaypointMenu(onWaypointSet);
|
||||
const canLockSystem = useMapCheckPermissions([UserPermission.LOCK_SYSTEM]);
|
||||
const canManageSystem = useMapCheckPermissions([UserPermission.UPDATE_SYSTEM]);
|
||||
const canDeleteSystem = useMapCheckPermissions([UserPermission.DELETE_SYSTEM]);
|
||||
const getUserRoutes = useUserRoute({ userHubs, systemId, onUserHubToggle });
|
||||
|
||||
return useMemo(() => {
|
||||
const {
|
||||
data: { pings, isSubscriptionActive },
|
||||
} = useMapRootState();
|
||||
|
||||
const ping = useMemo(() => (pings.length === 1 ? pings[0] : undefined), [pings]);
|
||||
const isShowPingBtn = useMemo(() => {
|
||||
if (!isSubscriptionActive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pings.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return pings[0].solar_system_id === systemId;
|
||||
}, [isSubscriptionActive, pings, systemId]);
|
||||
|
||||
return useMemo((): MenuItem[] => {
|
||||
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
||||
const systemStaticInfo = getSystemStaticInfo(systemId)!;
|
||||
|
||||
const hasPing = ping?.solar_system_id === systemId;
|
||||
|
||||
if (!system || !systemId) {
|
||||
return [];
|
||||
}
|
||||
@@ -72,55 +105,96 @@ export const useContextMenuSystemItems = ({
|
||||
),
|
||||
command: onHubToggle,
|
||||
},
|
||||
...getUserRoutes(),
|
||||
|
||||
{ separator: true },
|
||||
{
|
||||
label: !userHubs.includes(systemId) ? 'Add User Route' : 'Remove User Route',
|
||||
icon: !userHubs.includes(systemId) ? (
|
||||
<MapUserAddIcon className="mr-1 relative left-[-2px]" />
|
||||
) : (
|
||||
<MapUserDeleteIcon className="mr-1 relative left-[-2px]" />
|
||||
),
|
||||
command: onUserHubToggle,
|
||||
command: () => onTogglePing(PingType.Rally, systemId, hasPing),
|
||||
disabled: !isShowPingBtn,
|
||||
template: () => {
|
||||
const iconClasses = clsx({
|
||||
'pi text-cyan-400 hero-signal': !hasPing,
|
||||
'pi text-red-400 hero-signal-slash': hasPing,
|
||||
});
|
||||
|
||||
if (isShowPingBtn) {
|
||||
return <WdMenuItem icon={iconClasses}>{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}</WdMenuItem>;
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItemWithInfo
|
||||
infoTitle="Locked. Ping can be set only for one system."
|
||||
infoClass="pi-lock text-stone-500 mr-[12px]"
|
||||
>
|
||||
<WdMenuItem disabled icon={iconClasses}>
|
||||
{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}
|
||||
</WdMenuItem>
|
||||
</MenuItemWithInfo>
|
||||
);
|
||||
},
|
||||
},
|
||||
...(system.locked
|
||||
? canLockSystem
|
||||
? [
|
||||
{
|
||||
label: 'Unlock',
|
||||
icon: PrimeIcons.LOCK_OPEN,
|
||||
command: onLockToggle,
|
||||
},
|
||||
]
|
||||
: []
|
||||
: [
|
||||
...(canLockSystem
|
||||
? [
|
||||
{
|
||||
label: 'Lock',
|
||||
icon: PrimeIcons.LOCK,
|
||||
command: onLockToggle,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(system.locked && canLockSystem
|
||||
? [
|
||||
{
|
||||
label: 'Unlock',
|
||||
icon: PrimeIcons.LOCK_OPEN,
|
||||
command: onLockToggle,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(!system.locked && canManageSystem
|
||||
? [
|
||||
{
|
||||
label: 'Lock',
|
||||
icon: PrimeIcons.LOCK,
|
||||
command: onLockToggle,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
...(canDeleteSystem && !system.locked
|
||||
? [
|
||||
{ separator: true },
|
||||
{
|
||||
label: 'Delete',
|
||||
icon: PrimeIcons.TRASH,
|
||||
command: onDeleteSystem,
|
||||
disabled: hasPing,
|
||||
template: () => {
|
||||
if (!hasPing) {
|
||||
return <WdMenuItem icon="text-red-400 pi pi-trash">Delete</WdMenuItem>;
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItemWithInfo
|
||||
infoTitle="Locked. System can not be deleted until ping set."
|
||||
infoClass="pi-lock text-stone-500 mr-[12px]"
|
||||
>
|
||||
<WdMenuItem disabled icon="text-red-400 pi pi-trash">
|
||||
Delete
|
||||
</WdMenuItem>
|
||||
</MenuItemWithInfo>
|
||||
);
|
||||
},
|
||||
},
|
||||
]),
|
||||
]
|
||||
: []),
|
||||
];
|
||||
}, [
|
||||
canLockSystem,
|
||||
systems,
|
||||
systemId,
|
||||
systems,
|
||||
getTags,
|
||||
getStatus,
|
||||
getLabels,
|
||||
getWaypointMenu,
|
||||
getUserRoutes,
|
||||
hubs,
|
||||
onHubToggle,
|
||||
onOpenSettings,
|
||||
canLockSystem,
|
||||
onLockToggle,
|
||||
canDeleteSystem,
|
||||
onDeleteSystem,
|
||||
onOpenSettings,
|
||||
onTogglePing,
|
||||
ping,
|
||||
isShowPingBtn,
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { LayoutEventBlocker, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { LayoutEventBlocker, TooltipPosition, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons';
|
||||
|
||||
import classes from './FastSystemActions.module.scss';
|
||||
@@ -59,9 +59,21 @@ export const FastSystemActions = ({
|
||||
return (
|
||||
<LayoutEventBlocker className={clsx('flex px-2 gap-2 justify-between items-center h-full')}>
|
||||
<div className={clsx('flex gap-2 items-center h-full', classes.Links)}>
|
||||
<WdImgButton tooltip={{ content: 'Open zkillboard' }} source={ZKB_ICON} onClick={handleOpenZKB} />
|
||||
<WdImgButton tooltip={{ content: 'Open Anoikis' }} source={ANOIK_ICON} onClick={handleOpenAnoikis} />
|
||||
<WdImgButton tooltip={{ content: 'Open Dotlan' }} source={DOTLAN_ICON} onClick={handleOpenDotlan} />
|
||||
<WdImgButton
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Open zkillboard' }}
|
||||
source={ZKB_ICON}
|
||||
onClick={handleOpenZKB}
|
||||
/>
|
||||
<WdImgButton
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Open Anoikis' }}
|
||||
source={ANOIK_ICON}
|
||||
onClick={handleOpenAnoikis}
|
||||
/>
|
||||
<WdImgButton
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Open Dotlan' }}
|
||||
source={DOTLAN_ICON}
|
||||
onClick={handleOpenDotlan}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center pl-1">
|
||||
@@ -69,14 +81,14 @@ export const FastSystemActions = ({
|
||||
textSize={WdImageSize.off}
|
||||
className={PrimeIcons.COPY}
|
||||
onClick={copySystemNameToClipboard}
|
||||
tooltip={{ content: 'Copy system name' }}
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Copy system name' }}
|
||||
/>
|
||||
{showEdit && (
|
||||
<WdImgButton
|
||||
textSize={WdImageSize.off}
|
||||
className="pi pi-pen-to-square text-base"
|
||||
onClick={onOpenSettings}
|
||||
tooltip={{ content: 'Edit system name and description' }}
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Edit system name and description' }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -28,11 +28,12 @@ import {
|
||||
import { getBehaviorForTheme } from './helpers/getThemeBehavior';
|
||||
import { OnMapAddSystemCallback, OnMapSelectionChange } from './map.types';
|
||||
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
|
||||
import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { PingData, SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import clsx from 'clsx';
|
||||
import { useBackgroundVars } from './hooks/useBackgroundVars';
|
||||
import type { PanelPosition } from '@reactflow/core';
|
||||
|
||||
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
|
||||
|
||||
@@ -95,6 +96,8 @@ interface MapCompProps {
|
||||
isShowBackgroundPattern?: boolean;
|
||||
isSoftBackground?: boolean;
|
||||
theme?: string;
|
||||
pings: PingData[];
|
||||
minimapPlacement?: PanelPosition;
|
||||
}
|
||||
|
||||
const MapComp = ({
|
||||
@@ -112,6 +115,8 @@ const MapComp = ({
|
||||
isSoftBackground,
|
||||
theme,
|
||||
onAddSystem,
|
||||
pings,
|
||||
minimapPlacement = 'bottom-right',
|
||||
}: MapCompProps) => {
|
||||
const { getNodes } = useReactFlow();
|
||||
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
|
||||
@@ -206,8 +211,9 @@ const MapComp = ({
|
||||
...x,
|
||||
showKSpaceBG: showKSpaceBG,
|
||||
isThickConnections: isThickConnections,
|
||||
pings,
|
||||
}));
|
||||
}, [showKSpaceBG, isThickConnections, update]);
|
||||
}, [showKSpaceBG, isThickConnections, pings, update]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -270,7 +276,9 @@ const MapComp = ({
|
||||
// onlyRenderVisibleElements
|
||||
selectionMode={SelectionMode.Partial}
|
||||
>
|
||||
{isShowMinimap && <MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} />}
|
||||
{isShowMinimap && (
|
||||
<MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} position={minimapPlacement} />
|
||||
)}
|
||||
{isShowBackgroundPattern && <Background variant={variant} gap={gap} size={size} color={color} />}
|
||||
</ReactFlow>
|
||||
{/* <button className="z-auto btn btn-primary absolute top-20 right-20" onClick={handleGetPassages}>
|
||||
|
||||
@@ -40,6 +40,8 @@ const INITIAL_DATA: MapData = {
|
||||
isSubscriptionActive: false,
|
||||
mainCharacterEveId: null,
|
||||
followingCharacterEveId: null,
|
||||
userHubs: [],
|
||||
pings: [],
|
||||
};
|
||||
|
||||
export interface MapContextProps {
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
||||
|
||||
$pastel-blue: #5a7d9a;
|
||||
$pastel-pink: #d291bc;
|
||||
$pastel-pink: rgb(30, 161, 255);
|
||||
$dark-bg: #2d2d2d;
|
||||
$text-color: #ffffff;
|
||||
$tooltip-bg: #202020;
|
||||
|
||||
$neon-color-1: rgb(27, 132, 236);
|
||||
$neon-color-3: rgba(27, 132, 236, 0.40);
|
||||
|
||||
@keyframes move-stripes {
|
||||
from {
|
||||
background-position: 0 0;
|
||||
}
|
||||
to {
|
||||
background-position: 30px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.RootCustomNode {
|
||||
display: flex;
|
||||
width: 130px;
|
||||
@@ -32,7 +44,7 @@ $tooltip-bg: #202020;
|
||||
&.Amarria,
|
||||
&.Gallente,
|
||||
&.Caldaria {
|
||||
&::before {
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -48,7 +60,7 @@ $tooltip-bg: #202020;
|
||||
}
|
||||
|
||||
&.Mataria {
|
||||
&::before {
|
||||
&::after {
|
||||
background-image: url('/images/mataria-180.png');
|
||||
opacity: 0.6;
|
||||
background-position-x: 1px;
|
||||
@@ -57,7 +69,7 @@ $tooltip-bg: #202020;
|
||||
}
|
||||
|
||||
&.Caldaria {
|
||||
&::before {
|
||||
&::after {
|
||||
background-image: url('/images/caldaria-180.png');
|
||||
opacity: 0.6;
|
||||
background-position-x: 1px;
|
||||
@@ -66,7 +78,7 @@ $tooltip-bg: #202020;
|
||||
}
|
||||
|
||||
&.Amarria {
|
||||
&::before {
|
||||
&::after {
|
||||
opacity: 0.45;
|
||||
background-image: url('/images/amarr-180.png');
|
||||
background-position-x: 0;
|
||||
@@ -75,7 +87,7 @@ $tooltip-bg: #202020;
|
||||
}
|
||||
|
||||
&.Gallente {
|
||||
&::before {
|
||||
&::after {
|
||||
opacity: 0.5;
|
||||
background-image: url('/images/gallente-180.png');
|
||||
background-position-x: 1px;
|
||||
@@ -88,6 +100,29 @@ $tooltip-bg: #202020;
|
||||
box-shadow: 0 0 10px #9a1af1c2;
|
||||
}
|
||||
|
||||
&.rally {
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
|
||||
border-color: $neon-color-1;
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
$neon-color-3 0px,
|
||||
$neon-color-3 8px,
|
||||
transparent 8px,
|
||||
transparent 21px
|
||||
);
|
||||
background-size: 30px 30px;
|
||||
animation: move-stripes 3s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
&.eve-system-status-home {
|
||||
border: 1px solid var(--eve-solar-system-status-color-home-dark30);
|
||||
background-image: linear-gradient(45deg, var(--eve-solar-system-status-color-background), transparent);
|
||||
|
||||
@@ -71,8 +71,11 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
|
||||
className={clsx(
|
||||
classes.RootCustomNode,
|
||||
nodeVars.regionClass && classes[nodeVars.regionClass],
|
||||
nodeVars.status !== undefined ? classes[STATUS_CLASSES[nodeVars.status]] : '',
|
||||
{ [classes.selected]: nodeVars.selected },
|
||||
nodeVars.status !== undefined && classes[STATUS_CLASSES[nodeVars.status]],
|
||||
{
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.rally]: nodeVars.isRally,
|
||||
},
|
||||
)}
|
||||
onMouseDownCapture={e => nodeVars.dbClick(e)}
|
||||
>
|
||||
|
||||
@@ -71,7 +71,10 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
|
||||
classes.RootCustomNode,
|
||||
nodeVars.regionClass && classes[nodeVars.regionClass],
|
||||
nodeVars.status !== undefined ? classes[STATUS_CLASSES[nodeVars.status]] : '',
|
||||
{ [classes.selected]: nodeVars.selected },
|
||||
{
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.rally]: nodeVars.isRally,
|
||||
},
|
||||
)}
|
||||
onMouseDownCapture={e => nodeVars.dbClick(e)}
|
||||
>
|
||||
@@ -116,23 +119,13 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
|
||||
|
||||
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
|
||||
{nodeVars.customName && (
|
||||
<div
|
||||
className={clsx(
|
||||
classes.CustomName,
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
|
||||
)}
|
||||
>
|
||||
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||
{nodeVars.customName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!nodeVars.isWormhole && !nodeVars.customName && (
|
||||
<div
|
||||
className={clsx(
|
||||
classes.RegionName,
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
|
||||
)}
|
||||
>
|
||||
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||
{nodeVars.regionName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import {
|
||||
CommandCharacterAdded,
|
||||
CommandCharacterRemoved,
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
CommandCharacterUpdated,
|
||||
CommandPresentCharacters,
|
||||
} from '@/hooks/Mapper/types';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
export const useCommandsCharacters = () => {
|
||||
const { update } = useMapState();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useReactFlow } from 'reactflow';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandInit } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { convertConnection2Edge, convertSystem2Node } from '../../helpers';
|
||||
import { MapData, useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
import { CommandInit } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useReactFlow } from 'reactflow';
|
||||
import { convertConnection2Edge, convertSystem2Node } from '../../helpers';
|
||||
|
||||
export const useMapInit = () => {
|
||||
const rf = useReactFlow();
|
||||
|
||||
@@ -115,35 +115,18 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
||||
}, 500);
|
||||
break;
|
||||
|
||||
case Commands.pingAdded:
|
||||
case Commands.pingCancelled:
|
||||
case Commands.routes:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.signaturesUpdated:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.linkSignatureToSystem:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.detailedKillsUpdated:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.characterActivityData:
|
||||
break;
|
||||
|
||||
case Commands.trackingCharactersData:
|
||||
break;
|
||||
|
||||
case Commands.updateActivity:
|
||||
break;
|
||||
|
||||
case Commands.updateTracking:
|
||||
break;
|
||||
|
||||
case Commands.userSettingsUpdated:
|
||||
// do nothing
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -9,7 +9,7 @@ import { REGIONS_MAP, Spaces } from '@/hooks/Mapper/constants';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace';
|
||||
import { getSystemClassStyles } from '@/hooks/Mapper/components/map/helpers';
|
||||
import { sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||
import { CharacterTypeRaw, OutCommand, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { CharacterTypeRaw, OutCommand, PingType, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { useUnsplashedSignatures } from './useUnsplashedSignatures';
|
||||
import { useSystemName } from './useSystemName';
|
||||
import { LabelInfo, useLabelsInfo } from './useLabelsInfo';
|
||||
@@ -21,6 +21,45 @@ function getActivityType(count: number): string {
|
||||
return 'activityDanger';
|
||||
}
|
||||
|
||||
export interface SolarSystemNodeVars {
|
||||
id: string;
|
||||
selected: boolean;
|
||||
visible: boolean;
|
||||
isWormhole: boolean;
|
||||
classTitleColor: string | null;
|
||||
killsCount: number | null;
|
||||
killsActivityType: string | null;
|
||||
hasUserCharacters: boolean;
|
||||
showHandlers: boolean;
|
||||
regionClass: string | null;
|
||||
systemName: string;
|
||||
customName?: string | null;
|
||||
labelCustom: string | null;
|
||||
isShattered: boolean;
|
||||
tag?: string | null;
|
||||
status?: number;
|
||||
labelsInfo: LabelInfo[];
|
||||
dbClick: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
sortedStatics: Array<string | number>;
|
||||
effectName: string | null;
|
||||
regionName: string | null;
|
||||
solarSystemId: string;
|
||||
solarSystemName: string | null;
|
||||
locked: boolean;
|
||||
hubs: string[];
|
||||
name: string | null;
|
||||
isConnecting: boolean;
|
||||
hoverNodeId: string | null;
|
||||
charactersInSystem: Array<CharacterTypeRaw>;
|
||||
userCharacters: string[];
|
||||
unsplashedLeft: Array<SystemSignature>;
|
||||
unsplashedRight: Array<SystemSignature>;
|
||||
isThickConnections: boolean;
|
||||
isRally: boolean;
|
||||
classTitle: string | null;
|
||||
temporaryName?: string | null;
|
||||
}
|
||||
|
||||
const SpaceToClass: Record<string, string> = {
|
||||
[Spaces.Caldari]: 'Caldaria',
|
||||
[Spaces.Matar]: 'Mataria',
|
||||
@@ -41,7 +80,7 @@ export function useLocalCounter(nodeVars: SolarSystemNodeVars) {
|
||||
return { localCounterCharacters };
|
||||
}
|
||||
|
||||
export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarSystemNodeVars {
|
||||
export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarSystemNodeVars => {
|
||||
const { id, data, selected } = props;
|
||||
const {
|
||||
id: solar_system_id,
|
||||
@@ -92,6 +131,7 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
visibleNodes,
|
||||
showKSpaceBG,
|
||||
isThickConnections,
|
||||
pings,
|
||||
},
|
||||
outCommand,
|
||||
} = useMapState();
|
||||
@@ -154,6 +194,11 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
|
||||
const hubsAsStrings = useMemo(() => hubs.map(item => item.toString()), [hubs]);
|
||||
|
||||
const isRally = useMemo(
|
||||
() => pings.find(x => x.solar_system_id === solar_system_id && x.type === PingType.Rally),
|
||||
[pings, solar_system_id],
|
||||
);
|
||||
|
||||
const nodeVars: SolarSystemNodeVars = {
|
||||
id,
|
||||
selected,
|
||||
@@ -190,45 +235,8 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
temporaryName: computedTemporaryName,
|
||||
regionName: region_name,
|
||||
solarSystemName: solar_system_name,
|
||||
isRally,
|
||||
};
|
||||
|
||||
return nodeVars;
|
||||
}
|
||||
|
||||
export interface SolarSystemNodeVars {
|
||||
id: string;
|
||||
selected: boolean;
|
||||
visible: boolean;
|
||||
isWormhole: boolean;
|
||||
classTitleColor: string | null;
|
||||
killsCount: number | null;
|
||||
killsActivityType: string | null;
|
||||
hasUserCharacters: boolean;
|
||||
showHandlers: boolean;
|
||||
regionClass: string | null;
|
||||
systemName: string;
|
||||
customName?: string | null;
|
||||
labelCustom: string | null;
|
||||
isShattered: boolean;
|
||||
tag?: string | null;
|
||||
status?: number;
|
||||
labelsInfo: LabelInfo[];
|
||||
dbClick: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
sortedStatics: Array<string | number>;
|
||||
effectName: string | null;
|
||||
regionName: string | null;
|
||||
solarSystemId: string;
|
||||
solarSystemName: string | null;
|
||||
locked: boolean;
|
||||
hubs: string[];
|
||||
name: string | null;
|
||||
isConnecting: boolean;
|
||||
hoverNodeId: string | null;
|
||||
charactersInSystem: Array<CharacterTypeRaw>;
|
||||
userCharacters: string[];
|
||||
unsplashedLeft: Array<SystemSignature>;
|
||||
unsplashedRight: Array<SystemSignature>;
|
||||
isThickConnections: boolean;
|
||||
classTitle: string | null;
|
||||
temporaryName?: string | null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import classes from './MarkdownComment.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import Markdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { InfoDrawer, TimeAgo, TooltipPosition, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import remarkBreaks from 'remark-breaks';
|
||||
import {
|
||||
InfoDrawer,
|
||||
MarkdownTextViewer,
|
||||
TimeAgo,
|
||||
TooltipPosition,
|
||||
WdImgButton,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { useGetCacheCharacter } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { WdTransition } from '@/hooks/Mapper/components/ui-kit/WdTransition/WdTransition.tsx';
|
||||
@@ -13,7 +16,6 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
|
||||
const TOOLTIP_PROPS = { content: 'Remove comment', position: TooltipPosition.top };
|
||||
const REMARK_PLUGINS = [remarkGfm, remarkBreaks];
|
||||
|
||||
export interface MarkdownCommentProps {
|
||||
text: string;
|
||||
@@ -79,7 +81,7 @@ export const MarkdownComment = ({ text, time, characterEveId, id }: MarkdownComm
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Markdown remarkPlugins={REMARK_PLUGINS}>{text}</Markdown>
|
||||
<MarkdownTextViewer>{text}</MarkdownTextViewer>
|
||||
</InfoDrawer>
|
||||
|
||||
<ConfirmPopup
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export interface CommentsEditorProps {}
|
||||
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
export const CommentsEditor = ({}: CommentsEditorProps) => {
|
||||
const [textVal, setTextVal] = useState('');
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMemo } from 'react';
|
||||
import { RoutesList } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesList';
|
||||
|
||||
export const PingRoute = () => {
|
||||
const {
|
||||
data: { routes, pings, loadingPublicRoutes },
|
||||
} = useMapRootState();
|
||||
|
||||
const route = useMemo(() => {
|
||||
const [ping] = pings;
|
||||
if (!ping) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return routes?.routes.find(x => ping.solar_system_id === x.destination.toString()) ?? null;
|
||||
}, [routes, pings]);
|
||||
|
||||
const preparedRoute = useMemo(() => {
|
||||
if (!route) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...route,
|
||||
mapped_systems:
|
||||
route.systems?.map(solar_system_id =>
|
||||
routes?.systems_static_data.find(
|
||||
system_static_data => system_static_data.solar_system_id === solar_system_id,
|
||||
),
|
||||
) ?? [],
|
||||
};
|
||||
}, [route, routes?.systems_static_data]);
|
||||
|
||||
if (loadingPublicRoutes) {
|
||||
return <span className="m-0 text-[12px]">Loading...</span>;
|
||||
}
|
||||
|
||||
if (!preparedRoute || preparedRoute.origin === preparedRoute.destination) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="m-0 flex gap-2 items-center text-[12px]">
|
||||
{preparedRoute.has_connection && <div className="text-[12px]">{preparedRoute.systems?.length ?? 2}</div>}
|
||||
<RoutesList data={preparedRoute} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,284 @@
|
||||
import { Button } from 'primereact/button';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Toast } from 'primereact/toast';
|
||||
import clsx from 'clsx';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { Commands, OutCommand, PingType } from '@/hooks/Mapper/types';
|
||||
import {
|
||||
CharacterCardById,
|
||||
SystemView,
|
||||
TimeAgo,
|
||||
TooltipPosition,
|
||||
WdImgButton,
|
||||
WdImgButtonTooltip,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import useRefState from 'react-usestateref';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
import { ConfirmPopup } from 'primereact/confirmpopup';
|
||||
import { PingRoute } from '@/hooks/Mapper/components/mapInterface/components/PingsInterface/PingRoute.tsx';
|
||||
import { PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
const PING_PLACEMENT_MAP = {
|
||||
[PingsPlacement.rightTop]: 'top-right',
|
||||
[PingsPlacement.leftTop]: 'top-left',
|
||||
[PingsPlacement.rightBottom]: 'bottom-right',
|
||||
[PingsPlacement.leftBottom]: 'bottom-left',
|
||||
};
|
||||
|
||||
const PING_PLACEMENT_MAP_OFFSETS = {
|
||||
[PingsPlacement.rightTop]: { default: '!top-[56px]', withLeftMenu: '!top-[56px] !right-[64px]' },
|
||||
[PingsPlacement.rightBottom]: { default: '!bottom-[15px]', withLeftMenu: '!bottom-[15px] !right-[64px]' },
|
||||
[PingsPlacement.leftTop]: { default: '!top-[56px] !left-[64px]', withLeftMenu: '!top-[56px] !left-[64px]' },
|
||||
[PingsPlacement.leftBottom]: { default: '!left-[64px] !bottom-[15px]', withLeftMenu: '!bottom-[15px]' },
|
||||
};
|
||||
|
||||
const CLOSE_TOOLTIP_PROPS: WdImgButtonTooltip = {
|
||||
content: 'Hide',
|
||||
position: TooltipPosition.top,
|
||||
className: '!leading-[0]',
|
||||
};
|
||||
|
||||
const NAVIGATE_TOOLTIP_PROPS: WdImgButtonTooltip = {
|
||||
content: 'Navigate To',
|
||||
position: TooltipPosition.top,
|
||||
className: '!leading-[0]',
|
||||
};
|
||||
|
||||
const DELETE_TOOLTIP_PROPS: WdImgButtonTooltip = {
|
||||
content: 'Remove',
|
||||
position: TooltipPosition.top,
|
||||
className: '!leading-[0]',
|
||||
};
|
||||
|
||||
// const TOOLTIP_WAYPOINT_PROPS: WdImgButtonTooltip = {
|
||||
// content: 'Waypoint',
|
||||
// position: TooltipPosition.bottom,
|
||||
// className: '!leading-[0]',
|
||||
// };
|
||||
|
||||
const TITLES = {
|
||||
[PingType.Alert]: 'Alert',
|
||||
[PingType.Rally]: 'Rally Point',
|
||||
};
|
||||
|
||||
const ICONS = {
|
||||
[PingType.Alert]: 'pi-bell',
|
||||
[PingType.Rally]: 'pi-bell',
|
||||
};
|
||||
|
||||
export interface PingsInterfaceProps {
|
||||
hasLeftOffset?: boolean;
|
||||
}
|
||||
|
||||
// TODO: right now can be one ping. But in future will be multiple pings then:
|
||||
// 1. we will use this as container
|
||||
// 2. we will create PingInstance (which will contains ping Button and Toast
|
||||
// 3. ADD Context menu
|
||||
export const PingsInterface = ({ hasLeftOffset }: PingsInterfaceProps) => {
|
||||
const toast = useRef<Toast>(null);
|
||||
const [isShow, setIsShow, isShowRef] = useRefState(false);
|
||||
|
||||
const cpRemoveBtnRef = useRef<HTMLElement>();
|
||||
const [cpRemoveVisible, setCpRemoveVisible] = useState(false);
|
||||
|
||||
const {
|
||||
storedSettings: { interfaceSettings },
|
||||
data: { pings, selectedSystems },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const selectedSystem = useMemo(() => {
|
||||
if (selectedSystems.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return selectedSystems[0];
|
||||
}, [selectedSystems]);
|
||||
|
||||
const ping = useMemo(() => (pings.length === 1 ? pings[0] : null), [pings]);
|
||||
|
||||
const handleShowCP = useCallback(() => setCpRemoveVisible(true), []);
|
||||
const handleHideCP = useCallback(() => setCpRemoveVisible(false), []);
|
||||
|
||||
const navigateTo = useCallback(() => {
|
||||
if (!ping) {
|
||||
return;
|
||||
}
|
||||
|
||||
emitMapEvent({
|
||||
name: Commands.centerSystem,
|
||||
data: ping.solar_system_id?.toString(),
|
||||
});
|
||||
}, [ping]);
|
||||
|
||||
const removePing = useCallback(async () => {
|
||||
if (!ping) {
|
||||
return;
|
||||
}
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.cancelPing,
|
||||
data: { type: ping.type, solar_system_id: ping.solar_system_id },
|
||||
});
|
||||
}, [outCommand, ping]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ping) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tid = setTimeout(() => {
|
||||
toast.current?.replace({ severity: 'warn', detail: ping.message });
|
||||
setIsShow(true);
|
||||
}, 200);
|
||||
|
||||
return () => clearTimeout(tid);
|
||||
}, [ping]);
|
||||
|
||||
const handleClickShow = useCallback(() => {
|
||||
if (!ping) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isShowRef.current) {
|
||||
toast.current?.show({ severity: 'warn', detail: ping.message });
|
||||
setIsShow(true);
|
||||
return;
|
||||
}
|
||||
toast.current?.clear();
|
||||
setIsShow(false);
|
||||
}, [ping]);
|
||||
|
||||
const handleClickHide = useCallback(() => {
|
||||
toast.current?.clear();
|
||||
setIsShow(false);
|
||||
}, []);
|
||||
|
||||
const { placement, offsets } = useMemo(() => {
|
||||
const rawPlacement =
|
||||
interfaceSettings.pingsPlacement == null ? PingsPlacement.rightTop : interfaceSettings.pingsPlacement;
|
||||
|
||||
return {
|
||||
placement: PING_PLACEMENT_MAP[rawPlacement],
|
||||
offsets: PING_PLACEMENT_MAP_OFFSETS[rawPlacement],
|
||||
};
|
||||
}, [interfaceSettings]);
|
||||
|
||||
if (!ping) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isShowSelectedSystem = selectedSystem != null && selectedSystem !== ping.solar_system_id;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toast
|
||||
position={placement as never}
|
||||
className={clsx('!max-w-[initial] w-[500px]', hasLeftOffset ? offsets.withLeftMenu : offsets.default)}
|
||||
ref={toast}
|
||||
content={({ message }) => (
|
||||
<section
|
||||
className={clsx(
|
||||
'flex flex-col p-3 w-full border border-stone-800 shadow-md animate-fadeInDown rounded-[5px]',
|
||||
'bg-gradient-to-tr from-transparent to-sky-700/60 bg-stone-900/70',
|
||||
)}
|
||||
>
|
||||
<div className="flex gap-3">
|
||||
<i className={clsx('pi text-yellow-500 text-2xl', 'relative top-[2px]', ICONS[ping.type])}></i>
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<div className="m-0 font-semibold text-base text-white">{TITLES[ping.type]}</div>
|
||||
|
||||
<div className="flex gap-1 items-center">
|
||||
{isShowSelectedSystem && (
|
||||
<>
|
||||
<SystemView systemId={selectedSystem} />
|
||||
<span className="pi pi-angle-double-right text-[10px] relative top-[1px] text-stone-400" />
|
||||
</>
|
||||
)}
|
||||
<SystemView systemId={ping.solar_system_id} />
|
||||
{isShowSelectedSystem && (
|
||||
<WdImgButton
|
||||
className={clsx(PrimeIcons.QUESTION_CIRCLE, 'ml-[2px] relative top-[-2px] !text-[10px]')}
|
||||
tooltip={{
|
||||
position: TooltipPosition.top,
|
||||
content: (
|
||||
<div className="flex flex-col gap-1">
|
||||
The settings for the route are taken from the Routes settings and can be configured
|
||||
through them.
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-end">
|
||||
<CharacterCardById className="" characterId={ping.character_eve_id} simpleMode />
|
||||
<TimeAgo timestamp={ping.inserted_at.toString()} className="text-stone-400 text-[11px]" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedSystem != null && <PingRoute />}
|
||||
|
||||
<p className="m-0 text-[13px] text-stone-200 min-h-[20px] pr-[16px]">{message.detail}</p>
|
||||
</div>
|
||||
|
||||
<WdImgButton
|
||||
className={clsx(PrimeIcons.TIMES, 'hover:text-red-400 mt-[3px]')}
|
||||
tooltip={CLOSE_TOOLTIP_PROPS}
|
||||
onClick={handleClickHide}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/*Button bar*/}
|
||||
<div className="flex justify-end items-center gap-2 h-0 relative top-[-8px]">
|
||||
<WdImgButton
|
||||
className={clsx('pi-compass', 'hover:text-red-400 mt-[3px]')}
|
||||
tooltip={NAVIGATE_TOOLTIP_PROPS}
|
||||
onClick={navigateTo}
|
||||
/>
|
||||
|
||||
{/*@ts-ignore*/}
|
||||
<div ref={cpRemoveBtnRef}>
|
||||
<WdImgButton
|
||||
className={clsx('pi-trash', 'text-red-400 hover:text-red-300')}
|
||||
tooltip={DELETE_TOOLTIP_PROPS}
|
||||
onClick={handleShowCP}
|
||||
/>
|
||||
</div>
|
||||
{/* TODO ADD solar system menu*/}
|
||||
{/*<WdImgButton*/}
|
||||
{/* className={clsx('pi-map-marker', 'hover:text-red-400 mt-[3px]')}*/}
|
||||
{/* tooltip={TOOLTIP_WAYPOINT_PROPS}*/}
|
||||
{/* onClick={handleClickHide}*/}
|
||||
{/*/>*/}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
></Toast>
|
||||
|
||||
<Button
|
||||
icon="pi pi-bell"
|
||||
severity="warning"
|
||||
aria-label="Notification"
|
||||
size="small"
|
||||
className="w-[33px] h-[33px]"
|
||||
outlined
|
||||
onClick={handleClickShow}
|
||||
disabled={isShow}
|
||||
/>
|
||||
|
||||
<ConfirmPopup
|
||||
target={cpRemoveBtnRef.current}
|
||||
visible={cpRemoveVisible}
|
||||
onHide={handleHideCP}
|
||||
message="Are you sure you want to delete ping?"
|
||||
icon="pi pi-exclamation-triangle text-orange-400"
|
||||
accept={removePing}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './PingsInterface.tsx';
|
||||
@@ -146,7 +146,7 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
||||
}
|
||||
|
||||
const whShipSize = getWhSize(wormholes, signature.type);
|
||||
if (whShipSize) {
|
||||
if (whShipSize !== undefined && whShipSize !== null) {
|
||||
await outCommand({
|
||||
type: OutCommand.updateConnectionShipSizeType,
|
||||
data: {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { InputTextarea } from 'primereact/inputtextarea';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { PingType } from '@/hooks/Mapper/types/ping.ts';
|
||||
import { SystemView } from '@/hooks/Mapper/components/ui-kit';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const PING_TITLES = {
|
||||
[PingType.Rally]: 'RALLY',
|
||||
[PingType.Alert]: 'ALERT',
|
||||
};
|
||||
|
||||
interface SystemPingDialogProps {
|
||||
systemId: string;
|
||||
type: PingType;
|
||||
visible: boolean;
|
||||
setVisible: (visible: boolean) => void;
|
||||
}
|
||||
|
||||
export const SystemPingDialog = ({ systemId, type, visible, setVisible }: SystemPingDialogProps) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
|
||||
const [message, setMessage] = useState('');
|
||||
const inputRef = useRef<HTMLTextAreaElement>();
|
||||
|
||||
const ref = useRef({ message, outCommand, systemId, type });
|
||||
ref.current = { message, outCommand, systemId, type };
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
const { message, outCommand, systemId, type } = ref.current;
|
||||
|
||||
outCommand({
|
||||
type: OutCommand.addPing,
|
||||
data: {
|
||||
solar_system_id: systemId,
|
||||
type,
|
||||
message,
|
||||
},
|
||||
});
|
||||
setVisible(false);
|
||||
}, [setVisible]);
|
||||
|
||||
const onShow = useCallback(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
header={
|
||||
<div className="flex gap-1 text-[13px] items-center text-stone-300">
|
||||
<div>Ping:{` `}</div>
|
||||
<div
|
||||
className={clsx({
|
||||
['text-cyan-400']: type === PingType.Rally,
|
||||
})}
|
||||
>
|
||||
{PING_TITLES[type]}
|
||||
</div>
|
||||
<div className="text-[11px]">in</div> <SystemView systemId={systemId} className="relative top-[1px]" />
|
||||
</div>
|
||||
}
|
||||
visible={visible}
|
||||
draggable={false}
|
||||
style={{ width: '450px' }}
|
||||
onShow={onShow}
|
||||
onHide={() => {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
<form onSubmit={handleSave}>
|
||||
<div className="flex flex-col gap-3 px-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[11px]" htmlFor="username">
|
||||
Message
|
||||
</label>
|
||||
<InputTextarea
|
||||
// @ts-ignore
|
||||
ref={inputRef}
|
||||
autoResize
|
||||
rows={3}
|
||||
cols={30}
|
||||
value={message}
|
||||
onChange={e => setMessage(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button onClick={handleSave} size="small" severity="danger" label="Ping!"></Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './SystemPingDialog';
|
||||
@@ -2,3 +2,4 @@ export * from './Widget';
|
||||
export * from './SystemSettingsDialog';
|
||||
export * from './SystemCustomLabelDialog';
|
||||
export * from './SystemLinkSignatureDialog';
|
||||
export * from './PingsInterface';
|
||||
|
||||
@@ -62,7 +62,7 @@ export const DEFAULT_WIDGETS: WindowProps[] = [
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.userRoutes,
|
||||
position: { x: 10, y: 530 },
|
||||
position: { x: 10, y: 10 },
|
||||
size: { width: 510, height: 200 },
|
||||
zIndex: 0,
|
||||
content: () => <WRoutesUser />,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
|
||||
|
||||
export const sortCharacters = (a: CharacterTypeRaw & WithIsOwnCharacter, b: CharacterTypeRaw & WithIsOwnCharacter) => {
|
||||
if (a.online === b.online) {
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
|
||||
if (a.online !== b.online) {
|
||||
return a.online && !b.online ? -1 : 1;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import classes from './LocalCharactersItemTemplate.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import { CharacterCard } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { CharItemProps } from '@/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components';
|
||||
import { CharacterCard } from '@/hooks/Mapper/components/ui-kit';
|
||||
import clsx from 'clsx';
|
||||
import { VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
||||
import classes from './LocalCharactersItemTemplate.module.scss';
|
||||
|
||||
export type LocalCharactersItemTemplateProps = { showShipName: boolean } & CharItemProps &
|
||||
VirtualScrollerTemplateOptions;
|
||||
@@ -22,7 +22,7 @@ export const LocalCharactersItemTemplate = ({ showShipName, ...options }: LocalC
|
||||
)}
|
||||
style={{ height: `${options.props.itemSize}px` }}
|
||||
>
|
||||
<CharacterCard showShipName={showShipName} showTicker {...options} />
|
||||
<CharacterCard showShipName={showShipName} showTicker showShip {...options} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { createContext, forwardRef, useContext, useImperativeHandle, useState } from 'react';
|
||||
import React, { createContext, forwardRef, useContext } from 'react';
|
||||
import {
|
||||
RoutesImperativeHandle,
|
||||
RoutesProviderInnerProps,
|
||||
@@ -15,17 +15,14 @@ const RoutesContext = createContext<RoutesProviderInnerProps>({
|
||||
data: {},
|
||||
});
|
||||
|
||||
export const RoutesProvider = forwardRef<RoutesImperativeHandle, MapProviderProps>(({ children, ...props }, ref) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
// INFO: this component have imperative handler but now it not using.
|
||||
export const RoutesProvider = forwardRef<RoutesImperativeHandle, MapProviderProps>(
|
||||
({ children, ...props } /*, ref*/) => {
|
||||
// useImperativeHandle(ref, () => ({}));
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
stopLoading() {
|
||||
setLoading(false);
|
||||
},
|
||||
}));
|
||||
|
||||
return <RoutesContext.Provider value={{ ...props, loading, setLoading }}>{children}</RoutesContext.Provider>;
|
||||
});
|
||||
return <RoutesContext.Provider value={{ ...props /*, loading, setLoading*/ }}>{children}</RoutesContext.Provider>;
|
||||
},
|
||||
);
|
||||
RoutesProvider.displayName = 'RoutesProvider';
|
||||
|
||||
export const useRouteProvider = () => {
|
||||
|
||||
@@ -3,16 +3,15 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import {
|
||||
LayoutEventBlocker,
|
||||
LoadingWrapper,
|
||||
SystemViewStandalone,
|
||||
SystemView,
|
||||
TooltipPosition,
|
||||
WdCheckbox,
|
||||
WdImgButton,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
||||
|
||||
import { forwardRef, MouseEvent, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers/getSystemById.ts';
|
||||
import classes from './RoutesWidget.module.scss';
|
||||
import { useLoadRoutes } from './hooks';
|
||||
import { RoutesList } from './RoutesList';
|
||||
import clsx from 'clsx';
|
||||
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
||||
@@ -42,24 +41,13 @@ export const RoutesWidgetContent = () => {
|
||||
const {
|
||||
data: { selectedSystems, systems, isSubscriptionActive },
|
||||
} = useMapRootState();
|
||||
const { hubs = [], routesList, isRestricted } = useRouteProvider();
|
||||
const { hubs = [], routesList, isRestricted, loading } = useRouteProvider();
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
|
||||
const { loading } = useLoadRoutes();
|
||||
|
||||
const { systems: systemStatics, loadSystems, lastUpdateKey } = useLoadSystemStatic({ systems: hubs ?? [] });
|
||||
const { systems: systemStatics, loadSystems } = useLoadSystemStatic({ systems: hubs ?? [] });
|
||||
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers();
|
||||
|
||||
const preparedHubs = useMemo(() => {
|
||||
return hubs.map(x => {
|
||||
const sys = getSystemById(systems, x.toString());
|
||||
|
||||
return { ...systemStatics.get(parseInt(x))!, ...(sys && { customName: sys.name ?? '' }) };
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [hubs, systems, systemStatics, lastUpdateKey]);
|
||||
|
||||
const preparedRoutes: Route[] = useMemo(() => {
|
||||
return (
|
||||
routesList?.routes
|
||||
@@ -125,9 +113,7 @@ export const RoutesWidgetContent = () => {
|
||||
<LoadingWrapper loading={loading}>
|
||||
<div className={clsx(classes.RoutesGrid, 'px-2 py-2')}>
|
||||
{preparedRoutes.map(route => {
|
||||
const sys = preparedHubs.find(x => x.solar_system_id === route.destination)!;
|
||||
|
||||
// TODO do not delte this console log
|
||||
// TODO do not delete this console log
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('JOipP', `Check sys [${route.destination}]:`, sys);
|
||||
|
||||
@@ -144,12 +130,12 @@ export const RoutesWidgetContent = () => {
|
||||
}}
|
||||
/>
|
||||
|
||||
<SystemViewStandalone
|
||||
key={route.destination}
|
||||
<SystemView
|
||||
systemId={route.destination.toString()}
|
||||
className={clsx('select-none text-center cursor-context-menu')}
|
||||
hideRegion
|
||||
compact
|
||||
{...sys}
|
||||
showCustomName
|
||||
/>
|
||||
</div>
|
||||
<div className="text-right pl-1">{route.has_connection ? route.systems?.length ?? 2 : ''}</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useRouteProvider } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
|
||||
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { LoadRoutesCommand } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
|
||||
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||
|
||||
function usePrevious<T>(value: T): T | undefined {
|
||||
const ref = useRef<T>();
|
||||
@@ -13,8 +14,22 @@ function usePrevious<T>(value: T): T | undefined {
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
export const useLoadRoutes = () => {
|
||||
const { data: routesSettings, loadRoutesCommand, hubs, routesList, loading, setLoading } = useRouteProvider();
|
||||
type UseLoadRoutesProps = {
|
||||
loadRoutesCommand: LoadRoutesCommand;
|
||||
hubs: string[];
|
||||
routesList: RoutesList | undefined;
|
||||
data: RoutesType;
|
||||
deps?: unknown[];
|
||||
};
|
||||
|
||||
export const useLoadRoutes = ({
|
||||
data: routesSettings,
|
||||
loadRoutesCommand,
|
||||
hubs,
|
||||
routesList,
|
||||
deps = [],
|
||||
}: UseLoadRoutesProps) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const {
|
||||
data: { selectedSystems, systems, connections },
|
||||
@@ -55,7 +70,8 @@ export const useLoadRoutes = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
.map(x => routesSettings[x]),
|
||||
...deps,
|
||||
]);
|
||||
|
||||
return { loading, loadRoutes };
|
||||
return { loading, loadRoutes, setLoading };
|
||||
};
|
||||
|
||||
@@ -10,17 +10,14 @@ export type RoutesWidgetProps = {
|
||||
update: (d: RoutesType) => void;
|
||||
hubs: string[];
|
||||
routesList: RoutesList | undefined;
|
||||
loading: boolean;
|
||||
|
||||
loadRoutesCommand: LoadRoutesCommand;
|
||||
addHubCommand: AddHubCommand;
|
||||
toggleHubCommand: ToggleHubCommand;
|
||||
isRestricted?: boolean;
|
||||
};
|
||||
|
||||
export type RoutesProviderInnerProps = RoutesWidgetProps & {
|
||||
loading: boolean;
|
||||
setLoading(loading: boolean): void;
|
||||
};
|
||||
export type RoutesProviderInnerProps = RoutesWidgetProps;
|
||||
|
||||
export type RoutesImperativeHandle = {
|
||||
stopLoading: () => void;
|
||||
|
||||
@@ -12,10 +12,10 @@ import {
|
||||
SIGNATURE_SETTING_STORE_KEY,
|
||||
SIGNATURE_WINDOW_ID,
|
||||
SignatureSettingsType,
|
||||
SIGNATURES_DELETION_TIMING,
|
||||
SIGNATURE_DELETION_TIMEOUTS,
|
||||
getDeletionTimeoutMs,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
/**
|
||||
* Custom hook for managing pending signature deletions and undo countdown.
|
||||
@@ -27,16 +27,32 @@ function useSignatureUndo(
|
||||
) {
|
||||
const [countdown, setCountdown] = useState<number>(0);
|
||||
const [pendingIds, setPendingIds] = useState<Set<string>>(new Set());
|
||||
const [deletedSignatures, setDeletedSignatures] = useState<ExtendedSystemSignature[]>([]);
|
||||
const intervalRef = useRef<number | null>(null);
|
||||
|
||||
const addDeleted = useCallback((ids: string[]) => {
|
||||
const addDeleted = useCallback((signatures: ExtendedSystemSignature[]) => {
|
||||
const newIds = signatures.map(sig => sig.eve_id);
|
||||
setPendingIds(prev => {
|
||||
const next = new Set(prev);
|
||||
ids.forEach(id => next.add(id));
|
||||
newIds.forEach(id => next.add(id));
|
||||
return next;
|
||||
});
|
||||
setDeletedSignatures(prev => [...prev, ...signatures]);
|
||||
}, []);
|
||||
|
||||
// Clear deleted signatures when system changes
|
||||
useEffect(() => {
|
||||
if (systemId) {
|
||||
setDeletedSignatures([]);
|
||||
setPendingIds(new Set());
|
||||
setCountdown(0);
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}
|
||||
}, [systemId]);
|
||||
|
||||
// kick off or clear countdown whenever pendingIds changes
|
||||
useEffect(() => {
|
||||
// clear any existing timer
|
||||
@@ -47,13 +63,13 @@ function useSignatureUndo(
|
||||
|
||||
if (pendingIds.size === 0) {
|
||||
setCountdown(0);
|
||||
setDeletedSignatures([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// determine timeout from settings
|
||||
const timingKey = Number(settings[SETTINGS_KEYS.DELETION_TIMING] ?? SIGNATURES_DELETION_TIMING.DEFAULT);
|
||||
const timeoutMs =
|
||||
Number(SIGNATURE_DELETION_TIMEOUTS[timingKey as keyof typeof SIGNATURE_DELETION_TIMEOUTS]) || 10000;
|
||||
const timeoutMs = getDeletionTimeoutMs(settings);
|
||||
|
||||
setCountdown(Math.ceil(timeoutMs / 1000));
|
||||
|
||||
// start new interval
|
||||
@@ -63,6 +79,7 @@ function useSignatureUndo(
|
||||
clearInterval(intervalRef.current!);
|
||||
intervalRef.current = null;
|
||||
setPendingIds(new Set());
|
||||
setDeletedSignatures([]);
|
||||
return 0;
|
||||
}
|
||||
return prev - 1;
|
||||
@@ -75,7 +92,7 @@ function useSignatureUndo(
|
||||
intervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [pendingIds, settings[SETTINGS_KEYS.DELETION_TIMING]]);
|
||||
}, [pendingIds, settings]);
|
||||
|
||||
// undo handler
|
||||
const handleUndo = useCallback(async () => {
|
||||
@@ -85,6 +102,7 @@ function useSignatureUndo(
|
||||
data: { system_id: systemId, eve_ids: Array.from(pendingIds) },
|
||||
});
|
||||
setPendingIds(new Set());
|
||||
setDeletedSignatures([]);
|
||||
setCountdown(0);
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
@@ -95,6 +113,7 @@ function useSignatureUndo(
|
||||
return {
|
||||
pendingIds,
|
||||
countdown,
|
||||
deletedSignatures,
|
||||
addDeleted,
|
||||
handleUndo,
|
||||
};
|
||||
@@ -118,7 +137,11 @@ export const SystemSignatures = () => {
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
const isSystemSelected = useMemo(() => selectedSystems.length === 1, [selectedSystems.length]);
|
||||
const { pendingIds, countdown, addDeleted, handleUndo } = useSignatureUndo(systemId, currentSettings, outCommand);
|
||||
const { pendingIds, countdown, deletedSignatures, addDeleted, handleUndo } = useSignatureUndo(
|
||||
systemId,
|
||||
currentSettings,
|
||||
outCommand,
|
||||
);
|
||||
|
||||
useHotkey(true, ['z', 'Z'], (event: KeyboardEvent) => {
|
||||
if (pendingIds.size > 0 && countdown > 0) {
|
||||
@@ -175,6 +198,7 @@ export const SystemSignatures = () => {
|
||||
<SystemSignaturesContent
|
||||
systemId={systemId}
|
||||
settings={currentSettings}
|
||||
deletedSignatures={deletedSignatures}
|
||||
onLazyDeleteChange={handleLazyDeleteToggle}
|
||||
onCountChange={handleCountChange}
|
||||
onSignatureDeleted={addDeleted}
|
||||
|
||||
@@ -58,7 +58,8 @@ interface SystemSignaturesContentProps {
|
||||
onLazyDeleteChange?: (value: boolean) => void;
|
||||
onCountChange?: (count: number) => void;
|
||||
filterSignature?: (signature: SystemSignature) => boolean;
|
||||
onSignatureDeleted?: (deletedIds: string[]) => void;
|
||||
onSignatureDeleted?: (deletedSignatures: ExtendedSystemSignature[]) => void;
|
||||
deletedSignatures?: ExtendedSystemSignature[];
|
||||
}
|
||||
|
||||
export const SystemSignaturesContent = ({
|
||||
@@ -71,6 +72,7 @@ export const SystemSignaturesContent = ({
|
||||
onCountChange,
|
||||
filterSignature,
|
||||
onSignatureDeleted,
|
||||
deletedSignatures = [],
|
||||
}: SystemSignaturesContentProps) => {
|
||||
const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState<SystemSignature | null>(null);
|
||||
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
|
||||
@@ -126,10 +128,8 @@ export const SystemSignaturesContent = ({
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (onSignatureDeleted && selectedSignatures.length > 0) {
|
||||
const deletedIds = selectedSignatures.map(s => s.eve_id);
|
||||
onSignatureDeleted(deletedIds);
|
||||
}
|
||||
|
||||
// Delete key should always immediately delete, never show pending deletions
|
||||
handleDeleteSelected();
|
||||
});
|
||||
|
||||
@@ -158,9 +158,16 @@ export const SystemSignaturesContent = ({
|
||||
|
||||
const handleSelectSignatures = useCallback(
|
||||
(e: { value: SystemSignature[] }) => {
|
||||
selectable ? onSelect?.(e.value[0]) : setSelectedSignatures(e.value as ExtendedSystemSignature[]);
|
||||
// Filter out deleted signatures from selection
|
||||
const selectableSignatures = e.value.filter(
|
||||
sig => !deletedSignatures.some(deleted => deleted.eve_id === sig.eve_id),
|
||||
);
|
||||
|
||||
selectable
|
||||
? onSelect?.(selectableSignatures[0])
|
||||
: setSelectedSignatures(selectableSignatures as ExtendedSystemSignature[]);
|
||||
},
|
||||
[onSelect, selectable, setSelectedSignatures],
|
||||
[onSelect, selectable, setSelectedSignatures, deletedSignatures],
|
||||
);
|
||||
|
||||
const { showDescriptionColumn, showUpdatedColumn, showCharacterColumn, showCharacterPortrait } = useMemo(
|
||||
@@ -174,7 +181,11 @@ export const SystemSignaturesContent = ({
|
||||
);
|
||||
|
||||
const filteredSignatures = useMemo<ExtendedSystemSignature[]>(() => {
|
||||
return signatures.filter(sig => {
|
||||
// Get the set of deleted signature IDs for quick lookup
|
||||
const deletedIds = new Set(deletedSignatures.map(sig => sig.eve_id));
|
||||
|
||||
// Common filter function
|
||||
const shouldShowSignature = (sig: ExtendedSystemSignature): boolean => {
|
||||
if (filterSignature && !filterSignature(sig)) {
|
||||
return false;
|
||||
}
|
||||
@@ -203,9 +214,27 @@ export const SystemSignaturesContent = ({
|
||||
return true;
|
||||
}
|
||||
|
||||
return settings[sig.kind];
|
||||
return settings[sig.kind] as boolean;
|
||||
};
|
||||
|
||||
// Filter active signatures, excluding any that are in the deleted list
|
||||
const activeSignatures = signatures.filter(sig => {
|
||||
// Skip if this signature is in the deleted list
|
||||
if (deletedIds.has(sig.eve_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return shouldShowSignature(sig);
|
||||
});
|
||||
}, [signatures, hideLinkedSignatures, settings, filterSignature]);
|
||||
|
||||
// Add deleted signatures with pending deletion flag, applying the same filters
|
||||
const deletedWithPendingFlag = deletedSignatures.filter(shouldShowSignature).map(sig => ({
|
||||
...sig,
|
||||
pendingDeletion: true,
|
||||
}));
|
||||
|
||||
return [...activeSignatures, ...deletedWithPendingFlag];
|
||||
}, [signatures, hideLinkedSignatures, settings, filterSignature, deletedSignatures]);
|
||||
|
||||
const onRowMouseEnter = useCallback((e: DataTableRowMouseEvent) => {
|
||||
setHoveredSignature(e.data as SystemSignature);
|
||||
@@ -249,7 +278,8 @@ export const SystemSignaturesContent = ({
|
||||
{hasUnsupportedLanguage && (
|
||||
<div className="w-full flex justify-center items-center text-amber-500 text-xs p-1 bg-amber-950/20 border-b border-amber-800/30">
|
||||
<i className={PrimeIcons.EXCLAMATION_TRIANGLE + ' mr-1'} />
|
||||
Non-English signatures detected. Some signatures may not display correctly. Double-click to edit signature details.
|
||||
Non-English signatures detected. Some signatures may not display correctly. Double-click to edit signature
|
||||
details.
|
||||
</div>
|
||||
)}
|
||||
<DataTable
|
||||
|
||||
@@ -148,7 +148,8 @@ export enum SIGNATURES_DELETION_TIMING {
|
||||
EXTENDED,
|
||||
}
|
||||
|
||||
export type SignatureDeletionTimingType = { [key in SIGNATURES_DELETION_TIMING]?: unknown };
|
||||
// Now use a stricter type: every timing key maps to a number
|
||||
export type SignatureDeletionTimingType = Record<SIGNATURES_DELETION_TIMING, number>;
|
||||
|
||||
export const SIGNATURE_SETTINGS = {
|
||||
filterFlags: [
|
||||
@@ -219,12 +220,28 @@ export const SETTINGS_VALUES: SignatureSettingsType = {
|
||||
[SETTINGS_KEYS.COMBAT_SITE]: true,
|
||||
};
|
||||
|
||||
// Now this map is strongly typed as “number” for each timing enum
|
||||
export const SIGNATURE_DELETION_TIMEOUTS: SignatureDeletionTimingType = {
|
||||
[SIGNATURES_DELETION_TIMING.DEFAULT]: 10_000,
|
||||
[SIGNATURES_DELETION_TIMING.IMMEDIATE]: 0,
|
||||
[SIGNATURES_DELETION_TIMING.DEFAULT]: 10_000,
|
||||
[SIGNATURES_DELETION_TIMING.EXTENDED]: 30_000,
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to extract the deletion timeout in milliseconds from settings
|
||||
*/
|
||||
export function getDeletionTimeoutMs(settings: SignatureSettingsType): number {
|
||||
const raw = settings[SETTINGS_KEYS.DELETION_TIMING];
|
||||
const timing =
|
||||
raw && typeof raw === 'object' && 'value' in raw
|
||||
? (raw as { value: SIGNATURES_DELETION_TIMING }).value
|
||||
: (raw as SIGNATURES_DELETION_TIMING | undefined);
|
||||
|
||||
const validTiming = typeof timing === 'number' ? timing : SIGNATURES_DELETION_TIMING.DEFAULT;
|
||||
|
||||
return SIGNATURE_DELETION_TIMEOUTS[validTiming];
|
||||
}
|
||||
|
||||
// Replace the flat structure with a nested structure by language
|
||||
export const LANGUAGE_TYPE_MAPPINGS = {
|
||||
EN: {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { UNKNOWN_SIGNATURE_NAME } from '@/hooks/Mapper/helpers';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
export const getState = (_: string[], newSig: SystemSignature) => {
|
||||
let state = -1;
|
||||
if (!newSig.group) {
|
||||
if (!newSig.group || newSig.group === SignatureGroup.CosmicSignature) {
|
||||
state = 0;
|
||||
} else if (!newSig.name || newSig.name === '' || newSig.name === UNKNOWN_SIGNATURE_NAME) {
|
||||
state = 1;
|
||||
} else if (newSig.name !== '') {
|
||||
state = 2;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
@@ -5,7 +5,10 @@ import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import useRefState from 'react-usestateref';
|
||||
|
||||
import { SETTINGS_KEYS } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import {
|
||||
SETTINGS_KEYS,
|
||||
getDeletionTimeoutMs,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { getActualSigs } from '../helpers';
|
||||
import { UseSystemSignaturesDataProps } from './types';
|
||||
@@ -20,7 +23,7 @@ export const useSystemSignaturesData = ({
|
||||
onLazyDeleteChange,
|
||||
onSignatureDeleted,
|
||||
}: Omit<UseSystemSignaturesDataProps, 'deletionTiming'> & {
|
||||
onSignatureDeleted?: (deletedIds: string[]) => void;
|
||||
onSignatureDeleted?: (deletedSignatures: ExtendedSystemSignature[]) => void;
|
||||
}) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
const [signatures, setSignatures, signaturesRef] = useRefState<ExtendedSystemSignature[]>([]);
|
||||
@@ -70,13 +73,20 @@ export const useSystemSignaturesData = ({
|
||||
? signaturesRef.current.filter(sig => !sig.pendingDeletion)
|
||||
: signaturesRef.current.filter(sig => !sig.pendingDeletion || !sig.pendingAddition);
|
||||
|
||||
const { added, updated, removed } = getActualSigs(currentNonPending, incomingSignatures, !lazyDeleteValue, true);
|
||||
const { added, updated, removed } = getActualSigs(currentNonPending, incomingSignatures, !lazyDeleteValue, false);
|
||||
|
||||
if (removed.length > 0) {
|
||||
await processRemovedSignatures(removed, added, updated);
|
||||
if (onSignatureDeleted) {
|
||||
const deletedIds = removed.map(sig => sig.eve_id);
|
||||
onSignatureDeleted(deletedIds);
|
||||
|
||||
// Only show pending deletions if:
|
||||
// 1. Lazy deletion is enabled AND
|
||||
// 2. Deletion timing is not immediate (> 0)
|
||||
if (onSignatureDeleted && lazyDeleteValue) {
|
||||
const timeoutMs = getDeletionTimeoutMs(settings);
|
||||
|
||||
if (timeoutMs > 0) {
|
||||
onSignatureDeleted(removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,11 +112,18 @@ export const useSystemSignaturesData = ({
|
||||
|
||||
const handleDeleteSelected = useCallback(async () => {
|
||||
if (!selectedSignatures.length) return;
|
||||
|
||||
const selectedIds = selectedSignatures.map(s => s.eve_id);
|
||||
const finalList = signatures.filter(s => !selectedIds.includes(s.eve_id));
|
||||
|
||||
// IMPORTANT: Send deletion to server BEFORE updating local state
|
||||
// Otherwise signaturesRef.current will be updated and getActualSigs won't detect removals
|
||||
await handleUpdateSignatures(finalList, false, true);
|
||||
|
||||
// Update local state after server call
|
||||
setSignatures(finalList);
|
||||
setSelectedSignatures([]);
|
||||
}, [handleUpdateSignatures, selectedSignatures, signatures]);
|
||||
}, [handleUpdateSignatures, selectedSignatures, signatures, setSignatures]);
|
||||
|
||||
const handleSelectAll = useCallback(() => {
|
||||
setSelectedSignatures(signatures);
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Commands, OutCommand } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import {
|
||||
AddHubCommand,
|
||||
LoadRoutesCommand,
|
||||
RoutesImperativeHandle,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
|
||||
import { useCallback, useRef } from 'react';
|
||||
@@ -13,24 +12,11 @@ export const WRoutesPublic = () => {
|
||||
const {
|
||||
outCommand,
|
||||
storedSettings: { settingsRoutes, settingsRoutesUpdate },
|
||||
data: { hubs, routes },
|
||||
data: { hubs, routes, loadingPublicRoutes },
|
||||
} = useMapRootState();
|
||||
|
||||
const ref = useRef<RoutesImperativeHandle>(null);
|
||||
|
||||
const loadRoutesCommand: LoadRoutesCommand = useCallback(
|
||||
async (systemId, routesSettings) => {
|
||||
outCommand({
|
||||
type: OutCommand.getRoutes,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
routes_settings: routesSettings,
|
||||
},
|
||||
});
|
||||
},
|
||||
[outCommand],
|
||||
);
|
||||
|
||||
const addHubCommand: AddHubCommand = useCallback(
|
||||
async systemId => {
|
||||
if (hubs.includes(systemId)) {
|
||||
@@ -72,10 +58,10 @@ export const WRoutesPublic = () => {
|
||||
ref={ref}
|
||||
title="Routes"
|
||||
data={settingsRoutes}
|
||||
loading={loadingPublicRoutes}
|
||||
update={settingsRoutesUpdate}
|
||||
hubs={hubs}
|
||||
routesList={routes}
|
||||
loadRoutesCommand={loadRoutesCommand}
|
||||
addHubCommand={addHubCommand}
|
||||
toggleHubCommand={toggleHubCommand}
|
||||
/>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { RoutesWidget } from '@/hooks/Mapper/components/mapInterface/widgets';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { useLoadRoutes } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/hooks';
|
||||
|
||||
export const WRoutesUser = () => {
|
||||
const {
|
||||
@@ -61,9 +62,17 @@ export const WRoutesUser = () => {
|
||||
[userHubs, outCommand],
|
||||
);
|
||||
|
||||
// INFO: User routes loading only if open widget with user routes
|
||||
const { loading, setLoading } = useLoadRoutes({
|
||||
data: settingsRoutes,
|
||||
hubs: userHubs,
|
||||
loadRoutesCommand,
|
||||
routesList: userRoutes,
|
||||
});
|
||||
|
||||
useMapEventListener(event => {
|
||||
if (event.name === Commands.userRoutes) {
|
||||
ref.current?.stopLoading();
|
||||
setLoading(false);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -76,7 +85,7 @@ export const WRoutesUser = () => {
|
||||
update={settingsRoutesUpdate}
|
||||
hubs={userHubs}
|
||||
routesList={userRoutes}
|
||||
loadRoutesCommand={loadRoutesCommand}
|
||||
loading={loading}
|
||||
addHubCommand={addHubCommand}
|
||||
toggleHubCommand={toggleHubCommand}
|
||||
isRestricted
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useCharacterActivityHandlers } from './hooks/useCharacterActivityHandle
|
||||
import { TrackingDialog } from '@/hooks/Mapper/components/mapRootContent/components/TrackingDialog';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { Commands } from '@/hooks/Mapper/types';
|
||||
import { PingsInterface } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
|
||||
export interface MapRootContentProps {}
|
||||
|
||||
@@ -62,17 +63,21 @@ export const MapRootContent = ({}: MapRootContentProps) => {
|
||||
onShowOnTheMap={handleShowOnTheMap}
|
||||
onShowMapSettings={handleShowMapSettings}
|
||||
onShowTrackingDialog={handleShowTrackingDialog}
|
||||
additionalContent={<PingsInterface hasLeftOffset />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none">
|
||||
<Topbar>
|
||||
<MapContextMenu
|
||||
onShowOnTheMap={handleShowOnTheMap}
|
||||
onShowMapSettings={handleShowMapSettings}
|
||||
onShowTrackingDialog={handleShowTrackingDialog}
|
||||
/>
|
||||
<div className="flex items-center ml-1">
|
||||
<PingsInterface />
|
||||
<MapContextMenu
|
||||
onShowOnTheMap={handleShowOnTheMap}
|
||||
onShowMapSettings={handleShowMapSettings}
|
||||
onShowTrackingDialog={handleShowTrackingDialog}
|
||||
/>
|
||||
</div>
|
||||
</Topbar>
|
||||
{mapInterface}
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,10 @@ export interface MapContextMenuProps {
|
||||
}
|
||||
|
||||
export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings, onShowTrackingDialog }: MapContextMenuProps) => {
|
||||
const { outCommand, setInterfaceSettings } = useMapRootState();
|
||||
const {
|
||||
outCommand,
|
||||
storedSettings: { setInterfaceSettings },
|
||||
} = useMapRootState();
|
||||
|
||||
const canTrackCharacters = useMapCheckPermissions([UserPermission.TRACK_CHARACTER]);
|
||||
|
||||
|
||||
@@ -4,13 +4,7 @@ import { useCallback, useRef, useState } from 'react';
|
||||
import { TabPanel, TabView } from 'primereact/tabview';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import {
|
||||
CONNECTIONS_CHECKBOXES_PROPS,
|
||||
SIGNATURES_CHECKBOXES_PROPS,
|
||||
SYSTEMS_CHECKBOXES_PROPS,
|
||||
THEME_SETTING,
|
||||
UI_CHECKBOXES_PROPS,
|
||||
} from './constants.ts';
|
||||
import { CONNECTIONS_CHECKBOXES_PROPS, SIGNATURES_CHECKBOXES_PROPS, SYSTEMS_CHECKBOXES_PROPS } from './constants.ts';
|
||||
import {
|
||||
MapSettingsProvider,
|
||||
useMapSettings,
|
||||
@@ -34,6 +28,8 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => {
|
||||
refVars.current = { outCommand, onHide, visible };
|
||||
|
||||
const handleShow = useCallback(async () => {
|
||||
// TODO: need fix it - add type?
|
||||
// @ts-ignore
|
||||
const { user_settings } = await refVars.current.outCommand({
|
||||
type: OutCommand.getUserSettings,
|
||||
data: null,
|
||||
@@ -88,17 +84,9 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => {
|
||||
{renderSettingsList(SIGNATURES_CHECKBOXES_PROPS)}
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
|
||||
{renderSettingsList(UI_CHECKBOXES_PROPS)}
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel header="Widgets" className="h-full" headerClassName={styles.verticalTabHeader}>
|
||||
<WidgetsSettings />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel header="Theme" headerClassName={styles.verticalTabHeader}>
|
||||
{renderSettingItem(THEME_SETTING)}
|
||||
</TabPanel>
|
||||
</TabView>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -88,8 +88,9 @@ export const MapSettingsProvider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
if (item.type === 'dropdown' && item.options) {
|
||||
return (
|
||||
<div key={item.prop.toString()} className="flex items-center gap-2 mt-2">
|
||||
<label className="text-sm">{item.label}:</label>
|
||||
<div key={item.prop.toString()} className="grid grid-cols-[auto_1fr_auto] items-center">
|
||||
<label className="text-[var(--gray-200)] text-[13px] select-none">{item.label}:</label>
|
||||
<div className="border-b-2 border-dotted border-[#3f3f3f] h-px mx-3" />
|
||||
<Dropdown
|
||||
className="text-sm"
|
||||
value={currentValue}
|
||||
|
||||
@@ -1,13 +1,34 @@
|
||||
import { COMMON_CHECKBOXES_PROPS } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts';
|
||||
import {
|
||||
MINI_MAP_PLACEMENT,
|
||||
PINGS_PLACEMENT,
|
||||
THEME_SETTING,
|
||||
UI_CHECKBOXES_PROPS,
|
||||
} from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts';
|
||||
import { useMapSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx';
|
||||
import { SettingsListItem } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const CommonSettings = () => {
|
||||
const { renderSettingItem } = useMapSettings();
|
||||
|
||||
const renderSettingsList = (list: SettingsListItem[]) => {
|
||||
return list.map(renderSettingItem);
|
||||
};
|
||||
const renderSettingsList = useCallback(
|
||||
(list: SettingsListItem[]) => {
|
||||
return list.map(renderSettingItem);
|
||||
},
|
||||
[renderSettingItem],
|
||||
);
|
||||
|
||||
return <div className="w-full h-full flex flex-col gap-1">{renderSettingsList(COMMON_CHECKBOXES_PROPS)}</div>;
|
||||
return (
|
||||
<div className="flex flex-col h-full gap-1">
|
||||
<div>
|
||||
<div className="w-full h-full flex flex-col gap-1">{renderSettingsList(UI_CHECKBOXES_PROPS)}</div>
|
||||
</div>
|
||||
|
||||
<div className="border-b-2 border-dotted border-stone-700/50 h-px my-3" />
|
||||
|
||||
<div className="grid grid-cols-[1fr_auto]">{renderSettingItem(MINI_MAP_PLACEMENT)}</div>
|
||||
<div className="grid grid-cols-[1fr_auto]">{renderSettingItem(PINGS_PLACEMENT)}</div>
|
||||
<div className="grid grid-cols-[1fr_auto]">{renderSettingItem(THEME_SETTING)}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,9 +10,9 @@ interface PrettySwitchboxProps {
|
||||
|
||||
export const PrettySwitchbox = ({ checked, setChecked, label }: PrettySwitchboxProps) => {
|
||||
return (
|
||||
<label className={styles.CheckboxContainer}>
|
||||
<span>{label}</span>
|
||||
<div />
|
||||
<label className="grid grid-cols-[auto_1fr_auto] items-center">
|
||||
<span className="text-[var(--gray-200)] text-[13px] select-none">{label}</span>
|
||||
<div className="border-b-2 border-dotted border-[#3f3f3f] h-px mx-3" />
|
||||
<div className={styles.smallInputSwitch}>
|
||||
<WdCheckbox size="m" label={''} value={checked} onChange={e => setChecked(e.checked ?? false)} />
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SettingsListItem, UserSettingsRemoteProps } from './types.ts';
|
||||
import { InterfaceStoredSettingsProps } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { AvailableThemes, MiniMapPlacement, PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export const DEFAULT_REMOTE_SETTINGS = {
|
||||
[UserSettingsRemoteProps.link_signature_on_splash]: false,
|
||||
@@ -14,13 +14,13 @@ export const UserSettingsRemoteList = [
|
||||
UserSettingsRemoteProps.delete_connection_with_sigs,
|
||||
];
|
||||
|
||||
export const COMMON_CHECKBOXES_PROPS: SettingsListItem[] = [
|
||||
{
|
||||
prop: InterfaceStoredSettingsProps.isShowMinimap,
|
||||
label: 'Show Minimap',
|
||||
type: 'checkbox',
|
||||
},
|
||||
];
|
||||
// export const COMMON_CHECKBOXES_PROPS: SettingsListItem[] = [
|
||||
// // {
|
||||
// // prop: InterfaceStoredSettingsProps.isShowMinimap,
|
||||
// // label: 'Show Minimap',
|
||||
// // type: 'checkbox',
|
||||
// // },
|
||||
// ];
|
||||
|
||||
export const SYSTEMS_CHECKBOXES_PROPS: SettingsListItem[] = [
|
||||
{
|
||||
@@ -90,3 +90,32 @@ export const THEME_SETTING: SettingsListItem = {
|
||||
type: 'dropdown',
|
||||
options: THEME_OPTIONS,
|
||||
};
|
||||
|
||||
export const MINI_MAP_PLACEMENT_OPTIONS = [
|
||||
{ label: 'Right Bottom', value: MiniMapPlacement.rightBottom },
|
||||
{ label: 'Right Top', value: MiniMapPlacement.rightTop },
|
||||
{ label: 'Left Top', value: MiniMapPlacement.leftTop },
|
||||
{ label: 'Left Bottom', value: MiniMapPlacement.leftBottom },
|
||||
{ label: 'Hide', value: MiniMapPlacement.hide },
|
||||
];
|
||||
|
||||
export const MINI_MAP_PLACEMENT: SettingsListItem = {
|
||||
prop: 'minimapPlacement',
|
||||
label: 'Minimap Placement',
|
||||
type: 'dropdown',
|
||||
options: MINI_MAP_PLACEMENT_OPTIONS,
|
||||
};
|
||||
|
||||
export const PINGS_PLACEMENT_OPTIONS = [
|
||||
{ label: 'Right Top', value: PingsPlacement.rightTop },
|
||||
{ label: 'Left Top', value: PingsPlacement.leftTop },
|
||||
{ label: 'Left Bottom', value: PingsPlacement.leftBottom },
|
||||
{ label: 'Right Bottom', value: PingsPlacement.rightBottom },
|
||||
];
|
||||
|
||||
export const PINGS_PLACEMENT: SettingsListItem = {
|
||||
prop: 'pingsPlacement',
|
||||
label: 'Pings Placement',
|
||||
type: 'dropdown',
|
||||
options: PINGS_PLACEMENT_OPTIONS,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InterfaceStoredSettings } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { InterfaceStoredSettings } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export enum UserSettingsRemoteProps {
|
||||
link_signature_on_splash = 'link_signature_on_splash',
|
||||
|
||||
@@ -35,7 +35,7 @@ const itemTemplate = (item: CharacterTypeRaw & WithIsOwnCharacter, options: Virt
|
||||
})}
|
||||
style={{ height: options.props.itemSize + 'px' }}
|
||||
>
|
||||
<CharacterCard showCorporationLogo showAllyLogo showSystem showTicker {...item} />
|
||||
<CharacterCard showCorporationLogo showAllyLogo showSystem showTicker showShip {...item} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import classes from './RightBar.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback } from 'react';
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
|
||||
@@ -12,24 +12,21 @@ interface RightBarProps {
|
||||
onShowOnTheMap?: () => void;
|
||||
onShowMapSettings?: () => void;
|
||||
onShowTrackingDialog?: () => void;
|
||||
additionalContent?: ReactNode;
|
||||
}
|
||||
|
||||
export const RightBar = ({ onShowOnTheMap, onShowMapSettings, onShowTrackingDialog }: RightBarProps) => {
|
||||
export const RightBar = ({
|
||||
onShowOnTheMap,
|
||||
onShowMapSettings,
|
||||
onShowTrackingDialog,
|
||||
additionalContent,
|
||||
}: RightBarProps) => {
|
||||
const {
|
||||
storedSettings: { interfaceSettings, setInterfaceSettings },
|
||||
} = useMapRootState();
|
||||
|
||||
const canTrackCharacters = useMapCheckPermissions([UserPermission.TRACK_CHARACTER]);
|
||||
|
||||
const isShowMinimap = interfaceSettings.isShowMinimap === undefined ? true : interfaceSettings.isShowMinimap;
|
||||
|
||||
const toggleMinimap = useCallback(() => {
|
||||
setInterfaceSettings(x => ({
|
||||
...x,
|
||||
isShowMinimap: !x.isShowMinimap,
|
||||
}));
|
||||
}, [setInterfaceSettings]);
|
||||
|
||||
const toggleKSpace = useCallback(() => {
|
||||
setInterfaceSettings(x => ({
|
||||
...x,
|
||||
@@ -78,6 +75,7 @@ export const RightBar = ({ onShowOnTheMap, onShowMapSettings, onShowTrackingDial
|
||||
</WdTooltipWrapper>
|
||||
</>
|
||||
)}
|
||||
{additionalContent}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center mb-2 gap-1">
|
||||
@@ -106,16 +104,6 @@ export const RightBar = ({ onShowOnTheMap, onShowMapSettings, onShowTrackingDial
|
||||
</button>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
<WdTooltipWrapper content={isShowMinimap ? 'Hide minimap' : 'Show minimap'} position={TooltipPosition.left}>
|
||||
<button
|
||||
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
|
||||
type="button"
|
||||
onClick={toggleMinimap}
|
||||
>
|
||||
<i className={isShowMinimap ? 'pi pi-eye' : 'pi pi-eye-slash'}></i>
|
||||
</button>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
<WdTooltipWrapper content="Switch to menu" position={TooltipPosition.left}>
|
||||
<button
|
||||
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
|
||||
|
||||
@@ -67,8 +67,8 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
|
||||
|
||||
if (values.type) {
|
||||
const whShipSize = getWhSize(wormholes, values.type);
|
||||
if (whShipSize) {
|
||||
outCommand({
|
||||
if (whShipSize !== undefined && whShipSize !== null) {
|
||||
await outCommand({
|
||||
type: OutCommand.updateConnectionShipSizeType,
|
||||
data: {
|
||||
source: systemId,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Map, MAP_ROOT_ID } from '@/hooks/Mapper/components/map/Map.tsx';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OnMapAddSystemCallback, OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
SystemLinkSignatureDialog,
|
||||
SystemSettingsDialog,
|
||||
} from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import classes from './MapWrapper.module.scss';
|
||||
import { Connections } from '@/hooks/Mapper/components/mapRootContent/components/Connections';
|
||||
import { ContextMenuSystemMultiple, useContextMenuSystemMultipleHandlers } from '../contexts/ContextMenuSystemMultiple';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
@@ -27,26 +26,40 @@ import {
|
||||
SearchOnSubmitCallback,
|
||||
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
|
||||
import { useHotkey } from '../../hooks/useHotkey';
|
||||
import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/constants.ts';
|
||||
import { PingType } from '@/hooks/Mapper/types/ping.ts';
|
||||
import { SystemPingDialog } from '@/hooks/Mapper/components/mapInterface/components/SystemPingDialog';
|
||||
import { MiniMapPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { MINIMAP_PLACEMENT_MAP } from '@/hooks/Mapper/constants.ts';
|
||||
import type { PanelPosition } from '@reactflow/core';
|
||||
import { MINI_MAP_PLACEMENT_OFFSETS } from './constants.ts';
|
||||
|
||||
// TODO: INFO - this component needs for abstract work with Map instance
|
||||
export const MapWrapper = () => {
|
||||
const {
|
||||
update,
|
||||
outCommand,
|
||||
data: { selectedConnections, selectedSystems, hubs, userHubs, systems, linkSignatureToSystem, systemSignatures },
|
||||
data: {
|
||||
pings,
|
||||
selectedConnections,
|
||||
selectedSystems,
|
||||
hubs,
|
||||
userHubs,
|
||||
systems,
|
||||
linkSignatureToSystem,
|
||||
systemSignatures,
|
||||
},
|
||||
storedSettings: { interfaceSettings },
|
||||
} = useMapRootState();
|
||||
|
||||
const {
|
||||
isShowMenu,
|
||||
isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
|
||||
isShowKSpace,
|
||||
isThickConnections,
|
||||
isShowBackgroundPattern,
|
||||
isShowUnsplashedSignatures,
|
||||
isSoftBackground,
|
||||
theme,
|
||||
minimapPlacement,
|
||||
} = interfaceSettings;
|
||||
|
||||
const { deleteSystems } = useDeleteSystems();
|
||||
@@ -58,6 +71,7 @@ export const MapWrapper = () => {
|
||||
const { handleSystemMultipleContext, ...systemMultipleCtxProps } = useContextMenuSystemMultipleHandlers();
|
||||
|
||||
const [openSettings, setOpenSettings] = useState<string | null>(null);
|
||||
const [openPing, setOpenPing] = useState<{ type: PingType; solar_system_id: string } | null>(null);
|
||||
const [openCustomLabel, setOpenCustomLabel] = useState<string | null>(null);
|
||||
const [openAddSystem, setOpenAddSystem] = useState<XYPosition | null>(null);
|
||||
const [selectedConnection, setSelectedConnection] = useState<SolarSystemConnection | null>(null);
|
||||
@@ -99,6 +113,8 @@ export const MapWrapper = () => {
|
||||
event => {
|
||||
switch (event.type) {
|
||||
case OutCommand.openSettings:
|
||||
// TODO - need fix it
|
||||
// @ts-ignore
|
||||
setOpenSettings(event.data.system_id);
|
||||
break;
|
||||
default:
|
||||
@@ -111,6 +127,7 @@ export const MapWrapper = () => {
|
||||
);
|
||||
|
||||
const handleSystemContextMenu = useCallback(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(ev: any, systemId: string) => {
|
||||
const { selectedSystems, systems } = ref.current;
|
||||
if (selectedSystems.length > 1) {
|
||||
@@ -130,11 +147,13 @@ export const MapWrapper = () => {
|
||||
const handleDeleteSelected = useCallback(() => {
|
||||
const restDel = getNodes()
|
||||
.filter(x => x.selected && !x.data.locked)
|
||||
.filter(x => !pings.some(p => x.data.id === p.solar_system_id))
|
||||
.map(x => x.data.id);
|
||||
|
||||
if (restDel.length > 0) {
|
||||
ref.current.deleteSystems(restDel);
|
||||
}
|
||||
}, [getNodes]);
|
||||
}, [getNodes, pings]);
|
||||
|
||||
const onAddSystem: OnMapAddSystemCallback = useCallback(({ coordinates }) => {
|
||||
setOpenAddSystem(coordinates);
|
||||
@@ -158,6 +177,27 @@ export const MapWrapper = () => {
|
||||
[openAddSystem, outCommand],
|
||||
);
|
||||
|
||||
const handleOpenSettings = useCallback(() => {
|
||||
ref.current.systemContextProps.systemId && setOpenSettings(ref.current.systemContextProps.systemId);
|
||||
}, []);
|
||||
|
||||
const handleTogglePing = useCallback(async (type: PingType, solar_system_id: string, hasPing: boolean) => {
|
||||
if (hasPing) {
|
||||
await outCommand({
|
||||
type: OutCommand.cancelPing,
|
||||
data: { type, solar_system_id: solar_system_id },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setOpenPing({ type, solar_system_id });
|
||||
}, []);
|
||||
|
||||
const handleCustomLabelDialog = useCallback(() => {
|
||||
const { systemContextProps } = ref.current;
|
||||
systemContextProps.systemId && setOpenCustomLabel(systemContextProps.systemId);
|
||||
}, []);
|
||||
|
||||
useHotkey(false, ['Delete'], (event: KeyboardEvent) => {
|
||||
const targetWindow = (event.target as HTMLHtmlElement)?.closest(`[data-window-id="${MAP_ROOT_ID}"]`);
|
||||
|
||||
@@ -179,6 +219,22 @@ export const MapWrapper = () => {
|
||||
outCommand({ type: OutCommand.loadSignatures, data: {} });
|
||||
}, [isShowUnsplashedSignatures, systems]);
|
||||
|
||||
const { showMinimap, minimapPosition, minimapClasses } = useMemo(() => {
|
||||
const rawPlacement = minimapPlacement == null ? MiniMapPlacement.rightBottom : minimapPlacement;
|
||||
|
||||
if (rawPlacement === MiniMapPlacement.hide) {
|
||||
return { minimapPosition: undefined, showMinimap: false, minimapClasses: '' };
|
||||
}
|
||||
|
||||
const mmClasses = MINI_MAP_PLACEMENT_OFFSETS[rawPlacement];
|
||||
|
||||
return {
|
||||
minimapPosition: MINIMAP_PLACEMENT_MAP[rawPlacement] as PanelPosition,
|
||||
showMinimap: true,
|
||||
minimapClasses: isShowMenu ? mmClasses.default : mmClasses.withLeftMenu,
|
||||
};
|
||||
}, [minimapPlacement, isShowMenu]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Map
|
||||
@@ -188,19 +244,29 @@ export const MapWrapper = () => {
|
||||
onConnectionInfoClick={handleConnectionDbClick}
|
||||
onSystemContextMenu={handleSystemContextMenu}
|
||||
onSelectionContextMenu={handleSystemMultipleContext}
|
||||
minimapClasses={!isShowMenu ? classes.MiniMap : undefined}
|
||||
isShowMinimap={isShowMinimap}
|
||||
minimapClasses={minimapClasses}
|
||||
isShowMinimap={showMinimap}
|
||||
showKSpaceBG={isShowKSpace}
|
||||
isThickConnections={isThickConnections}
|
||||
isShowBackgroundPattern={isShowBackgroundPattern}
|
||||
isSoftBackground={isSoftBackground}
|
||||
theme={theme}
|
||||
pings={pings}
|
||||
onAddSystem={onAddSystem}
|
||||
minimapPlacement={minimapPosition}
|
||||
/>
|
||||
|
||||
{openSettings != null && (
|
||||
<SystemSettingsDialog systemId={openSettings} visible setVisible={() => setOpenSettings(null)} />
|
||||
)}
|
||||
{openPing != null && (
|
||||
<SystemPingDialog
|
||||
systemId={openPing.solar_system_id}
|
||||
type={openPing.type}
|
||||
visible
|
||||
setVisible={() => setOpenPing(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{openCustomLabel != null && (
|
||||
<SystemCustomLabelDialog systemId={openCustomLabel} visible setVisible={() => setOpenCustomLabel(null)} />
|
||||
@@ -223,13 +289,9 @@ export const MapWrapper = () => {
|
||||
hubs={hubs}
|
||||
userHubs={userHubs}
|
||||
{...systemContextProps}
|
||||
onOpenSettings={() => {
|
||||
systemContextProps.systemId && setOpenSettings(systemContextProps.systemId);
|
||||
}}
|
||||
onCustomLabelDialog={() => {
|
||||
const { systemContextProps } = ref.current;
|
||||
systemContextProps.systemId && setOpenCustomLabel(systemContextProps.systemId);
|
||||
}}
|
||||
onOpenSettings={handleOpenSettings}
|
||||
onTogglePing={handleTogglePing}
|
||||
onCustomLabelDialog={handleCustomLabelDialog}
|
||||
/>
|
||||
|
||||
<ContextMenuSystemMultiple {...systemMultipleCtxProps} />
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { MiniMapPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export const MINI_MAP_PLACEMENT_OFFSETS = {
|
||||
[MiniMapPlacement.rightTop]: { default: '!top-[48px]', withLeftMenu: '!top-[48px] !right-[58px]' },
|
||||
[MiniMapPlacement.rightBottom]: { default: '!bottom-[0px]', withLeftMenu: '!bottom-[0px] !right-[58px]' },
|
||||
[MiniMapPlacement.leftTop]: { default: '!top-[48px] !left-[56px]', withLeftMenu: '!top-[48px] !left-[56px]' },
|
||||
[MiniMapPlacement.leftBottom]: { default: '!left-[56px] !bottom-[0px]', withLeftMenu: '!left-[56px] !bottom-[0px]' },
|
||||
};
|
||||
@@ -1,7 +1,8 @@
|
||||
import { MutableRefObject, useCallback, useEffect, useRef } from 'react';
|
||||
import { Command, Commands, MapHandlers } from '@/hooks/Mapper/types';
|
||||
import { MapEvent } from '@/hooks/Mapper/events';
|
||||
// import { useThrottle } from '@/hooks/Mapper/hooks';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { Command, Commands, MapHandlers } from '@/hooks/Mapper/types';
|
||||
import { MutableRefObject, useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
export const useCommonMapEventProcessor = () => {
|
||||
const mapRef = useRef<MapHandlers>() as MutableRefObject<MapHandlers>;
|
||||
@@ -11,13 +12,14 @@ export const useCommonMapEventProcessor = () => {
|
||||
|
||||
const refQueue = useRef<MapEvent<Command>[]>([]);
|
||||
|
||||
// const ref = useRef({})
|
||||
|
||||
const runCommand = useCallback(({ name, data }: MapEvent<Command>) => {
|
||||
switch (name) {
|
||||
case Commands.addSystems:
|
||||
case Commands.removeSystems:
|
||||
// case Commands.updateSystems:
|
||||
// case Commands.addConnections:
|
||||
// case Commands.removeConnections:
|
||||
// case Commands.updateConnection:
|
||||
refQueue.current.push({ name, data });
|
||||
return;
|
||||
}
|
||||
@@ -26,9 +28,17 @@ export const useCommonMapEventProcessor = () => {
|
||||
mapRef.current?.command(name, data);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refQueue.current.forEach(x => mapRef.current?.command(x.name, x.data));
|
||||
const processQueue = useCallback(() => {
|
||||
const commands = [...refQueue.current];
|
||||
refQueue.current = [];
|
||||
commands.forEach(x => mapRef.current?.command(x.name, x.data));
|
||||
}, []);
|
||||
|
||||
// const throttledProcessQueue = useThrottle(processQueue, 200);
|
||||
|
||||
useEffect(() => {
|
||||
// throttledProcessQueue();
|
||||
processQueue();
|
||||
}, [systems]);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Characters } from '../characters/Characters';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMemo } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { sortOnlineFunc } from '@/hooks/Mapper/components/hooks/useGetOwnOnlineCharacters.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||
import clsx from 'clsx';
|
||||
import { useMemo } from 'react';
|
||||
import { Characters } from '../characters/Characters';
|
||||
|
||||
const Topbar = ({ children }: WithChildren) => {
|
||||
const {
|
||||
@@ -24,7 +24,10 @@ const Topbar = ({ children }: WithChildren) => {
|
||||
>
|
||||
<span className="flex-1"></span>
|
||||
<span className="mr-2"></span>
|
||||
<Characters data={charsToShow} />
|
||||
<div className="flex gap-1 items-center">
|
||||
<Characters data={charsToShow} />
|
||||
</div>
|
||||
|
||||
{children}
|
||||
</nav>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import { useCallback } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { SystemView } from '@/hooks/Mapper/components/ui-kit/SystemView';
|
||||
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
|
||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
import {
|
||||
TooltipPosition,
|
||||
WdEveEntityPortrait,
|
||||
@@ -11,19 +5,30 @@ import {
|
||||
WdEveEntityPortraitType,
|
||||
WdTooltipWrapper,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { SystemView } from '@/hooks/Mapper/components/ui-kit/SystemView';
|
||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
import { isDocked } from '@/hooks/Mapper/helpers/isDocked.ts';
|
||||
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
|
||||
import { WithClassName } from '@/hooks/Mapper/types/common.ts';
|
||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback } from 'react';
|
||||
import classes from './CharacterCard.module.scss';
|
||||
|
||||
type CharacterCardProps = {
|
||||
export type CharacterCardProps = {
|
||||
compact?: boolean;
|
||||
showSystem?: boolean;
|
||||
showTicker?: boolean;
|
||||
showShip?: boolean;
|
||||
showShipName?: boolean;
|
||||
useSystemsCache?: boolean;
|
||||
showCorporationLogo?: boolean;
|
||||
showAllyLogo?: boolean;
|
||||
} & CharacterTypeRaw &
|
||||
WithIsOwnCharacter;
|
||||
simpleMode?: boolean;
|
||||
} & WithIsOwnCharacter &
|
||||
WithClassName;
|
||||
|
||||
type CharacterCardInnerProps = CharacterCardProps & CharacterTypeRaw;
|
||||
|
||||
const SHIP_NAME_RX = /u'|'/g;
|
||||
export const getShipName = (name: string) => {
|
||||
@@ -34,16 +39,19 @@ export const getShipName = (name: string) => {
|
||||
};
|
||||
|
||||
export const CharacterCard = ({
|
||||
simpleMode,
|
||||
compact = false,
|
||||
isOwn,
|
||||
showSystem,
|
||||
showShip,
|
||||
showShipName,
|
||||
showCorporationLogo,
|
||||
showAllyLogo,
|
||||
showTicker,
|
||||
useSystemsCache,
|
||||
className,
|
||||
...char
|
||||
}: CharacterCardProps) => {
|
||||
}: CharacterCardInnerProps) => {
|
||||
const handleSelect = useCallback(() => {
|
||||
emitMapEvent({
|
||||
name: Commands.centerSystem,
|
||||
@@ -56,9 +64,55 @@ export const CharacterCard = ({
|
||||
const shipType = char.ship?.ship_type_info?.name;
|
||||
const locationShown = showSystem && char.location?.solar_system_id;
|
||||
|
||||
// INFO: Simple mode show only name and icon of ally/corp. By default it compact view
|
||||
if (simpleMode) {
|
||||
return (
|
||||
<div className={clsx('text-xs box-border', className)} onClick={handleSelect}>
|
||||
<div className="flex items-center gap-1 relative">
|
||||
<WdEveEntityPortrait eveId={char.eve_id} size={WdEveEntityPortraitSize.w18} />
|
||||
|
||||
{!char.alliance_id && (
|
||||
<WdTooltipWrapper position={TooltipPosition.top} content={char.corporation_name}>
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.corporation}
|
||||
eveId={char.corporation_id?.toString()}
|
||||
size={WdEveEntityPortraitSize.w18}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
|
||||
{char.alliance_id && (
|
||||
<WdTooltipWrapper position={TooltipPosition.top} content={char.alliance_name}>
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.alliance}
|
||||
eveId={char.alliance_id?.toString()}
|
||||
size={WdEveEntityPortraitSize.w18}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
|
||||
<div className="flex overflow-hidden text-left">
|
||||
<div className="flex">
|
||||
<span
|
||||
className={clsx(
|
||||
'overflow-hidden text-ellipsis whitespace-nowrap',
|
||||
isOwn ? 'text-orange-400' : 'text-gray-200',
|
||||
)}
|
||||
title={char.name}
|
||||
>
|
||||
{char.name}
|
||||
</span>
|
||||
{showTicker && <span className="flex-shrink-0 text-gray-400 ml-1">[{tickerText}]</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<div className="text-xs box-border w-full" onClick={handleSelect}>
|
||||
<div className={clsx('text-xs box-border w-full', className)} onClick={handleSelect}>
|
||||
<div className="w-full flex items-center gap-1 relative">
|
||||
<WdEveEntityPortrait eveId={char.eve_id} size={WdEveEntityPortraitSize.w18} />
|
||||
|
||||
@@ -66,7 +120,7 @@ export const CharacterCard = ({
|
||||
<WdTooltipWrapper position={TooltipPosition.top} content={char.corporation_name}>
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.corporation}
|
||||
eveId={char.corporation_id.toString()}
|
||||
eveId={char.corporation_id?.toString()}
|
||||
size={WdEveEntityPortraitSize.w18}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
@@ -76,7 +130,7 @@ export const CharacterCard = ({
|
||||
<WdTooltipWrapper position={TooltipPosition.top} content={char.alliance_name}>
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.alliance}
|
||||
eveId={char.alliance_id.toString()}
|
||||
eveId={char.alliance_id?.toString()}
|
||||
size={WdEveEntityPortraitSize.w18}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
@@ -98,7 +152,7 @@ export const CharacterCard = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{shipType && (
|
||||
{showShip && shipType && (
|
||||
<>
|
||||
{!showShipName && (
|
||||
<div
|
||||
@@ -123,10 +177,10 @@ export const CharacterCard = ({
|
||||
</div>
|
||||
)}
|
||||
{char.ship && (
|
||||
<WdTooltipWrapper position={TooltipPosition.top} content={char.ship.ship_type_info.name}>
|
||||
<WdTooltipWrapper position={TooltipPosition.top} content={char.ship.ship_type_info?.name}>
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.ship}
|
||||
eveId={char.ship.ship_type_id.toString()}
|
||||
eveId={char.ship.ship_type_id?.toString()}
|
||||
size={WdEveEntityPortraitSize.w18}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
@@ -148,7 +202,7 @@ export const CharacterCard = ({
|
||||
<WdTooltipWrapper position={TooltipPosition.top} content={char.corporation_name}>
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.corporation}
|
||||
eveId={char.corporation_id.toString()}
|
||||
eveId={char.corporation_id?.toString()}
|
||||
size={WdEveEntityPortraitSize.w33}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
@@ -158,7 +212,7 @@ export const CharacterCard = ({
|
||||
<WdTooltipWrapper position={TooltipPosition.top} content={char.alliance_name}>
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.alliance}
|
||||
eveId={char.alliance_id.toString()}
|
||||
eveId={char.alliance_id?.toString()}
|
||||
size={WdEveEntityPortraitSize.w33}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
@@ -192,7 +246,7 @@ export const CharacterCard = ({
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
{shipType && (
|
||||
{showShip && shipType && (
|
||||
<>
|
||||
<div className="flex flex-col flex-shrink-0 items-end self-start">
|
||||
<div
|
||||
@@ -212,7 +266,7 @@ export const CharacterCard = ({
|
||||
{char.ship && (
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.ship}
|
||||
eveId={char.ship.ship_type_id.toString()}
|
||||
eveId={char.ship.ship_type_id?.toString()}
|
||||
size={WdEveEntityPortraitSize.w33}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { CharacterCard, CharacterCardProps } from '@/hooks/Mapper/components/ui-kit/CharacterCard';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
type CharacterCardByIdProps = {
|
||||
characterId: string;
|
||||
} & Omit<CharacterCardProps, 'isOwn'>;
|
||||
|
||||
export const CharacterCardById = ({ characterId, ...props }: CharacterCardByIdProps) => {
|
||||
const {
|
||||
data: { characters },
|
||||
} = useMapRootState();
|
||||
|
||||
const charInfo = useMemo(() => {
|
||||
return characters.find(x => x.eve_id === characterId);
|
||||
}, [characterId, characters]);
|
||||
|
||||
if (!charInfo) {
|
||||
return 'No character found.';
|
||||
}
|
||||
|
||||
return <CharacterCard isOwn={false} {...charInfo} {...props} />;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './CharacterCardById';
|
||||
@@ -0,0 +1,11 @@
|
||||
import Markdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkBreaks from 'remark-breaks';
|
||||
|
||||
const REMARK_PLUGINS = [remarkGfm, remarkBreaks];
|
||||
|
||||
type MarkdownTextViewerProps = { children: string };
|
||||
|
||||
export const MarkdownTextViewer = ({ children }: MarkdownTextViewerProps) => {
|
||||
return <Markdown remarkPlugins={REMARK_PLUGINS}>{children}</Markdown>;
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit/WdTooltip';
|
||||
import clsx from 'clsx';
|
||||
|
||||
type MenuItemWithInfoProps = { infoTitle: ReactNode; infoClass?: string } & WithChildren;
|
||||
export const MenuItemWithInfo = ({ children, infoClass, infoTitle }: MenuItemWithInfoProps) => {
|
||||
return (
|
||||
<div className="flex justify-between w-full h-full items-center">
|
||||
{children}
|
||||
<WdTooltipWrapper
|
||||
content={infoTitle}
|
||||
position={TooltipPosition.top}
|
||||
className="!opacity-100 !pointer-events-auto"
|
||||
>
|
||||
<div className={clsx('pi text-orange-400', infoClass)} />
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
import { WithClassName } from '@/hooks/Mapper/types/common.ts';
|
||||
import { SystemViewStandalone } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { SystemViewStandalone, SystemViewStandaloneProps } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
||||
import { useMemo } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
@@ -8,18 +7,11 @@ import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||
export type SystemViewProps = {
|
||||
systemId: string;
|
||||
systemInfo?: SolarSystemStaticInfoRaw;
|
||||
hideRegion?: boolean;
|
||||
useSystemsCache?: boolean;
|
||||
showCustomName?: boolean;
|
||||
} & WithClassName;
|
||||
} & Pick<SystemViewStandaloneProps, 'className' | 'compact' | 'hideRegion'>;
|
||||
|
||||
export const SystemView = ({
|
||||
systemId,
|
||||
systemInfo: customSystemInfo,
|
||||
hideRegion,
|
||||
className,
|
||||
showCustomName,
|
||||
}: SystemViewProps) => {
|
||||
export const SystemView = ({ systemId, systemInfo: customSystemInfo, showCustomName, ...rest }: SystemViewProps) => {
|
||||
const memSystems = useMemo(() => [systemId], [systemId]);
|
||||
const { systems, loading } = useLoadSystemStatic({ systems: memSystems });
|
||||
|
||||
@@ -47,13 +39,8 @@ export const SystemView = ({
|
||||
}
|
||||
|
||||
if (!mapSystemInfo) {
|
||||
return <SystemViewStandalone hideRegion={hideRegion} className={className} {...systemInfo} />;
|
||||
return <SystemViewStandalone {...rest} {...systemInfo} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SystemViewStandalone hideRegion={hideRegion} className={className} {...systemInfo} />
|
||||
<span>{systemInfo.solar_system_name}</span>
|
||||
</div>
|
||||
);
|
||||
return <SystemViewStandalone customName={mapSystemInfo.name ?? undefined} {...rest} {...systemInfo} />;
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { WithClassName } from '@/hooks/Mapper/types/common.ts';
|
||||
|
||||
interface TimeAgoProps {
|
||||
timestamp: string; // Теперь тип string, так как приходит ISO 8601 строка
|
||||
}
|
||||
|
||||
export const TimeAgo = ({ timestamp }: TimeAgoProps) => {
|
||||
export const TimeAgo = ({ timestamp, className }: TimeAgoProps & WithClassName) => {
|
||||
const [timeAgo, setTimeAgo] = useState<string>('');
|
||||
const timeoutIdRef = useRef<number | null>(null);
|
||||
|
||||
@@ -64,5 +65,5 @@ export const TimeAgo = ({ timestamp }: TimeAgoProps) => {
|
||||
return `${days} days ago`;
|
||||
};
|
||||
|
||||
return <span>{timeAgo}</span>;
|
||||
return <span className={className}>{timeAgo}</span>;
|
||||
};
|
||||
|
||||
@@ -11,12 +11,14 @@ export enum WdImageSize {
|
||||
large = 'large',
|
||||
}
|
||||
|
||||
export type WdImgButtonTooltip = Pick<WdTooltipWrapperProps, 'content' | 'position' | 'offset' | 'className'>;
|
||||
|
||||
export type WdImgButtonProps = {
|
||||
onClick?(e: MouseEvent): void;
|
||||
source?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
tooltip?: Pick<WdTooltipWrapperProps, 'content' | 'position' | 'offset' | 'className'>;
|
||||
tooltip?: WdImgButtonTooltip;
|
||||
textSize?: WdImageSize;
|
||||
} & WithClassName &
|
||||
HTMLProps<HTMLDivElement>;
|
||||
|
||||
16
assets/js/hooks/Mapper/components/ui-kit/WdMenuItem.tsx
Normal file
16
assets/js/hooks/Mapper/components/ui-kit/WdMenuItem.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||
import clsx from 'clsx';
|
||||
|
||||
type WdMenuItemProps = { icon?: string; disabled?: boolean } & WithChildren;
|
||||
export const WdMenuItem = ({ children, icon, disabled }: WdMenuItemProps) => {
|
||||
return (
|
||||
<a
|
||||
className={clsx('flex gap-[6px] w-full h-full items-center px-[12px] !py-0 ml-[-2px]', 'p-menuitem-link', {
|
||||
'p-disabled': disabled,
|
||||
})}
|
||||
>
|
||||
{icon && <div className={clsx('min-w-[20px]', icon)}></div>}
|
||||
<div className="w-full">{children}</div>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './CharacterCard';
|
||||
export * from './CharacterCardById';
|
||||
export * from './InfoDrawer';
|
||||
export * from './FixedTooltip';
|
||||
export * from './LayoutEventBlocker';
|
||||
@@ -17,3 +18,6 @@ export * from './WdRadioButton';
|
||||
export * from './WdEveEntityPortrait';
|
||||
export * from './WdTransition';
|
||||
export * from './LoadingWrapper';
|
||||
export * from './WdMenuItem';
|
||||
export * from './MenuItemWithInfo';
|
||||
export * from './MarkdownTextViewer.tsx';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export enum SESSION_KEY {
|
||||
viewPort = 'viewPort',
|
||||
windows = 'windows',
|
||||
@@ -139,3 +141,10 @@ export const K162_TYPES_MAP: { [key: string]: K162Type } = K162_TYPES.reduce(
|
||||
(acc, x) => ({ ...acc, [x.value]: x }),
|
||||
{},
|
||||
);
|
||||
|
||||
export const MINIMAP_PLACEMENT_MAP = {
|
||||
[PingsPlacement.rightTop]: 'top-right',
|
||||
[PingsPlacement.leftTop]: 'top-left',
|
||||
[PingsPlacement.rightBottom]: 'bottom-right',
|
||||
[PingsPlacement.leftBottom]: 'bottom-left',
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './usePageVisibility';
|
||||
export * from './useActualizeSettings';
|
||||
export * from './useClipboard';
|
||||
export * from './useHotkey';
|
||||
export * from './usePageVisibility';
|
||||
export * from './useSkipContextMenu';
|
||||
export * from './useActualizeSettings';
|
||||
export * from './useThrottle';
|
||||
|
||||
13
assets/js/hooks/Mapper/hooks/useThrottle.ts
Normal file
13
assets/js/hooks/Mapper/hooks/useThrottle.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
export const useThrottle = (callback: any, limit: number) => {
|
||||
const lastCallRef = useRef(0);
|
||||
const throttledCallback = useCallback(() => {
|
||||
const now = Date.now();
|
||||
if (now - lastCallRef.current >= limit) {
|
||||
lastCallRef.current = now;
|
||||
callback();
|
||||
}
|
||||
}, [callback, limit]);
|
||||
return throttledCallback;
|
||||
};
|
||||
@@ -22,6 +22,7 @@ import { DetailedKill } from '../types/kills';
|
||||
import { InterfaceStoredSettings, RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { DEFAULT_ROUTES_SETTINGS, STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/constants.ts';
|
||||
import { useMapUserSettings } from '@/hooks/Mapper/mapRootProvider/hooks/useMapUserSettings.ts';
|
||||
import { useGlobalHooks } from '@/hooks/Mapper/mapRootProvider/hooks/useGlobalHooks.ts';
|
||||
|
||||
export type MapRootData = MapUnionTypes & {
|
||||
selectedSystems: string[];
|
||||
@@ -34,6 +35,7 @@ export type MapRootData = MapUnionTypes & {
|
||||
loading?: boolean;
|
||||
};
|
||||
trackingCharactersData: TrackingCharacter[];
|
||||
loadingPublicRoutes: boolean;
|
||||
};
|
||||
|
||||
const INITIAL_DATA: MapRootData = {
|
||||
@@ -66,11 +68,12 @@ const INITIAL_DATA: MapRootData = {
|
||||
linkSignatureToSystem: null,
|
||||
mainCharacterEveId: null,
|
||||
followingCharacterEveId: null,
|
||||
pings: [],
|
||||
loadingPublicRoutes: false,
|
||||
};
|
||||
|
||||
export enum InterfaceStoredSettingsProps {
|
||||
isShowMenu = 'isShowMenu',
|
||||
isShowMinimap = 'isShowMinimap',
|
||||
isShowKSpace = 'isShowKSpace',
|
||||
isThickConnections = 'isThickConnections',
|
||||
isShowUnsplashedSignatures = 'isShowUnsplashedSignatures',
|
||||
@@ -143,6 +146,7 @@ type MapRootProviderProps = {
|
||||
// eslint-disable-next-line react/display-name
|
||||
const MapRootHandlers = forwardRef(({ children }: WithChildren, fwdRef: ForwardedRef<any>) => {
|
||||
useMapRootHandlers(fwdRef);
|
||||
useGlobalHooks();
|
||||
return <>{children}</>;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import { AvailableThemes, InterfaceStoredSettings, RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import {
|
||||
AvailableThemes,
|
||||
InterfaceStoredSettings,
|
||||
MiniMapPlacement,
|
||||
PingsPlacement,
|
||||
RoutesType,
|
||||
} from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
|
||||
isShowMenu: false,
|
||||
isShowMinimap: true,
|
||||
isShowKSpace: false,
|
||||
isThickConnections: false,
|
||||
isShowUnsplashedSignatures: false,
|
||||
isShowBackgroundPattern: true,
|
||||
isSoftBackground: false,
|
||||
theme: AvailableThemes.default,
|
||||
pingsPlacement: PingsPlacement.rightTop,
|
||||
minimapPlacement: MiniMapPlacement.rightBottom,
|
||||
};
|
||||
|
||||
export const DEFAULT_ROUTES_SETTINGS: RoutesType = {
|
||||
|
||||
@@ -9,3 +9,4 @@ export * from './useCommandsCharacters';
|
||||
export * from './useCommandComments';
|
||||
export * from './useGetCacheCharacter';
|
||||
export * from './useCommandsActivity';
|
||||
export * from './useCommandPings';
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CommandPingAdded, CommandPingCancelled } from '@/hooks/Mapper/types';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
export const useCommandPings = () => {
|
||||
const {
|
||||
update,
|
||||
data: { pings },
|
||||
} = useMapRootState();
|
||||
const ref = useRef({ update, pings });
|
||||
ref.current = { update, pings };
|
||||
|
||||
const pingAdded = useCallback((pings: CommandPingAdded) => {
|
||||
ref.current.update({ pings });
|
||||
}, []);
|
||||
|
||||
const pingCancelled = useCallback(({ type, solar_system_id }: CommandPingCancelled) => {
|
||||
const newPings = ref.current.pings.filter(x => x.solar_system_id !== solar_system_id && x.type !== type);
|
||||
ref.current.update({ pings: newPings });
|
||||
}, []);
|
||||
|
||||
return { pingAdded, pingCancelled };
|
||||
};
|
||||
@@ -70,6 +70,9 @@ export const useCommandsSystems = () => {
|
||||
const updateSystemSignatures = useCallback(
|
||||
async (systemId: string) => {
|
||||
const { update, systemSignatures } = ref.current;
|
||||
|
||||
// TODO need to fix it
|
||||
// @ts-ignore
|
||||
const { signatures } = await outCommand({
|
||||
type: OutCommand.getSignatures,
|
||||
data: { system_id: `${systemId}` },
|
||||
@@ -80,7 +83,7 @@ export const useCommandsSystems = () => {
|
||||
[outCommand],
|
||||
);
|
||||
|
||||
const updateLinkSignatureToSystem = useCallback(async (command: CommandLinkSignatureToSystem) => {
|
||||
const updateLinkSignatureToSystem = useCallback(async (command: CommandLinkSignatureToSystem | null) => {
|
||||
const { update } = ref.current;
|
||||
update({ linkSignatureToSystem: command }, true);
|
||||
}, []);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { useLoadPublicRoutes } from './useLoadPublicRoutes';
|
||||
|
||||
/* TODO this hook needs for call some actions which should affect all mapper*/
|
||||
export const useGlobalHooks = () => {
|
||||
useLoadPublicRoutes();
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { LoadRoutesCommand } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { Commands, OutCommand } from '@/hooks/Mapper/types';
|
||||
import { useLoadRoutes } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/hooks';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
|
||||
export const useLoadPublicRoutes = () => {
|
||||
const {
|
||||
outCommand,
|
||||
storedSettings: { settingsRoutes },
|
||||
data: { hubs, routes, pings },
|
||||
update,
|
||||
} = useMapRootState();
|
||||
|
||||
const loadRoutesCommand: LoadRoutesCommand = useCallback(
|
||||
async (systemId, routesSettings) => {
|
||||
outCommand({
|
||||
type: OutCommand.getRoutes,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
routes_settings: routesSettings,
|
||||
},
|
||||
});
|
||||
},
|
||||
[outCommand],
|
||||
);
|
||||
|
||||
const { loading, setLoading } = useLoadRoutes({
|
||||
data: settingsRoutes,
|
||||
hubs: hubs,
|
||||
loadRoutesCommand,
|
||||
routesList: routes,
|
||||
deps: [pings],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
update({ loadingPublicRoutes: loading });
|
||||
}, [loading, update]);
|
||||
|
||||
useMapEventListener(event => {
|
||||
if (event.name === Commands.routes) {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -23,10 +23,13 @@ import {
|
||||
Commands,
|
||||
MapHandlers,
|
||||
CommandCommentRemoved,
|
||||
CommandPingAdded,
|
||||
CommandPingCancelled,
|
||||
} from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
|
||||
import {
|
||||
useCommandComments,
|
||||
useCommandPings,
|
||||
useCommandsCharacters,
|
||||
useCommandsConnections,
|
||||
useCommandsSystems,
|
||||
@@ -57,6 +60,7 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
const mapRoutes = useRoutes();
|
||||
const mapUserRoutes = useUserRoutes();
|
||||
const { addComment, removeComment } = useCommandComments();
|
||||
const { pingAdded, pingCancelled } = useCommandPings();
|
||||
const { characterActivityData, trackingCharactersData, userSettingsUpdated } = useCommandsActivity();
|
||||
|
||||
useImperativeHandle(
|
||||
@@ -163,6 +167,14 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
removeComment(data as CommandCommentRemoved);
|
||||
break;
|
||||
|
||||
case Commands.pingAdded:
|
||||
pingAdded(data as CommandPingAdded);
|
||||
break;
|
||||
|
||||
case Commands.pingCancelled:
|
||||
pingCancelled(data as CommandPingCancelled);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
|
||||
break;
|
||||
|
||||
@@ -8,7 +8,7 @@ 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, WindowsManagerOnChange } 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 = {
|
||||
@@ -17,7 +17,7 @@ export type WindowStoreInfo = {
|
||||
visible: WidgetsIds[];
|
||||
viewPort?: { w: number; h: number } | undefined;
|
||||
};
|
||||
export type UpdateWidgetSettingsFunc = (widgets: WindowProps[]) => void;
|
||||
// export type UpdateWidgetSettingsFunc = (widgets: WindowProps[]) => void;
|
||||
export type ToggleWidgetVisibility = (widgetId: WidgetsIds) => void;
|
||||
|
||||
export const getDefaultWidgetProps = () => ({
|
||||
@@ -66,7 +66,7 @@ export const useStoreWidgets = () => {
|
||||
...x,
|
||||
windows: windows.map(wnd => {
|
||||
if (wnd.id === widgetId) {
|
||||
return { ...wnd, position: { x: SNAP_GAP, y: SNAP_GAP }, zIndex: maxZIndex + 1 };
|
||||
return { ...wnd, /*position: { x: SNAP_GAP, y: SNAP_GAP },*/ zIndex: maxZIndex + 1 };
|
||||
}
|
||||
|
||||
return wnd;
|
||||
|
||||
@@ -3,15 +3,31 @@ export enum AvailableThemes {
|
||||
pathfinder = 'pathfinder',
|
||||
}
|
||||
|
||||
export enum MiniMapPlacement {
|
||||
rightTop = 'rightTop',
|
||||
rightBottom = 'rightBottom',
|
||||
leftTop = 'leftTop',
|
||||
leftBottom = 'leftBottom',
|
||||
hide = 'hide',
|
||||
}
|
||||
|
||||
export enum PingsPlacement {
|
||||
rightTop = 'rightTop',
|
||||
rightBottom = 'rightBottom',
|
||||
leftTop = 'leftTop',
|
||||
leftBottom = 'leftBottom',
|
||||
}
|
||||
|
||||
export type InterfaceStoredSettings = {
|
||||
isShowMenu: boolean;
|
||||
isShowMinimap: boolean;
|
||||
isShowKSpace: boolean;
|
||||
isThickConnections: boolean;
|
||||
isShowUnsplashedSignatures: boolean;
|
||||
isShowBackgroundPattern: boolean;
|
||||
isSoftBackground: boolean;
|
||||
theme: AvailableThemes;
|
||||
minimapPlacement: MiniMapPlacement;
|
||||
pingsPlacement: PingsPlacement;
|
||||
};
|
||||
|
||||
export type RoutesType = {
|
||||
|
||||
@@ -33,6 +33,7 @@ export type CharacterTypeRaw = {
|
||||
corporation_id: number;
|
||||
corporation_name: string;
|
||||
corporation_ticker: string;
|
||||
tracking_paused: boolean;
|
||||
};
|
||||
|
||||
export interface TrackingCharacter {
|
||||
|
||||
@@ -8,3 +8,4 @@ export * from './signatures';
|
||||
export * from './connectionPassages';
|
||||
export * from './permissions';
|
||||
export * from './comment';
|
||||
export * from './ping';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { SolarSystemRawType, SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types/system.ts';
|
||||
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
|
||||
import { WormholeDataRaw } from '@/hooks/Mapper/types/wormholes.ts';
|
||||
import { CommentType, PingData, SystemSignature, UserPermissions } from '@/hooks/Mapper/types';
|
||||
import { ActivitySummary, CharacterTypeRaw, TrackingCharacter } from '@/hooks/Mapper/types/character.ts';
|
||||
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
|
||||
import { DetailedKill, Kill } from '@/hooks/Mapper/types/kills.ts';
|
||||
import { CommentType, SystemSignature, UserPermissions } from '@/hooks/Mapper/types';
|
||||
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||
import { SolarSystemRawType, SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types/system.ts';
|
||||
import { WormholeDataRaw } from '@/hooks/Mapper/types/wormholes.ts';
|
||||
|
||||
export enum Commands {
|
||||
init = 'init',
|
||||
@@ -37,6 +37,8 @@ export enum Commands {
|
||||
updateTracking = 'update_tracking',
|
||||
userSettingsUpdated = 'user_settings_updated',
|
||||
showTracking = 'show_tracking',
|
||||
pingAdded = 'ping_added',
|
||||
pingCancelled = 'ping_cancelled',
|
||||
}
|
||||
|
||||
export type Command =
|
||||
@@ -69,7 +71,9 @@ export type Command =
|
||||
| Commands.userSettingsUpdated
|
||||
| Commands.updateActivity
|
||||
| Commands.updateTracking
|
||||
| Commands.showTracking;
|
||||
| Commands.showTracking
|
||||
| Commands.pingAdded
|
||||
| Commands.pingCancelled;
|
||||
|
||||
export type CommandInit = {
|
||||
systems: SolarSystemRawType[];
|
||||
@@ -78,6 +82,8 @@ export type CommandInit = {
|
||||
system_static_infos: SolarSystemStaticInfoRaw[];
|
||||
connections: SolarSystemConnection[];
|
||||
wormholes: WormholeDataRaw[];
|
||||
|
||||
// TODO WHY HERE ANY?!!?!?
|
||||
effects: any[];
|
||||
characters: CharacterTypeRaw[];
|
||||
present_characters: string[];
|
||||
@@ -143,6 +149,8 @@ export type CommandUpdateTracking = {
|
||||
track: boolean;
|
||||
follow: boolean;
|
||||
};
|
||||
export type CommandPingAdded = PingData[];
|
||||
export type CommandPingCancelled = Pick<PingData, 'type' | 'solar_system_id'>;
|
||||
|
||||
export interface UserSettings {
|
||||
primaryCharacterId?: string;
|
||||
@@ -190,6 +198,8 @@ export interface CommandData {
|
||||
[Commands.systemCommentRemoved]: CommandCommentRemoved;
|
||||
[Commands.systemCommentsUpdated]: unknown;
|
||||
[Commands.showTracking]: CommandShowTracking;
|
||||
[Commands.pingAdded]: CommandPingAdded;
|
||||
[Commands.pingCancelled]: CommandPingCancelled;
|
||||
}
|
||||
|
||||
export interface MapHandlers {
|
||||
@@ -248,6 +258,11 @@ export enum OutCommand {
|
||||
updateCharacterTracking = 'updateCharacterTracking',
|
||||
updateFollowingCharacter = 'updateFollowingCharacter',
|
||||
updateMainCharacter = 'updateMainCharacter',
|
||||
addPing = 'add_ping',
|
||||
cancelPing = 'cancel_ping',
|
||||
startTracking = 'startTracking',
|
||||
|
||||
// Only UI commands
|
||||
openSettings = 'open_settings',
|
||||
showActivity = 'show_activity',
|
||||
showTracking = 'show_tracking',
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CharacterTypeRaw } from '@/hooks/Mapper/types/character.ts';
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types/system.ts';
|
||||
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
|
||||
import { UserPermissions } from '@/hooks/Mapper/types';
|
||||
import { PingData, UserPermissions } from '@/hooks/Mapper/types';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
|
||||
|
||||
export type MapUnionTypes = {
|
||||
@@ -28,4 +28,5 @@ export type MapUnionTypes = {
|
||||
|
||||
mainCharacterEveId: string | null;
|
||||
followingCharacterEveId: string | null;
|
||||
pings: PingData[];
|
||||
};
|
||||
|
||||
12
assets/js/hooks/Mapper/types/ping.ts
Normal file
12
assets/js/hooks/Mapper/types/ping.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export enum PingType {
|
||||
Alert,
|
||||
Rally,
|
||||
}
|
||||
|
||||
export type PingData = {
|
||||
inserted_at: number;
|
||||
character_eve_id: string;
|
||||
solar_system_id: string;
|
||||
message: string;
|
||||
type: PingType;
|
||||
};
|
||||
@@ -1,9 +1,6 @@
|
||||
import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { RefObject, useCallback } from 'react';
|
||||
|
||||
// Force reload the page after 5 minutes of inactivity
|
||||
const FORCE_PAGE_RELOAD_TIMEOUT = 1000 * 60 * 5;
|
||||
|
||||
export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRef: RefObject<any>) => {
|
||||
const handleCommand = useCallback(
|
||||
async ({ type, data }) => {
|
||||
@@ -16,13 +13,7 @@ export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRe
|
||||
[hooksRef.current],
|
||||
);
|
||||
|
||||
const handleMapEvent = useCallback(({ type, body, timestamp }) => {
|
||||
const timeDiff = Date.now() - Date.parse(timestamp);
|
||||
// If the event is older than the timeout, force reload the page
|
||||
if (timeDiff > FORCE_PAGE_RELOAD_TIMEOUT) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
const handleMapEvent = useCallback(({ type, body }) => {
|
||||
handlerRefs.forEach(ref => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
|
||||
@@ -53,16 +53,6 @@ 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", "true")
|
||||
|> 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")
|
||||
@@ -127,9 +117,15 @@ config :wanderer_app,
|
||||
admins: admins,
|
||||
corp_id: System.get_env("WANDERER_CORP_ID", "-1") |> String.to_integer(),
|
||||
corp_wallet: System.get_env("WANDERER_CORP_WALLET", ""),
|
||||
corp_wallet_eve_id: System.get_env("WANDERER_CORP_WALLET_EVE_ID", "-1"),
|
||||
public_api_disabled: public_api_disabled,
|
||||
character_api_disabled: character_api_disabled,
|
||||
zkill_preload_disabled: zkill_preload_disabled,
|
||||
character_tracking_pause_disabled:
|
||||
System.get_env("WANDERER_CHARACTER_TRACKING_PAUSE_DISABLED", "true")
|
||||
|> String.to_existing_atom(),
|
||||
character_api_disabled:
|
||||
System.get_env("WANDERER_CHARACTER_API_DISABLED", "true") |> String.to_existing_atom(),
|
||||
zkill_preload_disabled:
|
||||
System.get_env("WANDERER_ZKILL_PRELOAD_DISABLED", "false") |> String.to_existing_atom(),
|
||||
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,
|
||||
|
||||
@@ -28,5 +28,7 @@ defmodule WandererApp.Api do
|
||||
resource WandererApp.Api.UserTransaction
|
||||
resource WandererApp.Api.CorpWalletTransaction
|
||||
resource WandererApp.Api.License
|
||||
resource WandererApp.Api.MapPing
|
||||
resource WandererApp.Api.MapInvite
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,17 +3,19 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
|
||||
use Ash.Resource,
|
||||
domain: WandererApp.Api,
|
||||
data_layer: AshPostgres.DataLayer
|
||||
data_layer: AshPostgres.DataLayer,
|
||||
extensions: [AshCloak]
|
||||
|
||||
@derive {Jason.Encoder, only: [
|
||||
:id,
|
||||
:map_id,
|
||||
:character_id,
|
||||
:tracked,
|
||||
:followed,
|
||||
:inserted_at,
|
||||
:updated_at
|
||||
]}
|
||||
@derive {Jason.Encoder,
|
||||
only: [
|
||||
:id,
|
||||
:map_id,
|
||||
:character_id,
|
||||
:tracked,
|
||||
:followed,
|
||||
:inserted_at,
|
||||
:updated_at
|
||||
]}
|
||||
|
||||
postgres do
|
||||
repo(WandererApp.Repo)
|
||||
@@ -23,8 +25,10 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
code_interface do
|
||||
define(:create, action: :create)
|
||||
define(:destroy, action: :destroy)
|
||||
define(:update, action: :update)
|
||||
|
||||
define(:read_by_map, action: :read_by_map)
|
||||
define(:read_by_map_and_character, action: :read_by_map_and_character)
|
||||
define(:by_map_filtered, action: :by_map_filtered)
|
||||
define(:tracked_by_map_filtered, action: :tracked_by_map_filtered)
|
||||
define(:tracked_by_character, action: :tracked_by_character)
|
||||
@@ -44,7 +48,31 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
:tracked
|
||||
]
|
||||
|
||||
defaults [:create, :read, :update, :destroy]
|
||||
defaults [:read, :destroy]
|
||||
|
||||
create :create do
|
||||
primary? true
|
||||
upsert? true
|
||||
upsert_identity :uniq_map_character
|
||||
|
||||
upsert_fields [
|
||||
:map_id,
|
||||
:character_id
|
||||
]
|
||||
|
||||
accept [
|
||||
:map_id,
|
||||
:character_id,
|
||||
:tracked,
|
||||
:followed
|
||||
]
|
||||
|
||||
argument :map_id, :uuid, allow_nil?: false
|
||||
argument :character_id, :uuid, allow_nil?: false
|
||||
|
||||
change manage_relationship(:map_id, :map, on_lookup: :relate, on_no_match: nil)
|
||||
change manage_relationship(:character_id, :character, on_lookup: :relate, on_no_match: nil)
|
||||
end
|
||||
|
||||
read :by_map_filtered do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
@@ -67,6 +95,15 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
filter(expr(map_id == ^arg(:map_id)))
|
||||
end
|
||||
|
||||
read :read_by_map_and_character do
|
||||
get? true
|
||||
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
argument(:character_id, :uuid, allow_nil?: false)
|
||||
|
||||
filter(expr(map_id == ^arg(:map_id) and character_id == ^arg(:character_id)))
|
||||
end
|
||||
|
||||
read :tracked_by_map_all do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
filter(expr(map_id == ^arg(:map_id) and tracked == true))
|
||||
@@ -77,6 +114,20 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
filter(expr(character_id == ^arg(:character_id) and tracked == true))
|
||||
end
|
||||
|
||||
update :update do
|
||||
primary? true
|
||||
require_atomic? false
|
||||
|
||||
accept([
|
||||
:ship,
|
||||
:ship_name,
|
||||
:ship_item_id,
|
||||
:solar_system_id,
|
||||
:structure_id,
|
||||
:station_id
|
||||
])
|
||||
end
|
||||
|
||||
update :track do
|
||||
accept [:map_id, :character_id]
|
||||
argument :map_id, :string, allow_nil?: false
|
||||
@@ -134,6 +185,28 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
end
|
||||
end
|
||||
|
||||
cloak do
|
||||
vault(WandererApp.Vault)
|
||||
|
||||
attributes([
|
||||
:ship,
|
||||
:ship_name,
|
||||
:ship_item_id,
|
||||
:solar_system_id,
|
||||
:structure_id,
|
||||
:station_id
|
||||
])
|
||||
|
||||
decrypt_by_default([
|
||||
:ship,
|
||||
:ship_name,
|
||||
:ship_item_id,
|
||||
:solar_system_id,
|
||||
:structure_id,
|
||||
:station_id
|
||||
])
|
||||
end
|
||||
|
||||
attributes do
|
||||
uuid_primary_key :id
|
||||
|
||||
@@ -147,6 +220,13 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :solar_system_id, :integer
|
||||
attribute :structure_id, :integer
|
||||
attribute :station_id, :integer
|
||||
attribute :ship, :integer
|
||||
attribute :ship_name, :string
|
||||
attribute :ship_item_id, :integer
|
||||
|
||||
create_timestamp(:inserted_at)
|
||||
update_timestamp(:updated_at)
|
||||
end
|
||||
|
||||
96
lib/wanderer_app/api/map_invite.ex
Normal file
96
lib/wanderer_app/api/map_invite.ex
Normal file
@@ -0,0 +1,96 @@
|
||||
defmodule WandererApp.Api.MapInvite do
|
||||
@moduledoc false
|
||||
|
||||
use Ash.Resource,
|
||||
domain: WandererApp.Api,
|
||||
data_layer: AshPostgres.DataLayer
|
||||
|
||||
postgres do
|
||||
repo(WandererApp.Repo)
|
||||
table("map_invites_v1")
|
||||
end
|
||||
|
||||
code_interface do
|
||||
define(:new, action: :new)
|
||||
define(:read, action: :read)
|
||||
define(:destroy, action: :destroy)
|
||||
|
||||
define(:by_id,
|
||||
get_by: [:id],
|
||||
action: :read
|
||||
)
|
||||
|
||||
define(:by_map,
|
||||
action: :by_map
|
||||
)
|
||||
end
|
||||
|
||||
actions do
|
||||
default_accept [
|
||||
:token
|
||||
]
|
||||
|
||||
defaults [:read, :update, :destroy]
|
||||
|
||||
create :new do
|
||||
accept [
|
||||
:map_id,
|
||||
:token,
|
||||
:type,
|
||||
:valid_until
|
||||
]
|
||||
|
||||
primary?(true)
|
||||
|
||||
argument :map_id, :uuid, allow_nil?: true
|
||||
|
||||
change manage_relationship(:map_id, :map, on_lookup: :relate, on_no_match: nil)
|
||||
end
|
||||
|
||||
read :by_map do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
|
||||
filter(expr(map_id == ^arg(:map_id)))
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
uuid_primary_key :id
|
||||
|
||||
attribute :token, :string do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :type, :atom do
|
||||
default "user"
|
||||
|
||||
constraints(
|
||||
one_of: [
|
||||
:user,
|
||||
:admin
|
||||
]
|
||||
)
|
||||
|
||||
allow_nil?(false)
|
||||
end
|
||||
|
||||
attribute :valid_until, :utc_datetime do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
create_timestamp(:inserted_at)
|
||||
update_timestamp(:updated_at)
|
||||
end
|
||||
|
||||
relationships do
|
||||
belongs_to :map, WandererApp.Api.Map do
|
||||
attribute_writable? true
|
||||
end
|
||||
end
|
||||
|
||||
postgres do
|
||||
references do
|
||||
reference :map, on_delete: :delete
|
||||
end
|
||||
end
|
||||
end
|
||||
116
lib/wanderer_app/api/map_ping.ex
Normal file
116
lib/wanderer_app/api/map_ping.ex
Normal file
@@ -0,0 +1,116 @@
|
||||
defmodule WandererApp.Api.MapPing do
|
||||
@moduledoc false
|
||||
|
||||
use Ash.Resource,
|
||||
domain: WandererApp.Api,
|
||||
data_layer: AshPostgres.DataLayer
|
||||
|
||||
postgres do
|
||||
repo(WandererApp.Repo)
|
||||
table("map_pings_v1")
|
||||
end
|
||||
|
||||
code_interface do
|
||||
define(:new, action: :new)
|
||||
define(:destroy, action: :destroy)
|
||||
|
||||
define(:by_map,
|
||||
action: :by_map
|
||||
)
|
||||
|
||||
define(:by_map_and_system,
|
||||
action: :by_map_and_system
|
||||
)
|
||||
|
||||
define(:by_inserted_before, action: :by_inserted_before, args: [:inserted_before])
|
||||
end
|
||||
|
||||
actions do
|
||||
default_accept [
|
||||
:type,
|
||||
:message
|
||||
]
|
||||
|
||||
defaults [:read, :update, :destroy]
|
||||
|
||||
create :new do
|
||||
accept [
|
||||
:map_id,
|
||||
:system_id,
|
||||
:character_id,
|
||||
:type,
|
||||
:message
|
||||
]
|
||||
|
||||
primary?(true)
|
||||
|
||||
argument :map_id, :uuid, allow_nil?: false
|
||||
argument :system_id, :uuid, allow_nil?: false
|
||||
argument :character_id, :uuid, allow_nil?: false
|
||||
|
||||
change manage_relationship(:map_id, :map, on_lookup: :relate, on_no_match: nil)
|
||||
change manage_relationship(:system_id, :system, on_lookup: :relate, on_no_match: nil)
|
||||
change manage_relationship(:character_id, :character, on_lookup: :relate, on_no_match: nil)
|
||||
end
|
||||
|
||||
read :by_map do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
|
||||
filter(expr(map_id == ^arg(:map_id)))
|
||||
end
|
||||
|
||||
read :by_map_and_system do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
argument(:system_id, :string, allow_nil?: false)
|
||||
|
||||
filter(expr(map_id == ^arg(:map_id) and system_id == ^arg(:system_id)))
|
||||
end
|
||||
|
||||
read :by_inserted_before do
|
||||
argument(:inserted_before, :utc_datetime, allow_nil?: false)
|
||||
|
||||
filter(expr(inserted_at <= ^arg(:inserted_before)))
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
uuid_primary_key :id
|
||||
|
||||
# ping: 0
|
||||
# rally_point: 1
|
||||
attribute :type, :integer do
|
||||
default 0
|
||||
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :message, :string do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
create_timestamp(:inserted_at)
|
||||
update_timestamp(:updated_at)
|
||||
end
|
||||
|
||||
relationships do
|
||||
belongs_to :map, WandererApp.Api.Map do
|
||||
attribute_writable? true
|
||||
end
|
||||
|
||||
belongs_to :system, WandererApp.Api.MapSystem do
|
||||
attribute_writable? true
|
||||
end
|
||||
|
||||
belongs_to :character, WandererApp.Api.Character do
|
||||
attribute_writable? true
|
||||
end
|
||||
end
|
||||
|
||||
postgres do
|
||||
references do
|
||||
reference :map, on_delete: :delete
|
||||
reference :system, on_delete: :delete
|
||||
reference :character, on_delete: :delete
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -25,9 +25,18 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
|
||||
define(:by_system_id, action: :by_system_id, args: [:system_id])
|
||||
define(:by_system_id_all, action: :by_system_id_all, args: [:system_id])
|
||||
define(:by_system_id_and_eve_ids, action: :by_system_id_and_eve_ids, args: [:system_id, :eve_ids])
|
||||
|
||||
define(:by_system_id_and_eve_ids,
|
||||
action: :by_system_id_and_eve_ids,
|
||||
args: [:system_id, :eve_ids]
|
||||
)
|
||||
|
||||
define(:by_linked_system_id, action: :by_linked_system_id, args: [:linked_system_id])
|
||||
define(:by_deleted_and_updated_before!, action: :by_deleted_and_updated_before, args: [:deleted, :updated_before])
|
||||
|
||||
define(:by_deleted_and_updated_before!,
|
||||
action: :by_deleted_and_updated_before,
|
||||
args: [:deleted, :updated_before]
|
||||
)
|
||||
end
|
||||
|
||||
actions do
|
||||
@@ -88,7 +97,8 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
:group,
|
||||
:type,
|
||||
:custom_info,
|
||||
:deleted
|
||||
:deleted,
|
||||
:update_forced_at
|
||||
]
|
||||
|
||||
primary? true
|
||||
@@ -177,6 +187,10 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
default false
|
||||
end
|
||||
|
||||
attribute :update_forced_at, :utc_datetime do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
create_timestamp(:inserted_at)
|
||||
update_timestamp(:updated_at)
|
||||
end
|
||||
@@ -192,21 +206,20 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
end
|
||||
|
||||
@derive {Jason.Encoder,
|
||||
only: [
|
||||
:id,
|
||||
:system_id,
|
||||
:eve_id,
|
||||
:character_eve_id,
|
||||
:name,
|
||||
:description,
|
||||
:type,
|
||||
:linked_system_id,
|
||||
:kind,
|
||||
:group,
|
||||
:custom_info,
|
||||
:deleted,
|
||||
:inserted_at,
|
||||
:updated_at
|
||||
]
|
||||
}
|
||||
only: [
|
||||
:id,
|
||||
:system_id,
|
||||
:eve_id,
|
||||
:character_eve_id,
|
||||
:name,
|
||||
:description,
|
||||
:type,
|
||||
:linked_system_id,
|
||||
:kind,
|
||||
:group,
|
||||
:custom_info,
|
||||
:deleted,
|
||||
:inserted_at,
|
||||
:updated_at
|
||||
]}
|
||||
end
|
||||
|
||||
@@ -112,6 +112,8 @@ defmodule WandererApp.Api.UserActivity do
|
||||
:map_connection_added,
|
||||
:map_connection_updated,
|
||||
:map_connection_removed,
|
||||
:map_rally_added,
|
||||
:map_rally_cancelled,
|
||||
:signatures_added,
|
||||
:signatures_removed
|
||||
]
|
||||
|
||||
@@ -20,9 +20,9 @@ defmodule WandererApp.Application do
|
||||
pools: %{
|
||||
default: [
|
||||
# number of connections per pool
|
||||
size: 25,
|
||||
size: 50,
|
||||
# number of pools (so total 50 connections)
|
||||
count: 2
|
||||
count: 4
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -47,17 +47,16 @@ defmodule WandererApp.Application do
|
||||
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.TrackerPoolSupervisor, []},
|
||||
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?()) ++
|
||||
maybe_start_zkb(WandererApp.Env.zkill_preload_disabled?())
|
||||
|
||||
opts = [strategy: :one_for_one, name: WandererApp.Supervisor]
|
||||
|
||||
@@ -78,6 +77,12 @@ defmodule WandererApp.Application do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp maybe_start_zkb(false),
|
||||
do: [WandererApp.Zkb.Supervisor, WandererApp.Map.ZkbDataFetcher]
|
||||
|
||||
defp maybe_start_zkb(_),
|
||||
do: []
|
||||
|
||||
defp maybe_start_corp_wallet_tracker(true),
|
||||
do: [
|
||||
WandererApp.StartCorpWalletTrackerTask
|
||||
|
||||
@@ -7,6 +7,15 @@ defmodule WandererApp.Character do
|
||||
@read_character_wallet_scope "esi-wallet.read_character_wallet.v1"
|
||||
@read_corp_wallet_scope "esi-wallet.read_corporation_wallets.v1"
|
||||
|
||||
@default_character_tracking_data %{
|
||||
solar_system_id: nil,
|
||||
structure_id: nil,
|
||||
station_id: nil,
|
||||
ship: nil,
|
||||
ship_name: nil,
|
||||
ship_item_id: nil
|
||||
}
|
||||
|
||||
@decorate cacheable(
|
||||
cache: WandererApp.Cache,
|
||||
key: "characters-#{character_eve_id}"
|
||||
@@ -45,6 +54,40 @@ defmodule WandererApp.Character do
|
||||
end
|
||||
end
|
||||
|
||||
def get_map_character(map_id, character_id, opts \\ []) do
|
||||
case get_character(character_id) do
|
||||
{:ok, character} ->
|
||||
# If we are forcing the character to not be present, we merge the character state with map settings
|
||||
character_is_present =
|
||||
if opts |> Keyword.get(:not_present, false) do
|
||||
false
|
||||
else
|
||||
WandererApp.Character.TrackerManager.Impl.character_is_present(map_id, character_id)
|
||||
end
|
||||
|
||||
{:ok,
|
||||
character
|
||||
|> maybe_merge_map_character_settings(
|
||||
map_id,
|
||||
character_is_present
|
||||
)}
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
def get_map_character!(map_id, character_id) do
|
||||
case get_map_character(map_id, character_id) do
|
||||
{:ok, character} ->
|
||||
character
|
||||
|
||||
_ ->
|
||||
Logger.error("Failed to get map character #{map_id} #{character_id}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_character_eve_ids!(character_ids),
|
||||
do:
|
||||
character_ids
|
||||
@@ -136,20 +179,24 @@ defmodule WandererApp.Character do
|
||||
end
|
||||
|
||||
def search(character_id, opts \\ []) do
|
||||
{:ok, %{access_token: access_token, eve_id: eve_id} = _character} =
|
||||
get_character(character_id)
|
||||
get_character(character_id)
|
||||
|> case do
|
||||
{:ok, %{access_token: access_token, eve_id: eve_id} = _character} ->
|
||||
case WandererApp.Esi.search(eve_id |> String.to_integer(),
|
||||
access_token: access_token,
|
||||
character_id: character_id,
|
||||
refresh_token?: true,
|
||||
params: opts[:params]
|
||||
) do
|
||||
{:ok, result} ->
|
||||
{:ok, result |> prepare_search_results()}
|
||||
|
||||
case WandererApp.Esi.search(eve_id |> String.to_integer(),
|
||||
access_token: access_token,
|
||||
character_id: character_id,
|
||||
refresh_token?: true,
|
||||
params: opts[:params]
|
||||
) do
|
||||
{:ok, result} ->
|
||||
{:ok, result |> _prepare_search_results()}
|
||||
error ->
|
||||
Logger.warning("#{__MODULE__} failed search: #{inspect(error)}")
|
||||
{:ok, []}
|
||||
end
|
||||
|
||||
{:error, error} ->
|
||||
Logger.warning("#{__MODULE__} failed search: #{inspect(error)}")
|
||||
error ->
|
||||
{:ok, []}
|
||||
end
|
||||
end
|
||||
@@ -160,12 +207,27 @@ defmodule WandererApp.Character do
|
||||
|
||||
def can_track_wallet?(_), do: false
|
||||
|
||||
def can_track_corp_wallet?(%{scopes: scopes} = _character) when not is_nil(scopes) do
|
||||
scopes |> String.split(" ") |> Enum.member?(@read_corp_wallet_scope)
|
||||
end
|
||||
def can_track_corp_wallet?(%{scopes: scopes} = _character)
|
||||
when not is_nil(scopes),
|
||||
do: scopes |> String.split(" ") |> Enum.member?(@read_corp_wallet_scope)
|
||||
|
||||
def can_track_corp_wallet?(_), do: false
|
||||
|
||||
@decorate cacheable(
|
||||
cache: WandererApp.Cache,
|
||||
key: "can_pause_tracking-#{character_id}"
|
||||
)
|
||||
def can_pause_tracking?(character_id) do
|
||||
case get_character(character_id) do
|
||||
{:ok, character} when not is_nil(character) ->
|
||||
not WandererApp.Env.character_tracking_pause_disabled?() &&
|
||||
not can_track_corp_wallet?(character)
|
||||
|
||||
_ ->
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def get_ship(%{ship: ship_type_id, ship_name: ship_name} = _character)
|
||||
when not is_nil(ship_type_id) and is_integer(ship_type_id) do
|
||||
ship_type_id
|
||||
@@ -208,7 +270,38 @@ defmodule WandererApp.Character do
|
||||
end
|
||||
end
|
||||
|
||||
defp _prepare_search_results(result) do
|
||||
defp maybe_merge_map_character_settings(%{id: character_id} = character, map_id, true) do
|
||||
{:ok, tracking_paused} =
|
||||
WandererApp.Cache.lookup("character:#{character_id}:tracking_paused", false)
|
||||
|
||||
character
|
||||
|> Map.merge(%{tracking_paused: tracking_paused})
|
||||
end
|
||||
|
||||
defp maybe_merge_map_character_settings(
|
||||
%{id: character_id} = character,
|
||||
map_id,
|
||||
_character_is_present
|
||||
) do
|
||||
{:ok, tracking_paused} =
|
||||
WandererApp.Cache.lookup("character:#{character_id}:tracking_paused", false)
|
||||
|
||||
WandererApp.MapCharacterSettingsRepo.get(map_id, character_id)
|
||||
|> case do
|
||||
{:ok, settings} when not is_nil(settings) ->
|
||||
character
|
||||
|> Map.put(:online, false)
|
||||
|> Map.merge(settings)
|
||||
|
||||
_ ->
|
||||
character
|
||||
|> Map.put(:online, false)
|
||||
|> Map.merge(@default_character_tracking_data)
|
||||
end
|
||||
|> Map.merge(%{tracking_paused: tracking_paused})
|
||||
end
|
||||
|
||||
defp prepare_search_results(result) do
|
||||
{:ok, characters} =
|
||||
_load_eve_info(Map.get(result, "character"), :get_character_info, &_map_character_info/1)
|
||||
|
||||
|
||||
@@ -33,8 +33,16 @@ defmodule WandererApp.Character.Tracker do
|
||||
status: binary()
|
||||
}
|
||||
|
||||
@online_error_timeout :timer.minutes(3)
|
||||
@forbidden_ttl :timer.minutes(1)
|
||||
@pause_tracking_timeout :timer.minutes(60 * 10)
|
||||
@offline_timeout :timer.minutes(5)
|
||||
@online_error_timeout :timer.minutes(2)
|
||||
@ship_error_timeout :timer.minutes(2)
|
||||
@location_error_timeout :timer.minutes(2)
|
||||
@online_forbidden_ttl :timer.seconds(7)
|
||||
@online_limit_ttl :timer.seconds(7)
|
||||
@forbidden_ttl :timer.seconds(5)
|
||||
@limit_ttl :timer.seconds(5)
|
||||
@location_limit_ttl :timer.seconds(1)
|
||||
@pubsub_client Application.compile_env(:wanderer_app, :pubsub_client)
|
||||
|
||||
def new(), do: __struct__()
|
||||
@@ -49,6 +57,95 @@ defmodule WandererApp.Character.Tracker do
|
||||
|> new()
|
||||
end
|
||||
|
||||
def check_offline(character_id) do
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:last_online_time")
|
||||
|> case do
|
||||
nil ->
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:last_online_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
|
||||
:ok
|
||||
|
||||
last_online_time ->
|
||||
duration = DateTime.diff(DateTime.utc_now(), last_online_time, :millisecond)
|
||||
|
||||
if duration >= @offline_timeout do
|
||||
pause_tracking(character_id)
|
||||
|
||||
:ok
|
||||
else
|
||||
:skip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_online_errors(character_id),
|
||||
do: check_tracking_errors(character_id, "online", @online_error_timeout)
|
||||
|
||||
def check_ship_errors(character_id),
|
||||
do: check_tracking_errors(character_id, "ship", @ship_error_timeout)
|
||||
|
||||
def check_location_errors(character_id),
|
||||
do: check_tracking_errors(character_id, "location", @location_error_timeout)
|
||||
|
||||
defp check_tracking_errors(character_id, type, timeout) do
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:#{type}_error_time")
|
||||
|> case do
|
||||
nil ->
|
||||
:skip
|
||||
|
||||
error_time ->
|
||||
duration = DateTime.diff(DateTime.utc_now(), error_time, :millisecond)
|
||||
|
||||
if duration >= timeout do
|
||||
pause_tracking(character_id)
|
||||
|
||||
:ok
|
||||
else
|
||||
:skip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp pause_tracking(character_id) do
|
||||
if WandererApp.Character.can_pause_tracking?(character_id) &&
|
||||
not WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused") do
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_error_time")
|
||||
WandererApp.Cache.delete("character:#{character_id}:ship_error_time")
|
||||
WandererApp.Cache.delete("character:#{character_id}:location_error_time")
|
||||
WandererApp.Character.update_character(character_id, %{online: false})
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, %{
|
||||
is_online: false
|
||||
})
|
||||
|
||||
Logger.warning("[CharacterTracker] paused for #{character_id}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:tracking_paused",
|
||||
true,
|
||||
ttl: @pause_tracking_timeout
|
||||
)
|
||||
|
||||
{:ok, %{solar_system_id: solar_system_id}} =
|
||||
WandererApp.Character.get_character(character_id)
|
||||
|
||||
{:ok, %{active_maps: active_maps}} =
|
||||
WandererApp.Character.get_character_state(character_id)
|
||||
|
||||
active_maps
|
||||
|> Enum.each(fn map_id ->
|
||||
WandererApp.Cache.put(
|
||||
"map:#{map_id}:character:#{character_id}:start_solar_system_id",
|
||||
solar_system_id
|
||||
)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def update_settings(character_id, track_settings) do
|
||||
{:ok, character_state} = WandererApp.Character.get_character_state(character_id)
|
||||
|
||||
@@ -61,8 +158,138 @@ defmodule WandererApp.Character.Tracker do
|
||||
|> maybe_start_ship_tracking(track_settings)}
|
||||
end
|
||||
|
||||
def update_online(character_id) when is_binary(character_id),
|
||||
do:
|
||||
character_id
|
||||
|> WandererApp.Character.get_character_state!()
|
||||
|> update_online()
|
||||
|
||||
def update_online(%{track_online: true, character_id: character_id} = character_state) do
|
||||
case WandererApp.Character.get_character(character_id) do
|
||||
{:ok, %{eve_id: eve_id, access_token: access_token}}
|
||||
when not is_nil(access_token) ->
|
||||
(WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
|
||||
|> case do
|
||||
true ->
|
||||
{:error, :skipped}
|
||||
|
||||
_ ->
|
||||
case WandererApp.Esi.get_character_online(eve_id,
|
||||
access_token: access_token,
|
||||
character_id: character_id
|
||||
) do
|
||||
{:ok, online} ->
|
||||
online = get_online(online)
|
||||
|
||||
if online.online == true do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:last_online_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_error_time")
|
||||
WandererApp.Cache.delete("character:#{character_id}:ship_error_time")
|
||||
WandererApp.Cache.delete("character:#{character_id}:location_error_time")
|
||||
WandererApp.Cache.delete("character:#{character_id}:info_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:ship_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:location_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:wallet_forbidden")
|
||||
WandererApp.Character.update_character(character_id, online)
|
||||
|
||||
update = %{
|
||||
character_state
|
||||
| is_online: online.online,
|
||||
track_ship: online.online,
|
||||
track_location: online.online
|
||||
}
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, update)
|
||||
|
||||
:ok
|
||||
|
||||
{:error, error} when error in [:forbidden, :not_found, :timeout] ->
|
||||
Logger.warning("#{__MODULE__} failed to update_online: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:online_forbidden",
|
||||
true,
|
||||
ttl: @online_forbidden_ttl
|
||||
)
|
||||
|
||||
if is_nil(
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:online_error_time")
|
||||
) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:online_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, :skipped}
|
||||
|
||||
{:error, :error_limited, headers} ->
|
||||
reset_timeout = get_reset_timeout(headers)
|
||||
|
||||
Logger.warning(".")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:online_forbidden",
|
||||
true,
|
||||
ttl: reset_timeout
|
||||
)
|
||||
|
||||
{:error, :skipped}
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("#{__MODULE__} failed to update_online: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:online_forbidden",
|
||||
true,
|
||||
ttl: @online_forbidden_ttl
|
||||
)
|
||||
|
||||
if is_nil(
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:online_error_time")
|
||||
) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:online_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, :skipped}
|
||||
|
||||
_ ->
|
||||
{:error, :skipped}
|
||||
end
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, :skipped}
|
||||
end
|
||||
end
|
||||
|
||||
def update_online(_), do: {:error, :skipped}
|
||||
|
||||
defp get_reset_timeout(_headers, _default_timeout \\ @limit_ttl)
|
||||
|
||||
defp get_reset_timeout(
|
||||
%{"x-esi-error-limit-remain" => ["0"], "x-esi-error-limit-reset" => [reset_seconds]},
|
||||
_default_timeout
|
||||
)
|
||||
when is_binary(reset_seconds),
|
||||
do: :timer.seconds((reset_seconds |> String.to_integer()) + 1)
|
||||
|
||||
defp get_reset_timeout(_headers, default_timeout), do: default_timeout
|
||||
|
||||
def update_info(character_id) do
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:info_forbidden")
|
||||
(WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:info_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
|
||||
|> case do
|
||||
true ->
|
||||
{:error, :skipped}
|
||||
@@ -79,8 +306,8 @@ defmodule WandererApp.Character.Tracker do
|
||||
|
||||
:ok
|
||||
|
||||
{:error, :forbidden} ->
|
||||
Logger.warning("#{__MODULE__} failed to get_character_info: forbidden")
|
||||
{:error, error} when error in [:forbidden, :not_found, :timeout] ->
|
||||
Logger.warning("#{__MODULE__} failed to get_character_info: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:info_forbidden",
|
||||
@@ -88,20 +315,42 @@ defmodule WandererApp.Character.Tracker do
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
{:error, :forbidden}
|
||||
{:error, error}
|
||||
|
||||
{:error, :error_limited, headers} ->
|
||||
reset_timeout = get_reset_timeout(headers)
|
||||
|
||||
Logger.warning(".")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:info_forbidden",
|
||||
true,
|
||||
ttl: reset_timeout
|
||||
)
|
||||
|
||||
{:error, :error_limited}
|
||||
|
||||
{:error, error} ->
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:info_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
Logger.error("#{__MODULE__} failed to get_character_info: #{inspect(error)}")
|
||||
{:error, error}
|
||||
|
||||
_ ->
|
||||
{:error, :skipped}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_ship(character_id) when is_binary(character_id) do
|
||||
character_id
|
||||
|> WandererApp.Character.get_character_state!()
|
||||
|> update_ship()
|
||||
end
|
||||
def update_ship(character_id) when is_binary(character_id),
|
||||
do:
|
||||
character_id
|
||||
|> WandererApp.Character.get_character_state!()
|
||||
|> update_ship()
|
||||
|
||||
def update_ship(
|
||||
%{character_id: character_id, track_ship: true, is_online: true} = character_state
|
||||
@@ -110,7 +359,9 @@ defmodule WandererApp.Character.Tracker do
|
||||
|> WandererApp.Character.get_character()
|
||||
|> case do
|
||||
{:ok, %{eve_id: eve_id, access_token: access_token}} when not is_nil(access_token) ->
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:ship_forbidden")
|
||||
(WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:ship_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
|
||||
|> case do
|
||||
true ->
|
||||
{:error, :skipped}
|
||||
@@ -118,16 +369,15 @@ defmodule WandererApp.Character.Tracker do
|
||||
_ ->
|
||||
case WandererApp.Esi.get_character_ship(eve_id,
|
||||
access_token: access_token,
|
||||
character_id: character_id,
|
||||
refresh_token?: true
|
||||
character_id: character_id
|
||||
) do
|
||||
{:ok, ship} ->
|
||||
{:ok, ship} when is_non_struct_map(ship) ->
|
||||
character_state |> maybe_update_ship(ship)
|
||||
|
||||
:ok
|
||||
|
||||
{:error, :forbidden} ->
|
||||
Logger.warning("#{__MODULE__} failed to update_ship: forbidden")
|
||||
{:error, error} when error in [:forbidden, :not_found, :timeout] ->
|
||||
Logger.warning("#{__MODULE__} failed to update_ship: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:ship_forbidden",
|
||||
@@ -135,7 +385,27 @@ defmodule WandererApp.Character.Tracker do
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
{:error, :forbidden}
|
||||
if is_nil(WandererApp.Cache.lookup!("character:#{character_id}:ship_error_time")) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:ship_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, error}
|
||||
|
||||
{:error, :error_limited, headers} ->
|
||||
reset_timeout = get_reset_timeout(headers)
|
||||
|
||||
Logger.warning(".")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:ship_forbidden",
|
||||
true,
|
||||
ttl: reset_timeout
|
||||
)
|
||||
|
||||
{:error, :error_limited}
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("#{__MODULE__} failed to update_ship: #{inspect(error)}")
|
||||
@@ -146,7 +416,32 @@ defmodule WandererApp.Character.Tracker do
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
if is_nil(WandererApp.Cache.lookup!("character:#{character_id}:ship_error_time")) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:ship_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, error}
|
||||
|
||||
_ ->
|
||||
Logger.error("#{__MODULE__} failed to update_ship: wrong response")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:ship_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
if is_nil(WandererApp.Cache.lookup!("character:#{character_id}:ship_error_time")) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:ship_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, :skipped}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -157,18 +452,18 @@ defmodule WandererApp.Character.Tracker do
|
||||
|
||||
def update_ship(_), do: {:error, :skipped}
|
||||
|
||||
def update_location(character_id) when is_binary(character_id) do
|
||||
character_id
|
||||
|> WandererApp.Character.get_character_state!()
|
||||
|> update_location()
|
||||
end
|
||||
def update_location(character_id) when is_binary(character_id),
|
||||
do:
|
||||
character_id
|
||||
|> WandererApp.Character.get_character_state!()
|
||||
|> update_location()
|
||||
|
||||
def update_location(
|
||||
%{track_location: true, is_online: true, character_id: character_id} = character_state
|
||||
) do
|
||||
case WandererApp.Character.get_character(character_id) do
|
||||
{:ok, %{eve_id: eve_id, access_token: access_token}} when not is_nil(access_token) ->
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:location_forbidden")
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused")
|
||||
|> case do
|
||||
true ->
|
||||
{:error, :skipped}
|
||||
@@ -176,36 +471,68 @@ defmodule WandererApp.Character.Tracker do
|
||||
_ ->
|
||||
case WandererApp.Esi.get_character_location(eve_id,
|
||||
access_token: access_token,
|
||||
character_id: character_id,
|
||||
refresh_token?: true
|
||||
character_id: character_id
|
||||
) do
|
||||
{:ok, location} ->
|
||||
{:ok, location} when is_non_struct_map(location) ->
|
||||
character_state
|
||||
|> maybe_update_location(location)
|
||||
|
||||
:ok
|
||||
|
||||
{:error, :forbidden} ->
|
||||
Logger.warning("#{__MODULE__} failed to update_location: forbidden")
|
||||
{:error, error} when error in [:forbidden, :not_found, :timeout] ->
|
||||
Logger.warning("#{__MODULE__} failed to update_location: #{inspect(error)}")
|
||||
|
||||
if is_nil(
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:location_error_time")
|
||||
) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:location_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, :skipped}
|
||||
|
||||
{:error, :error_limited, headers} ->
|
||||
Logger.warning(".")
|
||||
|
||||
reset_timeout = get_reset_timeout(headers, @location_limit_ttl)
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:location_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
ttl: reset_timeout
|
||||
)
|
||||
|
||||
{:error, :forbidden}
|
||||
{:error, :error_limited}
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("#{__MODULE__} failed to update_location: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:location_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
if is_nil(
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:location_error_time")
|
||||
) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:location_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, error}
|
||||
{:error, :skipped}
|
||||
|
||||
_ ->
|
||||
Logger.error("#{__MODULE__} failed to update_location: wrong response")
|
||||
|
||||
if is_nil(
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:location_error_time")
|
||||
) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:location_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
{:error, :skipped}
|
||||
end
|
||||
|
||||
_ ->
|
||||
@@ -219,121 +546,6 @@ defmodule WandererApp.Character.Tracker do
|
||||
|
||||
def update_location(_), do: {:error, :skipped}
|
||||
|
||||
def update_online(character_id) when is_binary(character_id) do
|
||||
character_id
|
||||
|> WandererApp.Character.get_character_state!()
|
||||
|> update_online()
|
||||
end
|
||||
|
||||
def update_online(%{track_online: true, character_id: character_id} = character_state) do
|
||||
case WandererApp.Character.get_character(character_id) do
|
||||
{:ok, %{eve_id: eve_id, access_token: access_token}}
|
||||
when not is_nil(access_token) ->
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden")
|
||||
|> case do
|
||||
true ->
|
||||
{:error, :skipped}
|
||||
|
||||
_ ->
|
||||
case WandererApp.Esi.get_character_online(eve_id,
|
||||
access_token: access_token,
|
||||
character_id: character_id,
|
||||
refresh_token?: true
|
||||
) do
|
||||
{:ok, online} ->
|
||||
online = get_online(online)
|
||||
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_error_time")
|
||||
WandererApp.Character.update_character(character_id, online)
|
||||
|
||||
if not online.online do
|
||||
WandererApp.Cache.delete("character:#{character_id}:location_started")
|
||||
WandererApp.Cache.delete("character:#{character_id}:start_solar_system_id")
|
||||
end
|
||||
|
||||
update = %{
|
||||
character_state
|
||||
| is_online: online.online,
|
||||
track_ship: online.online,
|
||||
track_location: online.online
|
||||
}
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, update)
|
||||
|
||||
:ok
|
||||
|
||||
{:error, :forbidden} ->
|
||||
Logger.warning("#{__MODULE__} failed to update_online: forbidden")
|
||||
|
||||
if not WandererApp.Cache.lookup!(
|
||||
"character:#{character_id}:online_forbidden",
|
||||
false
|
||||
) do
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:online_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
if is_nil(
|
||||
WandererApp.Cache.lookup("character:#{character_id}:online_error_time")
|
||||
) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:online_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
:ok
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("#{__MODULE__} failed to update_online: #{inspect(error)}")
|
||||
|
||||
if is_nil(WandererApp.Cache.lookup("character:#{character_id}:online_error_time")) do
|
||||
WandererApp.Cache.insert(
|
||||
"character:#{character_id}:online_error_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, :skipped}
|
||||
end
|
||||
end
|
||||
|
||||
def update_online(_), do: {:error, :skipped}
|
||||
|
||||
def check_online_errors(character_id) do
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:online_error_time")
|
||||
|> case do
|
||||
nil ->
|
||||
:skip
|
||||
|
||||
error_time ->
|
||||
duration = DateTime.diff(DateTime.utc_now(), error_time, :second)
|
||||
|
||||
if duration >= @online_error_timeout do
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_error_time")
|
||||
WandererApp.Character.update_character(character_id, %{online: false})
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, %{
|
||||
is_online: false
|
||||
})
|
||||
|
||||
:ok
|
||||
else
|
||||
:skip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_wallet(character_id) do
|
||||
character_id
|
||||
|> WandererApp.Character.get_character()
|
||||
@@ -344,7 +556,9 @@ defmodule WandererApp.Character.Tracker do
|
||||
|> WandererApp.Character.can_track_wallet?()
|
||||
|> case do
|
||||
true ->
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:wallet_forbidden")
|
||||
(WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:wallet_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
|
||||
|> case do
|
||||
true ->
|
||||
{:error, :skipped}
|
||||
@@ -353,8 +567,7 @@ defmodule WandererApp.Character.Tracker do
|
||||
case WandererApp.Esi.get_character_wallet(eve_id,
|
||||
params: %{datasource: "tranquility"},
|
||||
access_token: access_token,
|
||||
character_id: character_id,
|
||||
refresh_token?: true
|
||||
character_id: character_id
|
||||
) do
|
||||
{:ok, result} ->
|
||||
{:ok, state} = WandererApp.Character.get_character_state(character_id)
|
||||
@@ -362,8 +575,8 @@ defmodule WandererApp.Character.Tracker do
|
||||
|
||||
:ok
|
||||
|
||||
{:error, :forbidden} ->
|
||||
Logger.warning("#{__MODULE__} failed to _update_wallet: forbidden")
|
||||
{:error, error} when error in [:forbidden, :not_found, :timeout] ->
|
||||
Logger.warning("#{__MODULE__} failed to update_wallet: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:wallet_forbidden",
|
||||
@@ -371,11 +584,42 @@ defmodule WandererApp.Character.Tracker do
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
{:error, :forbidden}
|
||||
{:error, :skipped}
|
||||
|
||||
{:error, :error_limited, headers} ->
|
||||
reset_timeout = get_reset_timeout(headers)
|
||||
|
||||
Logger.warning(".")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:wallet_forbidden",
|
||||
true,
|
||||
ttl: reset_timeout
|
||||
)
|
||||
|
||||
{:error, :skipped}
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("#{__MODULE__} failed to _update_wallet: #{inspect(error)}")
|
||||
{:error, error}
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:wallet_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
{:error, :skipped}
|
||||
|
||||
error ->
|
||||
Logger.error("#{__MODULE__} failed to _update_wallet: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:wallet_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
{:error, :skipped}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -389,83 +633,99 @@ defmodule WandererApp.Character.Tracker do
|
||||
end
|
||||
|
||||
defp update_alliance(%{character_id: character_id} = state, alliance_id) do
|
||||
alliance_id
|
||||
|> WandererApp.Esi.get_alliance_info()
|
||||
(WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
|
||||
|> case do
|
||||
{:ok, %{"name" => alliance_name, "ticker" => alliance_ticker}} ->
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
|
||||
character_update = %{
|
||||
alliance_id: alliance_id,
|
||||
alliance_name: alliance_name,
|
||||
alliance_ticker: alliance_ticker
|
||||
}
|
||||
|
||||
{:ok, _character} =
|
||||
WandererApp.Api.Character.update_alliance(character, character_update)
|
||||
|
||||
WandererApp.Character.update_character(character_id, character_update)
|
||||
|
||||
@pubsub_client.broadcast(
|
||||
WandererApp.PubSub,
|
||||
"character:#{character_id}:alliance",
|
||||
{:character_alliance, {character_id, character_update}}
|
||||
)
|
||||
|
||||
true ->
|
||||
state
|
||||
|
||||
_error ->
|
||||
Logger.error("Failed to get alliance info for #{alliance_id}")
|
||||
state
|
||||
_ ->
|
||||
alliance_id
|
||||
|> WandererApp.Esi.get_alliance_info()
|
||||
|> case do
|
||||
{:ok, %{"name" => alliance_name, "ticker" => alliance_ticker}} ->
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
|
||||
character_update = %{
|
||||
alliance_id: alliance_id,
|
||||
alliance_name: alliance_name,
|
||||
alliance_ticker: alliance_ticker
|
||||
}
|
||||
|
||||
{:ok, _character} =
|
||||
WandererApp.Api.Character.update_alliance(character, character_update)
|
||||
|
||||
WandererApp.Character.update_character(character_id, character_update)
|
||||
|
||||
@pubsub_client.broadcast(
|
||||
WandererApp.PubSub,
|
||||
"character:#{character_id}:alliance",
|
||||
{:character_alliance, {character_id, character_update}}
|
||||
)
|
||||
|
||||
state
|
||||
|
||||
_error ->
|
||||
Logger.error("Failed to get alliance info for #{alliance_id}")
|
||||
state
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp update_corporation(%{character_id: character_id} = state, corporation_id) do
|
||||
corporation_id
|
||||
|> WandererApp.Esi.get_corporation_info()
|
||||
(WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden") ||
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
|
||||
|> case do
|
||||
{:ok, %{"name" => corporation_name, "ticker" => corporation_ticker} = corporation_info} ->
|
||||
alliance_id = Map.get(corporation_info, "alliance_id")
|
||||
true ->
|
||||
state
|
||||
|
||||
{:ok, character} =
|
||||
WandererApp.Character.get_character(character_id)
|
||||
_ ->
|
||||
corporation_id
|
||||
|> WandererApp.Esi.get_corporation_info()
|
||||
|> case do
|
||||
{:ok, %{"name" => corporation_name, "ticker" => corporation_ticker} = corporation_info} ->
|
||||
alliance_id = Map.get(corporation_info, "alliance_id")
|
||||
|
||||
character_update = %{
|
||||
corporation_id: corporation_id,
|
||||
corporation_name: corporation_name,
|
||||
corporation_ticker: corporation_ticker,
|
||||
alliance_id: alliance_id
|
||||
}
|
||||
{:ok, character} =
|
||||
WandererApp.Character.get_character(character_id)
|
||||
|
||||
{:ok, _character} =
|
||||
WandererApp.Api.Character.update_corporation(character, character_update)
|
||||
|
||||
WandererApp.Character.update_character(character_id, character_update)
|
||||
|
||||
@pubsub_client.broadcast(
|
||||
WandererApp.PubSub,
|
||||
"character:#{character_id}:corporation",
|
||||
{:character_corporation,
|
||||
{character_id,
|
||||
%{
|
||||
character_update = %{
|
||||
corporation_id: corporation_id,
|
||||
corporation_name: corporation_name,
|
||||
corporation_ticker: corporation_ticker
|
||||
}}}
|
||||
)
|
||||
corporation_ticker: corporation_ticker,
|
||||
alliance_id: alliance_id
|
||||
}
|
||||
|
||||
state
|
||||
|> Map.merge(%{alliance_id: alliance_id, corporation_id: corporation_id})
|
||||
|> maybe_update_alliance()
|
||||
{:ok, _character} =
|
||||
WandererApp.Api.Character.update_corporation(character, character_update)
|
||||
|
||||
error ->
|
||||
Logger.warning(
|
||||
"Failed to get corporation info for character #{character_id}: #{inspect(error)}",
|
||||
character_id: character_id,
|
||||
corporation_id: corporation_id
|
||||
)
|
||||
WandererApp.Character.update_character(character_id, character_update)
|
||||
|
||||
state
|
||||
@pubsub_client.broadcast(
|
||||
WandererApp.PubSub,
|
||||
"character:#{character_id}:corporation",
|
||||
{:character_corporation,
|
||||
{character_id,
|
||||
%{
|
||||
corporation_id: corporation_id,
|
||||
corporation_name: corporation_name,
|
||||
corporation_ticker: corporation_ticker
|
||||
}}}
|
||||
)
|
||||
|
||||
state
|
||||
|> Map.merge(%{alliance_id: alliance_id, corporation_id: corporation_id})
|
||||
|> maybe_update_alliance()
|
||||
|
||||
error ->
|
||||
Logger.warning(
|
||||
"Failed to get corporation info for character #{character_id}: #{inspect(error)}",
|
||||
character_id: character_id,
|
||||
corporation_id: corporation_id
|
||||
)
|
||||
|
||||
state
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -475,7 +735,8 @@ defmodule WandererApp.Character.Tracker do
|
||||
} =
|
||||
state,
|
||||
ship
|
||||
) do
|
||||
)
|
||||
when is_non_struct_map(ship) do
|
||||
ship_type_id = Map.get(ship, "ship_type_id")
|
||||
ship_name = Map.get(ship, "ship_name")
|
||||
|
||||
@@ -499,6 +760,12 @@ defmodule WandererApp.Character.Tracker do
|
||||
state
|
||||
end
|
||||
|
||||
defp maybe_update_ship(
|
||||
state,
|
||||
_ship
|
||||
),
|
||||
do: state
|
||||
|
||||
defp maybe_update_location(
|
||||
%{
|
||||
character_id: character_id
|
||||
@@ -508,32 +775,12 @@ defmodule WandererApp.Character.Tracker do
|
||||
) do
|
||||
location = get_location(location)
|
||||
|
||||
if not is_location_started?(character_id) do
|
||||
WandererApp.Cache.lookup!("character:#{character_id}:start_solar_system_id", nil)
|
||||
|> case do
|
||||
nil ->
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:start_solar_system_id",
|
||||
location.solar_system_id
|
||||
)
|
||||
|
||||
start_solar_system_id ->
|
||||
if location.solar_system_id != start_solar_system_id do
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:location_started",
|
||||
true
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
{:ok,
|
||||
%{solar_system_id: solar_system_id, structure_id: structure_id, station_id: station_id} =
|
||||
character} =
|
||||
WandererApp.Character.get_character(character_id)
|
||||
|
||||
(not is_location_started?(character_id) ||
|
||||
is_location_updated?(location, solar_system_id, structure_id, station_id))
|
||||
is_location_updated?(location, solar_system_id, structure_id, station_id)
|
||||
|> case do
|
||||
true ->
|
||||
{:ok, _character} = WandererApp.Api.Character.update_location(character, location)
|
||||
@@ -548,13 +795,6 @@ defmodule WandererApp.Character.Tracker do
|
||||
state
|
||||
end
|
||||
|
||||
defp is_location_started?(character_id),
|
||||
do:
|
||||
WandererApp.Cache.lookup!(
|
||||
"character:#{character_id}:location_started",
|
||||
false
|
||||
)
|
||||
|
||||
defp is_location_updated?(
|
||||
%{
|
||||
solar_system_id: new_solar_system_id,
|
||||
@@ -692,16 +932,31 @@ defmodule WandererApp.Character.Tracker do
|
||||
defp maybe_update_active_maps(
|
||||
%{character_id: character_id, active_maps: active_maps} =
|
||||
state,
|
||||
%{map_id: map_id, track: true} = _track_settings
|
||||
%{map_id: map_id, track: true} = track_settings
|
||||
) do
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:map:#{map_id}:tracking_start_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
if not Enum.member?(active_maps, map_id) do
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:map:#{map_id}:tracking_start_time",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
|
||||
WandererApp.Cache.take("character:#{character_id}:last_active_time")
|
||||
WandererApp.Cache.put(
|
||||
"map:#{map_id}:character:#{character_id}:start_solar_system_id",
|
||||
track_settings |> Map.get(:solar_system_id)
|
||||
)
|
||||
|
||||
%{state | active_maps: [map_id | active_maps] |> Enum.uniq()}
|
||||
WandererApp.Cache.delete("map:#{map_id}:character:#{character_id}:solar_system_id")
|
||||
WandererApp.Cache.delete("map:#{map_id}:character:#{character_id}:station_id")
|
||||
WandererApp.Cache.delete("map:#{map_id}:character:#{character_id}:structure_id")
|
||||
|
||||
WandererApp.Cache.take("character:#{character_id}:last_active_time")
|
||||
|
||||
%{state | active_maps: [map_id | active_maps]}
|
||||
else
|
||||
WandererApp.Cache.take("character:#{character_id}:last_active_time")
|
||||
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_update_active_maps(
|
||||
|
||||
@@ -13,8 +13,9 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
}
|
||||
|
||||
@garbage_collection_interval :timer.minutes(15)
|
||||
@untrack_characters_interval :timer.minutes(5)
|
||||
@inactive_character_timeout :timer.minutes(5)
|
||||
@untrack_characters_interval :timer.minutes(1)
|
||||
@inactive_character_timeout :timer.minutes(10)
|
||||
@untrack_character_timeout :timer.minutes(10)
|
||||
|
||||
@logger Application.compile_env(:wanderer_app, :logger)
|
||||
|
||||
@@ -77,8 +78,6 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
Logger.debug(fn -> "Shutting down character tracker: #{inspect(character_id)}" end)
|
||||
|
||||
WandererApp.Cache.delete("character:#{character_id}:last_active_time")
|
||||
WandererApp.Cache.delete("character:#{character_id}:location_started")
|
||||
WandererApp.Cache.delete("character:#{character_id}:start_solar_system_id")
|
||||
WandererApp.Character.delete_character_state(character_id)
|
||||
|
||||
tracked_characters =
|
||||
@@ -109,32 +108,49 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
} = track_settings
|
||||
) do
|
||||
if track do
|
||||
WandererApp.Cache.insert_or_update(
|
||||
"character_untrack_queue",
|
||||
[],
|
||||
fn untrack_queue ->
|
||||
untrack_queue
|
||||
|> Enum.reject(fn {m_id, c_id} -> m_id == map_id and c_id == character_id end)
|
||||
end
|
||||
)
|
||||
remove_from_untrack_queue(map_id, character_id)
|
||||
|
||||
{:ok, character_state} =
|
||||
WandererApp.Character.Tracker.update_settings(character_id, track_settings)
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, character_state)
|
||||
else
|
||||
WandererApp.Cache.insert_or_update(
|
||||
"character_untrack_queue",
|
||||
[{map_id, character_id}],
|
||||
fn untrack_queue ->
|
||||
[{map_id, character_id} | untrack_queue] |> Enum.uniq()
|
||||
end
|
||||
)
|
||||
add_to_untrack_queue(map_id, character_id)
|
||||
end
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def add_to_untrack_queue(map_id, character_id) do
|
||||
if not WandererApp.Cache.has_key?("#{map_id}:#{character_id}:untrack_requested") do
|
||||
WandererApp.Cache.insert(
|
||||
"#{map_id}:#{character_id}:untrack_requested",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
end
|
||||
|
||||
WandererApp.Cache.insert_or_update(
|
||||
"character_untrack_queue",
|
||||
[{map_id, character_id}],
|
||||
fn untrack_queue ->
|
||||
[{map_id, character_id} | untrack_queue] |> Enum.uniq()
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
def remove_from_untrack_queue(map_id, character_id) do
|
||||
WandererApp.Cache.delete("#{map_id}:#{character_id}:untrack_requested")
|
||||
|
||||
WandererApp.Cache.insert_or_update(
|
||||
"character_untrack_queue",
|
||||
[],
|
||||
fn untrack_queue ->
|
||||
untrack_queue
|
||||
|> Enum.reject(fn {m_id, c_id} -> m_id == map_id and c_id == character_id end)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
def get_characters(
|
||||
state,
|
||||
_opts \\ []
|
||||
@@ -189,7 +205,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
end,
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task,
|
||||
timeout: :timer.seconds(15)
|
||||
timeout: :timer.seconds(60)
|
||||
)
|
||||
|> Enum.map(fn result ->
|
||||
case result do
|
||||
@@ -210,17 +226,52 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
) do
|
||||
Process.send_after(self(), :untrack_characters, @untrack_characters_interval)
|
||||
|
||||
WandererApp.Cache.get_and_remove!("character_untrack_queue", [])
|
||||
WandererApp.Cache.lookup!("character_untrack_queue", [])
|
||||
|> Task.async_stream(
|
||||
fn {map_id, character_id} ->
|
||||
if not character_is_present(map_id, character_id) do
|
||||
untrack_timeout_reached =
|
||||
if WandererApp.Cache.has_key?("#{map_id}:#{character_id}:untrack_requested") do
|
||||
untrack_requested =
|
||||
WandererApp.Cache.lookup!(
|
||||
"#{map_id}:#{character_id}:untrack_requested",
|
||||
DateTime.utc_now()
|
||||
)
|
||||
|
||||
duration = DateTime.diff(DateTime.utc_now(), untrack_requested, :millisecond)
|
||||
duration >= @untrack_character_timeout
|
||||
else
|
||||
false
|
||||
end
|
||||
|
||||
Logger.debug(fn -> "Untrack timeout reached: #{inspect(untrack_timeout_reached)}" end)
|
||||
|
||||
if untrack_timeout_reached do
|
||||
remove_from_untrack_queue(map_id, character_id)
|
||||
|
||||
WandererApp.Cache.delete("map:#{map_id}:character:#{character_id}:solar_system_id")
|
||||
WandererApp.Cache.delete("map:#{map_id}:character:#{character_id}:station_id")
|
||||
WandererApp.Cache.delete("map:#{map_id}:character:#{character_id}:structure_id")
|
||||
|
||||
{:ok, character_state} =
|
||||
WandererApp.Character.Tracker.update_settings(character_id, %{
|
||||
map_id: map_id,
|
||||
track: false
|
||||
})
|
||||
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
|
||||
{:ok, _updated} =
|
||||
WandererApp.MapCharacterSettingsRepo.update(map_id, character_id, %{
|
||||
ship: character.ship,
|
||||
ship_name: character.ship_name,
|
||||
ship_item_id: character.ship_item_id,
|
||||
solar_system_id: character.solar_system_id,
|
||||
structure_id: character.structure_id,
|
||||
station_id: character.station_id
|
||||
})
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, character_state)
|
||||
WandererApp.Map.Server.Impl.broadcast!(map_id, :untrack_character, character_id)
|
||||
end
|
||||
end,
|
||||
max_concurrency: System.schedulers_online(),
|
||||
@@ -246,7 +297,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
def handle_info(_event, state),
|
||||
do: state
|
||||
|
||||
defp character_is_present(map_id, character_id) do
|
||||
def character_is_present(map_id, character_id) do
|
||||
{:ok, presence_character_ids} =
|
||||
WandererApp.Cache.lookup("map_#{map_id}:presence_character_ids", [])
|
||||
|
||||
|
||||
@@ -16,14 +16,19 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
@registry :tracker_pool_registry
|
||||
@unique_registry :unique_tracker_pool_registry
|
||||
|
||||
@update_location_interval :timer.seconds(2)
|
||||
@update_location_interval :timer.seconds(1)
|
||||
@update_online_interval :timer.seconds(5)
|
||||
@check_online_errors_interval :timer.seconds(30)
|
||||
@check_offline_characters_interval :timer.minutes(2)
|
||||
@check_online_errors_interval :timer.minutes(1)
|
||||
@check_ship_errors_interval :timer.minutes(1)
|
||||
@check_location_errors_interval :timer.minutes(1)
|
||||
@update_ship_interval :timer.seconds(2)
|
||||
@update_info_interval :timer.minutes(1)
|
||||
@update_wallet_interval :timer.minutes(1)
|
||||
@inactive_character_timeout :timer.minutes(5)
|
||||
|
||||
@pause_tracking_timeout :timer.minutes(60 * 24)
|
||||
|
||||
@logger Application.compile_env(:wanderer_app, :logger)
|
||||
|
||||
def new(), do: __struct__()
|
||||
@@ -49,7 +54,15 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
# end)
|
||||
|
||||
tracked_ids
|
||||
|> Enum.each(fn id -> Cachex.put(@cache, id, uuid) end)
|
||||
|> Enum.each(fn id ->
|
||||
# WandererApp.Cache.put(
|
||||
# "character:#{id}:tracking_paused",
|
||||
# true,
|
||||
# ttl: @pause_tracking_timeout
|
||||
# )
|
||||
|
||||
Cachex.put(@cache, id, uuid)
|
||||
end)
|
||||
|
||||
state =
|
||||
%{
|
||||
@@ -75,10 +88,15 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
[tracked_id | r_tracked_ids]
|
||||
end)
|
||||
|
||||
# WandererApp.Cache.put(
|
||||
# "character:#{tracked_id}:tracking_paused",
|
||||
# true,
|
||||
# ttl: @pause_tracking_timeout
|
||||
# )
|
||||
|
||||
# Cachex.get_and_update(@cache, :tracked_characters, fn ids ->
|
||||
# {:commit, ids ++ [tracked_id]}
|
||||
# end)
|
||||
|
||||
Cachex.put(@cache, tracked_id, uuid)
|
||||
|
||||
{:noreply, %{state | characters: [tracked_id | characters]}}
|
||||
@@ -96,7 +114,7 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
# Cachex.get_and_update(@cache, :tracked_characters, fn ids ->
|
||||
# {:commit, ids |> Enum.reject(fn id -> id == tracked_id end)}
|
||||
# end)
|
||||
|
||||
#
|
||||
Cachex.del(@cache, tracked_id)
|
||||
|
||||
{:noreply, %{state | characters: characters |> Enum.reject(fn id -> id == tracked_id end)}}
|
||||
@@ -115,7 +133,10 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
)
|
||||
|
||||
Process.send_after(self(), :update_online, 100)
|
||||
Process.send_after(self(), :check_online_errors, @check_online_errors_interval)
|
||||
Process.send_after(self(), :check_online_errors, :timer.seconds(60))
|
||||
Process.send_after(self(), :check_ship_errors, :timer.seconds(90))
|
||||
Process.send_after(self(), :check_location_errors, :timer.seconds(120))
|
||||
Process.send_after(self(), :check_offline_characters, @check_offline_characters_interval)
|
||||
Process.send_after(self(), :update_location, 300)
|
||||
Process.send_after(self(), :update_ship, 500)
|
||||
Process.send_after(self(), :update_info, 1500)
|
||||
@@ -155,12 +176,20 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
) do
|
||||
Process.send_after(self(), :update_online, @update_online_interval)
|
||||
|
||||
characters
|
||||
|> Enum.map(fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_online, [
|
||||
character_id
|
||||
])
|
||||
end)
|
||||
try do
|
||||
characters
|
||||
|> Enum.map(fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_online, [
|
||||
character_id
|
||||
])
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] update_online => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -174,14 +203,66 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
) do
|
||||
Process.send_after(self(), :update_online, @update_online_interval)
|
||||
|
||||
characters
|
||||
|> Enum.each(fn character_id ->
|
||||
WandererApp.Character.update_character(character_id, %{online: false})
|
||||
try do
|
||||
characters
|
||||
|> Enum.each(fn character_id ->
|
||||
WandererApp.Character.update_character(character_id, %{online: false})
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, %{
|
||||
is_online: false
|
||||
})
|
||||
end)
|
||||
WandererApp.Character.update_character_state(character_id, %{
|
||||
is_online: false
|
||||
})
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] update_online => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(
|
||||
:check_offline_characters,
|
||||
%{
|
||||
characters: characters
|
||||
} =
|
||||
state
|
||||
) do
|
||||
Process.send_after(self(), :check_offline_characters, @check_offline_characters_interval)
|
||||
|
||||
try do
|
||||
characters
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
if WandererApp.Character.can_pause_tracking?(character_id) do
|
||||
WandererApp.TaskWrapper.start_link(
|
||||
WandererApp.Character.Tracker,
|
||||
:check_offline,
|
||||
[
|
||||
character_id
|
||||
]
|
||||
)
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> @logger.error("Error in check_offline: #{inspect(reason)}")
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] check_offline => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -195,21 +276,113 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
) do
|
||||
Process.send_after(self(), :check_online_errors, @check_online_errors_interval)
|
||||
|
||||
characters
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :check_online_errors, [
|
||||
character_id
|
||||
])
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> @logger.error("Error in check_online_errors: #{inspect(reason)}")
|
||||
end)
|
||||
try do
|
||||
characters
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(
|
||||
WandererApp.Character.Tracker,
|
||||
:check_online_errors,
|
||||
[
|
||||
character_id
|
||||
]
|
||||
)
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> @logger.error("Error in check_online_errors: #{inspect(reason)}")
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] check_online_errors => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(
|
||||
:check_ship_errors,
|
||||
%{
|
||||
characters: characters
|
||||
} =
|
||||
state
|
||||
) do
|
||||
Process.send_after(self(), :check_ship_errors, @check_ship_errors_interval)
|
||||
|
||||
try do
|
||||
characters
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(
|
||||
WandererApp.Character.Tracker,
|
||||
:check_ship_errors,
|
||||
[
|
||||
character_id
|
||||
]
|
||||
)
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> @logger.error("Error in check_ship_errors: #{inspect(reason)}")
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] check_ship_errors => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(
|
||||
:check_location_errors,
|
||||
%{
|
||||
characters: characters
|
||||
} =
|
||||
state
|
||||
) do
|
||||
Process.send_after(self(), :check_location_errors, @check_location_errors_interval)
|
||||
|
||||
try do
|
||||
characters
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(
|
||||
WandererApp.Character.Tracker,
|
||||
:check_location_errors,
|
||||
[
|
||||
character_id
|
||||
]
|
||||
)
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> @logger.error("Error in check_location_errors: #{inspect(reason)}")
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] check_location_errors => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -224,12 +397,20 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
) do
|
||||
Process.send_after(self(), :update_location, @update_location_interval)
|
||||
|
||||
characters
|
||||
|> Enum.map(fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_location, [
|
||||
character_id
|
||||
])
|
||||
end)
|
||||
try do
|
||||
characters
|
||||
|> Enum.map(fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_location, [
|
||||
character_id
|
||||
])
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] update_location => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -253,12 +434,20 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
) do
|
||||
Process.send_after(self(), :update_ship, @update_ship_interval)
|
||||
|
||||
characters
|
||||
|> Enum.map(fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_ship, [
|
||||
character_id
|
||||
])
|
||||
end)
|
||||
try do
|
||||
characters
|
||||
|> Enum.map(fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_ship, [
|
||||
character_id
|
||||
])
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] update_ship => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -282,21 +471,29 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
) do
|
||||
Process.send_after(self(), :update_info, @update_info_interval)
|
||||
|
||||
characters
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_info, [
|
||||
character_id
|
||||
])
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> @logger.error("Error in update_info: #{inspect(reason)}")
|
||||
end)
|
||||
try do
|
||||
characters
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_info, [
|
||||
character_id
|
||||
])
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> Logger.error("Error in update_info: #{inspect(reason)}")
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] update_info => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -320,21 +517,29 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
) do
|
||||
Process.send_after(self(), :update_wallet, @update_wallet_interval)
|
||||
|
||||
characters
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_wallet, [
|
||||
character_id
|
||||
])
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> @logger.error("Error in update_wallet: #{inspect(reason)}")
|
||||
end)
|
||||
try do
|
||||
characters
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character.Tracker, :update_wallet, [
|
||||
character_id
|
||||
])
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> Logger.error("Error in update_wallet: #{inspect(reason)}")
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("""
|
||||
[Tracker Pool] update_wallet => exception: #{Exception.message(e)}
|
||||
#{Exception.format_stacktrace(__STACKTRACE__)}
|
||||
""")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user