mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-11-03 07:57:05 +00:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1ecd3690e | ||
|
|
3250fe1ec6 | ||
|
|
48e8cd93b9 | ||
|
|
afacbb16b6 | ||
|
|
dfad127f32 | ||
|
|
300c1b5a18 | ||
|
|
bb38e1710b | ||
|
|
0857a82de5 | ||
|
|
da5afcc91c | ||
|
|
0002979fda | ||
|
|
080af16d41 | ||
|
|
d03a0b7083 | ||
|
|
5ba21f5386 | ||
|
|
10eeae5295 | ||
|
|
a5bead15d0 | ||
|
|
0de674adde | ||
|
|
1db65965d0 | ||
|
|
bbed17f631 | ||
|
|
0af4a3a731 | ||
|
|
49d503705a | ||
|
|
c55dd7b8d9 | ||
|
|
7ddcab3537 | ||
|
|
040b46c345 | ||
|
|
cd11ab6775 | ||
|
|
a5d776f3b1 | ||
|
|
e02caf341d | ||
|
|
3c04caa67c | ||
|
|
e76b564cbf | ||
|
|
5b2de88c3d | ||
|
|
82080b232f | ||
|
|
666bc7af43 | ||
|
|
dc077d5a5b | ||
|
|
29c840c64a | ||
|
|
65e0f89f33 | ||
|
|
d3b9b36332 | ||
|
|
90bbf29ea1 | ||
|
|
57f73684e8 | ||
|
|
7833cdebb2 | ||
|
|
67a5ae2985 | ||
|
|
f58c52d26b | ||
|
|
41e7739461 | ||
|
|
332152b677 | ||
|
|
85b49fe1f0 | ||
|
|
e7924532be | ||
|
|
475d950ad6 | ||
|
|
e6cfb29c6f | ||
|
|
dee6e86db1 | ||
|
|
72f088331f | ||
|
|
bbf536d10e | ||
|
|
149ac98297 | ||
|
|
b90a2910c9 | ||
|
|
c4da8a3a8d | ||
|
|
3ca75583d2 | ||
|
|
5f4607ae6f | ||
|
|
d880c6873f | ||
|
|
937649b2ed | ||
|
|
78e912c886 | ||
|
|
696c7d2cd1 |
@@ -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
|
||||
|
||||
|
||||
@@ -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: {}
|
||||
|
||||
2
.github/workflows/release_actions.yml
vendored
2
.github/workflows/release_actions.yml
vendored
@@ -18,4 +18,4 @@ jobs:
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
port: ${{ secrets.PORT }}
|
||||
script: |
|
||||
/app/release/linux/deploy.sh ${{ github.event.release.tag_name }}
|
||||
/home/wanderer/app/deploy.sh ${{ github.event.release.tag_name }}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
erlang 25.3
|
||||
elixir 1.16-otp-25
|
||||
erlang 26.2.5.5
|
||||
elixir 1.17.3-otp-26
|
||||
nodejs 18.0.0
|
||||
|
||||
186
CHANGELOG.md
186
CHANGELOG.md
@@ -2,6 +2,192 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v1.21.0](https://github.com/wanderer-industries/wanderer/compare/v1.20.1...v1.21.0) (2024-11-24)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: add new gate design, change EOL placement
|
||||
|
||||
## [v1.20.1](https://github.com/wanderer-industries/wanderer/compare/v1.20.0...v1.20.1) (2024-11-22)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.20.0](https://github.com/wanderer-industries/wanderer/compare/v1.19.3...v1.20.0) (2024-11-22)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: Add connection type for Gates, add new Update logic
|
||||
|
||||
## [v1.19.3](https://github.com/wanderer-industries/wanderer/compare/v1.19.2...v1.19.3) (2024-11-20)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fix adding systems on splash (#71)
|
||||
|
||||
* Core: Fix adding systems on splash
|
||||
|
||||
## [v1.19.2](https://github.com/wanderer-industries/wanderer/compare/v1.19.1...v1.19.2) (2024-11-19)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.19.1](https://github.com/wanderer-industries/wanderer/compare/v1.19.0...v1.19.1) (2024-11-19)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.19.0](https://github.com/wanderer-industries/wanderer/compare/v1.18.1...v1.19.0) (2024-11-19)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Signatures: Add user setting to show Inserted time in a separate column
|
||||
|
||||
## [v1.18.1](https://github.com/wanderer-industries/wanderer/compare/v1.18.0...v1.18.1) (2024-11-17)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.18.0](https://github.com/wanderer-industries/wanderer/compare/v1.17.0...v1.18.0) (2024-11-16)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: a lot of design issues
|
||||
|
||||
## [v1.17.0](https://github.com/wanderer-industries/wanderer/compare/v1.16.1...v1.17.0) (2024-11-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Signatures: Add user setting to show Description in a separate column
|
||||
|
||||
## [v1.16.1](https://github.com/wanderer-industries/wanderer/compare/v1.16.0...v1.16.1) (2024-11-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signatures: Fix signature stored filters
|
||||
|
||||
## [v1.16.0](https://github.com/wanderer-industries/wanderer/compare/v1.15.5...v1.16.0) (2024-11-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Signatures: Add additional filters support to signature list, show description icon
|
||||
|
||||
## [v1.15.5](https://github.com/wanderer-industries/wanderer/compare/v1.15.4...v1.15.5) (2024-11-14)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.15.4](https://github.com/wanderer-industries/wanderer/compare/v1.15.3...v1.15.4) (2024-11-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Untracked characters still tracked on map (#63)
|
||||
|
||||
## [v1.15.3](https://github.com/wanderer-industries/wanderer/compare/v1.15.2...v1.15.3) (2024-11-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:
|
||||
|
||||
* Connections: Add connection mark EOL time (#56)
|
||||
|
||||
## [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:
|
||||
|
||||
* ACL: Add an ability to assign member role without DnD
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
18
README.md
18
README.md
@@ -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 don’t 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 don’t 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
|
||||
|
||||
|
||||
@@ -466,3 +466,407 @@ body {
|
||||
transform: rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Map refresh */
|
||||
.socket {
|
||||
scale: 0.5;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
left: 50%;
|
||||
/* margin-left: -75px; */
|
||||
top: 50%;
|
||||
/* margin-top: -50px; */
|
||||
}
|
||||
|
||||
.hex-brick {
|
||||
background: #000;
|
||||
width: 30px;
|
||||
height: 17px;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
animation-name: fade;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
-webkit-animation-name: fade;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.hex-brick--active {
|
||||
animation-name: fade-active;
|
||||
-webkit-animation-name: fade-active;
|
||||
}
|
||||
|
||||
.h2 {
|
||||
transform: rotate(60deg);
|
||||
-webkit-transform: rotate(60deg);
|
||||
}
|
||||
|
||||
.h3 {
|
||||
transform: rotate(-60deg);
|
||||
-webkit-transform: rotate(-60deg);
|
||||
}
|
||||
|
||||
.gel {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
transition: all 0.3s;
|
||||
-webkit-transition: all 0.3s;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.center-gel {
|
||||
margin-left: -15px;
|
||||
margin-top: -15px;
|
||||
|
||||
animation-name: pulse-version;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
-webkit-animation-name: pulse-version;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
margin-left: -47px;
|
||||
margin-top: -15px;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
margin-left: -31px;
|
||||
margin-top: -43px;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
margin-left: 1px;
|
||||
margin-top: -43px;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
margin-left: 17px;
|
||||
margin-top: -15px;
|
||||
}
|
||||
.c5 {
|
||||
margin-left: -31px;
|
||||
margin-top: 13px;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
margin-left: 1px;
|
||||
margin-top: 13px;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
margin-left: -63px;
|
||||
margin-top: -43px;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
margin-left: 33px;
|
||||
margin-top: -43px;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
margin-left: -15px;
|
||||
margin-top: 41px;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
margin-left: -63px;
|
||||
margin-top: 13px;
|
||||
}
|
||||
|
||||
.c11 {
|
||||
margin-left: 33px;
|
||||
margin-top: 13px;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
margin-left: -15px;
|
||||
margin-top: -71px;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
margin-left: -47px;
|
||||
margin-top: -71px;
|
||||
}
|
||||
|
||||
.c14 {
|
||||
margin-left: 17px;
|
||||
margin-top: -71px;
|
||||
}
|
||||
|
||||
.c15 {
|
||||
margin-left: -47px;
|
||||
margin-top: 41px;
|
||||
}
|
||||
|
||||
.c16 {
|
||||
margin-left: 17px;
|
||||
margin-top: 41px;
|
||||
}
|
||||
|
||||
.c17 {
|
||||
margin-left: -79px;
|
||||
margin-top: -15px;
|
||||
}
|
||||
|
||||
.c18 {
|
||||
margin-left: 49px;
|
||||
margin-top: -15px;
|
||||
}
|
||||
|
||||
.c19 {
|
||||
margin-left: -63px;
|
||||
margin-top: -99px;
|
||||
}
|
||||
|
||||
.c20 {
|
||||
margin-left: 33px;
|
||||
margin-top: -99px;
|
||||
}
|
||||
|
||||
.c21 {
|
||||
margin-left: 1px;
|
||||
margin-top: -99px;
|
||||
}
|
||||
|
||||
.c22 {
|
||||
margin-left: -31px;
|
||||
margin-top: -99px;
|
||||
}
|
||||
|
||||
.c23 {
|
||||
margin-left: -63px;
|
||||
margin-top: 69px;
|
||||
}
|
||||
|
||||
.c24 {
|
||||
margin-left: 33px;
|
||||
margin-top: 69px;
|
||||
}
|
||||
|
||||
.c25 {
|
||||
margin-left: 1px;
|
||||
margin-top: 69px;
|
||||
}
|
||||
|
||||
.c26 {
|
||||
margin-left: -31px;
|
||||
margin-top: 69px;
|
||||
}
|
||||
|
||||
.c27 {
|
||||
margin-left: -79px;
|
||||
margin-top: -15px;
|
||||
}
|
||||
|
||||
.c28 {
|
||||
margin-left: -95px;
|
||||
margin-top: -43px;
|
||||
}
|
||||
|
||||
.c29 {
|
||||
margin-left: -95px;
|
||||
margin-top: 13px;
|
||||
}
|
||||
|
||||
.c30 {
|
||||
margin-left: 49px;
|
||||
margin-top: 41px;
|
||||
}
|
||||
|
||||
.c31 {
|
||||
margin-left: -79px;
|
||||
margin-top: -71px;
|
||||
}
|
||||
|
||||
.c32 {
|
||||
margin-left: -111px;
|
||||
margin-top: -15px;
|
||||
}
|
||||
|
||||
.c33 {
|
||||
margin-left: 65px;
|
||||
margin-top: -43px;
|
||||
}
|
||||
|
||||
.c34 {
|
||||
margin-left: 65px;
|
||||
margin-top: 13px;
|
||||
}
|
||||
|
||||
.c35 {
|
||||
margin-left: -79px;
|
||||
margin-top: 41px;
|
||||
}
|
||||
|
||||
.c36 {
|
||||
margin-left: 49px;
|
||||
margin-top: -71px;
|
||||
}
|
||||
|
||||
.c37 {
|
||||
margin-left: 81px;
|
||||
margin-top: -15px;
|
||||
}
|
||||
|
||||
.r1 {
|
||||
animation-name: pulse-version;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-delay: 0.2s;
|
||||
-webkit-animation-name: pulse-version;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
-webkit-animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.r2 {
|
||||
animation-name: pulse-version;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-delay: 0.4s;
|
||||
-webkit-animation-name: pulse-version;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
-webkit-animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.r3 {
|
||||
animation-name: pulse-version;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-delay: 0.6s;
|
||||
-webkit-animation-name: pulse-version;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
-webkit-animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
.r1 > .hex-brick {
|
||||
animation-name: fade;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-delay: 0.2s;
|
||||
-webkit-animation-name: fade;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
-webkit-animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.r1 > .hex-brick--active {
|
||||
animation-name: fade-active;
|
||||
-webkit-animation-name: fade-active;
|
||||
}
|
||||
|
||||
.r2 > .hex-brick {
|
||||
animation-name: fade;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-delay: 0.4s;
|
||||
-webkit-animation-name: fade;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
-webkit-animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.r2 > .hex-brick--active {
|
||||
animation-name: fade-active;
|
||||
-webkit-animation-name: fade-active;
|
||||
}
|
||||
|
||||
.r3 > .hex-brick {
|
||||
animation-name: fade;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-delay: 0.6s;
|
||||
-webkit-animation-name: fade;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
-webkit-animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
.r3 > .hex-brick--active {
|
||||
animation-name: fade-active;
|
||||
-webkit-animation-name: fade-active;
|
||||
}
|
||||
|
||||
@keyframes pulse-version {
|
||||
0% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
-webkit-transform: scale(0.01);
|
||||
transform: scale(0.01);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade {
|
||||
0% {
|
||||
background: #09d0e2;
|
||||
}
|
||||
|
||||
50% {
|
||||
background: #8ae6ee;
|
||||
}
|
||||
|
||||
100% {
|
||||
background: #09d0e2;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-active {
|
||||
0% {
|
||||
background: #ff52d9;
|
||||
}
|
||||
|
||||
50% {
|
||||
background: #ff52d9;
|
||||
}
|
||||
|
||||
100% {
|
||||
background: #ff52d9;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes pulse {
|
||||
0% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
-webkit-transform: scale(0.01);
|
||||
transform: scale(0.01);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes fade {
|
||||
0% {
|
||||
background: #abf8ff;
|
||||
}
|
||||
|
||||
50% {
|
||||
background: #389ca6;
|
||||
}
|
||||
|
||||
100% {
|
||||
background: #abf8ff;
|
||||
}
|
||||
}
|
||||
/* Map refresh END */
|
||||
|
||||
@@ -15,11 +15,10 @@ const ErrorFallback = () => {
|
||||
};
|
||||
|
||||
export default function MapRoot({ hooks }) {
|
||||
const mapRef = useRef<MapHandlers>(null);
|
||||
const providerRef = useRef<MapHandlers>(null);
|
||||
const hooksRef = useRef<any>(hooks);
|
||||
|
||||
const mapperHandlerRefs = useRef([mapRef, providerRef]);
|
||||
const mapperHandlerRefs = useRef([providerRef]);
|
||||
|
||||
const { handleCommand, handleMapEvent, handleMapEvents } = useMapperHandlers(mapperHandlerRefs.current, hooksRef);
|
||||
|
||||
@@ -41,7 +40,7 @@ export default function MapRoot({ hooks }) {
|
||||
|
||||
return (
|
||||
<PrimeReactProvider>
|
||||
<MapRootProvider fwdRef={providerRef} outCommand={handleCommand} mapRef={mapRef}>
|
||||
<MapRootProvider fwdRef={providerRef} outCommand={handleCommand}>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}>
|
||||
<ReactFlowProvider>
|
||||
<MapRootContent />
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import { useCallback } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { useAutoAnimate } from '@formkit/auto-animate/react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { CharacterTypeRaw } from '@/hooks/Mapper/types';
|
||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
|
||||
const Characters = ({ data }: { data: CharacterTypeRaw[] }) => {
|
||||
const [parent] = useAutoAnimate();
|
||||
const { mapRef } = useMapRootState();
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(character: CharacterTypeRaw) => {
|
||||
mapRef.current?.command(Commands.centerSystem, character?.location?.solar_system_id?.toString());
|
||||
},
|
||||
[mapRef],
|
||||
);
|
||||
const handleSelect = useCallback((character: CharacterTypeRaw) => {
|
||||
emitMapEvent({
|
||||
name: Commands.centerSystem,
|
||||
data: character?.location?.solar_system_id?.toString(),
|
||||
});
|
||||
}, []);
|
||||
|
||||
const items = data.map(character => (
|
||||
<li
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { RefObject, useCallback, useRef, useState } from 'react';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { Commands, MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { Commands, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import * as React from 'react';
|
||||
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
|
||||
interface UseContextMenuSystemHandlersProps {
|
||||
hubs: string[];
|
||||
outCommand: OutCommandHandler;
|
||||
mapRef: RefObject<MapHandlers>;
|
||||
}
|
||||
|
||||
export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand, mapRef }: UseContextMenuSystemHandlersProps) => {
|
||||
export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContextMenuSystemHandlersProps) => {
|
||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||
|
||||
const [system, setSystem] = useState<string>();
|
||||
const routeRef = useRef<(SolarSystemStaticInfoRaw | undefined)[]>([]);
|
||||
|
||||
const ref = useRef({ hubs, system, outCommand, mapRef });
|
||||
ref.current = { hubs, system, outCommand, mapRef };
|
||||
const ref = useRef({ hubs, system, outCommand });
|
||||
ref.current = { hubs, system, outCommand };
|
||||
|
||||
const open = useCallback(
|
||||
(ev: React.SyntheticEvent, systemId: string, route: (SolarSystemStaticInfoRaw | undefined)[]) => {
|
||||
@@ -48,7 +48,7 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand, mapRef }: U
|
||||
}, []);
|
||||
|
||||
const onAddSystem = useCallback(() => {
|
||||
const { system: solarSystemId, outCommand, mapRef } = ref.current;
|
||||
const { system: solarSystemId, outCommand } = ref.current;
|
||||
if (!solarSystemId) {
|
||||
return;
|
||||
}
|
||||
@@ -60,7 +60,11 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand, mapRef }: U
|
||||
},
|
||||
});
|
||||
setTimeout(() => {
|
||||
mapRef.current?.command(Commands.centerSystem, solarSystemId);
|
||||
emitMapEvent({
|
||||
name: Commands.centerSystem,
|
||||
data: solarSystemId,
|
||||
});
|
||||
|
||||
setSystem(undefined);
|
||||
}, 200);
|
||||
}, []);
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useRef } from 'react';
|
||||
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect } from 'react';
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
ConnectionMode,
|
||||
Edge,
|
||||
MiniMap,
|
||||
Node,
|
||||
NodeChange,
|
||||
NodeDragHandler,
|
||||
OnConnect,
|
||||
OnMoveEnd,
|
||||
OnSelectionChangeFunc,
|
||||
SelectionDragHandler,
|
||||
SelectionMode,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
NodeChange,
|
||||
useReactFlow,
|
||||
} from 'reactflow';
|
||||
import 'reactflow/dist/style.css';
|
||||
@@ -21,8 +19,7 @@ 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 { useNodesState, useEdgesState, useMapHandlers, useUpdateNodes } from './hooks';
|
||||
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import {
|
||||
ContextMenuConnection,
|
||||
@@ -37,7 +34,6 @@ 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 };
|
||||
|
||||
@@ -93,12 +89,14 @@ interface MapCompProps {
|
||||
refn: ForwardedRef<MapHandlers>;
|
||||
onCommand: OutCommandHandler;
|
||||
onSelectionChange: OnMapSelectionChange;
|
||||
onManualDelete(systems: string[]): void;
|
||||
onConnectionInfoClick?(e: SolarSystemConnection): void;
|
||||
onSelectionContextMenu?: NodeSelectionMouseHandler;
|
||||
minimapClasses?: string;
|
||||
isShowMinimap?: boolean;
|
||||
onSystemContextMenu: (event: MouseEvent<Element>, systemId: string) => void;
|
||||
showKSpaceBG?: boolean;
|
||||
isThickConnections?: boolean;
|
||||
}
|
||||
|
||||
const MapComp = ({
|
||||
@@ -109,26 +107,20 @@ const MapComp = ({
|
||||
onSystemContextMenu,
|
||||
onConnectionInfoClick,
|
||||
onSelectionContextMenu,
|
||||
onManualDelete,
|
||||
isShowMinimap,
|
||||
showKSpaceBG,
|
||||
isThickConnections,
|
||||
}: MapCompProps) => {
|
||||
const { getNode } = useReactFlow();
|
||||
const [nodes, , onNodesChange] = useNodesState<SolarSystemRawType>(initialNodes);
|
||||
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>[]>(initialEdges);
|
||||
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
|
||||
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges);
|
||||
|
||||
useMapHandlers(refn, onSelectionChange);
|
||||
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 => {
|
||||
@@ -186,35 +178,41 @@ const MapComp = ({
|
||||
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);
|
||||
}
|
||||
if (change.type !== 'remove') {
|
||||
return [...acc, change];
|
||||
}
|
||||
|
||||
const node = getNode(change.id);
|
||||
if (!node) {
|
||||
return [...acc, change];
|
||||
}
|
||||
|
||||
if (node.data.locked) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
systemsIdsToRemove.push(node.data.id);
|
||||
return [...acc, change];
|
||||
}, [] as NodeChange[]);
|
||||
|
||||
if (systemsIdsToRemove.length) {
|
||||
deleteSystems(systemsIdsToRemove);
|
||||
if (systemsIdsToRemove.length > 0) {
|
||||
onManualDelete(systemsIdsToRemove);
|
||||
}
|
||||
|
||||
onNodesChange(nextChanges);
|
||||
},
|
||||
[deleteSystems, getNode, onNodesChange],
|
||||
[getNode, onManualDelete, onNodesChange],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
update(x => ({
|
||||
...x,
|
||||
showKSpaceBG: showKSpaceBG,
|
||||
isThickConnections: isThickConnections,
|
||||
}));
|
||||
}, [showKSpaceBG, update]);
|
||||
}, [showKSpaceBG, isThickConnections, update]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -238,6 +236,7 @@ const MapComp = ({
|
||||
onConnectStart={() => update({ isConnecting: true })}
|
||||
onConnectEnd={() => update({ isConnecting: false })}
|
||||
onNodeMouseEnter={(_, node) => update({ hoverNodeId: node.id })}
|
||||
// onKeyUp=
|
||||
onNodeMouseLeave={() => update({ hoverNodeId: null })}
|
||||
onEdgeClick={(_, t) => {
|
||||
onConnectionInfoClick?.(t.data);
|
||||
|
||||
@@ -8,6 +8,7 @@ export type MapData = MapUnionTypes & {
|
||||
hoverNodeId: string | null;
|
||||
visibleNodes: Set<string>;
|
||||
showKSpaceBG: boolean;
|
||||
isThickConnections: boolean;
|
||||
};
|
||||
|
||||
interface MapProviderProps {
|
||||
@@ -17,6 +18,7 @@ interface MapProviderProps {
|
||||
|
||||
const INITIAL_DATA: MapData = {
|
||||
wormholesData: {},
|
||||
wormholes: [],
|
||||
effects: {},
|
||||
characters: [],
|
||||
userCharacters: [],
|
||||
@@ -29,6 +31,7 @@ const INITIAL_DATA: MapData = {
|
||||
hoverNodeId: null,
|
||||
visibleNodes: new Set(),
|
||||
showKSpaceBG: false,
|
||||
isThickConnections: false,
|
||||
};
|
||||
|
||||
export interface MapContextProps {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { MenuItem } from 'primereact/menuitem';
|
||||
import { Edge } from '@reactflow/core/dist/esm/types/edges';
|
||||
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import clsx from 'clsx';
|
||||
import classes from './ContextMenuConnection.module.scss';
|
||||
import { MASS_STATE_NAMES, MASS_STATE_NAMES_ORDER } from '@/hooks/Mapper/components/map/constants.ts';
|
||||
@@ -35,36 +35,49 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
|
||||
}
|
||||
|
||||
const isFrigateSize = edge.data?.ship_size_type === ShipSizeStatus.small;
|
||||
const isWormhole = edge.data?.type !== ConnectionType.gate;
|
||||
|
||||
return [
|
||||
{
|
||||
label: `EOL`,
|
||||
className: clsx({
|
||||
[classes.ConnectionTimeEOL]: edge.data?.time_status === TimeStatus.eol,
|
||||
}),
|
||||
icon: PrimeIcons.CLOCK,
|
||||
command: onChangeTimeState,
|
||||
},
|
||||
{
|
||||
label: `Frigate`,
|
||||
className: clsx({
|
||||
[classes.ConnectionFrigate]: isFrigateSize,
|
||||
}),
|
||||
icon: PrimeIcons.CLOUD,
|
||||
command: () =>
|
||||
onChangeShipSizeStatus(
|
||||
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.normal : ShipSizeStatus.small,
|
||||
),
|
||||
},
|
||||
{
|
||||
label: `Save mass`,
|
||||
className: clsx({
|
||||
[classes.ConnectionSave]: edge.data?.locked,
|
||||
}),
|
||||
icon: PrimeIcons.LOCK,
|
||||
command: () => onToggleMassSave(!edge.data?.locked),
|
||||
},
|
||||
...(!isFrigateSize
|
||||
...(isWormhole
|
||||
? [
|
||||
{
|
||||
label: `EOL`,
|
||||
className: clsx({
|
||||
[classes.ConnectionTimeEOL]: edge.data?.time_status === TimeStatus.eol,
|
||||
}),
|
||||
icon: PrimeIcons.CLOCK,
|
||||
command: onChangeTimeState,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(isWormhole
|
||||
? [
|
||||
{
|
||||
label: `Frigate`,
|
||||
className: clsx({
|
||||
[classes.ConnectionFrigate]: isFrigateSize,
|
||||
}),
|
||||
icon: PrimeIcons.CLOUD,
|
||||
command: () =>
|
||||
onChangeShipSizeStatus(
|
||||
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.normal : ShipSizeStatus.small,
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(isWormhole
|
||||
? [
|
||||
{
|
||||
label: `Save mass`,
|
||||
className: clsx({
|
||||
[classes.ConnectionSave]: edge.data?.locked,
|
||||
}),
|
||||
icon: PrimeIcons.LOCK,
|
||||
command: () => onToggleMassSave(!edge.data?.locked),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(isWormhole && !isFrigateSize
|
||||
? [
|
||||
{
|
||||
label: `Mass status`,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { useMapState } from '../../MapProvider.tsx';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { Edge } from '@reactflow/core/dist/esm/types/edges';
|
||||
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
|
||||
export const useContextMenuConnectionHandlers = () => {
|
||||
@@ -47,6 +47,23 @@ export const useContextMenuConnectionHandlers = () => {
|
||||
setEdge(undefined);
|
||||
};
|
||||
|
||||
const onChangeType = useCallback((type: ConnectionType) => {
|
||||
const { edge, outCommand } = ref.current;
|
||||
|
||||
if (!edge) {
|
||||
return;
|
||||
}
|
||||
|
||||
outCommand({
|
||||
type: OutCommand.updateConnectionType,
|
||||
data: {
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
value: type,
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onChangeMassState = useCallback((status: MassState) => {
|
||||
const { edge, outCommand } = ref.current;
|
||||
|
||||
@@ -118,6 +135,7 @@ export const useContextMenuConnectionHandlers = () => {
|
||||
contextMenuRef,
|
||||
onDeleteConnection,
|
||||
onChangeTimeState,
|
||||
onChangeType,
|
||||
onChangeMassState,
|
||||
onChangeShipSizeStatus,
|
||||
onToggleMassSave,
|
||||
|
||||
@@ -21,7 +21,11 @@
|
||||
}
|
||||
|
||||
&.Frigate {
|
||||
stroke: #4e62c9;
|
||||
stroke: #d4f0ff;
|
||||
}
|
||||
|
||||
&.Gate {
|
||||
stroke: #1c1e15;
|
||||
}
|
||||
|
||||
&.Hovered {
|
||||
@@ -37,9 +41,16 @@
|
||||
}
|
||||
|
||||
&.Frigate {
|
||||
stroke: #41acd7;
|
||||
stroke: #d4f0ff;
|
||||
}
|
||||
}
|
||||
|
||||
&.Tick {
|
||||
stroke-width: 3px;
|
||||
|
||||
&.Hovered {
|
||||
stroke-width: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +72,19 @@
|
||||
stroke: #ef7dce;
|
||||
}
|
||||
}
|
||||
|
||||
&.Tick {
|
||||
stroke-width: 5px;
|
||||
|
||||
&.TimeCrit {
|
||||
stroke-width: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&.Gate {
|
||||
stroke: #9aff40;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ClickPath {
|
||||
@@ -93,5 +117,14 @@
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
z-index: 1001;
|
||||
|
||||
&.Tick {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
|
||||
&.Right {
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,39 +1,64 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import classes from './SolarSystemEdge.module.scss';
|
||||
import { EdgeLabelRenderer, EdgeProps, getBezierPath, Position, useStore } from 'reactflow';
|
||||
import { EdgeLabelRenderer, EdgeProps, getBezierPath, getSmoothStepPath, Position, useStore } from 'reactflow';
|
||||
import { getEdgeParams } from '@/hooks/Mapper/components/map/utils.ts';
|
||||
import clsx from 'clsx';
|
||||
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
|
||||
const MAP_TRANSLATES: Record<string, string> = {
|
||||
[Position.Top]: 'translate(-50%, 0%)',
|
||||
[Position.Top]: 'translate(-48%, 0%)',
|
||||
[Position.Bottom]: 'translate(-50%, -100%)',
|
||||
[Position.Left]: 'translate(0%, -50%)',
|
||||
[Position.Right]: 'translate(-100%, -50%)',
|
||||
};
|
||||
|
||||
const MAP_OFFSETS_TICK: Record<string, { x: number; y: number }> = {
|
||||
[Position.Top]: { x: 0, y: 3 },
|
||||
[Position.Bottom]: { x: 0, y: -3 },
|
||||
[Position.Left]: { x: 3, y: 0 },
|
||||
[Position.Right]: { x: -3, y: 0 },
|
||||
};
|
||||
|
||||
const MAP_OFFSETS: Record<string, { x: number; y: number }> = {
|
||||
[Position.Top]: { x: 0, y: 0 },
|
||||
[Position.Bottom]: { x: 0, y: 0 },
|
||||
[Position.Left]: { x: 0, y: 0 },
|
||||
[Position.Right]: { x: 0, y: 0 },
|
||||
};
|
||||
|
||||
export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }: EdgeProps<SolarSystemConnection>) => {
|
||||
const sourceNode = useStore(useCallback(store => store.nodeInternals.get(source), [source]));
|
||||
const targetNode = useStore(useCallback(store => store.nodeInternals.get(target), [target]));
|
||||
const isWormhole = data?.type !== ConnectionType.gate;
|
||||
|
||||
const {
|
||||
data: { isThickConnections },
|
||||
} = useMapState();
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
const [path, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos] = useMemo(() => {
|
||||
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);
|
||||
|
||||
const [edgePath, labelX, labelY] = getBezierPath({
|
||||
sourceX: sx,
|
||||
sourceY: sy,
|
||||
const offset = isThickConnections ? MAP_OFFSETS_TICK[targetPos] : MAP_OFFSETS[targetPos];
|
||||
|
||||
const method = isWormhole ? getBezierPath : getSmoothStepPath;
|
||||
|
||||
const [edgePath, labelX, labelY] = method({
|
||||
sourceX: sx - offset.x,
|
||||
sourceY: sy - offset.y,
|
||||
sourcePosition: sourcePos,
|
||||
targetPosition: targetPos,
|
||||
targetX: tx,
|
||||
targetY: ty,
|
||||
targetX: tx + offset.x,
|
||||
targetY: ty + offset.y,
|
||||
});
|
||||
|
||||
return [edgePath, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos];
|
||||
}, [sourceNode, targetNode]);
|
||||
}, [isThickConnections, sourceNode, targetNode, isWormhole]);
|
||||
|
||||
if (!sourceNode || !targetNode || !data) {
|
||||
return null;
|
||||
@@ -44,8 +69,10 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
|
||||
<path
|
||||
id={`back_${id}`}
|
||||
className={clsx(classes.EdgePathBack, {
|
||||
[classes.TimeCrit]: data.time_status === TimeStatus.eol,
|
||||
[classes.Tick]: isThickConnections,
|
||||
[classes.TimeCrit]: isWormhole && data.time_status === TimeStatus.eol,
|
||||
[classes.Hovered]: hovered,
|
||||
[classes.Gate]: !isWormhole,
|
||||
})}
|
||||
d={path}
|
||||
markerEnd={markerEnd}
|
||||
@@ -54,10 +81,12 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
|
||||
<path
|
||||
id={`front_${id}`}
|
||||
className={clsx(classes.EdgePathFront, {
|
||||
[classes.Tick]: isThickConnections,
|
||||
[classes.Hovered]: hovered,
|
||||
[classes.MassVerge]: data.mass_status === MassState.verge,
|
||||
[classes.MassHalf]: data.mass_status === MassState.half,
|
||||
[classes.Frigate]: data.ship_size_type === ShipSizeStatus.small,
|
||||
[classes.MassVerge]: isWormhole && data.mass_status === MassState.verge,
|
||||
[classes.MassHalf]: isWormhole && data.mass_status === MassState.half,
|
||||
[classes.Frigate]: isWormhole && data.ship_size_type === ShipSizeStatus.small,
|
||||
[classes.Gate]: !isWormhole,
|
||||
})}
|
||||
d={path}
|
||||
markerEnd={markerEnd}
|
||||
@@ -75,11 +104,19 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
|
||||
|
||||
<EdgeLabelRenderer>
|
||||
<div
|
||||
className={clsx(classes.Handle, 'react-flow__handle absolute nodrag pointer-events-none')}
|
||||
className={clsx(
|
||||
classes.Handle,
|
||||
{ [classes.Tick]: isThickConnections, [classes.Right]: Position.Right === sourcePos },
|
||||
'react-flow__handle absolute nodrag pointer-events-none',
|
||||
)}
|
||||
style={{ transform: `${MAP_TRANSLATES[sourcePos]} translate(${sx}px,${sy}px)` }}
|
||||
/>
|
||||
<div
|
||||
className={clsx(classes.Handle, 'react-flow__handle absolute nodrag pointer-events-none')}
|
||||
className={clsx(
|
||||
classes.Handle,
|
||||
{ [classes.Tick]: isThickConnections },
|
||||
'react-flow__handle absolute nodrag pointer-events-none',
|
||||
)}
|
||||
style={{ transform: `${MAP_TRANSLATES[targetPos]} translate(${tx}px,${ty}px)` }}
|
||||
/>
|
||||
|
||||
@@ -89,7 +126,7 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
|
||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||
}}
|
||||
>
|
||||
{data.locked && (
|
||||
{isWormhole && data.locked && (
|
||||
<WdTooltipWrapper
|
||||
content="Save mass"
|
||||
className={clsx(
|
||||
|
||||
@@ -212,6 +212,14 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
color: #ffb01d;
|
||||
}
|
||||
|
||||
/* Firefox kostyl */
|
||||
@-moz-document url-prefix() {
|
||||
.classSystemName {
|
||||
font-family: inherit !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.classSystemName {
|
||||
//font-weight: bold;
|
||||
}
|
||||
@@ -262,6 +270,13 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
& > * {
|
||||
line-height: 10px;
|
||||
}
|
||||
|
||||
/* Firefox kostyl */
|
||||
@-moz-document url-prefix() {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.Handlers {
|
||||
@@ -299,4 +314,25 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
&.HandleLeft {
|
||||
left: -2px;
|
||||
}
|
||||
|
||||
&.Tick {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
|
||||
&.HandleTop {
|
||||
top: -3px;
|
||||
}
|
||||
|
||||
&.HandleRight {
|
||||
right: -3px;
|
||||
}
|
||||
|
||||
&.HandleBottom {
|
||||
bottom: -3px;
|
||||
}
|
||||
|
||||
&.HandleLeft {
|
||||
left: -3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
|
||||
hoverNodeId,
|
||||
visibleNodes,
|
||||
showKSpaceBG,
|
||||
isThickConnections,
|
||||
},
|
||||
outCommand,
|
||||
} = useMapState();
|
||||
@@ -239,28 +240,40 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
|
||||
<div onMouseDownCapture={dbClick} className={classes.Handlers}>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleTop, { [classes.selected]: selected })}
|
||||
className={clsx(classes.Handle, classes.HandleTop, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Top}
|
||||
id="a"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleRight, { [classes.selected]: selected })}
|
||||
className={clsx(classes.Handle, classes.HandleRight, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Right}
|
||||
id="b"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleBottom, { [classes.selected]: selected })}
|
||||
className={clsx(classes.Handle, classes.HandleBottom, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Bottom}
|
||||
id="c"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleLeft, { [classes.selected]: selected })}
|
||||
className={clsx(classes.Handle, classes.HandleLeft, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Left}
|
||||
id="d"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MassState } from '@/hooks/Mapper/types';
|
||||
import { ConnectionType, MassState } from '@/hooks/Mapper/types';
|
||||
|
||||
export enum SOLAR_SYSTEM_CLASS_IDS {
|
||||
ccp1 = -1,
|
||||
@@ -712,6 +712,13 @@ export const STATUS_CLASSES: Record<number, string> = {
|
||||
[STATUSES.dangerous]: 'eve-system-status-dangerous',
|
||||
};
|
||||
|
||||
export const TYPE_NAMES_ORDER = [ConnectionType.wormhole, ConnectionType.gate];
|
||||
|
||||
export const TYPE_NAMES = {
|
||||
[ConnectionType.wormhole]: 'Wormhole',
|
||||
[ConnectionType.gate]: 'Gate',
|
||||
};
|
||||
|
||||
export const MASS_STATE_NAMES_ORDER = [MassState.verge, MassState.half, MassState.normal];
|
||||
|
||||
export const MASS_STATE_NAMES = {
|
||||
|
||||
@@ -12,6 +12,7 @@ export const useMapAddSystems = () => {
|
||||
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);
|
||||
}, []);
|
||||
|
||||
@@ -5,24 +5,21 @@ import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts
|
||||
|
||||
export const useMapRemoveSystems = (onSelectionChange: OnMapSelectionChange) => {
|
||||
const rf = useReactFlow();
|
||||
const ref = useRef({ onSelectionChange });
|
||||
ref.current = { onSelectionChange };
|
||||
const ref = useRef({ onSelectionChange, rf });
|
||||
ref.current = { onSelectionChange, rf };
|
||||
|
||||
return useCallback(
|
||||
(systems: CommandRemoveSystems) => {
|
||||
rf.deleteElements({ nodes: systems.map(x => ({ id: `${x}` })) });
|
||||
return useCallback((systems: CommandRemoveSystems) => {
|
||||
ref.current.rf.deleteElements({ nodes: systems.map(x => ({ id: `${x}` })) });
|
||||
|
||||
const newSelection = rf
|
||||
.getNodes()
|
||||
.filter(x => !systems.includes(parseInt(x.id)))
|
||||
.filter(x => x.selected)
|
||||
.map(x => x.id);
|
||||
const newSelection = ref.current.rf
|
||||
.getNodes()
|
||||
.filter(x => !systems.includes(parseInt(x.id)))
|
||||
.filter(x => x.selected)
|
||||
.map(x => x.id);
|
||||
|
||||
ref.current.onSelectionChange({
|
||||
systems: newSelection,
|
||||
connections: [],
|
||||
});
|
||||
},
|
||||
[rf],
|
||||
);
|
||||
ref.current.onSelectionChange({
|
||||
systems: newSelection,
|
||||
connections: [],
|
||||
});
|
||||
}, []);
|
||||
};
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './useMapHandlers';
|
||||
export * from './useUpdateNodes';
|
||||
export * from './useNodesEdgesState';
|
||||
|
||||
@@ -19,8 +19,6 @@ import {
|
||||
MapHandlers,
|
||||
} from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
|
||||
import {
|
||||
useCommandsCharacters,
|
||||
useCommandsConnections,
|
||||
@@ -60,13 +58,16 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
||||
mapInit(data as CommandInit);
|
||||
break;
|
||||
case Commands.addSystems:
|
||||
setTimeout(() => mapAddSystems(data as CommandAddSystems), 100);
|
||||
break;
|
||||
case Commands.updateSystems:
|
||||
mapUpdateSystems(data as CommandUpdateSystems);
|
||||
break;
|
||||
case Commands.removeSystems:
|
||||
setTimeout(() => removeSystems(data as CommandRemoveSystems), 100);
|
||||
break;
|
||||
case Commands.addConnections:
|
||||
setTimeout(() => addConnections(data as CommandAddConnections), 100);
|
||||
break;
|
||||
case Commands.removeConnections:
|
||||
removeConnections(data as CommandRemoveConnections);
|
||||
@@ -111,13 +112,17 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
||||
connections: [],
|
||||
});
|
||||
selectSystem(systemId as CommandSelectSystem);
|
||||
}, 100);
|
||||
}, 500);
|
||||
break;
|
||||
|
||||
case Commands.routes:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.signaturesUpdated:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.linkSignatureToSystem:
|
||||
// do nothing here
|
||||
break;
|
||||
@@ -131,20 +136,4 @@ 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;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { useState, useCallback, type Dispatch, type SetStateAction } from 'react';
|
||||
|
||||
import { applyNodeChanges, applyEdgeChanges } from '../utils/changes';
|
||||
import { OnNodesChange, Edge, OnEdgesChange, Node } from 'reactflow';
|
||||
|
||||
/**
|
||||
* Hook for managing the state of nodes - should only be used for prototyping / simple use cases.
|
||||
*
|
||||
* @public
|
||||
* @param initialNodes
|
||||
* @returns an array [nodes, setNodes, onNodesChange]
|
||||
*/
|
||||
export function useNodesState<NodeType extends Node>(
|
||||
initialNodes: NodeType[],
|
||||
): [NodeType[], Dispatch<SetStateAction<NodeType[]>>, OnNodesChange] {
|
||||
const [nodes, setNodes] = useState(initialNodes);
|
||||
const onNodesChange: OnNodesChange = useCallback(changes => setNodes(nds => applyNodeChanges(changes, nds)), []);
|
||||
|
||||
return [nodes, setNodes, onNodesChange];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for managing the state of edges - should only be used for prototyping / simple use cases.
|
||||
*
|
||||
* @public
|
||||
* @param initialEdges
|
||||
* @returns an array [edges, setEdges, onEdgesChange]
|
||||
*/
|
||||
export function useEdgesState<EdgeType extends Edge = Edge>(
|
||||
initialEdges: EdgeType[],
|
||||
): [EdgeType[], Dispatch<SetStateAction<EdgeType[]>>, OnEdgesChange] {
|
||||
const [edges, setEdges] = useState(initialEdges);
|
||||
const onEdgesChange: OnEdgesChange = useCallback(changes => setEdges(eds => applyEdgeChanges(changes, eds)), []);
|
||||
|
||||
return [edges, setEdges, onEdgesChange];
|
||||
}
|
||||
174
assets/js/hooks/Mapper/components/map/utils/changes.ts
Normal file
174
assets/js/hooks/Mapper/components/map/utils/changes.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { EdgeChange, NodeChange, Node, Edge } from 'reactflow';
|
||||
|
||||
// This function applies changes to nodes or edges that are triggered by React Flow internally.
|
||||
// When you drag a node for example, React Flow will send a position change update.
|
||||
// This function then applies the changes and returns the updated elements.
|
||||
function applyChanges(changes: any[], elements: any[]): any[] {
|
||||
// we need this hack to handle the setNodes and setEdges function of the useReactFlow hook for controlled flows
|
||||
if (changes.some(c => c.type === 'reset')) {
|
||||
return changes.filter(c => c.type === 'reset').map(c => c.item);
|
||||
}
|
||||
|
||||
const updatedElements: any[] = [];
|
||||
// By storing a map of changes for each element, we can a quick lookup as we
|
||||
// iterate over the elements array!
|
||||
const changesMap = new Map<any, any[]>();
|
||||
const addItemChanges: any[] = [];
|
||||
|
||||
for (const change of changes) {
|
||||
if (change.type === 'add') {
|
||||
addItemChanges.push(change);
|
||||
continue;
|
||||
} else if (change.type === 'remove' || change.type === 'replace') {
|
||||
// For a 'remove' change we can safely ignore any other changes queued for
|
||||
// the same element, it's going to be removed anyway!
|
||||
changesMap.set(change.id, [change]);
|
||||
} else {
|
||||
const elementChanges = changesMap.get(change.id);
|
||||
|
||||
if (elementChanges) {
|
||||
// If we have some changes queued already, we can do a mutable update of
|
||||
// that array and save ourselves some copying.
|
||||
elementChanges.push(change);
|
||||
} else {
|
||||
changesMap.set(change.id, [change]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const element of elements) {
|
||||
const changes = changesMap.get(element.id);
|
||||
|
||||
// When there are no changes for an element we can just push it unmodified,
|
||||
// no need to copy it.
|
||||
if (!changes) {
|
||||
updatedElements.push(element);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we have a 'remove' change queued, it'll be the only change in the array
|
||||
if (changes[0].type === 'remove') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (changes[0].type === 'replace') {
|
||||
updatedElements.push({ ...changes[0].item });
|
||||
continue;
|
||||
}
|
||||
|
||||
// For other types of changes, we want to start with a shallow copy of the
|
||||
// object so React knows this element has changed. Sequential changes will
|
||||
/// each _mutate_ this object, so there's only ever one copy.
|
||||
const updatedElement = { ...element };
|
||||
|
||||
for (const change of changes) {
|
||||
applyChange(change, updatedElement);
|
||||
}
|
||||
|
||||
updatedElements.push(updatedElement);
|
||||
}
|
||||
|
||||
// we need to wait for all changes to be applied before adding new items
|
||||
// to be able to add them at the correct index
|
||||
if (addItemChanges.length) {
|
||||
addItemChanges.forEach(change => {
|
||||
if (change.index !== undefined) {
|
||||
updatedElements.splice(change.index, 0, { ...change.item });
|
||||
} else {
|
||||
updatedElements.push({ ...change.item });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return updatedElements;
|
||||
}
|
||||
|
||||
// Applies a single change to an element. This is a *mutable* update.
|
||||
function applyChange(change: any, element: any): any {
|
||||
switch (change.type) {
|
||||
case 'select': {
|
||||
element.selected = change.selected;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'position': {
|
||||
if (typeof change.position !== 'undefined') {
|
||||
element.position = change.position;
|
||||
}
|
||||
|
||||
if (typeof change.dragging !== 'undefined') {
|
||||
element.dragging = change.dragging;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'dimensions': {
|
||||
if (typeof change.dimensions !== 'undefined') {
|
||||
element.measured ??= {};
|
||||
element.measured.width = change.dimensions.width;
|
||||
element.measured.height = change.dimensions.height;
|
||||
|
||||
if (change.setAttributes) {
|
||||
element.width = change.dimensions.width;
|
||||
element.height = change.dimensions.height;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof change.resizing === 'boolean') {
|
||||
element.resizing = change.resizing;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop in function that applies node changes to an array of nodes.
|
||||
* @public
|
||||
* @remarks Various events on the <ReactFlow /> component can produce an {@link NodeChange} that describes how to update the edges of your flow in some way.
|
||||
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
|
||||
* @param changes - Array of changes to apply
|
||||
* @param nodes - Array of nodes to apply the changes to
|
||||
* @returns Array of updated nodes
|
||||
* @example
|
||||
* const onNodesChange = useCallback(
|
||||
(changes) => {
|
||||
setNodes((oldNodes) => applyNodeChanges(changes, oldNodes));
|
||||
},
|
||||
[setNodes],
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactFLow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
|
||||
);
|
||||
*/
|
||||
export function applyNodeChanges<NodeType extends Node = Node>(changes: NodeChange[], nodes: NodeType[]): NodeType[] {
|
||||
return applyChanges(changes, nodes) as NodeType[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop in function that applies edge changes to an array of edges.
|
||||
* @public
|
||||
* @remarks Various events on the <ReactFlow /> component can produce an {@link EdgeChange} that describes how to update the edges of your flow in some way.
|
||||
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
|
||||
* @param changes - Array of changes to apply
|
||||
* @param edges - Array of edge to apply the changes to
|
||||
* @returns Array of updated edges
|
||||
* @example
|
||||
* const onEdgesChange = useCallback(
|
||||
(changes) => {
|
||||
setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges));
|
||||
},
|
||||
[setEdges],
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactFlow nodes={nodes} edges={edges} onEdgesChange={onEdgesChange} />
|
||||
);
|
||||
*/
|
||||
export function applyEdgeChanges<EdgeType extends Edge = Edge>(changes: EdgeChange[], edges: EdgeType[]): EdgeType[] {
|
||||
return applyChanges(changes, edges) as EdgeType[];
|
||||
}
|
||||
@@ -48,6 +48,7 @@ const restoreWindowsFromLS = (): WidgetGridItem[] => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const raw = localStorage.getItem(SESSION_KEY.windows);
|
||||
if (!raw) {
|
||||
console.warn('No windows found in local storage!!');
|
||||
return DEFAULT_WINDOWS;
|
||||
}
|
||||
|
||||
@@ -63,7 +64,7 @@ const restoreWindowsFromLS = (): WidgetGridItem[] => {
|
||||
};
|
||||
|
||||
export const MapInterface = () => {
|
||||
const [items, setItems] = useState<WidgetGridItem[]>(restoreWindowsFromLS());
|
||||
const [items, setItems] = useState<WidgetGridItem[]>(restoreWindowsFromLS);
|
||||
|
||||
return (
|
||||
<WidgetsGrid
|
||||
|
||||
@@ -79,7 +79,7 @@ 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 (
|
||||
|
||||
@@ -10,13 +10,17 @@ import {
|
||||
Setting,
|
||||
COSMIC_SIGNATURE,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
|
||||
import { SignatureGroup } from '@/hooks/Mapper/types';
|
||||
|
||||
interface SystemLinkSignatureDialogProps {
|
||||
data: CommandLinkSignatureToSystem;
|
||||
setVisible: (visible: boolean) => void;
|
||||
}
|
||||
|
||||
const signatureSettings: Setting[] = [{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true }];
|
||||
const signatureSettings: Setting[] = [
|
||||
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true },
|
||||
{ key: SignatureGroup.Wormhole, name: 'Wormhole', value: true },
|
||||
];
|
||||
|
||||
export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
@@ -59,6 +63,7 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
||||
>
|
||||
<SystemSignaturesContent
|
||||
systemId={`${data.solar_system_source}`}
|
||||
hideLinkedSignatures
|
||||
settings={signatureSettings}
|
||||
onSelect={handleSelect}
|
||||
selectable={true}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -5,7 +5,7 @@ import { SystemViewStandalone, WdTooltip, WdTooltipHandlers } from '@/hooks/Mapp
|
||||
import { getBackgroundClass, getShapeClass } from '@/hooks/Mapper/components/map/helpers';
|
||||
import { MouseEvent, useCallback, useRef, useState } from 'react';
|
||||
import { Commands } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
|
||||
export type RouteSystemProps = {
|
||||
destination: number;
|
||||
@@ -88,11 +88,10 @@ export interface RoutesListProps {
|
||||
|
||||
export const RoutesList = ({ data, onContextMenu }: RoutesListProps) => {
|
||||
const [selected, setSelected] = useState<number | null>(null);
|
||||
const { mapRef } = useMapRootState();
|
||||
|
||||
const handleClick = useCallback(
|
||||
(systemId: number) => mapRef.current?.command(Commands.centerSystem, systemId.toString()),
|
||||
[mapRef],
|
||||
(systemId: number) => emitMapEvent({ name: Commands.centerSystem, data: systemId?.toString() }),
|
||||
[],
|
||||
);
|
||||
|
||||
if (!data.has_connection) {
|
||||
|
||||
@@ -30,7 +30,6 @@ const sortByDist = (a: Route, b: Route) => {
|
||||
export const RoutesWidgetContent = () => {
|
||||
const {
|
||||
data: { selectedSystems, hubs = [], systems, routes },
|
||||
mapRef,
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
@@ -42,7 +41,6 @@ export const RoutesWidgetContent = () => {
|
||||
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers({
|
||||
outCommand,
|
||||
hubs,
|
||||
mapRef,
|
||||
});
|
||||
|
||||
const preparedHubs = useMemo(() => {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,10 @@ import { Dialog } from 'primereact/dialog';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { Checkbox } from 'primereact/checkbox';
|
||||
import { TabPanel, TabView } from 'primereact/tabview';
|
||||
import styles from './SystemSignatureSettingsDialog.module.scss';
|
||||
|
||||
export type Setting = { key: string; name: string; value: boolean };
|
||||
export type Setting = { key: string; name: string; value: boolean; isFilter?: boolean };
|
||||
|
||||
export const COSMIC_SIGNATURE = 'Cosmic Signature';
|
||||
export const COSMIC_ANOMALY = 'Cosmic Anomaly';
|
||||
@@ -24,8 +26,12 @@ export const SystemSignatureSettingsDialog = ({
|
||||
onSave,
|
||||
onCancel,
|
||||
}: SystemSignatureSettingsDialogProps) => {
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const [settings, setSettings] = useState<Setting[]>(defaultSettings);
|
||||
|
||||
const filterSettings = settings.filter(setting => setting.isFilter);
|
||||
const userSettings = settings.filter(setting => !setting.isFilter);
|
||||
|
||||
const handleSettingsChange = (key: string) => {
|
||||
setSettings(prevState => prevState.map(item => (item.key === key ? { ...item, value: !item.value } : item)));
|
||||
};
|
||||
@@ -35,23 +41,53 @@ export const SystemSignatureSettingsDialog = ({
|
||||
}, [onSave, settings]);
|
||||
|
||||
return (
|
||||
<Dialog header="Filter signatures" visible draggable={false} style={{ width: '300px' }} onHide={onCancel}>
|
||||
<Dialog header="System Signatures Settings" visible={true} onHide={onCancel} className="w-full max-w-lg">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-2">
|
||||
{settings.map(setting => {
|
||||
return (
|
||||
<div key={setting.key} className="flex items-center">
|
||||
<Checkbox
|
||||
inputId={setting.key}
|
||||
checked={setting.value}
|
||||
onChange={() => handleSettingsChange(setting.key)}
|
||||
/>
|
||||
<label htmlFor={setting.key} className="ml-2">
|
||||
{setting.name}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className={styles.verticalTabsContainer}>
|
||||
<TabView
|
||||
activeIndex={activeIndex}
|
||||
onTabChange={e => setActiveIndex(e.index)}
|
||||
className={styles.verticalTabView}
|
||||
>
|
||||
<TabPanel header="Filters" headerClassName={styles.verticalTabHeader}>
|
||||
<div className="w-full h-full flex flex-col gap-1">
|
||||
{filterSettings.map(setting => {
|
||||
return (
|
||||
<div key={setting.key} className="flex items-center">
|
||||
<Checkbox
|
||||
inputId={setting.key}
|
||||
checked={setting.value}
|
||||
onChange={() => handleSettingsChange(setting.key)}
|
||||
/>
|
||||
<label htmlFor={setting.key} className="ml-2">
|
||||
{setting.name}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
|
||||
<div className="w-full h-full flex flex-col gap-1">
|
||||
{userSettings.map(setting => {
|
||||
return (
|
||||
<div key={setting.key} className="flex items-center">
|
||||
<Checkbox
|
||||
inputId={setting.key}
|
||||
checked={setting.value}
|
||||
onChange={() => handleSettingsChange(setting.key)}
|
||||
/>
|
||||
<label htmlFor={setting.key} className="ml-2">
|
||||
{setting.name}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</TabPanel>
|
||||
</TabView>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
SHIP,
|
||||
DRONE,
|
||||
} from './SystemSignatureSettingsDialog';
|
||||
import { SignatureGroup } from '@/hooks/Mapper/types';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
@@ -19,17 +20,27 @@ import { PrimeIcons } from 'primereact/api';
|
||||
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
const settings: Setting[] = [
|
||||
{ key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true },
|
||||
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true },
|
||||
{ key: DEPLOYABLE, name: 'Show Deployables', value: true },
|
||||
{ key: STRUCTURE, name: 'Show Structures', value: true },
|
||||
{ key: STARBASE, name: 'Show Starbase', value: true },
|
||||
{ key: SHIP, name: 'Show Ships', value: true },
|
||||
{ key: DRONE, name: 'Show Drones And Charges', value: true },
|
||||
];
|
||||
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v4_1';
|
||||
export const SHOW_DESCRIPTION_COLUMN_SETTING = 'show_description_column_setting';
|
||||
export const SHOW_INSERTED_COLUMN_SETTING = 'show_inserted_column_setting';
|
||||
|
||||
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings';
|
||||
const settings: Setting[] = [
|
||||
{ key: SHOW_INSERTED_COLUMN_SETTING, name: 'Show Inserted Column', value: false, isFilter: false },
|
||||
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: false, isFilter: false },
|
||||
{ key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true, isFilter: true },
|
||||
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true, isFilter: true },
|
||||
{ key: DEPLOYABLE, name: 'Show Deployables', value: true, isFilter: true },
|
||||
{ key: STRUCTURE, name: 'Show Structures', value: true, isFilter: true },
|
||||
{ key: STARBASE, name: 'Show Starbase', value: true, isFilter: true },
|
||||
{ key: SHIP, name: 'Show Ships', value: true, isFilter: true },
|
||||
{ key: DRONE, name: 'Show Drones And Charges', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.Wormhole, name: 'Show Wormholes', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.RelicSite, name: 'Show Relic Sites', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.DataSite, name: 'Show Data Sites', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.OreSite, name: 'Show Ore Sites', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.GasSite, name: 'Show Gas Sites', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.CombatSite, name: 'Show Combat Sites', value: true, isFilter: true },
|
||||
];
|
||||
|
||||
const defaultSettings = () => {
|
||||
return [...settings];
|
||||
@@ -91,8 +102,7 @@ export const SystemSignatures = () => {
|
||||
</InfoDrawer>
|
||||
<InfoDrawer title={<b className="text-slate-50">How to delete?</b>}>
|
||||
For delete any signature first of all you need select before
|
||||
<br /> and then use <b className="text-sky-500">Del</b> or{' '}
|
||||
<b className="text-sky-500">Backspace</b>
|
||||
<br /> and then use <b className="text-sky-500">Backspace</b>
|
||||
</InfoDrawer>
|
||||
</div>
|
||||
) as React.ReactNode,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useClipboard } from '@/hooks/Mapper/hooks/useClipboard';
|
||||
import { parseSignatures } from '@/hooks/Mapper/helpers';
|
||||
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
|
||||
import { DataTable, DataTableRowClickEvent, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable';
|
||||
import { Column } from 'primereact/column';
|
||||
@@ -21,16 +22,22 @@ import {
|
||||
getRowColorByTimeLeft,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers';
|
||||
import {
|
||||
renderDescription,
|
||||
renderIcon,
|
||||
renderInfoColumn,
|
||||
renderTimeLeft,
|
||||
renderInsertedTimeLeft,
|
||||
renderUpdatedTimeLeft,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { SignatureSettings } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
|
||||
import { COSMIC_SIGNATURE } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
|
||||
import {
|
||||
SHOW_DESCRIPTION_COLUMN_SETTING,
|
||||
SHOW_INSERTED_COLUMN_SETTING,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures';
|
||||
type SystemSignaturesSortSettings = {
|
||||
sortField: string;
|
||||
sortOrder: SortOrder;
|
||||
@@ -44,10 +51,17 @@ const SORT_DEFAULT_VALUES: SystemSignaturesSortSettings = {
|
||||
interface SystemSignaturesContentProps {
|
||||
systemId: string;
|
||||
settings: Setting[];
|
||||
hideLinkedSignatures?: boolean;
|
||||
selectable?: boolean;
|
||||
onSelect?: (signature: SystemSignature) => void;
|
||||
}
|
||||
export const SystemSignaturesContent = ({ systemId, settings, selectable, onSelect }: SystemSignaturesContentProps) => {
|
||||
export const SystemSignaturesContent = ({
|
||||
systemId,
|
||||
settings,
|
||||
hideLinkedSignatures,
|
||||
selectable,
|
||||
onSelect,
|
||||
}: SystemSignaturesContentProps) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
|
||||
const [signatures, setSignatures, signaturesRef] = useRefState<SystemSignature[]>([]);
|
||||
@@ -80,13 +94,41 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
}
|
||||
}, []);
|
||||
|
||||
const groupSettings = useMemo(() => settings.filter(s => (GROUPS_LIST as string[]).includes(s.key)), [settings]);
|
||||
const showDescriptionColumn = useMemo(
|
||||
() => settings.find(s => s.key === SHOW_DESCRIPTION_COLUMN_SETTING)?.value,
|
||||
[settings],
|
||||
);
|
||||
|
||||
const showInsertedColumn = useMemo(
|
||||
() => settings.find(s => s.key === SHOW_INSERTED_COLUMN_SETTING)?.value,
|
||||
[settings],
|
||||
);
|
||||
|
||||
const filteredSignatures = useMemo(() => {
|
||||
return signatures
|
||||
.filter(x => settings.find(y => y.key === x.kind)?.value)
|
||||
.filter(x => {
|
||||
if (hideLinkedSignatures && !!x.linked_system) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isCosmicSignature = x.kind === COSMIC_SIGNATURE;
|
||||
|
||||
if (isCosmicSignature) {
|
||||
const showCosmicSignatures = settings.find(y => y.key === COSMIC_SIGNATURE)?.value;
|
||||
if (showCosmicSignatures) {
|
||||
return !x.group || groupSettings.find(y => y.key === x.group)?.value;
|
||||
} else {
|
||||
return !!x.group && groupSettings.find(y => y.key === x.group)?.value;
|
||||
}
|
||||
}
|
||||
|
||||
return settings.find(y => y.key === x.kind)?.value;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return new Date(b.updated_at || 0).getTime() - new Date(a.updated_at || 0).getTime();
|
||||
});
|
||||
}, [signatures, settings]);
|
||||
}, [signatures, settings, groupSettings, hideLinkedSignatures]);
|
||||
|
||||
const handleGetSignatures = useCallback(async () => {
|
||||
const { signatures } = await outCommand({
|
||||
@@ -98,26 +140,6 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
setSignatures(signatures);
|
||||
}, [outCommand, systemId]);
|
||||
|
||||
// const updateSignatures = useCallback(
|
||||
// async (newSignatures: SystemSignature[], updateOnly: boolean) => {
|
||||
// const { added, updated, removed } = getActualSigs(signaturesRef.current, newSignatures, updateOnly);
|
||||
|
||||
// const { signatures: updatedSignatures } = await outCommand({
|
||||
// type: OutCommand.updateSignatures,
|
||||
// data: {
|
||||
// system_id: systemId,
|
||||
// added,
|
||||
// updated,
|
||||
// removed,
|
||||
// },
|
||||
// });
|
||||
|
||||
// setSignatures(() => updatedSignatures);
|
||||
// setSelectedSignatures([]);
|
||||
// },
|
||||
// [outCommand, systemId],
|
||||
// );
|
||||
|
||||
const handleUpdateSignatures = useCallback(
|
||||
async (newSignatures: SystemSignature[], updateOnly: boolean) => {
|
||||
const { added, updated, removed } = getActualSigs(signaturesRef.current, newSignatures, updateOnly);
|
||||
@@ -181,7 +203,7 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
|
||||
useHotkey(true, ['a'], handleSelectAll);
|
||||
|
||||
useHotkey(false, ['Backspace', 'Delete'], handleDeleteSelected);
|
||||
useHotkey(false, ['Backspace'], handleDeleteSelected);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectable) {
|
||||
@@ -345,22 +367,46 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
style={{ maxWidth: nameColumnWidth }}
|
||||
hidden={compact || medium}
|
||||
></Column>
|
||||
{showDescriptionColumn && (
|
||||
<Column
|
||||
field="description"
|
||||
header="Description"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
body={renderDescription}
|
||||
hidden={compact}
|
||||
sortable
|
||||
></Column>
|
||||
)}
|
||||
|
||||
{showInsertedColumn && (
|
||||
<Column
|
||||
field="inserted_at"
|
||||
header="Inserted"
|
||||
dataType="date"
|
||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
body={renderInsertedTimeLeft}
|
||||
sortable
|
||||
></Column>
|
||||
)}
|
||||
|
||||
<Column
|
||||
field="updated_at"
|
||||
header="Updated"
|
||||
dataType="date"
|
||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
body={renderTimeLeft}
|
||||
body={renderUpdatedTimeLeft}
|
||||
sortable
|
||||
></Column>
|
||||
|
||||
<Column
|
||||
bodyClassName="p-0 pl-1 pr-2"
|
||||
field="group"
|
||||
body={renderToolbar}
|
||||
// headerClassName={headerClasses}
|
||||
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||
></Column>
|
||||
{!selectable && (
|
||||
<Column
|
||||
bodyClassName="p-0 pl-1 pr-2"
|
||||
field="group"
|
||||
body={renderToolbar}
|
||||
// headerClassName={headerClasses}
|
||||
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||
></Column>
|
||||
)}
|
||||
</DataTable>
|
||||
</>
|
||||
)}
|
||||
@@ -370,12 +416,14 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
content={hoveredSig ? <SignatureView {...hoveredSig} /> : null}
|
||||
/>
|
||||
|
||||
<SignatureSettings
|
||||
systemId={systemId}
|
||||
show={showSignatureSettings}
|
||||
onHide={() => setShowSignatureSettings(false)}
|
||||
signatureData={selectedSignature}
|
||||
/>
|
||||
{showSignatureSettings && (
|
||||
<SignatureSettings
|
||||
systemId={systemId}
|
||||
show
|
||||
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">
|
||||
|
||||
@@ -19,6 +19,8 @@ export const getActualSigs = (
|
||||
const isNeedUpgrade = getState(GROUPS_LIST, newSig) > getState(GROUPS_LIST, oldSig);
|
||||
if (isNeedUpgrade) {
|
||||
updated.push({ ...oldSig, group: newSig.group, name: newSig.name });
|
||||
} else {
|
||||
updated.push({ ...oldSig });
|
||||
}
|
||||
} else {
|
||||
if (!updateOnly) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export * from './renderIcon';
|
||||
export * from './renderDescription';
|
||||
export * from './renderName';
|
||||
export * from './renderTimeLeft';
|
||||
export * from './renderInsertedTimeLeft';
|
||||
export * from './renderUpdatedTimeLeft';
|
||||
export * from './renderLinkedSystem';
|
||||
export * from './renderInfoColumn';
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
export const renderDescription = (row: SystemSignature) => {
|
||||
return <span title={row?.description}>{row?.description}</span>;
|
||||
};
|
||||
@@ -1,5 +1,9 @@
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { SystemViewStandalone, WHClassView } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { renderName } from './renderName.tsx';
|
||||
import classes from './renderInfoColumn.module.scss';
|
||||
@@ -32,13 +36,23 @@ export const renderInfoColumn = (row: SystemSignature) => {
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{row.description && (
|
||||
<WdTooltipWrapper content={row.description}>
|
||||
<span className={clsx(PrimeIcons.EXCLAMATION_CIRCLE, 'text-[12px]')}></span>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (row.description != null && row.description.length > 0) {
|
||||
return <span title={row.description}>{row.description}</span>;
|
||||
}
|
||||
|
||||
return renderName(row);
|
||||
return (
|
||||
<div className="flex gap-1 items-center">
|
||||
{renderName(row)}{' '}
|
||||
{row.description && (
|
||||
<WdTooltipWrapper content={row.description}>
|
||||
<span className={clsx(PrimeIcons.EXCLAMATION_CIRCLE, 'text-[12px]')}></span>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { TimeLeft } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
export const renderInsertedTimeLeft = (row: SystemSignature) => {
|
||||
return (
|
||||
<div className="flex w-full items-center">
|
||||
<TimeLeft cDate={row.inserted_at ? new Date(row.inserted_at) : undefined} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { TimeLeft } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
export const renderTimeLeft = (row: SystemSignature) => {
|
||||
export const renderUpdatedTimeLeft = (row: SystemSignature) => {
|
||||
return (
|
||||
<div className="flex w-full items-center">
|
||||
<TimeLeft cDate={row.updated_at ? new Date(row.updated_at) : undefined} />
|
||||
@@ -7,13 +7,13 @@ 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";
|
||||
import { MapSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings';
|
||||
|
||||
export interface MapRootContentProps {}
|
||||
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
export const MapRootContent = ({}: MapRootContentProps) => {
|
||||
const { mapRef, interfaceSettings } = useMapRootState();
|
||||
const { interfaceSettings } = useMapRootState();
|
||||
const { isShowMenu } = interfaceSettings;
|
||||
|
||||
const [showOnTheMap, setShowOnTheMap] = useState(false);
|
||||
@@ -26,7 +26,7 @@ export const MapRootContent = ({}: MapRootContentProps) => {
|
||||
useSkipContextMenu();
|
||||
|
||||
return (
|
||||
<Layout map={<MapWrapper refn={mapRef} />}>
|
||||
<Layout map={<MapWrapper />}>
|
||||
{!isShowMenu ? (
|
||||
<div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none">
|
||||
<div className="absolute top-0 left-0 w-[calc(100%-3.5rem)] h-full pointer-events-none">
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
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 {
|
||||
ConnectionType,
|
||||
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();
|
||||
|
||||
@@ -68,26 +77,49 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
|
||||
return connections.find(x => x.source === selectedConnection.source && x.target === selectedConnection.target);
|
||||
}, [connections, selectedConnection]);
|
||||
|
||||
const isWormhole = useMemo(() => {
|
||||
return cnInfo?.type !== ConnectionType.gate;
|
||||
}, [cnInfo]);
|
||||
|
||||
const [passages, setPassages] = useState<Passage[]>([]);
|
||||
const [info, setInfo] = useState<ConnectionInfoOutput | null>(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(() => {
|
||||
@@ -109,7 +141,7 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
|
||||
>
|
||||
<div className={clsx(classes.SidebarContent, '')}>
|
||||
{/* Connection Info */}
|
||||
<div className="px-2 pb-3 flex flex-col gap-2">
|
||||
<div className="px-2 flex flex-col gap-2">
|
||||
{/* Connection Info Row */}
|
||||
<InfoDrawer title="Connection" rightSide>
|
||||
<div className="flex justify-end gap-2 items-center">
|
||||
@@ -127,10 +159,25 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
|
||||
</div>
|
||||
</InfoDrawer>
|
||||
|
||||
{/* Connection Info Row */}
|
||||
<InfoDrawer title="Approximate mass of passages" rightSide>
|
||||
{kgToTons(approximateMass)}
|
||||
</InfoDrawer>
|
||||
<div className="flex justify-between gap-2">
|
||||
{/*Left column*/}
|
||||
<div>
|
||||
{isWormhole && info?.marl_eol_time && (
|
||||
<InfoDrawer title="Mark EOL Time">
|
||||
<TimeAgo timestamp={info.marl_eol_time} />
|
||||
</InfoDrawer>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/*Right column*/}
|
||||
<div>
|
||||
{isWormhole && (
|
||||
<InfoDrawer title="Approximate mass of passages" rightSide>
|
||||
{kgToTons(approximateMass)}
|
||||
</InfoDrawer>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2"></div>
|
||||
</div>
|
||||
|
||||
@@ -61,6 +61,7 @@ const CONNECTIONS_CHECKBOXES_PROPS: CheckboxesList = [
|
||||
|
||||
const UI_CHECKBOXES_PROPS: CheckboxesList = [
|
||||
{ prop: InterfaceStoredSettingsProps.isShowMenu, label: 'Enable compact map menu bar' },
|
||||
{ prop: InterfaceStoredSettingsProps.isThickConnections, label: 'Thicker connections' },
|
||||
];
|
||||
|
||||
export const MapSettings = ({ show, onHide }: MapSettingsProps) => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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 {
|
||||
@@ -25,102 +24,106 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
|
||||
const { outCommand } = useMapRootState();
|
||||
|
||||
const handleShow = async () => {};
|
||||
const form = useForm<Partial<SystemSignaturePrepared>>({});
|
||||
const signatureForm = useForm<Partial<SystemSignaturePrepared>>({});
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!signatureData) {
|
||||
return;
|
||||
}
|
||||
const handleSave = useCallback(
|
||||
async (e: any) => {
|
||||
e?.preventDefault();
|
||||
if (!signatureData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { group, ...values } = form.getValues();
|
||||
let out = { ...signatureData };
|
||||
const { group, ...values } = signatureForm.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,
|
||||
},
|
||||
});
|
||||
}
|
||||
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 (values.type != null) {
|
||||
out = { ...out, type: values.type };
|
||||
}
|
||||
|
||||
if (signatureData.group !== SignatureGroup.Wormhole) {
|
||||
out = { ...out, name: '' };
|
||||
}
|
||||
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 ?? '' };
|
||||
}
|
||||
}
|
||||
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 };
|
||||
}
|
||||
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! };
|
||||
|
||||
// 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 },
|
||||
type: OutCommand.updateSignatures,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
added: [],
|
||||
updated: [out],
|
||||
removed: [],
|
||||
},
|
||||
});
|
||||
|
||||
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]);
|
||||
signatureForm.reset();
|
||||
onHide();
|
||||
},
|
||||
[signatureForm, onHide, outCommand, signatureData, systemId],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!signatureData) {
|
||||
form.reset();
|
||||
signatureForm.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const { linked_system, ...rest } = signatureData;
|
||||
|
||||
form.reset({
|
||||
signatureForm.reset({
|
||||
linked_system: linked_system?.solar_system_id.toString() ?? undefined,
|
||||
...rest,
|
||||
});
|
||||
}, [form, signatureData]);
|
||||
}, [signatureForm, signatureData]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -138,32 +141,34 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<FormProvider {...signatureForm}>
|
||||
<form onSubmit={handleSave}>
|
||||
<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 />
|
||||
<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>
|
||||
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
|
||||
<span>Description:</span>
|
||||
<Controller
|
||||
name="description"
|
||||
control={signatureForm.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>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
</SystemsSettingsProvider>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Map } from '@/hooks/Mapper/components/map/Map.tsx';
|
||||
import { ForwardedRef, useCallback, useRef, useState } from 'react';
|
||||
import { MapHandlers, OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||
import isEqual from 'lodash.isequal';
|
||||
import { ContextMenuSystem, useContextMenuSystemHandlers } from '@/hooks/Mapper/components/contexts';
|
||||
import {
|
||||
SystemCustomLabelDialog,
|
||||
SystemSettingsDialog,
|
||||
SystemLinkSignatureDialog,
|
||||
SystemSettingsDialog,
|
||||
} from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import classes from './MapWrapper.module.scss';
|
||||
import { Connections } from '@/hooks/Mapper/components/mapRootContent/components/Connections';
|
||||
@@ -20,25 +20,45 @@ import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
|
||||
import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/MapRootProvider';
|
||||
|
||||
interface MapWrapperProps {
|
||||
refn: ForwardedRef<MapHandlers>;
|
||||
}
|
||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
import { useCommonMapEventProcessor } from '@/hooks/Mapper/components/mapWrapper/hooks/useCommonMapEventProcessor.ts';
|
||||
|
||||
// TODO: INFO - this component needs for abstract work with Map instance
|
||||
export const MapWrapper = ({ refn }: MapWrapperProps) => {
|
||||
export const MapWrapper = () => {
|
||||
const {
|
||||
update,
|
||||
outCommand,
|
||||
data: { selectedConnections, selectedSystems, hubs, systems },
|
||||
interfaceSettings: { isShowMenu, isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap, isShowKSpace },
|
||||
interfaceSettings: {
|
||||
isShowMenu,
|
||||
isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
|
||||
isShowKSpace,
|
||||
isThickConnections,
|
||||
},
|
||||
} = useMapRootState();
|
||||
const { deleteSystems } = useDeleteSystems();
|
||||
const { mapRef, runCommand } = useCommonMapEventProcessor();
|
||||
|
||||
const { open, ...systemContextProps } = useContextMenuSystemHandlers({ systems, hubs, outCommand });
|
||||
const { handleSystemMultipleContext, ...systemMultipleCtxProps } = useContextMenuSystemMultipleHandlers();
|
||||
|
||||
const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems });
|
||||
ref.current = { selectedConnections, selectedSystems, systemContextProps, systems };
|
||||
const [openSettings, setOpenSettings] = useState<string | null>(null);
|
||||
const [openLinkSignatures, setOpenLinkSignatures] = useState<any | null>(null);
|
||||
const [openCustomLabel, setOpenCustomLabel] = useState<string | null>(null);
|
||||
const [selectedConnection, setSelectedConnection] = useState<SolarSystemConnection | null>(null);
|
||||
|
||||
const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems });
|
||||
ref.current = { selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems };
|
||||
|
||||
useMapEventListener(event => {
|
||||
switch (event.name) {
|
||||
case Commands.linkSignatureToSystem:
|
||||
setOpenLinkSignatures(event.data);
|
||||
return true;
|
||||
}
|
||||
|
||||
runCommand(event);
|
||||
});
|
||||
|
||||
const onSelectionChange: OnMapSelectionChange = useCallback(
|
||||
({ systems, connections }) => {
|
||||
@@ -59,9 +79,6 @@ export const MapWrapper = ({ refn }: MapWrapperProps) => {
|
||||
[update],
|
||||
);
|
||||
|
||||
const [openSettings, setOpenSettings] = useState<string | null>(null);
|
||||
const [openLinkSignatures, setOpenLinkSignatures] = useState<any | null>(null);
|
||||
const [openCustomLabel, setOpenCustomLabel] = useState<string | null>(null);
|
||||
const handleCommand: OutCommandHandler = useCallback(
|
||||
event => {
|
||||
switch (event.type) {
|
||||
@@ -95,22 +112,19 @@ export const MapWrapper = ({ refn }: MapWrapperProps) => {
|
||||
[open],
|
||||
);
|
||||
|
||||
const [selectedConnection, setSelectedConnection] = useState<SolarSystemConnection | null>(null);
|
||||
|
||||
const handleConnectionDbClick = useCallback((e: SolarSystemConnection) => setSelectedConnection(e), []);
|
||||
|
||||
useMapEventListener(event => {
|
||||
switch (event.name) {
|
||||
case Commands.linkSignatureToSystem:
|
||||
setOpenLinkSignatures(event.data);
|
||||
return true;
|
||||
const handleManualDelete = useCallback((toDelete: string[]) => {
|
||||
const restDel = toDelete.filter(x => ref.current.systems.some(y => y.id === x));
|
||||
if (restDel.length > 0) {
|
||||
ref.current.deleteSystems(restDel);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Map
|
||||
ref={refn}
|
||||
ref={mapRef}
|
||||
onCommand={handleCommand}
|
||||
onSelectionChange={onSelectionChange}
|
||||
onConnectionInfoClick={handleConnectionDbClick}
|
||||
@@ -119,6 +133,8 @@ export const MapWrapper = ({ refn }: MapWrapperProps) => {
|
||||
minimapClasses={!isShowMenu ? classes.MiniMap : undefined}
|
||||
isShowMinimap={isShowMinimap}
|
||||
showKSpaceBG={isShowKSpace}
|
||||
onManualDelete={handleManualDelete}
|
||||
isThickConnections={isThickConnections}
|
||||
/>
|
||||
|
||||
{openSettings != null && (
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { MutableRefObject, useCallback, useEffect, useRef } from 'react';
|
||||
import { Command, Commands, MapHandlers } from '@/hooks/Mapper/types';
|
||||
import { MapEvent } from '@/hooks/Mapper/events';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export const useCommonMapEventProcessor = () => {
|
||||
const mapRef = useRef<MapHandlers>() as MutableRefObject<MapHandlers>;
|
||||
const {
|
||||
data: { systems },
|
||||
} = useMapRootState();
|
||||
|
||||
const refQueue = useRef<MapEvent<Command>[]>([]);
|
||||
|
||||
// const ref = useRef({})
|
||||
|
||||
const runCommand = useCallback(({ name, data }: MapEvent<Command>) => {
|
||||
switch (name) {
|
||||
case Commands.addSystems:
|
||||
case Commands.removeSystems:
|
||||
// case Commands.addConnections:
|
||||
refQueue.current.push({ name, data });
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore hz why here type error
|
||||
mapRef.current?.command(name, data);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refQueue.current.forEach(x => mapRef.current?.command(x.name, x.data));
|
||||
refQueue.current = [];
|
||||
}, [systems]);
|
||||
|
||||
return {
|
||||
mapRef,
|
||||
runCommand,
|
||||
};
|
||||
};
|
||||
@@ -4,7 +4,7 @@ import classes from './CharacterCard.module.scss';
|
||||
import { SystemView } from '@/hooks/Mapper/components/ui-kit/SystemView';
|
||||
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
|
||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
|
||||
type CharacterCardProps = {
|
||||
compact?: boolean;
|
||||
@@ -34,11 +34,12 @@ export const CharacterCard = ({
|
||||
useSystemsCache,
|
||||
...char
|
||||
}: CharacterCardProps) => {
|
||||
const { mapRef } = useMapRootState();
|
||||
|
||||
const handleSelect = useCallback(() => {
|
||||
mapRef.current?.command(Commands.centerSystem, char?.location?.solar_system_id?.toString());
|
||||
}, [mapRef, char]);
|
||||
emitMapEvent({
|
||||
name: Commands.centerSystem,
|
||||
data: char?.location?.solar_system_id?.toString(),
|
||||
});
|
||||
}, [char]);
|
||||
|
||||
return (
|
||||
<div className={clsx(classes.CharacterCard, 'w-full text-xs', 'flex flex-col box-border')} onClick={handleSelect}>
|
||||
|
||||
@@ -31,24 +31,26 @@ export enum InterfaceStoredSettingsProps {
|
||||
isShowMenu = 'isShowMenu',
|
||||
isShowMinimap = 'isShowMinimap',
|
||||
isShowKSpace = 'isShowKSpace',
|
||||
isThickConnections = 'isThickConnections',
|
||||
}
|
||||
|
||||
export type InterfaceStoredSettings = {
|
||||
isShowMenu: boolean;
|
||||
isShowMinimap: boolean;
|
||||
isShowKSpace: boolean;
|
||||
isThickConnections: boolean;
|
||||
};
|
||||
|
||||
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
|
||||
isShowMenu: false,
|
||||
isShowMinimap: true,
|
||||
isShowKSpace: false,
|
||||
isThickConnections: false,
|
||||
};
|
||||
|
||||
export interface MapRootContextProps {
|
||||
update: ContextStoreDataUpdate<MapRootData>;
|
||||
data: MapRootData;
|
||||
mapRef: RefObject<MapHandlers>;
|
||||
outCommand: OutCommandHandler;
|
||||
interfaceSettings: InterfaceStoredSettings;
|
||||
setInterfaceSettings: Dispatch<SetStateAction<InterfaceStoredSettings>>;
|
||||
@@ -57,7 +59,6 @@ export interface MapRootContextProps {
|
||||
const MapRootContext = createContext<MapRootContextProps>({
|
||||
update: () => {},
|
||||
data: { ...INITIAL_DATA },
|
||||
mapRef: { current: null },
|
||||
// @ts-ignore
|
||||
outCommand: async () => void 0,
|
||||
interfaceSettings: STORED_INTERFACE_DEFAULT_VALUES,
|
||||
@@ -67,7 +68,6 @@ const MapRootContext = createContext<MapRootContextProps>({
|
||||
type MapRootProviderProps = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fwdRef: ForwardedRef<any>;
|
||||
mapRef: RefObject<MapHandlers>;
|
||||
outCommand: OutCommandHandler;
|
||||
} & WithChildren;
|
||||
|
||||
@@ -78,7 +78,7 @@ const MapRootHandlers = forwardRef(({ children }: WithChildren, fwdRef: Forwarde
|
||||
});
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
export const MapRootProvider = ({ children, fwdRef, mapRef, outCommand }: MapRootProviderProps) => {
|
||||
export const MapRootProvider = ({ children, fwdRef, outCommand }: MapRootProviderProps) => {
|
||||
const { update, ref } = useContextStore<MapRootData>({ ...INITIAL_DATA });
|
||||
|
||||
const [interfaceSettings, setInterfaceSettings] = useLocalStorageState<InterfaceStoredSettings>(
|
||||
@@ -94,7 +94,6 @@ export const MapRootProvider = ({ children, fwdRef, mapRef, outCommand }: MapRoo
|
||||
update,
|
||||
data: ref,
|
||||
outCommand: outCommand,
|
||||
mapRef: mapRef,
|
||||
setInterfaceSettings,
|
||||
interfaceSettings,
|
||||
}}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandAddSystems, CommandRemoveSystems, CommandUpdateSystems } from '@/hooks/Mapper/types';
|
||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
||||
|
||||
export const useCommandsSystems = () => {
|
||||
const {
|
||||
@@ -8,40 +9,52 @@ export const useCommandsSystems = () => {
|
||||
data: { systems },
|
||||
} = useMapRootState();
|
||||
|
||||
const ref = useRef({ systems, update });
|
||||
ref.current = { systems, update };
|
||||
const { addSystemStatic } = useLoadSystemStatic({ systems: [] });
|
||||
|
||||
const addSystems = useCallback(
|
||||
(addSystems: CommandAddSystems) => {
|
||||
update({
|
||||
systems: [...ref.current.systems.filter(sys => addSystems.some(x => sys.id !== x.id)), ...addSystems],
|
||||
});
|
||||
},
|
||||
[update],
|
||||
);
|
||||
const ref = useRef({ systems, update, addSystemStatic });
|
||||
ref.current = { systems, update, addSystemStatic };
|
||||
|
||||
const addSystems = useCallback((systemsToAdd: CommandAddSystems) => {
|
||||
const { update, addSystemStatic, systems } = ref.current;
|
||||
|
||||
systemsToAdd.forEach(sys => {
|
||||
if (sys.system_static_info) {
|
||||
addSystemStatic(sys.system_static_info);
|
||||
}
|
||||
});
|
||||
|
||||
update(
|
||||
{
|
||||
systems: [...systems.filter(sys => !systemsToAdd.some(x => sys.id === x.id)), ...systemsToAdd],
|
||||
},
|
||||
true,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const removeSystems = useCallback((toRemove: CommandRemoveSystems) => {
|
||||
const { update, systems } = ref.current;
|
||||
update({
|
||||
systems: systems.filter(x => !toRemove.includes(parseInt(x.id))),
|
||||
});
|
||||
update(
|
||||
{
|
||||
systems: systems.filter(x => !toRemove.includes(parseInt(x.id))),
|
||||
},
|
||||
true,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const updateSystems = useCallback(
|
||||
(systems: CommandUpdateSystems) => {
|
||||
const out = ref.current.systems.map(current => {
|
||||
const newSystem = systems.find(x => current.id === x.id);
|
||||
if (!newSystem) {
|
||||
return current;
|
||||
}
|
||||
const updateSystems = useCallback((updatedSystems: CommandUpdateSystems) => {
|
||||
const { update, systems } = ref.current;
|
||||
|
||||
return newSystem;
|
||||
});
|
||||
const out = systems.map(current => {
|
||||
const newSystem = updatedSystems.find(x => current.id === x.id);
|
||||
if (!newSystem) {
|
||||
return current;
|
||||
}
|
||||
|
||||
update({ systems: out });
|
||||
},
|
||||
[update],
|
||||
);
|
||||
return newSystem;
|
||||
});
|
||||
|
||||
update({ systems: out }, true);
|
||||
}, []);
|
||||
|
||||
return { addSystems, removeSystems, updateSystems };
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ interface UseLoadSystemStaticProps {
|
||||
systems: (number | string)[];
|
||||
}
|
||||
|
||||
export const useLoadSystemStatic = ({ systems }: UseLoadSystemStaticProps) => {
|
||||
export const useLoadSystemStatic = ({ systems = [] }: UseLoadSystemStaticProps) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [lastUpdateKey, setLastUpdateKey] = useState(0);
|
||||
@@ -51,6 +51,9 @@ export const useLoadSystemStatic = ({ systems }: UseLoadSystemStaticProps) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!systems.length) {
|
||||
return;
|
||||
}
|
||||
loadSystems(systems);
|
||||
// eslint-disable-next-line
|
||||
}, [systems]);
|
||||
|
||||
@@ -44,87 +44,75 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
return {
|
||||
command(type, data) {
|
||||
switch (type) {
|
||||
case Commands.init:
|
||||
case Commands.init: // USED
|
||||
mapInit(data as CommandInit);
|
||||
break;
|
||||
case Commands.addSystems:
|
||||
case Commands.addSystems: // USED
|
||||
addSystems(data as CommandAddSystems);
|
||||
setTimeout(() => {
|
||||
emitMapEvent({ name: Commands.addSystems, data });
|
||||
}, 100);
|
||||
break;
|
||||
case Commands.updateSystems:
|
||||
case Commands.updateSystems: // USED
|
||||
updateSystems(data as CommandUpdateSystems);
|
||||
break;
|
||||
case Commands.removeSystems:
|
||||
case Commands.removeSystems: // USED
|
||||
removeSystems(data as CommandRemoveSystems);
|
||||
setTimeout(() => {
|
||||
emitMapEvent({ name: Commands.removeSystems, data });
|
||||
}, 100);
|
||||
|
||||
break;
|
||||
case Commands.addConnections:
|
||||
case Commands.addConnections: // USED
|
||||
addConnections(data as CommandAddConnections);
|
||||
setTimeout(() => {
|
||||
emitMapEvent({ name: Commands.addConnections, data });
|
||||
}, 100);
|
||||
break;
|
||||
case Commands.removeConnections:
|
||||
case Commands.removeConnections: // USED
|
||||
removeConnections(data as CommandRemoveConnections);
|
||||
break;
|
||||
case Commands.updateConnection:
|
||||
case Commands.updateConnection: // USED
|
||||
updateConnection(data as CommandUpdateConnection);
|
||||
break;
|
||||
case Commands.charactersUpdated:
|
||||
case Commands.charactersUpdated: // USED
|
||||
charactersUpdated(data as CommandCharactersUpdated);
|
||||
break;
|
||||
case Commands.characterAdded:
|
||||
case Commands.characterAdded: // USED
|
||||
characterAdded(data as CommandCharacterAdded);
|
||||
break;
|
||||
case Commands.characterRemoved:
|
||||
case Commands.characterRemoved: // USED
|
||||
characterRemoved(data as CommandCharacterRemoved);
|
||||
break;
|
||||
case Commands.characterUpdated:
|
||||
case Commands.characterUpdated: // USED
|
||||
characterUpdated(data as CommandCharacterUpdated);
|
||||
break;
|
||||
case Commands.presentCharacters:
|
||||
case Commands.presentCharacters: // USED
|
||||
presentCharacters(data as CommandPresentCharacters);
|
||||
break;
|
||||
case Commands.mapUpdated:
|
||||
case Commands.mapUpdated: // USED
|
||||
mapUpdated(data as CommandMapUpdated);
|
||||
break;
|
||||
case Commands.routes:
|
||||
mapRoutes(data as CommandRoutes);
|
||||
break;
|
||||
|
||||
case Commands.centerSystem:
|
||||
case Commands.signaturesUpdated: // USED
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.selectSystem:
|
||||
case Commands.linkSignatureToSystem: // USED
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.linkSignatureToSystem:
|
||||
// TODO command data type lost
|
||||
// @ts-ignore
|
||||
emitMapEvent({ name: Commands.linkSignatureToSystem, data });
|
||||
case Commands.centerSystem: // USED
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.selectSystem: // USED
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.killsUpdated:
|
||||
// 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;
|
||||
}
|
||||
|
||||
emitMapEvent({ name: type, data });
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
export enum ConnectionType {
|
||||
wormhole,
|
||||
gate,
|
||||
}
|
||||
|
||||
export enum MassState {
|
||||
normal,
|
||||
half,
|
||||
@@ -32,4 +37,6 @@ export type SolarSystemConnection = {
|
||||
|
||||
source: string;
|
||||
target: string;
|
||||
|
||||
type?: ConnectionType;
|
||||
};
|
||||
|
||||
@@ -11,6 +11,10 @@ export type Passage = {
|
||||
character: PassageLimitedCharacterType;
|
||||
};
|
||||
|
||||
export type ConnectionInfoOutput = {
|
||||
marl_eol_time: string;
|
||||
};
|
||||
|
||||
export type ConnectionOutput = {
|
||||
passages: Passage[];
|
||||
};
|
||||
|
||||
@@ -118,7 +118,9 @@ export enum OutCommand {
|
||||
getCharacterJumps = 'get_character_jumps',
|
||||
getSignatures = 'get_signatures',
|
||||
getSystemStaticInfos = 'get_system_static_infos',
|
||||
getConnectionInfo = 'get_connection_info',
|
||||
updateConnectionTimeStatus = 'update_connection_time_status',
|
||||
updateConnectionType = 'update_connection_type',
|
||||
updateConnectionMassStatus = 'update_connection_mass_status',
|
||||
updateConnectionShipSizeType = 'update_connection_ship_size_type',
|
||||
updateConnectionLocked = 'update_connection_locked',
|
||||
|
||||
@@ -25,5 +25,6 @@ export type SystemSignature = {
|
||||
group: SignatureGroup;
|
||||
type: string;
|
||||
linked_system?: SolarSystemStaticInfoRaw;
|
||||
inserted_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { RefObject, useCallback } from 'react';
|
||||
|
||||
import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { getQueryVariable } from './utils';
|
||||
|
||||
export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRef: RefObject<any>) => {
|
||||
const handleCommand = useCallback(
|
||||
@@ -16,10 +15,6 @@ export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRe
|
||||
);
|
||||
|
||||
const handleMapEvent = useCallback(({ type, body }) => {
|
||||
if (getQueryVariable('debug') === 'true') {
|
||||
console.log(type, body);
|
||||
}
|
||||
|
||||
handlerRefs.forEach(ref => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -4,13 +4,26 @@ export default {
|
||||
mounted() {
|
||||
const hook = this;
|
||||
|
||||
const button = hook.el.querySelector('.update-button');
|
||||
const refreshZone = hook.el.querySelector('#refresh-area');
|
||||
|
||||
button.addEventListener('click', function () {
|
||||
const lastVersion = hook.el.dataset.version;
|
||||
localStorage.setItem(LAST_VERSION_KEY, lastVersion);
|
||||
window.location.reload();
|
||||
});
|
||||
const handleUpdate = function (e: Event) {
|
||||
const hexBricks = hook.el.querySelectorAll('.hex-brick');
|
||||
|
||||
// Add a new class to each element
|
||||
hexBricks.forEach(el => {
|
||||
el.classList.add('hex-brick--active');
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
const lastVersion = hook.el.dataset.version;
|
||||
localStorage.setItem(LAST_VERSION_KEY, lastVersion);
|
||||
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
refreshZone.addEventListener('click', handleUpdate);
|
||||
refreshZone.addEventListener('mouseover', handleUpdate);
|
||||
|
||||
this.updated();
|
||||
},
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "0.7.0",
|
||||
"@react-rxjs/core": "^0.10.7",
|
||||
"@react-rxjs/utils": "^0.9.7",
|
||||
"@shopify/draggable": "^1.1.3",
|
||||
"clsx": "^2.1.1",
|
||||
"daisyui": "^4.11.1",
|
||||
@@ -33,8 +31,7 @@
|
||||
"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",
|
||||
"reactflow": "^11.11.4",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"topbar": "^3.0.0",
|
||||
"use-local-storage-state": "^19.3.1"
|
||||
|
||||
@@ -469,19 +469,6 @@
|
||||
resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz"
|
||||
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
|
||||
|
||||
"@react-rxjs/core@^0.10.7":
|
||||
version "0.10.7"
|
||||
resolved "https://registry.npmjs.org/@react-rxjs/core/-/core-0.10.7.tgz"
|
||||
integrity sha512-dornp8pUs9OcdqFKKRh9+I2FVe21gWufNun6RYU1ddts7kUy9i4Thvl0iqcPFbGY61cJQMAJF7dxixWMSD/A/A==
|
||||
dependencies:
|
||||
"@rx-state/core" "0.1.4"
|
||||
use-sync-external-store "^1.0.0"
|
||||
|
||||
"@react-rxjs/utils@^0.9.7":
|
||||
version "0.9.7"
|
||||
resolved "https://registry.npmjs.org/@react-rxjs/utils/-/utils-0.9.7.tgz"
|
||||
integrity sha512-m9CUTdRsglObvUAlYfB24QvN+QH4XqCGEKnCdSILIeOx7mMqSi9TTFp2zrj5XqtMiLnj4ReAdDxrXegLPB73bQ==
|
||||
|
||||
"@reactflow/background@11.3.14":
|
||||
version "11.3.14"
|
||||
resolved "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz"
|
||||
@@ -650,11 +637,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz#5a2d08b81e8064b34242d5cc9973ef8dd1e60503"
|
||||
integrity sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==
|
||||
|
||||
"@rx-state/core@0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.npmjs.org/@rx-state/core/-/core-0.1.4.tgz"
|
||||
integrity sha512-Z+3hjU2xh1HisLxt+W5hlYX/eGSDaXXP+ns82gq/PLZpkXLu0uwcNUh9RLY3Clq4zT+hSsA3vcpIGt6+UAb8rQ==
|
||||
|
||||
"@shopify/draggable@^1.1.3":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/@shopify/draggable/-/draggable-1.1.3.tgz"
|
||||
@@ -3280,9 +3262,9 @@ react@18.2.0:
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
reactflow@^11.10.4:
|
||||
reactflow@^11.11.4:
|
||||
version "11.11.4"
|
||||
resolved "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz"
|
||||
resolved "https://registry.yarnpkg.com/reactflow/-/reactflow-11.11.4.tgz#e3593e313420542caed81aecbd73fb9bc6576653"
|
||||
integrity sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==
|
||||
dependencies:
|
||||
"@reactflow/background" "11.3.14"
|
||||
@@ -3421,13 +3403,6 @@ run-parallel@^1.1.9:
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
rxjs@^7.8.1:
|
||||
version "7.8.1"
|
||||
resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz"
|
||||
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
safe-array-concat@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz"
|
||||
@@ -3732,7 +3707,7 @@ ts-interface-checker@^0.1.9:
|
||||
resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz"
|
||||
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
|
||||
|
||||
tslib@^2.1.0, tslib@^2.6.2:
|
||||
tslib@^2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
|
||||
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
||||
@@ -3838,7 +3813,7 @@ use-local-storage-state@^19.3.1:
|
||||
resolved "https://registry.npmjs.org/use-local-storage-state/-/use-local-storage-state-19.3.1.tgz"
|
||||
integrity sha512-y3Z1dODXvZXZB4qtLDNN8iuXbsYD6TAxz61K58GWB9/yKwrNG9ynI0GzCTHi/Je1rMiyOwMimz0oyFsZn+Kj7Q==
|
||||
|
||||
use-sync-external-store@1.2.0, use-sync-external-store@^1.0.0:
|
||||
use-sync-external-store@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"
|
||||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
||||
|
||||
@@ -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
|
||||
@@ -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(
|
||||
@@ -171,43 +172,65 @@ config :wanderer_app, WandererApp.Scheduler,
|
||||
timeout: :infinity
|
||||
|
||||
if config_env() == :prod do
|
||||
database_url =
|
||||
System.get_env("DATABASE_URL") ||
|
||||
raise """
|
||||
environment variable DATABASE_URL is missing.
|
||||
For example: ecto://USER:PASS@HOST/DATABASE
|
||||
"""
|
||||
database_unix_socket =
|
||||
System.get_env("DATABASE_UNIX_SOCKET")
|
||||
|
||||
maybe_ipv6 =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("ECTO_IPV6", "false")
|
||||
|> String.to_existing_atom()
|
||||
database =
|
||||
database_unix_socket
|
||||
|> case do
|
||||
true -> [:inet6]
|
||||
_ -> []
|
||||
end
|
||||
nil ->
|
||||
System.get_env("DATABASE_URL") ||
|
||||
raise """
|
||||
environment variable DATABASE_URL is missing.
|
||||
For example: ecto://USER:PASS@HOST/DATABASE
|
||||
"""
|
||||
|
||||
db_ssl_enabled =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("DATABASE_SSL_ENABLED", "false")
|
||||
|> String.to_existing_atom()
|
||||
|
||||
db_ssl_verify_none =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("DATABASE_SSL_VERIFY_NONE", "false")
|
||||
|> String.to_existing_atom()
|
||||
|
||||
client_opts =
|
||||
if db_ssl_verify_none do
|
||||
[verify: :verify_none]
|
||||
_ ->
|
||||
System.get_env("DATABASE_NAME") ||
|
||||
raise """
|
||||
environment variable DATABASE_NAME is missing.
|
||||
For example: "wanderer"
|
||||
"""
|
||||
end
|
||||
|
||||
config :wanderer_app, WandererApp.Repo,
|
||||
url: database_url,
|
||||
ssl: db_ssl_enabled,
|
||||
ssl_opts: client_opts,
|
||||
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
|
||||
socket_options: maybe_ipv6
|
||||
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
|
||||
|
||||
if not is_nil(database_unix_socket) do
|
||||
config :wanderer_app, WandererApp.Repo,
|
||||
socket_dir: database_unix_socket,
|
||||
database: database
|
||||
else
|
||||
db_ssl_enabled =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("DATABASE_SSL_ENABLED", "false")
|
||||
|> String.to_existing_atom()
|
||||
|
||||
db_ssl_verify_none =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("DATABASE_SSL_VERIFY_NONE", "false")
|
||||
|> String.to_existing_atom()
|
||||
|
||||
client_opts =
|
||||
if db_ssl_verify_none do
|
||||
[verify: :verify_none]
|
||||
end
|
||||
|
||||
maybe_ipv6 =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("ECTO_IPV6", "false")
|
||||
|> String.to_existing_atom()
|
||||
|> case do
|
||||
true -> [:inet6]
|
||||
_ -> []
|
||||
end
|
||||
|
||||
config :wanderer_app, WandererApp.Repo,
|
||||
url: database,
|
||||
ssl: db_ssl_enabled,
|
||||
ssl_opts: client_opts,
|
||||
socket_options: maybe_ipv6
|
||||
end
|
||||
|
||||
# The secret key base is used to sign/encrypt cookies and other secrets.
|
||||
# A default value is used in config/dev.exs and config/test.exs but you
|
||||
|
||||
20
fly.toml
20
fly.toml
@@ -3,9 +3,10 @@
|
||||
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
|
||||
#
|
||||
|
||||
app = 'wanderer'
|
||||
app = 'wanderer-test'
|
||||
primary_region = 'ams'
|
||||
kill_signal = 'SIGTERM'
|
||||
swap_size_mb = 512
|
||||
|
||||
[build]
|
||||
|
||||
@@ -13,18 +14,14 @@ kill_signal = 'SIGTERM'
|
||||
release_command = '/app/bin/migrate.sh'
|
||||
|
||||
[env]
|
||||
PHX_HOST = 'wanderer.fly.dev'
|
||||
PHX_HOST = 'wanderer-test.fly.dev'
|
||||
PHX_SERVER = 'true'
|
||||
PORT = '8080'
|
||||
|
||||
[metrics]
|
||||
port = 4021
|
||||
path = "/metrics"
|
||||
|
||||
[http_service]
|
||||
internal_port = 8080
|
||||
force_https = true
|
||||
auto_stop_machines = false
|
||||
auto_stop_machines = 'off'
|
||||
auto_start_machines = false
|
||||
min_machines_running = 0
|
||||
processes = ['app']
|
||||
@@ -35,6 +32,9 @@ path = "/metrics"
|
||||
soft_limit = 1000
|
||||
|
||||
[[vm]]
|
||||
memory = '1gb'
|
||||
cpu_kind = 'shared'
|
||||
cpus = 1
|
||||
size = 'shared-cpu-1x'
|
||||
|
||||
[[metrics]]
|
||||
port = 4021
|
||||
path = '/metrics'
|
||||
https = false
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,12 +17,12 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
action: :read_by_map
|
||||
)
|
||||
|
||||
define(:tracked_by_map,
|
||||
action: :tracked_by_map
|
||||
define(:tracked_by_map_filtered,
|
||||
action: :tracked_by_map_filtered
|
||||
)
|
||||
|
||||
define(:tracked_by_map_all,
|
||||
action: :read_tracked_by_map
|
||||
action: :tracked_by_map_all
|
||||
)
|
||||
|
||||
define(:track, action: :track)
|
||||
@@ -38,7 +38,7 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
|
||||
defaults [:create, :read, :update, :destroy]
|
||||
|
||||
read :tracked_by_map do
|
||||
read :tracked_by_map_filtered do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
argument(:character_ids, {:array, :uuid}, allow_nil?: false)
|
||||
|
||||
@@ -52,7 +52,7 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
filter(expr(map_id == ^arg(:map_id)))
|
||||
end
|
||||
|
||||
read :read_tracked_by_map do
|
||||
read :tracked_by_map_all do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
filter(expr(map_id == ^arg(:map_id) and tracked == true))
|
||||
end
|
||||
|
||||
@@ -29,13 +29,16 @@ defmodule WandererApp.Api.MapConnection do
|
||||
define(:update_ship_size_type, action: :update_ship_size_type)
|
||||
define(:update_locked, action: :update_locked)
|
||||
define(:update_custom_info, action: :update_custom_info)
|
||||
define(:update_type, action: :update_type)
|
||||
define(:update_wormhole_type, action: :update_wormhole_type)
|
||||
end
|
||||
|
||||
actions do
|
||||
default_accept [
|
||||
:map_id,
|
||||
:solar_system_source,
|
||||
:solar_system_target
|
||||
:solar_system_target,
|
||||
:type
|
||||
]
|
||||
|
||||
defaults [:create, :read, :update, :destroy]
|
||||
@@ -92,6 +95,14 @@ defmodule WandererApp.Api.MapConnection do
|
||||
update :update_custom_info do
|
||||
accept [:custom_info]
|
||||
end
|
||||
|
||||
update :update_type do
|
||||
accept [:type]
|
||||
end
|
||||
|
||||
update :update_wormhole_type do
|
||||
accept [:wormhole_type]
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
@@ -126,6 +137,14 @@ defmodule WandererApp.Api.MapConnection do
|
||||
allow_nil?(true)
|
||||
end
|
||||
|
||||
# where 0 - Wormhole
|
||||
# where 1 - Gate
|
||||
attribute :type, :integer do
|
||||
default(0)
|
||||
|
||||
allow_nil?(true)
|
||||
end
|
||||
|
||||
attribute :wormhole_type, :string
|
||||
|
||||
attribute :count_of_passage, :integer do
|
||||
|
||||
@@ -12,6 +12,7 @@ defmodule WandererApp.Api.MapSystem do
|
||||
|
||||
code_interface do
|
||||
define(:create, action: :create)
|
||||
define(:destroy, action: :destroy)
|
||||
|
||||
define(:by_id,
|
||||
get_by: [:id],
|
||||
|
||||
@@ -16,6 +16,7 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
define(:update, action: :update)
|
||||
define(:update_linked_system, action: :update_linked_system)
|
||||
define(:update_type, action: :update_type)
|
||||
define(:update_group, action: :update_group)
|
||||
|
||||
define(:by_id,
|
||||
get_by: [:id],
|
||||
@@ -71,7 +72,8 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
:description,
|
||||
:kind,
|
||||
:group,
|
||||
:type
|
||||
:type,
|
||||
:updated
|
||||
]
|
||||
|
||||
primary? true
|
||||
@@ -86,6 +88,10 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
accept [:type]
|
||||
end
|
||||
|
||||
update :update_group do
|
||||
accept [:group]
|
||||
end
|
||||
|
||||
read :by_system_id do
|
||||
argument(:system_id, :string, allow_nil?: false)
|
||||
|
||||
@@ -123,6 +129,8 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
attribute :kind, :string
|
||||
attribute :group, :string
|
||||
|
||||
attribute :updated, :integer
|
||||
|
||||
create_timestamp(:inserted_at)
|
||||
update_timestamp(:updated_at)
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -37,32 +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"
|
||||
}
|
||||
)
|
||||
end
|
||||
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()
|
||||
@@ -184,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)
|
||||
|
||||
@@ -200,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,
|
||||
@@ -210,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: %{
|
||||
@@ -239,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
|
||||
@@ -308,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
|
||||
@@ -385,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()
|
||||
)
|
||||
@@ -394,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()
|
||||
)
|
||||
@@ -416,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
|
||||
@@ -437,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()),
|
||||
@@ -448,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}} ->
|
||||
@@ -487,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] ->
|
||||
@@ -525,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)
|
||||
@@ -600,7 +599,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
end
|
||||
end
|
||||
|
||||
defp _map_route_info(
|
||||
defp map_route_info(
|
||||
%{
|
||||
"origin" => origin,
|
||||
"destination" => destination,
|
||||
@@ -609,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
|
||||
@@ -638,5 +637,5 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
}
|
||||
end
|
||||
|
||||
defp _map_route_info(_), do: nil
|
||||
defp map_route_info(_), do: nil
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -214,7 +216,7 @@ defmodule WandererApp.Map do
|
||||
%{visible: true} = system ->
|
||||
system
|
||||
|
||||
_ ->
|
||||
_system ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -262,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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -183,12 +183,24 @@ 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
|
||||
|> map_pid!
|
||||
|> GenServer.cast({&Impl.update_connection_time_status/2, [connection_info]})
|
||||
|
||||
def update_connection_type(map_id, connection_info) when is_binary(map_id),
|
||||
do:
|
||||
map_id
|
||||
|> map_pid!
|
||||
|> GenServer.cast({&Impl.update_connection_type/2, [connection_info]})
|
||||
|
||||
def update_connection_mass_status(map_id, connection_info) when is_binary(map_id),
|
||||
do:
|
||||
map_id
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -237,7 +237,7 @@ defmodule WandererApp.Map.SubscriptionManager do
|
||||
defp _renew_subscription(%{auto_renew?: true} = subscription) when is_map(subscription) do
|
||||
with {:ok, %{map: map}} <-
|
||||
subscription |> WandererApp.MapSubscriptionRepo.load_relationships([:map]),
|
||||
{:ok, estimated_price} <- estimate_price(subscription, true),
|
||||
{:ok, estimated_price, discount} <- estimate_price(subscription, true),
|
||||
{:ok, map_balance} <- get_balance(map) do
|
||||
case map_balance >= estimated_price do
|
||||
true ->
|
||||
@@ -245,7 +245,7 @@ defmodule WandererApp.Map.SubscriptionManager do
|
||||
WandererApp.MapTransactionRepo.create(%{
|
||||
map_id: map.id,
|
||||
user_id: nil,
|
||||
amount: estimated_price,
|
||||
amount: estimated_price - discount,
|
||||
type: :out
|
||||
})
|
||||
|
||||
@@ -267,7 +267,7 @@ defmodule WandererApp.Map.SubscriptionManager do
|
||||
|
||||
:telemetry.execute([:wanderer_app, :map, :subscription, :renew], %{count: 1}, %{
|
||||
map_id: map.id,
|
||||
amount: estimated_price
|
||||
amount: estimated_price - discount
|
||||
})
|
||||
|
||||
:ok
|
||||
|
||||
512
lib/wanderer_app/map/server/map_server_connections_impl.ex
Normal file
512
lib/wanderer_app/map/server/map_server_connections_impl.ex
Normal file
@@ -0,0 +1,512 @@
|
||||
defmodule WandererApp.Map.Server.ConnectionsImpl do
|
||||
@moduledoc """
|
||||
Holds state for a map and exposes an interface to managing the map instance
|
||||
"""
|
||||
require Logger
|
||||
|
||||
alias WandererApp.Map.Server.Impl
|
||||
|
||||
# @ccp1 -1
|
||||
@c1 1
|
||||
@c2 2
|
||||
@c3 3
|
||||
@c4 4
|
||||
@c5 5
|
||||
@c6 6
|
||||
@hs 7
|
||||
@ls 8
|
||||
@ns 9
|
||||
# @ccp2 10
|
||||
# @ccp3 11
|
||||
@thera 12
|
||||
@c13 13
|
||||
@sentinel 14
|
||||
@baribican 15
|
||||
@vidette 16
|
||||
@conflux 17
|
||||
@redoubt 18
|
||||
@a1 19
|
||||
@a2 20
|
||||
@a3 21
|
||||
@a4 22
|
||||
@a5 23
|
||||
@ccp4 24
|
||||
# @pochven 25
|
||||
# @zarzakh 10100
|
||||
|
||||
@jita 30_000_142
|
||||
|
||||
@wh_space [
|
||||
@c1,
|
||||
@c2,
|
||||
@c3,
|
||||
@c4,
|
||||
@c5,
|
||||
@c6,
|
||||
@c13,
|
||||
@thera,
|
||||
@sentinel,
|
||||
@baribican,
|
||||
@vidette,
|
||||
@conflux,
|
||||
@redoubt
|
||||
]
|
||||
|
||||
@known_space [@hs, @ls, @ns]
|
||||
|
||||
@prohibited_systems [@jita]
|
||||
@prohibited_system_classes [
|
||||
@a1,
|
||||
@a2,
|
||||
@a3,
|
||||
@a4,
|
||||
@a5,
|
||||
@ccp4
|
||||
]
|
||||
|
||||
# this class of systems will guaranty that no one real class will take that place
|
||||
# @unknown 100_100
|
||||
#
|
||||
@connection_time_status_eol 1
|
||||
@connection_auto_eol_hours 21
|
||||
@connection_auto_expire_hours 24
|
||||
@connection_eol_expire_timeout :timer.hours(3) + :timer.minutes(30)
|
||||
|
||||
@connection_type_wormhole 0
|
||||
@connection_type_stargate 1
|
||||
|
||||
def init_eol_cache(map_id, connections_eol_time) do
|
||||
connections_eol_time
|
||||
|> Enum.each(fn {connection_id, connection_eol_time} ->
|
||||
WandererApp.Cache.put(
|
||||
"map_#{map_id}:conn_#{connection_id}:mark_eol_time",
|
||||
connection_eol_time,
|
||||
ttl: @connection_eol_expire_timeout
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
def add_connection(
|
||||
%{map_id: map_id} = state,
|
||||
%{
|
||||
solar_system_source_id: solar_system_source_id,
|
||||
solar_system_target_id: solar_system_target_id,
|
||||
character_id: character_id
|
||||
} = _connection_info
|
||||
) do
|
||||
:ok =
|
||||
maybe_add_connection(
|
||||
map_id,
|
||||
%{solar_system_id: solar_system_target_id},
|
||||
%{
|
||||
solar_system_id: solar_system_source_id
|
||||
},
|
||||
character_id
|
||||
)
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def delete_connection(
|
||||
%{map_id: map_id} = state,
|
||||
%{
|
||||
solar_system_source_id: solar_system_source_id,
|
||||
solar_system_target_id: solar_system_target_id
|
||||
} = _connection_info
|
||||
) do
|
||||
:ok =
|
||||
maybe_remove_connection(map_id, %{solar_system_id: solar_system_target_id}, %{
|
||||
solar_system_id: solar_system_source_id
|
||||
})
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def get_connection_info(
|
||||
%{map_id: map_id} = _state,
|
||||
%{
|
||||
solar_system_source_id: solar_system_source_id,
|
||||
solar_system_target_id: solar_system_target_id
|
||||
} = _connection_info
|
||||
) do
|
||||
WandererApp.Map.find_connection(
|
||||
map_id,
|
||||
solar_system_source_id,
|
||||
solar_system_target_id
|
||||
)
|
||||
|> case do
|
||||
{:ok, %{id: connection_id}} ->
|
||||
connection_mark_eol_time = get_connection_mark_eol_time(map_id, connection_id, nil)
|
||||
{:ok, %{marl_eol_time: connection_mark_eol_time}}
|
||||
|
||||
_ ->
|
||||
{:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def update_connection_time_status(
|
||||
%{map_id: map_id} = state,
|
||||
connection_update
|
||||
),
|
||||
do:
|
||||
update_connection(state, :update_time_status, [:time_status], connection_update, fn
|
||||
%{id: connection_id, time_status: time_status} ->
|
||||
case time_status == @connection_time_status_eol do
|
||||
true ->
|
||||
WandererApp.Cache.put(
|
||||
"map_#{map_id}:conn_#{connection_id}:mark_eol_time",
|
||||
DateTime.utc_now(),
|
||||
ttl: @connection_eol_expire_timeout
|
||||
)
|
||||
|
||||
_ ->
|
||||
WandererApp.Cache.delete("map_#{map_id}:conn_#{connection_id}:mark_eol_time")
|
||||
end
|
||||
end)
|
||||
|
||||
def update_connection_type(
|
||||
state,
|
||||
connection_update
|
||||
),
|
||||
do: update_connection(state, :update_type, [:type], connection_update)
|
||||
|
||||
def update_connection_mass_status(
|
||||
state,
|
||||
connection_update
|
||||
),
|
||||
do: update_connection(state, :update_mass_status, [:mass_status], connection_update)
|
||||
|
||||
def update_connection_ship_size_type(
|
||||
state,
|
||||
connection_update
|
||||
),
|
||||
do: update_connection(state, :update_ship_size_type, [:ship_size_type], connection_update)
|
||||
|
||||
def update_connection_locked(
|
||||
state,
|
||||
connection_update
|
||||
),
|
||||
do: update_connection(state, :update_locked, [:locked], connection_update)
|
||||
|
||||
def update_connection_custom_info(
|
||||
state,
|
||||
connection_update
|
||||
),
|
||||
do: update_connection(state, :update_custom_info, [:custom_info], connection_update)
|
||||
|
||||
def cleanup_connections(%{map_id: map_id} = state) do
|
||||
state =
|
||||
map_id
|
||||
|> WandererApp.Map.list_connections!()
|
||||
|> Enum.filter(fn %{
|
||||
inserted_at: inserted_at,
|
||||
solar_system_source: solar_system_source_id,
|
||||
solar_system_target: solar_system_target_id,
|
||||
type: type
|
||||
} ->
|
||||
type != @connection_type_stargate &&
|
||||
DateTime.diff(DateTime.utc_now(), inserted_at, :hour) >=
|
||||
@connection_auto_eol_hours &&
|
||||
is_connection_valid(
|
||||
:wormholes,
|
||||
solar_system_source_id,
|
||||
solar_system_target_id
|
||||
)
|
||||
end)
|
||||
|> Enum.reduce(state, fn %{
|
||||
solar_system_source: solar_system_source_id,
|
||||
solar_system_target: solar_system_target_id
|
||||
},
|
||||
state ->
|
||||
state
|
||||
|> update_connection_time_status(%{
|
||||
solar_system_source_id: solar_system_source_id,
|
||||
solar_system_target_id: solar_system_target_id,
|
||||
time_status: @connection_time_status_eol
|
||||
})
|
||||
end)
|
||||
|
||||
state =
|
||||
map_id
|
||||
|> WandererApp.Map.list_connections!()
|
||||
|> Enum.filter(fn %{
|
||||
id: connection_id,
|
||||
inserted_at: inserted_at,
|
||||
solar_system_source: solar_system_source_id,
|
||||
solar_system_target: solar_system_target_id,
|
||||
type: type
|
||||
} ->
|
||||
connection_mark_eol_time =
|
||||
get_connection_mark_eol_time(map_id, connection_id)
|
||||
|
||||
reverse_connection =
|
||||
WandererApp.Map.get_connection(
|
||||
map_id,
|
||||
solar_system_target_id,
|
||||
solar_system_source_id
|
||||
)
|
||||
|
||||
is_connection_exist =
|
||||
is_connection_exist(
|
||||
map_id,
|
||||
solar_system_source_id,
|
||||
solar_system_target_id
|
||||
) || not is_nil(reverse_connection)
|
||||
|
||||
is_connection_valid =
|
||||
is_connection_valid(
|
||||
:wormholes,
|
||||
solar_system_source_id,
|
||||
solar_system_target_id
|
||||
)
|
||||
|
||||
not is_connection_exist ||
|
||||
(type != @connection_type_stargate && is_connection_valid &&
|
||||
(DateTime.diff(DateTime.utc_now(), inserted_at, :hour) >=
|
||||
@connection_auto_expire_hours ||
|
||||
DateTime.diff(DateTime.utc_now(), connection_mark_eol_time, :hour) >=
|
||||
@connection_auto_expire_hours - @connection_auto_eol_hours))
|
||||
end)
|
||||
|> Enum.reduce(state, fn %{
|
||||
solar_system_source: solar_system_source_id,
|
||||
solar_system_target: solar_system_target_id
|
||||
},
|
||||
state ->
|
||||
state
|
||||
|> delete_connection(%{
|
||||
solar_system_source_id: solar_system_source_id,
|
||||
solar_system_target_id: solar_system_target_id
|
||||
})
|
||||
end)
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def maybe_add_connection(map_id, location, old_location, character_id)
|
||||
when not is_nil(location) and not is_nil(old_location) and
|
||||
not is_nil(old_location.solar_system_id) and
|
||||
location.solar_system_id != old_location.solar_system_id do
|
||||
character_id
|
||||
|> WandererApp.Character.get_character!()
|
||||
|> case do
|
||||
nil ->
|
||||
:ok
|
||||
|
||||
character ->
|
||||
:telemetry.execute([:wanderer_app, :map, :character, :jump], %{count: 1}, %{})
|
||||
|
||||
{:ok, _} =
|
||||
WandererApp.Api.MapChainPassages.new(%{
|
||||
map_id: map_id,
|
||||
character_id: character_id,
|
||||
ship_type_id: character.ship,
|
||||
ship_name: character.ship_name,
|
||||
solar_system_source_id: old_location.solar_system_id,
|
||||
solar_system_target_id: location.solar_system_id
|
||||
})
|
||||
end
|
||||
|
||||
case WandererApp.Map.check_connection(map_id, location, old_location) do
|
||||
:ok ->
|
||||
connection_type =
|
||||
is_connection_valid(
|
||||
:stargates,
|
||||
old_location.solar_system_id,
|
||||
location.solar_system_id
|
||||
)
|
||||
|> case do
|
||||
true ->
|
||||
@connection_type_stargate
|
||||
|
||||
_ ->
|
||||
@connection_type_wormhole
|
||||
end
|
||||
|
||||
{:ok, connection} =
|
||||
WandererApp.MapConnectionRepo.create(%{
|
||||
map_id: map_id,
|
||||
solar_system_source: old_location.solar_system_id,
|
||||
solar_system_target: location.solar_system_id,
|
||||
type: connection_type
|
||||
})
|
||||
|
||||
WandererApp.Map.add_connection(map_id, connection)
|
||||
|
||||
Impl.broadcast!(map_id, :maybe_select_system, %{
|
||||
character_id: character_id,
|
||||
solar_system_id: location.solar_system_id
|
||||
})
|
||||
|
||||
Impl.broadcast!(map_id, :add_connection, connection)
|
||||
|
||||
Impl.broadcast!(map_id, :maybe_link_signature, %{
|
||||
character_id: character_id,
|
||||
solar_system_source: old_location.solar_system_id,
|
||||
solar_system_target: location.solar_system_id
|
||||
})
|
||||
|
||||
:ok
|
||||
|
||||
{:error, error} ->
|
||||
Logger.debug(fn -> "Failed to add connection: #{inspect(error, pretty: true)}" end)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_add_connection(_map_id, _location, _old_location, _character_id), do: :ok
|
||||
|
||||
def can_add_location(_scope, nil), do: false
|
||||
|
||||
def can_add_location(:all, _solar_system_id), do: true
|
||||
|
||||
def can_add_location(:none, _solar_system_id), do: false
|
||||
|
||||
def can_add_location(scope, solar_system_id) do
|
||||
system_static_info =
|
||||
case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do
|
||||
{:ok, system_static_info} when not is_nil(system_static_info) ->
|
||||
system_static_info
|
||||
|
||||
_ ->
|
||||
%{system_class: nil}
|
||||
end
|
||||
|
||||
case scope do
|
||||
:wormholes ->
|
||||
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
|
||||
not (@prohibited_systems |> Enum.member?(solar_system_id)) and
|
||||
@wh_space |> Enum.member?(system_static_info.system_class)
|
||||
|
||||
:stargates ->
|
||||
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
|
||||
@known_space |> Enum.member?(system_static_info.system_class)
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def is_connection_exist(map_id, from_solar_system_id, to_solar_system_id),
|
||||
do:
|
||||
not is_nil(
|
||||
WandererApp.Map.find_system_by_location(
|
||||
map_id,
|
||||
%{solar_system_id: from_solar_system_id}
|
||||
)
|
||||
) &&
|
||||
not is_nil(
|
||||
WandererApp.Map.find_system_by_location(
|
||||
map_id,
|
||||
%{solar_system_id: to_solar_system_id}
|
||||
)
|
||||
)
|
||||
|
||||
def is_connection_valid(_scope, nil, _to_solar_system_id), do: false
|
||||
|
||||
def is_connection_valid(:all, _from_solar_system_id, _to_solar_system_id), do: true
|
||||
|
||||
def is_connection_valid(:none, _from_solar_system_id, _to_solar_system_id), do: false
|
||||
|
||||
def is_connection_valid(scope, from_solar_system_id, to_solar_system_id) do
|
||||
{:ok, known_jumps} =
|
||||
WandererApp.Api.MapSolarSystemJumps.find(%{
|
||||
before_system_id: from_solar_system_id,
|
||||
current_system_id: to_solar_system_id
|
||||
})
|
||||
|
||||
system_static_info =
|
||||
case WandererApp.CachedInfo.get_system_static_info(to_solar_system_id) do
|
||||
{:ok, system_static_info} when not is_nil(system_static_info) ->
|
||||
system_static_info
|
||||
|
||||
_ ->
|
||||
%{system_class: nil}
|
||||
end
|
||||
|
||||
case scope do
|
||||
:wormholes ->
|
||||
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
|
||||
not (@prohibited_systems |> Enum.member?(to_solar_system_id)) and
|
||||
known_jumps |> Enum.empty?() and to_solar_system_id != @jita and
|
||||
from_solar_system_id != @jita
|
||||
|
||||
:stargates ->
|
||||
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
|
||||
not (known_jumps |> Enum.empty?())
|
||||
end
|
||||
end
|
||||
|
||||
def get_connection_mark_eol_time(map_id, connection_id, default \\ DateTime.utc_now()) do
|
||||
WandererApp.Cache.get("map_#{map_id}:conn_#{connection_id}:mark_eol_time")
|
||||
|> case do
|
||||
nil ->
|
||||
default
|
||||
|
||||
value ->
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_remove_connection(map_id, location, old_location)
|
||||
when not is_nil(location) and not is_nil(old_location) and
|
||||
location.solar_system_id != old_location.solar_system_id do
|
||||
case WandererApp.Map.find_connection(
|
||||
map_id,
|
||||
location.solar_system_id,
|
||||
old_location.solar_system_id
|
||||
) do
|
||||
{:ok, connection} when not is_nil(connection) ->
|
||||
:ok = WandererApp.MapConnectionRepo.destroy(map_id, connection)
|
||||
|
||||
Impl.broadcast!(map_id, :remove_connections, [connection])
|
||||
map_id |> WandererApp.Map.remove_connection(connection)
|
||||
|
||||
_error ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_remove_connection(_map_id, _location, _old_location), do: :ok
|
||||
|
||||
defp update_connection(
|
||||
%{map_id: map_id} = state,
|
||||
update_method,
|
||||
attributes,
|
||||
%{
|
||||
solar_system_source_id: solar_system_source_id,
|
||||
solar_system_target_id: solar_system_target_id
|
||||
} = update,
|
||||
callback_fn \\ nil
|
||||
) do
|
||||
with {:ok, connection} <-
|
||||
WandererApp.Map.find_connection(
|
||||
map_id,
|
||||
solar_system_source_id,
|
||||
solar_system_target_id
|
||||
),
|
||||
{:ok, update_map} <- Impl.get_update_map(update, attributes),
|
||||
{:ok, updated_connection} <-
|
||||
apply(WandererApp.MapConnectionRepo, update_method, [
|
||||
connection,
|
||||
update_map
|
||||
]),
|
||||
:ok <-
|
||||
WandererApp.Map.update_connection(
|
||||
map_id,
|
||||
connection |> Map.merge(update_map)
|
||||
) do
|
||||
if not is_nil(callback_fn) do
|
||||
callback_fn.(updated_connection)
|
||||
end
|
||||
|
||||
Impl.broadcast!(map_id, :update_connection, updated_connection)
|
||||
|
||||
state
|
||||
else
|
||||
{:error, error} ->
|
||||
Logger.error("Failed to update connection: #{inspect(error, pretty: true)}")
|
||||
|
||||
state
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
|
||||
@@ -3,4 +3,23 @@ defmodule WandererApp.MapCharacterSettingsRepo do
|
||||
|
||||
def create(settings),
|
||||
do: WandererApp.Api.MapCharacterSettings.create(settings)
|
||||
|
||||
def get_tracked_by_map_filtered(map_id, character_ids),
|
||||
do:
|
||||
WandererApp.Api.MapCharacterSettings.tracked_by_map_filtered(%{
|
||||
map_id: map_id,
|
||||
character_ids: character_ids
|
||||
})
|
||||
|
||||
def get_all_by_map(map_id),
|
||||
do: WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id})
|
||||
|
||||
def get_tracked_by_map_all(map_id),
|
||||
do: WandererApp.Api.MapCharacterSettings.tracked_by_map_all(%{map_id: map_id})
|
||||
|
||||
def track(settings), do: settings |> WandererApp.Api.MapCharacterSettings.track()
|
||||
def untrack(settings), do: settings |> WandererApp.Api.MapCharacterSettings.untrack()
|
||||
|
||||
def track!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.track!()
|
||||
def untrack!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.untrack!()
|
||||
end
|
||||
|
||||
@@ -27,6 +27,7 @@ defmodule WandererApp.MapConnectionRepo do
|
||||
end
|
||||
end
|
||||
|
||||
def create(connection), do: connection |> WandererApp.Api.MapConnection.create()
|
||||
def create!(connection), do: connection |> WandererApp.Api.MapConnection.create!()
|
||||
|
||||
def destroy(map_id, connection) when not is_nil(connection) do
|
||||
@@ -70,6 +71,11 @@ defmodule WandererApp.MapConnectionRepo do
|
||||
connection
|
||||
|> WandererApp.Api.MapConnection.update_time_status(update)
|
||||
|
||||
def update_type(connection, update),
|
||||
do:
|
||||
connection
|
||||
|> WandererApp.Api.MapConnection.update_type(update)
|
||||
|
||||
def update_mass_status(connection, update),
|
||||
do:
|
||||
connection
|
||||
|
||||
@@ -33,19 +33,26 @@ defmodule WandererApp.MapSystemRepo do
|
||||
{:error, error}
|
||||
end
|
||||
|
||||
def cleanup_labels(%{labels: labels} = system, opts) do
|
||||
def cleanup_labels!(%{labels: labels} = system, opts) do
|
||||
store_custom_labels? =
|
||||
Keyword.get(opts, :store_custom_labels, "false") |> String.to_existing_atom()
|
||||
Keyword.get(opts, :store_custom_labels)
|
||||
|
||||
labels = get_filtered_labels(labels, store_custom_labels?)
|
||||
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_labels!(%{
|
||||
|> update_labels!(%{
|
||||
labels: labels
|
||||
})
|
||||
end
|
||||
|
||||
def cleanup_tags(system) do
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_tag(%{
|
||||
tag: nil
|
||||
})
|
||||
end
|
||||
|
||||
def cleanup_tags!(system) do
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_tag!(%{
|
||||
tag: nil
|
||||
@@ -56,7 +63,7 @@ defmodule WandererApp.MapSystemRepo do
|
||||
labels
|
||||
|> Jason.decode!()
|
||||
|> case do
|
||||
%{"customLabel" => customLabel} = labels when is_binary(customLabel) ->
|
||||
%{"customLabel" => customLabel} when is_binary(customLabel) ->
|
||||
%{"customLabel" => customLabel, "labels" => []}
|
||||
|> Jason.encode!()
|
||||
|
||||
@@ -97,6 +104,11 @@ defmodule WandererApp.MapSystemRepo do
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_labels(update)
|
||||
|
||||
def update_labels!(system, update),
|
||||
do:
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_labels!(update)
|
||||
|
||||
def update_position(system, update),
|
||||
do:
|
||||
system
|
||||
@@ -106,4 +118,14 @@ defmodule WandererApp.MapSystemRepo do
|
||||
do:
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_position!(update)
|
||||
|
||||
def update_visible(system, update),
|
||||
do:
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_visible(update)
|
||||
|
||||
def update_visible!(system, update),
|
||||
do:
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_visible!(update)
|
||||
end
|
||||
|
||||
@@ -146,7 +146,12 @@ defmodule WandererAppWeb.CoreComponents do
|
||||
class="flex flex-col p-4 items-center absolute bottom-16 left-1 gap-2 tooltip tooltip-right"
|
||||
data-tip="server: Tranquility"
|
||||
>
|
||||
<div class={"block w-4 h-4 rounded-full shadow-inner #{if @online, do: " bg-green-500 animate-pulse", else: "bg-red-500"}"}>
|
||||
<div
|
||||
:if={@online}
|
||||
class="absolute block w-4 h-4 rounded-full shadow-inner bg-green-500 animate-ping"
|
||||
>
|
||||
</div>
|
||||
<div class={"block w-4 h-4 rounded-full shadow-inner #{if @online, do: " bg-green-500", else: "bg-red-500"}"}>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
@@ -30,25 +30,35 @@ defmodule WandererAppWeb.Layouts do
|
||||
phx-hook="NewVersionUpdate"
|
||||
phx-update="ignore"
|
||||
data-version={@app_version}
|
||||
class="!z-100 hidden group alert items-center fixed bottom-52 left-2 fade-in-scale text-white !bg-opacity-70 w-10 h-10 hover:w-[250px] hover:h-[70px] rounded p-px overflow-hidden"
|
||||
class="!z-1000 hidden absolute top-0 left-0 w-full h-full group items-center fade-in-scale text-white !bg-opacity-70 rounded p-px overflow-hidden flex items-center"
|
||||
>
|
||||
<div class="group animate-rotate absolute inset-0 h-full w-full rounded-full bg-[conic-gradient(#0ea5e9_20deg,transparent_120deg)] group-hover:bg-[#0ea5e9]" />
|
||||
|
||||
<div class="!bg-black rounded w-9 h-9 hover:m-0 group-hover:w-[246px] group-hover:h-[66px] flex items-center justify-center p-2 relative z-20">
|
||||
<.icon name="hero-bell-alert" class="animate-pulse group-hover:hidden absolute top-2 h-5 w-5" />
|
||||
<div class="opacity-0 group-hover:opacity-100 flex flex-col items-center justify-center w-[250px] h-full">
|
||||
<div class="text-white text-nowrap text-sm">
|
||||
New Version Available
|
||||
</div>
|
||||
<a href="/changelog" target="_blank" class="text-sm link-secondary">What's new?</a>
|
||||
<div class="hs-overlay-backdrop transition duration absolute left-0 top-0 w-full h-full bg-gray-900 bg-opacity-50 dark:bg-opacity-80 dark:bg-neutral-900">
|
||||
</div>
|
||||
<div class="absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] flex items-center">
|
||||
<div class="rounded w-9 h-9 w-[80px] h-[66px] flex items-center justify-center relative z-20">
|
||||
<.icon name="hero-chevron-double-right" class="w-9 h-9 mr-[-40px]" />
|
||||
</div>
|
||||
<div id="refresh-area">
|
||||
<.live_component module={WandererAppWeb.MapRefresh} id="map-refresh" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="invisible group-hover:visible update-button p-button p-component p-button-outlined p-button-sm p-0 px-1 w-[76px]"
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
<div class="rounded h-[66px] flex items-center justify-center relative z-20">
|
||||
<div class=" flex items-center w-[200px] h-full">
|
||||
<.icon name="hero-chevron-double-left" class="w-9 h-9 mr-[20px]" />
|
||||
<div class=" flex flex-col items-center justify-center h-full">
|
||||
<div class="text-white text-nowrap text-sm [text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]">
|
||||
Update Required
|
||||
</div>
|
||||
<a
|
||||
href="/changelog"
|
||||
target="_blank"
|
||||
class="text-sm link-secondary [text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]"
|
||||
>
|
||||
What's new?
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
@@ -12,6 +12,7 @@ defmodule WandererAppWeb.MapPicker do
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def update(
|
||||
%{
|
||||
current_user: current_user,
|
||||
@@ -29,6 +30,7 @@ defmodule WandererAppWeb.MapPicker do
|
||||
end)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div id={@id}>
|
||||
@@ -56,6 +58,7 @@ defmodule WandererAppWeb.MapPicker do
|
||||
"""
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("select", %{"map_slug" => map_slug} = _params, socket) do
|
||||
notify_to(socket.assigns.notify_to, socket.assigns.event_name, map_slug)
|
||||
|
||||
|
||||
197
lib/wanderer_app_web/components/map/map_refresh.ex
Normal file
197
lib/wanderer_app_web/components/map/map_refresh.ex
Normal file
@@ -0,0 +1,197 @@
|
||||
defmodule WandererAppWeb.MapRefresh do
|
||||
use WandererAppWeb, :live_component
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div id="map-refresh" class="socket">
|
||||
<div class="gel center-gel">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c1 r1">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c2 r1">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c3 r1">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c4 r1">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c5 r1">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c6 r1">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
|
||||
<div class="gel c7 r2">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
|
||||
<div class="gel c8 r2">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c9 r2">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c10 r2">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c11 r2">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c12 r2">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c13 r2">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c14 r2">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c15 r2">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c16 r2">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c17 r2">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c18 r2">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c19 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c20 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c21 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c22 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c23 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c24 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c25 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c26 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c28 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c29 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c30 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c31 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c32 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c33 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c34 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c35 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c36 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
<div class="gel c37 r3">
|
||||
<div class="hex-brick h1"></div>
|
||||
<div class="hex-brick h2"></div>
|
||||
<div class="hex-brick h3"></div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,5 @@
|
||||
defmodule WandererAppWeb.AccessListsLive do
|
||||
alias Pathex.Builder.Viewer
|
||||
use WandererAppWeb, :live_view
|
||||
|
||||
require Logger
|
||||
@@ -89,7 +90,10 @@ defmodule WandererAppWeb.AccessListsLive do
|
||||
|> assign(:page_title, "Access Lists - Members")
|
||||
|> assign(:selected_acl_id, acl_id)
|
||||
|> assign(:access_list, access_list)
|
||||
|> assign(:members, members)
|
||||
|> assign(
|
||||
:members,
|
||||
members
|
||||
)
|
||||
else
|
||||
_ ->
|
||||
socket
|
||||
@@ -145,7 +149,7 @@ defmodule WandererAppWeb.AccessListsLive do
|
||||
:send_after,
|
||||
[self(), {:search, text}, 100],
|
||||
"member_search_#{socket.assigns.selected_acl_id}",
|
||||
500
|
||||
250
|
||||
)
|
||||
|
||||
[%{label: "Loading...", value: :loading, disabled: true}]
|
||||
@@ -288,7 +292,11 @@ defmodule WandererAppWeb.AccessListsLive do
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("dropped", %{"draggedId" => dragged_id, "dropzoneId" => dropzone_id}, socket) do
|
||||
def handle_event(
|
||||
"dropped",
|
||||
%{"draggedId" => dragged_id, "dropzoneId" => dropzone_id},
|
||||
%{assigns: %{access_list: access_list, members: members}} = socket
|
||||
) do
|
||||
role_atom =
|
||||
[:admin, :manager, :member, :viewer, :blocked]
|
||||
|> Enum.find(fn role_atom -> to_string(role_atom) == dropzone_id end)
|
||||
@@ -299,13 +307,27 @@ defmodule WandererAppWeb.AccessListsLive do
|
||||
|
||||
role_atom ->
|
||||
member =
|
||||
socket.assigns.members
|
||||
members
|
||||
|> Enum.find(&(&1.id == dragged_id))
|
||||
|
||||
{:noreply, socket |> maybe_update_role(member, role_atom, socket.assigns.access_list)}
|
||||
{:noreply, socket |> maybe_update_role(member, role_atom, access_list)}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(
|
||||
{"update_role", %{member_id: member_id, role: role}},
|
||||
%{assigns: %{access_list: access_list, members: members}} = socket
|
||||
) do
|
||||
role_atom = role |> String.to_existing_atom()
|
||||
|
||||
member =
|
||||
members
|
||||
|> Enum.find(&(&1.id == member_id))
|
||||
|
||||
{:noreply, socket |> maybe_update_role(member, role_atom, access_list)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("noop", _, socket) do
|
||||
{:noreply, socket}
|
||||
@@ -325,10 +347,33 @@ defmodule WandererAppWeb.AccessListsLive do
|
||||
|> Enum.map(& &1.id)
|
||||
|> Enum.at(0)
|
||||
|
||||
{:ok, options} = search(active_character_id, text)
|
||||
uniq_search_req_id = UUID.uuid4(:default)
|
||||
|
||||
send_update(LiveSelect.Component, options: options, id: socket.assigns.member_search_id)
|
||||
{:noreply, socket |> assign(member_search_options: options)}
|
||||
Task.async(fn ->
|
||||
{:ok, options} = search(active_character_id, text)
|
||||
|
||||
{:search_results, uniq_search_req_id, options}
|
||||
end)
|
||||
|
||||
{:noreply, socket |> assign(uniq_search_req_id: uniq_search_req_id)}
|
||||
end
|
||||
|
||||
def handle_info(
|
||||
{ref, result},
|
||||
%{assigns: %{member_search_id: member_search_id, uniq_search_req_id: uniq_search_req_id}} =
|
||||
socket
|
||||
)
|
||||
when is_reference(ref) do
|
||||
Process.demonitor(ref, [:flush])
|
||||
|
||||
case result do
|
||||
{:search_results, ^uniq_search_req_id, options} ->
|
||||
send_update(LiveSelect.Component, options: options, id: member_search_id)
|
||||
{:noreply, socket |> assign(member_search_options: options)}
|
||||
|
||||
_ ->
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
@@ -403,6 +448,7 @@ defmodule WandererAppWeb.AccessListsLive do
|
||||
_ ->
|
||||
socket
|
||||
|> put_flash(:error, "You're not allowed to assign this role")
|
||||
|> push_navigate(to: ~p"/access-lists/#{socket.assigns.selected_acl_id}")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -411,10 +457,11 @@ defmodule WandererAppWeb.AccessListsLive do
|
||||
_member,
|
||||
_role_atom,
|
||||
_access_list
|
||||
) do
|
||||
socket
|
||||
|> put_flash(:info, "Only Characters can have Admin or Manager roles")
|
||||
end
|
||||
),
|
||||
do:
|
||||
socket
|
||||
|> put_flash(:info, "Only Characters can have Admin or Manager roles")
|
||||
|> push_navigate(to: ~p"/access-lists/#{socket.assigns.selected_acl_id}")
|
||||
|
||||
defp characters_has_role?(character_eve_ids, access_list, role_atom) do
|
||||
access_list.members
|
||||
@@ -614,27 +661,6 @@ defmodule WandererAppWeb.AccessListsLive do
|
||||
"""
|
||||
end
|
||||
|
||||
def member_item(assigns) do
|
||||
~H"""
|
||||
<div class="flex items-center gap-2">
|
||||
<.icon :if={not is_nil(@member.role)} name={member_role_icon(@member.role)} class="w-6 h-6" />
|
||||
<div class="avatar">
|
||||
<div class="rounded-md w-8 h-8">
|
||||
<img src={member_icon_url(@member)} alt={@member.name} />
|
||||
</div>
|
||||
</div>
|
||||
<%= @member.name %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def member_role_icon(:admin), do: "hero-user-group-solid"
|
||||
def member_role_icon(:manager), do: "hero-academic-cap-solid"
|
||||
def member_role_icon(:member), do: "hero-user-solid"
|
||||
def member_role_icon(:viewer), do: "hero-eye-solid"
|
||||
def member_role_icon(:blocked), do: "hero-no-symbol-solid text-red-500"
|
||||
def member_role_icon(_), do: "hero-cake-solid"
|
||||
|
||||
def search_member_icon_url(%{character: true} = option),
|
||||
do: member_icon_url(%{eve_character_id: option.value})
|
||||
|
||||
|
||||
@@ -82,14 +82,20 @@
|
||||
id="acl_members"
|
||||
>
|
||||
<div
|
||||
:for={member <- @members |> Enum.sort(&(&1.name < &2.name))}
|
||||
:for={member <- @members |> Enum.sort_by(&{&1.role, &1.name}, &<=/2)}
|
||||
draggable="true"
|
||||
id={member.id}
|
||||
class="draggable !p-1 h-10 cursor-move bg-black bg-opacity-25 hover:text-white"
|
||||
data-dropzone="pool"
|
||||
>
|
||||
<div class="flex justify-between relative">
|
||||
<.member_item member={member} />
|
||||
<.live_component
|
||||
module={WandererAppWeb.AclMember}
|
||||
id={"select_role_" <> member.id}
|
||||
notify_to={self()}
|
||||
member={member}
|
||||
event_name="update_role"
|
||||
/>
|
||||
<button
|
||||
:if={can_delete_member?(member, @access_list, @current_user)}
|
||||
class="z-10 absolute top-0 right-2"
|
||||
@@ -150,6 +156,71 @@
|
||||
show
|
||||
on_cancel={JS.patch(~p"/access-lists/#{@selected_acl_id}")}
|
||||
>
|
||||
<%!-- <div class="mt-4 mb-2 p-tabmenu p-component " data-pc-section="tabmenu">
|
||||
<ul
|
||||
class="p-tabmenu-nav border-none h-[25px] w-full flex"
|
||||
role="menubar"
|
||||
data-pc-section="menu"
|
||||
>
|
||||
<li
|
||||
id="pr_id_17_0"
|
||||
class="p-tabmenuitem p-highlight"
|
||||
role="presentation"
|
||||
data-p-highlight="true"
|
||||
data-p-disabled="false"
|
||||
data-pc-section="menuitem"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
role="menuitem"
|
||||
aria-label="Router Link"
|
||||
tabindex="0"
|
||||
class="p-menuitem-link"
|
||||
data-pc-section="action"
|
||||
>
|
||||
<span class="p-menuitem-text" data-pc-section="label">Character</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
id="pr_id_17_1"
|
||||
class="p-tabmenuitem"
|
||||
role="presentation"
|
||||
data-p-highlight="false"
|
||||
data-p-disabled="false"
|
||||
data-pc-section="menuitem"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
role="menuitem"
|
||||
aria-label="Programmatic"
|
||||
tabindex="-1"
|
||||
class="p-menuitem-link"
|
||||
data-pc-section="action"
|
||||
>
|
||||
<span class="p-menuitem-text" data-pc-section="label">Corporation</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
id="pr_id_17_2"
|
||||
class="p-tabmenuitem"
|
||||
role="presentation"
|
||||
data-p-highlight="false"
|
||||
data-p-disabled="false"
|
||||
data-pc-section="menuitem"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
role="menuitem"
|
||||
aria-label="External"
|
||||
tabindex="-1"
|
||||
class="p-menuitem-link"
|
||||
data-pc-section="action"
|
||||
>
|
||||
<span class="p-menuitem-text" data-pc-section="label">Alliance</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div> --%>
|
||||
<.form :let={f} for={@member_form} phx-submit={@live_action}>
|
||||
<.live_select
|
||||
field={f[:member_id]}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
defmodule WandererAppWeb.AclMember do
|
||||
use WandererAppWeb, :live_component
|
||||
|
||||
use LiveViewEvents
|
||||
|
||||
@roles [
|
||||
:admin,
|
||||
:manager,
|
||||
:member,
|
||||
:viewer,
|
||||
:blocked
|
||||
]
|
||||
|
||||
@impl true
|
||||
def mount(socket) do
|
||||
{:ok, socket |> assign(roles: get_roles())}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def update(
|
||||
%{
|
||||
member: member
|
||||
} = assigns,
|
||||
socket
|
||||
) do
|
||||
socket = handle_info_or_assign(socket, assigns)
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(member: member, form: to_form(%{"role" => member.role}))}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div id={@id} class="flex items-center gap-2">
|
||||
<.icon :if={not is_nil(@member.role)} name={member_role_icon(@member.role)} class="w-6 h-6" />
|
||||
<.form :let={f} id={"role_form_" <> @id} for={@form} phx-change="select" phx-target={@myself}>
|
||||
<.input
|
||||
type="select"
|
||||
field={f[:role]}
|
||||
class="select h-8 min-h-[0px] !pt-1 !pb-1 text-sm bg-neutral-900 w-[70px]"
|
||||
placeholder="Select a role..."
|
||||
options={Enum.map(@roles, fn role -> {role.label, role.value} end)}
|
||||
/>
|
||||
</.form>
|
||||
<div class="avatar">
|
||||
<div class="rounded-md w-8 h-8">
|
||||
<img src={member_icon_url(@member)} alt={@member.name} />
|
||||
</div>
|
||||
</div>
|
||||
<%= @member.name %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event(
|
||||
"select",
|
||||
%{"role" => role} = _params,
|
||||
%{assigns: %{event_name: event_name, member: member, notify_to: notify_to}} = socket
|
||||
) do
|
||||
notify_to(notify_to, event_name, %{
|
||||
member_id: member.id,
|
||||
role: role
|
||||
})
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def member_role_icon(:admin), do: "hero-user-group-solid"
|
||||
def member_role_icon(:manager), do: "hero-academic-cap-solid"
|
||||
def member_role_icon(:member), do: "hero-user-solid"
|
||||
def member_role_icon(:viewer), do: "hero-eye-solid"
|
||||
def member_role_icon(:blocked), do: "hero-no-symbol-solid text-red-500"
|
||||
def member_role_icon(_), do: "hero-cake-solid"
|
||||
|
||||
def member_role_title(:admin), do: "Admin"
|
||||
def member_role_title(:manager), do: "Manager"
|
||||
def member_role_title(:member), do: "Member"
|
||||
def member_role_title(:viewer), do: "Viewer"
|
||||
def member_role_title(:blocked), do: "-blocked-"
|
||||
def member_role_title(_), do: "-"
|
||||
|
||||
defp get_roles(), do: @roles |> Enum.map(&%{label: member_role_title(&1), value: &1})
|
||||
end
|
||||
@@ -41,7 +41,7 @@ defmodule WandererAppWeb.CharactersTrackingLive do
|
||||
selected_map = socket.assigns.maps |> Enum.find(&(&1.slug == map_slug))
|
||||
|
||||
{:ok, character_settings} =
|
||||
case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: selected_map.id}) do
|
||||
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(selected_map.id) do
|
||||
{:ok, settings} ->
|
||||
{:ok, settings}
|
||||
|
||||
@@ -83,7 +83,7 @@ defmodule WandererAppWeb.CharactersTrackingLive do
|
||||
|
||||
case character_settings |> Enum.find(&(&1.character_id == character_id)) do
|
||||
nil ->
|
||||
WandererApp.Api.MapCharacterSettings.create(%{
|
||||
WandererApp.MapCharacterSettingsRepo.create(%{
|
||||
character_id: character_id,
|
||||
map_id: selected_map.id,
|
||||
tracked: true
|
||||
@@ -95,18 +95,18 @@ defmodule WandererAppWeb.CharactersTrackingLive do
|
||||
case character_setting.tracked do
|
||||
true ->
|
||||
character_setting
|
||||
|> WandererApp.Api.MapCharacterSettings.untrack!()
|
||||
|> WandererApp.MapCharacterSettingsRepo.untrack!()
|
||||
|
||||
_ ->
|
||||
character_setting
|
||||
|> WandererApp.Api.MapCharacterSettings.track!()
|
||||
|> WandererApp.MapCharacterSettingsRepo.track!()
|
||||
end
|
||||
end
|
||||
|
||||
%{result: characters} = socket.assigns.characters
|
||||
|
||||
{:ok, character_settings} =
|
||||
case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: selected_map.id}) do
|
||||
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(selected_map.id) do
|
||||
{:ok, settings} -> {:ok, settings}
|
||||
_ -> {:ok, []}
|
||||
end
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
defmodule WandererAppWeb.MapActivityEventHandler do
|
||||
use WandererAppWeb, :live_component
|
||||
use Phoenix.Component
|
||||
require Logger
|
||||
|
||||
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler}
|
||||
|
||||
def handle_server_event(
|
||||
%{
|
||||
event: :character_activity,
|
||||
payload: character_activity
|
||||
},
|
||||
socket
|
||||
),
|
||||
do: socket |> assign(:character_activity, character_activity)
|
||||
|
||||
def handle_server_event(event, socket),
|
||||
do: MapCoreEventHandler.handle_server_event(event, socket)
|
||||
|
||||
def handle_ui_event("show_activity", _, %{assigns: %{map_id: map_id}} = socket) do
|
||||
Task.async(fn ->
|
||||
{:ok, character_activity} = map_id |> get_character_activity()
|
||||
|
||||
{:character_activity, character_activity}
|
||||
end)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:show_activity?, true)}
|
||||
end
|
||||
|
||||
def handle_ui_event("hide_activity", _, socket),
|
||||
do: {:noreply, socket |> assign(show_activity?: false)}
|
||||
|
||||
def handle_ui_event(event, body, socket),
|
||||
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
|
||||
|
||||
defp get_character_activity(map_id) do
|
||||
{:ok, jumps} = WandererApp.Api.MapChainPassages.by_map_id(%{map_id: map_id})
|
||||
|
||||
jumps =
|
||||
jumps
|
||||
|> Enum.map(fn p ->
|
||||
%{p | character: p.character |> MapEventHandler.map_ui_character_stat()}
|
||||
end)
|
||||
|
||||
{:ok, %{jumps: jumps}}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,388 @@
|
||||
defmodule WandererAppWeb.MapCharactersEventHandler do
|
||||
use WandererAppWeb, :live_component
|
||||
use Phoenix.Component
|
||||
require Logger
|
||||
|
||||
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler}
|
||||
|
||||
def handle_server_event(%{event: :character_added, payload: character}, socket) do
|
||||
socket
|
||||
|> MapEventHandler.push_map_event(
|
||||
"character_added",
|
||||
character |> map_ui_character()
|
||||
)
|
||||
end
|
||||
|
||||
def handle_server_event(%{event: :character_removed, payload: character}, socket) do
|
||||
socket
|
||||
|> MapEventHandler.push_map_event(
|
||||
"character_removed",
|
||||
character |> map_ui_character()
|
||||
)
|
||||
end
|
||||
|
||||
def handle_server_event(%{event: :character_updated, payload: character}, socket) do
|
||||
socket
|
||||
|> MapEventHandler.push_map_event(
|
||||
"character_updated",
|
||||
character |> map_ui_character()
|
||||
)
|
||||
end
|
||||
|
||||
def handle_server_event(
|
||||
%{event: :characters_updated},
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id
|
||||
}
|
||||
} = socket
|
||||
) do
|
||||
characters =
|
||||
map_id
|
||||
|> WandererApp.Map.list_characters()
|
||||
|> Enum.map(&map_ui_character/1)
|
||||
|
||||
socket
|
||||
|> MapEventHandler.push_map_event(
|
||||
"characters_updated",
|
||||
characters
|
||||
)
|
||||
end
|
||||
|
||||
def handle_server_event(
|
||||
%{event: :present_characters_updated, payload: present_character_eve_ids},
|
||||
socket
|
||||
),
|
||||
do:
|
||||
socket
|
||||
|> MapEventHandler.push_map_event(
|
||||
"present_characters",
|
||||
present_character_eve_ids
|
||||
)
|
||||
|
||||
def handle_server_event(event, socket),
|
||||
do: MapCoreEventHandler.handle_server_event(event, socket)
|
||||
|
||||
def handle_ui_event(
|
||||
"add_character",
|
||||
_,
|
||||
%{
|
||||
assigns: %{
|
||||
current_user: current_user,
|
||||
map_id: map_id,
|
||||
user_permissions: %{track_character: true}
|
||||
}
|
||||
} = socket
|
||||
) do
|
||||
{:ok, character_settings} =
|
||||
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) do
|
||||
{:ok, settings} -> {:ok, settings}
|
||||
_ -> {:ok, []}
|
||||
end
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(
|
||||
show_tracking?: true,
|
||||
character_settings: character_settings
|
||||
)
|
||||
|> assign_async(:characters, fn ->
|
||||
{:ok, map} =
|
||||
map_id
|
||||
|> WandererApp.MapRepo.get([:acls])
|
||||
|
||||
map
|
||||
|> WandererApp.Maps.load_characters(
|
||||
character_settings,
|
||||
current_user.id
|
||||
)
|
||||
end)}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"add_character",
|
||||
_,
|
||||
%{
|
||||
assigns: %{
|
||||
user_permissions: %{track_character: false}
|
||||
}
|
||||
} = socket
|
||||
),
|
||||
do:
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(
|
||||
:error,
|
||||
"You don't have permissions to track characters. Please contact administrator."
|
||||
)}
|
||||
|
||||
def handle_ui_event(
|
||||
"toggle_track",
|
||||
%{"character-id" => character_id},
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
character_settings: character_settings,
|
||||
current_user: current_user,
|
||||
only_tracked_characters: only_tracked_characters
|
||||
}
|
||||
} = socket
|
||||
) do
|
||||
socket =
|
||||
case character_settings |> Enum.find(&(&1.character_id == character_id)) do
|
||||
nil ->
|
||||
{:ok, map_character_settings} =
|
||||
WandererApp.MapCharacterSettingsRepo.create(%{
|
||||
character_id: character_id,
|
||||
map_id: map_id,
|
||||
tracked: true
|
||||
})
|
||||
|
||||
character = map_character_settings |> Ash.load!(:character) |> Map.get(:character)
|
||||
|
||||
:ok = track_characters([character], map_id, true)
|
||||
:ok = add_characters([character], map_id, true)
|
||||
|
||||
socket
|
||||
|
||||
character_setting ->
|
||||
case character_setting.tracked do
|
||||
true ->
|
||||
{:ok, map_character_settings} =
|
||||
character_setting
|
||||
|> WandererApp.MapCharacterSettingsRepo.untrack()
|
||||
|
||||
character = map_character_settings |> Ash.load!(:character) |> Map.get(:character)
|
||||
|
||||
:ok = untrack_characters([character], map_id)
|
||||
:ok = remove_characters([character], map_id)
|
||||
|
||||
if only_tracked_characters do
|
||||
Process.send_after(self(), :not_all_characters_tracked, 10)
|
||||
end
|
||||
|
||||
socket
|
||||
|
||||
_ ->
|
||||
{:ok, map_character_settings} =
|
||||
character_setting
|
||||
|> WandererApp.MapCharacterSettingsRepo.track()
|
||||
|
||||
character = map_character_settings |> Ash.load!(:character) |> Map.get(:character)
|
||||
|
||||
:ok = track_characters([character], map_id, true)
|
||||
:ok = add_characters([character], map_id, true)
|
||||
|
||||
socket
|
||||
end
|
||||
end
|
||||
|
||||
%{result: characters} = socket.assigns.characters
|
||||
|
||||
{:ok, map_characters} = get_tracked_map_characters(map_id, current_user)
|
||||
|
||||
user_character_eve_ids = map_characters |> Enum.map(& &1.eve_id)
|
||||
|
||||
{:ok, character_settings} =
|
||||
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) do
|
||||
{:ok, settings} -> {:ok, settings}
|
||||
_ -> {:ok, []}
|
||||
end
|
||||
|
||||
characters =
|
||||
characters
|
||||
|> Enum.map(fn c ->
|
||||
WandererApp.Maps.map_character(
|
||||
c,
|
||||
character_settings |> Enum.find(&(&1.character_id == c.id))
|
||||
)
|
||||
end)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(user_characters: user_character_eve_ids)
|
||||
|> assign(has_tracked_characters?: has_tracked_characters?(user_character_eve_ids))
|
||||
|> assign(character_settings: character_settings)
|
||||
|> assign_async(:characters, fn ->
|
||||
{:ok, %{characters: characters}}
|
||||
end)
|
||||
|> MapEventHandler.push_map_event(
|
||||
"init",
|
||||
%{
|
||||
user_characters: user_character_eve_ids,
|
||||
reset: false
|
||||
}
|
||||
)}
|
||||
end
|
||||
|
||||
def handle_ui_event("hide_tracking", _, socket),
|
||||
do: {:noreply, socket |> assign(show_tracking?: false)}
|
||||
|
||||
def handle_ui_event(event, body, socket),
|
||||
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
|
||||
|
||||
def has_tracked_characters?([]), do: false
|
||||
def has_tracked_characters?(_user_characters), do: true
|
||||
|
||||
def get_tracked_map_characters(map_id, current_user) do
|
||||
case WandererApp.MapCharacterSettingsRepo.get_tracked_by_map_filtered(
|
||||
map_id,
|
||||
current_user.characters |> Enum.map(& &1.id)
|
||||
) do
|
||||
{:ok, settings} ->
|
||||
{:ok,
|
||||
settings
|
||||
|> Enum.map(fn s -> s |> Ash.load!(:character) |> Map.get(:character) end)}
|
||||
|
||||
_ ->
|
||||
{:ok, []}
|
||||
end
|
||||
end
|
||||
|
||||
def map_ui_character(character),
|
||||
do:
|
||||
character
|
||||
|> Map.take([
|
||||
:eve_id,
|
||||
:name,
|
||||
:online,
|
||||
:corporation_id,
|
||||
:corporation_name,
|
||||
:corporation_ticker,
|
||||
:alliance_id,
|
||||
:alliance_name,
|
||||
:alliance_ticker
|
||||
])
|
||||
|> Map.put_new(:ship, WandererApp.Character.get_ship(character))
|
||||
|> Map.put_new(:location, get_location(character))
|
||||
|
||||
def add_characters([], _map_id, _track_character), do: :ok
|
||||
|
||||
def add_characters([character | characters], map_id, track_character) do
|
||||
map_id
|
||||
|> WandererApp.Map.Server.add_character(character, track_character)
|
||||
|
||||
add_characters(characters, map_id, track_character)
|
||||
end
|
||||
|
||||
def remove_characters([], _map_id), do: :ok
|
||||
|
||||
def remove_characters([character | characters], map_id) do
|
||||
map_id
|
||||
|> WandererApp.Map.Server.remove_character(character.id)
|
||||
|
||||
remove_characters(characters, map_id)
|
||||
end
|
||||
|
||||
def untrack_characters(characters, map_id) do
|
||||
characters
|
||||
|> Enum.each(fn character ->
|
||||
WandererAppWeb.Presence.untrack(self(), map_id, character.id)
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"#{inspect(self())}_map_#{map_id}:character_#{character.id}:tracked",
|
||||
false
|
||||
)
|
||||
|
||||
:ok =
|
||||
Phoenix.PubSub.unsubscribe(
|
||||
WandererApp.PubSub,
|
||||
"character:#{character.eve_id}"
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
def track_characters(_, _, false), do: :ok
|
||||
|
||||
def track_characters([], _map_id, _is_track_character?), do: :ok
|
||||
|
||||
def track_characters(
|
||||
[character | characters],
|
||||
map_id,
|
||||
true
|
||||
) do
|
||||
track_character(character, map_id)
|
||||
|
||||
track_characters(characters, map_id, true)
|
||||
end
|
||||
|
||||
def track_character(
|
||||
%{
|
||||
id: character_id,
|
||||
eve_id: eve_id,
|
||||
corporation_id: corporation_id,
|
||||
alliance_id: alliance_id
|
||||
},
|
||||
map_id
|
||||
) do
|
||||
WandererAppWeb.Presence.track(self(), map_id, character_id, %{})
|
||||
|
||||
case WandererApp.Cache.lookup!(
|
||||
"#{inspect(self())}_map_#{map_id}:character_#{character_id}:tracked",
|
||||
false
|
||||
) do
|
||||
true ->
|
||||
:ok
|
||||
|
||||
_ ->
|
||||
:ok =
|
||||
Phoenix.PubSub.subscribe(
|
||||
WandererApp.PubSub,
|
||||
"character:#{eve_id}"
|
||||
)
|
||||
|
||||
:ok =
|
||||
WandererApp.Cache.put(
|
||||
"#{inspect(self())}_map_#{map_id}:character_#{character_id}:tracked",
|
||||
true
|
||||
)
|
||||
end
|
||||
|
||||
case WandererApp.Cache.lookup(
|
||||
"#{inspect(self())}_map_#{map_id}:corporation_#{corporation_id}:tracked",
|
||||
false
|
||||
) do
|
||||
{:ok, true} ->
|
||||
:ok
|
||||
|
||||
{:ok, false} ->
|
||||
:ok =
|
||||
Phoenix.PubSub.subscribe(
|
||||
WandererApp.PubSub,
|
||||
"corporation:#{corporation_id}"
|
||||
)
|
||||
|
||||
:ok =
|
||||
WandererApp.Cache.put(
|
||||
"#{inspect(self())}_map_#{map_id}:corporation_#{corporation_id}:tracked",
|
||||
true
|
||||
)
|
||||
end
|
||||
|
||||
case WandererApp.Cache.lookup(
|
||||
"#{inspect(self())}_map_#{map_id}:alliance_#{alliance_id}:tracked",
|
||||
false
|
||||
) do
|
||||
{:ok, true} ->
|
||||
:ok
|
||||
|
||||
{:ok, false} ->
|
||||
:ok =
|
||||
Phoenix.PubSub.subscribe(
|
||||
WandererApp.PubSub,
|
||||
"alliance:#{alliance_id}"
|
||||
)
|
||||
|
||||
:ok =
|
||||
WandererApp.Cache.put(
|
||||
"#{inspect(self())}_map_#{map_id}:alliance_#{alliance_id}:tracked",
|
||||
true
|
||||
)
|
||||
end
|
||||
|
||||
:ok = WandererApp.Character.TrackerManager.start_tracking(character_id)
|
||||
end
|
||||
|
||||
defp get_location(character),
|
||||
do: %{solar_system_id: character.solar_system_id, structure_id: character.structure_id}
|
||||
end
|
||||
@@ -0,0 +1,225 @@
|
||||
defmodule WandererAppWeb.MapConnectionsEventHandler do
|
||||
use WandererAppWeb, :live_component
|
||||
use Phoenix.Component
|
||||
require Logger
|
||||
|
||||
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler}
|
||||
|
||||
def handle_server_event(%{event: :update_connection, payload: connection}, socket),
|
||||
do:
|
||||
socket
|
||||
|> MapEventHandler.push_map_event(
|
||||
"update_connection",
|
||||
MapEventHandler.map_ui_connection(connection)
|
||||
)
|
||||
|
||||
def handle_server_event(%{event: :remove_connections, payload: connections}, socket) do
|
||||
connection_ids =
|
||||
connections |> Enum.map(&MapEventHandler.map_ui_connection/1) |> Enum.map(& &1.id)
|
||||
|
||||
socket
|
||||
|> MapEventHandler.push_map_event(
|
||||
"remove_connections",
|
||||
connection_ids
|
||||
)
|
||||
end
|
||||
|
||||
def handle_server_event(%{event: :add_connection, payload: connection}, socket) do
|
||||
connections = [MapEventHandler.map_ui_connection(connection)]
|
||||
|
||||
socket
|
||||
|> MapEventHandler.push_map_event(
|
||||
"add_connections",
|
||||
connections
|
||||
)
|
||||
end
|
||||
|
||||
def handle_server_event(event, socket),
|
||||
do: MapCoreEventHandler.handle_server_event(event, socket)
|
||||
|
||||
def handle_ui_event(
|
||||
"manual_add_connection",
|
||||
%{"source" => solar_system_source_id, "target" => solar_system_target_id} = _event,
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
current_user: current_user,
|
||||
tracked_character_ids: tracked_character_ids,
|
||||
has_tracked_characters?: true,
|
||||
user_permissions: %{add_connection: true}
|
||||
}
|
||||
} =
|
||||
socket
|
||||
) do
|
||||
map_id
|
||||
|> WandererApp.Map.Server.add_connection(%{
|
||||
solar_system_source_id: solar_system_source_id |> String.to_integer(),
|
||||
solar_system_target_id: solar_system_target_id |> String.to_integer(),
|
||||
character_id: tracked_character_ids |> List.first()
|
||||
})
|
||||
|
||||
{:ok, _} =
|
||||
WandererApp.User.ActivityTracker.track_map_event(:map_connection_added, %{
|
||||
character_id: tracked_character_ids |> List.first(),
|
||||
user_id: current_user.id,
|
||||
map_id: map_id,
|
||||
solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(),
|
||||
solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer()
|
||||
})
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"manual_delete_connection",
|
||||
%{"source" => solar_system_source_id, "target" => solar_system_target_id} = _event,
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
current_user: current_user,
|
||||
tracked_character_ids: tracked_character_ids,
|
||||
has_tracked_characters?: true,
|
||||
user_permissions: %{delete_connection: true}
|
||||
}
|
||||
} =
|
||||
socket
|
||||
) do
|
||||
map_id
|
||||
|> WandererApp.Map.Server.delete_connection(%{
|
||||
solar_system_source_id: solar_system_source_id |> String.to_integer(),
|
||||
solar_system_target_id: solar_system_target_id |> String.to_integer()
|
||||
})
|
||||
|
||||
{:ok, _} =
|
||||
WandererApp.User.ActivityTracker.track_map_event(:map_connection_removed, %{
|
||||
character_id: tracked_character_ids |> List.first(),
|
||||
user_id: current_user.id,
|
||||
map_id: map_id,
|
||||
solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(),
|
||||
solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer()
|
||||
})
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"update_connection_" <> param,
|
||||
%{
|
||||
"source" => solar_system_source_id,
|
||||
"target" => solar_system_target_id,
|
||||
"value" => value
|
||||
} = _event,
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
current_user: current_user,
|
||||
tracked_character_ids: tracked_character_ids,
|
||||
has_tracked_characters?: true,
|
||||
user_permissions: %{update_system: true}
|
||||
}
|
||||
} =
|
||||
socket
|
||||
) do
|
||||
method_atom =
|
||||
case param do
|
||||
"time_status" -> :update_connection_time_status
|
||||
"type" -> :update_connection_type
|
||||
"mass_status" -> :update_connection_mass_status
|
||||
"ship_size_type" -> :update_connection_ship_size_type
|
||||
"locked" -> :update_connection_locked
|
||||
"custom_info" -> :update_connection_custom_info
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
key_atom =
|
||||
case param do
|
||||
"time_status" -> :time_status
|
||||
"type" -> :type
|
||||
"mass_status" -> :mass_status
|
||||
"ship_size_type" -> :ship_size_type
|
||||
"locked" -> :locked
|
||||
"custom_info" -> :custom_info
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
{:ok, _} =
|
||||
WandererApp.User.ActivityTracker.track_map_event(:map_connection_updated, %{
|
||||
character_id: tracked_character_ids |> List.first(),
|
||||
user_id: current_user.id,
|
||||
map_id: map_id,
|
||||
solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(),
|
||||
solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer(),
|
||||
key: key_atom,
|
||||
value: value
|
||||
})
|
||||
|
||||
apply(WandererApp.Map.Server, method_atom, [
|
||||
map_id,
|
||||
%{
|
||||
solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(),
|
||||
solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer()
|
||||
}
|
||||
|> Map.put_new(key_atom, value)
|
||||
])
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"get_connection_info",
|
||||
%{"from" => from, "to" => to} = _event,
|
||||
%{assigns: %{map_id: map_id}} = socket
|
||||
) do
|
||||
{:ok, info} = map_id |> get_connection_info(from, to)
|
||||
|
||||
{:reply, info, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"get_passages",
|
||||
%{"from" => from, "to" => to} = _event,
|
||||
%{assigns: %{map_id: map_id}} = socket
|
||||
) do
|
||||
{:ok, passages} = map_id |> get_connection_passages(from, to)
|
||||
|
||||
{:reply, passages, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(event, body, socket),
|
||||
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
|
||||
|
||||
defp get_connection_passages(map_id, from, to) do
|
||||
{:ok, passages} = WandererApp.MapChainPassagesRepo.by_connection(map_id, from, to)
|
||||
|
||||
passages =
|
||||
passages
|
||||
|> Enum.map(fn p ->
|
||||
%{
|
||||
p
|
||||
| character: p.character |> MapEventHandler.map_ui_character_stat()
|
||||
}
|
||||
|> Map.put_new(
|
||||
:ship,
|
||||
WandererApp.Character.get_ship(%{ship: p.ship_type_id, ship_name: p.ship_name})
|
||||
)
|
||||
|> Map.drop([:ship_type_id, :ship_name])
|
||||
end)
|
||||
|
||||
{:ok, %{passages: passages}}
|
||||
end
|
||||
|
||||
defp get_connection_info(map_id, from, to) do
|
||||
map_id
|
||||
|> WandererApp.Map.Server.get_connection_info(%{
|
||||
solar_system_source_id: "#{from}" |> String.to_integer(),
|
||||
solar_system_target_id: "#{to}" |> String.to_integer()
|
||||
})
|
||||
|> case do
|
||||
{:ok, info} ->
|
||||
{:ok, info}
|
||||
|
||||
_ ->
|
||||
{:ok, %{}}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,544 @@
|
||||
defmodule WandererAppWeb.MapCoreEventHandler do
|
||||
use WandererAppWeb, :live_component
|
||||
use Phoenix.Component
|
||||
require Logger
|
||||
|
||||
alias WandererAppWeb.{MapEventHandler, MapCharactersEventHandler}
|
||||
|
||||
def handle_server_event(:update_permissions, socket) do
|
||||
DebounceAndThrottle.Debounce.apply(
|
||||
Process,
|
||||
:send_after,
|
||||
[self(), :refresh_permissions, 100],
|
||||
"update_permissions_#{inspect(self())}",
|
||||
1000
|
||||
)
|
||||
|
||||
socket
|
||||
end
|
||||
|
||||
def handle_server_event(
|
||||
:refresh_permissions,
|
||||
%{assigns: %{current_user: current_user, map_slug: map_slug}} = socket
|
||||
) do
|
||||
{:ok, %{id: map_id, user_permissions: user_permissions, owner_id: owner_id}} =
|
||||
map_slug
|
||||
|> WandererApp.Api.Map.get_map_by_slug!()
|
||||
|> Ash.load(:user_permissions, actor: current_user)
|
||||
|
||||
user_permissions =
|
||||
WandererApp.Permissions.get_map_permissions(
|
||||
user_permissions,
|
||||
owner_id,
|
||||
current_user.characters |> Enum.map(& &1.id)
|
||||
)
|
||||
|
||||
case user_permissions do
|
||||
%{view_system: false} ->
|
||||
socket
|
||||
|> Phoenix.LiveView.put_flash(:error, "Your access to the map have been revoked.")
|
||||
|> Phoenix.LiveView.push_navigate(to: ~p"/maps")
|
||||
|
||||
%{track_character: track_character} ->
|
||||
{:ok, map_characters} =
|
||||
case WandererApp.MapCharacterSettingsRepo.get_tracked_by_map_filtered(
|
||||
map_id,
|
||||
current_user.characters |> Enum.map(& &1.id)
|
||||
) do
|
||||
{:ok, settings} ->
|
||||
{:ok,
|
||||
settings
|
||||
|> Enum.map(fn s -> s |> Ash.load!(:character) |> Map.get(:character) end)}
|
||||
|
||||
_ ->
|
||||
{:ok, []}
|
||||
end
|
||||
|
||||
case track_character do
|
||||
false ->
|
||||
:ok = MapCharactersEventHandler.untrack_characters(map_characters, map_id)
|
||||
:ok = MapCharactersEventHandler.remove_characters(map_characters, map_id)
|
||||
|
||||
_ ->
|
||||
:ok = MapCharactersEventHandler.track_characters(map_characters, map_id, true)
|
||||
|
||||
:ok =
|
||||
MapCharactersEventHandler.add_characters(map_characters, map_id, track_character)
|
||||
end
|
||||
|
||||
socket
|
||||
|> assign(user_permissions: user_permissions)
|
||||
|> MapEventHandler.push_map_event(
|
||||
"user_permissions",
|
||||
user_permissions
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_server_event(
|
||||
%{
|
||||
event: :load_map
|
||||
},
|
||||
%{assigns: %{current_user: current_user, map_slug: map_slug}} = socket
|
||||
) do
|
||||
ErrorTracker.set_context(%{user_id: current_user.id})
|
||||
|
||||
map_slug
|
||||
|> WandererApp.MapRepo.get_by_slug_with_permissions(current_user)
|
||||
|> case do
|
||||
{:ok, map} ->
|
||||
socket |> init_map(map)
|
||||
|
||||
{:error, _} ->
|
||||
socket
|
||||
|> put_flash(
|
||||
:error,
|
||||
"Something went wrong. Please try one more time or submit an issue."
|
||||
)
|
||||
|> push_navigate(to: ~p"/maps")
|
||||
end
|
||||
end
|
||||
|
||||
def handle_server_event(
|
||||
%{event: :map_server_started},
|
||||
socket
|
||||
),
|
||||
do: socket |> handle_map_server_started()
|
||||
|
||||
def handle_server_event(%{event: :update_map, payload: map_diff}, socket),
|
||||
do:
|
||||
socket
|
||||
|> MapEventHandler.push_map_event(
|
||||
"map_updated",
|
||||
map_diff
|
||||
)
|
||||
|
||||
def handle_server_event(
|
||||
%{event: "presence_diff"},
|
||||
socket
|
||||
),
|
||||
do: socket
|
||||
|
||||
def handle_server_event(event, socket) do
|
||||
Logger.warning(fn -> "unhandled map core event: #{inspect(event)}" end)
|
||||
socket
|
||||
end
|
||||
|
||||
def handle_ui_event("ui_loaded", _body, %{assigns: %{map_slug: map_slug} = assigns} = socket) do
|
||||
assigns
|
||||
|> Map.get(:map_id)
|
||||
|> case do
|
||||
map_id when not is_nil(map_id) ->
|
||||
maybe_start_map(map_id)
|
||||
|
||||
_ ->
|
||||
WandererApp.Cache.insert("map_#{map_slug}:ui_loaded", true)
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"live_select_change",
|
||||
%{"id" => id, "text" => text},
|
||||
socket
|
||||
)
|
||||
when id == "_system_id_live_select_component" do
|
||||
options =
|
||||
WandererApp.Api.MapSolarSystem.find_by_name!(%{name: text})
|
||||
|> Enum.take(100)
|
||||
|> Enum.map(&map_system/1)
|
||||
|
||||
send_update(LiveSelect.Component, options: options, id: id)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event("toggle_track_" <> character_id, _, socket),
|
||||
do:
|
||||
MapCharactersEventHandler.handle_ui_event(
|
||||
"toggle_track",
|
||||
%{"character-id" => character_id},
|
||||
socket
|
||||
)
|
||||
|
||||
def handle_ui_event(
|
||||
"get_user_settings",
|
||||
_,
|
||||
%{assigns: %{map_id: map_id, current_user: current_user}} = socket
|
||||
) do
|
||||
{:ok, user_settings} =
|
||||
WandererApp.MapUserSettingsRepo.get!(map_id, current_user.id)
|
||||
|> WandererApp.MapUserSettingsRepo.to_form_data()
|
||||
|
||||
{:reply, %{user_settings: user_settings}, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"update_user_settings",
|
||||
user_settings_form,
|
||||
%{assigns: %{map_id: map_id, current_user: current_user}} = socket
|
||||
) do
|
||||
settings =
|
||||
user_settings_form
|
||||
|> Map.take(["select_on_spash", "link_signature_on_splash", "delete_connection_with_sigs"])
|
||||
|> Jason.encode!()
|
||||
|
||||
{:ok, user_settings} =
|
||||
WandererApp.MapUserSettingsRepo.create_or_update(map_id, current_user.id, settings)
|
||||
|
||||
{:noreply,
|
||||
socket |> assign(user_settings_form: user_settings_form, map_user_settings: user_settings)}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"log_map_error",
|
||||
%{"componentStack" => component_stack, "error" => error},
|
||||
socket
|
||||
) do
|
||||
Logger.error(fn -> "map_ui_error: #{error} \n#{component_stack} " end)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:error, "Something went wrong. Please try refresh page or submit an issue.")
|
||||
|> push_event("js-exec", %{
|
||||
to: "#map-loader",
|
||||
attr: "data-loading",
|
||||
timeout: 100
|
||||
})}
|
||||
end
|
||||
|
||||
def handle_ui_event("noop", _, socket), do: {:noreply, socket}
|
||||
|
||||
def handle_ui_event(
|
||||
_event,
|
||||
_body,
|
||||
%{assigns: %{has_tracked_characters?: false}} =
|
||||
socket
|
||||
),
|
||||
do:
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(
|
||||
:error,
|
||||
"You should enable tracking for at least one character."
|
||||
)}
|
||||
|
||||
def handle_ui_event(event, body, socket) do
|
||||
Logger.warning(fn -> "unhandled map ui event: #{event} #{inspect(body)}" end)
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
defp maybe_start_map(map_id) do
|
||||
{:ok, map_server_started} = WandererApp.Cache.lookup("map_#{map_id}:started", false)
|
||||
|
||||
if map_server_started do
|
||||
Process.send_after(self(), %{event: :map_server_started}, 10)
|
||||
else
|
||||
WandererApp.Map.Manager.start_map(map_id)
|
||||
end
|
||||
end
|
||||
|
||||
defp init_map(
|
||||
%{assigns: %{current_user: current_user, map_slug: map_slug}} = socket,
|
||||
%{
|
||||
id: map_id,
|
||||
deleted: false,
|
||||
only_tracked_characters: only_tracked_characters,
|
||||
user_permissions: user_permissions,
|
||||
name: map_name,
|
||||
owner_id: owner_id
|
||||
} = map
|
||||
) do
|
||||
user_permissions =
|
||||
WandererApp.Permissions.get_map_permissions(
|
||||
user_permissions,
|
||||
owner_id,
|
||||
current_user.characters |> Enum.map(& &1.id)
|
||||
)
|
||||
|
||||
{:ok, character_settings} =
|
||||
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) do
|
||||
{:ok, settings} -> {:ok, settings}
|
||||
_ -> {:ok, []}
|
||||
end
|
||||
|
||||
{:ok, %{characters: availaible_map_characters}} =
|
||||
WandererApp.Maps.load_characters(map, character_settings, current_user.id)
|
||||
|
||||
can_view? = user_permissions.view_system
|
||||
can_track? = user_permissions.track_character
|
||||
|
||||
tracked_character_ids =
|
||||
availaible_map_characters |> Enum.filter(& &1.tracked) |> Enum.map(& &1.id)
|
||||
|
||||
all_character_tracked? =
|
||||
not (availaible_map_characters |> Enum.empty?()) and
|
||||
availaible_map_characters |> Enum.all?(& &1.tracked)
|
||||
|
||||
cond do
|
||||
(only_tracked_characters and can_track? and all_character_tracked?) or
|
||||
(not only_tracked_characters and can_view?) ->
|
||||
Phoenix.PubSub.subscribe(WandererApp.PubSub, map_id)
|
||||
{:ok, ui_loaded} = WandererApp.Cache.get_and_remove("map_#{map_slug}:ui_loaded", false)
|
||||
|
||||
if ui_loaded do
|
||||
maybe_start_map(map_id)
|
||||
end
|
||||
|
||||
socket
|
||||
|> assign(
|
||||
map_id: map_id,
|
||||
page_title: map_name,
|
||||
user_permissions: user_permissions,
|
||||
tracked_character_ids: tracked_character_ids,
|
||||
only_tracked_characters: only_tracked_characters
|
||||
)
|
||||
|
||||
only_tracked_characters and can_track? and not all_character_tracked? ->
|
||||
Process.send_after(self(), :not_all_characters_tracked, 10)
|
||||
socket
|
||||
|
||||
true ->
|
||||
Process.send_after(self(), :no_permissions, 10)
|
||||
socket
|
||||
end
|
||||
end
|
||||
|
||||
defp init_map(socket, _map) do
|
||||
Process.send_after(self(), :no_access, 10)
|
||||
socket
|
||||
end
|
||||
|
||||
defp handle_map_server_started(
|
||||
%{
|
||||
assigns: %{
|
||||
current_user: current_user,
|
||||
map_id: map_id,
|
||||
user_permissions:
|
||||
%{view_system: true, track_character: track_character} = user_permissions
|
||||
}
|
||||
} = socket
|
||||
) do
|
||||
with {:ok, _} <- current_user |> WandererApp.Api.User.update_last_map(%{last_map_id: map_id}),
|
||||
{:ok, map_user_settings} <- WandererApp.MapUserSettingsRepo.get(map_id, current_user.id),
|
||||
{:ok, tracked_map_characters} <-
|
||||
MapCharactersEventHandler.get_tracked_map_characters(map_id, current_user),
|
||||
{:ok, characters_limit} <- map_id |> WandererApp.Map.get_characters_limit(),
|
||||
{:ok, present_character_ids} <-
|
||||
WandererApp.Cache.lookup("map_#{map_id}:presence_character_ids", []),
|
||||
{:ok, kills} <- WandererApp.Cache.lookup("map_#{map_id}:zkb_kills", Map.new()) do
|
||||
user_character_eve_ids = tracked_map_characters |> Enum.map(& &1.eve_id)
|
||||
|
||||
events =
|
||||
case tracked_map_characters |> Enum.any?(&(&1.access_token == nil)) do
|
||||
true ->
|
||||
[:invalid_token_message]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
|
||||
events =
|
||||
case tracked_map_characters |> Enum.empty?() do
|
||||
true ->
|
||||
events ++ [:empty_tracked_characters]
|
||||
|
||||
_ ->
|
||||
events
|
||||
end
|
||||
|
||||
events =
|
||||
case present_character_ids |> Enum.count() < characters_limit do
|
||||
true ->
|
||||
events ++ [{:track_characters, tracked_map_characters, track_character}]
|
||||
|
||||
_ ->
|
||||
events ++ [:map_character_limit]
|
||||
end
|
||||
|
||||
initial_data =
|
||||
map_id
|
||||
|> get_map_data()
|
||||
|> Map.merge(%{
|
||||
kills:
|
||||
kills
|
||||
|> Enum.filter(fn {_, kills} -> kills > 0 end)
|
||||
|> Enum.map(&MapEventHandler.map_ui_kill/1),
|
||||
present_characters:
|
||||
present_character_ids
|
||||
|> WandererApp.Character.get_character_eve_ids!(),
|
||||
user_characters: user_character_eve_ids,
|
||||
user_permissions: user_permissions,
|
||||
system_static_infos: nil,
|
||||
wormhole_types: nil,
|
||||
effects: nil,
|
||||
reset: false
|
||||
})
|
||||
|
||||
system_static_infos =
|
||||
map_id
|
||||
|> WandererApp.Map.list_systems!()
|
||||
|> Enum.map(&WandererApp.CachedInfo.get_system_static_info!(&1.solar_system_id))
|
||||
|> Enum.map(&MapEventHandler.map_ui_system_static_info/1)
|
||||
|
||||
initial_data =
|
||||
initial_data
|
||||
|> Map.put(
|
||||
:wormholes,
|
||||
WandererApp.CachedInfo.get_wormhole_types!()
|
||||
)
|
||||
|> Map.put(
|
||||
:effects,
|
||||
WandererApp.CachedInfo.get_effects!()
|
||||
)
|
||||
|> Map.put(
|
||||
:system_static_infos,
|
||||
system_static_infos
|
||||
)
|
||||
|> Map.put(:reset, true)
|
||||
|
||||
socket
|
||||
|> map_start(%{
|
||||
map_id: map_id,
|
||||
map_user_settings: map_user_settings,
|
||||
user_characters: user_character_eve_ids,
|
||||
initial_data: initial_data,
|
||||
events: events
|
||||
})
|
||||
else
|
||||
error ->
|
||||
Logger.error(fn -> "map_start_error: #{error}" end)
|
||||
Process.send_after(self(), :no_access, 10)
|
||||
|
||||
socket
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_map_server_started(socket) do
|
||||
Process.send_after(self(), :no_access, 10)
|
||||
socket
|
||||
end
|
||||
|
||||
defp map_start(
|
||||
socket,
|
||||
%{
|
||||
map_id: map_id,
|
||||
map_user_settings: map_user_settings,
|
||||
user_characters: user_character_eve_ids,
|
||||
initial_data: initial_data,
|
||||
events: events
|
||||
} = _started_data
|
||||
) do
|
||||
socket =
|
||||
socket
|
||||
|> handle_map_start_events(map_id, events)
|
||||
|
||||
map_characters = map_id |> WandererApp.Map.list_characters()
|
||||
|
||||
socket
|
||||
|> assign(
|
||||
map_loaded?: true,
|
||||
map_user_settings: map_user_settings,
|
||||
user_characters: user_character_eve_ids,
|
||||
has_tracked_characters?:
|
||||
MapCharactersEventHandler.has_tracked_characters?(user_character_eve_ids)
|
||||
)
|
||||
|> MapEventHandler.push_map_event(
|
||||
"init",
|
||||
initial_data
|
||||
|> Map.put(
|
||||
:characters,
|
||||
map_characters |> Enum.map(&MapCharactersEventHandler.map_ui_character/1)
|
||||
)
|
||||
)
|
||||
|> push_event("js-exec", %{
|
||||
to: "#map-loader",
|
||||
attr: "data-loaded"
|
||||
})
|
||||
end
|
||||
|
||||
defp handle_map_start_events(socket, map_id, events) do
|
||||
events
|
||||
|> Enum.reduce(socket, fn event, socket ->
|
||||
case event do
|
||||
{:track_characters, map_characters, track_character} ->
|
||||
:ok =
|
||||
MapCharactersEventHandler.track_characters(map_characters, map_id, track_character)
|
||||
|
||||
:ok = MapCharactersEventHandler.add_characters(map_characters, map_id, track_character)
|
||||
socket
|
||||
|
||||
:invalid_token_message ->
|
||||
socket
|
||||
|> put_flash(
|
||||
:error,
|
||||
"One of your characters has expired token. Please refresh it on characters page."
|
||||
)
|
||||
|
||||
:empty_tracked_characters ->
|
||||
socket
|
||||
|> put_flash(
|
||||
:info,
|
||||
"You should enable tracking for at least one character to work with map."
|
||||
)
|
||||
|
||||
:map_character_limit ->
|
||||
socket
|
||||
|> put_flash(
|
||||
:error,
|
||||
"Map reached its character limit, your characters won't be tracked. Please contact administrator."
|
||||
)
|
||||
|
||||
_ ->
|
||||
socket
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp get_map_data(map_id, include_static_data? \\ true) do
|
||||
{:ok, hubs} = map_id |> WandererApp.Map.list_hubs()
|
||||
{:ok, connections} = map_id |> WandererApp.Map.list_connections()
|
||||
{:ok, systems} = map_id |> WandererApp.Map.list_systems()
|
||||
|
||||
%{
|
||||
systems:
|
||||
systems
|
||||
|> Enum.map(fn system -> MapEventHandler.map_ui_system(system, include_static_data?) end),
|
||||
hubs: hubs,
|
||||
connections: connections |> Enum.map(&MapEventHandler.map_ui_connection/1)
|
||||
}
|
||||
end
|
||||
|
||||
defp get_tracked_map_characters(map_id, current_user) do
|
||||
case WandererApp.MapCharacterSettingsRepo.get_tracked_by_map_filtered(
|
||||
map_id,
|
||||
current_user.characters |> Enum.map(& &1.id)
|
||||
) do
|
||||
{:ok, settings} ->
|
||||
{:ok,
|
||||
settings
|
||||
|> Enum.map(fn s -> s |> Ash.load!(:character) |> Map.get(:character) end)}
|
||||
|
||||
_ ->
|
||||
{:ok, []}
|
||||
end
|
||||
end
|
||||
|
||||
defp map_system(
|
||||
%{
|
||||
solar_system_name: solar_system_name,
|
||||
constellation_name: constellation_name,
|
||||
region_name: region_name,
|
||||
solar_system_id: solar_system_id,
|
||||
class_title: class_title
|
||||
} = _system
|
||||
),
|
||||
do: %{
|
||||
label: solar_system_name,
|
||||
value: solar_system_id,
|
||||
constellation_name: constellation_name,
|
||||
region_name: region_name,
|
||||
class_title: class_title
|
||||
}
|
||||
end
|
||||
@@ -0,0 +1,131 @@
|
||||
defmodule WandererAppWeb.MapRoutesEventHandler do
|
||||
use WandererAppWeb, :live_component
|
||||
use Phoenix.Component
|
||||
require Logger
|
||||
|
||||
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler}
|
||||
|
||||
def handle_server_event(
|
||||
%{
|
||||
event: :routes,
|
||||
payload: {solar_system_id, %{routes: routes, systems_static_data: systems_static_data}}
|
||||
},
|
||||
socket
|
||||
),
|
||||
do:
|
||||
socket
|
||||
|> MapEventHandler.push_map_event(
|
||||
"routes",
|
||||
%{
|
||||
solar_system_id: solar_system_id,
|
||||
loading: false,
|
||||
routes: routes,
|
||||
systems_static_data: systems_static_data
|
||||
}
|
||||
)
|
||||
|
||||
def handle_server_event(event, socket),
|
||||
do: MapCoreEventHandler.handle_server_event(event, socket)
|
||||
|
||||
def handle_ui_event(
|
||||
"get_routes",
|
||||
%{"system_id" => solar_system_id, "routes_settings" => routes_settings} = _event,
|
||||
%{assigns: %{map_id: map_id, map_loaded?: true}} = socket
|
||||
) do
|
||||
Task.async(fn ->
|
||||
{:ok, hubs} = map_id |> WandererApp.Map.list_hubs()
|
||||
|
||||
{:ok, routes} =
|
||||
WandererApp.Maps.find_routes(
|
||||
map_id,
|
||||
hubs,
|
||||
solar_system_id,
|
||||
get_routes_settings(routes_settings)
|
||||
)
|
||||
|
||||
{:routes, {solar_system_id, routes}}
|
||||
end)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"set_autopilot_waypoint",
|
||||
%{
|
||||
"character_eve_ids" => character_eve_ids,
|
||||
"add_to_beginning" => add_to_beginning,
|
||||
"clear_other_waypoints" => clear_other_waypoints,
|
||||
"destination_id" => destination_id
|
||||
} = _event,
|
||||
%{assigns: %{current_user: current_user, has_tracked_characters?: true}} = socket
|
||||
) do
|
||||
character_eve_ids
|
||||
|> Task.async_stream(fn character_eve_id ->
|
||||
set_autopilot_waypoint(
|
||||
current_user,
|
||||
character_eve_id,
|
||||
add_to_beginning,
|
||||
clear_other_waypoints,
|
||||
destination_id
|
||||
)
|
||||
end)
|
||||
|> Enum.map(fn _result -> :skip end)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(event, body, socket),
|
||||
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
|
||||
|
||||
defp get_routes_settings(%{
|
||||
"path_type" => path_type,
|
||||
"include_mass_crit" => include_mass_crit,
|
||||
"include_eol" => include_eol,
|
||||
"include_frig" => include_frig,
|
||||
"include_cruise" => include_cruise,
|
||||
"avoid_wormholes" => avoid_wormholes,
|
||||
"avoid_pochven" => avoid_pochven,
|
||||
"avoid_edencom" => avoid_edencom,
|
||||
"avoid_triglavian" => avoid_triglavian,
|
||||
"include_thera" => include_thera,
|
||||
"avoid" => avoid
|
||||
}),
|
||||
do: %{
|
||||
path_type: path_type,
|
||||
include_mass_crit: include_mass_crit,
|
||||
include_eol: include_eol,
|
||||
include_frig: include_frig,
|
||||
include_cruise: include_cruise,
|
||||
avoid_wormholes: avoid_wormholes,
|
||||
avoid_pochven: avoid_pochven,
|
||||
avoid_edencom: avoid_edencom,
|
||||
avoid_triglavian: avoid_triglavian,
|
||||
include_thera: include_thera,
|
||||
avoid: avoid
|
||||
}
|
||||
|
||||
defp get_routes_settings(_), do: %{}
|
||||
|
||||
defp set_autopilot_waypoint(
|
||||
current_user,
|
||||
character_eve_id,
|
||||
add_to_beginning,
|
||||
clear_other_waypoints,
|
||||
destination_id
|
||||
) do
|
||||
case current_user.characters
|
||||
|> Enum.find(fn c -> c.eve_id == character_eve_id end) do
|
||||
nil ->
|
||||
:skip
|
||||
|
||||
%{id: character_id} = _character ->
|
||||
character_id
|
||||
|> WandererApp.Character.set_autopilot_waypoint(destination_id,
|
||||
add_to_beginning: add_to_beginning,
|
||||
clear_other_waypoints: clear_other_waypoints
|
||||
)
|
||||
|
||||
:skip
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,358 @@
|
||||
defmodule WandererAppWeb.MapSignaturesEventHandler do
|
||||
use WandererAppWeb, :live_component
|
||||
use Phoenix.Component
|
||||
require Logger
|
||||
|
||||
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler}
|
||||
|
||||
def handle_server_event(
|
||||
%{
|
||||
event: :maybe_link_signature,
|
||||
payload: %{
|
||||
character_id: character_id,
|
||||
solar_system_source: solar_system_source,
|
||||
solar_system_target: solar_system_target
|
||||
}
|
||||
},
|
||||
%{
|
||||
assigns: %{
|
||||
current_user: current_user,
|
||||
map_id: map_id,
|
||||
map_user_settings: map_user_settings
|
||||
}
|
||||
} = socket
|
||||
) do
|
||||
is_user_character =
|
||||
current_user.characters |> Enum.map(& &1.id) |> Enum.member?(character_id)
|
||||
|
||||
is_link_signature_on_splash =
|
||||
map_user_settings
|
||||
|> WandererApp.MapUserSettingsRepo.to_form_data!()
|
||||
|> WandererApp.MapUserSettingsRepo.get_boolean_setting("link_signature_on_splash")
|
||||
|
||||
{:ok, signatures} =
|
||||
WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{
|
||||
map_id: map_id,
|
||||
solar_system_id: solar_system_source
|
||||
})
|
||||
|> case do
|
||||
{:ok, system} ->
|
||||
{:ok, get_system_signatures(system.id)}
|
||||
|
||||
_ ->
|
||||
{:ok, []}
|
||||
end
|
||||
|
||||
(is_user_character && is_link_signature_on_splash && not (signatures |> Enum.empty?()))
|
||||
|> case do
|
||||
true ->
|
||||
socket
|
||||
|> MapEventHandler.push_map_event("link_signature_to_system", %{
|
||||
solar_system_source: solar_system_source,
|
||||
solar_system_target: solar_system_target
|
||||
})
|
||||
|
||||
false ->
|
||||
socket
|
||||
end
|
||||
end
|
||||
|
||||
def handle_server_event(
|
||||
%{event: :signatures_updated, payload: solar_system_id},
|
||||
socket
|
||||
),
|
||||
do:
|
||||
socket
|
||||
|> MapEventHandler.push_map_event(
|
||||
"signatures_updated",
|
||||
solar_system_id
|
||||
)
|
||||
|
||||
def handle_server_event(event, socket),
|
||||
do: MapCoreEventHandler.handle_server_event(event, socket)
|
||||
|
||||
def handle_ui_event(
|
||||
"update_signatures",
|
||||
%{
|
||||
"system_id" => solar_system_id,
|
||||
"added" => added_signatures,
|
||||
"updated" => updated_signatures,
|
||||
"removed" => removed_signatures
|
||||
},
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
map_user_settings: map_user_settings,
|
||||
user_characters: user_characters,
|
||||
user_permissions: %{update_system: true}
|
||||
}
|
||||
} = socket
|
||||
) do
|
||||
WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{
|
||||
map_id: map_id,
|
||||
solar_system_id: solar_system_id |> String.to_integer()
|
||||
})
|
||||
|> case do
|
||||
{:ok, system} ->
|
||||
first_character_eve_id =
|
||||
user_characters |> List.first()
|
||||
|
||||
case not is_nil(first_character_eve_id) do
|
||||
true ->
|
||||
added_signatures =
|
||||
added_signatures
|
||||
|> parse_signatures(first_character_eve_id, system.id)
|
||||
|
||||
updated_signatures =
|
||||
updated_signatures
|
||||
|> parse_signatures(first_character_eve_id, system.id)
|
||||
|
||||
updated_signatures_eve_ids =
|
||||
updated_signatures
|
||||
|> Enum.map(fn s -> s.eve_id end)
|
||||
|
||||
removed_signatures_eve_ids =
|
||||
removed_signatures
|
||||
|> parse_signatures(first_character_eve_id, system.id)
|
||||
|> Enum.map(fn s -> s.eve_id end)
|
||||
|
||||
delete_connection_with_sigs =
|
||||
map_user_settings
|
||||
|> WandererApp.MapUserSettingsRepo.to_form_data!()
|
||||
|> WandererApp.MapUserSettingsRepo.get_boolean_setting(
|
||||
"delete_connection_with_sigs"
|
||||
)
|
||||
|
||||
WandererApp.Api.MapSystemSignature.by_system_id!(system.id)
|
||||
|> Enum.filter(fn s -> s.eve_id in removed_signatures_eve_ids end)
|
||||
|> Enum.each(fn s ->
|
||||
if delete_connection_with_sigs && not is_nil(s.linked_system_id) do
|
||||
map_id
|
||||
|> WandererApp.Map.Server.delete_connection(%{
|
||||
solar_system_source_id: solar_system_id |> String.to_integer(),
|
||||
solar_system_target_id: s.linked_system_id
|
||||
})
|
||||
end
|
||||
|
||||
s
|
||||
|> Ash.destroy!()
|
||||
end)
|
||||
|
||||
WandererApp.Api.MapSystemSignature.by_system_id!(system.id)
|
||||
|> Enum.filter(fn s -> s.eve_id in updated_signatures_eve_ids end)
|
||||
|> Enum.each(fn s ->
|
||||
updated = updated_signatures |> Enum.find(fn u -> u.eve_id == s.eve_id end)
|
||||
|
||||
if not is_nil(updated) do
|
||||
s
|
||||
|> WandererApp.Api.MapSystemSignature.update(
|
||||
updated
|
||||
|> Map.put(:updated, System.os_time())
|
||||
)
|
||||
end
|
||||
end)
|
||||
|
||||
added_signatures
|
||||
|> Enum.map(fn s ->
|
||||
s |> WandererApp.Api.MapSystemSignature.create!()
|
||||
end)
|
||||
|
||||
Phoenix.PubSub.broadcast!(WandererApp.PubSub, map_id, %{
|
||||
event: :signatures_updated,
|
||||
payload: system.solar_system_id
|
||||
})
|
||||
|
||||
{:reply, %{signatures: get_system_signatures(system.id)}, socket}
|
||||
|
||||
_ ->
|
||||
{:reply, %{signatures: []},
|
||||
socket
|
||||
|> put_flash(
|
||||
:error,
|
||||
"You should enable tracking for at least one character to work with signatures."
|
||||
)}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"get_signatures",
|
||||
%{"system_id" => solar_system_id},
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id
|
||||
}
|
||||
} = socket
|
||||
) do
|
||||
case WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{
|
||||
map_id: map_id,
|
||||
solar_system_id: solar_system_id |> String.to_integer()
|
||||
}) do
|
||||
{:ok, system} ->
|
||||
{:reply, %{signatures: get_system_signatures(system.id)}, socket}
|
||||
|
||||
_ ->
|
||||
{:reply, %{signatures: []}, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"link_signature_to_system",
|
||||
%{
|
||||
"signature_eve_id" => signature_eve_id,
|
||||
"solar_system_source" => solar_system_source,
|
||||
"solar_system_target" => solar_system_target
|
||||
},
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
user_characters: user_characters,
|
||||
user_permissions: %{update_system: true}
|
||||
}
|
||||
} = socket
|
||||
) do
|
||||
case WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{
|
||||
map_id: map_id,
|
||||
solar_system_id: solar_system_source
|
||||
}) do
|
||||
{:ok, system} ->
|
||||
first_character_eve_id =
|
||||
user_characters |> List.first()
|
||||
|
||||
case not is_nil(first_character_eve_id) do
|
||||
true ->
|
||||
WandererApp.Api.MapSystemSignature.by_system_id!(system.id)
|
||||
|> Enum.filter(fn s -> s.eve_id == signature_eve_id end)
|
||||
|> Enum.each(fn s ->
|
||||
s
|
||||
|> WandererApp.Api.MapSystemSignature.update_group!(%{group: "Wormhole"})
|
||||
|> WandererApp.Api.MapSystemSignature.update_linked_system(%{
|
||||
linked_system_id: solar_system_target
|
||||
})
|
||||
end)
|
||||
|
||||
Phoenix.PubSub.broadcast!(WandererApp.PubSub, map_id, %{
|
||||
event: :signatures_updated,
|
||||
payload: solar_system_source
|
||||
})
|
||||
|
||||
{:noreply, socket}
|
||||
|
||||
_ ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(
|
||||
:error,
|
||||
"You should enable tracking for at least one character to work with signatures."
|
||||
)}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"unlink_signature",
|
||||
%{
|
||||
"signature_eve_id" => signature_eve_id,
|
||||
"solar_system_source" => solar_system_source
|
||||
},
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
user_characters: user_characters,
|
||||
user_permissions: %{update_system: true}
|
||||
}
|
||||
} = socket
|
||||
) do
|
||||
case WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{
|
||||
map_id: map_id,
|
||||
solar_system_id: solar_system_source
|
||||
}) do
|
||||
{:ok, system} ->
|
||||
first_character_eve_id =
|
||||
user_characters |> List.first()
|
||||
|
||||
case not is_nil(first_character_eve_id) do
|
||||
true ->
|
||||
WandererApp.Api.MapSystemSignature.by_system_id!(system.id)
|
||||
|> Enum.filter(fn s -> s.eve_id == signature_eve_id end)
|
||||
|> Enum.each(fn s ->
|
||||
s
|
||||
|> WandererApp.Api.MapSystemSignature.update_linked_system(%{
|
||||
linked_system_id: nil
|
||||
})
|
||||
end)
|
||||
|
||||
Phoenix.PubSub.broadcast!(WandererApp.PubSub, map_id, %{
|
||||
event: :signatures_updated,
|
||||
payload: solar_system_source
|
||||
})
|
||||
|
||||
{:noreply, socket}
|
||||
|
||||
_ ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(
|
||||
:error,
|
||||
"You should enable tracking for at least one character to work with signatures."
|
||||
)}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_ui_event(event, body, socket),
|
||||
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
|
||||
|
||||
defp get_system_signatures(system_id),
|
||||
do:
|
||||
system_id
|
||||
|> WandererApp.Api.MapSystemSignature.by_system_id!()
|
||||
|> Enum.map(fn %{
|
||||
inserted_at: inserted_at,
|
||||
updated_at: updated_at,
|
||||
linked_system_id: linked_system_id
|
||||
} = s ->
|
||||
s
|
||||
|> Map.take([
|
||||
:eve_id,
|
||||
:name,
|
||||
:description,
|
||||
:kind,
|
||||
:group,
|
||||
:type
|
||||
])
|
||||
|> Map.put(:linked_system, MapEventHandler.get_system_static_info(linked_system_id))
|
||||
|> Map.put(:inserted_at, inserted_at |> Calendar.strftime("%Y/%m/%d %H:%M:%S"))
|
||||
|> Map.put(:updated_at, updated_at |> Calendar.strftime("%Y/%m/%d %H:%M:%S"))
|
||||
end)
|
||||
|
||||
defp parse_signatures(signatures, character_eve_id, system_id),
|
||||
do:
|
||||
signatures
|
||||
|> Enum.map(fn %{
|
||||
"eve_id" => eve_id,
|
||||
"name" => name,
|
||||
"kind" => kind,
|
||||
"group" => group
|
||||
} = signature ->
|
||||
%{
|
||||
system_id: system_id,
|
||||
eve_id: eve_id,
|
||||
name: name,
|
||||
description: Map.get(signature, "description"),
|
||||
kind: kind,
|
||||
group: group,
|
||||
type: Map.get(signature, "type"),
|
||||
character_eve_id: character_eve_id
|
||||
}
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,326 @@
|
||||
defmodule WandererAppWeb.MapSystemsEventHandler do
|
||||
use WandererAppWeb, :live_component
|
||||
use Phoenix.Component
|
||||
require Logger
|
||||
|
||||
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler}
|
||||
|
||||
def handle_server_event(%{event: :add_system, payload: system}, socket),
|
||||
do:
|
||||
socket
|
||||
|> MapEventHandler.push_map_event("add_systems", [MapEventHandler.map_ui_system(system)])
|
||||
|
||||
def handle_server_event(%{event: :update_system, payload: system}, socket),
|
||||
do:
|
||||
socket
|
||||
|> MapEventHandler.push_map_event("update_systems", [MapEventHandler.map_ui_system(system)])
|
||||
|
||||
def handle_server_event(%{event: :systems_removed, payload: solar_system_ids}, socket),
|
||||
do:
|
||||
socket
|
||||
|> MapEventHandler.push_map_event("remove_systems", solar_system_ids)
|
||||
|
||||
def handle_server_event(
|
||||
%{
|
||||
event: :maybe_select_system,
|
||||
payload: %{
|
||||
character_id: character_id,
|
||||
solar_system_id: solar_system_id
|
||||
}
|
||||
},
|
||||
%{assigns: %{current_user: current_user, map_user_settings: map_user_settings}} = socket
|
||||
) do
|
||||
is_user_character =
|
||||
current_user.characters |> Enum.map(& &1.id) |> Enum.member?(character_id)
|
||||
|
||||
is_select_on_spash =
|
||||
map_user_settings
|
||||
|> WandererApp.MapUserSettingsRepo.to_form_data!()
|
||||
|> WandererApp.MapUserSettingsRepo.get_boolean_setting("select_on_spash")
|
||||
|
||||
(is_user_character && is_select_on_spash)
|
||||
|> case do
|
||||
true ->
|
||||
socket
|
||||
|> MapEventHandler.push_map_event("select_system", solar_system_id)
|
||||
|
||||
false ->
|
||||
socket
|
||||
end
|
||||
end
|
||||
|
||||
def handle_server_event(%{event: :kills_updated, payload: kills}, socket) do
|
||||
kills =
|
||||
kills
|
||||
|> Enum.map(&MapEventHandler.map_ui_kill/1)
|
||||
|
||||
socket
|
||||
|> MapEventHandler.push_map_event(
|
||||
"kills_updated",
|
||||
kills
|
||||
)
|
||||
end
|
||||
|
||||
def handle_server_event(event, socket),
|
||||
do: MapCoreEventHandler.handle_server_event(event, socket)
|
||||
|
||||
def handle_ui_event(
|
||||
"add_system",
|
||||
%{"system_id" => solar_system_id} = _event,
|
||||
%{
|
||||
assigns:
|
||||
%{
|
||||
map_id: map_id,
|
||||
map_slug: map_slug,
|
||||
current_user: current_user,
|
||||
tracked_character_ids: tracked_character_ids,
|
||||
user_permissions: %{add_system: true}
|
||||
} = assigns
|
||||
} = socket
|
||||
)
|
||||
when is_binary(solar_system_id) and solar_system_id != "" do
|
||||
coordinates = Map.get(assigns, :coordinates)
|
||||
|
||||
WandererApp.Map.Server.add_system(
|
||||
map_id,
|
||||
%{
|
||||
solar_system_id: solar_system_id |> String.to_integer(),
|
||||
coordinates: coordinates
|
||||
},
|
||||
current_user.id,
|
||||
tracked_character_ids |> List.first()
|
||||
)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> push_patch(to: ~p"/#{map_slug}")}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"manual_add_system",
|
||||
%{"coordinates" => coordinates} = _event,
|
||||
%{
|
||||
assigns: %{
|
||||
has_tracked_characters?: true,
|
||||
map_slug: map_slug,
|
||||
user_permissions: %{add_system: true}
|
||||
}
|
||||
} =
|
||||
socket
|
||||
),
|
||||
do:
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(coordinates: coordinates)
|
||||
|> push_patch(to: ~p"/#{map_slug}/add-system")}
|
||||
|
||||
def handle_ui_event(
|
||||
"add_hub",
|
||||
%{"system_id" => solar_system_id} = _event,
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
current_user: current_user,
|
||||
tracked_character_ids: tracked_character_ids,
|
||||
has_tracked_characters?: true,
|
||||
user_permissions: %{update_system: true}
|
||||
}
|
||||
} =
|
||||
socket
|
||||
) do
|
||||
map_id
|
||||
|> WandererApp.Map.Server.add_hub(%{
|
||||
solar_system_id: solar_system_id
|
||||
})
|
||||
|
||||
{:ok, _} =
|
||||
WandererApp.User.ActivityTracker.track_map_event(:hub_added, %{
|
||||
character_id: tracked_character_ids |> List.first(),
|
||||
user_id: current_user.id,
|
||||
map_id: map_id,
|
||||
solar_system_id: solar_system_id
|
||||
})
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"delete_hub",
|
||||
%{"system_id" => solar_system_id} = _event,
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
current_user: current_user,
|
||||
tracked_character_ids: tracked_character_ids,
|
||||
has_tracked_characters?: true,
|
||||
user_permissions: %{update_system: true}
|
||||
}
|
||||
} =
|
||||
socket
|
||||
) do
|
||||
map_id
|
||||
|> WandererApp.Map.Server.remove_hub(%{
|
||||
solar_system_id: solar_system_id
|
||||
})
|
||||
|
||||
{:ok, _} =
|
||||
WandererApp.User.ActivityTracker.track_map_event(:hub_removed, %{
|
||||
character_id: tracked_character_ids |> List.first(),
|
||||
user_id: current_user.id,
|
||||
map_id: map_id,
|
||||
solar_system_id: solar_system_id
|
||||
})
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"update_system_position",
|
||||
position,
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
has_tracked_characters?: true,
|
||||
user_permissions: %{update_system: true}
|
||||
}
|
||||
} = socket
|
||||
) do
|
||||
map_id
|
||||
|> update_system_position(position)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"update_system_positions",
|
||||
positions,
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
has_tracked_characters?: true,
|
||||
user_permissions: %{update_system: true}
|
||||
}
|
||||
} = socket
|
||||
) do
|
||||
map_id
|
||||
|> update_system_positions(positions)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"update_system_" <> param,
|
||||
%{"system_id" => solar_system_id, "value" => value} = _event,
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
current_user: current_user,
|
||||
tracked_character_ids: tracked_character_ids,
|
||||
has_tracked_characters?: true,
|
||||
user_permissions: %{update_system: true}
|
||||
}
|
||||
} =
|
||||
socket
|
||||
) do
|
||||
method_atom =
|
||||
case param do
|
||||
"name" -> :update_system_name
|
||||
"description" -> :update_system_description
|
||||
"labels" -> :update_system_labels
|
||||
"locked" -> :update_system_locked
|
||||
"tag" -> :update_system_tag
|
||||
"status" -> :update_system_status
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
key_atom =
|
||||
case param do
|
||||
"name" -> :name
|
||||
"description" -> :description
|
||||
"labels" -> :labels
|
||||
"locked" -> :locked
|
||||
"tag" -> :tag
|
||||
"status" -> :status
|
||||
_ -> :none
|
||||
end
|
||||
|
||||
apply(WandererApp.Map.Server, method_atom, [
|
||||
map_id,
|
||||
%{
|
||||
solar_system_id: "#{solar_system_id}" |> String.to_integer()
|
||||
}
|
||||
|> Map.put_new(key_atom, value)
|
||||
])
|
||||
|
||||
{:ok, _} =
|
||||
WandererApp.User.ActivityTracker.track_map_event(:system_updated, %{
|
||||
character_id: tracked_character_ids |> List.first(),
|
||||
user_id: current_user.id,
|
||||
map_id: map_id,
|
||||
solar_system_id: "#{solar_system_id}" |> String.to_integer(),
|
||||
key: key_atom,
|
||||
value: value
|
||||
})
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"get_system_static_infos",
|
||||
%{"solar_system_ids" => solar_system_ids} = _event,
|
||||
socket
|
||||
) do
|
||||
system_static_infos =
|
||||
solar_system_ids
|
||||
|> Enum.map(&WandererApp.CachedInfo.get_system_static_info!/1)
|
||||
|> Enum.map(&MapEventHandler.map_ui_system_static_info/1)
|
||||
|
||||
{:reply, %{system_static_infos: system_static_infos}, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"delete_systems",
|
||||
solar_system_ids,
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
current_user: current_user,
|
||||
tracked_character_ids: tracked_character_ids,
|
||||
has_tracked_characters?: true,
|
||||
user_permissions: %{delete_system: true}
|
||||
}
|
||||
} =
|
||||
socket
|
||||
) do
|
||||
map_id
|
||||
|> WandererApp.Map.Server.delete_systems(
|
||||
solar_system_ids |> Enum.map(&String.to_integer/1),
|
||||
current_user.id,
|
||||
tracked_character_ids |> List.first()
|
||||
)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(event, body, socket),
|
||||
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
|
||||
|
||||
defp update_system_positions(_map_id, []), do: :ok
|
||||
|
||||
defp update_system_positions(map_id, [position | rest]) do
|
||||
update_system_position(map_id, position)
|
||||
update_system_positions(map_id, rest)
|
||||
end
|
||||
|
||||
defp update_system_position(map_id, %{
|
||||
"position" => %{"x" => x, "y" => y},
|
||||
"solar_system_id" => solar_system_id
|
||||
}),
|
||||
do:
|
||||
map_id
|
||||
|> WandererApp.Map.Server.update_system_position(%{
|
||||
solar_system_id: solar_system_id |> String.to_integer(),
|
||||
position_x: x,
|
||||
position_y: y
|
||||
})
|
||||
end
|
||||
296
lib/wanderer_app_web/live/maps/map_event_handler.ex
Normal file
296
lib/wanderer_app_web/live/maps/map_event_handler.ex
Normal file
@@ -0,0 +1,296 @@
|
||||
defmodule WandererAppWeb.MapEventHandler do
|
||||
use WandererAppWeb, :live_component
|
||||
use Phoenix.Component
|
||||
require Logger
|
||||
|
||||
alias WandererAppWeb.{
|
||||
MapActivityEventHandler,
|
||||
MapCharactersEventHandler,
|
||||
MapConnectionsEventHandler,
|
||||
MapCoreEventHandler,
|
||||
MapRoutesEventHandler,
|
||||
MapSignaturesEventHandler,
|
||||
MapSystemsEventHandler
|
||||
}
|
||||
|
||||
@map_characters_events [
|
||||
:character_added,
|
||||
:character_removed,
|
||||
:character_updated,
|
||||
:characters_updated,
|
||||
:present_characters_updated
|
||||
]
|
||||
|
||||
@map_characters_ui_events [
|
||||
"add_character",
|
||||
"toggle_track",
|
||||
"hide_tracking"
|
||||
]
|
||||
|
||||
@map_system_events [
|
||||
:add_system,
|
||||
:update_system,
|
||||
:systems_removed,
|
||||
:maybe_select_system,
|
||||
:kills_updated
|
||||
]
|
||||
|
||||
@map_system_ui_events [
|
||||
"add_hub",
|
||||
"delete_hub",
|
||||
"add_system",
|
||||
"delete_systems",
|
||||
"manual_add_system",
|
||||
"get_system_static_infos",
|
||||
"update_system_position",
|
||||
"update_system_positions",
|
||||
"update_system_name",
|
||||
"update_system_description",
|
||||
"update_system_labels",
|
||||
"update_system_locked",
|
||||
"update_system_tag",
|
||||
"update_system_status"
|
||||
]
|
||||
|
||||
@map_connection_events [
|
||||
:add_connection,
|
||||
:remove_connections,
|
||||
:update_connection
|
||||
]
|
||||
|
||||
@map_connection_ui_events [
|
||||
"manual_add_connection",
|
||||
"manual_delete_connection",
|
||||
"get_connection_info",
|
||||
"get_passages",
|
||||
"update_connection_time_status",
|
||||
"update_connection_type",
|
||||
"update_connection_mass_status",
|
||||
"update_connection_ship_size_type",
|
||||
"update_connection_locked",
|
||||
"update_connection_custom_info"
|
||||
]
|
||||
|
||||
@map_activity_events [
|
||||
:character_activity
|
||||
]
|
||||
|
||||
@map_activity_ui_events [
|
||||
"show_activity",
|
||||
"hide_activity"
|
||||
]
|
||||
|
||||
@map_routes_events [
|
||||
:routes
|
||||
]
|
||||
|
||||
@map_routes_ui_events [
|
||||
"get_routes",
|
||||
"set_autopilot_waypoint"
|
||||
]
|
||||
|
||||
@map_signatures_events [
|
||||
:maybe_link_signature,
|
||||
:signatures_updated
|
||||
]
|
||||
|
||||
@map_signatures_ui_events [
|
||||
"update_signatures",
|
||||
"get_signatures",
|
||||
"link_signature_to_system",
|
||||
"unlink_signature"
|
||||
]
|
||||
|
||||
def handle_event(socket, %{event: event_name} = event)
|
||||
when event_name in @map_characters_events,
|
||||
do: MapCharactersEventHandler.handle_server_event(event, socket)
|
||||
|
||||
def handle_event(socket, %{event: event_name} = event)
|
||||
when event_name in @map_system_events,
|
||||
do: MapSystemsEventHandler.handle_server_event(event, socket)
|
||||
|
||||
def handle_event(socket, %{event: event_name} = event)
|
||||
when event_name in @map_connection_events,
|
||||
do: MapConnectionsEventHandler.handle_server_event(event, socket)
|
||||
|
||||
def handle_event(socket, %{event: event_name} = event)
|
||||
when event_name in @map_activity_events,
|
||||
do: MapActivityEventHandler.handle_server_event(event, socket)
|
||||
|
||||
def handle_event(socket, %{event: event_name} = event)
|
||||
when event_name in @map_routes_events,
|
||||
do: MapRoutesEventHandler.handle_server_event(event, socket)
|
||||
|
||||
def handle_event(socket, %{event: event_name} = event)
|
||||
when event_name in @map_signatures_events,
|
||||
do: MapSignaturesEventHandler.handle_server_event(event, socket)
|
||||
|
||||
def handle_event(socket, {ref, result}) when is_reference(ref) do
|
||||
Process.demonitor(ref, [:flush])
|
||||
|
||||
case result do
|
||||
{:map_error, map_error} ->
|
||||
Process.send_after(self(), map_error, 100)
|
||||
socket
|
||||
|
||||
{event, payload} ->
|
||||
Process.send_after(
|
||||
self(),
|
||||
%{
|
||||
event: event,
|
||||
payload: payload
|
||||
},
|
||||
10
|
||||
)
|
||||
|
||||
socket
|
||||
|
||||
_ ->
|
||||
socket
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event(socket, event),
|
||||
do: MapCoreEventHandler.handle_server_event(event, socket)
|
||||
|
||||
def handle_ui_event(event, body, socket)
|
||||
when event in @map_characters_ui_events,
|
||||
do: MapCharactersEventHandler.handle_ui_event(event, body, socket)
|
||||
|
||||
def handle_ui_event(event, body, socket)
|
||||
when event in @map_system_ui_events,
|
||||
do: MapSystemsEventHandler.handle_ui_event(event, body, socket)
|
||||
|
||||
def handle_ui_event(event, body, socket)
|
||||
when event in @map_connection_ui_events,
|
||||
do: MapConnectionsEventHandler.handle_ui_event(event, body, socket)
|
||||
|
||||
def handle_ui_event(event, body, socket)
|
||||
when event in @map_routes_ui_events,
|
||||
do: MapRoutesEventHandler.handle_ui_event(event, body, socket)
|
||||
|
||||
def handle_ui_event(event, body, socket)
|
||||
when event in @map_signatures_ui_events,
|
||||
do: MapSignaturesEventHandler.handle_ui_event(event, body, socket)
|
||||
|
||||
def handle_ui_event(event, body, socket)
|
||||
when event in @map_activity_ui_events,
|
||||
do: MapActivityEventHandler.handle_ui_event(event, body, socket)
|
||||
|
||||
def handle_ui_event(event, body, socket),
|
||||
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
|
||||
|
||||
def get_system_static_info(nil), do: nil
|
||||
|
||||
def get_system_static_info(solar_system_id) do
|
||||
case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do
|
||||
{:ok, system_static_info} ->
|
||||
map_ui_system_static_info(system_static_info)
|
||||
|
||||
_ ->
|
||||
%{}
|
||||
end
|
||||
end
|
||||
|
||||
def push_map_event(socket, type, body),
|
||||
do:
|
||||
socket
|
||||
|> Phoenix.LiveView.Utils.push_event("map_event", %{
|
||||
type: type,
|
||||
body: body
|
||||
})
|
||||
|
||||
def map_ui_character_stat(character),
|
||||
do:
|
||||
character
|
||||
|> Map.take([
|
||||
:eve_id,
|
||||
:name,
|
||||
:corporation_ticker,
|
||||
:alliance_ticker
|
||||
])
|
||||
|
||||
def map_ui_connection(
|
||||
%{
|
||||
solar_system_source: solar_system_source,
|
||||
solar_system_target: solar_system_target,
|
||||
mass_status: mass_status,
|
||||
time_status: time_status,
|
||||
type: type,
|
||||
ship_size_type: ship_size_type,
|
||||
locked: locked
|
||||
} = _connection
|
||||
),
|
||||
do: %{
|
||||
id: "#{solar_system_source}_#{solar_system_target}",
|
||||
mass_status: mass_status,
|
||||
time_status: time_status,
|
||||
type: type,
|
||||
ship_size_type: ship_size_type,
|
||||
locked: locked,
|
||||
source: "#{solar_system_source}",
|
||||
target: "#{solar_system_target}"
|
||||
}
|
||||
|
||||
def map_ui_system(
|
||||
%{
|
||||
solar_system_id: solar_system_id,
|
||||
name: name,
|
||||
description: description,
|
||||
position_x: position_x,
|
||||
position_y: position_y,
|
||||
locked: locked,
|
||||
tag: tag,
|
||||
labels: labels,
|
||||
status: status,
|
||||
visible: visible
|
||||
} = _system,
|
||||
_include_static_data? \\ true
|
||||
) do
|
||||
system_static_info = get_system_static_info(solar_system_id)
|
||||
|
||||
%{
|
||||
id: "#{solar_system_id}",
|
||||
position: %{x: position_x, y: position_y},
|
||||
description: description,
|
||||
name: name,
|
||||
system_static_info: system_static_info,
|
||||
labels: labels,
|
||||
locked: locked,
|
||||
status: status,
|
||||
tag: tag,
|
||||
visible: visible
|
||||
}
|
||||
end
|
||||
|
||||
def map_ui_system_static_info(nil), do: %{}
|
||||
|
||||
def map_ui_system_static_info(system_static_info),
|
||||
do:
|
||||
system_static_info
|
||||
|> Map.take([
|
||||
:region_id,
|
||||
:constellation_id,
|
||||
:solar_system_id,
|
||||
:solar_system_name,
|
||||
:solar_system_name_lc,
|
||||
:constellation_name,
|
||||
:region_name,
|
||||
:system_class,
|
||||
:security,
|
||||
:type_description,
|
||||
:class_title,
|
||||
:is_shattered,
|
||||
:effect_name,
|
||||
:effect_power,
|
||||
:statics,
|
||||
:wandering,
|
||||
:triglavian_invasion_status,
|
||||
:sun_type_id
|
||||
])
|
||||
|
||||
def map_ui_kill({solar_system_id, kills}),
|
||||
do: %{solar_system_id: solar_system_id, kills: kills}
|
||||
|
||||
def map_ui_kill(_kill), do: %{}
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -104,20 +104,18 @@
|
||||
id="characters-tracking-table"
|
||||
class="h-[400px] !overflow-y-auto"
|
||||
rows={characters}
|
||||
row_click={fn character -> send(self(), "toggle_track_#{character.id}") end}
|
||||
|
||||
>
|
||||
<:col :let={character} label="Tracked">
|
||||
<div class="flex items-center gap-3">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
phx-click="toggle_track"
|
||||
phx-value-character-id={character.id}
|
||||
id={"character-track-#{character.id}"}
|
||||
checked={character.tracked}
|
||||
/>
|
||||
</label>
|
||||
<label class="flex items-center gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
phx-click="toggle_track"
|
||||
phx-value-character-id={character.id}
|
||||
id={"character-track-#{character.id}"}
|
||||
checked={character.tracked}
|
||||
/>
|
||||
<div class="flex items-center gap-3">
|
||||
<.avatar url={member_icon_url(character.eve_id)} label={character.name} />
|
||||
<div>
|
||||
@@ -127,7 +125,7 @@
|
||||
<div class="text-sm opacity-50"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</:col>
|
||||
</.table>
|
||||
</.async_result>
|
||||
|
||||
@@ -8,11 +8,14 @@ defmodule WandererAppWeb.MapsLive do
|
||||
@pubsub_client Application.compile_env(:wanderer_app, :pubsub_client)
|
||||
|
||||
@impl true
|
||||
def mount(_params, %{"user_id" => user_id} = _session, socket) when not is_nil(user_id) do
|
||||
def mount(
|
||||
_params,
|
||||
%{"user_id" => user_id} = _session,
|
||||
%{assigns: %{current_user: current_user}} = socket
|
||||
)
|
||||
when not is_nil(user_id) do
|
||||
{:ok, active_characters} = WandererApp.Api.Character.active_by_user(%{user_id: user_id})
|
||||
|
||||
current_user = socket.assigns.current_user
|
||||
|
||||
user_characters =
|
||||
active_characters
|
||||
|> Enum.map(&map_character/1)
|
||||
@@ -601,16 +604,6 @@ defmodule WandererAppWeb.MapsLive do
|
||||
|
||||
{added_acls, removed_acls} = map.acls |> Enum.map(& &1.id) |> _get_acls_diff(form["acls"])
|
||||
|
||||
added_acls
|
||||
|> Enum.each(fn acl_id ->
|
||||
:telemetry.execute([:wanderer_app, :map, :acl, :add], %{count: 1})
|
||||
end)
|
||||
|
||||
removed_acls
|
||||
|> Enum.each(fn acl_id ->
|
||||
:telemetry.execute([:wanderer_app, :map, :acl, :remove], %{count: 1})
|
||||
end)
|
||||
|
||||
Phoenix.PubSub.broadcast(
|
||||
WandererApp.PubSub,
|
||||
"maps:#{map.id}",
|
||||
@@ -855,9 +848,9 @@ defmodule WandererAppWeb.MapsLive do
|
||||
|> Enum.map(fn acl -> acl |> Ash.load!(:members) end)
|
||||
|
||||
{:ok, characters_count} =
|
||||
case WandererApp.Api.MapCharacterSettings.tracked_by_map_all(%{
|
||||
map_id: map.id
|
||||
}) do
|
||||
map.id
|
||||
|> WandererApp.MapCharacterSettingsRepo.get_tracked_by_map_all()
|
||||
|> case do
|
||||
{:ok, settings} ->
|
||||
{:ok,
|
||||
settings
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user