mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-11-14 13:16:10 +00:00
Compare commits
72 Commits
v1.19.1
...
ui-issues-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b549189644 | ||
|
|
35279d17b4 | ||
|
|
bb403aa0c5 | ||
|
|
04327c288b | ||
|
|
94d60e40d0 | ||
|
|
8505fcb6b7 | ||
|
|
9aec57166d | ||
|
|
a3739f2950 | ||
|
|
3d3b152758 | ||
|
|
0e03730543 | ||
|
|
97e07a6511 | ||
|
|
a77a51ba15 | ||
|
|
42e706e1c2 | ||
|
|
025dd06053 | ||
|
|
bcb421d879 | ||
|
|
66056ab54b | ||
|
|
bb92f76ceb | ||
|
|
84076b340b | ||
|
|
48caae5c0e | ||
|
|
77dd23795a | ||
|
|
2771d6304e | ||
|
|
9946edffa4 | ||
|
|
50bf2fd9d3 | ||
|
|
bdcde168aa | ||
|
|
5807142e20 | ||
|
|
ec2d9565b9 | ||
|
|
a18a71c73d | ||
|
|
93a6bd1156 | ||
|
|
581a410aef | ||
|
|
ab02fe988c | ||
|
|
b8d20fb21b | ||
|
|
12fa1a0be8 | ||
|
|
85a84f7507 | ||
|
|
2385313013 | ||
|
|
c7ce727571 | ||
|
|
8b165ff478 | ||
|
|
6d7d0cc72d | ||
|
|
f7eba5d4fd | ||
|
|
73ef6dae73 | ||
|
|
7fa6df1e5e | ||
|
|
e1a2ffb151 | ||
|
|
6d7727a32d | ||
|
|
6d7a94bd5a | ||
|
|
ecc3fb17e1 | ||
|
|
209e2bf0a5 | ||
|
|
b1947e57a4 | ||
|
|
74507501a5 | ||
|
|
c73481fd58 | ||
|
|
7795ad0b0c | ||
|
|
aff768f413 | ||
|
|
310b60f5b6 | ||
|
|
100f0be86a | ||
|
|
87e115e40d | ||
|
|
ef5f36e4c4 | ||
|
|
099650420d | ||
|
|
8ccf7fffa5 | ||
|
|
b97a055bf7 | ||
|
|
663fee6699 | ||
|
|
33d5f3938b | ||
|
|
ef6b45d7a1 | ||
|
|
c1ecd3690e | ||
|
|
3250fe1ec6 | ||
|
|
48e8cd93b9 | ||
|
|
afacbb16b6 | ||
|
|
dfad127f32 | ||
|
|
300c1b5a18 | ||
|
|
bb38e1710b | ||
|
|
0857a82de5 | ||
|
|
da5afcc91c | ||
|
|
0002979fda | ||
|
|
080af16d41 | ||
|
|
d03a0b7083 |
211
CHANGELOG.md
211
CHANGELOG.md
@@ -2,6 +2,217 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v1.29.5](https://github.com/wanderer-industries/wanderer/compare/v1.29.4...v1.29.5) (2024-12-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fix character trackers cleanup
|
||||
|
||||
## [v1.29.4](https://github.com/wanderer-industries/wanderer/compare/v1.29.3...v1.29.4) (2024-12-10)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Small fixes
|
||||
|
||||
## [v1.29.3](https://github.com/wanderer-industries/wanderer/compare/v1.29.2...v1.29.3) (2024-12-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Increased eve DB data download timeout
|
||||
|
||||
## [v1.29.2](https://github.com/wanderer-industries/wanderer/compare/v1.29.1...v1.29.2) (2024-12-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fix unpkg CDN issues, fix Abyssals sites adding as systems on map
|
||||
|
||||
## [v1.29.1](https://github.com/wanderer-industries/wanderer/compare/v1.29.0...v1.29.1) (2024-12-05)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.29.0](https://github.com/wanderer-industries/wanderer/compare/v1.28.1...v1.29.0) (2024-12-05)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Signatures: Show 'Unsplashed' signatures on the map (optionally)
|
||||
|
||||
## [v1.28.1](https://github.com/wanderer-industries/wanderer/compare/v1.28.0...v1.28.1) (2024-12-04)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.28.0](https://github.com/wanderer-industries/wanderer/compare/v1.27.1...v1.28.0) (2024-12-04)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: Added an option to show 'Offline characters' to map admins & managers only
|
||||
|
||||
## [v1.27.1](https://github.com/wanderer-industries/wanderer/compare/v1.27.0...v1.27.1) (2024-12-04)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix 'On the map' visibility
|
||||
|
||||
## [v1.27.0](https://github.com/wanderer-industries/wanderer/compare/v1.26.1...v1.27.0) (2024-12-03)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: Hide 'On the map' list for 'Viewer' role
|
||||
|
||||
## [v1.26.1](https://github.com/wanderer-industries/wanderer/compare/v1.26.0...v1.26.1) (2024-12-03)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signatures: Fix error on splash wh
|
||||
|
||||
## [v1.26.0](https://github.com/wanderer-industries/wanderer/compare/v1.25.2...v1.26.0) (2024-12-03)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Signatures: Keep 'Lazy delete' enabled setting
|
||||
|
||||
## [v1.25.2](https://github.com/wanderer-industries/wanderer/compare/v1.25.1...v1.25.2) (2024-12-01)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signatures: Fix lazy delete on system switch
|
||||
|
||||
## [v1.25.1](https://github.com/wanderer-industries/wanderer/compare/v1.25.0...v1.25.1) (2024-11-28)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signatures: Fix colors & add 'Backspace' hotkey to delete signatures
|
||||
|
||||
## [v1.25.0](https://github.com/wanderer-industries/wanderer/compare/v1.24.2...v1.25.0) (2024-11-28)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Signatures: Automatically remove signature if linked system removed
|
||||
|
||||
## [v1.24.2](https://github.com/wanderer-industries/wanderer/compare/v1.24.1...v1.24.2) (2024-11-27)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signatures: Fix paste signatures
|
||||
|
||||
## [v1.24.1](https://github.com/wanderer-industries/wanderer/compare/v1.24.0...v1.24.1) (2024-11-27)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.24.0](https://github.com/wanderer-industries/wanderer/compare/v1.23.0...v1.24.0) (2024-11-27)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Signatures: Added "Lazy delete" option & got rid of update popup
|
||||
|
||||
## [v1.23.0](https://github.com/wanderer-industries/wanderer/compare/v1.22.0...v1.23.0) (2024-11-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: Lock systems available to manager/admin roles only (#75)
|
||||
|
||||
* Map: Lock systems available to manager/admin roles only
|
||||
|
||||
* Map: Fix add system & add acl member select behaviour
|
||||
|
||||
## [v1.22.0](https://github.com/wanderer-industries/wanderer/compare/v1.21.0...v1.22.0) (2024-11-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Map: Rework design of checkboxes in Signatures settings dialog. Rework design of checkboxes in Routes settings dialog. Now signature will deleteing by Delete hotkey was Backspace. Fixed size of group column in signatures list. Instead Updated column will be Added, updated may be turn on in settings. (#76)
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
@@ -466,3 +466,467 @@ 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 */
|
||||
|
||||
.inputContainer {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
align-items: center;
|
||||
}
|
||||
.inputContainer > span:nth-child(1),
|
||||
.inputContainer > label:nth-child(1) {
|
||||
color: var(--gray-200);
|
||||
font-size: 13px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.inputContainer > :nth-child(2) {
|
||||
border-bottom: 2px dotted #3f3f3f;
|
||||
height: 1px;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.smallInputSwitch {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.smallInputSwitch .p-inputswitch {
|
||||
height: 1rem;
|
||||
width: 2rem;
|
||||
}
|
||||
.smallInputSwitch .p-inputswitch.p-inputswitch-checked .p-inputswitch-slider::before {
|
||||
transform: translateX(1rem);
|
||||
}
|
||||
.smallInputSwitch .p-inputswitch.p-highlight .p-inputswitch-slider:before {
|
||||
transform: translateX(1rem);
|
||||
}
|
||||
.smallInputSwitch .p-inputswitch .p-inputswitch-slider::before {
|
||||
width: 0.8rem;
|
||||
height: 0.8rem;
|
||||
margin-top: -0.4rem;
|
||||
margin-left: -3px;
|
||||
}
|
||||
|
||||
.checkboxRoot.sizeXS {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
.checkboxRoot.sizeXS .p-checkbox-box,
|
||||
.checkboxRoot.sizeXS .p-checkbox-input {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
.checkboxRoot.sizeM {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.checkboxRoot.sizeM .p-checkbox-box,
|
||||
.checkboxRoot.sizeM .p-checkbox-input {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ import { PrimeIcons } from 'primereact/api';
|
||||
import { ContextMenuSystemProps } from '@/hooks/Mapper/components/contexts';
|
||||
import { useWaypointMenu } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components';
|
||||
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||
|
||||
export const useContextMenuSystemItems = ({
|
||||
onDeleteSystem,
|
||||
@@ -25,6 +28,7 @@ export const useContextMenuSystemItems = ({
|
||||
const getStatus = useStatusMenu(systems, systemId, onSystemStatus);
|
||||
const getLabels = useLabelsMenu(systems, systemId, onSystemLabels, onCustomLabelDialog);
|
||||
const getWaypointMenu = useWaypointMenu(onWaypointSet);
|
||||
const canLockSystem = useMapCheckPermissions([UserPermission.LOCK_SYSTEM]);
|
||||
|
||||
return useMemo(() => {
|
||||
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
||||
@@ -41,6 +45,8 @@ export const useContextMenuSystemItems = ({
|
||||
<FastSystemActions
|
||||
systemId={systemId}
|
||||
systemName={system.system_static_info.solar_system_name}
|
||||
regionName={system.system_static_info.region_name}
|
||||
isWH={isWormholeSpace(system.system_static_info.system_class)}
|
||||
showEdit
|
||||
onOpenSettings={onOpenSettings}
|
||||
/>
|
||||
@@ -58,19 +64,25 @@ export const useContextMenuSystemItems = ({
|
||||
command: onHubToggle,
|
||||
},
|
||||
...(system.locked
|
||||
? [
|
||||
{
|
||||
label: 'Unlock',
|
||||
icon: PrimeIcons.LOCK_OPEN,
|
||||
command: onLockToggle,
|
||||
},
|
||||
]
|
||||
? canLockSystem
|
||||
? [
|
||||
{
|
||||
label: 'Unlock',
|
||||
icon: PrimeIcons.LOCK_OPEN,
|
||||
command: onLockToggle,
|
||||
},
|
||||
]
|
||||
: []
|
||||
: [
|
||||
{
|
||||
label: 'Lock',
|
||||
icon: PrimeIcons.LOCK,
|
||||
command: onLockToggle,
|
||||
},
|
||||
...(canLockSystem
|
||||
? [
|
||||
{
|
||||
label: 'Lock',
|
||||
icon: PrimeIcons.LOCK,
|
||||
command: onLockToggle,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ separator: true },
|
||||
{
|
||||
label: 'Delete',
|
||||
@@ -80,6 +92,7 @@ export const useContextMenuSystemItems = ({
|
||||
]),
|
||||
];
|
||||
}, [
|
||||
canLockSystem,
|
||||
systems,
|
||||
systemId,
|
||||
getTags,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/ty
|
||||
import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components';
|
||||
import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||
|
||||
export interface ContextMenuSystemInfoProps {
|
||||
systemStatics: Map<number, SolarSystemStaticInfoRaw>;
|
||||
@@ -48,7 +49,6 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
|
||||
if (!systemId || !system) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
className: classes.FastActions,
|
||||
@@ -57,6 +57,8 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
|
||||
<FastSystemActions
|
||||
systemId={systemId}
|
||||
systemName={system.solar_system_name}
|
||||
regionName={system.region_name}
|
||||
isWH={isWormholeSpace(system.system_class)}
|
||||
onOpenSettings={onOpenSettings}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -9,13 +9,22 @@ import { PrimeIcons } from 'primereact/api';
|
||||
export interface FastSystemActionsProps {
|
||||
systemId: string;
|
||||
systemName: string;
|
||||
regionName: string;
|
||||
isWH: boolean;
|
||||
showEdit?: boolean;
|
||||
onOpenSettings(): void;
|
||||
}
|
||||
|
||||
export const FastSystemActions = ({ systemId, systemName, onOpenSettings, showEdit }: FastSystemActionsProps) => {
|
||||
const ref = useRef({ systemId, systemName });
|
||||
ref.current = { systemId, systemName };
|
||||
export const FastSystemActions = ({
|
||||
systemId,
|
||||
systemName,
|
||||
regionName,
|
||||
isWH,
|
||||
onOpenSettings,
|
||||
showEdit,
|
||||
}: FastSystemActionsProps) => {
|
||||
const ref = useRef({ systemId, systemName, regionName, isWH });
|
||||
ref.current = { systemId, systemName, regionName, isWH };
|
||||
|
||||
const handleOpenZKB = useCallback(
|
||||
() => window.open(`https://zkillboard.com/system/${ref.current.systemId}`, '_blank'),
|
||||
@@ -27,10 +36,17 @@ export const FastSystemActions = ({ systemId, systemName, onOpenSettings, showEd
|
||||
[],
|
||||
);
|
||||
|
||||
const handleOpenDotlan = useCallback(
|
||||
() => window.open(`https://evemaps.dotlan.net/system/${ref.current.systemName}`, '_blank'),
|
||||
[],
|
||||
);
|
||||
const handleOpenDotlan = useCallback(() => {
|
||||
if (ref.current.isWH) {
|
||||
window.open(`https://evemaps.dotlan.net/system/${ref.current.systemName}`, '_blank');
|
||||
return;
|
||||
}
|
||||
|
||||
return window.open(
|
||||
`https://evemaps.dotlan.net/map/${ref.current.regionName.replace(/ /gim, '_')}/${ref.current.systemName}#jumps`,
|
||||
'_blank',
|
||||
);
|
||||
}, []);
|
||||
|
||||
const copySystemNameToClipboard = useCallback(async () => {
|
||||
try {
|
||||
@@ -43,9 +59,9 @@ export const FastSystemActions = ({ systemId, systemName, onOpenSettings, showEd
|
||||
return (
|
||||
<LayoutEventBlocker className={clsx('flex px-2 gap-2 justify-between items-center h-full')}>
|
||||
<div className={clsx('flex gap-2 items-center h-full', classes.Links)}>
|
||||
<WdImgButton source={ZKB_ICON} onClick={handleOpenZKB} />
|
||||
<WdImgButton source={ANOIK_ICON} onClick={handleOpenAnoikis} />
|
||||
<WdImgButton source={DOTLAN_ICON} onClick={handleOpenDotlan} />
|
||||
<WdImgButton tooltip={{ content: 'Open zkillboard' }} source={ZKB_ICON} onClick={handleOpenZKB} />
|
||||
<WdImgButton tooltip={{ content: 'Open Anoikis' }} source={ANOIK_ICON} onClick={handleOpenAnoikis} />
|
||||
<WdImgButton tooltip={{ content: 'Open Dotlan' }} source={DOTLAN_ICON} onClick={handleOpenDotlan} />
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center pl-1">
|
||||
|
||||
@@ -14,6 +14,9 @@ export const useSystemInfo = ({ systemId }: UseSystemInfoProps) => {
|
||||
|
||||
const { systems: systemStatics } = useLoadSystemStatic({ systems: [systemId] });
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('JOipP', `systemStatics`, systemStatics);
|
||||
|
||||
return useMemo(() => {
|
||||
const staticInfo = systemStatics.get(parseInt(systemId));
|
||||
const dynamicInfo = getSystemById(systems, systemId);
|
||||
|
||||
@@ -2,3 +2,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.BackgroundAlternateColor {
|
||||
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ import ReactFlow, {
|
||||
OnSelectionChangeFunc,
|
||||
SelectionDragHandler,
|
||||
SelectionMode,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
useReactFlow,
|
||||
} from 'reactflow';
|
||||
import 'reactflow/dist/style.css';
|
||||
@@ -21,7 +19,7 @@ import classes from './Map.module.scss';
|
||||
import './styles/neon-theme.scss';
|
||||
import './styles/eve-common.scss';
|
||||
import { MapProvider, useMapState } from './MapProvider';
|
||||
import { useMapHandlers, useUpdateNodes } from './hooks';
|
||||
import { useNodesState, useEdgesState, useMapHandlers, useUpdateNodes } from './hooks';
|
||||
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import {
|
||||
ContextMenuConnection,
|
||||
@@ -36,6 +34,7 @@ import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
|
||||
import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
|
||||
|
||||
@@ -99,6 +98,8 @@ interface MapCompProps {
|
||||
onSystemContextMenu: (event: MouseEvent<Element>, systemId: string) => void;
|
||||
showKSpaceBG?: boolean;
|
||||
isThickConnections?: boolean;
|
||||
isShowBackgroundPattern?: boolean;
|
||||
isSoftBackground?: boolean;
|
||||
}
|
||||
|
||||
const MapComp = ({
|
||||
@@ -113,10 +114,12 @@ const MapComp = ({
|
||||
isShowMinimap,
|
||||
showKSpaceBG,
|
||||
isThickConnections,
|
||||
isShowBackgroundPattern,
|
||||
isSoftBackground,
|
||||
}: 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);
|
||||
@@ -218,7 +221,7 @@ const MapComp = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.MapRoot}>
|
||||
<div className={clsx(classes.MapRoot, { ['bg-neutral-900']: isSoftBackground })}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
@@ -265,7 +268,7 @@ const MapComp = ({
|
||||
selectionMode={SelectionMode.Partial}
|
||||
>
|
||||
{isShowMinimap && <MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} />}
|
||||
<Background />
|
||||
{isShowBackgroundPattern && <Background />}
|
||||
</ReactFlow>
|
||||
{/* <button className="z-auto btn btn-primary absolute top-20 right-20" onClick={handleGetPassages}>
|
||||
Test // DON NOT REMOVE
|
||||
|
||||
@@ -32,6 +32,7 @@ const INITIAL_DATA: MapData = {
|
||||
visibleNodes: new Set(),
|
||||
showKSpaceBG: false,
|
||||
isThickConnections: false,
|
||||
userPermissions: {},
|
||||
};
|
||||
|
||||
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,
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
stroke: #d4f0ff;
|
||||
}
|
||||
|
||||
&.Gate {
|
||||
stroke: #1c1e15;
|
||||
}
|
||||
|
||||
&.Hovered {
|
||||
stroke: #4e5d6c;
|
||||
stroke-width: 2px;
|
||||
@@ -76,6 +80,11 @@
|
||||
stroke-width: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&.Gate {
|
||||
stroke: #9aff40;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ClickPath {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
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';
|
||||
@@ -33,6 +33,7 @@ const MAP_OFFSETS: Record<string, { x: number; y: number }> = {
|
||||
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 },
|
||||
@@ -45,7 +46,9 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
|
||||
|
||||
const offset = isThickConnections ? MAP_OFFSETS_TICK[targetPos] : MAP_OFFSETS[targetPos];
|
||||
|
||||
const [edgePath, labelX, labelY] = getBezierPath({
|
||||
const method = isWormhole ? getBezierPath : getSmoothStepPath;
|
||||
|
||||
const [edgePath, labelX, labelY] = method({
|
||||
sourceX: sx - offset.x,
|
||||
sourceY: sy - offset.y,
|
||||
sourcePosition: sourcePos,
|
||||
@@ -53,8 +56,9 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
|
||||
targetX: tx + offset.x,
|
||||
targetY: ty + offset.y,
|
||||
});
|
||||
|
||||
return [edgePath, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos];
|
||||
}, [isThickConnections, sourceNode, targetNode]);
|
||||
}, [isThickConnections, sourceNode, targetNode, isWormhole]);
|
||||
|
||||
if (!sourceNode || !targetNode || !data) {
|
||||
return null;
|
||||
@@ -66,8 +70,9 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
|
||||
id={`back_${id}`}
|
||||
className={clsx(classes.EdgePathBack, {
|
||||
[classes.Tick]: isThickConnections,
|
||||
[classes.TimeCrit]: data.time_status === TimeStatus.eol,
|
||||
[classes.TimeCrit]: isWormhole && data.time_status === TimeStatus.eol,
|
||||
[classes.Hovered]: hovered,
|
||||
[classes.Gate]: !isWormhole,
|
||||
})}
|
||||
d={path}
|
||||
markerEnd={markerEnd}
|
||||
@@ -78,9 +83,10 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
|
||||
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}
|
||||
@@ -120,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(
|
||||
|
||||
@@ -6,7 +6,7 @@ $pastel-green: #88b04b;
|
||||
$pastel-yellow: #ffdd59;
|
||||
$dark-bg: #2d2d2d;
|
||||
$text-color: #ffffff;
|
||||
$tooltip-bg: #202020; // Темный фон для подсказок
|
||||
$tooltip-bg: #202020; // Dark background for tooltips
|
||||
|
||||
.RootCustomNode {
|
||||
display: flex;
|
||||
@@ -136,7 +136,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
.Bookmarks {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 0;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
left: 4px;
|
||||
|
||||
@@ -182,6 +182,42 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
}
|
||||
}
|
||||
|
||||
.Unsplashed {
|
||||
position: absolute;
|
||||
width: calc(50% - 4px);
|
||||
z-index: -1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
left: 2px;
|
||||
|
||||
&--right {
|
||||
left: calc(50% + 6px);
|
||||
}
|
||||
|
||||
& > .Signature {
|
||||
width: 13px;
|
||||
height: 4px;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
border-radius: 5px;
|
||||
color: #ffffff;
|
||||
font-size: 8px;
|
||||
text-align: center;
|
||||
padding-top: 2px;
|
||||
font-weight: bolder;
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
display: block;
|
||||
|
||||
background-color: #833ca4;
|
||||
|
||||
&:not(:first-child) {
|
||||
box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
@@ -276,7 +312,6 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.Handlers {
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Handle, Position, WrapNodeProps } from 'reactflow';
|
||||
import { MapSolarSystemType } from '../../map.types';
|
||||
import classes from './SolarSystemNode.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
import {
|
||||
EFFECT_BACKGROUND_STYLES,
|
||||
LABELS_INFO,
|
||||
@@ -12,8 +14,9 @@ import {
|
||||
} from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||
import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp';
|
||||
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
|
||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
import { getSystemClassStyles } from '@/hooks/Mapper/components/map/helpers';
|
||||
import { getSystemClassStyles, prepareUnsplashedChunks } from '@/hooks/Mapper/components/map/helpers';
|
||||
import { sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
|
||||
@@ -50,6 +53,9 @@ export const getActivityType = (count: number) => {
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarSystemType>) => {
|
||||
const { interfaceSettings } = useMapRootState();
|
||||
const { isShowUnsplashedSignatures } = interfaceSettings;
|
||||
|
||||
const {
|
||||
system_class,
|
||||
security,
|
||||
@@ -63,6 +69,8 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
|
||||
solar_system_name,
|
||||
} = data.system_static_info;
|
||||
|
||||
const signatures = data.system_signatures;
|
||||
|
||||
const { locked, name, tag, status, labels, id } = data || {};
|
||||
|
||||
const customName = solar_system_name !== name ? name : undefined;
|
||||
@@ -128,6 +136,22 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
|
||||
const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
|
||||
const regionClass = showKSpaceBG ? SpaceToClass[space] : null;
|
||||
|
||||
const [unsplashedLeft, unsplashedRight] = useMemo(() => {
|
||||
if (!isShowUnsplashedSignatures) {
|
||||
return [[], []];
|
||||
}
|
||||
|
||||
return prepareUnsplashedChunks(
|
||||
signatures
|
||||
.filter(s => s.group === 'Wormhole' && !s.linked_system)
|
||||
.map(s => ({
|
||||
eve_id: s.eve_id,
|
||||
type: s.type,
|
||||
custom_info: s.custom_info,
|
||||
})),
|
||||
);
|
||||
}, [isShowUnsplashedSignatures, signatures]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{visible && (
|
||||
@@ -237,6 +261,22 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
|
||||
)}
|
||||
</div>
|
||||
|
||||
{visible && isShowUnsplashedSignatures && (
|
||||
<div className={classes.Unsplashed}>
|
||||
{unsplashedLeft.map(x => (
|
||||
<UnsplashedSignature key={x.sig_id} signature={x} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{visible && isShowUnsplashedSignatures && (
|
||||
<div className={clsx([classes.Unsplashed, classes['Unsplashed--right']])}>
|
||||
{unsplashedRight.map(x => (
|
||||
<UnsplashedSignature key={x.sig_id} signature={x} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div onMouseDownCapture={dbClick} className={classes.Handlers}>
|
||||
<Handle
|
||||
type="source"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
||||
|
||||
.Signature {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
display: block;
|
||||
|
||||
& > .Box {
|
||||
width: 13px;
|
||||
height: 4px;
|
||||
border-radius: 4px;
|
||||
color: #ffffff;
|
||||
font-size: 8px;
|
||||
text-align: center;
|
||||
font-weight: bolder;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { InfoDrawer } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
import classes from './UnsplashedSignature.module.scss';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { WORMHOLE_CLASS_STYLES, WORMHOLES_ADDITIONAL_INFO } from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import { useMemo } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { renderInfoColumn } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
||||
|
||||
import { k162Types } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
|
||||
|
||||
interface UnsplashedSignatureProps {
|
||||
signature: SystemSignature;
|
||||
}
|
||||
export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) => {
|
||||
const {
|
||||
data: { wormholesData },
|
||||
} = useMapRootState();
|
||||
|
||||
const whData = useMemo(() => wormholesData[signature.type], [signature.type, wormholesData]);
|
||||
const whClass = useMemo(() => (whData ? WORMHOLES_ADDITIONAL_INFO[whData.dest] : null), [whData]);
|
||||
|
||||
const k162TypeOption = useMemo(() => {
|
||||
if (!signature.custom_info) {
|
||||
return null;
|
||||
}
|
||||
const customInfo = JSON.parse(signature.custom_info);
|
||||
if (!customInfo.k162Type) {
|
||||
return null;
|
||||
}
|
||||
return k162Types.find(x => x.value === customInfo.k162Type);
|
||||
}, [signature]);
|
||||
|
||||
const whClassStyle = useMemo(() => {
|
||||
if (signature.type === 'K162' && k162TypeOption) {
|
||||
const k162Data = wormholesData[k162TypeOption.whClassName];
|
||||
const k162Class = k162Data ? WORMHOLES_ADDITIONAL_INFO[k162Data.dest] : null;
|
||||
return k162Class ? WORMHOLE_CLASS_STYLES[k162Class.wormholeClassID] : '';
|
||||
}
|
||||
return whClass ? WORMHOLE_CLASS_STYLES[whClass.wormholeClassID] : '';
|
||||
}, [signature, whClass, k162TypeOption, wormholesData]);
|
||||
|
||||
return (
|
||||
<WdTooltipWrapper
|
||||
className={clsx(classes.Signature)}
|
||||
content={
|
||||
(
|
||||
<div className="flex flex-col gap-1">
|
||||
<InfoDrawer title={<b className="text-slate-50">{signature.eve_id}</b>}>
|
||||
{renderInfoColumn(signature)}
|
||||
</InfoDrawer>
|
||||
</div>
|
||||
) as React.ReactNode
|
||||
}
|
||||
>
|
||||
<div className={clsx(classes.Box, whClassStyle)}>
|
||||
<svg width="13" height="4" viewBox="0 0 13 4" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="13" height="4" rx="2" className={whClassStyle} fill="currentColor" />
|
||||
</svg>
|
||||
</div>
|
||||
</WdTooltipWrapper>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './UnsplashedSignature.tsx';
|
||||
@@ -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 = {
|
||||
|
||||
@@ -3,3 +3,4 @@ export * from './convertSystem2Node';
|
||||
export * from './getSystemClassStyles';
|
||||
export * from './getShapeClass';
|
||||
export * from './getBackgroundClass';
|
||||
export * from './prepareUnsplashedChunks';
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Helper function to split an array into chunks of size
|
||||
const chunkArray = (array: any[], size: number) => {
|
||||
const chunks = [];
|
||||
for (let i = 0; i < array.length; i += size) {
|
||||
chunks.push(array.slice(i, i + size));
|
||||
}
|
||||
return chunks;
|
||||
};
|
||||
|
||||
export const prepareUnsplashedChunks = (items: any[]) => {
|
||||
// Split the items into chunks of 4
|
||||
const chunks = chunkArray(items, 4);
|
||||
|
||||
// Get the column elements
|
||||
const leftColumn: any[] = [];
|
||||
const rightColumn: any[] = [];
|
||||
|
||||
chunks.forEach((chunk, index) => {
|
||||
const column = index % 2 === 0 ? leftColumn : rightColumn;
|
||||
|
||||
chunk.forEach(item => {
|
||||
column.push(item);
|
||||
});
|
||||
});
|
||||
|
||||
return [leftColumn, rightColumn];
|
||||
};
|
||||
@@ -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);
|
||||
}, []);
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './useMapHandlers';
|
||||
export * from './useUpdateNodes';
|
||||
export * from './useNodesEdgesState';
|
||||
|
||||
@@ -112,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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,6 +6,7 @@ import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CommandLinkSignatureToSystem } from '@/hooks/Mapper/types';
|
||||
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
|
||||
import { SHOW_DESCRIPTION_COLUMN_SETTING } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatures';
|
||||
import {
|
||||
Setting,
|
||||
COSMIC_SIGNATURE,
|
||||
@@ -20,6 +21,7 @@ interface SystemLinkSignatureDialogProps {
|
||||
const signatureSettings: Setting[] = [
|
||||
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true },
|
||||
{ key: SignatureGroup.Wormhole, name: 'Wormhole', value: true },
|
||||
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: true, isFilter: false },
|
||||
];
|
||||
|
||||
export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
||||
@@ -8,6 +8,10 @@ import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
|
||||
import { CharacterCard, LayoutEventBlocker, WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { sortCharacters } from '@/hooks/Mapper/components/mapInterface/helpers/sortCharacters.ts';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
import { useMapCheckPermissions, useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
|
||||
type CharItemProps = {
|
||||
compact: boolean;
|
||||
@@ -62,6 +66,14 @@ export const LocalCharacters = () => {
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
|
||||
const restrictOfflineShowing = useMapGetOption('restrict_offline_showing');
|
||||
const isAdminOrManager = useMapCheckPermissions([UserPermission.MANAGE_MAP]);
|
||||
|
||||
const showOffline = useMemo(
|
||||
() => !restrictOfflineShowing || isAdminOrManager,
|
||||
[isAdminOrManager, restrictOfflineShowing],
|
||||
);
|
||||
|
||||
const itemTemplate = useItemTemplate();
|
||||
|
||||
const sorted = useMemo(() => {
|
||||
@@ -70,32 +82,39 @@ export const LocalCharacters = () => {
|
||||
.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id), compact: settings.compact }))
|
||||
.sort(sortCharacters);
|
||||
|
||||
if (!settings.showOffline) {
|
||||
if (!showOffline || !settings.showOffline) {
|
||||
return sorted.filter(c => c.online);
|
||||
}
|
||||
|
||||
return sorted;
|
||||
// eslint-disable-next-line
|
||||
}, [characters, settings.showOffline, settings.compact, systemId, userCharacters, presentCharacters]);
|
||||
}, [showOffline, characters, settings.showOffline, settings.compact, systemId, userCharacters, presentCharacters]);
|
||||
|
||||
const isNobodyHere = sorted.length === 0;
|
||||
const isNotSelectedSystem = selectedSystems.length !== 1;
|
||||
const showList = sorted.length > 0 && selectedSystems.length === 1;
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const compact = useMaxWidth(ref, 145);
|
||||
|
||||
return (
|
||||
<Widget
|
||||
label={
|
||||
<div className="flex justify-between items-center text-xs w-full">
|
||||
<div className="flex justify-between items-center text-xs w-full" ref={ref}>
|
||||
<span className="select-none">Local{showList ? ` [${sorted.length}]` : ''}</span>
|
||||
<LayoutEventBlocker className="flex items-center gap-2">
|
||||
<WdCheckbox
|
||||
size="xs"
|
||||
labelSide="left"
|
||||
label={'Show offline'}
|
||||
value={settings.showOffline}
|
||||
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300"
|
||||
onChange={() => setSettings(() => ({ ...settings, showOffline: !settings.showOffline }))}
|
||||
/>
|
||||
{showOffline && (
|
||||
<WdTooltipWrapper content="Show offline characters in system">
|
||||
<WdCheckbox
|
||||
size="xs"
|
||||
labelSide="left"
|
||||
label={compact ? '' : 'Show offline'}
|
||||
value={settings.showOffline}
|
||||
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300"
|
||||
onChange={() => setSettings(() => ({ ...settings, showOffline: !settings.showOffline }))}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
|
||||
<span
|
||||
className={clsx('w-4 h-4 cursor-pointer', {
|
||||
@@ -115,7 +134,9 @@ export const LocalCharacters = () => {
|
||||
)}
|
||||
|
||||
{isNobodyHere && !isNotSelectedSystem && (
|
||||
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">Nobody here</div>
|
||||
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">
|
||||
Nobody here
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showList && (
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
|
||||
import {
|
||||
RoutesType,
|
||||
useRouteProvider,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
|
||||
import { CheckboxChangeEvent } from 'primereact/checkbox';
|
||||
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
|
||||
|
||||
interface RoutesSettingsDialog {
|
||||
visible: boolean;
|
||||
@@ -38,8 +37,8 @@ export const RoutesSettingsDialog = ({ visible, setVisible }: RoutesSettingsDial
|
||||
currentData.current = data;
|
||||
|
||||
const handleChangeEvent = useCallback(
|
||||
(propName: keyof RoutesType) => (event: CheckboxChangeEvent) => {
|
||||
optionsRef.current = { ...optionsRef.current, [propName]: event.checked };
|
||||
(propName: keyof RoutesType) => (event: boolean) => {
|
||||
optionsRef.current = { ...optionsRef.current, [propName]: event };
|
||||
updateKey(x => x + 1);
|
||||
},
|
||||
[],
|
||||
@@ -71,14 +70,14 @@ export const RoutesSettingsDialog = ({ visible, setVisible }: RoutesSettingsDial
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-3 p-2.5">
|
||||
<div className="flex flex-col gap-2 mb-2">
|
||||
{checkboxes.map(({ label, propName }) => (
|
||||
<WdCheckbox
|
||||
<PrettySwitchbox
|
||||
key={propName}
|
||||
label={label}
|
||||
value={optionsRef.current[propName]}
|
||||
onChange={handleChangeEvent(propName)}
|
||||
checked={optionsRef.current[propName]}
|
||||
setChecked={handleChangeEvent(propName)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,8 @@ import { PrimeIcons } from 'primereact/api';
|
||||
import { RoutesSettingsDialog } from './RoutesSettingsDialog';
|
||||
import { RoutesProvider, useRouteProvider } from './RoutesProvider.tsx';
|
||||
import { ContextMenuSystemInfo, useContextMenuSystemInfoHandlers } from '@/hooks/Mapper/components/contexts';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
|
||||
const sortByDist = (a: Route, b: Route) => {
|
||||
const distA = a.has_connection ? a.systems?.length || 0 : Infinity;
|
||||
@@ -170,20 +172,25 @@ export const RoutesWidgetComp = () => {
|
||||
});
|
||||
}, [data, update]);
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const compact = useMaxWidth(ref, 155);
|
||||
|
||||
return (
|
||||
<Widget
|
||||
label={
|
||||
<div className="flex justify-between items-center text-xs w-full">
|
||||
<div className="flex justify-between items-center text-xs w-full" ref={ref}>
|
||||
<span className="select-none">Routes</span>
|
||||
<LayoutEventBlocker className="flex items-center gap-2">
|
||||
<WdCheckbox
|
||||
size="xs"
|
||||
labelSide="left"
|
||||
label={'Show shortest'}
|
||||
value={!isSecure}
|
||||
onChange={handleSecureChange}
|
||||
classNameLabel={clsx('text-red-400')}
|
||||
/>
|
||||
<WdTooltipWrapper content="Show shortest route">
|
||||
<WdCheckbox
|
||||
size="xs"
|
||||
labelSide="left"
|
||||
label={compact ? '' : 'Show shortest'}
|
||||
value={!isSecure}
|
||||
onChange={handleSecureChange}
|
||||
classNameLabel={clsx('text-red-400')}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
<WdImgButton className={PrimeIcons.SLIDERS_H} onClick={() => setRouteSettingsVisible(true)} />
|
||||
</LayoutEventBlocker>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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';
|
||||
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
|
||||
|
||||
export type Setting = { key: string; name: string; value: boolean; isFilter?: boolean };
|
||||
|
||||
@@ -41,8 +41,8 @@ export const SystemSignatureSettingsDialog = ({
|
||||
}, [onSave, settings]);
|
||||
|
||||
return (
|
||||
<Dialog header="System Signatures Settings" visible={true} onHide={onCancel} className="w-full max-w-lg">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Dialog header="System Signatures Settings" visible={true} onHide={onCancel} className="w-full max-w-lg h-[500px]">
|
||||
<div className="flex flex-col gap-3 justify-between h-full">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className={styles.verticalTabsContainer}>
|
||||
<TabView
|
||||
@@ -54,16 +54,12 @@ export const SystemSignatureSettingsDialog = ({
|
||||
<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>
|
||||
<PrettySwitchbox
|
||||
key={setting.key}
|
||||
label={setting.name}
|
||||
checked={setting.value}
|
||||
setChecked={() => handleSettingsChange(setting.key)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -72,16 +68,12 @@ export const SystemSignatureSettingsDialog = ({
|
||||
<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>
|
||||
<PrettySwitchbox
|
||||
key={setting.key}
|
||||
label={setting.name}
|
||||
checked={setting.value}
|
||||
setChecked={() => handleSettingsChange(setting.key)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { InfoDrawer, LayoutEventBlocker, TooltipPosition, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import {
|
||||
InfoDrawer,
|
||||
LayoutEventBlocker,
|
||||
TooltipPosition,
|
||||
WdImgButton,
|
||||
WdCheckbox,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { SystemSignaturesContent } from './SystemSignaturesContent';
|
||||
import {
|
||||
Setting,
|
||||
@@ -14,19 +20,26 @@ import {
|
||||
} from './SystemSignatureSettingsDialog';
|
||||
import { SignatureGroup } from '@/hooks/Mapper/types';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState, useMemo, useRef } from 'react';
|
||||
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CheckboxChangeEvent } from 'primereact/checkbox';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
|
||||
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v4_1';
|
||||
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v5_2';
|
||||
export const SHOW_DESCRIPTION_COLUMN_SETTING = 'show_description_column_setting';
|
||||
export const SHOW_INSERTED_COLUMN_SETTING = 'show_inserted_column_setting';
|
||||
export const SHOW_UPDATED_COLUMN_SETTING = 'SHOW_UPDATED_COLUMN_SETTING';
|
||||
export const LAZY_DELETE_SIGNATURES_SETTING = 'LAZY_DELETE_SIGNATURES_SETTING';
|
||||
export const KEEP_LAZY_DELETE_SETTING = 'KEEP_LAZY_DELETE_ENABLED_SETTING';
|
||||
|
||||
const settings: Setting[] = [
|
||||
{ key: SHOW_INSERTED_COLUMN_SETTING, name: 'Show Inserted Column', value: false, isFilter: false },
|
||||
{ key: SHOW_UPDATED_COLUMN_SETTING, name: 'Show Updated Column', value: false, isFilter: false },
|
||||
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: false, isFilter: false },
|
||||
{ key: LAZY_DELETE_SIGNATURES_SETTING, name: 'Lazy Delete Signatures', value: false, isFilter: false },
|
||||
{ key: KEEP_LAZY_DELETE_SETTING, name: 'Keep "Lazy Delete" Enabled', 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 },
|
||||
@@ -58,12 +71,25 @@ export const SystemSignatures = () => {
|
||||
|
||||
const isNotSelectedSystem = selectedSystems.length !== 1;
|
||||
|
||||
const lazyDeleteValue = useMemo(() => {
|
||||
return settings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)!.value;
|
||||
}, [settings]);
|
||||
|
||||
const handleSettingsChange = useCallback((settings: Setting[]) => {
|
||||
setSettings(settings);
|
||||
localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(settings));
|
||||
setVisible(false);
|
||||
}, []);
|
||||
|
||||
const handleLazyDeleteChange = useCallback((value: boolean) => {
|
||||
setSettings(settings => {
|
||||
const lazyDelete = settings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)!;
|
||||
lazyDelete.value = value;
|
||||
localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(settings));
|
||||
return [...settings];
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const restoredSettings = localStorage.getItem(SIGNATURE_SETTINGS_KEY);
|
||||
|
||||
@@ -72,13 +98,27 @@ export const SystemSignatures = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const compact = useMaxWidth(ref, 260);
|
||||
|
||||
return (
|
||||
<Widget
|
||||
label={
|
||||
<div className="flex justify-between items-center text-xs w-full h-full">
|
||||
<div className="flex gap-1">System Signatures</div>
|
||||
<div className="flex justify-between items-center text-xs w-full h-full" ref={ref}>
|
||||
<div className="flex gap-1 whitespace-nowrap text-ellipsis overflow-hidden">System Signatures</div>
|
||||
|
||||
<LayoutEventBlocker className="flex gap-2.5">
|
||||
<WdTooltipWrapper content="Enable Lazy delete">
|
||||
<WdCheckbox
|
||||
size="xs"
|
||||
labelSide="left"
|
||||
label={compact ? '' : 'Lazy delete'}
|
||||
value={lazyDeleteValue}
|
||||
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300 whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
onChange={(event: CheckboxChangeEvent) => handleLazyDeleteChange(!!event.checked)}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
<WdImgButton
|
||||
className={PrimeIcons.QUESTION_CIRCLE}
|
||||
tooltip={{
|
||||
@@ -102,7 +142,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">Backspace</b>
|
||||
<br /> and then use <b className="text-sky-500">Del</b>
|
||||
</InfoDrawer>
|
||||
</div>
|
||||
) as React.ReactNode,
|
||||
@@ -118,7 +158,7 @@ export const SystemSignatures = () => {
|
||||
System is not selected
|
||||
</div>
|
||||
) : (
|
||||
<SystemSignaturesContent systemId={systemId} settings={settings} />
|
||||
<SystemSignaturesContent systemId={systemId} settings={settings} onLazyDeleteChange={handleLazyDeleteChange} />
|
||||
)}
|
||||
{visible && (
|
||||
<SystemSignatureSettingsDialog
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
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';
|
||||
@@ -12,6 +11,7 @@ import useRefState from 'react-usestateref';
|
||||
import { Setting } from '../SystemSignatureSettingsDialog';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
|
||||
import { useClipboard } from '@/hooks/Mapper/hooks/useClipboard';
|
||||
|
||||
import classes from './SystemSignaturesContent.module.scss';
|
||||
import clsx from 'clsx';
|
||||
@@ -22,10 +22,10 @@ import {
|
||||
getRowColorByTimeLeft,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers';
|
||||
import {
|
||||
renderAddedTimeLeft,
|
||||
renderDescription,
|
||||
renderIcon,
|
||||
renderInfoColumn,
|
||||
renderInsertedTimeLeft,
|
||||
renderUpdatedTimeLeft,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
@@ -36,7 +36,9 @@ import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrap
|
||||
import { COSMIC_SIGNATURE } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
|
||||
import {
|
||||
SHOW_DESCRIPTION_COLUMN_SETTING,
|
||||
SHOW_INSERTED_COLUMN_SETTING,
|
||||
SHOW_UPDATED_COLUMN_SETTING,
|
||||
LAZY_DELETE_SIGNATURES_SETTING,
|
||||
KEEP_LAZY_DELETE_SETTING,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures';
|
||||
type SystemSignaturesSortSettings = {
|
||||
sortField: string;
|
||||
@@ -44,7 +46,7 @@ type SystemSignaturesSortSettings = {
|
||||
};
|
||||
|
||||
const SORT_DEFAULT_VALUES: SystemSignaturesSortSettings = {
|
||||
sortField: 'updated_at',
|
||||
sortField: 'inserted_at',
|
||||
sortOrder: -1,
|
||||
};
|
||||
|
||||
@@ -54,6 +56,7 @@ interface SystemSignaturesContentProps {
|
||||
hideLinkedSignatures?: boolean;
|
||||
selectable?: boolean;
|
||||
onSelect?: (signature: SystemSignature) => void;
|
||||
onLazyDeleteChange?: (value: boolean) => void;
|
||||
}
|
||||
export const SystemSignaturesContent = ({
|
||||
systemId,
|
||||
@@ -61,14 +64,13 @@ export const SystemSignaturesContent = ({
|
||||
hideLinkedSignatures,
|
||||
selectable,
|
||||
onSelect,
|
||||
onLazyDeleteChange,
|
||||
}: SystemSignaturesContentProps) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
|
||||
const [signatures, setSignatures, signaturesRef] = useRefState<SystemSignature[]>([]);
|
||||
const [selectedSignatures, setSelectedSignatures] = useState<SystemSignature[]>([]);
|
||||
const [nameColumnWidth, setNameColumnWidth] = useState('auto');
|
||||
const [parsedSignatures, setParsedSignatures] = useState<SystemSignature[]>([]);
|
||||
const [askUser, setAskUser] = useState(false);
|
||||
const [selectedSignature, setSelectedSignature] = useState<SystemSignature | null>(null);
|
||||
|
||||
const [hoveredSig, setHoveredSig] = useState<SystemSignature | null>(null);
|
||||
@@ -80,10 +82,20 @@ export const SystemSignaturesContent = ({
|
||||
const tableRef = useRef<HTMLDivElement>(null);
|
||||
const compact = useMaxWidth(tableRef, 260);
|
||||
const medium = useMaxWidth(tableRef, 380);
|
||||
const refData = useRef({ selectable });
|
||||
refData.current = { selectable };
|
||||
|
||||
const tooltipRef = useRef<WdTooltipHandlers>(null);
|
||||
|
||||
const { clipboardContent } = useClipboard();
|
||||
const { clipboardContent, setClipboardContent } = useClipboard();
|
||||
|
||||
const lazyDeleteValue = useMemo(() => {
|
||||
return settings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)?.value ?? false;
|
||||
}, [settings]);
|
||||
|
||||
const keepLazyDeleteValue = useMemo(() => {
|
||||
return settings.find(setting => setting.key === KEEP_LAZY_DELETE_SETTING)?.value ?? false;
|
||||
}, [settings]);
|
||||
|
||||
const handleResize = useCallback(() => {
|
||||
if (tableRef.current) {
|
||||
@@ -100,10 +112,7 @@ export const SystemSignaturesContent = ({
|
||||
[settings],
|
||||
);
|
||||
|
||||
const showInsertedColumn = useMemo(
|
||||
() => settings.find(s => s.key === SHOW_INSERTED_COLUMN_SETTING)?.value,
|
||||
[settings],
|
||||
);
|
||||
const showUpdatedColumn = useMemo(() => settings.find(s => s.key === SHOW_UPDATED_COLUMN_SETTING)?.value, [settings]);
|
||||
|
||||
const filteredSignatures = useMemo(() => {
|
||||
return signatures
|
||||
@@ -136,13 +145,17 @@ export const SystemSignaturesContent = ({
|
||||
data: { system_id: systemId },
|
||||
});
|
||||
|
||||
setAskUser(false);
|
||||
setSignatures(signatures);
|
||||
}, [outCommand, systemId]);
|
||||
|
||||
const handleUpdateSignatures = useCallback(
|
||||
async (newSignatures: SystemSignature[], updateOnly: boolean) => {
|
||||
const { added, updated, removed } = getActualSigs(signaturesRef.current, newSignatures, updateOnly);
|
||||
async (newSignatures: SystemSignature[], updateOnly: boolean, skipUpdateUntouched?: boolean) => {
|
||||
const { added, updated, removed } = getActualSigs(
|
||||
signaturesRef.current,
|
||||
newSignatures,
|
||||
updateOnly,
|
||||
skipUpdateUntouched,
|
||||
);
|
||||
|
||||
const { signatures: updatedSignatures } = await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
@@ -160,34 +173,32 @@ export const SystemSignaturesContent = ({
|
||||
[outCommand, systemId],
|
||||
);
|
||||
|
||||
const handleDeleteSelected = useCallback(async () => {
|
||||
if (selectable) {
|
||||
return;
|
||||
}
|
||||
if (selectedSignatures.length === 0) {
|
||||
return;
|
||||
}
|
||||
const selectedSignaturesEveIds = selectedSignatures.map(x => x.eve_id);
|
||||
await handleUpdateSignatures(
|
||||
signatures.filter(x => !selectedSignaturesEveIds.includes(x.eve_id)),
|
||||
false,
|
||||
);
|
||||
}, [handleUpdateSignatures, selectable, signatures, selectedSignatures]);
|
||||
const handleDeleteSelected = useCallback(
|
||||
async (e: KeyboardEvent) => {
|
||||
if (selectable) {
|
||||
return;
|
||||
}
|
||||
if (selectedSignatures.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const selectedSignaturesEveIds = selectedSignatures.map(x => x.eve_id);
|
||||
await handleUpdateSignatures(
|
||||
signatures.filter(x => !selectedSignaturesEveIds.includes(x.eve_id)),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
},
|
||||
[handleUpdateSignatures, selectable, signatures, selectedSignatures],
|
||||
);
|
||||
|
||||
const handleSelectAll = useCallback(() => {
|
||||
setSelectedSignatures(signatures);
|
||||
}, [signatures]);
|
||||
|
||||
const handleReplaceAll = useCallback(() => {
|
||||
handleUpdateSignatures(parsedSignatures, false);
|
||||
setAskUser(false);
|
||||
}, [parsedSignatures, handleUpdateSignatures]);
|
||||
|
||||
const handleUpdateOnly = useCallback(() => {
|
||||
handleUpdateSignatures(parsedSignatures, true);
|
||||
setAskUser(false);
|
||||
}, [parsedSignatures, handleUpdateSignatures]);
|
||||
|
||||
const handleSelectSignatures = useCallback(
|
||||
// TODO still will be good to define types if we use typescript
|
||||
// @ts-ignore
|
||||
@@ -201,38 +212,51 @@ export const SystemSignaturesContent = ({
|
||||
[onSelect, selectable],
|
||||
);
|
||||
|
||||
useHotkey(true, ['a'], handleSelectAll);
|
||||
|
||||
useHotkey(false, ['Backspace'], handleDeleteSelected);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!clipboardContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handlePaste = async (clipboardContent: string) => {
|
||||
const newSignatures = parseSignatures(
|
||||
clipboardContent,
|
||||
settings.map(x => x.key),
|
||||
);
|
||||
|
||||
const { removed } = getActualSigs(signaturesRef.current, newSignatures, false);
|
||||
handleUpdateSignatures(newSignatures, !lazyDeleteValue);
|
||||
|
||||
if (!signaturesRef.current || !signaturesRef.current.length || !removed.length) {
|
||||
handleUpdateSignatures(newSignatures, false);
|
||||
} else {
|
||||
setParsedSignatures(newSignatures);
|
||||
setAskUser(true);
|
||||
if (lazyDeleteValue && !keepLazyDeleteValue) {
|
||||
onLazyDeleteChange?.(false);
|
||||
}
|
||||
}, [clipboardContent, selectable]);
|
||||
};
|
||||
|
||||
const handleEnterRow = useCallback(
|
||||
(e: DataTableRowMouseEvent) => {
|
||||
setHoveredSig(filteredSignatures[e.index]);
|
||||
tooltipRef.current?.show(e.originalEvent);
|
||||
},
|
||||
[filteredSignatures],
|
||||
);
|
||||
|
||||
const handleLeaveRow = useCallback((e: DataTableRowMouseEvent) => {
|
||||
tooltipRef.current?.hide(e.originalEvent);
|
||||
setHoveredSig(null);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (refData.current.selectable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!clipboardContent?.text) {
|
||||
return;
|
||||
}
|
||||
|
||||
handlePaste(clipboardContent.text);
|
||||
setClipboardContent(null);
|
||||
}, [clipboardContent, selectable, lazyDeleteValue, keepLazyDeleteValue]);
|
||||
|
||||
useHotkey(true, ['a'], handleSelectAll);
|
||||
useHotkey(false, ['Backspace', 'Delete'], handleDeleteSelected);
|
||||
|
||||
useEffect(() => {
|
||||
if (!systemId) {
|
||||
setSignatures([]);
|
||||
setAskUser(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -266,19 +290,6 @@ export const SystemSignaturesContent = ({
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleEnterRow = useCallback(
|
||||
(e: DataTableRowMouseEvent) => {
|
||||
setHoveredSig(filteredSignatures[e.index]);
|
||||
tooltipRef.current?.show(e.originalEvent);
|
||||
},
|
||||
[filteredSignatures],
|
||||
);
|
||||
|
||||
const handleLeaveRow = useCallback((e: DataTableRowMouseEvent) => {
|
||||
tooltipRef.current?.hide(e.originalEvent);
|
||||
setHoveredSig(null);
|
||||
}, []);
|
||||
|
||||
const renderToolbar = (/*row: SystemSignature*/) => {
|
||||
return (
|
||||
<div className="flex justify-end items-center gap-2 mr-[4px]">
|
||||
@@ -330,7 +341,7 @@ export const SystemSignaturesContent = ({
|
||||
return clsx(classes.TableRowCompact, 'bg-amber-500/50 hover:bg-amber-500/70 transition duration-200');
|
||||
}
|
||||
|
||||
const dateClass = getRowColorByTimeLeft(row.updated_at ? new Date(row.updated_at) : undefined);
|
||||
const dateClass = getRowColorByTimeLeft(row.inserted_at ? new Date(row.inserted_at) : undefined);
|
||||
if (!dateClass) {
|
||||
return clsx(classes.TableRowCompact, 'hover:bg-purple-400/20 transition duration-200');
|
||||
}
|
||||
@@ -357,11 +368,11 @@ export const SystemSignaturesContent = ({
|
||||
header="Group"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
hidden={compact}
|
||||
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
|
||||
sortable
|
||||
></Column>
|
||||
<Column
|
||||
field="info"
|
||||
// header="Info"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
body={renderInfoColumn}
|
||||
style={{ maxWidth: nameColumnWidth }}
|
||||
@@ -378,26 +389,26 @@ export const SystemSignaturesContent = ({
|
||||
></Column>
|
||||
)}
|
||||
|
||||
{showInsertedColumn && (
|
||||
<Column
|
||||
field="inserted_at"
|
||||
header="Added"
|
||||
dataType="date"
|
||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
body={renderAddedTimeLeft}
|
||||
sortable
|
||||
></Column>
|
||||
|
||||
{showUpdatedColumn && (
|
||||
<Column
|
||||
field="inserted_at"
|
||||
header="Inserted"
|
||||
field="updated_at"
|
||||
header="Updated"
|
||||
dataType="date"
|
||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
body={renderInsertedTimeLeft}
|
||||
body={renderUpdatedTimeLeft}
|
||||
sortable
|
||||
></Column>
|
||||
)}
|
||||
|
||||
<Column
|
||||
field="updated_at"
|
||||
header="Updated"
|
||||
dataType="date"
|
||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
body={renderUpdatedTimeLeft}
|
||||
sortable
|
||||
></Column>
|
||||
|
||||
{!selectable && (
|
||||
<Column
|
||||
bodyClassName="p-0 pl-1 pr-2"
|
||||
@@ -424,27 +435,6 @@ export const SystemSignaturesContent = ({
|
||||
signatureData={selectedSignature}
|
||||
/>
|
||||
)}
|
||||
|
||||
{askUser && (
|
||||
<div className="absolute left-[1px] top-[29px] h-[calc(100%-30px)] w-[calc(100%-3px)] bg-stone-900/10 backdrop-blur-sm">
|
||||
<div className="absolute top-0 left-0 w-full h-full flex flex-col items-center justify-center">
|
||||
<div className="text-stone-400/80 text-sm">
|
||||
<div className="flex flex-col text-center gap-2">
|
||||
<button className="p-button p-component p-button-outlined p-button-sm btn-wide">
|
||||
<span className="p-button-label p-c" onClick={handleUpdateOnly}>
|
||||
Update
|
||||
</span>
|
||||
</button>
|
||||
<button className="p-button p-component p-button-outlined p-button-sm btn-wide">
|
||||
<span className="p-button-label p-c" onClick={handleReplaceAll}>
|
||||
Update & Delete missing
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ export const getActualSigs = (
|
||||
oldSignatures: SystemSignature[],
|
||||
newSignatures: SystemSignature[],
|
||||
updateOnly: boolean,
|
||||
skipUpdateUntouched?: boolean,
|
||||
): { added: SystemSignature[]; updated: SystemSignature[]; removed: SystemSignature[] } => {
|
||||
const updated: SystemSignature[] = [];
|
||||
const removed: SystemSignature[] = [];
|
||||
@@ -19,7 +20,7 @@ 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 {
|
||||
} else if (!skipUpdateUntouched) {
|
||||
updated.push({ ...oldSig });
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export * from './renderIcon';
|
||||
export * from './renderDescription';
|
||||
export * from './renderName';
|
||||
export * from './renderInsertedTimeLeft';
|
||||
export * from './renderAddedTimeLeft';
|
||||
export * from './renderUpdatedTimeLeft';
|
||||
export * from './renderLinkedSystem';
|
||||
export * from './renderInfoColumn';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { TimeLeft } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
export const renderInsertedTimeLeft = (row: SystemSignature) => {
|
||||
export const renderAddedTimeLeft = (row: SystemSignature) => {
|
||||
return (
|
||||
<div className="flex w-full items-center">
|
||||
<TimeLeft cDate={row.inserted_at ? new Date(row.inserted_at) : undefined} />
|
||||
@@ -1,3 +0,0 @@
|
||||
.whFontSize {
|
||||
font-size: 11px !important;
|
||||
}
|
||||
@@ -2,21 +2,31 @@ import { PrimeIcons } from 'primereact/api';
|
||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { SystemViewStandalone, WHClassView } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
import {
|
||||
k162Types,
|
||||
renderK162Type,
|
||||
} from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { renderName } from './renderName.tsx';
|
||||
import classes from './renderInfoColumn.module.scss';
|
||||
|
||||
export const renderInfoColumn = (row: SystemSignature) => {
|
||||
if (!row.group || row.group === SignatureGroup.Wormhole) {
|
||||
let k162TypeOption = null;
|
||||
if (row.custom_info) {
|
||||
const customInfo = JSON.parse(row.custom_info);
|
||||
if (customInfo.k162Type) {
|
||||
k162TypeOption = k162Types.find(x => x.value === customInfo.k162Type);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex justify-start items-center gap-[6px]">
|
||||
<div className="flex justify-start items-center gap-[4px]">
|
||||
{row.type && (
|
||||
<WHClassView
|
||||
className="text-[11px]"
|
||||
classNameWh={classes.whFontSize}
|
||||
highlightName
|
||||
classNameWh="!text-[11px] !font-bold"
|
||||
hideWhClass={!!row.linked_system}
|
||||
whClassName={row.type}
|
||||
noOffset
|
||||
@@ -24,9 +34,10 @@ export const renderInfoColumn = (row: SystemSignature) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{!row.linked_system && row.type === 'K162' && !!k162TypeOption && <>{renderK162Type(k162TypeOption)}</>}
|
||||
|
||||
{row.linked_system && (
|
||||
<>
|
||||
{/*<span className="w-4 h-4 hero-arrow-long-right"></span>*/}
|
||||
<span title={row.linked_system?.solar_system_name}>
|
||||
<SystemViewStandalone
|
||||
className={clsx('select-none text-center cursor-context-menu')}
|
||||
|
||||
@@ -5,12 +5,14 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
||||
import clsx from 'clsx';
|
||||
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';
|
||||
@@ -75,8 +77,12 @@ 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);
|
||||
const [info, setInfo] = useState<ConnectionInfoOutput | null>(null);
|
||||
|
||||
const loadInfo = useCallback(
|
||||
async (connection: SolarSystemConnection) => {
|
||||
@@ -135,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">
|
||||
@@ -153,14 +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>
|
||||
|
||||
<InfoDrawer title="Mark EOL Time" rightSide>
|
||||
{info?.marl_eol_time ? <TimeAgo timestamp={info.marl_eol_time} /> : ' unknown '}
|
||||
</InfoDrawer>
|
||||
{/*Right column*/}
|
||||
<div>
|
||||
{isWormhole && (
|
||||
<InfoDrawer title="Approximate mass of passages" rightSide>
|
||||
{kgToTons(approximateMass)}
|
||||
</InfoDrawer>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2"></div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,8 @@ import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrap
|
||||
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { MenuItem } from 'primereact/menuitem';
|
||||
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
||||
|
||||
export interface MapContextMenuProps {
|
||||
onShowOnTheMap?: () => void;
|
||||
@@ -14,6 +16,8 @@ export interface MapContextMenuProps {
|
||||
export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings }: MapContextMenuProps) => {
|
||||
const { outCommand, setInterfaceSettings } = useMapRootState();
|
||||
|
||||
const canTrackCharacters = useMapCheckPermissions([UserPermission.TRACK_CHARACTER]);
|
||||
|
||||
const menuRight = useRef<Menu>(null);
|
||||
|
||||
const handleAddCharacter = useCallback(() => {
|
||||
@@ -24,34 +28,40 @@ export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings }: MapContext
|
||||
}, [outCommand]);
|
||||
|
||||
const items = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
label: 'Tracking',
|
||||
icon: 'pi pi-user-plus',
|
||||
command: handleAddCharacter,
|
||||
},
|
||||
{
|
||||
label: 'On the map',
|
||||
icon: 'pi pi-hashtag',
|
||||
command: onShowOnTheMap,
|
||||
},
|
||||
{ separator: true },
|
||||
{
|
||||
label: 'Settings',
|
||||
icon: `pi pi-cog`,
|
||||
command: onShowMapSettings,
|
||||
},
|
||||
{
|
||||
label: 'Dock menu',
|
||||
icon: 'pi pi-window-maximize',
|
||||
command: () =>
|
||||
setInterfaceSettings(x => ({
|
||||
...x,
|
||||
isShowMenu: !x.isShowMenu,
|
||||
})),
|
||||
},
|
||||
] as MenuItem[];
|
||||
}, [handleAddCharacter, onShowMapSettings, onShowOnTheMap, setInterfaceSettings]);
|
||||
return (
|
||||
[
|
||||
{
|
||||
label: 'Tracking',
|
||||
icon: 'pi pi-user-plus',
|
||||
command: handleAddCharacter,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
label: 'On the map',
|
||||
icon: 'pi pi-hashtag',
|
||||
command: onShowOnTheMap,
|
||||
visible: canTrackCharacters,
|
||||
},
|
||||
{ separator: true, visible: true },
|
||||
{
|
||||
label: 'Settings',
|
||||
icon: `pi pi-cog`,
|
||||
command: onShowMapSettings,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
label: 'Dock menu',
|
||||
icon: 'pi pi-window-maximize',
|
||||
command: () =>
|
||||
setInterfaceSettings(x => ({
|
||||
...x,
|
||||
isShowMenu: !x.isShowMenu,
|
||||
})),
|
||||
visible: true,
|
||||
},
|
||||
] as MenuItem[]
|
||||
).filter(item => item.visible);
|
||||
}, [canTrackCharacters, handleAddCharacter, onShowMapSettings, onShowOnTheMap, setInterfaceSettings]);
|
||||
|
||||
return (
|
||||
<div className="ml-1">
|
||||
|
||||
@@ -53,6 +53,7 @@ const SYSTEMS_CHECKBOXES_PROPS: CheckboxesList = [
|
||||
|
||||
const SIGNATURES_CHECKBOXES_PROPS: CheckboxesList = [
|
||||
{ prop: UserSettingsRemoteProps.link_signature_on_splash, label: 'Link signature on splash' },
|
||||
{ prop: InterfaceStoredSettingsProps.isShowUnsplashedSignatures, label: 'Show unsplashed signatures' },
|
||||
];
|
||||
|
||||
const CONNECTIONS_CHECKBOXES_PROPS: CheckboxesList = [
|
||||
@@ -62,6 +63,8 @@ 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' },
|
||||
{ prop: InterfaceStoredSettingsProps.isShowBackgroundPattern, label: 'Show background pattern' },
|
||||
{ prop: InterfaceStoredSettingsProps.isSoftBackground, label: 'Enable soft background' },
|
||||
];
|
||||
|
||||
export const MapSettings = ({ show, onHide }: MapSettingsProps) => {
|
||||
@@ -128,7 +131,7 @@ export const MapSettings = ({ show, onHide }: MapSettingsProps) => {
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
header="Map settings"
|
||||
header="Map user settings"
|
||||
visible={show}
|
||||
draggable={false}
|
||||
style={{ width: '550px' }}
|
||||
|
||||
@@ -8,6 +8,8 @@ import clsx from 'clsx';
|
||||
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
|
||||
import { CharacterCard, WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
import { useMapCheckPermissions, useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
||||
|
||||
type WindowLocalSettingsType = {
|
||||
compact: boolean;
|
||||
@@ -50,14 +52,22 @@ export const OnTheMap = ({ show, onHide }: OnTheMapProps) => {
|
||||
defaultValue: STORED_DEFAULT_VALUES,
|
||||
});
|
||||
|
||||
const restrictOfflineShowing = useMapGetOption('restrict_offline_showing');
|
||||
const isAdminOrManager = useMapCheckPermissions([UserPermission.MANAGE_MAP]);
|
||||
|
||||
const showOffline = useMemo(
|
||||
() => !restrictOfflineShowing || isAdminOrManager,
|
||||
[isAdminOrManager, restrictOfflineShowing],
|
||||
);
|
||||
|
||||
const sorted = useMemo(() => {
|
||||
const out = characters.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id) })).sort(sortCharacters);
|
||||
if (!settings.hideOffline) {
|
||||
if (showOffline && !settings.hideOffline) {
|
||||
return out;
|
||||
}
|
||||
|
||||
return out.filter(x => x.online);
|
||||
}, [characters, settings.hideOffline, userCharacters]);
|
||||
}, [showOffline, characters, settings.hideOffline, userCharacters]);
|
||||
|
||||
return (
|
||||
<Sidebar
|
||||
@@ -70,14 +80,16 @@ export const OnTheMap = ({ show, onHide }: OnTheMapProps) => {
|
||||
>
|
||||
<div className={clsx(classes.SidebarContent, '')}>
|
||||
<div className={'flex justify-end items-center gap-2 px-3'}>
|
||||
<WdCheckbox
|
||||
size="m"
|
||||
labelSide="left"
|
||||
label={'Hide offline'}
|
||||
value={settings.hideOffline}
|
||||
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300"
|
||||
onChange={() => setSettings(() => ({ ...settings, hideOffline: !settings.hideOffline }))}
|
||||
/>
|
||||
{showOffline && (
|
||||
<WdCheckbox
|
||||
size="m"
|
||||
labelSide="left"
|
||||
label={'Hide offline'}
|
||||
value={settings.hideOffline}
|
||||
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300"
|
||||
onChange={() => setSettings(() => ({ ...settings, hideOffline: !settings.hideOffline }))}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<VirtualScroller
|
||||
|
||||
@@ -6,6 +6,9 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
||||
|
||||
interface RightBarProps {
|
||||
onShowOnTheMap?: () => void;
|
||||
onShowMapSettings?: () => void;
|
||||
@@ -14,6 +17,8 @@ interface RightBarProps {
|
||||
export const RightBar = ({ onShowOnTheMap, onShowMapSettings }: RightBarProps) => {
|
||||
const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
|
||||
|
||||
const canTrackCharacters = useMapCheckPermissions([UserPermission.TRACK_CHARACTER]);
|
||||
|
||||
const isShowMinimap = interfaceSettings.isShowMinimap === undefined ? true : interfaceSettings.isShowMinimap;
|
||||
|
||||
const handleAddCharacter = useCallback(() => {
|
||||
@@ -64,19 +69,21 @@ export const RightBar = ({ onShowOnTheMap, onShowMapSettings }: RightBarProps) =
|
||||
</button>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
<WdTooltipWrapper content="Show on the map" position={TooltipPosition.left}>
|
||||
<button
|
||||
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
|
||||
type="button"
|
||||
onClick={onShowOnTheMap}
|
||||
>
|
||||
<i className="pi pi-hashtag"></i>
|
||||
</button>
|
||||
</WdTooltipWrapper>
|
||||
{canTrackCharacters && (
|
||||
<WdTooltipWrapper content="Show on the map" position={TooltipPosition.left}>
|
||||
<button
|
||||
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
|
||||
type="button"
|
||||
onClick={onShowOnTheMap}
|
||||
>
|
||||
<i className="pi pi-hashtag"></i>
|
||||
</button>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center mb-2 gap-1">
|
||||
<WdTooltipWrapper content="User settings" position={TooltipPosition.left}>
|
||||
<WdTooltipWrapper content="Map user settings" position={TooltipPosition.left}>
|
||||
<button
|
||||
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
|
||||
type="button"
|
||||
|
||||
@@ -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,120 @@ 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 };
|
||||
}
|
||||
out = {
|
||||
...out,
|
||||
custom_info: JSON.stringify({
|
||||
k162Type: values.k162Type,
|
||||
}),
|
||||
};
|
||||
|
||||
if (signatureData.group !== SignatureGroup.Wormhole) {
|
||||
out = { ...out, name: '' };
|
||||
}
|
||||
if (values.type != null) {
|
||||
out = { ...out, type: values.type };
|
||||
}
|
||||
|
||||
break;
|
||||
case SignatureGroup.CosmicSignature:
|
||||
out = { ...out, type: '', name: '' };
|
||||
break;
|
||||
default:
|
||||
if (values.name != null) {
|
||||
out = { ...out, name: values.name ?? '' };
|
||||
}
|
||||
}
|
||||
if (signatureData.group !== SignatureGroup.Wormhole) {
|
||||
out = { ...out, name: '' };
|
||||
}
|
||||
|
||||
if (values.description != null) {
|
||||
out = { ...out, description: values.description };
|
||||
}
|
||||
break;
|
||||
case SignatureGroup.CosmicSignature:
|
||||
out = { ...out, type: '', name: '' };
|
||||
break;
|
||||
default:
|
||||
if (values.name != null) {
|
||||
out = { ...out, name: values.name ?? '' };
|
||||
}
|
||||
}
|
||||
|
||||
if (values.description != null) {
|
||||
out = { ...out, description: values.description };
|
||||
}
|
||||
|
||||
// Note: when type of signature changed from WH to other type - we should drop name
|
||||
if (
|
||||
group !== SignatureGroup.Wormhole && // new
|
||||
signatureData.group === SignatureGroup.Wormhole && // prev
|
||||
signatureData.linked_system
|
||||
) {
|
||||
await outCommand({
|
||||
type: OutCommand.unlinkSignature,
|
||||
data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
|
||||
});
|
||||
|
||||
out = { ...out, type: '' };
|
||||
}
|
||||
|
||||
if (group === SignatureGroup.Wormhole && signatureData.linked_system != null && values.linked_system === null) {
|
||||
await outCommand({
|
||||
type: OutCommand.unlinkSignature,
|
||||
data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
|
||||
});
|
||||
}
|
||||
|
||||
// Note: despite groups have optional type - this will always set
|
||||
out = { ...out, group: group! };
|
||||
|
||||
// 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;
|
||||
const { linked_system, custom_info, ...rest } = signatureData;
|
||||
|
||||
form.reset({
|
||||
let k162Type = null;
|
||||
if (custom_info) {
|
||||
const customInfo = JSON.parse(custom_info);
|
||||
k162Type = customInfo.k162Type;
|
||||
}
|
||||
|
||||
signatureForm.reset({
|
||||
linked_system: linked_system?.solar_system_id.toString() ?? undefined,
|
||||
k162Type: k162Type,
|
||||
...rest,
|
||||
});
|
||||
}, [form, signatureData]);
|
||||
}, [signatureForm, signatureData]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -138,32 +155,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,7 +1,13 @@
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { SignatureWormholeTypeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureWormholeTypeSelect';
|
||||
import { SignatureK162TypeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
|
||||
import { SignatureLeadsToSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect';
|
||||
|
||||
export const SignatureGroupContentWormholes = () => {
|
||||
const { watch } = useFormContext<SystemSignature>();
|
||||
const type = watch('type');
|
||||
|
||||
return (
|
||||
<>
|
||||
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
|
||||
@@ -9,6 +15,13 @@ export const SignatureGroupContentWormholes = () => {
|
||||
<SignatureWormholeTypeSelect name="type" />
|
||||
</label>
|
||||
|
||||
{type === 'K162' && (
|
||||
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
|
||||
<span>K162 Type:</span>
|
||||
<SignatureK162TypeSelect name="k162Type" />
|
||||
</label>
|
||||
)}
|
||||
|
||||
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
|
||||
<span>Leads To:</span>
|
||||
<SignatureLeadsToSelect name="linked_system" />
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import { Dropdown } from 'primereact/dropdown';
|
||||
import clsx from 'clsx';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useMemo } from 'react';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { WHClassView } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
export const k162Types = [
|
||||
{
|
||||
label: 'Hi-Sec',
|
||||
value: 'hs',
|
||||
whClassName: 'A641',
|
||||
},
|
||||
{
|
||||
label: 'Low-Sec',
|
||||
value: 'ls',
|
||||
whClassName: 'J377',
|
||||
},
|
||||
{
|
||||
label: 'Null-Sec',
|
||||
value: 'ns',
|
||||
whClassName: 'C248',
|
||||
},
|
||||
{
|
||||
label: 'C1',
|
||||
value: 'c1',
|
||||
whClassName: 'E004',
|
||||
},
|
||||
{
|
||||
label: 'C2',
|
||||
value: 'c2',
|
||||
whClassName: 'D382',
|
||||
},
|
||||
{
|
||||
label: 'C3',
|
||||
value: 'c3',
|
||||
whClassName: 'L477',
|
||||
},
|
||||
{
|
||||
label: 'C4',
|
||||
value: 'c4',
|
||||
whClassName: 'M001',
|
||||
},
|
||||
{
|
||||
label: 'C5',
|
||||
value: 'c5',
|
||||
whClassName: 'L614',
|
||||
},
|
||||
{
|
||||
label: 'C6',
|
||||
value: 'c6',
|
||||
whClassName: 'G008',
|
||||
},
|
||||
{
|
||||
label: 'C13',
|
||||
value: 'c13',
|
||||
whClassName: 'A009',
|
||||
},
|
||||
{
|
||||
label: 'Thera',
|
||||
value: 'thera',
|
||||
whClassName: 'F353',
|
||||
},
|
||||
{
|
||||
label: 'Pochven',
|
||||
value: 'pochven',
|
||||
whClassName: 'F216',
|
||||
},
|
||||
];
|
||||
|
||||
const renderNoValue = () => <div className="flex gap-2 items-center">-Unknown-</div>;
|
||||
|
||||
// @ts-ignore
|
||||
export const renderK162Type = (option: {
|
||||
label?: string;
|
||||
value: string;
|
||||
security?: string;
|
||||
system_class?: number;
|
||||
whClassName?: string;
|
||||
}) => {
|
||||
if (!option) {
|
||||
return renderNoValue();
|
||||
}
|
||||
const { value, whClassName = '' } = option;
|
||||
if (value == null) {
|
||||
return renderNoValue();
|
||||
}
|
||||
|
||||
return (
|
||||
<WHClassView
|
||||
classNameWh="!text-[11px] !font-bold"
|
||||
hideWhClassName
|
||||
hideTooltip
|
||||
whClassName={whClassName}
|
||||
noOffset
|
||||
useShortTitle
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export interface SignatureK162TypeSelectProps {
|
||||
name: string;
|
||||
defaultValue?: string;
|
||||
}
|
||||
|
||||
export const SignatureK162TypeSelect = ({ name, defaultValue = '' }: SignatureK162TypeSelectProps) => {
|
||||
const { control } = useFormContext<SystemSignature>();
|
||||
|
||||
const options = useMemo(() => {
|
||||
return [{ value: null }, ...k162Types];
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Controller
|
||||
// @ts-ignore
|
||||
name={name}
|
||||
control={control}
|
||||
defaultValue={defaultValue}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Dropdown
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
options={options}
|
||||
optionValue="value"
|
||||
placeholder="Select K162 type"
|
||||
className={clsx('w-full')}
|
||||
scrollHeight="240px"
|
||||
itemTemplate={renderK162Type}
|
||||
valueTemplate={renderK162Type}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './SignatureK162TypeSelect.tsx';
|
||||
@@ -13,13 +13,14 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
// @ts-ignore
|
||||
const renderLinkedSystemItem = (option: { value: string }) => {
|
||||
if (option.value == null) {
|
||||
return <div className="flex gap-2 items-center">No linked system</div>;
|
||||
const { value } = option;
|
||||
if (value == null) {
|
||||
return <div className="flex gap-2 items-center">- Unknown -</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center">
|
||||
<SystemView systemId={option.value} className={classes.SystemView} />
|
||||
<SystemView systemId={value} className={classes.SystemView} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -65,6 +66,7 @@ export const SignatureLeadsToSelect = ({ name, defaultValue = '' }: SignatureLea
|
||||
const leadsToOptions = useMemo(() => {
|
||||
return [
|
||||
{ value: null },
|
||||
|
||||
...leadsTo
|
||||
.filter(systemId => {
|
||||
const systemStatic = systemStatics.get(parseInt(systemId));
|
||||
|
||||
@@ -17,7 +17,25 @@ const getPossibleWormholes = (systemStatic: SolarSystemStaticInfoRaw, wormholes:
|
||||
|
||||
// @ts-ignore
|
||||
const spawnClassGroup = SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS[whType];
|
||||
const possibleWHTypes = wormholes.filter(x => x.src.includes(spawnClassGroup));
|
||||
const possibleWHTypes = wormholes.filter(x => {
|
||||
return x.src.some(x => {
|
||||
const [group, type] = x.split('-');
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('JOipP', `group, type`, group, type);
|
||||
|
||||
if (type === 'shattered') {
|
||||
return systemStatic.is_shattered && group === spawnClassGroup;
|
||||
}
|
||||
|
||||
return group === spawnClassGroup;
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('JOipP', `possibleWHTypes`, possibleWHTypes);
|
||||
|
||||
// debugger;
|
||||
|
||||
return {
|
||||
statics: possibleWHTypes
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './SignatureGroupSelect';
|
||||
export * from './SignatureGroupContent';
|
||||
export * from './SignatureK162TypeSelect';
|
||||
|
||||
@@ -34,6 +34,8 @@ export const MapWrapper = () => {
|
||||
isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
|
||||
isShowKSpace,
|
||||
isThickConnections,
|
||||
isShowBackgroundPattern,
|
||||
isSoftBackground,
|
||||
},
|
||||
} = useMapRootState();
|
||||
const { deleteSystems } = useDeleteSystems();
|
||||
@@ -135,6 +137,8 @@ export const MapWrapper = () => {
|
||||
showKSpaceBG={isShowKSpace}
|
||||
onManualDelete={handleManualDelete}
|
||||
isThickConnections={isThickConnections}
|
||||
isShowBackgroundPattern={isShowBackgroundPattern}
|
||||
isSoftBackground={isSoftBackground}
|
||||
/>
|
||||
|
||||
{openSettings != null && (
|
||||
|
||||
@@ -18,7 +18,7 @@ const Topbar = ({ children }: WithChildren) => {
|
||||
<nav
|
||||
className={clsx(
|
||||
'px-2 flex items-center justify-center min-w-0 h-12 pointer-events-auto',
|
||||
'border-b border-gray-900 bg-gray-800 bg-opacity-5',
|
||||
'border-b border-stone-800 bg-gray-800 bg-opacity-5',
|
||||
'bg-opacity-70 bg-neutral-900',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -3,15 +3,23 @@ import { SystemViewStandalone } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
||||
import { useMemo } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||
|
||||
export type SystemViewProps = {
|
||||
systemId: string;
|
||||
systemInfo?: SolarSystemStaticInfoRaw;
|
||||
hideRegion?: boolean;
|
||||
useSystemsCache?: boolean;
|
||||
showCustomName?: boolean;
|
||||
} & WithClassName;
|
||||
|
||||
export const SystemView = ({ systemId, hideRegion, className, showCustomName }: SystemViewProps) => {
|
||||
export const SystemView = ({
|
||||
systemId,
|
||||
systemInfo: customSystemInfo,
|
||||
hideRegion,
|
||||
className,
|
||||
showCustomName,
|
||||
}: SystemViewProps) => {
|
||||
const memSystems = useMemo(() => [systemId], [systemId]);
|
||||
const { systems, loading } = useLoadSystemStatic({ systems: memSystems });
|
||||
|
||||
@@ -20,9 +28,12 @@ export const SystemView = ({ systemId, hideRegion, className, showCustomName }:
|
||||
} = useMapRootState();
|
||||
|
||||
const systemInfo = useMemo(() => {
|
||||
if (!systemId) {
|
||||
return customSystemInfo;
|
||||
}
|
||||
return systems.get(parseInt(systemId));
|
||||
// eslint-disable-next-line
|
||||
}, [systemId, systems, loading]);
|
||||
}, [customSystemInfo, systemId, systems, loading]);
|
||||
|
||||
const mapSystemInfo = useMemo(() => {
|
||||
if (!showCustomName) {
|
||||
|
||||
@@ -25,7 +25,6 @@ export const SystemViewStandalone = ({
|
||||
className,
|
||||
hideRegion,
|
||||
customName,
|
||||
|
||||
class_title,
|
||||
system_class,
|
||||
solar_system_name,
|
||||
@@ -38,6 +37,7 @@ export const SystemViewStandalone = ({
|
||||
...props
|
||||
}: SystemViewStandaloneProps) => {
|
||||
const classTitleColor = getSystemClassStyles({ systemClass: system_class, security });
|
||||
|
||||
const isWH = isWormholeSpace(system_class);
|
||||
|
||||
const handleClick = useCallback(
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
.WHClassViewRoot {
|
||||
|
||||
}
|
||||
|
||||
.WHClassViewContent {
|
||||
|
||||
@@ -18,7 +18,9 @@ export interface WHClassViewProps {
|
||||
whClassName: string;
|
||||
noOffset?: boolean;
|
||||
useShortTitle?: boolean;
|
||||
hideTooltip?: boolean;
|
||||
hideWhClass?: boolean;
|
||||
hideWhClassName?: boolean;
|
||||
highlightName?: boolean;
|
||||
className?: string;
|
||||
classNameWh?: string;
|
||||
@@ -28,7 +30,9 @@ export const WHClassView = ({
|
||||
whClassName,
|
||||
noOffset,
|
||||
useShortTitle,
|
||||
hideTooltip,
|
||||
hideWhClass,
|
||||
hideWhClassName,
|
||||
highlightName,
|
||||
className,
|
||||
classNameWh,
|
||||
@@ -45,25 +49,27 @@ export const WHClassView = ({
|
||||
|
||||
return (
|
||||
<div className={clsx(classes.WHClassViewRoot, className)}>
|
||||
<Tooltip
|
||||
target={`.wh-name${whClassName}${uid}`}
|
||||
position="right"
|
||||
mouseTrack
|
||||
mouseTrackLeft={20}
|
||||
mouseTrackTop={30}
|
||||
className="border border-green-300 rounded border-opacity-10 bg-stone-900 bg-opacity-90 "
|
||||
>
|
||||
<div className="flex gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<InfoDrawer title="Total mass">{prepareMass(whData.total_mass)}</InfoDrawer>
|
||||
<InfoDrawer title="Jump mass">{prepareMass(whData.max_mass_per_jump)}</InfoDrawer>
|
||||
{!hideTooltip && (
|
||||
<Tooltip
|
||||
target={`.wh-name${whClassName}${uid}`}
|
||||
position="right"
|
||||
mouseTrack
|
||||
mouseTrackLeft={20}
|
||||
mouseTrackTop={30}
|
||||
className="border border-green-300 rounded border-opacity-10 bg-stone-900 bg-opacity-90 "
|
||||
>
|
||||
<div className="flex gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<InfoDrawer title="Total mass">{prepareMass(whData.total_mass)}</InfoDrawer>
|
||||
<InfoDrawer title="Jump mass">{prepareMass(whData.max_mass_per_jump)}</InfoDrawer>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<InfoDrawer title="Lifetime">{whData.lifetime}h</InfoDrawer>
|
||||
<InfoDrawer title="Mass regen">{prepareMass(whData.mass_regen)}</InfoDrawer>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<InfoDrawer title="Lifetime">{whData.lifetime}h</InfoDrawer>
|
||||
<InfoDrawer title="Mass regen">{prepareMass(whData.mass_regen)}</InfoDrawer>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={clsx(
|
||||
@@ -73,7 +79,7 @@ export const WHClassView = ({
|
||||
`wh-name${whClassName}${uid}`,
|
||||
)}
|
||||
>
|
||||
<span className={clsx({ [whClassStyle]: highlightName })}>{whClassName}</span>
|
||||
{!hideWhClassName && <span className={clsx({ [whClassStyle]: highlightName })}>{whClassName}</span>}
|
||||
{!hideWhClass && whClass && (
|
||||
<span className={clsx(classes.WHClassName, whClassStyle, classNameWh)}>
|
||||
{useShortTitle ? whClass.shortTitle : whClass.shortName}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
export const useClipboard = () => {
|
||||
const [clipboardContent, setClipboardContent] = useState<string | null>(null);
|
||||
const [clipboardContent, setClipboardContent] = useState<{ text: string } | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const getClipboardContent = useCallback(async () => {
|
||||
try {
|
||||
const text = await navigator.clipboard.readText();
|
||||
setClipboardContent(text);
|
||||
setClipboardContent({ text });
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError('Failed to read clipboard content.');
|
||||
@@ -18,7 +18,7 @@ export const useClipboard = () => {
|
||||
const handlePaste = (event: ClipboardEvent) => {
|
||||
const text = event.clipboardData?.getData('text');
|
||||
if (text) {
|
||||
setClipboardContent(text);
|
||||
setClipboardContent({ text });
|
||||
setError(null);
|
||||
}
|
||||
};
|
||||
@@ -30,5 +30,5 @@ export const useClipboard = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { clipboardContent, error, getClipboardContent };
|
||||
return { clipboardContent, error, getClipboardContent, setClipboardContent };
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const useHotkey = (isMetaKey: boolean, hotkeys: string[], callback: () => void) => {
|
||||
export const useHotkey = (isMetaKey: boolean, hotkeys: string[], callback: (e: KeyboardEvent) => void) => {
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if ((!isMetaKey || event.ctrlKey || event.metaKey) && hotkeys.includes(event.key)) {
|
||||
@@ -8,14 +8,14 @@ export const useHotkey = (isMetaKey: boolean, hotkeys: string[], callback: () =>
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
callback();
|
||||
callback(event);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
window.addEventListener('keydown', handleKeyDown, { capture: true });
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
window.removeEventListener('keydown', handleKeyDown, { capture: true });
|
||||
};
|
||||
}, [isMetaKey, hotkeys, callback]);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
|
||||
import { createContext, Dispatch, ForwardedRef, forwardRef, RefObject, SetStateAction, useContext } from 'react';
|
||||
import { MapHandlers, MapUnionTypes, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||
import { createContext, Dispatch, ForwardedRef, forwardRef, SetStateAction, useContext } from 'react';
|
||||
import { MapUnionTypes, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||
import { useMapRootHandlers } from '@/hooks/Mapper/mapRootProvider/hooks';
|
||||
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
@@ -25,6 +25,8 @@ const INITIAL_DATA: MapRootData = {
|
||||
|
||||
selectedSystems: [],
|
||||
selectedConnections: [],
|
||||
userPermissions: {},
|
||||
options: {},
|
||||
};
|
||||
|
||||
export enum InterfaceStoredSettingsProps {
|
||||
@@ -32,6 +34,9 @@ export enum InterfaceStoredSettingsProps {
|
||||
isShowMinimap = 'isShowMinimap',
|
||||
isShowKSpace = 'isShowKSpace',
|
||||
isThickConnections = 'isThickConnections',
|
||||
isShowUnsplashedSignatures = 'isShowUnsplashedSignatures',
|
||||
isShowBackgroundPattern = 'isShowBackgroundPattern',
|
||||
isSoftBackground = 'isSoftBackground',
|
||||
}
|
||||
|
||||
export type InterfaceStoredSettings = {
|
||||
@@ -39,6 +44,9 @@ export type InterfaceStoredSettings = {
|
||||
isShowMinimap: boolean;
|
||||
isShowKSpace: boolean;
|
||||
isThickConnections: boolean;
|
||||
isShowUnsplashedSignatures: boolean;
|
||||
isShowBackgroundPattern: boolean;
|
||||
isSoftBackground: boolean;
|
||||
};
|
||||
|
||||
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
|
||||
@@ -46,6 +54,9 @@ export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
|
||||
isShowMinimap: true,
|
||||
isShowKSpace: false,
|
||||
isThickConnections: false,
|
||||
isShowUnsplashedSignatures: false,
|
||||
isShowBackgroundPattern: true,
|
||||
isSoftBackground: false,
|
||||
};
|
||||
|
||||
export interface MapRootContextProps {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export * from './useMapInit';
|
||||
export * from './useMapUpdated';
|
||||
export * from './useMapCheckPermissions';
|
||||
export * from './useMapGetOption';
|
||||
export * from './useRoutes';
|
||||
export * from './useCommandsConnections';
|
||||
export * from './useCommandsSystems';
|
||||
|
||||
@@ -2,53 +2,86 @@ 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';
|
||||
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
export const useCommandsSystems = () => {
|
||||
const {
|
||||
update,
|
||||
data: { systems },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const { addSystemStatic } = useLoadSystemStatic({ systems: [] });
|
||||
|
||||
const ref = useRef({ systems, update });
|
||||
ref.current = { systems, update };
|
||||
const ref = useRef({ systems, update, addSystemStatic });
|
||||
ref.current = { systems, update, addSystemStatic };
|
||||
|
||||
const addSystems = useCallback(
|
||||
(addSystems: CommandAddSystems) => {
|
||||
addSystems.forEach(sys => {
|
||||
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: [...ref.current.systems.filter(sys => !addSystems.some(x => sys.id === x.id)), ...addSystems],
|
||||
});
|
||||
},
|
||||
[addSystemStatic, update],
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
return newSystem;
|
||||
});
|
||||
|
||||
update({ systems: out }, true);
|
||||
}, []);
|
||||
|
||||
const updateSystemSignatures = useCallback(
|
||||
async (systemId: string) => {
|
||||
const { update, systems } = ref.current;
|
||||
|
||||
const { signatures } = await outCommand({
|
||||
type: OutCommand.getSignatures,
|
||||
data: { system_id: `${systemId}` },
|
||||
});
|
||||
|
||||
update({ systems: out });
|
||||
const out = systems.map(current => {
|
||||
if (current.id === `${systemId}`) {
|
||||
return { ...current, system_signatures: signatures };
|
||||
}
|
||||
|
||||
return current;
|
||||
});
|
||||
|
||||
update({ systems: out }, true);
|
||||
|
||||
emitMapEvent({ name: Commands.updateSystems, data: out });
|
||||
},
|
||||
[update],
|
||||
[outCommand],
|
||||
);
|
||||
|
||||
return { addSystems, removeSystems, updateSystems };
|
||||
return { addSystems, removeSystems, updateSystems, updateSystemSignatures };
|
||||
};
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
||||
|
||||
export const useMapCheckPermissions = (permissions: UserPermission[]) => {
|
||||
const {
|
||||
data: { userPermissions },
|
||||
} = useMapRootState();
|
||||
|
||||
return useMemo(() => permissions.every(x => userPermissions[x]), [permissions, userPermissions]);
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export const useMapGetOption = (option: string) => {
|
||||
const {
|
||||
data: { options },
|
||||
} = useMapRootState();
|
||||
|
||||
return useMemo(() => options[option], [option, options]);
|
||||
};
|
||||
@@ -19,6 +19,8 @@ export const useMapInit = () => {
|
||||
user_characters,
|
||||
present_characters,
|
||||
hubs,
|
||||
user_permissions,
|
||||
options,
|
||||
}: CommandInit) => {
|
||||
const updateData: Partial<MapRootData> = {};
|
||||
|
||||
@@ -51,10 +53,18 @@ export const useMapInit = () => {
|
||||
updateData.connections = connections;
|
||||
}
|
||||
|
||||
if (user_permissions) {
|
||||
updateData.userPermissions = user_permissions;
|
||||
}
|
||||
|
||||
if (hubs) {
|
||||
updateData.hubs = hubs;
|
||||
}
|
||||
|
||||
if (options) {
|
||||
updateData.options = options;
|
||||
}
|
||||
|
||||
if (system_static_infos) {
|
||||
system_static_infos.forEach(static_info => {
|
||||
addSystemStatic(static_info);
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
CommandRemoveSystems,
|
||||
CommandRoutes,
|
||||
Commands,
|
||||
CommandSignaturesUpdated,
|
||||
CommandUpdateConnection,
|
||||
CommandUpdateSystems,
|
||||
MapHandlers,
|
||||
@@ -31,7 +32,7 @@ import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
|
||||
export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
const mapInit = useMapInit();
|
||||
const { addSystems, removeSystems, updateSystems } = useCommandsSystems();
|
||||
const { addSystems, removeSystems, updateSystems, updateSystemSignatures } = useCommandsSystems();
|
||||
const { addConnections, removeConnections, updateConnection } = useCommandsConnections();
|
||||
const { charactersUpdated, characterAdded, characterRemoved, characterUpdated, presentCharacters } =
|
||||
useCommandsCharacters();
|
||||
@@ -87,6 +88,14 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
mapRoutes(data as CommandRoutes);
|
||||
break;
|
||||
|
||||
case Commands.signaturesUpdated: // USED
|
||||
updateSystemSignatures(data as CommandSignaturesUpdated);
|
||||
break;
|
||||
|
||||
case Commands.linkSignatureToSystem: // USED
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.centerSystem: // USED
|
||||
// do nothing here
|
||||
break;
|
||||
@@ -95,22 +104,10 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
// case Commands.linkSignatureToSystem:
|
||||
// // TODO command data type lost
|
||||
// // @ts-ignore
|
||||
// emitMapEvent({ name: Commands.linkSignatureToSystem, data });
|
||||
// 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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -6,3 +6,4 @@ export * from './system';
|
||||
export * from './mapUnionTypes';
|
||||
export * from './signatures';
|
||||
export * from './connectionPassages';
|
||||
export * from './permissions';
|
||||
|
||||
@@ -4,6 +4,7 @@ import { WormholeDataRaw } from '@/hooks/Mapper/types/wormholes.ts';
|
||||
import { CharacterTypeRaw } from '@/hooks/Mapper/types/character.ts';
|
||||
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||
import { Kill } from '@/hooks/Mapper/types/kills.ts';
|
||||
import { UserPermissions } from '@/hooks/Mapper/types';
|
||||
|
||||
export enum Commands {
|
||||
init = 'init',
|
||||
@@ -58,9 +59,10 @@ export type CommandInit = {
|
||||
characters: CharacterTypeRaw[];
|
||||
present_characters: string[];
|
||||
user_characters: string[];
|
||||
user_permissions: any;
|
||||
user_permissions: UserPermissions;
|
||||
hubs: string[];
|
||||
routes: RoutesList;
|
||||
options: Record<string, string | boolean>;
|
||||
reset?: boolean;
|
||||
};
|
||||
export type CommandAddSystems = SolarSystemRawType[];
|
||||
@@ -120,6 +122,7 @@ export enum OutCommand {
|
||||
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',
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CharacterTypeRaw } from '@/hooks/Mapper/types/character.ts';
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types/system.ts';
|
||||
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
|
||||
import { UserPermissions } from '@/hooks/Mapper/types';
|
||||
|
||||
export type MapUnionTypes = {
|
||||
wormholesData: Record<string, WormholeDataRaw>;
|
||||
@@ -17,4 +18,6 @@ export type MapUnionTypes = {
|
||||
routes?: RoutesList;
|
||||
kills: Record<number, number>;
|
||||
connections: SolarSystemConnection[];
|
||||
userPermissions: Partial<UserPermissions>;
|
||||
options: Record<string, string | boolean>;
|
||||
};
|
||||
|
||||
19
assets/js/hooks/Mapper/types/permissions.ts
Normal file
19
assets/js/hooks/Mapper/types/permissions.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export enum UserPermission {
|
||||
ADMIN_MAP = 'admin_map',
|
||||
MANAGE_MAP = 'manage_map',
|
||||
VIEW_SYSTEM = 'view_system',
|
||||
VIEW_CHARACTER = 'view_character',
|
||||
VIEW_CONNECTION = 'view_connection',
|
||||
ADD_SYSTEM = 'add_system',
|
||||
ADD_CONNECTION = 'add_connection',
|
||||
UPDATE_SYSTEM = 'update_system',
|
||||
TRACK_CHARACTER = 'track_character',
|
||||
DELETE_CONNECTION = 'delete_connection',
|
||||
DELETE_SYSTEM = 'delete_system',
|
||||
LOCK_SYSTEM = 'lock_system',
|
||||
ADD_ACL = 'add_acl',
|
||||
DELETE_ACL = 'delete_acl',
|
||||
DELETE_MAP = 'delete_map',
|
||||
}
|
||||
|
||||
export type UserPermissions = Record<UserPermission, boolean>;
|
||||
@@ -21,9 +21,11 @@ export type SystemSignature = {
|
||||
eve_id: string;
|
||||
kind: string;
|
||||
name: string;
|
||||
custom_info?: string;
|
||||
description?: string;
|
||||
group: SignatureGroup;
|
||||
type: string;
|
||||
k162Type?: string;
|
||||
linked_system?: SolarSystemStaticInfoRaw;
|
||||
inserted_at?: string;
|
||||
updated_at?: string;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { XYPosition } from 'reactflow';
|
||||
|
||||
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
|
||||
|
||||
export enum SolarSystemStaticInfoRawNames {
|
||||
regionId = 'region_id',
|
||||
constellationId = 'constellation_id',
|
||||
@@ -116,4 +118,5 @@ export type SolarSystemRawType = {
|
||||
name: string | null;
|
||||
|
||||
system_static_info: SolarSystemStaticInfoRaw;
|
||||
system_signatures: SystemSignature[];
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
@@ -34,7 +32,6 @@
|
||||
"react-hook-form": "^7.53.1",
|
||||
"react-usestateref": "^1.0.9",
|
||||
"reactflow": "^11.11.4",
|
||||
"rxjs": "^7.8.1",
|
||||
"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"
|
||||
@@ -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==
|
||||
|
||||
@@ -12,11 +12,16 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
|
||||
code_interface do
|
||||
define(:create, action: :create)
|
||||
define(:destroy, action: :destroy)
|
||||
|
||||
define(:read_by_map,
|
||||
action: :read_by_map
|
||||
)
|
||||
|
||||
define(:by_map_filtered,
|
||||
action: :by_map_filtered
|
||||
)
|
||||
|
||||
define(:tracked_by_map_filtered,
|
||||
action: :tracked_by_map_filtered
|
||||
)
|
||||
@@ -38,6 +43,13 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
|
||||
defaults [:create, :read, :update, :destroy]
|
||||
|
||||
read :by_map_filtered do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
argument(:character_ids, {:array, :uuid}, allow_nil?: false)
|
||||
|
||||
filter(expr(map_id == ^arg(:map_id) and character_id in ^arg(:character_ids)))
|
||||
end
|
||||
|
||||
read :tracked_by_map_filtered do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
argument(:character_ids, {:array, :uuid}, allow_nil?: false)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,6 +24,7 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
)
|
||||
|
||||
define(:by_system_id, action: :by_system_id, args: [:system_id])
|
||||
define(:by_linked_system_id, action: :by_linked_system_id, args: [:linked_system_id])
|
||||
end
|
||||
|
||||
actions do
|
||||
@@ -55,7 +56,8 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
:description,
|
||||
:kind,
|
||||
:group,
|
||||
:type
|
||||
:type,
|
||||
:custom_info
|
||||
]
|
||||
|
||||
argument :system_id, :uuid, allow_nil?: false
|
||||
@@ -73,6 +75,7 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
:kind,
|
||||
:group,
|
||||
:type,
|
||||
:custom_info,
|
||||
:updated
|
||||
]
|
||||
|
||||
@@ -97,6 +100,12 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
|
||||
filter(expr(system_id == ^arg(:system_id)))
|
||||
end
|
||||
|
||||
read :by_linked_system_id do
|
||||
argument(:linked_system_id, :integer, allow_nil?: false)
|
||||
|
||||
filter(expr(linked_system_id == ^arg(:linked_system_id)))
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
@@ -129,6 +138,10 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
attribute :kind, :string
|
||||
attribute :group, :string
|
||||
|
||||
attribute :custom_info, :string do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :updated, :integer
|
||||
|
||||
create_timestamp(:inserted_at)
|
||||
|
||||
@@ -61,12 +61,18 @@ defmodule WandererApp.Character do
|
||||
end)
|
||||
end
|
||||
|
||||
def get_character_state(character_id) do
|
||||
def get_character_state(character_id, init_if_empty? \\ true) do
|
||||
case Cachex.get(:character_state_cache, character_id) do
|
||||
{:ok, nil} ->
|
||||
character_state = WandererApp.Character.Tracker.init(character_id: character_id)
|
||||
Cachex.put(:character_state_cache, character_id, character_state)
|
||||
{:ok, character_state}
|
||||
case init_if_empty? do
|
||||
true ->
|
||||
character_state = WandererApp.Character.Tracker.init(character_id: character_id)
|
||||
Cachex.put(:character_state_cache, character_id, character_state)
|
||||
{:ok, character_state}
|
||||
|
||||
_ ->
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
{:ok, character_state} ->
|
||||
{:ok, character_state}
|
||||
|
||||
@@ -446,8 +446,13 @@ defmodule WandererApp.Character.Tracker do
|
||||
|> Map.merge(%{alliance_id: alliance_id, corporation_id: corporation_id})
|
||||
|> maybe_update_alliance()
|
||||
|
||||
_error ->
|
||||
Logger.warning("Failed to get corporation info for #{corporation_id}")
|
||||
error ->
|
||||
Logger.warning(
|
||||
"Failed to get corporation info for character #{character_id}: #{inspect(error)}",
|
||||
character_id: character_id,
|
||||
corporation_id: corporation_id
|
||||
)
|
||||
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
@@ -83,22 +83,28 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
end
|
||||
|
||||
def stop_tracking(%__MODULE__{} = state, character_id) do
|
||||
{:ok, %{start_time: start_time}} = WandererApp.Character.get_character_state(character_id)
|
||||
{:ok, character_state} = WandererApp.Character.get_character_state(character_id, false)
|
||||
|
||||
duration = DateTime.diff(DateTime.utc_now(), start_time, :second)
|
||||
:telemetry.execute([:wanderer_app, :character, :tracker, :running], %{duration: duration})
|
||||
:telemetry.execute([:wanderer_app, :character, :tracker, :stopped], %{count: 1})
|
||||
Logger.debug(fn -> "Shutting down character tracker: #{inspect(character_id)}" end)
|
||||
case character_state do
|
||||
nil ->
|
||||
state
|
||||
|
||||
WandererApp.Cache.delete("character:#{character_id}:location_started")
|
||||
WandererApp.Cache.delete("character:#{character_id}:start_solar_system_id")
|
||||
WandererApp.Character.delete_character_state(character_id)
|
||||
%{start_time: start_time} ->
|
||||
duration = DateTime.diff(DateTime.utc_now(), start_time, :second)
|
||||
:telemetry.execute([:wanderer_app, :character, :tracker, :running], %{duration: duration})
|
||||
:telemetry.execute([:wanderer_app, :character, :tracker, :stopped], %{count: 1})
|
||||
Logger.debug(fn -> "Shutting down character tracker: #{inspect(character_id)}" end)
|
||||
|
||||
tracked_characters = state.characters |> Enum.reject(fn c_id -> c_id == character_id end)
|
||||
WandererApp.Cache.delete("character:#{character_id}:location_started")
|
||||
WandererApp.Cache.delete("character:#{character_id}:start_solar_system_id")
|
||||
WandererApp.Character.delete_character_state(character_id)
|
||||
|
||||
WandererApp.Cache.insert("tracked_characters", tracked_characters)
|
||||
tracked_characters = state.characters |> Enum.reject(fn c_id -> c_id == character_id end)
|
||||
|
||||
%{state | characters: tracked_characters}
|
||||
WandererApp.Cache.insert("tracked_characters", tracked_characters)
|
||||
|
||||
%{state | characters: tracked_characters}
|
||||
end
|
||||
end
|
||||
|
||||
def update_track_settings(
|
||||
@@ -429,8 +435,19 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
end
|
||||
|
||||
def handle_info({:stop_track, character_id}, state) do
|
||||
@logger.debug(fn -> "Stopping character tracker: #{inspect(character_id)}" end)
|
||||
stop_tracking(state, character_id)
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:is_stop_tracking")
|
||||
|> case do
|
||||
false ->
|
||||
WandererApp.Cache.insert("character:#{character_id}:is_stop_tracking", true)
|
||||
Logger.debug(fn -> "Stopping character tracker: #{inspect(character_id)}" end)
|
||||
state = state |> stop_tracking(character_id)
|
||||
WandererApp.Cache.delete("character:#{character_id}:is_stop_tracking")
|
||||
|
||||
state
|
||||
|
||||
_ ->
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(_event, state),
|
||||
|
||||
@@ -49,7 +49,7 @@ defmodule WandererApp.EveDataService do
|
||||
end)
|
||||
end)
|
||||
|
||||
Task.await_many(tasks, :timer.minutes(1))
|
||||
Task.await_many(tasks, :timer.minutes(30))
|
||||
end
|
||||
|
||||
def download_file(file_name) do
|
||||
|
||||
@@ -14,6 +14,7 @@ defmodule WandererApp.Map do
|
||||
hubs: [],
|
||||
connections: Map.new(),
|
||||
acls: [],
|
||||
options: Map.new(),
|
||||
characters_limit: nil,
|
||||
hubs_limit: nil
|
||||
|
||||
@@ -69,6 +70,9 @@ defmodule WandererApp.Map do
|
||||
def get_characters_limit(map_id),
|
||||
do: {:ok, map_id |> get_map!() |> Map.get(:characters_limit, 100)}
|
||||
|
||||
def get_options(map_id),
|
||||
do: {:ok, map_id |> get_map!() |> Map.get(:options, Map.new())}
|
||||
|
||||
@doc """
|
||||
Returns a full list of characters in the map
|
||||
"""
|
||||
@@ -187,7 +191,7 @@ defmodule WandererApp.Map do
|
||||
case characters |> Enum.member?(character_id) do
|
||||
true ->
|
||||
map_id
|
||||
|> update_map(%{characters: Enum.reject(characters, fn id -> id == character_id end)})
|
||||
|> update_map(%{characters: characters |> Enum.reject(fn id -> id == character_id end)})
|
||||
|
||||
:ok
|
||||
|
||||
@@ -251,6 +255,13 @@ defmodule WandererApp.Map do
|
||||
map
|
||||
end
|
||||
|
||||
def update_options!(%{map_id: map_id} = map, options) do
|
||||
map_id
|
||||
|> update_map(%{options: options})
|
||||
|
||||
map
|
||||
end
|
||||
|
||||
def add_systems!(map, []), do: map
|
||||
|
||||
def add_systems!(%{map_id: map_id} = map, [system | rest]) do
|
||||
|
||||
@@ -195,6 +195,12 @@ defmodule WandererApp.Map.Server do
|
||||
|> 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
180
lib/wanderer_app/map/server/map_server_acls_impl.ex
Normal file
180
lib/wanderer_app/map/server/map_server_acls_impl.ex
Normal file
@@ -0,0 +1,180 @@
|
||||
defmodule WandererApp.Map.Server.AclsImpl do
|
||||
@moduledoc false
|
||||
|
||||
require Logger
|
||||
|
||||
alias WandererApp.Map.Server.Impl
|
||||
|
||||
@pubsub_client Application.compile_env(:wanderer_app, :pubsub_client)
|
||||
|
||||
def handle_map_acl_updated(%{map_id: map_id, map: old_map} = state, added_acls, removed_acls) do
|
||||
{:ok, map} =
|
||||
WandererApp.MapRepo.get(map_id,
|
||||
acls: [
|
||||
:owner_id,
|
||||
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
|
||||
]
|
||||
)
|
||||
|
||||
track_acls(added_acls)
|
||||
|
||||
result =
|
||||
(added_acls ++ removed_acls)
|
||||
|> Task.async_stream(
|
||||
fn acl_id ->
|
||||
update_acl(acl_id)
|
||||
end,
|
||||
max_concurrency: 10,
|
||||
timeout: :timer.seconds(15)
|
||||
)
|
||||
|> Enum.reduce(
|
||||
%{
|
||||
eve_alliance_ids: [],
|
||||
eve_character_ids: [],
|
||||
eve_corporation_ids: []
|
||||
},
|
||||
fn result, acc ->
|
||||
case result do
|
||||
{:ok, val} ->
|
||||
{:ok,
|
||||
%{
|
||||
eve_alliance_ids: eve_alliance_ids,
|
||||
eve_character_ids: eve_character_ids,
|
||||
eve_corporation_ids: eve_corporation_ids
|
||||
}} = val
|
||||
|
||||
%{
|
||||
acc
|
||||
| eve_alliance_ids: eve_alliance_ids ++ acc.eve_alliance_ids,
|
||||
eve_character_ids: eve_character_ids ++ acc.eve_character_ids,
|
||||
eve_corporation_ids: eve_corporation_ids ++ acc.eve_corporation_ids
|
||||
}
|
||||
|
||||
error ->
|
||||
Logger.error("Failed to update map #{map_id} acl: #{inspect(error, pretty: true)}")
|
||||
|
||||
acc
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
map_update = %{acls: map.acls, scope: map.scope}
|
||||
|
||||
WandererApp.Map.update_map(map_id, map_update)
|
||||
|
||||
broadcast_acl_updates({:ok, result}, map_id)
|
||||
|
||||
%{state | map: Map.merge(old_map, map_update)}
|
||||
end
|
||||
|
||||
def handle_acl_updated(map_id, acl_id) do
|
||||
{:ok, map} =
|
||||
WandererApp.MapRepo.get(map_id,
|
||||
acls: [
|
||||
:owner_id,
|
||||
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
|
||||
]
|
||||
)
|
||||
|
||||
if map.acls |> Enum.map(& &1.id) |> Enum.member?(acl_id) do
|
||||
WandererApp.Map.update_map(map_id, %{acls: map.acls})
|
||||
|
||||
:ok =
|
||||
acl_id
|
||||
|> update_acl()
|
||||
|> broadcast_acl_updates(map_id)
|
||||
end
|
||||
end
|
||||
|
||||
def track_acls([]), do: :ok
|
||||
|
||||
def track_acls([acl_id | rest]) do
|
||||
track_acl(acl_id)
|
||||
track_acls(rest)
|
||||
end
|
||||
|
||||
defp track_acl(acl_id),
|
||||
do: @pubsub_client.subscribe(WandererApp.PubSub, "acls:#{acl_id}")
|
||||
|
||||
defp broadcast_acl_updates(
|
||||
{:ok,
|
||||
%{
|
||||
eve_character_ids: eve_character_ids,
|
||||
eve_corporation_ids: eve_corporation_ids,
|
||||
eve_alliance_ids: eve_alliance_ids
|
||||
}},
|
||||
map_id
|
||||
) do
|
||||
eve_character_ids
|
||||
|> Enum.uniq()
|
||||
|> Enum.each(fn eve_character_id ->
|
||||
@pubsub_client.broadcast(
|
||||
WandererApp.PubSub,
|
||||
"character:#{eve_character_id}",
|
||||
:update_permissions
|
||||
)
|
||||
end)
|
||||
|
||||
eve_corporation_ids
|
||||
|> Enum.uniq()
|
||||
|> Enum.each(fn eve_corporation_id ->
|
||||
@pubsub_client.broadcast(
|
||||
WandererApp.PubSub,
|
||||
"corporation:#{eve_corporation_id}",
|
||||
:update_permissions
|
||||
)
|
||||
end)
|
||||
|
||||
eve_alliance_ids
|
||||
|> Enum.uniq()
|
||||
|> Enum.each(fn eve_alliance_id ->
|
||||
@pubsub_client.broadcast(
|
||||
WandererApp.PubSub,
|
||||
"alliance:#{eve_alliance_id}",
|
||||
:update_permissions
|
||||
)
|
||||
end)
|
||||
|
||||
character_ids =
|
||||
map_id
|
||||
|> WandererApp.Map.get_map!()
|
||||
|> Map.get(:characters, [])
|
||||
|
||||
WandererApp.Cache.insert("map_#{map_id}:invalidate_character_ids", character_ids)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp broadcast_acl_updates(_, _map_id), do: :ok
|
||||
|
||||
defp update_acl(acl_id) do
|
||||
{:ok, %{owner: owner, members: members}} =
|
||||
WandererApp.AccessListRepo.get(acl_id, [:owner, :members])
|
||||
|
||||
result =
|
||||
members
|
||||
|> Enum.reduce(
|
||||
%{eve_character_ids: [owner.eve_id], eve_corporation_ids: [], eve_alliance_ids: []},
|
||||
fn member, acc ->
|
||||
case member do
|
||||
%{eve_character_id: eve_character_id} when not is_nil(eve_character_id) ->
|
||||
acc
|
||||
|> Map.put(:eve_character_ids, [eve_character_id | acc.eve_character_ids])
|
||||
|
||||
%{eve_corporation_id: eve_corporation_id} when not is_nil(eve_corporation_id) ->
|
||||
acc
|
||||
|> Map.put(:eve_corporation_ids, [eve_corporation_id | acc.eve_corporation_ids])
|
||||
|
||||
%{eve_alliance_id: eve_alliance_id} when not is_nil(eve_alliance_id) ->
|
||||
acc
|
||||
|> Map.put(:eve_alliance_ids, [eve_alliance_id | acc.eve_alliance_ids])
|
||||
|
||||
_ ->
|
||||
acc
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
{:ok, result}
|
||||
end
|
||||
end
|
||||
459
lib/wanderer_app/map/server/map_server_characters_impl.ex
Normal file
459
lib/wanderer_app/map/server/map_server_characters_impl.ex
Normal file
@@ -0,0 +1,459 @@
|
||||
defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
@moduledoc false
|
||||
|
||||
require Logger
|
||||
|
||||
alias WandererApp.Map.Server.{Impl, ConnectionsImpl, SystemsImpl}
|
||||
|
||||
def get_characters(%{map_id: map_id} = _state),
|
||||
do: {:ok, map_id |> WandererApp.Map.list_characters()}
|
||||
|
||||
def add_character(%{map_id: map_id} = state, %{id: character_id} = character, track_character) do
|
||||
Task.start_link(fn ->
|
||||
with :ok <- map_id |> WandererApp.Map.add_character(character),
|
||||
{:ok, _} <-
|
||||
WandererApp.MapCharacterSettingsRepo.create(%{
|
||||
character_id: character_id,
|
||||
map_id: map_id,
|
||||
tracked: track_character
|
||||
}),
|
||||
{:ok, character} <- WandererApp.Character.get_character(character_id) do
|
||||
Impl.broadcast!(map_id, :character_added, character)
|
||||
|
||||
:telemetry.execute([:wanderer_app, :map, :character, :added], %{count: 1})
|
||||
|
||||
:ok
|
||||
else
|
||||
_error ->
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
Impl.broadcast!(map_id, :character_added, character)
|
||||
:ok
|
||||
end
|
||||
end)
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def remove_character(map_id, character_id) do
|
||||
Task.start_link(fn ->
|
||||
with :ok <- WandererApp.Map.remove_character(map_id, character_id),
|
||||
{:ok, character} <- WandererApp.Character.get_character(character_id) do
|
||||
Impl.broadcast!(map_id, :character_removed, character)
|
||||
|
||||
:telemetry.execute([:wanderer_app, :map, :character, :removed], %{count: 1})
|
||||
|
||||
:ok
|
||||
else
|
||||
{:error, _error} ->
|
||||
:ok
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def update_tracked_characters(map_id) do
|
||||
Task.start_link(fn ->
|
||||
{:ok, map_tracked_character_ids} =
|
||||
map_id
|
||||
|> WandererApp.MapCharacterSettingsRepo.get_tracked_by_map_all()
|
||||
|> case do
|
||||
{:ok, settings} -> {:ok, settings |> Enum.map(&Map.get(&1, :character_id))}
|
||||
_ -> {:ok, []}
|
||||
end
|
||||
|
||||
{:ok, tracked_characters} = WandererApp.Cache.lookup("tracked_characters", [])
|
||||
|
||||
map_active_tracked_characters =
|
||||
map_tracked_character_ids
|
||||
|> Enum.filter(fn character -> character in tracked_characters end)
|
||||
|
||||
WandererApp.Cache.insert("maps:#{map_id}:tracked_characters", map_active_tracked_characters)
|
||||
|
||||
:ok
|
||||
end)
|
||||
end
|
||||
|
||||
def untrack_characters(map_id, character_ids),
|
||||
do:
|
||||
character_ids
|
||||
|> Enum.each(fn character_id ->
|
||||
WandererApp.Character.TrackerManager.update_track_settings(character_id, %{
|
||||
map_id: map_id,
|
||||
track: false
|
||||
})
|
||||
end)
|
||||
|
||||
def cleanup_characters(map_id, owner_id) do
|
||||
{:ok, invalidate_character_ids} =
|
||||
WandererApp.Cache.lookup(
|
||||
"map_#{map_id}:invalidate_character_ids",
|
||||
[]
|
||||
)
|
||||
|
||||
acls =
|
||||
map_id
|
||||
|> WandererApp.Map.get_map!()
|
||||
|> Map.get(:acls, [])
|
||||
|
||||
invalidate_character_ids
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
character_id
|
||||
|> WandererApp.Character.get_character()
|
||||
|> case do
|
||||
{:ok, character} ->
|
||||
[character_permissions] =
|
||||
WandererApp.Permissions.check_characters_access([character], acls)
|
||||
|
||||
map_permissions =
|
||||
WandererApp.Permissions.get_map_permissions(
|
||||
character_permissions,
|
||||
owner_id,
|
||||
[character_id]
|
||||
)
|
||||
|
||||
case map_permissions do
|
||||
%{view_system: false} ->
|
||||
{:remove_character, character_id}
|
||||
|
||||
%{track_character: false} ->
|
||||
{:remove_character, character_id}
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
end,
|
||||
timeout: :timer.seconds(60),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, {:remove_character, character_id}} ->
|
||||
remove_and_untrack_characters(map_id, [character_id])
|
||||
:ok
|
||||
|
||||
{:ok, _result} ->
|
||||
:ok
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Error in cleanup_characters: #{inspect(reason)}")
|
||||
end)
|
||||
|
||||
WandererApp.Cache.insert(
|
||||
"map_#{map_id}:invalidate_character_ids",
|
||||
[]
|
||||
)
|
||||
end
|
||||
|
||||
defp remove_and_untrack_characters(map_id, character_ids) do
|
||||
Logger.debug(fn ->
|
||||
"Map #{map_id} - remove and untrack characters #{inspect(character_ids)}"
|
||||
end)
|
||||
|
||||
map_id
|
||||
|> untrack_characters(character_ids)
|
||||
|
||||
map_id
|
||||
|> WandererApp.MapCharacterSettingsRepo.get_by_map_filtered(character_ids)
|
||||
|> case do
|
||||
{:ok, settings} ->
|
||||
settings
|
||||
|> Enum.each(fn s ->
|
||||
WandererApp.MapCharacterSettingsRepo.destroy!(s)
|
||||
remove_character(map_id, s.character_id)
|
||||
end)
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
def track_characters(_map_id, []), do: :ok
|
||||
|
||||
def track_characters(map_id, [character_id | rest]) do
|
||||
track_character(map_id, character_id)
|
||||
track_characters(map_id, rest)
|
||||
end
|
||||
|
||||
def update_characters(%{map_id: map_id} = state) do
|
||||
WandererApp.Cache.lookup!("maps:#{map_id}:tracked_characters", [])
|
||||
|> Enum.map(fn character_id ->
|
||||
Task.start_link(fn ->
|
||||
character_updates =
|
||||
maybe_update_online(map_id, character_id) ++
|
||||
maybe_update_location(map_id, character_id) ++
|
||||
maybe_update_ship(map_id, character_id) ++
|
||||
maybe_update_alliance(map_id, character_id) ++
|
||||
maybe_update_corporation(map_id, character_id)
|
||||
|
||||
character_updates
|
||||
|> Enum.filter(fn update -> update != :skip end)
|
||||
|> Enum.map(fn update ->
|
||||
update
|
||||
|> case do
|
||||
{:character_location, location_info, old_location_info} ->
|
||||
update_location(
|
||||
character_id,
|
||||
location_info,
|
||||
old_location_info,
|
||||
state
|
||||
)
|
||||
|
||||
:broadcast
|
||||
|
||||
{:character_ship, _info} ->
|
||||
:broadcast
|
||||
|
||||
{:character_online, _info} ->
|
||||
:broadcast
|
||||
|
||||
{:character_alliance, _info} ->
|
||||
WandererApp.Cache.insert_or_update(
|
||||
"map_#{map_id}:invalidate_character_ids",
|
||||
[character_id],
|
||||
fn ids ->
|
||||
[character_id | ids]
|
||||
end
|
||||
)
|
||||
|
||||
:broadcast
|
||||
|
||||
{:character_corporation, _info} ->
|
||||
WandererApp.Cache.insert_or_update(
|
||||
"map_#{map_id}:invalidate_character_ids",
|
||||
[character_id],
|
||||
fn ids ->
|
||||
[character_id | ids]
|
||||
end
|
||||
)
|
||||
|
||||
:broadcast
|
||||
|
||||
_ ->
|
||||
:skip
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(fn update -> update != :skip end)
|
||||
|> Enum.uniq()
|
||||
|> Enum.each(fn update ->
|
||||
case update do
|
||||
:broadcast ->
|
||||
update_character(map_id, character_id)
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
end)
|
||||
|
||||
:ok
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
defp update_character(map_id, character_id) do
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
Impl.broadcast!(map_id, :character_updated, character)
|
||||
end
|
||||
|
||||
defp update_location(
|
||||
character_id,
|
||||
location,
|
||||
old_location,
|
||||
%{map: map, map_id: map_id, rtree_name: rtree_name, map_opts: map_opts} = _state
|
||||
) do
|
||||
case is_nil(old_location.solar_system_id) and
|
||||
ConnectionsImpl.can_add_location(map.scope, location.solar_system_id) do
|
||||
true ->
|
||||
:ok = SystemsImpl.maybe_add_system(map_id, location, nil, rtree_name, map_opts)
|
||||
|
||||
_ ->
|
||||
ConnectionsImpl.is_connection_valid(
|
||||
map.scope,
|
||||
old_location.solar_system_id,
|
||||
location.solar_system_id
|
||||
)
|
||||
|> case do
|
||||
true ->
|
||||
:ok =
|
||||
SystemsImpl.maybe_add_system(map_id, location, old_location, rtree_name, map_opts)
|
||||
|
||||
:ok =
|
||||
SystemsImpl.maybe_add_system(map_id, old_location, location, rtree_name, map_opts)
|
||||
|
||||
:ok =
|
||||
ConnectionsImpl.maybe_add_connection(map_id, location, old_location, character_id)
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp track_character(map_id, character_id),
|
||||
do:
|
||||
WandererApp.Character.TrackerManager.update_track_settings(character_id, %{
|
||||
map_id: map_id,
|
||||
track: true,
|
||||
track_online: true,
|
||||
track_location: true,
|
||||
track_ship: true
|
||||
})
|
||||
|
||||
defp maybe_update_online(map_id, character_id) do
|
||||
with {:ok, old_online} <-
|
||||
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:online"),
|
||||
{:ok, %{online: online}} <-
|
||||
WandererApp.Character.get_character(character_id) do
|
||||
case old_online != online do
|
||||
true ->
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:online",
|
||||
online
|
||||
)
|
||||
|
||||
[{:character_online, %{online: online}}]
|
||||
|
||||
_ ->
|
||||
[:skip]
|
||||
end
|
||||
else
|
||||
error ->
|
||||
Logger.error("Failed to update online: #{inspect(error, pretty: true)}")
|
||||
[:skip]
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_update_ship(map_id, character_id) do
|
||||
with {:ok, old_ship_type_id} <-
|
||||
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:ship_type_id"),
|
||||
{:ok, old_ship_name} <-
|
||||
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:ship_name"),
|
||||
{:ok, %{ship: ship_type_id, ship_name: ship_name}} <-
|
||||
WandererApp.Character.get_character(character_id) do
|
||||
case old_ship_type_id != ship_type_id or
|
||||
old_ship_name != ship_name do
|
||||
true ->
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:ship_type_id",
|
||||
ship_type_id
|
||||
)
|
||||
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:ship_name",
|
||||
ship_name
|
||||
)
|
||||
|
||||
[{:character_ship, %{ship: ship_type_id, ship_name: ship_name}}]
|
||||
|
||||
_ ->
|
||||
[:skip]
|
||||
end
|
||||
else
|
||||
error ->
|
||||
Logger.error("Failed to update ship: #{inspect(error, pretty: true)}")
|
||||
[:skip]
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_update_location(map_id, character_id) do
|
||||
WandererApp.Cache.lookup!(
|
||||
"character:#{character_id}:location_started",
|
||||
false
|
||||
)
|
||||
|> case do
|
||||
true ->
|
||||
{:ok, old_solar_system_id} =
|
||||
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:solar_system_id")
|
||||
|
||||
{:ok, %{solar_system_id: solar_system_id}} =
|
||||
WandererApp.Character.get_character(character_id)
|
||||
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:solar_system_id",
|
||||
solar_system_id
|
||||
)
|
||||
|
||||
case solar_system_id != old_solar_system_id do
|
||||
true ->
|
||||
[
|
||||
{:character_location, %{solar_system_id: solar_system_id},
|
||||
%{solar_system_id: old_solar_system_id}}
|
||||
]
|
||||
|
||||
_ ->
|
||||
[:skip]
|
||||
end
|
||||
|
||||
false ->
|
||||
{:ok, old_solar_system_id} =
|
||||
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:solar_system_id")
|
||||
|
||||
{:ok, %{solar_system_id: solar_system_id} = _character} =
|
||||
WandererApp.Character.get_character(character_id)
|
||||
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:solar_system_id",
|
||||
solar_system_id
|
||||
)
|
||||
|
||||
if is_nil(old_solar_system_id) or solar_system_id != old_solar_system_id do
|
||||
[
|
||||
{:character_location, %{solar_system_id: solar_system_id}, %{solar_system_id: nil}}
|
||||
]
|
||||
else
|
||||
[:skip]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_update_alliance(map_id, character_id) do
|
||||
with {:ok, old_alliance_id} <-
|
||||
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:alliance_id"),
|
||||
{:ok, %{alliance_id: alliance_id}} <-
|
||||
WandererApp.Character.get_character(character_id) do
|
||||
case old_alliance_id != alliance_id do
|
||||
true ->
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:alliance_id",
|
||||
alliance_id
|
||||
)
|
||||
|
||||
[{:character_alliance, %{alliance_id: alliance_id}}]
|
||||
|
||||
_ ->
|
||||
[:skip]
|
||||
end
|
||||
else
|
||||
error ->
|
||||
Logger.error("Failed to update alliance: #{inspect(error, pretty: true)}")
|
||||
[:skip]
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_update_corporation(map_id, character_id) do
|
||||
with {:ok, old_corporation_id} <-
|
||||
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:corporation_id"),
|
||||
{:ok, %{corporation_id: corporation_id}} <-
|
||||
WandererApp.Character.get_character(character_id) do
|
||||
case old_corporation_id != corporation_id do
|
||||
true ->
|
||||
WandererApp.Cache.insert(
|
||||
"map:#{map_id}:character:#{character_id}:corporation_id",
|
||||
corporation_id
|
||||
)
|
||||
|
||||
[{:character_corporation, %{corporation_id: corporation_id}}]
|
||||
|
||||
_ ->
|
||||
[:skip]
|
||||
end
|
||||
else
|
||||
error ->
|
||||
Logger.error("Failed to update corporation: #{inspect(error, pretty: true)}")
|
||||
[:skip]
|
||||
end
|
||||
end
|
||||
end
|
||||
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 false
|
||||
|
||||
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(: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)
|
||||
|
||||
:all ->
|
||||
not (@prohibited_system_classes |> 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
|
||||
506
lib/wanderer_app/map/server/map_server_systems_impl.ex
Normal file
506
lib/wanderer_app/map/server/map_server_systems_impl.ex
Normal file
@@ -0,0 +1,506 @@
|
||||
defmodule WandererApp.Map.Server.SystemsImpl do
|
||||
@moduledoc false
|
||||
|
||||
require Logger
|
||||
|
||||
alias WandererApp.Map.Server.{Impl}
|
||||
|
||||
@ddrt Application.compile_env(:wanderer_app, :ddrt)
|
||||
@system_auto_expire_minutes 15
|
||||
@system_inactive_timeout :timer.minutes(15)
|
||||
|
||||
def init_last_activity_cache(map_id, systems_last_activity) do
|
||||
systems_last_activity
|
||||
|> Enum.each(fn {system_id, last_activity} ->
|
||||
WandererApp.Cache.put(
|
||||
"map_#{map_id}:system_#{system_id}:last_activity",
|
||||
last_activity,
|
||||
ttl: @system_inactive_timeout
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
def init_map_systems(state, [] = _systems), do: state
|
||||
|
||||
def init_map_systems(%{map_id: map_id, rtree_name: rtree_name} = state, systems) do
|
||||
systems
|
||||
|> Enum.each(fn %{id: system_id, solar_system_id: solar_system_id} = system ->
|
||||
@ddrt.insert(
|
||||
{solar_system_id, WandererApp.Map.PositionCalculator.get_system_bounding_rect(system)},
|
||||
rtree_name
|
||||
)
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"map_#{map_id}:system_#{system_id}:last_activity",
|
||||
DateTime.utc_now(),
|
||||
ttl: @system_inactive_timeout
|
||||
)
|
||||
end)
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def add_system(
|
||||
%{map_id: map_id} = state,
|
||||
%{
|
||||
solar_system_id: solar_system_id
|
||||
} = system_info,
|
||||
user_id,
|
||||
character_id
|
||||
) do
|
||||
case map_id |> WandererApp.Map.check_location(%{solar_system_id: solar_system_id}) do
|
||||
{:ok, _location} ->
|
||||
state |> _add_system(system_info, user_id, character_id)
|
||||
|
||||
{:error, :already_exists} ->
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup_systems(%{map_id: map_id} = state) do
|
||||
expired_systems =
|
||||
map_id
|
||||
|> WandererApp.Map.list_systems!()
|
||||
|> Enum.filter(fn %{
|
||||
id: system_id,
|
||||
visible: system_visible,
|
||||
locked: system_locked,
|
||||
solar_system_id: solar_system_id
|
||||
} = _system ->
|
||||
last_updated_time =
|
||||
WandererApp.Cache.get("map_#{map_id}:system_#{system_id}:last_activity")
|
||||
|
||||
if system_visible and not system_locked and
|
||||
(is_nil(last_updated_time) or
|
||||
DateTime.diff(DateTime.utc_now(), last_updated_time, :minute) >=
|
||||
@system_auto_expire_minutes) do
|
||||
no_active_connections? =
|
||||
map_id
|
||||
|> WandererApp.Map.find_connections(solar_system_id)
|
||||
|> Enum.empty?()
|
||||
|
||||
no_active_characters? =
|
||||
map_id |> WandererApp.Map.get_system_characters(solar_system_id) |> Enum.empty?()
|
||||
|
||||
no_active_connections? and no_active_characters?
|
||||
else
|
||||
false
|
||||
end
|
||||
end)
|
||||
|> Enum.map(& &1.solar_system_id)
|
||||
|
||||
case expired_systems |> Enum.empty?() do
|
||||
false ->
|
||||
state |> delete_systems(expired_systems, nil, nil)
|
||||
|
||||
_ ->
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
def update_system_name(
|
||||
state,
|
||||
update
|
||||
),
|
||||
do: state |> update_system(:update_name, [:name], update)
|
||||
|
||||
def update_system_description(
|
||||
state,
|
||||
update
|
||||
),
|
||||
do: state |> update_system(:update_description, [:description], update)
|
||||
|
||||
def update_system_status(
|
||||
state,
|
||||
update
|
||||
),
|
||||
do: state |> update_system(:update_status, [:status], update)
|
||||
|
||||
def update_system_tag(
|
||||
state,
|
||||
update
|
||||
),
|
||||
do: state |> update_system(:update_tag, [:tag], update)
|
||||
|
||||
def update_system_locked(
|
||||
state,
|
||||
update
|
||||
),
|
||||
do: state |> update_system(:update_locked, [:locked], update)
|
||||
|
||||
def update_system_labels(
|
||||
state,
|
||||
update
|
||||
),
|
||||
do: state |> update_system(:update_labels, [:labels], update)
|
||||
|
||||
def update_system_position(
|
||||
%{rtree_name: rtree_name} = state,
|
||||
update
|
||||
),
|
||||
do:
|
||||
state
|
||||
|> update_system(
|
||||
:update_position,
|
||||
[:position_x, :position_y],
|
||||
update,
|
||||
fn updated_system ->
|
||||
@ddrt.update(
|
||||
updated_system.solar_system_id,
|
||||
WandererApp.Map.PositionCalculator.get_system_bounding_rect(updated_system),
|
||||
rtree_name
|
||||
)
|
||||
end
|
||||
)
|
||||
|
||||
def add_hub(
|
||||
%{map_id: map_id} = state,
|
||||
hub_info
|
||||
) do
|
||||
with :ok <- WandererApp.Map.add_hub(map_id, hub_info),
|
||||
{:ok, hubs} = map_id |> WandererApp.Map.list_hubs(),
|
||||
{:ok, _} <-
|
||||
WandererApp.MapRepo.update_hubs(map_id, hubs) do
|
||||
Impl.broadcast!(map_id, :update_map, %{hubs: hubs})
|
||||
state
|
||||
else
|
||||
error ->
|
||||
Logger.error("Failed to add hub: #{inspect(error, pretty: true)}")
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
def remove_hub(
|
||||
%{map_id: map_id} = state,
|
||||
hub_info
|
||||
) do
|
||||
with :ok <- WandererApp.Map.remove_hub(map_id, hub_info),
|
||||
{:ok, hubs} = map_id |> WandererApp.Map.list_hubs(),
|
||||
{:ok, _} <-
|
||||
WandererApp.MapRepo.update_hubs(map_id, hubs) do
|
||||
Impl.broadcast!(map_id, :update_map, %{hubs: hubs})
|
||||
state
|
||||
else
|
||||
error ->
|
||||
Logger.error("Failed to remove hub: #{inspect(error, pretty: true)}")
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
def delete_systems(
|
||||
%{map_id: map_id, rtree_name: rtree_name} = state,
|
||||
removed_ids,
|
||||
user_id,
|
||||
character_id
|
||||
) do
|
||||
connections_to_remove =
|
||||
removed_ids
|
||||
|> Enum.map(fn solar_system_id ->
|
||||
WandererApp.Map.find_connections(map_id, solar_system_id)
|
||||
end)
|
||||
|> List.flatten()
|
||||
|> Enum.uniq_by(& &1.id)
|
||||
|
||||
:ok = WandererApp.Map.remove_connections(map_id, connections_to_remove)
|
||||
:ok = WandererApp.Map.remove_systems(map_id, removed_ids)
|
||||
|
||||
removed_ids
|
||||
|> Enum.each(fn solar_system_id ->
|
||||
map_id
|
||||
|> WandererApp.MapSystemRepo.remove_from_map(solar_system_id)
|
||||
|> case do
|
||||
{:ok, _} ->
|
||||
:ok
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("Failed to remove system from map: #{inspect(error, pretty: true)}")
|
||||
:ok
|
||||
end
|
||||
end)
|
||||
|
||||
connections_to_remove
|
||||
|> Enum.each(fn connection ->
|
||||
Logger.debug(fn -> "Removing connection from map: #{inspect(connection)}" end)
|
||||
WandererApp.MapConnectionRepo.destroy(map_id, connection)
|
||||
end)
|
||||
|
||||
removed_ids
|
||||
|> Enum.map(fn solar_system_id ->
|
||||
WandererApp.Api.MapSystemSignature.by_linked_system_id!(solar_system_id)
|
||||
end)
|
||||
|> List.flatten()
|
||||
|> Enum.uniq_by(& &1.system_id)
|
||||
|> Enum.each(fn s ->
|
||||
{:ok, %{system: system}} = s |> Ash.load([:system])
|
||||
Ash.destroy!(s)
|
||||
|
||||
Impl.broadcast!(map_id, :signatures_updated, system.solar_system_id)
|
||||
end)
|
||||
|
||||
@ddrt.delete(removed_ids, rtree_name)
|
||||
|
||||
Impl.broadcast!(map_id, :remove_connections, connections_to_remove)
|
||||
Impl.broadcast!(map_id, :systems_removed, removed_ids)
|
||||
|
||||
case not is_nil(user_id) do
|
||||
true ->
|
||||
{:ok, _} =
|
||||
WandererApp.User.ActivityTracker.track_map_event(:systems_removed, %{
|
||||
character_id: character_id,
|
||||
user_id: user_id,
|
||||
map_id: map_id,
|
||||
solar_system_ids: removed_ids
|
||||
})
|
||||
|
||||
:telemetry.execute(
|
||||
[:wanderer_app, :map, :systems, :remove],
|
||||
%{count: removed_ids |> Enum.count()}
|
||||
)
|
||||
|
||||
:ok
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def maybe_add_system(map_id, location, old_location, rtree_name, map_opts)
|
||||
when not is_nil(location) do
|
||||
case WandererApp.Map.check_location(map_id, location) do
|
||||
{:ok, location} ->
|
||||
{:ok, position} = calc_new_system_position(map_id, old_location, rtree_name, map_opts)
|
||||
|
||||
case WandererApp.MapSystemRepo.get_by_map_and_solar_system_id(
|
||||
map_id,
|
||||
location.solar_system_id
|
||||
) do
|
||||
{:ok, existing_system} when not is_nil(existing_system) ->
|
||||
{:ok, updated_system} =
|
||||
existing_system
|
||||
|> WandererApp.MapSystemRepo.update_position!(%{
|
||||
position_x: position.x,
|
||||
position_y: position.y
|
||||
})
|
||||
|> WandererApp.MapSystemRepo.cleanup_labels!(map_opts)
|
||||
|> WandererApp.MapSystemRepo.update_visible!(%{visible: true})
|
||||
|> WandererApp.MapSystemRepo.cleanup_tags()
|
||||
|
||||
@ddrt.insert(
|
||||
{existing_system.solar_system_id,
|
||||
WandererApp.Map.PositionCalculator.get_system_bounding_rect(%{
|
||||
position_x: position.x,
|
||||
position_y: position.y
|
||||
})},
|
||||
rtree_name
|
||||
)
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"map_#{map_id}:system_#{updated_system.id}:last_activity",
|
||||
DateTime.utc_now(),
|
||||
ttl: @system_inactive_timeout
|
||||
)
|
||||
|
||||
WandererApp.Map.add_system(map_id, updated_system)
|
||||
|
||||
Impl.broadcast!(map_id, :add_system, updated_system)
|
||||
:ok
|
||||
|
||||
_ ->
|
||||
{:ok, solar_system_info} =
|
||||
WandererApp.CachedInfo.get_system_static_info(location.solar_system_id)
|
||||
|
||||
WandererApp.MapSystemRepo.create(%{
|
||||
map_id: map_id,
|
||||
solar_system_id: location.solar_system_id,
|
||||
name: solar_system_info.solar_system_name,
|
||||
position_x: position.x,
|
||||
position_y: position.y
|
||||
})
|
||||
|> case do
|
||||
{:ok, new_system} ->
|
||||
@ddrt.insert(
|
||||
{new_system.solar_system_id,
|
||||
WandererApp.Map.PositionCalculator.get_system_bounding_rect(new_system)},
|
||||
rtree_name
|
||||
)
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"map_#{map_id}:system_#{new_system.id}:last_activity",
|
||||
DateTime.utc_now(),
|
||||
ttl: @system_inactive_timeout
|
||||
)
|
||||
|
||||
WandererApp.Map.add_system(map_id, new_system)
|
||||
Impl.broadcast!(map_id, :add_system, new_system)
|
||||
|
||||
:ok
|
||||
|
||||
error ->
|
||||
Logger.warning("Failed to create system: #{inspect(error, pretty: true)}")
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
error ->
|
||||
Logger.debug("Skip adding system: #{inspect(error, pretty: true)}")
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_add_system(_map_id, _location, _old_location, _rtree_name, _map_opts), do: :ok
|
||||
|
||||
defp _add_system(
|
||||
%{map_id: map_id, map_opts: map_opts, rtree_name: rtree_name} = state,
|
||||
%{
|
||||
solar_system_id: solar_system_id,
|
||||
coordinates: coordinates
|
||||
} = system_info,
|
||||
user_id,
|
||||
character_id
|
||||
) do
|
||||
%{"x" => x, "y" => y} =
|
||||
coordinates
|
||||
|> case do
|
||||
%{"x" => x, "y" => y} ->
|
||||
%{"x" => x, "y" => y}
|
||||
|
||||
_ ->
|
||||
%{x: x, y: y} =
|
||||
WandererApp.Map.PositionCalculator.get_new_system_position(nil, rtree_name, map_opts)
|
||||
|
||||
%{"x" => x, "y" => y}
|
||||
end
|
||||
|
||||
{:ok, system} =
|
||||
case WandererApp.MapSystemRepo.get_by_map_and_solar_system_id(map_id, solar_system_id) do
|
||||
{:ok, existing_system} when not is_nil(existing_system) ->
|
||||
use_old_coordinates = Map.get(system_info, :use_old_coordinates, false)
|
||||
|
||||
if use_old_coordinates do
|
||||
@ddrt.insert(
|
||||
{solar_system_id,
|
||||
WandererApp.Map.PositionCalculator.get_system_bounding_rect(%{
|
||||
position_x: existing_system.position_x,
|
||||
position_y: existing_system.position_y
|
||||
})},
|
||||
rtree_name
|
||||
)
|
||||
|
||||
existing_system
|
||||
|> WandererApp.MapSystemRepo.update_visible(%{visible: true})
|
||||
else
|
||||
@ddrt.insert(
|
||||
{solar_system_id,
|
||||
WandererApp.Map.PositionCalculator.get_system_bounding_rect(%{
|
||||
position_x: x,
|
||||
position_y: y
|
||||
})},
|
||||
rtree_name
|
||||
)
|
||||
|
||||
existing_system
|
||||
|> WandererApp.MapSystemRepo.update_position!(%{position_x: x, position_y: y})
|
||||
|> WandererApp.MapSystemRepo.cleanup_labels!(map_opts)
|
||||
|> WandererApp.MapSystemRepo.cleanup_tags!()
|
||||
|> WandererApp.MapSystemRepo.update_visible(%{visible: true})
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:ok, solar_system_info} =
|
||||
WandererApp.CachedInfo.get_system_static_info(solar_system_id)
|
||||
|
||||
@ddrt.insert(
|
||||
{solar_system_id,
|
||||
WandererApp.Map.PositionCalculator.get_system_bounding_rect(%{
|
||||
position_x: x,
|
||||
position_y: y
|
||||
})},
|
||||
rtree_name
|
||||
)
|
||||
|
||||
WandererApp.MapSystemRepo.create(%{
|
||||
map_id: map_id,
|
||||
solar_system_id: solar_system_id,
|
||||
name: solar_system_info.solar_system_name,
|
||||
position_x: x,
|
||||
position_y: y
|
||||
})
|
||||
end
|
||||
|
||||
:ok = map_id |> WandererApp.Map.add_system(system)
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"map_#{map_id}:system_#{system.id}:last_activity",
|
||||
DateTime.utc_now(),
|
||||
ttl: @system_inactive_timeout
|
||||
)
|
||||
|
||||
Impl.broadcast!(map_id, :add_system, system)
|
||||
|
||||
{:ok, _} =
|
||||
WandererApp.User.ActivityTracker.track_map_event(:system_added, %{
|
||||
character_id: character_id,
|
||||
user_id: user_id,
|
||||
map_id: map_id,
|
||||
solar_system_id: solar_system_id
|
||||
})
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
defp calc_new_system_position(map_id, old_location, rtree_name, opts),
|
||||
do:
|
||||
{:ok,
|
||||
map_id
|
||||
|> WandererApp.Map.find_system_by_location(old_location)
|
||||
|> WandererApp.Map.PositionCalculator.get_new_system_position(rtree_name, opts)}
|
||||
|
||||
defp update_system(
|
||||
%{map_id: map_id} = state,
|
||||
update_method,
|
||||
attributes,
|
||||
update,
|
||||
callback_fn \\ nil
|
||||
) do
|
||||
with :ok <- WandererApp.Map.update_system_by_solar_system_id(map_id, update),
|
||||
{:ok, system} <-
|
||||
WandererApp.MapSystemRepo.get_by_map_and_solar_system_id(
|
||||
map_id,
|
||||
update.solar_system_id
|
||||
),
|
||||
{:ok, update_map} <- Impl.get_update_map(update, attributes) do
|
||||
{:ok, updated_system} =
|
||||
apply(WandererApp.MapSystemRepo, update_method, [
|
||||
system,
|
||||
update_map
|
||||
])
|
||||
|
||||
if not is_nil(callback_fn) do
|
||||
callback_fn.(updated_system)
|
||||
end
|
||||
|
||||
update_map_system_last_activity(map_id, updated_system)
|
||||
|
||||
state
|
||||
else
|
||||
error ->
|
||||
Logger.error("Failed to update system: #{inspect(error, pretty: true)}")
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
defp update_map_system_last_activity(
|
||||
map_id,
|
||||
updated_system
|
||||
) do
|
||||
WandererApp.Cache.put(
|
||||
"map_#{map_id}:system_#{updated_system.id}:last_activity",
|
||||
DateTime.utc_now(),
|
||||
ttl: @system_inactive_timeout
|
||||
)
|
||||
|
||||
Impl.broadcast!(map_id, :update_system, updated_system)
|
||||
end
|
||||
end
|
||||
@@ -15,6 +15,8 @@ defmodule WandererApp.Permissions do
|
||||
@add_acl 1024
|
||||
@delete_acl 2048
|
||||
@delete_map 4096
|
||||
@manage_map 8192
|
||||
@admin_map 16384
|
||||
|
||||
@viewer_role [@view_system, @view_character, @view_connection]
|
||||
@member_role @viewer_role ++
|
||||
@@ -24,11 +26,10 @@ defmodule WandererApp.Permissions do
|
||||
@update_system,
|
||||
@track_character,
|
||||
@delete_connection,
|
||||
@delete_system,
|
||||
@lock_system
|
||||
@delete_system
|
||||
]
|
||||
@manager_role @member_role
|
||||
@admin_role @manager_role ++ [@add_acl, @delete_acl, @delete_map]
|
||||
@manager_role @member_role ++ [@lock_system, @manage_map]
|
||||
@admin_role @manager_role ++ [@add_acl, @delete_acl, @delete_map, @admin_map]
|
||||
|
||||
@viewer_role_mask @viewer_role |> Enum.reduce(0, fn x, acc -> x ||| acc end)
|
||||
@member_role_mask @member_role |> Enum.reduce(0, fn x, acc -> x ||| acc end)
|
||||
@@ -72,6 +73,8 @@ defmodule WandererApp.Permissions do
|
||||
|
||||
def get_permissions(user_permissions) do
|
||||
%{
|
||||
admin_map: check_permission(user_permissions, @admin_map),
|
||||
manage_map: check_permission(user_permissions, @manage_map),
|
||||
view_system: check_permission(user_permissions, @view_system),
|
||||
view_character: check_permission(user_permissions, @view_character),
|
||||
view_connection: check_permission(user_permissions, @view_connection),
|
||||
|
||||
@@ -11,6 +11,13 @@ defmodule WandererApp.MapCharacterSettingsRepo do
|
||||
character_ids: character_ids
|
||||
})
|
||||
|
||||
def get_by_map_filtered(map_id, character_ids),
|
||||
do:
|
||||
WandererApp.Api.MapCharacterSettings.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})
|
||||
|
||||
@@ -22,4 +29,6 @@ defmodule WandererApp.MapCharacterSettingsRepo do
|
||||
|
||||
def track!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.track!()
|
||||
def untrack!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.untrack!()
|
||||
|
||||
def destroy!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.destroy!()
|
||||
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
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
defmodule WandererApp.MapRepo do
|
||||
use WandererApp, :repository
|
||||
|
||||
@default_map_options %{"layout" => "left_to_right", "store_custom_labels" => "false"}
|
||||
@default_map_options %{
|
||||
"layout" => "left_to_right",
|
||||
"store_custom_labels" => "false",
|
||||
"restrict_offline_showing" => "false"
|
||||
}
|
||||
|
||||
def get(map_id, relationships \\ []) do
|
||||
map_id
|
||||
|
||||
@@ -143,10 +143,10 @@ defmodule WandererAppWeb.CoreComponents do
|
||||
def server_status(assigns) do
|
||||
~H"""
|
||||
<div
|
||||
class="flex flex-col p-4 items-center absolute bottom-16 left-1 gap-2 tooltip tooltip-right"
|
||||
class="flex flex-col p-4 items-center absolute bottom-16 left-2 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 class={"block w-2 h-2 rounded-full shadow-inner #{if @online, do: " bg-green-500", else: "bg-red-500"}"}>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
@@ -331,6 +331,7 @@ defmodule WandererAppWeb.CoreComponents do
|
||||
"""
|
||||
attr(:id, :any, default: nil)
|
||||
attr(:class, :string, default: nil)
|
||||
attr(:wrapper_class, :string, default: nil)
|
||||
attr(:name, :any)
|
||||
attr(:label, :string, default: nil)
|
||||
attr(:prefix, :string, default: nil)
|
||||
@@ -376,21 +377,62 @@ defmodule WandererAppWeb.CoreComponents do
|
||||
end)
|
||||
|
||||
~H"""
|
||||
<div phx-feedback-for={@name} class="form-control mt-8">
|
||||
<label class="label cursor-pointer gap-2">
|
||||
<span class="label-text"><%= @label %></span>
|
||||
<input type="hidden" name={@name} value="false" />
|
||||
<input
|
||||
type="checkbox"
|
||||
id={@id}
|
||||
name={@name}
|
||||
value="true"
|
||||
checked={@checked}
|
||||
class="checkbox"
|
||||
{@rest}
|
||||
/>
|
||||
<div phx-feedback-for={@name} class="form-control mt-2">
|
||||
<label class="inputContainer" for={@name}>
|
||||
<span><%= @label %></span>
|
||||
<div></div>
|
||||
<div class="smallInputSwitch">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class={[
|
||||
"checkboxRoot sizeM p-checkbox p-component",
|
||||
classes("p-highlight": @checked)
|
||||
]}
|
||||
data-p-highlight={@checked}
|
||||
data-p-disabled="false"
|
||||
data-pc-name="checkbox"
|
||||
data-pc-section="root"
|
||||
>
|
||||
<input
|
||||
id={@id}
|
||||
name={@name}
|
||||
type="checkbox"
|
||||
class="p-checkbox-input"
|
||||
aria-invalid="false"
|
||||
data-pc-section="input"
|
||||
value="true"
|
||||
checked={@checked}
|
||||
{@rest}
|
||||
/>
|
||||
<div
|
||||
class="p-checkbox-box"
|
||||
data-p-highlight={@checked}
|
||||
data-p-disabled="false"
|
||||
data-pc-section="box"
|
||||
>
|
||||
<svg
|
||||
:if={@checked}
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="p-icon p-checkbox-icon"
|
||||
aria-hidden="true"
|
||||
data-pc-section="icon"
|
||||
>
|
||||
<path
|
||||
d="M4.86199 11.5948C4.78717 11.5923 4.71366 11.5745 4.64596 11.5426C4.57826 11.5107 4.51779 11.4652 4.46827 11.4091L0.753985 7.69483C0.683167 7.64891 0.623706 7.58751 0.580092 7.51525C0.536478 7.44299 0.509851 7.36177 0.502221 7.27771C0.49459 7.19366 0.506156 7.10897 0.536046 7.03004C0.565935 6.95111 0.613367 6.88 0.674759 6.82208C0.736151 6.76416 0.8099 6.72095 0.890436 6.69571C0.970973 6.67046 1.05619 6.66385 1.13966 6.67635C1.22313 6.68886 1.30266 6.72017 1.37226 6.76792C1.44186 6.81567 1.4997 6.8786 1.54141 6.95197L4.86199 10.2503L12.6397 2.49483C12.7444 2.42694 12.8689 2.39617 12.9932 2.40745C13.1174 2.41873 13.2343 2.47141 13.3251 2.55705C13.4159 2.64268 13.4753 2.75632 13.4938 2.87973C13.5123 3.00315 13.4888 3.1292 13.4271 3.23768L5.2557 11.4091C5.20618 11.4652 5.14571 11.5107 5.07801 11.5426C5.01031 11.5745 4.9368 11.5923 4.86199 11.5948Z"
|
||||
fill="currentColor"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<label for={@name} class="select-none ml-1.5"></label>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<.error :for={msg <- @errors}><%= msg %></.error>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
@@ -398,24 +440,28 @@ defmodule WandererAppWeb.CoreComponents do
|
||||
def input(%{type: "range"} = assigns) do
|
||||
~H"""
|
||||
<div phx-feedback-for={@name}>
|
||||
<label class="form-control w-full">
|
||||
<div class="form-control w-full">
|
||||
<.label for={@id}>
|
||||
<span class="label-text"><%= @label %></span>
|
||||
<span class="label-value"><%= @value %></span>
|
||||
<span><%= @label %></span>
|
||||
<div></div>
|
||||
<%= @value %>
|
||||
</.label>
|
||||
<input
|
||||
type="range"
|
||||
id={@id}
|
||||
name={@name}
|
||||
value={@value}
|
||||
class={[
|
||||
"p-component w-full",
|
||||
@class,
|
||||
@errors != [] && "border-rose-400 focus:border-rose-400"
|
||||
]}
|
||||
{@rest}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<input
|
||||
type="range"
|
||||
id={@id}
|
||||
name={@name}
|
||||
value={@value}
|
||||
class={[
|
||||
"p-component w-full",
|
||||
@class,
|
||||
@errors != [] && "border-rose-400 focus:border-rose-400"
|
||||
]}
|
||||
{@rest}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<.error :for={msg <- @errors}><%= msg %></.error>
|
||||
</div>
|
||||
"""
|
||||
@@ -423,13 +469,20 @@ defmodule WandererAppWeb.CoreComponents do
|
||||
|
||||
def input(%{type: "select"} = assigns) do
|
||||
~H"""
|
||||
<div phx-feedback-for={@name}>
|
||||
<div
|
||||
phx-feedback-for={@name}
|
||||
class={[
|
||||
"inputContainer",
|
||||
@wrapper_class
|
||||
]}
|
||||
>
|
||||
<.label :if={@label} for={@id}><%= @label %></.label>
|
||||
<div :if={@label}></div>
|
||||
<select
|
||||
id={@id}
|
||||
name={@name}
|
||||
class={[
|
||||
"w-full",
|
||||
"p-component",
|
||||
@class
|
||||
]}
|
||||
multiple={@multiple}
|
||||
@@ -498,9 +551,9 @@ defmodule WandererAppWeb.CoreComponents do
|
||||
|
||||
def label(assigns) do
|
||||
~H"""
|
||||
<div for={@for} class="label">
|
||||
<label for={@for} class="inputContainer">
|
||||
<%= render_slot(@inner_block) %>
|
||||
</div>
|
||||
</label>
|
||||
"""
|
||||
end
|
||||
|
||||
|
||||
@@ -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>
|
||||
"""
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
>
|
||||
<%= @inner_content %>
|
||||
</main>
|
||||
<aside class="h-full w-14 left-0 absolute bg-gray-400 bg-opacity-5 text-gray-200 shadow-lg border-r border-gray-900 bg-opacity-70 bg-neutral-900">
|
||||
<aside class="h-full w-14 left-0 absolute bg-gray-400 bg-opacity-5 text-gray-200 shadow-lg border-r border-stone-800 bg-opacity-70 bg-neutral-900">
|
||||
<.sidebar_nav_links
|
||||
active_tab={@active_tab}
|
||||
show_admin={@show_admin}
|
||||
|
||||
@@ -32,14 +32,22 @@
|
||||
/>
|
||||
<script
|
||||
crossorigin="anonymous"
|
||||
src="https://unpkg.com/react@18/umd/react.production.min.js"
|
||||
integrity={integrity_hash("https://unpkg.com/react@18/umd/react.production.min.js")}
|
||||
src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"
|
||||
integrity={
|
||||
integrity_hash(
|
||||
"https://cdn.jsdelivr.net/npm/react-dom@16/umd/react-dom.development.js https://unpkg.com/react@18/umd/react.production.min.js"
|
||||
)
|
||||
}
|
||||
>
|
||||
</script>
|
||||
<script
|
||||
crossorigin="anonymous"
|
||||
src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
|
||||
integrity={integrity_hash("https://unpkg.com/react-dom@18/umd/react-dom.production.min.js")}
|
||||
src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"
|
||||
integrity={
|
||||
integrity_hash(
|
||||
"https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"
|
||||
)
|
||||
}
|
||||
>
|
||||
</script>
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ defmodule WandererAppWeb.MapPicker do
|
||||
:if={maps}
|
||||
type="select"
|
||||
field={f[:map_slug]}
|
||||
class="select h-8 min-h-[0px] !pt-1 !pb-1 text-sm bg-neutral-900"
|
||||
class="select h-8 min-h-[10px] !pt-1 !pb-1 text-sm bg-neutral-900"
|
||||
placeholder="Select a map..."
|
||||
options={Enum.map(@maps.result, fn map -> {map.label, map.value} end)}
|
||||
/>
|
||||
|
||||
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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user