Compare commits

...

101 Commits

Author SHA1 Message Date
CI
57f73684e8 chore: release version v1.15.2
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-07 21:45:12 +00:00
Dmitry Popov
7833cdebb2 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-07 22:44:22 +01:00
Dmitry Popov
67a5ae2985 chore: release version v1.15.0 2024-11-07 22:44:19 +01:00
CI
f58c52d26b chore: release version v1.15.1 2024-11-07 21:42:31 +00:00
Dmitry Popov
41e7739461 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-07 22:41:52 +01:00
Dmitry Popov
332152b677 fix(Dev): Update .devcontainer instructions 2024-11-07 22:41:43 +01:00
CI
85b49fe1f0 chore: release version v1.15.0 2024-11-07 21:40:09 +00:00
Dmitry Popov
e7924532be feat(Connections): Add connection mark EOL time (#56) 2024-11-08 01:39:44 +04:00
CI
475d950ad6 chore: release version v1.14.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-06 14:13:14 +00:00
Dmitry Popov
e6cfb29c6f fix(Core): Fix character tracking permissions 2024-11-06 15:12:44 +01:00
CI
dee6e86db1 chore: release version v1.14.0
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-05 07:23:19 +00:00
Dmitry Popov
72f088331f Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-05 08:22:50 +01:00
Dmitry Popov
bbf536d10e feat(ACL): Add an ability to assign member role without DnD 2024-11-05 08:22:46 +01:00
CI
149ac98297 chore: release version v1.13.12
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-04 07:24:03 +00:00
Dmitry Popov
b90a2910c9 fix(Map): Fix system revert issues 2024-11-04 08:23:26 +01:00
CI
c4da8a3a8d chore: release version v1.13.11 2024-11-02 21:31:48 +00:00
Dmitry Popov
3ca75583d2 fix(Map): Fix system revert issues 2024-11-02 22:31:20 +01:00
CI
5f4607ae6f chore: release version v1.13.10 2024-11-01 14:54:49 +00:00
Dmitry Popov
d880c6873f Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/wanderer_app/map/map_server_impl.ex
2024-11-01 15:53:05 +01:00
Dmitry Popov
937649b2ed fix(Map): Fix system revert issues 2024-11-01 15:51:15 +01:00
CI
78e912c886 chore: release version v1.13.9 2024-11-01 10:55:05 +00:00
Dmitry Popov
696c7d2cd1 Map events refactoring (#41)
* refactor(Map): Map event handling refactoring
2024-11-01 14:54:34 +04:00
CI
49c0cb026b chore: release version v1.13.8 2024-10-28 16:21:55 +00:00
Dmitry Popov
7091341b4b chore: release version v1.13.7 2024-10-28 17:21:26 +01:00
CI
8795ce6af3 chore: release version v1.13.7 2024-10-28 16:01:28 +00:00
Dmitry Popov
239b34d15a chore: release version v1.13.6 2024-10-28 17:00:53 +01:00
CI
729a5ad1a9 chore: release version v1.13.6 2024-10-28 15:44:15 +00:00
Dmitry Popov
8febc2476b Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-28 16:43:45 +01:00
Dmitry Popov
84b1317927 chore: release version v1.12.11 2024-10-28 16:43:42 +01:00
CI
bfb504e5db chore: release version v1.13.5 2024-10-28 14:31:58 +00:00
Dmitry Popov
9975deacfb Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-28 15:31:28 +01:00
Dmitry Popov
f77f071003 chore: release version v1.12.11 2024-10-28 15:31:25 +01:00
CI
4a8d55e83d chore: release version v1.13.4 2024-10-28 13:10:01 +00:00
Dmitry Popov
9a99f40e2a Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-28 14:09:34 +01:00
Dmitry Popov
428fa8035c chore: release version v1.12.11 2024-10-28 14:09:31 +01:00
CI
745f3dee17 chore: release version v1.13.3 2024-10-28 10:59:00 +00:00
Dmitry Popov
9907cc1875 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-28 11:58:33 +01:00
Dmitry Popov
130cd780a2 chore: release version v1.12.11 2024-10-28 11:58:30 +01:00
CI
a808e5d1a5 chore: release version v1.13.2 2024-10-28 10:52:13 +00:00
Dmitry Popov
b926117e26 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-28 11:51:45 +01:00
Dmitry Popov
fdf238accf chore: release version v1.12.11 2024-10-28 11:51:42 +01:00
CI
4e1c27e8a3 chore: release version v1.13.1 2024-10-28 10:35:24 +00:00
Dmitry Popov
a3e51a0ac5 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-28 11:34:55 +01:00
Dmitry Popov
d27bb0d54f chore: release version v1.12.11 2024-10-28 11:34:52 +01:00
CI
f6a750f06b chore: release version v1.13.0 2024-10-28 10:18:27 +00:00
Dmitry Popov
c4e2f63e69 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-28 11:17:52 +01:00
Dmitry Popov
675ffc8f42 feat(Core): Use ESI /characters/affiliation API
Use ESI /characters/affiliation to fetch characters corporation changes
(1H cached instead of 7D)
2024-10-28 11:17:49 +01:00
CI
cdc4221175 chore: release version v1.12.11 2024-10-25 12:17:07 +00:00
Dmitry Popov
843f3363fd Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-25 14:16:32 +02:00
Dmitry Popov
17653a6374 chore: release version v1.12.6 2024-10-25 14:16:28 +02:00
CI
7d860533a0 chore: release version v1.12.10 2024-10-24 20:01:03 +00:00
Dmitry Popov
0c751b3ced Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-24 22:00:36 +02:00
Dmitry Popov
80a522ab06 chore: release version v1.12.6 2024-10-24 22:00:33 +02:00
CI
0718d76e00 chore: release version v1.12.9 2024-10-24 19:49:25 +00:00
Dmitry Popov
a4887c5358 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-24 21:48:52 +02:00
Dmitry Popov
2ad5e122ff chore: release version v1.12.6 2024-10-24 21:48:50 +02:00
CI
832d874a0e chore: release version v1.12.8 2024-10-24 19:18:49 +00:00
Dmitry Popov
6a133d4dbd Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-24 21:18:07 +02:00
Dmitry Popov
d96dfa63c9 chore: release version v1.12.6 2024-10-24 21:18:03 +02:00
CI
d5c8c05426 chore: release version v1.12.7 2024-10-24 19:12:47 +00:00
Dmitry Popov
b6bb4647c7 chore: release version v1.12.6 2024-10-24 21:12:13 +02:00
CI
a81f06b743 chore: release version v1.12.6 2024-10-24 09:10:46 +00:00
Dmitry Popov
cb41a33546 Custom signatures (#37)
* feat(signatures): Add custom info to system signatures

* feat(connections): Add custom info to system connections

* feat(Map): Add system signature type

* feat(Map): Update wormhole types info

* feat(Map): Add undo action for removed systems

* feat(Map): Delete systems on Backspace hotkey

* feat(Map): Update k-space systems background & styles

* feat(Map): Update systems status background styles

* feat(Map): add support for new wh type data. add signatures settings modal menu; reworked signatures widget - was added info of wormhole;

---------

Co-authored-by: achichenkov <aleksei.chichenkov@telleqt.ai>
2024-10-24 13:10:17 +04:00
CI
005068de9c chore: release version v1.12.5 2024-10-22 08:18:22 +00:00
Dmitry Popov
d8c7b1e070 Auto delete connections (#38)
* feat(Map): Auto delete linked connections

* feat(Map): Auto delete linked connections
2024-10-22 12:17:53 +04:00
CI
4835dfcc42 chore: release version v1.12.4 2024-10-21 11:11:42 +00:00
Dmitry Popov
15bceb09a2 chore: release version v1.12.3 2024-10-21 13:11:13 +02:00
Dmitry Popov
13e818abfd fix(Map): Fix systems cleanup 2024-10-21 13:02:42 +02:00
CI
9c5f6049b5 chore: release version v1.12.3 2024-10-18 07:10:35 +00:00
Dmitry Popov
2095b619a4 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-18 11:10:06 +04:00
Dmitry Popov
df155cbc1b fix(Map): Fix regression issues 2024-10-18 11:10:02 +04:00
CI
3781729fd1 chore: release version v1.12.2 2024-10-16 21:12:27 +00:00
Dmitry Popov
d03c634ec0 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-17 01:11:41 +04:00
Dmitry Popov
93c979c218 chore: release version v1.12.0 2024-10-17 01:11:37 +04:00
CI
90fef94583 chore: release version v1.12.1 2024-10-16 20:11:16 +00:00
Dmitry Popov
0b8eec2263 fix(Map): Fix system add error after map page refresh 2024-10-17 00:10:36 +04:00
CI
9511af4e6d chore: release version v1.12.0 2024-10-16 14:12:30 +00:00
Aleksei Chichenkov
7deaf1fd9f Merge pull request #36 from wanderer-industries/refactor-settings
feat(Map): Prettify user settings
2024-10-16 17:12:04 +03:00
achichenkov
43cc5bd520 feat(Map): Prettify user settings
Fixes #35
2024-10-16 17:04:50 +03:00
CI
68b58aa520 chore: release version v1.11.5 2024-10-16 12:46:16 +00:00
Dmitry Popov
dbadd09af3 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-16 16:45:48 +04:00
Dmitry Popov
fcbe2c754f chore: release version v1.11.1 2024-10-16 16:45:44 +04:00
CI
ad4580677b chore: release version v1.11.4 2024-10-16 11:48:48 +00:00
Dmitry Popov
01a6cc7d92 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-16 15:48:20 +04:00
Dmitry Popov
95ce95a187 chore: release version v1.11.1 2024-10-16 15:48:16 +04:00
CI
ce8e6fbfb0 chore: release version v1.11.3 2024-10-16 05:51:27 +00:00
Dmitry Popov
a20eaed76b Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-16 09:50:49 +04:00
Dmitry Popov
419af72028 chore: release version v1.11.1 2024-10-16 09:50:45 +04:00
CI
8e499522f6 chore: release version v1.11.2 2024-10-15 22:13:53 +00:00
Dmitry Popov
84321b847e chore: release version v1.11.1 2024-10-16 02:13:18 +04:00
CI
c969a4d465 chore: release version v1.11.1 2024-10-14 14:31:41 +00:00
Dmitry Popov
0e12c850b6 chore: release version v1.11.0 2024-10-14 18:31:12 +04:00
CI
442835dd9b chore: release version v1.11.0 2024-10-14 14:24:17 +00:00
Dmitry Popov
b4ff99cb2e Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-14 18:23:43 +04:00
Dmitry Popov
aa0ecbc998 feat(Map): Add map level option to store custom labels 2024-10-14 18:23:39 +04:00
CI
cc412e93c0 chore: release version v1.10.0 2024-10-13 10:18:23 +00:00
Dmitry Popov
1d36fadbfa Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-13 14:17:43 +04:00
Dmitry Popov
56182bd87d feat(Map): Link signature on splash 2024-10-13 14:17:40 +04:00
CI
d290ff92b3 chore: release version v1.9.0 2024-10-13 10:10:38 +00:00
Dmitry Popov
298c5fd3b8 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-13 14:09:56 +04:00
Dmitry Popov
e365c43781 feat(Map): Link signature on splash 2024-10-13 14:09:53 +04:00
137 changed files with 7558 additions and 4123 deletions

View File

@@ -1,12 +1,7 @@
FROM elixir:1.16-otp-25
FROM elixir:1.17-otp-27
RUN apt update -yq
RUN apt install -yq curl gnupg mc inotify-tools
RUN apt install -yq curl gnupg
RUN apt --fix-broken install
RUN apt remove -y nodejs nodejs-doc
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash -
RUN apt install -y nodejs
RUN npm install --global yarn
RUN mix local.hex --force

View File

@@ -2,7 +2,7 @@ version: "0.1"
services:
db:
image: postgres:14.3
image: postgres:13-alpine
restart: always
environment:
POSTGRES_USER: postgres
@@ -10,13 +10,13 @@ services:
ports:
- "5432:5432"
volumes:
- db:/var/lib/postgresql/data
- db-new:/var/lib/postgresql/data
wanderer:
environment:
PORT: 8000
DB_HOST: db
WEB_APP_URL: "http://localhost:4444"
WEB_APP_URL: "http://localhost:8000"
ERL_AFLAGS: "-kernel shell_history enabled"
build:
context: .
@@ -33,4 +33,4 @@ services:
volumes:
elixir-artifacts: {}
db: {}
db-new: {}

View File

@@ -58,6 +58,8 @@ jobs:
otp: ["27"]
elixir: ["1.17"]
node-version: ["18.x"]
outputs:
commit_hash: ${{ steps.generate-changelog.outputs.commit_hash }}
steps:
- name: Prepare
run: |
@@ -108,16 +110,17 @@ jobs:
run: mix compile
- name: Generate Changelog & Update Tag Version
id: generate-changelog
run: |
git config --global user.name 'CI'
git config --global user.email 'ci@users.noreply.github.com'
mix git_ops.release --force-patch --yes
git push --follow-tags
echo "commit_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
docker:
name: 🛠 Build Docker Images
needs:
- build
needs: build
runs-on: ubuntu-22.04
permissions:
checks: write
@@ -141,6 +144,7 @@ jobs:
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
ref: ${{ needs.build.outputs.commit_hash }}
fetch-depth: 0
- name: Prepare Changelog
@@ -189,6 +193,30 @@ jobs:
- name: Image digest
run: echo ${{ steps.build.outputs.digest }}
- uses: markpatterson27/markdown-to-output@v1
id: extract-changelog
with:
filepath: CHANGELOG.md
- name: Get content
uses: 2428392/gh-truncate-string-action@v1.3.0
id: get-content
with:
stringToTruncate: |
📣 Wanderer new release available 🎉
**Version**: ${{ steps.get-latest-tag.outputs.tag }}
${{ steps.extract-changelog.outputs.body }}
maxLength: 500
truncationSymbol: "…"
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v5.3.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: ${{ steps.get-content.outputs.string }}
create-release:
name: 🏷 Create Release
runs-on: ubuntu-22.04

View File

@@ -2,25 +2,138 @@
<!-- changelog -->
## [v1.8.0](https://github.com/wanderer-industries/wanderer/compare/v1.7.0...v1.8.0) (2024-10-13)
## [v1.15.2](https://github.com/wanderer-industries/wanderer/compare/v1.15.1...v1.15.2) (2024-11-07)
## [v1.15.1](https://github.com/wanderer-industries/wanderer/compare/v1.15.0...v1.15.1) (2024-11-07)
### Bug Fixes:
* Dev: Update .devcontainer instructions
## [v1.15.0](https://github.com/wanderer-industries/wanderer/compare/v1.14.1...v1.15.0) (2024-11-07)
### Features:
* Map: Link signature on splash
* Connections: Add connection mark EOL time (#56)
## [v1.7.0](https://github.com/wanderer-industries/wanderer/compare/v1.6.0...v1.7.0) (2024-10-13)
## [v1.14.1](https://github.com/wanderer-industries/wanderer/compare/v1.14.0...v1.14.1) (2024-11-06)
### Bug Fixes:
* Core: Fix character tracking permissions
## [v1.14.0](https://github.com/wanderer-industries/wanderer/compare/v1.13.12...v1.14.0) (2024-11-05)
### Features:
* Map: Link signature on splash
* ACL: Add an ability to assign member role without DnD
## [v1.6.0](https://github.com/wanderer-industries/wanderer/compare/v1.5.0...v1.6.0) (2024-10-13)
## [v1.13.12](https://github.com/wanderer-industries/wanderer/compare/v1.13.11...v1.13.12) (2024-11-04)
### Bug Fixes:
* Map: Fix system revert issues
## [v1.13.11](https://github.com/wanderer-industries/wanderer/compare/v1.13.10...v1.13.11) (2024-11-02)
### Bug Fixes:
* Map: Fix system revert issues
## [v1.13.10](https://github.com/wanderer-industries/wanderer/compare/v1.13.9...v1.13.10) (2024-11-01)
### Bug Fixes:
* Map: Fix system revert issues
## [v1.13.9](https://github.com/wanderer-industries/wanderer/compare/v1.13.8...v1.13.9) (2024-11-01)
## [v1.13.8](https://github.com/wanderer-industries/wanderer/compare/v1.13.7...v1.13.8) (2024-10-28)
## [v1.13.0](https://github.com/wanderer-industries/wanderer/compare/v1.12.11...v1.13.0) (2024-10-28)
### Features:
* Core: Use ESI /characters/affiliation API
## [v1.12.4](https://github.com/wanderer-industries/wanderer/compare/v1.12.3...v1.12.4) (2024-10-21)
### Bug Fixes:
* Map: Fix systems cleanup
## [v1.12.3](https://github.com/wanderer-industries/wanderer/compare/v1.12.2...v1.12.3) (2024-10-18)
### Bug Fixes:
* Map: Fix regression issues
## [v1.12.1](https://github.com/wanderer-industries/wanderer/compare/v1.12.0...v1.12.1) (2024-10-16)
### Bug Fixes:
* Map: Fix system add error after map page refresh
## [v1.12.0](https://github.com/wanderer-industries/wanderer/compare/v1.11.5...v1.12.0) (2024-10-16)
### Features:
* Map: Prettify user settings
## [v1.11.0](https://github.com/wanderer-industries/wanderer/compare/v1.10.0...v1.11.0) (2024-10-14)
### Features:
* Map: Add map level option to store custom labels
## [v1.10.0](https://github.com/wanderer-industries/wanderer/compare/v1.9.0...v1.10.0) (2024-10-13)
@@ -34,15 +147,6 @@
### Features:
* Map: Follow Character on Map and auto select their current system
## [v1.4.0](https://github.com/wanderer-industries/wanderer/compare/v1.3.6...v1.4.0) (2024-10-11)
### Features:
* Map: Follow Character on Map and auto select their current system
@@ -56,35 +160,6 @@
* Signatures: Signatures update fixes
## [v1.3.5](https://github.com/wanderer-industries/wanderer/compare/v1.3.4...v1.3.5) (2024-10-09)
### Bug Fixes:
* Signatures: Signatures update fixes
## [v1.3.4](https://github.com/wanderer-industries/wanderer/compare/v1.3.3...v1.3.4) (2024-10-09)
## [v1.3.3](https://github.com/wanderer-industries/wanderer/compare/v1.3.2...v1.3.3) (2024-10-08)
## [v1.3.2](https://github.com/wanderer-industries/wanderer/compare/v1.3.1...v1.3.2) (2024-10-07)
## [v1.3.1](https://github.com/wanderer-industries/wanderer/compare/v1.3.0...v1.3.1) (2024-10-07)
## [v1.3.0](https://github.com/wanderer-industries/wanderer/compare/v1.2.10...v1.3.0) (2024-10-07)
@@ -98,26 +173,6 @@
* Map: Revision of sorting from also adding ability to sort all columns
## [v1.2.10](https://github.com/wanderer-industries/wanderer/compare/v1.2.9...v1.2.10) (2024-10-07)
## [v1.2.9](https://github.com/wanderer-industries/wanderer/compare/v1.2.8...v1.2.9) (2024-10-07)
## [v1.2.8](https://github.com/wanderer-industries/wanderer/compare/v1.2.7...v1.2.8) (2024-10-06)
## [v1.2.7](https://github.com/wanderer-industries/wanderer/compare/v1.2.6...v1.2.7) (2024-10-05)
## [v1.2.6](https://github.com/wanderer-industries/wanderer/compare/v1.2.5...v1.2.6) (2024-10-05)
@@ -154,11 +209,6 @@
* Map: Fix map loading after select a different map.
## [v1.2.2](https://github.com/wanderer-industries/wanderer/compare/v1.2.1...v1.2.2) (2024-10-02)
## [v1.2.1](https://github.com/wanderer-industries/wanderer/compare/v1.2.0...v1.2.1) (2024-10-02)
@@ -275,20 +325,10 @@
* docker: Fix DB connection in docker-compose internal network
## [v1.0.7](https://github.com/wanderer-industries/wanderer/compare/v1.0.6...v1.0.7) (2024-09-19)
## [v1.0.6](https://github.com/wanderer-industries/wanderer/compare/v1.0.5...v1.0.6) (2024-09-18)
## [v1.0.5](https://github.com/wanderer-industries/wanderer/compare/v1.0.4...v1.0.5) (2024-09-18)
## [v1.0.4](https://github.com/wanderer-industries/wanderer/compare/v1.0.3...v1.0.4) (2024-09-18)
### Bug Fixes
* core: skip search results for failed character info request
## [v1.0.3](https://github.com/wanderer-industries/wanderer/compare/v1.0.2...v1.0.3) (2024-09-18)
## [v1.0.2](https://github.com/wanderer-industries/wanderer/compare/v1.0.1...v1.0.2) (2024-09-18)
## [v1.0.1](https://github.com/wanderer-industries/wanderer/compare/v1.0.0...v1.0.1) (2024-09-18)

View File

@@ -20,11 +20,11 @@ Interested to learn more? [Check more on our website](https://wanderer.ltd/news)
Wanderer is open source project and we have a free as in beer and self-hosted solution called [Wanderer Community Edition (CE)](https://wanderer.ltd/news/community-edition). Here are the differences between Wanderer and Wanderer CE:
| | Wanderer Cloud | Wanderer Community Edition |
| ------------- | ------------- | ------------- |
| **Infrastructure management** | Easy and convenient. It takes 2 minutes to register your character and create a map. We manage everything so you dont have to worry about anything and can focus on gameplay. | You do it all yourself. You need to get a server and you need to manage your infrastructure. You are responsible for installation, maintenance, upgrades, server capacity, uptime, backup, security, stability, consistency, loading time and so on.|
| **Release schedule** | Continuously developed and improved with new features and updates multiple times per week. | Latest features and improvements won't be immediately available.|
| **Server location** | All visitor data is exclusively processed on EU-owned cloud infrastructure. We keep your site data on a secure, encrypted and green energy powered server in Germany. This ensures that your site data is protected by the strict European Union data privacy laws and ensures compliance with GDPR. Your website data never leaves the EU. | You have full control and can host your instance on any server in any country that you wish. Host it on a server in your basement or host it with any cloud provider wherever you want, even those that are not GDPR compliant.|
| | Wanderer Cloud | Wanderer Community Edition |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Infrastructure management** | Easy and convenient. It takes 2 minutes to register your character and create a map. We manage everything so you dont have to worry about anything and can focus on gameplay. | You do it all yourself. You need to get a server and you need to manage your infrastructure. You are responsible for installation, maintenance, upgrades, server capacity, uptime, backup, security, stability, consistency, loading time and so on. |
| **Release schedule** | Continuously developed and improved with new features and updates multiple times per week. | Latest features and improvements won't be immediately available. |
| **Server location** | All visitor data is exclusively processed on EU-owned cloud infrastructure. We keep your site data on a secure, encrypted and green energy powered server in Germany. This ensures that your site data is protected by the strict European Union data privacy laws and ensures compliance with GDPR. Your website data never leaves the EU. | You have full control and can host your instance on any server in any country that you wish. Host it on a server in your basement or host it with any cloud provider wherever you want, even those that are not GDPR compliant. |
Interested in self-hosting Wanderer CE on your server? Take a look at our [Wanderer CE installation instructions](https://github.com/wanderer-industries/community-edition/).
@@ -54,7 +54,13 @@ Now you can visit [`localhost:8000`](http://localhost:8000) from your browser.
#### Using .devcontainer
- Run devcontainer
- See how to start server in #setup section
- Install additional dependencies inside Dev container
- `root@0d0a785313b6:/app# apt update`
- `root@0d0a785313b6:/app# curl -sL https://deb.nodesource.com/setup_18.x | bash -`
- `root@0d0a785313b6:/app# apt-get install nodejs inotify-tools -y`
- `root@0d0a785313b6:/app# mix setup`
- See how to run server in #Run section
#### Using nix flakes

View File

@@ -85,3 +85,26 @@
}
}
.p-dropdown-label, .p-inputtext {
padding: 0.25rem 0.75rem;
font-size: 14px;
}
.p-dropdown-item {
padding: 0.25rem 0.5rem;
font-size: 14px;
}
.p-dropdown-item-group {
padding: 0.25rem 0.75rem;
font-size: 14px;
}
.p-dropdown-trigger {
width: 14px;
margin: 0 12px;
}
.p-dropdown-empty-message {
padding: 0.25rem 0.5rem;
}

View File

@@ -1,5 +1,5 @@
/* Основной класс диалога */
.p-dialog {
body .p-dialog {
display: flex;
flex-direction: column;
//position: absolute;
@@ -7,11 +7,26 @@
left: 0;
//visibility: hidden;
overflow: hidden;
border-radius: 6px;
border-radius: 2px;
box-shadow: 0 2px 10px 0 rgba(0,0,0,0.2);
transition: box-shadow 0.3s;
background: #fff;
z-index: 1000;
border: 1px solid #212121;
background: var(--surface-h);
color: var(--text-color);
.p-dialog-header {
background: #171717 !important;
color: var(--text-color);
.p-dialog-header-icon:focus-visible {
box-shadow: none !important;
}
}
.p-dialog-footer {
border-top: 1px solid var(--surface-d);
}
}
/* Стиль видимого диалога */
@@ -45,12 +60,12 @@
justify-content: space-between;
padding: 1rem;
background: #f4f4f4;
border-bottom: 1px solid #ddd;
//border-bottom: 1px solid #ddd;
}
/* Содержимое диалога */
.p-dialog-content {
padding: 1rem;
padding: 0.5rem;
overflow-y: auto;
flex: 1;
}
@@ -78,23 +93,3 @@
.p-dialog-header-close .pi {
font-size: 1.25rem;
}
/* Тема Saga Blue (пример) */
body .p-dialog {
background: var(--surface-a);
color: var(--text-color);
}
body .p-dialog .p-dialog-header,
body .p-dialog .p-dialog-footer {
background: var(--surface-b);
color: var(--text-color);
}
body .p-dialog .p-dialog-header {
border-bottom: 1px solid var(--surface-d);
}
body .p-dialog .p-dialog-footer {
border-top: 1px solid var(--surface-d);
}

View File

@@ -9,6 +9,7 @@
--surface-d: #3f4b5b;
--surface-e: #2a323d;
--surface-f: #2a323d;
--surface-h: #171717;
--text-color: rgba(255, 255, 255, 0.87);
--text-color-secondary: rgba(255, 255, 255, 0.6);
--primary-color: #8dd0ff;

View File

@@ -46,7 +46,7 @@ export const useLabelsMenu = (
}
// const labels = getLabels(system.labels);
const hasLabels = labels.list.length > 0;
const hasLabels = labels?.list?.length > 0;
const statusList = hasLabels ? LABELS_ORDER : LABELS_ORDER.slice(1);
return [

View File

@@ -18,7 +18,7 @@ export const useTagMenu = (
ref.current = { onSystemTag, systems, systemId };
return useCallback(() => {
const { onSystemTag, systemId , systems} = ref.current;
const { onSystemTag, systemId, systems } = ref.current;
const system = systemId ? getSystemById(systems, systemId) : undefined;
const isSelectedLetters = AVAILABLE_LETTERS.includes(system?.tag ?? '');

View File

@@ -4,6 +4,7 @@ import { OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.
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';
interface UseContextMenuSystemHandlersProps {
hubs: string[];
@@ -16,8 +17,10 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
const [system, setSystem] = useState<string>();
const ref = useRef({ hubs, system, systems, outCommand });
ref.current = { hubs, system, systems, outCommand };
const { deleteSystems } = useDeleteSystems();
const ref = useRef({ hubs, system, systems, outCommand, deleteSystems });
ref.current = { hubs, system, systems, outCommand, deleteSystems };
const open = useCallback((ev: any, systemId: string) => {
setSystem(systemId);
@@ -27,12 +30,12 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
}, []);
const onDeleteSystem = useCallback(() => {
const { system, outCommand } = ref.current;
const { system, deleteSystems } = ref.current;
if (!system) {
return;
}
outCommand({ type: OutCommand.deleteSystems, data: [system] });
deleteSystems([system]);
setSystem(undefined);
}, []);

View File

@@ -8,7 +8,7 @@ import { getSystemById } from '@/hooks/Mapper/helpers';
import { useWaypointMenu } from '@/hooks/Mapper/components/contexts/hooks';
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components';
import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks/useJumpPlannerMenu';
import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks';
import { Route } from '@/hooks/Mapper/types/routes.ts';
export interface ContextMenuSystemInfoProps {

View File

@@ -1,17 +1,17 @@
import { Node } from 'reactflow';
import { useRef, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import { ContextMenu } from 'primereact/contextmenu';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { SolarSystemRawType } from '@/hooks/Mapper/types';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
export const useContextMenuSystemMultipleHandlers = () => {
const contextMenuRef = useRef<ContextMenu | null>(null);
const { outCommand } = useMapRootState();
const [systems, setSystems] = useState<Node<SolarSystemRawType>[]>();
const { deleteSystems } = useDeleteSystems();
const handleSystemMultipleContext: NodeSelectionMouseHandler = (ev, systems_) => {
setSystems(systems_);
ev.preventDefault();
@@ -19,7 +19,7 @@ export const useContextMenuSystemMultipleHandlers = () => {
contextMenuRef.current?.show(ev);
};
const onDeleteSystems = () => {
const onDeleteSystems = useCallback(() => {
if (!systems) {
return;
}
@@ -29,12 +29,11 @@ export const useContextMenuSystemMultipleHandlers = () => {
return;
}
outCommand({ type: OutCommand.deleteSystems, data: sysToDel });
};
deleteSystems(sysToDel);
}, [deleteSystems, systems]);
return {
handleSystemMultipleContext,
contextMenuRef,
onDeleteSystems,
};

View File

@@ -1 +1,3 @@
export * from './useWaypointMenu';
export * from './useJumpPlannerMenu';
export * from './useDeleteSystems';

View File

@@ -0,0 +1,18 @@
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
export const useDeleteSystems = () => {
const { outCommand } = useMapRootState();
const deleteSystems = (systemIds: string[]) => {
if (!systemIds || !systemIds.length) {
return;
}
outCommand({ type: OutCommand.deleteSystems, data: systemIds });
};
return {
deleteSystems,
};
};

View File

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

View File

@@ -0,0 +1,33 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useMemo } from 'react';
import { getSystemById } from '@/hooks/Mapper/helpers';
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
interface UseSystemInfoProps {
systemId: string;
}
export const useSystemInfo = ({ systemId }: UseSystemInfoProps) => {
const {
data: { systems, connections },
} = useMapRootState();
const { systems: systemStatics } = useLoadSystemStatic({ systems: [systemId] });
return useMemo(() => {
const staticInfo = systemStatics.get(parseInt(systemId));
const dynamicInfo = getSystemById(systems, systemId);
if (!staticInfo || !dynamicInfo) {
throw new Error(`Error on getting system ${systemId}`);
}
const leadsTo = connections
.filter(x => [x.source, x.target].includes(systemId))
.map(x => [x.source, x.target])
.flat()
.filter(x => x !== systemId);
return { dynamicInfo, staticInfo, leadsTo };
}, [systemStatics, systemId, systems, connections]);
};

View File

@@ -1,4 +1,4 @@
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect } from 'react';
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useRef } from 'react';
import ReactFlow, {
Background,
ConnectionMode,
@@ -13,12 +13,15 @@ import ReactFlow, {
SelectionMode,
useEdgesState,
useNodesState,
NodeChange,
useReactFlow,
} from 'reactflow';
import 'reactflow/dist/style.css';
import classes from './Map.module.scss';
import './styles/neon-theme.scss';
import './styles/eve-common.scss';
import { MapProvider, useMapState } from './MapProvider';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useMapHandlers, useUpdateNodes } from './hooks';
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
import {
@@ -34,6 +37,7 @@ import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
@@ -108,6 +112,7 @@ const MapComp = ({
isShowMinimap,
showKSpaceBG,
}: MapCompProps) => {
const { getNode } = useReactFlow();
const [nodes, , onNodesChange] = useNodesState<SolarSystemRawType>(initialNodes);
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>[]>(initialEdges);
@@ -115,8 +120,15 @@ const MapComp = ({
useUpdateNodes(nodes);
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers();
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
const { update } = useMapState();
const {
data: { systems },
} = useMapRootState();
const { deleteSystems } = useDeleteSystems();
const systemsRef = useRef({ systems });
systemsRef.current = { systems };
const onConnect: OnConnect = useCallback(
params => {
@@ -171,6 +183,32 @@ const MapComp = ({
localStorage.setItem(SESSION_KEY.viewPort, JSON.stringify(viewport));
};
const handleNodesChange = useCallback(
(changes: NodeChange[]) => {
const systemsIdsToRemove: string[] = [];
const nextChanges = changes.reduce((acc, change) => {
if (change.type === 'remove') {
const node = getNode(change.id);
const { systems = [] } = systemsRef.current;
if (node?.data?.id && !systems.map(s => s.id).includes(node?.data?.id)) {
return [...acc, change];
} else if (!node?.data?.locked) {
systemsIdsToRemove.push(node?.data?.id);
}
return acc;
}
return [...acc, change];
}, [] as NodeChange[]);
if (systemsIdsToRemove.length) {
deleteSystems(systemsIdsToRemove);
}
onNodesChange(nextChanges);
},
[deleteSystems, getNode, onNodesChange],
);
useEffect(() => {
update(x => ({
...x,
@@ -184,7 +222,7 @@ const MapComp = ({
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onNodesChange={handleNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
// TODO we need save into session all of this
@@ -219,10 +257,10 @@ const MapComp = ({
minZoom={0.2}
maxZoom={1.5}
elevateNodesOnSelect
deleteKeyCode={['Delete']}
// TODO need create clear example with problem with that flag
// if system is not visible edge not drawing (and any render in Custom node is not happening)
// onlyRenderVisibleElements
deleteKeyCode={null}
selectionMode={SelectionMode.Partial}
>
{isShowMinimap && <MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} />}

View File

@@ -1,4 +1,4 @@
@import "@/hooks/Mapper/components/map/styles/eve-common-variables";
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
$pastel-blue: #5a7d9a;
$pastel-pink: #d291bc;
@@ -25,9 +25,11 @@ $tooltip-bg: #202020; // Темный фон для подсказок
z-index: 1;
overflow: hidden;
&.Mataria, &.Amarria, &.Gallente, &.Caldaria {
&::Before {
&.Mataria,
&.Amarria,
&.Gallente,
&.Caldaria {
&::before {
content: '';
position: absolute;
top: 0;
@@ -44,42 +46,40 @@ $tooltip-bg: #202020; // Темный фон для подсказок
&.Mataria {
&::before {
background-image: url("/images/mataria.png");
background-image: url('/images/mataria-180.png');
opacity: 0.6;
background-position-x: -28px;
background-position-y: -3px;
background-position-x: 1px;
background-position-y: -14px;
}
}
&.Caldaria {
&::before {
background-image: url("/images/caldaria.png");
background-image: url('/images/caldaria-180.png');
opacity: 0.6;
background-position-x: -16px;
background-position-y: -17px;
background-position-x: 1px;
background-position-y: -10px;
}
}
&.Amarria {
&::before {
opacity: 0.45;
background-image: url("/images/amarr.png");
background-position-x: 0px;
background-position-y: -1px;
width: calc(100% + 10px)
background-image: url('/images/amarr-180.png');
background-position-x: 0;
background-position-y: -13px;
}
}
&.Gallente {
&::before {
opacity: 0.6;
background-image: url("/images/gallente.png");
background-position-x: -1px;
background-position-y: -10px;
opacity: 0.5;
background-image: url('/images/gallente-180.png');
background-position-x: 1px;
background-position-y: 0;
}
}
&.selected {
border-color: $pastel-pink;
box-shadow: 0 0 10px #9a1af1c2;
@@ -95,7 +95,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
&.eve-system-status-home {
border: 1px solid darken($eve-solar-system-status-color-home, 30%);
background-image: linear-gradient(45deg, $eve-solar-system-status-friendly, transparent);
background-image: linear-gradient(275deg, $eve-solar-system-status-friendly, transparent);
&.selected {
border-color: $eve-solar-system-status-color-home;
@@ -104,7 +104,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
&.eve-system-status-friendly {
border: 1px solid darken($eve-solar-system-status-color-friendly, 20%);
background-image: linear-gradient(45deg, darken($eve-solar-system-status-friendly, 30%), transparent);
background-image: linear-gradient(275deg, darken($eve-solar-system-status-friendly, 30%), transparent);
&.selected {
border-color: darken($eve-solar-system-status-color-friendly, 5%);
@@ -113,7 +113,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
&.eve-system-status-lookingFor {
border: 1px solid darken($eve-solar-system-status-color-lookingFor, 15%);
background-image: linear-gradient(45deg, #45ff8f2f, #457fff2f);
background-image: linear-gradient(275deg, #45ff8f2f, #457fff2f);
&.selected {
border-color: $pastel-pink;
@@ -121,17 +121,16 @@ $tooltip-bg: #202020; // Темный фон для подсказок
}
&.eve-system-status-warning {
background-image: linear-gradient(45deg, $eve-solar-system-status-warning, transparent);
background-image: linear-gradient(275deg, $eve-solar-system-status-warning, transparent);
}
&.eve-system-status-dangerous {
background-image: linear-gradient(45deg, $eve-solar-system-status-dangerous, transparent);
background-image: linear-gradient(275deg, $eve-solar-system-status-dangerous, transparent);
}
&.eve-system-status-target {
background-image: linear-gradient(45deg, $eve-solar-system-status-target, transparent);
background-image: linear-gradient(275deg, $eve-solar-system-status-target, transparent);
}
}
.Bookmarks {
@@ -158,7 +157,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
//background-color: #833ca4;
&:not(:first-child) {
box-shadow: inset 4px -3px 4px rgba(0, 0, 0, .3);
box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3);
}
}
@@ -181,7 +180,6 @@ $tooltip-bg: #202020; // Темный фон для подсказок
font-size: 9px;
}
}
}
.icon {
@@ -219,9 +217,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
}
.solarSystemName {
}
}
.BottomRow {
@@ -288,11 +284,19 @@ $tooltip-bg: #202020; // Темный фон для подсказок
border-color: $pastel-pink;
}
&.HandleTop { top: -2px }
&.HandleTop {
top: -2px;
}
&.HandleRight { right: -2px }
&.HandleRight {
right: -2px;
}
&.HandleBottom { bottom: -2px }
&.HandleBottom {
bottom: -2px;
}
&.HandleLeft { left: -2px }
&.HandleLeft {
left: -2px;
}
}

View File

@@ -29,7 +29,7 @@ const SpaceToClass: Record<string, string> = {
};
const sortedLabels = (labels: string[]) => {
if (labels === null) {
if (!labels) {
return [];
}
@@ -133,7 +133,7 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
<div className={classes.Bookmarks}>
{labelCustom !== '' && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
<div>{labelCustom}</div>
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] ">{labelCustom}</span>
</div>
)}
@@ -168,14 +168,16 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
{visible && (
<>
<div className={classes.HeadRow}>
<div className={clsx(classes.classTitle, classTitleColor)}>{class_title ?? '-'}</div>
<div className={clsx(classes.classTitle, classTitleColor, '[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]')}>
{class_title ?? '-'}
</div>
{tag != null && tag !== '' && (
<div className={clsx(classes.TagTitle, 'text-sky-400 font-medium')}>{tag}</div>
)}
<div
className={clsx(
classes.classSystemName,
'flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
)}
>
{solar_system_name}
@@ -196,16 +198,16 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
{customName && (
<div className="text-blue-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">{customName}</div>
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-blue-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
{customName}
</div>
)}
{!isWormhole && !customName && (
<div
className={clsx('text-stone-400 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5', {
['text-teal-100 font-bold']: space === Spaces.Caldari,
['text-yellow-100 font-bold']: space === Spaces.Amarr || space === Spaces.Matar,
['text-lime-200/80 font-bold']: space === Spaces.Gallente,
})}
className={clsx(
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-stone-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
)}
>
{region_name}
</div>
@@ -215,10 +217,10 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
<div className="flex items-center justify-end">
<div className="flex gap-1 items-center">
{locked && <i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem' }}></i>}
{locked && <i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem', fontWeight: 'bold' }}></i>}
{hubs.includes(solar_system_id.toString()) && (
<i className={PrimeIcons.MAP_MARKER} style={{ fontSize: '0.45rem' }}></i>
<i className={PrimeIcons.MAP_MARKER} style={{ fontSize: '0.45rem', fontWeight: 'bold' }}></i>
)}
{charactersInSystem.length > 0 && (

View File

@@ -18,5 +18,9 @@ export const WormholeClassComp = ({ id }: WormholeClassComp) => {
}
const colorClass = WORMHOLE_CLASS_STYLES[wormholeDataAdditional.wormholeClassID.toString()];
return <div className={clsx(colorClass)}>{wormholeDataAdditional.shortName}</div>;
return (
<div className={clsx(colorClass, '[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]')}>
{wormholeDataAdditional.shortName}
</div>
);
};

View File

@@ -30,11 +30,77 @@ export enum SOLAR_SYSTEM_CLASS_IDS {
zarzakh = 10100,
}
export enum SOLAR_SYSTEM_CLASS_GROUPS {
ccp = 'ccp',
c1 = 'c1',
c2 = 'c2',
c3 = 'c3',
c4 = 'c4',
c5 = 'c5',
c6 = 'c6',
hs = 'hs',
ls = 'ls',
ns = 'ns',
thera = 'thera',
c13 = 'c13',
drifter = 'drifter',
unknown = 'unknown',
pochven = 'pochven',
jovian = 'jovian',
}
export const SOLAR_SYSTEM_TO_CLASS_GROUPS_CLASSES = {
c1: ['c1'],
c2: ['c2'],
c3: ['c3'],
c4: ['c4'],
c5: ['c5'],
c6: ['c6'],
hs: ['hs'],
ls: ['ls'],
ns: ['ns'],
thera: ['thera'],
c13: ['c13'],
pochven: ['pochven'],
drifter: ['sentinel', 'barbican', 'vidette', 'conflux', 'redoubt'],
jove: ['jove'],
};
export const SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS = {
ccp1: SOLAR_SYSTEM_CLASS_GROUPS.ccp,
c1: SOLAR_SYSTEM_CLASS_GROUPS.c1,
c2: SOLAR_SYSTEM_CLASS_GROUPS.c2,
c3: SOLAR_SYSTEM_CLASS_GROUPS.c3,
c4: SOLAR_SYSTEM_CLASS_GROUPS.c4,
c5: SOLAR_SYSTEM_CLASS_GROUPS.c5,
c6: SOLAR_SYSTEM_CLASS_GROUPS.c6,
hs: SOLAR_SYSTEM_CLASS_GROUPS.hs,
ls: SOLAR_SYSTEM_CLASS_GROUPS.ls,
ns: SOLAR_SYSTEM_CLASS_GROUPS.ns,
ccp2: SOLAR_SYSTEM_CLASS_GROUPS.ccp,
ccp3: SOLAR_SYSTEM_CLASS_GROUPS.ccp,
thera: SOLAR_SYSTEM_CLASS_GROUPS.thera,
c13: SOLAR_SYSTEM_CLASS_GROUPS.c13,
sentinel: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
baribican: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
vidette: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
conflux: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
redoubt: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
a1: SOLAR_SYSTEM_CLASS_GROUPS.unknown,
a2: SOLAR_SYSTEM_CLASS_GROUPS.unknown,
a3: SOLAR_SYSTEM_CLASS_GROUPS.unknown,
a4: SOLAR_SYSTEM_CLASS_GROUPS.unknown,
a5: SOLAR_SYSTEM_CLASS_GROUPS.unknown,
ccp4: SOLAR_SYSTEM_CLASS_GROUPS.ccp,
pochven: SOLAR_SYSTEM_CLASS_GROUPS.pochven,
};
type WormholesAdditionalInfoType = {
id: string;
shortName: string;
wormholeClassID: number;
title: string;
shortTitle: string;
effectPower?: number;
};
@@ -45,6 +111,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
shortName: 'CCP',
wormholeClassID: -1,
title: 'CCP System',
shortTitle: 'CCP',
},
{
id: 'c1',
@@ -52,6 +119,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 1,
effectPower: 1,
title: 'Class 1',
shortTitle: 'C1',
},
{
id: 'c2',
@@ -59,6 +127,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 2,
effectPower: 2,
title: 'Class 2',
shortTitle: 'C2',
},
{
id: 'c3',
@@ -66,6 +135,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 3,
effectPower: 3,
title: 'Class 3',
shortTitle: 'C3',
},
{
id: 'c4',
@@ -73,6 +143,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 4,
effectPower: 4,
title: 'Class 4',
shortTitle: 'C4',
},
{
id: 'c5',
@@ -80,6 +151,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 5,
effectPower: 5,
title: 'Class 5',
shortTitle: 'C5',
},
{
id: 'c6',
@@ -87,42 +159,49 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 6,
effectPower: 6,
title: 'Class 6',
shortTitle: 'C6',
},
{
id: 'hs',
shortName: 'H',
wormholeClassID: 7,
title: 'High-sec',
shortTitle: 'High-sec',
},
{
id: 'ls',
shortName: 'L',
wormholeClassID: 8,
title: 'Low-sec',
shortTitle: 'Low-sec',
},
{
id: 'ns',
shortName: 'N',
wormholeClassID: 9,
title: 'Null-sec',
shortTitle: 'Null-sec',
},
{
id: 'ccp2',
shortName: 'CCP',
wormholeClassID: 10,
title: 'CCP System',
shortTitle: 'CCP',
},
{
id: 'ccp3',
shortName: 'CCP',
wormholeClassID: 11,
title: 'CCP System',
shortTitle: 'CCP',
},
{
id: 'thera',
shortName: 'T',
wormholeClassID: 12,
title: 'Class 12 (Thera)',
shortTitle: 'Thera',
},
{
id: 'c13',
@@ -130,6 +209,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 13,
effectPower: 6,
title: 'Class 13 (Shattered Frigate)',
shortTitle: 'C13',
},
{
id: 'sentinel',
@@ -137,6 +217,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 14,
effectPower: 2,
title: 'Class 14 (Sentinel Drifter)',
shortTitle: 'Sentinel',
},
{
id: 'barbican',
@@ -144,6 +225,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 15,
effectPower: 2,
title: 'Class 15 (Barbican Drifter)',
shortTitle: 'Barbican',
},
{
id: 'vidette',
@@ -151,6 +233,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 16,
effectPower: 2,
title: 'Class 16 (Vidette Drifter)',
shortTitle: 'Vidette',
},
{
id: 'conflux',
@@ -158,6 +241,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 17,
effectPower: 2,
title: 'Class 17 (Conflux Drifter)',
shortTitle: 'Conflux',
},
{
id: 'redoubt',
@@ -165,59 +249,79 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
wormholeClassID: 18,
effectPower: 2,
title: 'Class 18 (Redoubt Drifter)',
shortTitle: 'Redoubt',
},
{
id: 'a1',
shortName: 'A1',
wormholeClassID: 19,
title: '(Abyssal class 1)',
shortTitle: 'A1',
},
{
id: 'a2',
shortName: 'A2',
wormholeClassID: 20,
title: '(Abyssal class 2)',
shortTitle: 'A2',
},
{
id: 'a3',
shortName: 'A3',
wormholeClassID: 21,
title: '(Abyssal class 3)',
shortTitle: 'A3',
},
{
id: 'a4',
shortName: 'A4',
wormholeClassID: 22,
title: '(Abyssal class 4)',
shortTitle: 'A4',
},
{
id: 'a5',
shortName: 'A5',
wormholeClassID: 23,
title: '(Abyssal class 5)',
shortTitle: 'A5',
},
{
id: 'ccp4',
shortName: 'CCP',
wormholeClassID: 24,
title: 'CCP System (Penalty)',
shortTitle: 'CCP',
},
{
id: 'pochven',
shortName: 'P',
wormholeClassID: 25,
title: 'Triglavian space (Pochven)',
shortTitle: 'Pochven',
},
{
id: 'zarzakh',
shortName: 'N',
wormholeClassID: 10100,
title: 'Pirate space',
shortTitle: 'Zarzakh',
},
{
id: 'k162',
shortName: 'K162',
wormholeClassID: 10101,
title: 'Reverse',
shortTitle: 'K162',
},
];
export const WORMHOLES_ADDITIONAL_INFO: Record<string, WormholesAdditionalInfoType> =
WORMHOLES_ADDITIONAL_INFO_RAW.reduce((acc, x) => ({ ...acc, [x.id]: x }), {});
export const WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID: Record<string, WormholesAdditionalInfoType> =
WORMHOLES_ADDITIONAL_INFO_RAW.reduce((acc, x) => ({ ...acc, [x.wormholeClassID]: x }), {});
// export const SOLAR_SYSTEM_CLASS_NAMES = {
// ccp1 = ,
// c1 = ,

View File

@@ -1,18 +1,18 @@
import { useReactFlow } from 'reactflow';
import { useCallback } from 'react';
import { useCallback, useRef } from 'react';
import { CommandCenterSystem } from '@/hooks/Mapper/types';
export const useCenterSystem = () => {
const rf = useReactFlow();
const ref = useRef({ rf });
ref.current = { rf };
return useCallback((systemId: CommandCenterSystem) => {
if (!rf) {
return;
}
const systemNode = rf.getNodes().find(x => x.data.id === systemId);
const systemNode = ref.current.rf.getNodes().find(x => x.data.id === systemId);
if (!systemNode) {
return;
}
rf.setCenter(systemNode.position.x, systemNode.position.y, { duration: 1000 });
ref.current.rf.setCenter(systemNode.position.x, systemNode.position.y, { duration: 1000 });
}, []);
};

View File

@@ -2,28 +2,17 @@ import { Node, useReactFlow } from 'reactflow';
import { useCallback, useRef } from 'react';
import { CommandAddSystems } from '@/hooks/Mapper/types/mapHandlers.ts';
import { convertSystem2Node } from '../../helpers';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
export const useMapAddSystems = () => {
const rf = useReactFlow();
const {
data: { systems },
update,
} = useMapState();
const ref = useRef({ rf, systems, update });
ref.current = { update, systems, rf };
const ref = useRef({ rf });
ref.current = { rf };
return useCallback(
(systems: CommandAddSystems) => {
const nodes = rf.getNodes();
const prepared: Node[] = systems.filter(x => !nodes.some(y => x.id === y.id)).map(convertSystem2Node);
rf.addNodes(prepared);
ref.current.update({
systems: [...ref.current.systems.filter(sys => systems.some(x => sys.id !== x.id)), ...systems],
});
},
[rf],
);
return useCallback((systems: CommandAddSystems) => {
const { rf } = ref.current;
const nodes = rf.getNodes();
const prepared: Node[] = systems.filter(x => !nodes.some(y => x.id === y.id)).map(convertSystem2Node);
rf.addNodes(prepared);
}, []);
};

View File

@@ -1,15 +1,15 @@
import { useReactFlow } from 'reactflow';
import { useCallback } from 'react';
import { useCallback, useRef } from 'react';
import { CommandSelectSystem } from '@/hooks/Mapper/types';
export const useSelectSystem = () => {
const rf = useReactFlow();
const ref = useRef({ rf });
ref.current = { rf };
return useCallback((systemId: CommandSelectSystem) => {
if (!rf) {
return;
}
rf.setNodes(nds =>
ref.current.rf.setNodes(nds =>
nds.map(node => {
return {
...node,

View File

@@ -18,6 +18,9 @@ import {
CommandUpdateSystems,
MapHandlers,
} from '@/hooks/Mapper/types/mapHandlers.ts';
import { useMapEventListener } from '@/hooks/Mapper/events';
import {
useCommandsCharacters,
useCommandsConnections,
@@ -57,16 +60,13 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
mapInit(data as CommandInit);
break;
case Commands.addSystems:
mapAddSystems(data as CommandAddSystems);
break;
case Commands.updateSystems:
mapUpdateSystems(data as CommandUpdateSystems);
break;
case Commands.removeSystems:
removeSystems(data as CommandRemoveSystems);
break;
case Commands.addConnections:
addConnections(data as CommandAddConnections);
break;
case Commands.removeConnections:
removeConnections(data as CommandRemoveConnections);
@@ -131,4 +131,20 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
},
[],
);
useMapEventListener(event => {
switch (event.name) {
case Commands.addConnections:
addConnections(event.data as CommandAddConnections);
break;
case Commands.addSystems:
mapAddSystems(event.data as CommandAddSystems);
break;
case Commands.removeSystems:
removeSystems(event.data as CommandRemoveSystems);
break;
default:
break;
}
});
};

View File

@@ -7,7 +7,7 @@ import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
import { IconField } from 'primereact/iconfield';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
import { WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { WdImageSize, WdImgButton, TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
interface SystemCustomLabelDialog {
systemId: string;
@@ -79,14 +79,14 @@ export const SystemCustomLabelDialog = ({ systemId, visible, setVisible }: Syste
// @ts-ignore
const handleInput = useCallback(e => {
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9\-[\](){}]/g, '');
}, []);
return (
<Dialog
header="Edit label"
visible={visible}
draggable={false}
draggable={true}
style={{ width: '250px' }}
onHide={onHide}
onShow={onShow}
@@ -100,9 +100,13 @@ export const SystemCustomLabelDialog = ({ systemId, visible, setVisible }: Syste
<IconField>
{label !== '' && (
<WdImgButton
className="pi pi-trash p-input-icon"
className="pi pi-trash text-red-400"
textSize={WdImageSize.large}
tooltip={{ content: 'Reset label' }}
tooltip={{
content: 'Remove custom label',
className: 'pi p-input-icon',
position: TooltipPosition.top,
}}
onClick={handleReset}
/>
)}
@@ -111,7 +115,7 @@ export const SystemCustomLabelDialog = ({ systemId, visible, setVisible }: Syste
aria-describedby="username-help"
autoComplete="off"
value={label}
maxLength={3}
maxLength={5}
onChange={e => setLabel(e.target.value)}
// @ts-expect-error
ref={inputRef}

View File

@@ -21,8 +21,6 @@ const signatureSettings: Setting[] = [{ key: COSMIC_SIGNATURE, name: 'Show Cosmi
export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => {
const { outCommand } = useMapRootState();
console.log(data);
const ref = useRef({ outCommand });
ref.current = { outCommand };
@@ -31,8 +29,8 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
}, [setVisible]);
const handleSelect = useCallback(
(signatures: SystemSignature[]) => {
if (!signatures.length) {
(signature: SystemSignature) => {
if (!signature) {
return;
}
@@ -42,12 +40,12 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
type: OutCommand.linkSignatureToSystem,
data: {
...data,
signature_eve_id: signatures[0].eve_id,
signature_eve_id: signature.eve_id,
},
});
setVisible(false);
},
[setVisible],
[data, setVisible],
);
return (

View File

@@ -90,7 +90,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
}, []);
const handleInput = useCallback((e: any) => {
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9\-[\](){}]/g, '');
}, []);
return (
@@ -160,7 +160,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
aria-describedby="label"
autoComplete="off"
value={label}
maxLength={3}
maxLength={5}
onChange={e => setLabel(e.target.value)}
onInput={handleInput}
/>

View File

@@ -13,9 +13,10 @@ export const SystemInfoContent = ({ systemId }: SystemInfoContentProps) => {
data: { systems, wormholesData },
} = useMapRootState();
const sys = getSystemById(systems, systemId)!;
const sys = getSystemById(systems, systemId)! || {};
const { description } = sys;
const { system_class, region_name, constellation_name, statics, effect_name, effect_power } = sys.system_static_info;
const { system_class, region_name, constellation_name, statics, effect_name, effect_power } =
sys.system_static_info || {};
const isWH = isWormholeSpace(system_class);
const sortedStatics = useMemo(() => sortWHClasses(wormholesData, statics), [wormholesData, statics]);

View File

@@ -1,10 +1,10 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useClipboard } from '@/hooks/Mapper/hooks/useClipboard';
import { parseSignatures } from '@/hooks/Mapper/helpers';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit';
import { DataTable, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable';
import { DataTable, DataTableRowClickEvent, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import useRefState from 'react-usestateref';
@@ -22,12 +22,14 @@ import {
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers';
import {
renderIcon,
renderName,
renderInfoColumn,
renderTimeLeft,
renderLinkedSystem,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
// import { PrimeIcons } from 'primereact/api';
import useLocalStorageState from 'use-local-storage-state';
import { PrimeIcons } from 'primereact/api';
import { SignatureSettings } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings';
import { useMapEventListener } from '@/hooks/Mapper/events';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
type SystemSignaturesSortSettings = {
sortField: string;
@@ -43,7 +45,7 @@ interface SystemSignaturesContentProps {
systemId: string;
settings: Setting[];
selectable?: boolean;
onSelect?: (signatures: SystemSignature[]) => void;
onSelect?: (signature: SystemSignature) => void;
}
export const SystemSignaturesContent = ({ systemId, settings, selectable, onSelect }: SystemSignaturesContentProps) => {
const { outCommand } = useMapRootState();
@@ -53,6 +55,7 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
const [nameColumnWidth, setNameColumnWidth] = useState('auto');
const [parsedSignatures, setParsedSignatures] = useState<SystemSignature[]>([]);
const [askUser, setAskUser] = useState(false);
const [selectedSignature, setSelectedSignature] = useState<SystemSignature | null>(null);
const [hoveredSig, setHoveredSig] = useState<SystemSignature | null>(null);
@@ -164,6 +167,8 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
}, [parsedSignatures, handleUpdateSignatures]);
const handleSelectSignatures = useCallback(
// TODO still will be good to define types if we use typescript
// @ts-ignore
e => {
if (selectable) {
onSelect?.(e.value);
@@ -212,6 +217,18 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
handleGetSignatures();
}, [systemId]);
useMapEventListener(event => {
switch (event.name) {
case Commands.signaturesUpdated:
if (event.data?.toString() !== systemId.toString()) {
return;
}
handleGetSignatures();
return true;
}
});
useEffect(() => {
const observer = new ResizeObserver(handleResize);
if (tableRef.current) {
@@ -240,13 +257,22 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
setHoveredSig(null);
}, []);
// const renderToolbar = (/*row: SystemSignature*/) => {
// return (
// <div className="flex justify-end items-center gap-2">
// <span className={clsx(PrimeIcons.PENCIL, 'text-[10px]')}></span>
// </div>
// );
// };
const renderToolbar = (/*row: SystemSignature*/) => {
return (
<div className="flex justify-end items-center gap-2 mr-[4px]">
<WdTooltipWrapper content="To Edit Signature do double click">
<span className={clsx(PrimeIcons.PENCIL, 'text-[10px]')}></span>
</WdTooltipWrapper>
</div>
);
};
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
const handleRowClick = (e: DataTableRowClickEvent) => {
setSelectedSignature(e.data as SystemSignature);
setShowSignatureSettings(true);
};
return (
<>
@@ -257,6 +283,7 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
</div>
) : (
<>
{/* @ts-ignore */}
<DataTable
className={classes.Table}
value={filteredSignatures}
@@ -268,6 +295,7 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
dataKey="eve_id"
tableClassName="w-full select-none"
resizableColumns={false}
onRowDoubleClick={handleRowClick}
rowHover
selectAll
sortField={sortSettings.sortField}
@@ -291,7 +319,7 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
<Column
bodyClassName="p-0 px-1"
field="group"
body={renderIcon}
body={x => renderIcon(x)}
style={{ maxWidth: 26, minWidth: 26, width: 26, height: 25 }}
></Column>
@@ -310,40 +338,29 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
sortable
></Column>
<Column
field="name"
header="Name"
field="info"
// header="Info"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
body={renderName}
body={renderInfoColumn}
style={{ maxWidth: nameColumnWidth }}
hidden={compact || medium}
sortable
></Column>
<Column
field="linked_system"
header="Linked System"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
body={renderLinkedSystem}
style={{ maxWidth: nameColumnWidth }}
hidden={compact}
sortable
></Column>
<Column
field="updated_at"
header="Updated"
dataType="date"
bodyClassName="w-[80px] text-ellipsis overflow-hidden whitespace-nowrap"
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
body={renderTimeLeft}
sortable
></Column>
{/*<Column*/}
{/* bodyClassName="p-0 pl-1 pr-2"*/}
{/* field="group"*/}
{/* body={renderToolbar}*/}
{/* headerClassName={headerClasses}*/}
{/* style={{ maxWidth: 26, minWidth: 26, width: 26 }}*/}
{/*></Column>*/}
<Column
bodyClassName="p-0 pl-1 pr-2"
field="group"
body={renderToolbar}
// headerClassName={headerClasses}
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
></Column>
</DataTable>
</>
)}
@@ -352,6 +369,14 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
ref={tooltipRef}
content={hoveredSig ? <SignatureView {...hoveredSig} /> : null}
/>
<SignatureSettings
systemId={systemId}
show={showSignatureSettings}
onHide={() => setShowSignatureSettings(false)}
signatureData={selectedSignature}
/>
{askUser && (
<div className="absolute left-[1px] top-[29px] h-[calc(100%-30px)] w-[calc(100%-3px)] bg-stone-900/10 backdrop-blur-sm">
<div className="absolute top-0 left-0 w-full h-full flex flex-col items-center justify-center">
@@ -364,7 +389,7 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
</button>
<button className="p-button p-component p-button-outlined p-button-sm btn-wide">
<span className="p-button-label p-c" onClick={handleReplaceAll}>
Update & Delete
Update & Delete missing
</span>
</button>
</div>

View File

@@ -2,3 +2,4 @@ export * from './renderIcon';
export * from './renderName';
export * from './renderTimeLeft';
export * from './renderLinkedSystem';
export * from './renderInfoColumn';

View File

@@ -1,7 +1,7 @@
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { GroupType, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { GROUPS } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
export const renderIcon = (row: SystemSignature) => {
export const renderIcon = (row: SystemSignature, customSize?: Omit<GroupType, 'icon' | 'id'>) => {
if (row.group == null) {
return null;
}
@@ -13,7 +13,7 @@ export const renderIcon = (row: SystemSignature) => {
return (
<div className="flex justify-center items-center">
<img src={group.icon} style={{ width: group.w, height: group.h }} />
<img src={group.icon} style={{ width: customSize?.w ?? group.w, height: customSize?.h ?? group.h }} />
</div>
);
};

View File

@@ -0,0 +1,3 @@
.whFontSize {
font-size: 11px !important;
}

View File

@@ -0,0 +1,44 @@
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { SystemViewStandalone, WHClassView } from '@/hooks/Mapper/components/ui-kit';
import clsx from 'clsx';
import { renderName } from './renderName.tsx';
import classes from './renderInfoColumn.module.scss';
export const renderInfoColumn = (row: SystemSignature) => {
if (!row.group || row.group === SignatureGroup.Wormhole) {
return (
<div className="flex justify-start items-center gap-[6px]">
{row.type && (
<WHClassView
className="text-[11px]"
classNameWh={classes.whFontSize}
highlightName
hideWhClass={!!row.linked_system}
whClassName={row.type}
noOffset
useShortTitle
/>
)}
{row.linked_system && (
<>
{/*<span className="w-4 h-4 hero-arrow-long-right"></span>*/}
<span title={row.linked_system?.solar_system_name}>
<SystemViewStandalone
className={clsx('select-none text-center cursor-context-menu')}
hideRegion
{...row.linked_system}
/>
</span>
</>
)}
</div>
);
}
if (row.description != null && row.description.length > 0) {
return <span title={row.description}>{row.description}</span>;
}
return renderName(row);
};

View File

@@ -7,6 +7,7 @@ import { useCallback, useState } from 'react';
import { OnTheMap, RightBar } from '@/hooks/Mapper/components/mapRootContent/components';
import { MapContextMenu } from '@/hooks/Mapper/components/mapRootContent/components/MapContextMenu/MapContextMenu.tsx';
import { useSkipContextMenu } from '@/hooks/Mapper/hooks/useSkipContextMenu';
import { MapSettings } from "@/hooks/Mapper/components/mapRootContent/components/MapSettings";
export interface MapRootContentProps {}
@@ -16,9 +17,11 @@ export const MapRootContent = ({}: MapRootContentProps) => {
const { isShowMenu } = interfaceSettings;
const [showOnTheMap, setShowOnTheMap] = useState(false);
const [showMapSettings, setShowMapSettings] = useState(false);
const mapInterface = <MapInterface />;
const handleShowOnTheMap = useCallback(() => setShowOnTheMap(true), []);
const handleShowMapSettings = useCallback(() => setShowMapSettings(true), []);
useSkipContextMenu();
@@ -31,18 +34,19 @@ export const MapRootContent = ({}: MapRootContentProps) => {
{mapInterface}
</div>
<div className="absolute top-0 right-0 w-14 h-[calc(100%+3.5rem)] pointer-events-auto">
<RightBar onShowOnTheMap={handleShowOnTheMap} />
<RightBar onShowOnTheMap={handleShowOnTheMap} onShowMapSettings={handleShowMapSettings} />
</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} />
<MapContextMenu onShowOnTheMap={handleShowOnTheMap} onShowMapSettings={handleShowMapSettings} />
</Topbar>
{mapInterface}
</div>
)}
<OnTheMap show={showOnTheMap} onHide={() => setShowOnTheMap(false)} />
<MapSettings show={showMapSettings} onHide={() => setShowMapSettings(false)} />
</Layout>
);
};

View File

@@ -1,13 +1,20 @@
import classes from './Connections.module.scss';
import { Sidebar } from 'primereact/sidebar';
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo, useState, useCallback } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import clsx from 'clsx';
import { ConnectionOutput, OutCommand, Passage, SolarSystemConnection } from '@/hooks/Mapper/types';
import {
ConnectionOutput,
ConnectionInfoOutput,
OutCommand,
Passage,
SolarSystemConnection,
} from '@/hooks/Mapper/types';
import { PassageCard } from './PassageCard';
import { InfoDrawer, SystemView } from '@/hooks/Mapper/components/ui-kit';
import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts';
import { TimeAgo } from '@/hooks/Mapper/components/ui-kit';
const sortByDate = (a: string, b: string) => new Date(a).getTime() - new Date(b).getTime();
@@ -69,25 +76,44 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
}, [connections, selectedConnection]);
const [passages, setPassages] = useState<Passage[]>([]);
const [info, setInfo] = useState<ConnectionInfoOutput>(null);
const loadInfo = useCallback(
async (connection: SolarSystemConnection) => {
const result = await outCommand<ConnectionInfoOutput>({
type: OutCommand.getConnectionInfo,
data: {
from: connection.source,
to: connection.target,
},
});
setInfo(result);
},
[outCommand],
);
const loadPassages = useCallback(
async (connection: SolarSystemConnection) => {
const result = await outCommand<ConnectionOutput>({
type: OutCommand.getPassages,
data: {
from: connection.source,
to: connection.target,
},
});
setPassages(result.passages.sort((a, b) => sortByDate(b.inserted_at, a.inserted_at)));
},
[outCommand],
);
useEffect(() => {
if (!selectedConnection) {
return;
}
const loadInfo = async () => {
const result = await outCommand<ConnectionOutput>({
type: OutCommand.getPassages,
data: {
from: selectedConnection.source,
to: selectedConnection.target,
},
});
setPassages(result.passages.sort((a, b) => sortByDate(b.inserted_at, a.inserted_at)));
};
loadInfo();
loadInfo(selectedConnection);
loadPassages(selectedConnection);
}, [selectedConnection]);
const approximateMass = useMemo(() => {
@@ -132,6 +158,10 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
{kgToTons(approximateMass)}
</InfoDrawer>
<InfoDrawer title="Mark EOL Time" rightSide>
{info?.marl_eol_time ? <TimeAgo timestamp={info.marl_eol_time} /> : ' unknown '}
</InfoDrawer>
<div className="flex gap-2"></div>
</div>

View File

@@ -8,10 +8,11 @@ import { MenuItem } from 'primereact/menuitem';
export interface MapContextMenuProps {
onShowOnTheMap?: () => void;
onShowMapSettings?: () => void;
}
export const MapContextMenu = ({ onShowOnTheMap }: MapContextMenuProps) => {
const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings }: MapContextMenuProps) => {
const { outCommand, setInterfaceSettings } = useMapRootState();
const menuRight = useRef<Menu>(null);
@@ -22,13 +23,6 @@ export const MapContextMenu = ({ onShowOnTheMap }: MapContextMenuProps) => {
});
}, [outCommand]);
const toggleMinimap = useCallback(() => {
setInterfaceSettings(x => ({
...x,
isShowMinimap: !x.isShowMinimap,
}));
}, [setInterfaceSettings]);
const items = useMemo(() => {
return [
{
@@ -43,9 +37,9 @@ export const MapContextMenu = ({ onShowOnTheMap }: MapContextMenuProps) => {
},
{ separator: true },
{
label: interfaceSettings.isShowMinimap ? 'Hide minimap' : 'Show minimap',
icon: `pi ${interfaceSettings.isShowMinimap ? 'pi-eye-slash' : 'pi-eye'}`,
command: toggleMinimap,
label: 'Settings',
icon: `pi pi-cog`,
command: onShowMapSettings,
},
{
label: 'Dock menu',
@@ -57,7 +51,7 @@ export const MapContextMenu = ({ onShowOnTheMap }: MapContextMenuProps) => {
})),
},
] as MenuItem[];
}, [handleAddCharacter, interfaceSettings.isShowMinimap, onShowOnTheMap, setInterfaceSettings, toggleMinimap]);
}, [handleAddCharacter, onShowMapSettings, onShowOnTheMap, setInterfaceSettings]);
return (
<div className="ml-1">

View File

@@ -0,0 +1,127 @@
.verticalTabsContainer {
display: flex;
width: 100%;
min-height: 300px;
:global {
.p-tabview {
width: 100%;
display: flex;
align-items: flex-start;
}
.p-tabview-panels {
padding: 6px 1rem !important;
flex-grow: 1;
}
.p-tabview-nav-container {
border-right: none;
height: 100%;
}
.p-tabview-nav {
flex-direction: column;
width: 150px;
min-height: 100%;
border: none;
li {
width: 100%;
border-right: 4px solid var(--surface-hover);
background-color: var(--surface-card);
transition: background-color 200ms, border-right-color 200ms;
&:hover {
background-color: var(--surface-hover);
border-right: 4px solid var(--surface-100);
}
.p-tabview-nav-link {
transition: color 200ms;
justify-content: flex-end;
padding: 10px;
//background-color: var(--surface-card);
background-color: initial;
border: none;
color: var(--gray-400);
border-radius: initial;
font-weight: 400;
margin: 0;
}
&.p-tabview-selected {
background-color: var(--surface-50);
border-right: 4px solid var(--primary-color);
.p-tabview-nav-link {
font-weight: 600;
color: var(--primary-color);
}
&:hover {
//background-color: var(--surface-hover);
border-right: 4px solid var(--primary-color);
}
}
}
}
.p-tabview-panel {
flex-grow: 1;
}
}
}
.CheckboxContainer {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
& > span:nth-child(1) {
color: var(--gray-200);
font-size: 13px;
}
& > :nth-child(2){
border-bottom: 2px dotted #3f3f3f;
height: 2px;
margin: 0 12px;
}
}
/* Уменьшение размеров InputSwitch с использованием глобальных стилей */
.smallInputSwitch {
height: 100%;
display: flex;
align-items: center;
:global {
.p-inputswitch {
height: 1rem;
width: 2rem;
&.p-inputswitch-checked {
.p-inputswitch-slider::before {
transform: translateX(1rem);
}
}
&.p-highlight .p-inputswitch-slider:before {
transform: translateX(1rem);
}
.p-inputswitch-slider::before {
width: 0.8rem;
height: 0.8rem;
margin-top: -0.4rem;
margin-left: -3px;
}
}
}
}

View File

@@ -0,0 +1,175 @@
import styles from './MapSettings.module.scss';
import { Dialog } from 'primereact/dialog';
import { useCallback, useMemo, useState } from 'react';
import { TabPanel, TabView } from 'primereact/tabview';
import { PrettySwitchbox } from './components';
import { InterfaceStoredSettings, InterfaceStoredSettingsProps, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OutCommand } from '@/hooks/Mapper/types';
export enum UserSettingsRemoteProps {
link_signature_on_splash = 'link_signature_on_splash',
select_on_spash = 'select_on_spash',
delete_connection_with_sigs = 'delete_connection_with_sigs',
}
export const DEFAULT_REMOTE_SETTINGS = {
[UserSettingsRemoteProps.link_signature_on_splash]: false,
[UserSettingsRemoteProps.select_on_spash]: false,
[UserSettingsRemoteProps.delete_connection_with_sigs]: false,
};
export const UserSettingsRemoteList = [
UserSettingsRemoteProps.link_signature_on_splash,
UserSettingsRemoteProps.select_on_spash,
UserSettingsRemoteProps.delete_connection_with_sigs,
];
export type UserSettingsRemote = {
link_signature_on_splash: boolean;
select_on_spash: boolean;
delete_connection_with_sigs: boolean;
};
export type UserSettings = UserSettingsRemote & InterfaceStoredSettings;
export interface MapSettingsProps {
show: boolean;
onHide: () => void;
}
type CheckboxesList = {
prop: keyof UserSettings;
label: string;
}[];
const COMMON_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: InterfaceStoredSettingsProps.isShowMinimap, label: 'Show Minimap' },
];
const SYSTEMS_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: InterfaceStoredSettingsProps.isShowKSpace, label: 'Highlight Low/High-security systems' },
{ prop: UserSettingsRemoteProps.select_on_spash, label: 'Auto-select splashed' },
];
const SIGNATURES_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: UserSettingsRemoteProps.link_signature_on_splash, label: 'Link signature on splash' },
];
const CONNECTIONS_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: UserSettingsRemoteProps.delete_connection_with_sigs, label: 'Delete connections to linked signatures' },
];
const UI_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: InterfaceStoredSettingsProps.isShowMenu, label: 'Enable compact map menu bar' },
];
export const MapSettings = ({ show, onHide }: MapSettingsProps) => {
const [activeIndex, setActiveIndex] = useState(0);
const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
const [userRemoteSettings, setUserRemoteSettings] = useState<UserSettingsRemote>({ ...DEFAULT_REMOTE_SETTINGS });
const mergedSettings = useMemo(() => {
return {
...interfaceSettings,
...userRemoteSettings,
};
}, [userRemoteSettings, interfaceSettings]);
const handleShow = async () => {
const { user_settings } = await outCommand({
type: OutCommand.getUserSettings,
data: null,
});
setUserRemoteSettings({
...user_settings,
});
};
const handleChangeChecked = useCallback(
(prop: keyof UserSettings) => async (checked: boolean) => {
// @ts-ignore
if (UserSettingsRemoteList.includes(prop)) {
const newRemoteSettings = {
...userRemoteSettings,
[prop]: checked,
};
await outCommand({
type: OutCommand.updateUserSettings,
data: newRemoteSettings,
});
setUserRemoteSettings(newRemoteSettings);
return;
}
setInterfaceSettings({
...interfaceSettings,
[prop]: checked,
});
},
[interfaceSettings, outCommand, setInterfaceSettings, userRemoteSettings],
);
const renderCheckboxesList = (list: CheckboxesList) => {
return list.map(x => {
return (
<PrettySwitchbox
key={x.prop}
label={x.label}
checked={mergedSettings[x.prop]}
setChecked={handleChangeChecked(x.prop)}
/>
);
});
};
return (
<Dialog
header="Map settings"
visible={show}
draggable={false}
style={{ width: '550px' }}
onShow={handleShow}
onHide={() => {
if (!show) {
return;
}
setActiveIndex(0);
onHide();
}}
>
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-2">
<div className={styles.verticalTabsContainer}>
<TabView
activeIndex={activeIndex}
onTabChange={e => setActiveIndex(e.index)}
className={styles.verticalTabView}
>
<TabPanel header="Common" headerClassName={styles.verticalTabHeader}>
<div className="w-full h-full flex flex-col gap-1">{renderCheckboxesList(COMMON_CHECKBOXES_PROPS)}</div>
</TabPanel>
<TabPanel header="Systems" headerClassName={styles.verticalTabHeader}>
<div className="w-full h-full flex flex-col gap-1">
{renderCheckboxesList(SYSTEMS_CHECKBOXES_PROPS)}
</div>
</TabPanel>
<TabPanel header="Connections" headerClassName={styles.verticalTabHeader}>
{renderCheckboxesList(CONNECTIONS_CHECKBOXES_PROPS)}
</TabPanel>
<TabPanel header="Signatures" headerClassName={styles.verticalTabHeader}>
{renderCheckboxesList(SIGNATURES_CHECKBOXES_PROPS)}
</TabPanel>
<TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
{renderCheckboxesList(UI_CHECKBOXES_PROPS)}
</TabPanel>
</TabView>
</div>
</div>
</div>
</Dialog>
);
};

View File

@@ -0,0 +1,48 @@
.CheckboxContainer {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
& > span:nth-child(1) {
color: var(--gray-200);
font-size: 13px;
user-select: none;
}
& > :nth-child(2){
border-bottom: 2px dotted #3f3f3f;
height: 1px;
margin: 0 12px;
}
}
/* Уменьшение размеров InputSwitch с использованием глобальных стилей */
.smallInputSwitch {
height: 100%;
display: flex;
align-items: center;
:global {
.p-inputswitch {
height: 1rem;
width: 2rem;
&.p-inputswitch-checked {
.p-inputswitch-slider::before {
transform: translateX(1rem);
}
}
&.p-highlight .p-inputswitch-slider:before {
transform: translateX(1rem);
}
.p-inputswitch-slider::before {
width: 0.8rem;
height: 0.8rem;
margin-top: -0.4rem;
margin-left: -3px;
}
}
}
}

View File

@@ -0,0 +1,20 @@
import styles from './MapSettings.module.scss';
import { WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
interface PrettySwitchboxProps {
checked: boolean;
setChecked: (checked: boolean) => void;
label: string;
}
export const PrettySwitchbox = ({ checked, setChecked, label }: PrettySwitchboxProps) => {
return (
<label className={styles.CheckboxContainer}>
<span>{label}</span>
<div />
<div className={styles.smallInputSwitch}>
<WdCheckbox size="m" label={''} value={checked} onChange={e => setChecked(e.checked ?? false)} />
</div>
</label>
);
};

View File

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

View File

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

View File

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

View File

@@ -8,9 +8,10 @@ import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
interface RightBarProps {
onShowOnTheMap?: () => void;
onShowMapSettings?: () => void;
}
export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
export const RightBar = ({ onShowOnTheMap, onShowMapSettings }: RightBarProps) => {
const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
const isShowMinimap = interfaceSettings.isShowMinimap === undefined ? true : interfaceSettings.isShowMinimap;
@@ -22,13 +23,6 @@ export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
});
}, [outCommand]);
const handleOpenUserSettings = useCallback(() => {
outCommand({
type: OutCommand.openUserSettings,
data: null,
});
}, [outCommand]);
const toggleMinimap = useCallback(() => {
setInterfaceSettings(x => ({
...x,
@@ -66,17 +60,7 @@ export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
type="button"
onClick={handleAddCharacter}
>
<i className="pi pi-user-plus text-lg"></i>
</button>
</WdTooltipWrapper>
<WdTooltipWrapper content="User settings" 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={handleOpenUserSettings}
>
<i className="pi pi-cog text-lg"></i>
<i className="pi pi-user-plus"></i>
</button>
</WdTooltipWrapper>
@@ -86,12 +70,22 @@ export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
type="button"
onClick={onShowOnTheMap}
>
<i className="pi pi-hashtag text-lg"></i>
<i className="pi pi-hashtag"></i>
</button>
</WdTooltipWrapper>
</div>
<div className="flex flex-col items-center mb-2 gap-1">
<WdTooltipWrapper content="User settings" 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={onShowMapSettings}
>
<i className="pi pi-cog"></i>
</button>
</WdTooltipWrapper>
<WdTooltipWrapper
content={
interfaceSettings.isShowKSpace ? 'Hide highlighting Imperial Space' : 'Show highlighting Imperial Space'
@@ -103,11 +97,7 @@ export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
type="button"
onClick={toggleKSpace}
>
{interfaceSettings.isShowKSpace ? (
<i className="pi pi-heart-fill text-lg"></i>
) : (
<i className="pi pi-heart text-lg"></i>
)}
<i className={interfaceSettings.isShowKSpace ? 'hero-cloud-solid' : 'hero-cloud'}></i>
</button>
</WdTooltipWrapper>
@@ -117,7 +107,7 @@ export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
type="button"
onClick={toggleMinimap}
>
{isShowMinimap ? <i className="pi pi-eye text-lg"></i> : <i className="pi pi-eye-slash text-lg"></i>}
<i className={isShowMinimap ? 'pi pi-eye' : 'pi pi-eye-slash'}></i>
</button>
</WdTooltipWrapper>
@@ -127,7 +117,7 @@ export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
type="button"
onClick={toggleMenu}
>
<i className="pi pi-window-minimize text-lg"></i>
<i className="pi pi-window-minimize"></i>
</button>
</WdTooltipWrapper>
</div>

View File

@@ -0,0 +1,10 @@
import { createGenericContext } from '@/hooks/Mapper/utils/abstractContextProvider.tsx';
export interface SystemsSettingsProvider {
systemId: string;
}
const { Provider, useContextValue } = createGenericContext<SystemsSettingsProvider>();
export const SystemsSettingsProvider = Provider;
export const useSystemsSettingsProvider = useContextValue;

View File

@@ -0,0 +1,81 @@
.verticalTabsContainer {
width: 100%;
min-height: 300px;
}
.verticalTabsContainer {
display: flex;
width: 100%;
min-height: 300px;
:global {
.p-tabview {
width: 100%;
display: flex;
align-items: flex-start;
}
.p-tabview-panels {
padding: 6px 1rem !important;
flex-grow: 1;
}
.p-tabview-nav-container {
border-right: none;
height: 100%;
}
.p-tabview-nav {
flex-direction: column;
width: 150px;
min-height: 100%;
border: none;
li {
width: 100%;
border-right: 4px solid var(--surface-hover);
background-color: var(--surface-card);
transition: background-color 200ms, border-right-color 200ms;
&:hover {
background-color: var(--surface-hover);
border-right: 4px solid var(--surface-100);
}
.p-tabview-nav-link {
transition: color 200ms;
justify-content: flex-end;
padding: 10px;
background-color: initial;
border: none;
color: var(--gray-400);
border-radius: initial;
font-weight: 400;
margin: 0;
}
&.p-tabview-selected {
background-color: var(--surface-50);
border-right: 4px solid var(--primary-color);
.p-tabview-nav-link {
font-weight: 600;
color: var(--primary-color);
}
&:hover {
border-right: 4px solid var(--primary-color);
}
}
}
}
.p-tabview-panel {
flex-grow: 1;
}
}
}

View File

@@ -0,0 +1,171 @@
import { Dialog } from 'primereact/dialog';
import { useCallback, useEffect } from 'react';
// import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OutCommand, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { Controller, FormProvider, useForm } from 'react-hook-form';
import {
SignatureGroupContent,
SignatureGroupSelect,
} from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components';
import { InputText } from 'primereact/inputtext';
import { SystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
import { Button } from 'primereact/button';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
type SystemSignaturePrepared = Omit<SystemSignature, 'linked_system'> & { linked_system: string };
export interface MapSettingsProps {
systemId: string;
show: boolean;
onHide: () => void;
signatureData: SystemSignature | null;
}
export const SignatureSettings = ({ systemId, show, onHide, signatureData }: MapSettingsProps) => {
const { outCommand } = useMapRootState();
const handleShow = async () => {};
const form = useForm<Partial<SystemSignaturePrepared>>({});
const handleSave = useCallback(async () => {
if (!signatureData) {
return;
}
const { group, ...values } = form.getValues();
let out = { ...signatureData };
switch (group) {
case SignatureGroup.Wormhole:
if (values.linked_system) {
await outCommand({
type: OutCommand.linkSignatureToSystem,
data: {
signature_eve_id: signatureData.eve_id,
solar_system_source: systemId,
solar_system_target: values.linked_system,
},
});
}
if (values.type != null) {
out = { ...out, type: values.type };
}
if (signatureData.group !== SignatureGroup.Wormhole) {
out = { ...out, name: '' };
}
break;
case SignatureGroup.CosmicSignature:
out = { ...out, type: '', name: '' };
break;
default:
if (values.name != null) {
out = { ...out, name: values.name ?? '' };
}
}
if (values.description != null) {
out = { ...out, description: values.description };
}
// Note: when type of signature changed from WH to other type - we should drop name
if (
group !== SignatureGroup.Wormhole && // new
signatureData.group === SignatureGroup.Wormhole && // prev
signatureData.linked_system
) {
await outCommand({
type: OutCommand.unlinkSignature,
data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
});
out = { ...out, type: '' };
}
if (group === SignatureGroup.Wormhole && signatureData.linked_system != null && values.linked_system === null) {
await outCommand({
type: OutCommand.unlinkSignature,
data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
});
}
// Note: despite groups have optional type - this will always set
out = { ...out, group: group! };
await outCommand({
type: OutCommand.updateSignatures,
data: {
system_id: systemId,
added: [],
updated: [out],
removed: [],
},
});
form.reset();
onHide();
}, [form, onHide, outCommand, signatureData, systemId]);
useEffect(() => {
if (!signatureData) {
form.reset();
return;
}
const { linked_system, ...rest } = signatureData;
form.reset({
linked_system: linked_system?.solar_system_id.toString() ?? undefined,
...rest,
});
}, [form, signatureData]);
return (
<Dialog
header={`Signature Edit [${signatureData?.eve_id}]`}
visible={show}
draggable={false}
style={{ width: '390px' }}
onShow={handleShow}
onHide={() => {
if (!show) {
return;
}
onHide();
}}
>
<SystemsSettingsProvider initialValue={{ systemId }}>
<FormProvider {...form}>
<div className="flex flex-col gap-2 justify-between">
<div className="w-full flex flex-col gap-1 p-1 min-h-[150px]">
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Group:</span>
<SignatureGroupSelect name="group" />
</label>
<SignatureGroupContent />
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Description:</span>
<Controller
name="description"
control={form.control}
render={({ field }) => (
<InputText placeholder="Type description" value={field.value} onChange={field.onChange} />
)}
/>
</label>
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
</div>
</div>
</FormProvider>
</SystemsSettingsProvider>
</Dialog>
);
};

View File

@@ -0,0 +1,45 @@
import { Controller, useFormContext } from 'react-hook-form';
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { useSystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
import { SignatureGroupContentWormholes } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupContentWormholes.tsx';
import { InputText } from 'primereact/inputtext';
export interface SignatureGroupContentProps {}
export const SignatureGroupContent = ({}: SignatureGroupContentProps) => {
const { watch, control } = useFormContext<SystemSignature>();
const group = watch('group');
const {
value: { systemId },
} = useSystemsSettingsProvider();
if (!systemId) {
return null;
}
if (group === SignatureGroup.Wormhole) {
return (
<>
<SignatureGroupContentWormholes />
</>
);
}
if (group === SignatureGroup.CosmicSignature) {
return <div></div>;
}
return (
<div>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Name:</span>
<Controller
name="name"
control={control}
render={({ field }) => <InputText placeholder="Name" value={field.value} onChange={field.onChange} />}
/>
</label>
</div>
);
};

View File

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

View File

@@ -0,0 +1,18 @@
import { SignatureWormholeTypeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureWormholeTypeSelect';
import { SignatureLeadsToSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect';
export const SignatureGroupContentWormholes = () => {
return (
<>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Type:</span>
<SignatureWormholeTypeSelect name="type" />
</label>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Leads To:</span>
<SignatureLeadsToSelect name="linked_system" />
</label>
</>
);
};

View File

@@ -0,0 +1,59 @@
import { Dropdown } from 'primereact/dropdown';
import clsx from 'clsx';
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { renderIcon } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
import { Controller, useFormContext } from 'react-hook-form';
const signatureGroupOptions = Object.keys(SignatureGroup).map(x => ({
value: SignatureGroup[x as keyof typeof SignatureGroup],
label: SignatureGroup[x as keyof typeof SignatureGroup],
}));
// @ts-ignore
const renderSignatureTemplate = option => {
if (!option) {
return 'No group selected';
}
return (
<div className="flex gap-2 items-center">
<span className="w-[20px] mt-[1px] flex justify-center items-center">
{renderIcon(
{ group: option.label } as SystemSignature,
option.label === SignatureGroup.CosmicSignature ? { w: 10, h: 10 } : { w: 16, h: 16 },
)}
</span>
<span>{option.label}</span>
</div>
);
};
export interface SignatureGroupSelectProps {
name: string;
defaultValue?: string;
}
export const SignatureGroupSelect = ({ name, defaultValue = '' }: SignatureGroupSelectProps) => {
const { control } = useFormContext();
return (
<Controller
name={name}
control={control}
defaultValue={defaultValue}
render={({ field }) => (
<Dropdown
value={field.value}
onChange={field.onChange}
options={signatureGroupOptions}
optionLabel="label"
optionValue="value"
placeholder="Select group"
className={clsx('w-full')}
scrollHeight="240px"
itemTemplate={renderSignatureTemplate}
valueTemplate={renderSignatureTemplate}
/>
)}
/>
);
};

View File

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

View File

@@ -0,0 +1,108 @@
import { Dropdown } from 'primereact/dropdown';
import clsx from 'clsx';
import { Controller, useFormContext } from 'react-hook-form';
import { useSystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
import { useSystemInfo } from '@/hooks/Mapper/components/hooks';
import { useMemo } from 'react';
import { SystemView } from '@/hooks/Mapper/components/ui-kit';
import classes from './SignatureLeadsToSelect.module.scss';
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
import { SystemSignature } from '@/hooks/Mapper/types';
import { WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID } from '@/hooks/Mapper/components/map/constants.ts';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
// @ts-ignore
const renderLinkedSystemItem = (option: { value: string }) => {
if (option.value == null) {
return <div className="flex gap-2 items-center">No linked system</div>;
}
return (
<div className="flex gap-2 items-center">
<SystemView systemId={option.value} className={classes.SystemView} />
</div>
);
};
// @ts-ignore
const renderLinkedSystemValue = (option: { value: string }) => {
if (!option) {
return 'Select Leads To system';
}
if (option.value == null) {
return 'Select Leads To system';
}
return (
<div className="flex gap-2 items-center">
<SystemView systemId={option.value} className={classes.SystemView} />
</div>
);
};
const renderLeadsToEmpty = () => <div className="flex items-center text-[14px]">No wormhole to select</div>;
export interface SignatureLeadsToSelectProps {
name: string;
defaultValue?: string;
}
export const SignatureLeadsToSelect = ({ name, defaultValue = '' }: SignatureLeadsToSelectProps) => {
const { control, watch } = useFormContext<SystemSignature>();
const group = watch('type');
const {
value: { systemId },
} = useSystemsSettingsProvider();
const { leadsTo } = useSystemInfo({ systemId });
const { systems: systemStatics } = useLoadSystemStatic({ systems: leadsTo });
const {
data: { wormholes },
} = useMapRootState();
const leadsToOptions = useMemo(() => {
return [
{ value: null },
...leadsTo
.filter(systemId => {
const systemStatic = systemStatics.get(parseInt(systemId));
const whInfo = wormholes.find(x => x.name === group);
if (!systemStatic || !whInfo || group === 'K162') {
return true;
}
const { id: whType } = WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID[systemStatic.system_class];
return whInfo.dest === whType;
})
.map(x => ({ value: x })),
];
}, [group, leadsTo, systemStatics, wormholes]);
return (
<Controller
// @ts-ignore
name={name}
control={control}
defaultValue={defaultValue}
render={({ field }) => {
return (
<Dropdown
value={field.value}
onChange={field.onChange}
options={leadsToOptions}
optionValue="value"
placeholder="Select Leads To wormhole"
className={clsx('w-full')}
scrollHeight="240px"
itemTemplate={renderLinkedSystemItem}
valueTemplate={renderLinkedSystemValue}
emptyMessage={renderLeadsToEmpty}
/>
);
}}
/>
);
};

View File

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

View File

@@ -0,0 +1,134 @@
import { Dropdown } from 'primereact/dropdown';
import clsx from 'clsx';
import { Respawn, SolarSystemStaticInfoRaw, WormholeDataRaw } from '@/hooks/Mapper/types';
import { Controller, useFormContext } from 'react-hook-form';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useSystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
import { useSystemInfo } from '@/hooks/Mapper/components/hooks';
import {
SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS,
WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID,
} from '@/hooks/Mapper/components/map/constants.ts';
import { useMemo } from 'react';
import { WHClassView } from '@/hooks/Mapper/components/ui-kit';
const getPossibleWormholes = (systemStatic: SolarSystemStaticInfoRaw, wormholes: WormholeDataRaw[]) => {
const { id: whType } = WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID[systemStatic.system_class];
// @ts-ignore
const spawnClassGroup = SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS[whType];
const possibleWHTypes = wormholes.filter(x => x.src.includes(spawnClassGroup));
return {
statics: possibleWHTypes
.filter(x => x.respawn.some(y => y === Respawn.static))
.filter(x => systemStatic.statics.includes(x.name)),
k162: wormholes.find(x => x.name === 'K162')!,
wanderings: possibleWHTypes.filter(x => x.respawn.some(y => y === Respawn.wandering)),
};
};
// @ts-ignore
const renderWHTypeGroupTemplate = option => {
return (
<div className="flex gap-2 items-center">
<span>{option.label}</span>
</div>
);
};
// @ts-ignore
const renderWHTypeTemplateValue = (option: { label: string; data: WormholeDataRaw }) => {
if (!option) {
return 'Select wormhole type';
}
return (
<div className="flex gap-2 items-center">
<WHClassView whClassName={option.data.name} noOffset useShortTitle />
</div>
);
};
// @ts-ignore
const renderWHTypeTemplate = (option: { label: string; data: WormholeDataRaw }) => {
return (
<div className="flex gap-2 items-center ml-[1rem]">
<WHClassView whClassName={option.data.name} noOffset useShortTitle />
</div>
);
};
export interface SignatureGroupSelectProps {
name: string;
defaultValue?: string;
}
export const SignatureWormholeTypeSelect = ({ name, defaultValue = '' }: SignatureGroupSelectProps) => {
const { control } = useFormContext();
const {
data: { wormholes },
} = useMapRootState();
const {
value: { systemId },
} = useSystemsSettingsProvider();
const system = useSystemInfo({ systemId });
const possibleWormholesOptions = useMemo(() => {
const possibleWormholes = getPossibleWormholes(system.staticInfo, wormholes);
return [
{
label: 'Statics',
items: [
...possibleWormholes.statics.map(x => ({
label: x.name,
value: x.name,
data: x,
})),
{
value: possibleWormholes.k162.name,
label: possibleWormholes.k162.name,
data: possibleWormholes.k162,
},
],
},
{
label: 'Wanderings',
items: possibleWormholes.wanderings.map(x => ({
label: x.name,
value: x.name,
data: x,
})),
},
];
}, [system, wormholes]);
return (
<Controller
name={name}
control={control}
defaultValue={defaultValue}
render={({ field }) => (
<Dropdown
value={field.value}
onChange={field.onChange}
options={possibleWormholesOptions}
optionLabel="label"
optionValue="value"
placeholder="Select wormhole type"
optionGroupLabel="label"
optionGroupChildren="items"
className={clsx('w-full')}
scrollHeight="240px"
optionGroupTemplate={renderWHTypeGroupTemplate}
itemTemplate={renderWHTypeTemplate}
valueTemplate={renderWHTypeTemplateValue}
/>
)}
/>
);
};

View File

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

View File

@@ -0,0 +1,2 @@
export * from './SignatureGroupSelect';
export * from './SignatureGroupContent';

View File

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

View File

@@ -5,6 +5,11 @@
.WHClassViewContent {
display: flex;
gap: 2px;
&.NoOffset {
gap: 4px;
align-items: center;
}
}
.WHClassName {
@@ -13,3 +18,12 @@
font-weight: bold;
top: -2px;
}
.NoOffset {
*.WHClassName {
position: relative;
font-size: 12px;
font-weight: initial !important;
top: initial !important;
}
}

View File

@@ -16,26 +16,42 @@ const prepareMass = (mass: number) => {
export interface WHClassViewProps {
whClassName: string;
noOffset?: boolean;
useShortTitle?: boolean;
hideWhClass?: boolean;
highlightName?: boolean;
className?: string;
classNameWh?: string;
}
export const WHClassView = ({ whClassName }: WHClassViewProps) => {
export const WHClassView = ({
whClassName,
noOffset,
useShortTitle,
hideWhClass,
highlightName,
className,
classNameWh,
}: WHClassViewProps) => {
const {
data: { wormholesData },
} = useMapRootState();
const whData = useMemo(() => wormholesData[whClassName], [whClassName, wormholesData]);
const whClass = useMemo(() => WORMHOLES_ADDITIONAL_INFO[whData.dest], [whData.dest]);
const whClassStyle = WORMHOLE_CLASS_STYLES[whClass.wormholeClassID];
const whClassStyle = WORMHOLE_CLASS_STYLES[whClass?.wormholeClassID] ?? '';
const uid = useMemo(() => new Date().getTime().toString(), []);
return (
<div className={classes.WHClassViewRoot}>
<div className={clsx(classes.WHClassViewRoot, className)}>
<Tooltip
target={`.wh-name${whClassName}`}
target={`.wh-name${whClassName}${uid}`}
position="right"
mouseTrack
mouseTrackLeft={20}
mouseTrackTop={30}
className="border border-green-300 rounded border-opacity-10 bg-stone-900 bg-opacity-70 "
className="border border-green-300 rounded border-opacity-10 bg-stone-900 bg-opacity-90 "
>
<div className="flex gap-3">
<div className="flex flex-col gap-1">
@@ -49,9 +65,20 @@ export const WHClassView = ({ whClassName }: WHClassViewProps) => {
</div>
</Tooltip>
<div className={clsx(classes.WHClassViewContent, 'wh-name select-none cursor-help', `wh-name${whClassName}`)}>
<span>{whClassName}</span>
<span className={clsx(classes.WHClassName, whClassStyle)}>{whClass.shortName}</span>
<div
className={clsx(
classes.WHClassViewContent,
{ [classes.NoOffset]: noOffset },
'wh-name select-none cursor-help',
`wh-name${whClassName}${uid}`,
)}
>
<span className={clsx({ [whClassStyle]: highlightName })}>{whClassName}</span>
{!hideWhClass && whClass && (
<span className={clsx(classes.WHClassName, whClassStyle, classNameWh)}>
{useShortTitle ? whClass.shortTitle : whClass.shortName}
</span>
)}
</div>
</div>
);

View File

@@ -1,13 +1,12 @@
import { createEvent } from 'react-event-hook';
export interface MapEvent {
name: string;
data: {
solar_system_source: number;
solar_system_target: number;
};
import { Command, CommandData } from '@/hooks/Mapper/types/mapHandlers.ts';
export interface MapEvent<T extends Command> {
name: T;
data: CommandData[T];
}
const { useMapEventListener, emitMapEvent } = createEvent('map-event')<MapEvent>();
const { useMapEventListener, emitMapEvent } = createEvent('map-event')<MapEvent<Command>>();
export { useMapEventListener, emitMapEvent };

View File

@@ -19,6 +19,7 @@ export const parseSignatures = (value: string, availableKeys: string[]): SystemS
kind: availableKeys.includes(sigArrInfo[1]) ? sigArrInfo[1] : COSMIC_SIGNATURE,
group: sigArrInfo[2],
name: sigArrInfo[3],
type: '',
});
}

View File

@@ -2,6 +2,10 @@ import { WORMHOLES_ADDITIONAL_INFO } from '@/hooks/Mapper/components/map/constan
import { WormholeDataRaw } from '@/hooks/Mapper/types';
export const sortWHClasses = (wormholesData: Record<string, WormholeDataRaw>, statics: string[]) => {
if (!statics) {
return [];
}
return statics
.map(x => wormholesData[x])
.map(x => ({ name: x.name, ...WORMHOLES_ADDITIONAL_INFO[x.dest] }))

View File

@@ -12,6 +12,7 @@ export type MapRootData = MapUnionTypes & {
const INITIAL_DATA: MapRootData = {
wormholesData: {},
wormholes: [],
effects: {},
characters: [],
userCharacters: [],
@@ -26,7 +27,13 @@ const INITIAL_DATA: MapRootData = {
selectedConnections: [],
};
type InterfaceStoredSettings = {
export enum InterfaceStoredSettingsProps {
isShowMenu = 'isShowMenu',
isShowMinimap = 'isShowMinimap',
isShowKSpace = 'isShowKSpace',
}
export type InterfaceStoredSettings = {
isShowMenu: boolean;
isShowMinimap: boolean;
isShowKSpace: boolean;

View File

@@ -24,6 +24,7 @@ export const useMapInit = () => {
if (wormholes) {
updateData.wormholesData = wormholes.reduce((acc, x) => ({ ...acc, [x.name]: x }), {});
updateData.wormholes = wormholes;
}
if (effects) {

View File

@@ -49,15 +49,25 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
break;
case Commands.addSystems:
addSystems(data as CommandAddSystems);
setTimeout(() => {
emitMapEvent({ name: Commands.addSystems, data });
}, 100);
break;
case Commands.updateSystems:
updateSystems(data as CommandUpdateSystems);
break;
case Commands.removeSystems:
removeSystems(data as CommandRemoveSystems);
setTimeout(() => {
emitMapEvent({ name: Commands.removeSystems, data });
}, 100);
break;
case Commands.addConnections:
addConnections(data as CommandAddConnections);
setTimeout(() => {
emitMapEvent({ name: Commands.addConnections, data });
}, 100);
break;
case Commands.removeConnections:
removeConnections(data as CommandRemoveConnections);
@@ -96,6 +106,8 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
break;
case Commands.linkSignatureToSystem:
// TODO command data type lost
// @ts-ignore
emitMapEvent({ name: Commands.linkSignatureToSystem, data });
break;
@@ -103,6 +115,12 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
// do nothing here
break;
case Commands.signaturesUpdated:
// TODO command data type lost
// @ts-ignore
emitMapEvent({ name: Commands.signaturesUpdated, data });
break;
default:
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
break;

View File

@@ -11,6 +11,10 @@ export type Passage = {
character: PassageLimitedCharacterType;
};
export type ConnectionInfoOutput = {
marl_eol_time: string;
};
export type ConnectionOutput = {
passages: Passage[];
};

View File

@@ -24,6 +24,7 @@ export enum Commands {
centerSystem = 'center_system',
selectSystem = 'select_system',
linkSignatureToSystem = 'link_signature_to_system',
signaturesUpdated = 'signatures_updated',
}
export type Command =
@@ -44,7 +45,8 @@ export type Command =
| Commands.routes
| Commands.selectSystem
| Commands.centerSystem
| Commands.linkSignatureToSystem;
| Commands.linkSignatureToSystem
| Commands.signaturesUpdated;
export type CommandInit = {
systems: SolarSystemRawType[];
@@ -80,8 +82,8 @@ export type CommandCenterSystem = string | undefined;
export type CommandLinkSignatureToSystem = {
solar_system_source: number;
solar_system_target: number;
signatures: any[];
};
export type CommandLinkSignaturesUpdated = number;
export interface CommandData {
[Commands.init]: CommandInit;
@@ -102,6 +104,7 @@ export interface CommandData {
[Commands.selectSystem]: CommandSelectSystem;
[Commands.centerSystem]: CommandCenterSystem;
[Commands.linkSignatureToSystem]: CommandLinkSignatureToSystem;
[Commands.signaturesUpdated]: CommandLinkSignaturesUpdated;
}
export interface MapHandlers {
@@ -115,10 +118,12 @@ export enum OutCommand {
getCharacterJumps = 'get_character_jumps',
getSignatures = 'get_signatures',
getSystemStaticInfos = 'get_system_static_infos',
getConnectionInfo = 'get_connection_info',
updateConnectionTimeStatus = 'update_connection_time_status',
updateConnectionMassStatus = 'update_connection_mass_status',
updateConnectionShipSizeType = 'update_connection_ship_size_type',
updateConnectionLocked = 'update_connection_locked',
updateConnectionCustomInfo = 'update_connection_custom_info',
updateSignatures = 'update_signatures',
updateSystemName = 'update_system_name',
updateSystemDescription = 'update_system_description',
@@ -141,6 +146,10 @@ export enum OutCommand {
// Only UI commands
openSettings = 'open_settings',
getUserSettings = 'get_user_settings',
updateUserSettings = 'update_user_settings',
unlinkSignature = 'unlink_signature',
}
export type OutCommandHandler = <T = any>(event: { type: OutCommand; data: any }) => Promise<T>;

View File

@@ -7,6 +7,7 @@ import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
export type MapUnionTypes = {
wormholesData: Record<string, WormholeDataRaw>;
wormholes: WormholeDataRaw[];
effects: Record<string, EffectRaw>;
characters: CharacterTypeRaw[];
userCharacters: string[];

View File

@@ -1,23 +1,13 @@
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
export type SystemSignature = {
eve_id: string;
kind: string;
name: string;
description?: string;
group: string;
linked_system?: SolarSystemStaticInfoRaw;
updated_at?: string;
};
export enum SignatureGroup {
CosmicSignature = 'Cosmic Signature',
Wormhole = 'Wormhole',
GasSite = 'Gas Site',
RelicSite = 'Relic Site',
DataSite = 'Data Site',
OreSite = 'Ore Site',
CombatSite = 'Combat Site',
Wormhole = 'Wormhole',
CosmicSignature = 'Cosmic Signature',
}
export type GroupType = {
@@ -26,3 +16,14 @@ export type GroupType = {
w: number;
h: number;
};
export type SystemSignature = {
eve_id: string;
kind: string;
name: string;
description?: string;
group: SignatureGroup;
type: string;
linked_system?: SolarSystemStaticInfoRaw;
updated_at?: string;
};

View File

@@ -1,3 +1,9 @@
export enum Respawn {
static = 'static',
wandering = 'wandering',
reverse = 'reverse',
}
export type WormholeDataRaw = {
dest: string;
id: number;
@@ -5,7 +11,7 @@ export type WormholeDataRaw = {
mass_regen: number;
max_mass_per_jump: number;
name: string;
sibling_groups: any;
respawn: Respawn[];
src: string[];
static: boolean;
total_mass: number;

View File

@@ -0,0 +1,26 @@
import { createContext, ReactNode, useContext, useState } from 'react';
type ContextType<T> = {
value: T;
setValue: (newValue: T) => void;
};
export const createGenericContext = <T,>() => {
const context = createContext<ContextType<T> | undefined>(undefined);
const Provider = ({ children, initialValue }: { children: ReactNode; initialValue: T }) => {
const [value, setValue] = useState<T>(initialValue);
return <context.Provider value={{ value, setValue }}>{children}</context.Provider>;
};
const useContextValue = () => {
const contextValue = useContext(context);
if (!contextValue) {
throw new Error('useContextValue must be used within a Provider');
}
return contextValue;
};
return { Provider, useContextValue };
};

View File

@@ -65,7 +65,7 @@ export class LabelsManager {
}
hasLabel(label: string) {
return this.parsedLabels.labels.includes(label);
return this.parsedLabels.labels?.includes(label);
}
toggleLabel(label: string) {

View File

@@ -8,7 +8,7 @@ export default {
const selector = '#' + this.el.id;
const droppable = new Droppable(containers, {
delay: 150,
delay: 100,
draggable: '.draggable',
dropzone: '.dropzone',
mirror: {

View File

@@ -31,6 +31,7 @@
"react-event-hook": "^3.1.2",
"react-flow-renderer": "^10.3.17",
"react-grid-layout": "^1.3.4",
"react-hook-form": "^7.53.1",
"react-usestateref": "^1.0.9",
"reactflow": "^11.10.4",
"rxjs": "^7.8.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

View File

@@ -3230,6 +3230,11 @@ react-grid-layout@^1.3.4:
react-resizable "^3.0.5"
resize-observer-polyfill "^1.5.1"
react-hook-form@^7.53.1:
version "7.53.1"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.1.tgz#3f2cd1ed2b3af99416a4ac674da2d526625add67"
integrity sha512-6aiQeBda4zjcuaugWvim9WsGqisoUk+etmFEsSUMm451/Ic8L/UAb7sRtMj3V+Hdzm6mMjU1VhiSzYUZeBm0Vg==
react-is@^16.13.1:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"

View File

@@ -55,11 +55,11 @@ map_subscriptions_enabled =
map_subscription_characters_limit =
config_dir
|> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_CHARACTERS_LIMIT", 100)
|> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_CHARACTERS_LIMIT", 10_000)
map_subscription_hubs_limit =
config_dir
|> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_HUBS_LIMIT", 10)
|> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_HUBS_LIMIT", 100)
wallet_tracking_enabled =
config_dir
@@ -77,7 +77,7 @@ config :wanderer_app,
web_app_url: web_app_url,
git_sha: System.get_env("GIT_SHA", "111"),
custom_route_base_url: System.get_env("CUSTOM_ROUTE_BASE_URL"),
invites: System.get_env("WANDERER_INVITES", "false") == "true",
invites: System.get_env("WANDERER_INVITES", "false") |> String.to_existing_atom(),
admin_username: System.get_env("WANDERER_ADMIN_USERNAME", "admin"),
admin_password: System.get_env("WANDERER_ADMIN_PASSWORD"),
admins: admins,
@@ -138,6 +138,7 @@ config :ueberauth, WandererApp.Ueberauth.Strategy.Eve.OAuth,
System.get_env("EVE_CLIENT_WITH_CORP_WALLET_SECRET", "<EVE_CLIENT_WITH_CORP_WALLET_SECRET>")
config :logger,
truncate: :infinity,
level:
String.to_existing_atom(
System.get_env(

View File

@@ -6,6 +6,7 @@
app = 'wanderer'
primary_region = 'ams'
kill_signal = 'SIGTERM'
swap_size_mb = 512
[build]
@@ -35,6 +36,6 @@ path = "/metrics"
soft_limit = 1000
[[vm]]
memory = '1gb'
memory = '256mb'
cpu_kind = 'shared'
cpus = 1

View File

@@ -7,6 +7,8 @@ defmodule WandererApp do
if it comes from the database, an external API or others.
"""
require Logger
@doc """
When used, dispatch to the appropriate domain service
"""
@@ -30,4 +32,19 @@ defmodule WandererApp do
defmacro __using__(which) when is_atom(which) do
apply(__MODULE__, which, [])
end
def log_exception(kind, reason, stacktrace) do
reason = Exception.normalize(kind, reason, stacktrace)
crash_reason =
case kind do
:throw -> {{:nocatch, reason}, stacktrace}
_ -> {reason, stacktrace}
end
Logger.error(
Exception.format(kind, reason, stacktrace),
crash_reason: crash_reason
)
end
end

View File

@@ -4,8 +4,6 @@ defmodule WandererApp.Api.Calculations.CalcMapPermissions do
use Ash.Resource.Calculation
require Ash.Query
import Bitwise
@impl true
def load(_query, _opts, _context) do
[
@@ -17,116 +15,8 @@ defmodule WandererApp.Api.Calculations.CalcMapPermissions do
end
@impl true
def calculate([record], _opts, %{actor: actor}) do
characters = actor.characters
character_ids = characters |> Enum.map(& &1.id)
character_eve_ids = characters |> Enum.map(& &1.eve_id)
character_corporation_ids =
characters |> Enum.map(& &1.corporation_id) |> Enum.map(&to_string/1)
character_alliance_ids = characters |> Enum.map(& &1.alliance_id) |> Enum.map(&to_string/1)
result =
record.acls
|> Enum.reduce([0, 0], fn acl, acc ->
is_owner? = acl.owner_id in character_ids
is_character_member? =
acl.members |> Enum.any?(fn member -> member.eve_character_id in character_eve_ids end)
is_corporation_member? =
acl.members
|> Enum.any?(fn member -> member.eve_corporation_id in character_corporation_ids end)
is_alliance_member? =
acl.members
|> Enum.any?(fn member -> member.eve_alliance_id in character_alliance_ids end)
if is_owner? || is_character_member? || is_corporation_member? || is_alliance_member? do
case acc do
[_, -1] ->
[-1, -1]
[-1, char_acc] ->
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
end
end)
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[-1, char_acc]
[any_acc, char_acc] ->
any_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids ||
member.eve_corporation_id in character_corporation_ids ||
member.eve_alliance_id in character_alliance_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
end
end)
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
end
end)
any_acc =
case any_acl_mask do
-1 -> -1
_ -> any_acc ||| any_acl_mask
end
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[any_acc, char_acc]
end
else
acc
end
end)
case result do
[_, -1] ->
[-1]
[-1, char_acc] ->
[char_acc]
[any_acc, _char_acc] ->
[any_acc]
end
end
def calculate([record], _opts, %{actor: actor}),
do: WandererApp.Permissions.check_characters_access(actor.characters, record.acls)
@impl true
def calculate(_records, _opts, _context) do

View File

@@ -28,6 +28,7 @@ defmodule WandererApp.Api.MapConnection do
define(:update_time_status, action: :update_time_status)
define(:update_ship_size_type, action: :update_ship_size_type)
define(:update_locked, action: :update_locked)
define(:update_custom_info, action: :update_custom_info)
end
actions do
@@ -87,6 +88,10 @@ defmodule WandererApp.Api.MapConnection do
update :update_locked do
accept [:locked]
end
update :update_custom_info do
accept [:custom_info]
end
end
attributes do
@@ -131,6 +136,10 @@ defmodule WandererApp.Api.MapConnection do
attribute :locked, :boolean
attribute :custom_info, :string do
allow_nil? true
end
create_timestamp(:inserted_at)
update_timestamp(:updated_at)
end

View File

@@ -15,6 +15,7 @@ defmodule WandererApp.Api.MapSystemSignature do
define(:create, action: :create)
define(:update, action: :update)
define(:update_linked_system, action: :update_linked_system)
define(:update_type, action: :update_type)
define(:by_id,
get_by: [:id],
@@ -32,7 +33,8 @@ defmodule WandererApp.Api.MapSystemSignature do
:name,
:description,
:kind,
:group
:group,
:type
]
defaults [:read, :destroy]
@@ -51,7 +53,8 @@ defmodule WandererApp.Api.MapSystemSignature do
:name,
:description,
:kind,
:group
:group,
:type
]
argument :system_id, :uuid, allow_nil?: false
@@ -68,7 +71,7 @@ defmodule WandererApp.Api.MapSystemSignature do
:description,
:kind,
:group,
:linked_system_id
:type
]
primary? true
@@ -79,6 +82,10 @@ defmodule WandererApp.Api.MapSystemSignature do
accept [:linked_system_id]
end
update :update_type do
accept [:type]
end
read :by_system_id do
argument(:system_id, :string, allow_nil?: false)
@@ -105,6 +112,10 @@ defmodule WandererApp.Api.MapSystemSignature do
allow_nil? true
end
attribute :type, :string do
allow_nil? true
end
attribute :linked_system_id, :integer do
allow_nil? true
end

View File

@@ -8,6 +8,10 @@ defmodule WandererApp.Api.UserActivity do
postgres do
repo(WandererApp.Repo)
table("user_activity_v1")
custom_indexes do
index [:entity_id, :event_type, :inserted_at], unique: true
end
end
code_interface do
@@ -104,6 +108,8 @@ defmodule WandererApp.Api.UserActivity do
update_timestamp(:updated_at)
end
relationships do
belongs_to :character, WandererApp.Api.Character do
allow_nil? true

View File

@@ -3,6 +3,8 @@ defmodule WandererApp.Application do
use Application
require Logger
@impl true
def start(_type, _args) do
children =
@@ -45,7 +47,16 @@ defmodule WandererApp.Application do
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: WandererApp.Supervisor]
Supervisor.start_link(children, opts)
|> case do
{:ok, _pid} = ok ->
ok
{:error, info} = e ->
Logger.error("Failed to start application: #{inspect(info)}")
e
end
end
# Tell Phoenix to update the endpoint configuration

View File

@@ -73,7 +73,8 @@ defmodule WandererApp.Character.Tracker do
case WandererApp.Esi.get_character_info(eve_id) do
{:ok, info} ->
{:ok, character_state} = WandererApp.Character.get_character_state(character_id)
update = maybe_update_corporation(character_state, info)
update = maybe_update_corporation(character_state, eve_id |> String.to_integer())
WandererApp.Character.update_character_state(character_id, update)
:ok
@@ -103,7 +104,9 @@ defmodule WandererApp.Character.Tracker do
end
def update_ship(%{character_id: character_id, track_ship: true} = character_state) do
case WandererApp.Character.get_character(character_id) do
character_id
|> 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")
|> case do
@@ -541,12 +544,17 @@ defmodule WandererApp.Character.Tracker do
defp maybe_update_corporation(
state,
%{
"corporation_id" => corporation_id
} = _info
character_eve_id
)
when not is_nil(corporation_id),
do: update_corporation(state, corporation_id)
when not is_nil(character_eve_id) and is_integer(character_eve_id) do
case WandererApp.Esi.post_characters_affiliation([character_eve_id]) do
{:ok, [character_aff_info]} when not is_nil(character_aff_info) ->
update_corporation(state, character_aff_info |> Map.get("corporation_id"))
error ->
state
end
end
defp maybe_update_corporation(
state,

View File

@@ -5,6 +5,10 @@ defmodule WandererApp.Esi do
defdelegate get_alliance_info(eve_id, opts \\ []), to: WandererApp.Esi.ApiClient
defdelegate get_corporation_info(eve_id, opts \\ []), to: WandererApp.Esi.ApiClient
defdelegate get_character_info(eve_id, opts \\ []), to: WandererApp.Esi.ApiClient
defdelegate post_characters_affiliation(character_eve_ids, opts \\ []),
to: WandererApp.Esi.ApiClient
defdelegate get_character_wallet(character_eve_id, opts \\ []), to: WandererApp.Esi.ApiClient
defdelegate get_corporation_wallets(corporation_id, opts \\ []), to: WandererApp.Esi.ApiClient

View File

@@ -37,21 +37,32 @@ defmodule WandererApp.Esi.ApiClient do
@logger Application.compile_env(:wanderer_app, :logger)
def get_server_status, do: _get("/status")
def get_server_status, do: get("/status")
def set_autopilot_waypoint(add_to_beginning, clear_other_waypoints, destination_id, opts \\ []) do
_post_esi(
"/ui/autopilot/waypoint",
opts
|> Keyword.merge(
params: %{
add_to_beginning: add_to_beginning,
clear_other_waypoints: clear_other_waypoints,
destination_id: destination_id
}
def set_autopilot_waypoint(add_to_beginning, clear_other_waypoints, destination_id, opts \\ []),
do:
post_esi(
"/ui/autopilot/waypoint",
opts
|> Keyword.merge(
params: %{
add_to_beginning: add_to_beginning,
clear_other_waypoints: clear_other_waypoints,
destination_id: destination_id
}
)
)
)
end
def post_characters_affiliation(character_eve_ids, _opts)
when is_list(character_eve_ids),
do:
post(
"#{@base_url}/characters/affiliation/",
json: character_eve_ids,
params: %{
datasource: "tranquility"
}
)
def find_routes(map_id, origin, hubs, routes_settings) do
origin = origin |> String.to_integer()
@@ -173,7 +184,7 @@ defmodule WandererApp.Esi.ApiClient do
routes =
all_routes
|> Enum.map(fn route_info ->
_map_route_info(route_info)
map_route_info(route_info)
end)
|> Enum.filter(fn route_info -> not is_nil(route_info) end)
@@ -189,7 +200,7 @@ defmodule WandererApp.Esi.ApiClient do
{:ok, result}
_ ->
case _get_all_routes_custom(hubs, origin, params) do
case get_all_routes_custom(hubs, origin, params) do
{:ok, result} ->
WandererApp.Cache.insert(
cache_key,
@@ -199,22 +210,21 @@ defmodule WandererApp.Esi.ApiClient do
{:ok, result}
{:error, error} ->
@logger.error("Error getting custom routes for #{inspect(origin)}: #{inspect(error)}")
{:error, _error} ->
@logger.error("Error getting custom routes for #{inspect(origin)}: #{inspect(hubs)}")
@logger.error(
"Error getting custom routes for #{inspect(origin)}: #{inspect(params)}"
)
_get_all_routes_eve(hubs, origin, params, opts)
get_all_routes_eve(hubs, origin, params, opts)
end
end
end
defp _get_all_routes_custom(hubs, origin, params),
defp get_all_routes_custom(hubs, origin, params),
do:
_post(
post(
"#{get_custom_route_base_url()}/route/multiple",
[
json: %{
@@ -228,7 +238,7 @@ defmodule WandererApp.Esi.ApiClient do
|> Keyword.merge(@timeout_opts)
)
def _get_all_routes_eve(hubs, origin, params, opts),
def get_all_routes_eve(hubs, origin, params, opts),
do:
{:ok,
hubs
@@ -297,7 +307,7 @@ defmodule WandererApp.Esi.ApiClient do
opts: [ttl: @ttl]
)
def get_character_info(eve_id, opts \\ []) do
case _get(
case get(
"/characters/#{eve_id}/",
opts |> _with_cache_opts()
) do
@@ -374,7 +384,7 @@ defmodule WandererApp.Esi.ApiClient do
defp _get_routes_eve(origin, destination, params, opts),
do:
_get(
get(
"/route/#{origin}/#{destination}/?#{params |> Plug.Conn.Query.encode()}",
opts |> _with_cache_opts()
)
@@ -383,14 +393,14 @@ defmodule WandererApp.Esi.ApiClient do
defp _get_alliance_info(alliance_eve_id, info_path, opts),
do:
_get(
get(
"/alliances/#{alliance_eve_id}/#{info_path}",
opts |> _with_cache_opts()
)
defp _get_corporation_info(corporation_eve_id, info_path, opts),
do:
_get(
get(
"/corporations/#{corporation_eve_id}/#{info_path}",
opts |> _with_cache_opts()
)
@@ -405,7 +415,7 @@ defmodule WandererApp.Esi.ApiClient do
character_id = opts |> Keyword.get(:character_id, nil)
if not _is_access_token_expired?(character_id) do
_get(
get(
path,
auth_opts,
opts
@@ -426,7 +436,7 @@ defmodule WandererApp.Esi.ApiClient do
defp _get_corporation_auth_data(corporation_eve_id, info_path, opts),
do:
_get(
get(
"/corporations/#{corporation_eve_id}/#{info_path}",
[params: opts[:params] || []] ++
(opts |> _get_auth_opts() |> _with_cache_opts()),
@@ -437,14 +447,14 @@ defmodule WandererApp.Esi.ApiClient do
opts |> Keyword.merge(@cache_opts) |> Keyword.merge(cache_dir: System.tmp_dir!())
end
defp _post_esi(path, opts),
defp post_esi(path, opts),
do:
_post(
post(
"#{@base_url}#{path}",
[params: opts[:params] || []] ++ (opts |> _get_auth_opts())
)
defp _get(path, api_opts \\ [], opts \\ []) do
defp get(path, api_opts \\ [], opts \\ []) do
try do
case Req.get("#{@base_url}#{path}", api_opts |> Keyword.merge(@retry_opts)) do
{:ok, %{status: 200, body: body}} ->
@@ -476,7 +486,7 @@ defmodule WandererApp.Esi.ApiClient do
end
end
defp _post(url, opts) do
defp post(url, opts) do
try do
case Req.post("#{url}", opts) do
{:ok, %{status: status, body: body}} when status in [200, 201] ->
@@ -514,7 +524,7 @@ defmodule WandererApp.Esi.ApiClient do
{:ok, token} ->
auth_opts = [access_token: token.access_token] |> _get_auth_opts()
_get(
get(
path,
api_opts |> Keyword.merge(auth_opts),
opts |> Keyword.merge(retry_count: retry_count + 1)
@@ -589,7 +599,7 @@ defmodule WandererApp.Esi.ApiClient do
end
end
defp _map_route_info(
defp map_route_info(
%{
"origin" => origin,
"destination" => destination,
@@ -598,14 +608,14 @@ defmodule WandererApp.Esi.ApiClient do
} = _route_info
),
do:
_map_route_info(%{
map_route_info(%{
origin: origin,
destination: destination,
systems: result_systems,
success: success
})
defp _map_route_info(
defp map_route_info(
%{origin: origin, destination: destination, systems: result_systems, success: success} =
_route_info
) do
@@ -627,5 +637,5 @@ defmodule WandererApp.Esi.ApiClient do
}
end
defp _map_route_info(_), do: nil
defp map_route_info(_), do: nil
end

View File

@@ -79,7 +79,8 @@ defmodule WandererApp.EveDataService do
max_mass_per_jump: row["max_mass_per_jump"],
static: row["static"],
mass_regen: row["mass_regen"],
sibling_groups: row["sibling_groups"]
sibling_groups: row["sibling_groups"],
respawn: row["respawn"]
}
end)
end

View File

@@ -8,6 +8,7 @@ defmodule WandererApp.Map do
defstruct map_id: nil,
name: nil,
scope: :none,
owner_id: nil,
characters: [],
systems: Map.new(),
hubs: [],
@@ -16,11 +17,12 @@ defmodule WandererApp.Map do
characters_limit: nil,
hubs_limit: nil
def new(%{id: map_id, name: name, scope: scope, acls: acls, hubs: hubs}) do
def new(%{id: map_id, name: name, scope: scope, owner_id: owner_id, acls: acls, hubs: hubs}) do
map =
struct!(__MODULE__,
map_id: map_id,
scope: scope,
owner_id: owner_id,
name: name,
acls: acls,
hubs: hubs
@@ -52,15 +54,6 @@ defmodule WandererApp.Map do
end
end
def get_map_options!(map) do
map
|> Map.get(:options)
|> case do
nil -> %{"layout" => "left_to_right"}
options -> Jason.decode!(options)
end
end
def update_map(map_id, map_update) do
Cachex.get_and_update(:map_cache, map_id, fn map ->
case map do
@@ -223,7 +216,7 @@ defmodule WandererApp.Map do
%{visible: true} = system ->
system
_ ->
_system ->
nil
end
end
@@ -271,7 +264,7 @@ defmodule WandererApp.Map do
case not Map.has_key?(systems, solar_system_id) do
true ->
map_id
|> update_map(%{systems: Map.put_new(systems, solar_system_id, system)})
|> update_map(%{systems: Map.put(systems, solar_system_id, system)})
:ok

View File

@@ -75,7 +75,7 @@ defmodule WandererApp.Map.PositionCalculator do
def get_available_positions(level, x, y, opts),
do: adjusted_coordinates(1 + level * 2, x, y, opts)
defp edge_coordinates(n, opts) when n > 1 do
defp edge_coordinates(n, _opts) when n > 1 do
min = -div(n, 2)
max = div(n, 2)
# Top edge

View File

@@ -183,6 +183,12 @@ defmodule WandererApp.Map.Server do
|> map_pid!
|> GenServer.cast({&Impl.delete_connection/2, [connection_info]})
def get_connection_info(map_id, connection_info) when is_binary(map_id),
do:
map_id
|> map_pid!
|> GenServer.call({&Impl.get_connection_info/2, [connection_info]}, :timer.minutes(1))
def update_connection_time_status(map_id, connection_info) when is_binary(map_id),
do:
map_id
@@ -207,6 +213,12 @@ defmodule WandererApp.Map.Server do
|> map_pid!
|> GenServer.cast({&Impl.update_connection_locked/2, [connection_info]})
def update_connection_custom_info(map_id, connection_info) when is_binary(map_id),
do:
map_id
|> map_pid!
|> GenServer.cast({&Impl.update_connection_custom_info/2, [connection_info]})
@impl true
def handle_continue(:load_state, state),
do: {:noreply, state |> Impl.load_state(), {:continue, :start_map}}

File diff suppressed because it is too large Load Diff

View File

@@ -87,4 +87,113 @@ defmodule WandererApp.Permissions do
delete_map: check_permission(user_permissions, @delete_map)
}
end
def check_characters_access(characters, acls) do
character_ids = characters |> Enum.map(& &1.id)
character_eve_ids = characters |> Enum.map(& &1.eve_id)
character_corporation_ids =
characters |> Enum.map(& &1.corporation_id) |> Enum.map(&to_string/1)
character_alliance_ids = characters |> Enum.map(& &1.alliance_id) |> Enum.map(&to_string/1)
result =
acls
|> Enum.reduce([0, 0], fn acl, acc ->
is_owner? = acl.owner_id in character_ids
is_character_member? =
acl.members |> Enum.any?(fn member -> member.eve_character_id in character_eve_ids end)
is_corporation_member? =
acl.members
|> Enum.any?(fn member -> member.eve_corporation_id in character_corporation_ids end)
is_alliance_member? =
acl.members
|> Enum.any?(fn member -> member.eve_alliance_id in character_alliance_ids end)
if is_owner? || is_character_member? || is_corporation_member? || is_alliance_member? do
case acc do
[_, -1] ->
[-1, -1]
[-1, char_acc] ->
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> calc_role_mask(member.role, acc)
end
end)
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[-1, char_acc]
[any_acc, char_acc] ->
any_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids ||
member.eve_corporation_id in character_corporation_ids ||
member.eve_alliance_id in character_alliance_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> calc_role_mask(member.role, acc)
end
end)
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> calc_role_mask(member.role, acc)
end
end)
any_acc =
case any_acl_mask do
-1 -> -1
_ -> any_acc ||| any_acl_mask
end
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[any_acc, char_acc]
end
else
acc
end
end)
case result do
[_, -1] ->
[-1]
[-1, char_acc] ->
[char_acc]
[any_acc, _char_acc] ->
[any_acc]
end
end
end

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