mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-01 13:33:02 +00:00
Compare commits
124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae3a34d5bf | ||
|
|
43df42e49b | ||
|
|
e670f3bf03 | ||
|
|
c26a9404c5 | ||
|
|
4001fe5eac | ||
|
|
2992dd8f8b | ||
|
|
98a03d1e59 | ||
|
|
2088393c79 | ||
|
|
093042b88a | ||
|
|
e5ef35c186 | ||
|
|
1cd23d5efd | ||
|
|
ead5818a3f | ||
|
|
a5f66ada68 | ||
|
|
0919742853 | ||
|
|
f3efffd259 | ||
|
|
f85317983c | ||
|
|
76f709b768 | ||
|
|
e3b2356302 | ||
|
|
3d810211ee | ||
|
|
7453795dc5 | ||
|
|
9de7cd99ee | ||
|
|
51489c1aa5 | ||
|
|
25dd6de770 | ||
|
|
2a825f5a02 | ||
|
|
908d249eb9 | ||
|
|
6cd119e8f4 | ||
|
|
9a59c8eb75 | ||
|
|
452c022d41 | ||
|
|
27e9bab82a | ||
|
|
edef860530 | ||
|
|
032cb63411 | ||
|
|
a1791ba578 | ||
|
|
3a69fd7786 | ||
|
|
8a90723c2e | ||
|
|
af2fc342c7 | ||
|
|
05ea2fcdbe | ||
|
|
6d4321fead | ||
|
|
3f6364c9ea | ||
|
|
0d11b12282 | ||
|
|
0796bcf7d0 | ||
|
|
0b5bec142a | ||
|
|
a5020b58f2 | ||
|
|
f039a74a8f | ||
|
|
0e6bb7390b | ||
|
|
b52b4eecca | ||
|
|
8186977d1d | ||
|
|
86adcfe4d7 | ||
|
|
ce2dd872c4 | ||
|
|
aadc53c90e | ||
|
|
cbc1b6b5c8 | ||
|
|
1aed7a9232 | ||
|
|
b549189644 | ||
|
|
35279d17b4 | ||
|
|
bb403aa0c5 | ||
|
|
04327c288b | ||
|
|
94d60e40d0 | ||
|
|
8505fcb6b7 | ||
|
|
e0a37f7635 | ||
|
|
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 |
@@ -6,3 +6,4 @@ export EVE_CLIENT_WITH_WALLET_ID="<EVE_CLIENT_WITH_WALLET_ID>"
|
||||
export EVE_CLIENT_WITH_WALLET_SECRET="<EVE_CLIENT_WITH_WALLET_SECRET>"
|
||||
export GIT_SHA="1111"
|
||||
export WANDERER_INVITES="false"
|
||||
export WANDERER_PUBLIC_API_DISABLED="false"
|
||||
|
||||
1172
CHANGELOG.md
1172
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
||||
@@ -108,3 +108,7 @@
|
||||
.p-dropdown-empty-message {
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
.p-autocomplete .p-autocomplete-multiple-container .p-autocomplete-token {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
@@ -88,6 +88,23 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
||||
setSystem(undefined);
|
||||
}, []);
|
||||
|
||||
const onSystemTemporaryName = useCallback((temporaryName?: string) => {
|
||||
const { system, outCommand } = ref.current;
|
||||
if (!system) {
|
||||
return;
|
||||
}
|
||||
|
||||
outCommand({
|
||||
type: OutCommand.updateSystemTemporaryName,
|
||||
data: {
|
||||
system_id: system,
|
||||
value: temporaryName ?? '',
|
||||
},
|
||||
});
|
||||
setSystem(undefined);
|
||||
}, []);
|
||||
|
||||
|
||||
const onSystemStatus = useCallback((status: number) => {
|
||||
const { system, outCommand } = ref.current;
|
||||
if (!system) {
|
||||
@@ -161,6 +178,7 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
||||
onLockToggle,
|
||||
onHubToggle,
|
||||
onSystemTag,
|
||||
onSystemTemporaryName,
|
||||
onSystemStatus,
|
||||
onSystemLabels,
|
||||
onOpenSettings,
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
@@ -31,11 +29,12 @@ import {
|
||||
useContextMenuConnectionHandlers,
|
||||
useContextMenuRootHandlers,
|
||||
} from './components';
|
||||
import { OnMapSelectionChange } from './map.types';
|
||||
import { OnMapAddSystemCallback, OnMapSelectionChange } from './map.types';
|
||||
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 };
|
||||
|
||||
@@ -93,12 +92,15 @@ interface MapCompProps {
|
||||
onSelectionChange: OnMapSelectionChange;
|
||||
onManualDelete(systems: string[]): void;
|
||||
onConnectionInfoClick?(e: SolarSystemConnection): void;
|
||||
onAddSystem?: OnMapAddSystemCallback;
|
||||
onSelectionContextMenu?: NodeSelectionMouseHandler;
|
||||
minimapClasses?: string;
|
||||
isShowMinimap?: boolean;
|
||||
onSystemContextMenu: (event: MouseEvent<Element>, systemId: string) => void;
|
||||
showKSpaceBG?: boolean;
|
||||
isThickConnections?: boolean;
|
||||
isShowBackgroundPattern?: boolean;
|
||||
isSoftBackground?: boolean;
|
||||
}
|
||||
|
||||
const MapComp = ({
|
||||
@@ -113,14 +115,17 @@ const MapComp = ({
|
||||
isShowMinimap,
|
||||
showKSpaceBG,
|
||||
isThickConnections,
|
||||
isShowBackgroundPattern,
|
||||
isSoftBackground,
|
||||
onAddSystem,
|
||||
}: MapCompProps) => {
|
||||
const { getNode } = useReactFlow();
|
||||
const [nodes, , onNodesChange] = useNodesState<SolarSystemRawType>(initialNodes);
|
||||
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>[]>(initialEdges);
|
||||
const { getNode, getNodes } = useReactFlow();
|
||||
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
|
||||
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges);
|
||||
|
||||
useMapHandlers(refn, onSelectionChange);
|
||||
useUpdateNodes(nodes);
|
||||
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers();
|
||||
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem });
|
||||
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
|
||||
const { update } = useMapState();
|
||||
|
||||
@@ -181,6 +186,12 @@ const MapComp = ({
|
||||
(changes: NodeChange[]) => {
|
||||
const systemsIdsToRemove: string[] = [];
|
||||
|
||||
// prevents single node deselection on background / same node click
|
||||
// allows deseletion of all nodes if multiple are currently selected
|
||||
if (changes.length === 1 && changes[0].type == 'select' && changes[0].selected === false) {
|
||||
changes[0].selected = getNodes().filter(node => node.selected).length === 1;
|
||||
}
|
||||
|
||||
const nextChanges = changes.reduce((acc, change) => {
|
||||
if (change.type !== 'remove') {
|
||||
return [...acc, change];
|
||||
@@ -218,7 +229,7 @@ const MapComp = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.MapRoot}>
|
||||
<div className={clsx(classes.MapRoot, { ['bg-neutral-900']: isSoftBackground })}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
@@ -265,7 +276,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,10 +3,17 @@ 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';
|
||||
import {
|
||||
MASS_STATE_NAMES,
|
||||
MASS_STATE_NAMES_ORDER,
|
||||
SHIP_SIZES_NAMES,
|
||||
SHIP_SIZES_NAMES_ORDER,
|
||||
SHIP_SIZES_NAMES_SHORT,
|
||||
SHIP_SIZES_SIZE,
|
||||
} from '@/hooks/Mapper/components/map/constants.ts';
|
||||
|
||||
export interface ContextMenuConnectionProps {
|
||||
contextMenuRef: RefObject<ContextMenu>;
|
||||
@@ -35,46 +42,72 @@ 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: `Mass status`,
|
||||
icon: PrimeIcons.CHART_PIE,
|
||||
items: MASS_STATE_NAMES_ORDER.map(x => ({
|
||||
label: MASS_STATE_NAMES[x],
|
||||
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.large : ShipSizeStatus.small,
|
||||
),
|
||||
},
|
||||
{
|
||||
label: `Save mass`,
|
||||
className: clsx({
|
||||
[classes.ConnectionSave]: edge.data?.locked,
|
||||
}),
|
||||
icon: PrimeIcons.LOCK,
|
||||
command: () => onToggleMassSave(!edge.data?.locked),
|
||||
},
|
||||
...(!isFrigateSize
|
||||
? [
|
||||
{
|
||||
label: `Mass status`,
|
||||
icon: PrimeIcons.CHART_PIE,
|
||||
items: MASS_STATE_NAMES_ORDER.map(x => ({
|
||||
label: MASS_STATE_NAMES[x],
|
||||
className: clsx({
|
||||
[classes.SelectedItem]: edge.data?.mass_status === x,
|
||||
}),
|
||||
command: () => onChangeMassState(x),
|
||||
})),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
{
|
||||
label: `Ship Size`,
|
||||
icon: PrimeIcons.CLOUD,
|
||||
items: SHIP_SIZES_NAMES_ORDER.map(x => ({
|
||||
label: (
|
||||
<div className="grid grid-cols-[20px_120px_1fr_40px] gap-2 items-center">
|
||||
<div className="text-[12px] font-bold text-stone-400">{SHIP_SIZES_NAMES_SHORT[x]}</div>
|
||||
<div>{SHIP_SIZES_NAMES[x]}</div>
|
||||
<div></div>
|
||||
<div className="flex justify-end whitespace-nowrap text-[12px] font-bold text-stone-500">
|
||||
{SHIP_SIZES_SIZE[x]} t.
|
||||
</div>
|
||||
</div>
|
||||
) as unknown as string, // TODO my lovely kostyl
|
||||
className: clsx({
|
||||
[classes.SelectedItem]: edge.data?.mass_status === x,
|
||||
[classes.SelectedItem]: edge.data?.ship_size_type === x,
|
||||
}),
|
||||
command: () => onChangeMassState(x),
|
||||
command: () => onChangeShipSizeStatus(x),
|
||||
})),
|
||||
},
|
||||
]
|
||||
@@ -85,7 +118,7 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
|
||||
command: onDeleteConnection,
|
||||
},
|
||||
];
|
||||
}, [edge, onChangeTimeState, onDeleteConnection, onChangeMassState, onChangeShipSizeStatus]);
|
||||
}, [edge, onChangeTimeState, onDeleteConnection, onChangeShipSizeStatus, onToggleMassSave, onChangeMassState]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -80,14 +97,16 @@ export const useContextMenuConnectionHandlers = () => {
|
||||
},
|
||||
});
|
||||
|
||||
outCommand({
|
||||
type: OutCommand.updateConnectionMassStatus,
|
||||
data: {
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
value: MassState.normal,
|
||||
},
|
||||
});
|
||||
if (status === ShipSizeStatus.small) {
|
||||
outCommand({
|
||||
type: OutCommand.updateConnectionMassStatus,
|
||||
data: {
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
value: MassState.normal,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onToggleMassSave = useCallback((locked: boolean) => {
|
||||
@@ -118,6 +137,7 @@ export const useContextMenuConnectionHandlers = () => {
|
||||
contextMenuRef,
|
||||
onDeleteConnection,
|
||||
onChangeTimeState,
|
||||
onChangeType,
|
||||
onChangeMassState,
|
||||
onChangeShipSizeStatus,
|
||||
onToggleMassSave,
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { useReactFlow, XYPosition } from 'reactflow';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { useMapState } from '../../MapProvider.tsx';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { OnMapAddSystemCallback } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||
|
||||
export const useContextMenuRootHandlers = () => {
|
||||
type UseContextMenuRootHandlers = {
|
||||
onAddSystem?: OnMapAddSystemCallback;
|
||||
};
|
||||
|
||||
export const useContextMenuRootHandlers = ({ onAddSystem }: UseContextMenuRootHandlers = {}) => {
|
||||
const rf = useReactFlow();
|
||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||
const { outCommand } = useMapState();
|
||||
const [position, setPosition] = useState<XYPosition | null>(null);
|
||||
|
||||
const handleRootContext = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
@@ -18,14 +20,17 @@ export const useContextMenuRootHandlers = () => {
|
||||
contextMenuRef.current?.show(e);
|
||||
};
|
||||
|
||||
const onAddSystem = () => {
|
||||
outCommand({ type: OutCommand.manualAddSystem, data: { coordinates: position } });
|
||||
};
|
||||
const ref = useRef({ onAddSystem, position });
|
||||
ref.current = { onAddSystem, position };
|
||||
|
||||
const onAddSystemCallback = useCallback(() => {
|
||||
ref.current.onAddSystem?.({ coordinates: position });
|
||||
}, [position]);
|
||||
|
||||
return {
|
||||
handleRootContext,
|
||||
|
||||
contextMenuRef,
|
||||
onAddSystem,
|
||||
onAddSystem: onAddSystemCallback,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -13,17 +13,21 @@
|
||||
stroke-width: 2px;
|
||||
|
||||
&.MassVerge:not(&.Frigate) {
|
||||
stroke: #af2900;
|
||||
stroke: #af0000;
|
||||
}
|
||||
|
||||
&.MassHalf:not(&.Frigate) {
|
||||
stroke: #a85f00;
|
||||
stroke: #ffd700;
|
||||
}
|
||||
|
||||
&.Frigate {
|
||||
stroke: #d4f0ff;
|
||||
}
|
||||
|
||||
&.Gate {
|
||||
stroke: #1c1e15;
|
||||
}
|
||||
|
||||
&.Hovered {
|
||||
stroke: #4e5d6c;
|
||||
stroke-width: 2px;
|
||||
@@ -76,6 +80,11 @@
|
||||
stroke-width: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&.Gate {
|
||||
stroke: #9aff40;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ClickPath {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
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';
|
||||
import { SHIP_SIZES_DESCRIPTION, SHIP_SIZES_NAMES_SHORT } from '@/hooks/Mapper/components/map/constants.ts';
|
||||
|
||||
const MAP_TRANSLATES: Record<string, string> = {
|
||||
[Position.Top]: 'translate(-48%, 0%)',
|
||||
@@ -30,9 +31,18 @@ const MAP_OFFSETS: Record<string, { x: number; y: number }> = {
|
||||
[Position.Right]: { x: 0, y: 0 },
|
||||
};
|
||||
|
||||
export const SHIP_SIZES_COLORS = {
|
||||
[ShipSizeStatus.small]: 'bg-indigo-400',
|
||||
[ShipSizeStatus.medium]: 'bg-cyan-500',
|
||||
[ShipSizeStatus.large]: '',
|
||||
[ShipSizeStatus.freight]: 'bg-lime-400',
|
||||
[ShipSizeStatus.capital]: 'bg-red-400',
|
||||
};
|
||||
|
||||
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 +55,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 +65,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 +79,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 +92,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 +135,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(
|
||||
@@ -131,6 +146,19 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
|
||||
<span className={clsx(PrimeIcons.LOCK, classes.icon)} />
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
|
||||
{isWormhole && data.ship_size_type !== ShipSizeStatus.large && (
|
||||
<WdTooltipWrapper
|
||||
content={SHIP_SIZES_DESCRIPTION[data.ship_size_type]}
|
||||
className={clsx(
|
||||
classes.LinkLabel,
|
||||
'pointer-events-auto rounded opacity-100 cursor-auto text-neutral-900 font-bold',
|
||||
SHIP_SIZES_COLORS[data.ship_size_type],
|
||||
)}
|
||||
>
|
||||
{SHIP_SIZES_NAMES_SHORT[data.ship_size_type]}
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
</div>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
|
||||
@@ -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,10 @@ 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 { useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
|
||||
|
||||
import {
|
||||
EFFECT_BACKGROUND_STYLES,
|
||||
LABELS_INFO,
|
||||
@@ -12,8 +16,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 +55,11 @@ 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 isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
|
||||
|
||||
const {
|
||||
system_class,
|
||||
security,
|
||||
@@ -63,9 +73,10 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
|
||||
solar_system_name,
|
||||
} = data.system_static_info;
|
||||
|
||||
const { locked, name, tag, status, labels, id } = data || {};
|
||||
const signatures = data.system_signatures;
|
||||
|
||||
const { locked, name, tag, status, labels, id, temporary_name: temporaryName } = data || {};
|
||||
|
||||
const customName = solar_system_name !== name ? name : undefined;
|
||||
|
||||
const {
|
||||
data: {
|
||||
@@ -128,6 +139,26 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
|
||||
const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
|
||||
const regionClass = showKSpaceBG ? SpaceToClass[space] : null;
|
||||
|
||||
const systemName = isTempSystemNameEnabled && temporaryName || solar_system_name;
|
||||
|
||||
const customName = (isTempSystemNameEnabled && temporaryName && name) || (solar_system_name !== name && name);
|
||||
|
||||
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 && (
|
||||
@@ -181,7 +212,7 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
|
||||
)}
|
||||
>
|
||||
{solar_system_name}
|
||||
{systemName}
|
||||
</div>
|
||||
|
||||
{isWormhole && (
|
||||
@@ -237,6 +268,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, ShipSizeStatus } 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 = {
|
||||
@@ -720,16 +727,41 @@ export const MASS_STATE_NAMES = {
|
||||
[MassState.verge]: 'Verge of collapse',
|
||||
};
|
||||
|
||||
// export const SHIP_SIZES_NAMES_ORDER = [
|
||||
// ShipSizeStatus.small,
|
||||
// ShipSizeStatus.normal,
|
||||
// // ShipSizeStatus.large,
|
||||
// // ShipSizeStatus.capital,
|
||||
// ];
|
||||
//
|
||||
// export const SHIP_SIZES_NAMES = {
|
||||
// [ShipSizeStatus.small]: 'Frigate',
|
||||
// [ShipSizeStatus.normal]: 'Normal',
|
||||
// // [ShipSizeStatus.large]: 'Normal',
|
||||
// // [ShipSizeStatus.capital]: 'Normal',
|
||||
// };
|
||||
export const SHIP_SIZES_NAMES_ORDER = [
|
||||
ShipSizeStatus.small,
|
||||
ShipSizeStatus.medium,
|
||||
ShipSizeStatus.large,
|
||||
ShipSizeStatus.freight,
|
||||
ShipSizeStatus.capital,
|
||||
];
|
||||
|
||||
export const SHIP_SIZES_NAMES = {
|
||||
[ShipSizeStatus.small]: 'Frigate',
|
||||
[ShipSizeStatus.medium]: 'Medium',
|
||||
[ShipSizeStatus.large]: 'Normal',
|
||||
[ShipSizeStatus.freight]: 'Huge',
|
||||
[ShipSizeStatus.capital]: 'Capital',
|
||||
};
|
||||
export const SHIP_SIZES_SIZE = {
|
||||
[ShipSizeStatus.small]: '5K',
|
||||
[ShipSizeStatus.medium]: '62K',
|
||||
[ShipSizeStatus.large]: '375K',
|
||||
[ShipSizeStatus.freight]: '1M',
|
||||
[ShipSizeStatus.capital]: '2M',
|
||||
};
|
||||
|
||||
export const SHIP_SIZES_DESCRIPTION = {
|
||||
[ShipSizeStatus.small]: 'Frigate wormhole - up to Destroyer | 5K t.',
|
||||
[ShipSizeStatus.medium]: 'Cruise wormhole - up to Battlecruiser | 62K t.',
|
||||
[ShipSizeStatus.large]: 'Large wormhole - up to Battleship | 375K t.',
|
||||
[ShipSizeStatus.freight]: 'Huge wormhole - up to Freighter | 1M t.',
|
||||
[ShipSizeStatus.capital]: 'Capital wormhole - up to Capital | 2M t.',
|
||||
};
|
||||
|
||||
export const SHIP_SIZES_NAMES_SHORT = {
|
||||
[ShipSizeStatus.small]: 'S',
|
||||
[ShipSizeStatus.medium]: 'M',
|
||||
[ShipSizeStatus.large]: 'L',
|
||||
[ShipSizeStatus.freight]: 'H',
|
||||
[ShipSizeStatus.capital]: 'XL',
|
||||
};
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types/system';
|
||||
import { SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||
import { XYPosition } from 'reactflow';
|
||||
|
||||
export type MapSolarSystemType = Omit<SolarSystemRawType, 'position'>;
|
||||
|
||||
@@ -7,3 +8,5 @@ export type OnMapSelectionChange = (event: {
|
||||
systems: string[];
|
||||
connections: Pick<SolarSystemConnection, 'source' | 'target'>[];
|
||||
}) => void;
|
||||
|
||||
export type OnMapAddSystemCallback = (props: { coordinates: XYPosition | null }) => void;
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
.SearchItem {
|
||||
& > * {
|
||||
font-size: 13px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.SearchItemEffect {
|
||||
font-weight: initial !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { IconField } from 'primereact/iconfield';
|
||||
import { AutoComplete } from 'primereact/autocomplete';
|
||||
import { OutCommand, SearchSystemItem } from '@/hooks/Mapper/types';
|
||||
import { SystemViewStandalone, WHClassView, WHEffectView } from '@/hooks/Mapper/components/ui-kit';
|
||||
import classes from './AddSystemDialog.module.scss';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||
import { sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||
|
||||
export type SearchOnSubmitCallback = (item: SearchSystemItem) => void;
|
||||
|
||||
interface AddSystemDialogProps {
|
||||
title?: string;
|
||||
visible: boolean;
|
||||
setVisible: (visible: boolean) => void;
|
||||
onSubmit?: SearchOnSubmitCallback;
|
||||
excludedSystems?: number[];
|
||||
}
|
||||
|
||||
export const AddSystemDialog = ({
|
||||
title = 'Add system',
|
||||
visible,
|
||||
setVisible,
|
||||
onSubmit,
|
||||
excludedSystems = [],
|
||||
}: AddSystemDialogProps) => {
|
||||
const {
|
||||
outCommand,
|
||||
data: { wormholesData },
|
||||
} = useMapRootState();
|
||||
|
||||
const inputRef = useRef<any>();
|
||||
const onShow = useCallback(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const [filteredItems, setFilteredItems] = useState<SearchSystemItem[]>([]);
|
||||
const [selectedItem, setSelectedItem] = useState<SearchSystemItem[] | null>(null);
|
||||
|
||||
const searchItems = useCallback(
|
||||
async (event: { query: string }) => {
|
||||
if (event.query.length < 2) {
|
||||
setFilteredItems([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const query = event.query;
|
||||
|
||||
if (query.length === 0) {
|
||||
setFilteredItems([]);
|
||||
} else {
|
||||
try {
|
||||
const result = await outCommand({
|
||||
type: OutCommand.searchSystems,
|
||||
data: {
|
||||
text: query,
|
||||
},
|
||||
});
|
||||
|
||||
let prepared = (result.systems as SearchSystemItem[]).sort((a, b) => {
|
||||
const amatch = a.label.indexOf(query);
|
||||
const bmatch = b.label.indexOf(query);
|
||||
return amatch - bmatch;
|
||||
});
|
||||
|
||||
if (excludedSystems) {
|
||||
prepared = prepared.filter(x => !excludedSystems.includes(x.system_static_info.solar_system_id));
|
||||
}
|
||||
|
||||
setFilteredItems(prepared);
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
setFilteredItems([]);
|
||||
}
|
||||
}
|
||||
},
|
||||
[excludedSystems, outCommand],
|
||||
);
|
||||
|
||||
const ref = useRef({ onSubmit, selectedItem });
|
||||
ref.current = { onSubmit, selectedItem };
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
const { onSubmit, selectedItem } = ref.current;
|
||||
setFilteredItems([]);
|
||||
setSelectedItem([]);
|
||||
|
||||
if (!selectedItem) {
|
||||
setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
onSubmit?.(selectedItem[0]);
|
||||
setVisible(false);
|
||||
}, [setVisible]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
header={title}
|
||||
visible={visible}
|
||||
draggable={false}
|
||||
style={{ width: '520px' }}
|
||||
onShow={onShow}
|
||||
onHide={() => {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-3 px-1.5">
|
||||
<div className="flex flex-col gap-2 py-3.5">
|
||||
<div className="flex flex-col gap-1">
|
||||
<IconField>
|
||||
<AutoComplete
|
||||
ref={inputRef}
|
||||
multiple
|
||||
showEmptyMessage
|
||||
scrollHeight="300px"
|
||||
value={selectedItem}
|
||||
suggestions={filteredItems}
|
||||
completeMethod={searchItems}
|
||||
onChange={e => {
|
||||
setSelectedItem(e.value.length < 2 ? e.value : [e.value[e.value.length - 1]]);
|
||||
}}
|
||||
emptyMessage="Not found any system..."
|
||||
placeholder="Type here..."
|
||||
field="label"
|
||||
id="value"
|
||||
className="w-full"
|
||||
itemTemplate={(item: SearchSystemItem) => {
|
||||
const { security, system_class, effect_power, effect_name, statics } = item.system_static_info;
|
||||
const sortedStatics = sortWHClasses(wormholesData, statics);
|
||||
const isWH = isWormholeSpace(system_class);
|
||||
|
||||
return (
|
||||
<div className={clsx('flex gap-1.5', classes.SearchItem)}>
|
||||
<SystemViewStandalone
|
||||
security={security}
|
||||
system_class={system_class}
|
||||
solar_system_id={item.value}
|
||||
class_title={item.class_title}
|
||||
solar_system_name={item.label}
|
||||
region_name={item.region_name}
|
||||
/>
|
||||
|
||||
{effect_name && isWH && (
|
||||
<WHEffectView
|
||||
effectName={effect_name}
|
||||
effectPower={effect_power}
|
||||
className={classes.SearchItemEffect}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isWH && (
|
||||
<div className="flex gap-1 grow justify-between">
|
||||
<div></div>
|
||||
<div className="flex gap-1">
|
||||
{sortedStatics.map(x => (
|
||||
<WHClassView key={x} whClassName={x} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
selectedItemTemplate={(item: SearchSystemItem) => (
|
||||
<SystemViewStandalone
|
||||
security={item.system_static_info.security}
|
||||
system_class={item.system_static_info.system_class}
|
||||
solar_system_id={item.value}
|
||||
class_title={item.class_title}
|
||||
solar_system_name={item.label}
|
||||
region_name={item.region_name}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</IconField>
|
||||
|
||||
<span className="text-[12px] text-stone-400 ml-1">*to search type at least 2 symbols.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
outlined
|
||||
disabled={!selectedItem || selectedItem.length !== 1}
|
||||
size="small"
|
||||
label="Submit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './AddSystemDialog';
|
||||
@@ -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) => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { InputTextarea } from 'primereact/inputtextarea';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
@@ -22,10 +23,15 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
|
||||
|
||||
|
||||
const system = getSystemById(systems, systemId);
|
||||
|
||||
|
||||
const [name, setName] = useState('');
|
||||
const [label, setLabel] = useState('');
|
||||
const [temporaryName, setTemporaryName] = useState('')
|
||||
const [description, setDescription] = useState('');
|
||||
const inputRef = useRef<HTMLInputElement>();
|
||||
|
||||
@@ -38,14 +44,15 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
|
||||
setName(system.name || '');
|
||||
setLabel(labels.customLabel);
|
||||
setTemporaryName(system.temporary_name || '');
|
||||
setDescription(system.description || '');
|
||||
}, [system]);
|
||||
|
||||
const ref = useRef({ name, description, label, outCommand, systemId, system });
|
||||
ref.current = { name, description, label, outCommand, systemId, system };
|
||||
const ref = useRef({ name, description, temporaryName, label, outCommand, systemId, system });
|
||||
ref.current = { name, description, label, temporaryName, outCommand, systemId, system };
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
const { name, description, label, outCommand, systemId, system } = ref.current;
|
||||
const { name, description, label, temporaryName, outCommand, systemId, system } = ref.current;
|
||||
|
||||
const outLabel = new LabelsManager(system?.labels ?? '');
|
||||
outLabel.updateCustomLabel(label);
|
||||
@@ -58,6 +65,14 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
},
|
||||
});
|
||||
|
||||
outCommand({
|
||||
type: OutCommand.updateSystemTemporaryName,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
value: temporaryName,
|
||||
},
|
||||
});
|
||||
|
||||
outCommand({
|
||||
type: OutCommand.updateSystemName,
|
||||
data: {
|
||||
@@ -167,6 +182,35 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
</IconField>
|
||||
</div>
|
||||
|
||||
{isTempSystemNameEnabled &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="username">Temporary Name</label>
|
||||
|
||||
<IconField>
|
||||
{temporaryName !== '' && (
|
||||
<WdImgButton
|
||||
className="pi pi-trash text-red-400"
|
||||
textSize={WdImageSize.large}
|
||||
tooltip={{
|
||||
content: 'Remove temporary name',
|
||||
className: 'pi p-input-icon',
|
||||
position: TooltipPosition.top,
|
||||
}}
|
||||
onClick={() => setTemporaryName('')}
|
||||
/>
|
||||
)}
|
||||
<InputText
|
||||
id="temporaryName"
|
||||
aria-describedby="temporaryName"
|
||||
autoComplete="off"
|
||||
value={temporaryName}
|
||||
maxLength={10}
|
||||
onChange={e => setTemporaryName(e.target.value)}
|
||||
/>
|
||||
</IconField>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="username">Description</label>
|
||||
<InputTextarea
|
||||
|
||||
@@ -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,13 @@ 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';
|
||||
import {
|
||||
AddSystemDialog,
|
||||
SearchOnSubmitCallback,
|
||||
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
|
||||
const sortByDist = (a: Route, b: Route) => {
|
||||
const distA = a.has_connection ? a.systems?.length || 0 : Infinity;
|
||||
@@ -161,6 +168,12 @@ export const RoutesWidgetContent = () => {
|
||||
export const RoutesWidgetComp = () => {
|
||||
const [routeSettingsVisible, setRouteSettingsVisible] = useState(false);
|
||||
const { data, update } = useRouteProvider();
|
||||
const {
|
||||
data: { hubs = [] },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const preparedHubs = useMemo(() => hubs.map(x => parseInt(x)), [hubs]);
|
||||
|
||||
const isSecure = data.path_type === 'secure';
|
||||
const handleSecureChange = useCallback(() => {
|
||||
@@ -170,27 +183,70 @@ export const RoutesWidgetComp = () => {
|
||||
});
|
||||
}, [data, update]);
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const compact = useMaxWidth(ref, 155);
|
||||
const [openAddSystem, setOpenAddSystem] = useState<boolean>(false);
|
||||
|
||||
const onAddSystem = useCallback(() => setOpenAddSystem(true), []);
|
||||
|
||||
const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback(
|
||||
async item => {
|
||||
if (preparedHubs.includes(item.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.addHub,
|
||||
data: { system_id: item.value },
|
||||
});
|
||||
},
|
||||
[hubs, outCommand],
|
||||
);
|
||||
|
||||
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')}
|
||||
<WdImgButton
|
||||
className={PrimeIcons.PLUS_CIRCLE}
|
||||
onClick={onAddSystem}
|
||||
tooltip={{
|
||||
content: 'Click here to add new system to routes',
|
||||
}}
|
||||
/>
|
||||
|
||||
<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)}
|
||||
tooltip={{
|
||||
content: 'Click here to open Routes settings',
|
||||
}}
|
||||
/>
|
||||
<WdImgButton className={PrimeIcons.SLIDERS_H} onClick={() => setRouteSettingsVisible(true)} />
|
||||
</LayoutEventBlocker>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<RoutesWidgetContent />
|
||||
<RoutesSettingsDialog visible={routeSettingsVisible} setVisible={setRouteSettingsVisible} />
|
||||
|
||||
<AddSystemDialog
|
||||
title="Add system to routes"
|
||||
visible={openAddSystem}
|
||||
setVisible={() => setOpenAddSystem(false)}
|
||||
onSubmit={handleSubmitAddSystem}
|
||||
/>
|
||||
</Widget>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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,32 +1,46 @@
|
||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { InfoDrawer, LayoutEventBlocker, TooltipPosition, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import {
|
||||
InfoDrawer,
|
||||
LayoutEventBlocker,
|
||||
SystemView,
|
||||
TooltipPosition,
|
||||
WdCheckbox,
|
||||
WdImgButton,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { SystemSignaturesContent } from './SystemSignaturesContent';
|
||||
import {
|
||||
Setting,
|
||||
SystemSignatureSettingsDialog,
|
||||
COSMIC_SIGNATURE,
|
||||
COSMIC_ANOMALY,
|
||||
COSMIC_SIGNATURE,
|
||||
DEPLOYABLE,
|
||||
STRUCTURE,
|
||||
STARBASE,
|
||||
SHIP,
|
||||
DRONE,
|
||||
Setting,
|
||||
SHIP,
|
||||
STARBASE,
|
||||
STRUCTURE,
|
||||
SystemSignatureSettingsDialog,
|
||||
} from './SystemSignatureSettingsDialog';
|
||||
import { SignatureGroup } from '@/hooks/Mapper/types';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } 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 +72,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 +99,34 @@ 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 justify-between items-center gap-1">
|
||||
{!compact && (
|
||||
<div className="flex whitespace-nowrap text-ellipsis overflow-hidden text-stone-400">
|
||||
Signatures {isNotSelectedSystem ? '' : 'in'}
|
||||
</div>
|
||||
)}
|
||||
{!isNotSelectedSystem && <SystemView systemId={systemId} className="select-none text-center" hideRegion />}
|
||||
</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 +150,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 +166,7 @@ export const SystemSignatures = () => {
|
||||
System is not selected
|
||||
</div>
|
||||
) : (
|
||||
<SystemSignaturesContent systemId={systemId} settings={settings} />
|
||||
<SystemSignaturesContent systemId={systemId} settings={settings} onLazyDeleteChange={handleLazyDeleteChange} />
|
||||
)}
|
||||
{visible && (
|
||||
<SystemSignatureSettingsDialog
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
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';
|
||||
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import {
|
||||
getGroupIdByRawGroup,
|
||||
GROUPS_LIST,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
|
||||
import { DataTable, DataTableRowClickEvent, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable';
|
||||
import { Column } from 'primereact/column';
|
||||
@@ -12,6 +14,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 +25,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 +39,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 +49,7 @@ type SystemSignaturesSortSettings = {
|
||||
};
|
||||
|
||||
const SORT_DEFAULT_VALUES: SystemSignaturesSortSettings = {
|
||||
sortField: 'updated_at',
|
||||
sortField: 'inserted_at',
|
||||
sortOrder: -1,
|
||||
};
|
||||
|
||||
@@ -54,6 +59,7 @@ interface SystemSignaturesContentProps {
|
||||
hideLinkedSignatures?: boolean;
|
||||
selectable?: boolean;
|
||||
onSelect?: (signature: SystemSignature) => void;
|
||||
onLazyDeleteChange?: (value: boolean) => void;
|
||||
}
|
||||
export const SystemSignaturesContent = ({
|
||||
systemId,
|
||||
@@ -61,14 +67,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 +85,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 +115,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
|
||||
@@ -113,13 +125,14 @@ export const SystemSignaturesContent = ({
|
||||
}
|
||||
|
||||
const isCosmicSignature = x.kind === COSMIC_SIGNATURE;
|
||||
const preparedGroup = getGroupIdByRawGroup(x.group);
|
||||
|
||||
if (isCosmicSignature) {
|
||||
const showCosmicSignatures = settings.find(y => y.key === COSMIC_SIGNATURE)?.value;
|
||||
if (showCosmicSignatures) {
|
||||
return !x.group || groupSettings.find(y => y.key === x.group)?.value;
|
||||
return !x.group || groupSettings.find(y => y.key === preparedGroup)?.value;
|
||||
} else {
|
||||
return !!x.group && groupSettings.find(y => y.key === x.group)?.value;
|
||||
return !!x.group && groupSettings.find(y => y.key === preparedGroup)?.value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,13 +149,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 +177,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 +216,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 +294,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 +345,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 +372,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 +393,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 +439,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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { GroupType, SignatureGroup } from '@/hooks/Mapper/types';
|
||||
import {
|
||||
GroupType,
|
||||
SignatureGroup,
|
||||
SignatureGroupENG,
|
||||
SignatureGroupRU,
|
||||
SignatureKind,
|
||||
SignatureKindENG,
|
||||
SignatureKindRU,
|
||||
} from '@/hooks/Mapper/types';
|
||||
|
||||
export const TIME_ONE_MINUTE = 1000 * 60;
|
||||
export const TIME_TEN_MINUTES = 1000 * 60 * 10;
|
||||
@@ -24,3 +32,43 @@ export const GROUPS: Record<SignatureGroup, GroupType> = {
|
||||
[SignatureGroup.Wormhole]: { id: SignatureGroup.Wormhole, icon: '/icons/brackets/wormhole.png', ...wh },
|
||||
[SignatureGroup.CosmicSignature]: { id: SignatureGroup.CosmicSignature, icon: '/icons/x_close14.png', w: 9, h: 9 },
|
||||
};
|
||||
|
||||
export const MAPPING_GROUP_TO_ENG = {
|
||||
// ENGLISH
|
||||
[SignatureGroupENG.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupENG.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupENG.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupENG.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupENG.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupENG.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupENG.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
|
||||
// RUSSIAN
|
||||
[SignatureGroupRU.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupRU.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupRU.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupRU.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupRU.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupRU.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupRU.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
};
|
||||
|
||||
export const MAPPING_TYPE_TO_ENG = {
|
||||
// ENGLISH
|
||||
[SignatureKindENG.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindENG.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindENG.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindENG.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindENG.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindENG.Drone]: SignatureKind.Drone,
|
||||
|
||||
// RUSSIAN
|
||||
[SignatureKindRU.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindRU.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindRU.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindRU.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindRU.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindRU.Drone]: SignatureKind.Drone,
|
||||
};
|
||||
|
||||
export const getGroupIdByRawGroup = (val: string) => MAPPING_GROUP_TO_ENG[val as SignatureGroup];
|
||||
|
||||
@@ -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,15 +53,18 @@ 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 = [
|
||||
{ prop: UserSettingsRemoteProps.delete_connection_with_sigs, label: 'Delete connections to linked signatures' },
|
||||
{ prop: InterfaceStoredSettingsProps.isThickConnections, label: 'Thicker connections' },
|
||||
];
|
||||
|
||||
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,17 @@ 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('-');
|
||||
|
||||
if (type === 'shattered') {
|
||||
return systemStatic.is_shattered && group === spawnClassGroup;
|
||||
}
|
||||
|
||||
return group === spawnClassGroup;
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
statics: possibleWHTypes
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './SignatureGroupSelect';
|
||||
export * from './SignatureGroupContent';
|
||||
export * from './SignatureK162TypeSelect';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Map } from '@/hooks/Mapper/components/map/Map.tsx';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||
import { OnMapAddSystemCallback, OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||
import isEqual from 'lodash.isequal';
|
||||
import { ContextMenuSystem, useContextMenuSystemHandlers } from '@/hooks/Mapper/components/contexts';
|
||||
import {
|
||||
@@ -14,14 +14,18 @@ import classes from './MapWrapper.module.scss';
|
||||
import { Connections } from '@/hooks/Mapper/components/mapRootContent/components/Connections';
|
||||
import { ContextMenuSystemMultiple, useContextMenuSystemMultipleHandlers } from '../contexts/ContextMenuSystemMultiple';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import { Node } from 'reactflow';
|
||||
import { Node, XYPosition } from 'reactflow';
|
||||
|
||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { emitMapEvent, useMapEventListener } from '@/hooks/Mapper/events';
|
||||
|
||||
import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/MapRootProvider';
|
||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
import { useCommonMapEventProcessor } from '@/hooks/Mapper/components/mapWrapper/hooks/useCommonMapEventProcessor.ts';
|
||||
import {
|
||||
AddSystemDialog,
|
||||
SearchOnSubmitCallback,
|
||||
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
|
||||
|
||||
// TODO: INFO - this component needs for abstract work with Map instance
|
||||
export const MapWrapper = () => {
|
||||
@@ -34,6 +38,8 @@ export const MapWrapper = () => {
|
||||
isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
|
||||
isShowKSpace,
|
||||
isThickConnections,
|
||||
isShowBackgroundPattern,
|
||||
isSoftBackground,
|
||||
},
|
||||
} = useMapRootState();
|
||||
const { deleteSystems } = useDeleteSystems();
|
||||
@@ -45,6 +51,7 @@ export const MapWrapper = () => {
|
||||
const [openSettings, setOpenSettings] = useState<string | null>(null);
|
||||
const [openLinkSignatures, setOpenLinkSignatures] = useState<any | null>(null);
|
||||
const [openCustomLabel, setOpenCustomLabel] = useState<string | null>(null);
|
||||
const [openAddSystem, setOpenAddSystem] = useState<XYPosition | null>(null);
|
||||
const [selectedConnection, setSelectedConnection] = useState<SolarSystemConnection | null>(null);
|
||||
|
||||
const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems });
|
||||
@@ -121,6 +128,28 @@ export const MapWrapper = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onAddSystem: OnMapAddSystemCallback = useCallback(({ coordinates }) => {
|
||||
setOpenAddSystem(coordinates);
|
||||
}, []);
|
||||
|
||||
const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback(
|
||||
async item => {
|
||||
if (ref.current.systems.some(x => x.system_static_info.solar_system_id === item.value)) {
|
||||
emitMapEvent({
|
||||
name: Commands.centerSystem,
|
||||
data: item.value.toString(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.manualAddSystem,
|
||||
data: { coordinates: openAddSystem, solar_system_id: item.value },
|
||||
});
|
||||
},
|
||||
[openAddSystem, outCommand],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Map
|
||||
@@ -135,6 +164,9 @@ export const MapWrapper = () => {
|
||||
showKSpaceBG={isShowKSpace}
|
||||
onManualDelete={handleManualDelete}
|
||||
isThickConnections={isThickConnections}
|
||||
isShowBackgroundPattern={isShowBackgroundPattern}
|
||||
isSoftBackground={isSoftBackground}
|
||||
onAddSystem={onAddSystem}
|
||||
/>
|
||||
|
||||
{openSettings != null && (
|
||||
@@ -149,6 +181,12 @@ export const MapWrapper = () => {
|
||||
<SystemLinkSignatureDialog data={openLinkSignatures} setVisible={() => setOpenLinkSignatures(null)} />
|
||||
)}
|
||||
|
||||
<AddSystemDialog
|
||||
visible={!!openAddSystem}
|
||||
setVisible={() => setOpenAddSystem(null)}
|
||||
onSubmit={handleSubmitAddSystem}
|
||||
/>
|
||||
|
||||
<Connections selectedConnection={selectedConnection} onHide={() => setSelectedConnection(null)} />
|
||||
|
||||
<ContextMenuSystem
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -31,12 +31,15 @@ const prepareEffects = (effects: Record<string, EffectRaw>, effectName: string,
|
||||
return out;
|
||||
};
|
||||
|
||||
let counter = 0;
|
||||
|
||||
export interface WHEffectViewProps {
|
||||
effectName: string;
|
||||
effectPower: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const WHEffectView = ({ effectName, effectPower }: WHEffectViewProps) => {
|
||||
export const WHEffectView = ({ effectName, effectPower, className }: WHEffectViewProps) => {
|
||||
const {
|
||||
data: { effects },
|
||||
} = useMapRootState();
|
||||
@@ -49,7 +52,7 @@ export const WHEffectView = ({ effectName, effectPower }: WHEffectViewProps) =>
|
||||
[effectName, effectPower, effects],
|
||||
);
|
||||
|
||||
const targetClass = `wh-effect-name${effectInfo.id}`;
|
||||
const targetClass = useMemo(() => `wh-effect-name${effectInfo.id}-${counter++}`, []);
|
||||
|
||||
return (
|
||||
<div className={classes.WHEffectViewRoot}>
|
||||
@@ -84,7 +87,7 @@ export const WHEffectView = ({ effectName, effectPower }: WHEffectViewProps) =>
|
||||
</div>
|
||||
</FixedTooltip>
|
||||
|
||||
<div className={clsx('font-bold select-none cursor-help w-min-content', effectClass, targetClass)}>
|
||||
<div className={clsx('font-bold select-none cursor-help w-min-content', effectClass, targetClass, className)}>
|
||||
{effectName}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { COSMIC_SIGNATURE } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { SignatureGroup, SignatureKind, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { MAPPING_TYPE_TO_ENG } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
|
||||
export const parseSignatures = (value: string, availableKeys: string[]): SystemSignature[] => {
|
||||
const outArr: SystemSignature[] = [];
|
||||
@@ -14,10 +14,12 @@ export const parseSignatures = (value: string, availableKeys: string[]): SystemS
|
||||
continue;
|
||||
}
|
||||
|
||||
const kind = MAPPING_TYPE_TO_ENG[sigArrInfo[1] as SignatureKind];
|
||||
|
||||
outArr.push({
|
||||
eve_id: sigArrInfo[0],
|
||||
kind: availableKeys.includes(sigArrInfo[1]) ? sigArrInfo[1] : COSMIC_SIGNATURE,
|
||||
group: sigArrInfo[2],
|
||||
kind: availableKeys.includes(kind) ? kind : SignatureKind.CosmicSignature,
|
||||
group: sigArrInfo[2] as SignatureGroup,
|
||||
name: sigArrInfo[3],
|
||||
type: '',
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
@@ -9,16 +14,12 @@ export enum TimeStatus {
|
||||
eol,
|
||||
}
|
||||
|
||||
// export enum ShipSizeStatus {
|
||||
// small, // frigates, destroyers - less than 5K t
|
||||
// medium, // less than 20K t
|
||||
// large, // less than 375K t
|
||||
// capital, // less than 1.8M t
|
||||
// }
|
||||
|
||||
export enum ShipSizeStatus {
|
||||
small, // frigates, destroyers - less than 5K t
|
||||
normal,
|
||||
small = 0, // frigates, destroyers - less than 5K t
|
||||
medium = 1, // less than 62K t
|
||||
large = 2, // less than 375K t
|
||||
freight = 3, // less than 1M t
|
||||
capital = 4, // less than 1.8M t
|
||||
}
|
||||
|
||||
export type SolarSystemConnection = {
|
||||
@@ -32,4 +33,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,12 +122,14 @@ 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',
|
||||
updateConnectionCustomInfo = 'update_connection_custom_info',
|
||||
updateSignatures = 'update_signatures',
|
||||
updateSystemName = 'update_system_name',
|
||||
updateSystemTemporaryName = 'update_system_temporary_name',
|
||||
updateSystemDescription = 'update_system_description',
|
||||
updateSystemLabels = 'update_system_labels',
|
||||
updateSystemLocked = 'update_system_locked',
|
||||
@@ -150,6 +154,7 @@ export enum OutCommand {
|
||||
getUserSettings = 'get_user_settings',
|
||||
updateUserSettings = 'update_user_settings',
|
||||
unlinkSignature = 'unlink_signature',
|
||||
searchSystems = 'search_systems',
|
||||
}
|
||||
|
||||
export type OutCommandHandler = <T = any>(event: { type: OutCommand; data: any }) => Promise<T>;
|
||||
|
||||
@@ -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>;
|
||||
@@ -10,6 +10,15 @@ export enum SignatureGroup {
|
||||
CombatSite = 'Combat Site',
|
||||
}
|
||||
|
||||
export enum SignatureKind {
|
||||
CosmicSignature = 'Cosmic Signature',
|
||||
CosmicAnomaly = 'Cosmic Anomaly',
|
||||
Structure = 'Structure',
|
||||
Ship = 'Ship',
|
||||
Deployable = 'Deployable',
|
||||
Drone = 'Drone',
|
||||
}
|
||||
|
||||
export type GroupType = {
|
||||
id: string;
|
||||
icon: string;
|
||||
@@ -19,12 +28,52 @@ export type GroupType = {
|
||||
|
||||
export type SystemSignature = {
|
||||
eve_id: string;
|
||||
kind: string;
|
||||
kind: SignatureKind;
|
||||
name: string;
|
||||
custom_info?: string;
|
||||
description?: string;
|
||||
group: SignatureGroup;
|
||||
type: string;
|
||||
k162Type?: string;
|
||||
linked_system?: SolarSystemStaticInfoRaw;
|
||||
inserted_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export enum SignatureKindENG {
|
||||
CosmicSignature = 'Cosmic Signature',
|
||||
CosmicAnomaly = 'Cosmic Anomaly',
|
||||
Structure = 'Structure',
|
||||
Ship = 'Ship',
|
||||
Deployable = 'Deployable',
|
||||
Drone = 'Drone',
|
||||
}
|
||||
|
||||
export enum SignatureKindRU {
|
||||
CosmicSignature = 'Скрытый сигнал',
|
||||
CosmicAnomaly = 'Космическая аномалия',
|
||||
Structure = 'Сооружение',
|
||||
Ship = 'Корабль',
|
||||
Deployable = 'Полевые блоки',
|
||||
Drone = 'Дрон',
|
||||
}
|
||||
|
||||
export enum SignatureGroupENG {
|
||||
CosmicSignature = 'Cosmic Signature',
|
||||
Wormhole = 'Wormhole',
|
||||
GasSite = 'Gas Site',
|
||||
RelicSite = 'Relic Site',
|
||||
DataSite = 'Data Site',
|
||||
OreSite = 'Ore Site',
|
||||
CombatSite = 'Combat Site',
|
||||
}
|
||||
|
||||
export enum SignatureGroupRU {
|
||||
CosmicSignature = 'Скрытый сигнал',
|
||||
Wormhole = 'Червоточина',
|
||||
GasSite = 'Газовый район',
|
||||
RelicSite = 'Археологический район',
|
||||
DataSite = 'Информационный район',
|
||||
OreSite = 'Астероидный район',
|
||||
CombatSite = 'Боевой район',
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { XYPosition } from 'reactflow';
|
||||
|
||||
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
|
||||
|
||||
export enum SolarSystemStaticInfoRawNames {
|
||||
regionId = 'region_id',
|
||||
constellationId = 'constellation_id',
|
||||
@@ -114,6 +116,18 @@ export type SolarSystemRawType = {
|
||||
tag: string | null;
|
||||
status: number;
|
||||
name: string | null;
|
||||
temporary_name: string | null;
|
||||
|
||||
system_static_info: SolarSystemStaticInfoRaw;
|
||||
system_signatures: SystemSignature[];
|
||||
};
|
||||
|
||||
export type SearchSystemItem = {
|
||||
class_title: string;
|
||||
constellation_name: string;
|
||||
label: string;
|
||||
region_name: string;
|
||||
system_static_info: SolarSystemStaticInfoRaw;
|
||||
value: number;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
export default {
|
||||
mounted() {
|
||||
const hook = this;
|
||||
const url = hook.el.dataset.url;
|
||||
const button = hook.el;
|
||||
const button = this.el;
|
||||
|
||||
button.addEventListener('click', function () {
|
||||
// Get the URL from the data attribute
|
||||
|
||||
button.classList.remove('copied');
|
||||
|
||||
// Copy the URL to the clipboard
|
||||
navigator.clipboard
|
||||
.writeText(url)
|
||||
.writeText(button.dataset.url)
|
||||
.then(() => {
|
||||
button.classList.add('copied');
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ import CopyToClipboard from './copyToClipboard';
|
||||
import DownloadJson from './downloadJson';
|
||||
import NewVersionUpdate from './newVersionUpdate';
|
||||
import MapAction from './maps/mapAction';
|
||||
import ShowCharactersAddAlert from './showCharactersAddAlert';
|
||||
|
||||
export default {
|
||||
DownloadJson,
|
||||
@@ -20,4 +21,5 @@ export default {
|
||||
Ping,
|
||||
CopyToClipboard,
|
||||
NewVersionUpdate,
|
||||
ShowCharactersAddAlert,
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
|
||||
11
assets/js/hooks/showCharactersAddAlert.ts
Normal file
11
assets/js/hooks/showCharactersAddAlert.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export default {
|
||||
mounted() {
|
||||
this.pushEvent('restore_show_characters_add_alert', {
|
||||
value: localStorage.getItem('wanderer:hide_characters_add_alert') !== 'true',
|
||||
});
|
||||
|
||||
document.getElementById('characters-add-alert-hide')?.addEventListener('click', e => {
|
||||
localStorage.setItem('wanderer:hide_characters_add_alert', 'true');
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -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"
|
||||
|
||||
BIN
assets/static/images/news/01-05-map-public-api/generate-key.png
Normal file
BIN
assets/static/images/news/01-05-map-public-api/generate-key.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
@@ -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==
|
||||
|
||||
@@ -48,6 +48,11 @@ port =
|
||||
|
||||
scheme = System.get_env("WEB_EXTERNAL_SCHEME", "http")
|
||||
|
||||
public_api_disabled =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("WANDERER_PUBLIC_API_DISABLED", "false")
|
||||
|> String.to_existing_atom()
|
||||
|
||||
map_subscriptions_enabled =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("WANDERER_MAP_SUBSCRIPTIONS_ENABLED", "false")
|
||||
@@ -83,6 +88,7 @@ config :wanderer_app,
|
||||
admins: admins,
|
||||
corp_id: System.get_env("WANDERER_CORP_ID", "-1") |> String.to_integer(),
|
||||
corp_wallet: System.get_env("WANDERER_CORP_WALLET", ""),
|
||||
public_api_disabled: public_api_disabled,
|
||||
map_subscriptions_enabled: map_subscriptions_enabled,
|
||||
wallet_tracking_enabled: wallet_tracking_enabled,
|
||||
subscription_settings: %{
|
||||
|
||||
@@ -21,6 +21,7 @@ defmodule WandererApp.Api.Map do
|
||||
define(:update_options, action: :update_options)
|
||||
define(:assign_owner, action: :assign_owner)
|
||||
define(:mark_as_deleted, action: :mark_as_deleted)
|
||||
define(:update_api_key, action: :update_api_key)
|
||||
|
||||
define(:by_id,
|
||||
get_by: [:id],
|
||||
@@ -122,6 +123,11 @@ defmodule WandererApp.Api.Map do
|
||||
|
||||
change(set_attribute(:deleted, true))
|
||||
end
|
||||
|
||||
update :update_api_key do
|
||||
accept [:public_api_key]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
attributes do
|
||||
@@ -141,6 +147,10 @@ defmodule WandererApp.Api.Map do
|
||||
attribute :description, :string
|
||||
attribute :personal_note, :string
|
||||
|
||||
attribute :public_api_key, :string do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :hubs, {:array, :string} do
|
||||
allow_nil?(true)
|
||||
|
||||
|
||||
@@ -12,32 +12,37 @@ 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(:tracked_by_map_filtered,
|
||||
action: :tracked_by_map_filtered
|
||||
)
|
||||
|
||||
define(:tracked_by_map_all,
|
||||
action: :tracked_by_map_all
|
||||
)
|
||||
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)
|
||||
define(:tracked_by_map_all, action: :tracked_by_map_all)
|
||||
|
||||
define(:track, action: :track)
|
||||
define(:untrack, action: :untrack)
|
||||
|
||||
define(:follow, action: :follow)
|
||||
define(:unfollow, action: :unfollow)
|
||||
end
|
||||
|
||||
actions do
|
||||
default_accept [
|
||||
:map_id,
|
||||
:character_id,
|
||||
:tracked
|
||||
:tracked,
|
||||
:followed
|
||||
]
|
||||
|
||||
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)
|
||||
@@ -64,6 +69,14 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
update :untrack do
|
||||
change(set_attribute(:tracked, false))
|
||||
end
|
||||
|
||||
update :follow do
|
||||
change(set_attribute(:followed, true))
|
||||
end
|
||||
|
||||
update :unfollow do
|
||||
change(set_attribute(:followed, false))
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
@@ -74,6 +87,11 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :followed, :boolean do
|
||||
default false
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
create_timestamp(:inserted_at)
|
||||
update_timestamp(:updated_at)
|
||||
end
|
||||
|
||||
@@ -29,13 +29,16 @@ defmodule WandererApp.Api.MapConnection do
|
||||
define(:update_ship_size_type, action: :update_ship_size_type)
|
||||
define(:update_locked, action: :update_locked)
|
||||
define(:update_custom_info, action: :update_custom_info)
|
||||
define(:update_type, action: :update_type)
|
||||
define(:update_wormhole_type, action: :update_wormhole_type)
|
||||
end
|
||||
|
||||
actions do
|
||||
default_accept [
|
||||
:map_id,
|
||||
:solar_system_source,
|
||||
:solar_system_target
|
||||
:solar_system_target,
|
||||
:type
|
||||
]
|
||||
|
||||
defaults [:create, :read, :update, :destroy]
|
||||
@@ -92,6 +95,14 @@ defmodule WandererApp.Api.MapConnection do
|
||||
update :update_custom_info do
|
||||
accept [:custom_info]
|
||||
end
|
||||
|
||||
update :update_type do
|
||||
accept [:type]
|
||||
end
|
||||
|
||||
update :update_wormhole_type do
|
||||
accept [:wormhole_type]
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
@@ -117,11 +128,21 @@ defmodule WandererApp.Api.MapConnection do
|
||||
allow_nil?(true)
|
||||
end
|
||||
|
||||
# where 0 - Frigate
|
||||
# where 1 - Medium and Large
|
||||
# where 2 - Capital
|
||||
# where 0 - Frigate (small
|
||||
# where 1 - Medium
|
||||
# where 2 - Large
|
||||
# where 3 - Freight
|
||||
# where 4 - Capital
|
||||
attribute :ship_size_type, :integer do
|
||||
default(1)
|
||||
default(2)
|
||||
|
||||
allow_nil?(true)
|
||||
end
|
||||
|
||||
# where 0 - Wormhole
|
||||
# where 1 - Gate
|
||||
attribute :type, :integer do
|
||||
default(0)
|
||||
|
||||
allow_nil?(true)
|
||||
end
|
||||
|
||||
@@ -46,6 +46,7 @@ defmodule WandererApp.Api.MapSystem do
|
||||
define(:update_locked, action: :update_locked)
|
||||
define(:update_status, action: :update_status)
|
||||
define(:update_tag, action: :update_tag)
|
||||
define(:update_temporary_name, action: :update_temporary_name)
|
||||
define(:update_labels, action: :update_labels)
|
||||
define(:update_position, action: :update_position)
|
||||
define(:update_visible, action: :update_visible)
|
||||
@@ -102,6 +103,10 @@ defmodule WandererApp.Api.MapSystem do
|
||||
accept [:tag]
|
||||
end
|
||||
|
||||
update :update_temporary_name do
|
||||
accept [:temporary_name]
|
||||
end
|
||||
|
||||
update :update_labels do
|
||||
accept [:labels]
|
||||
end
|
||||
@@ -141,6 +146,10 @@ defmodule WandererApp.Api.MapSystem do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :temporary_name, :string do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :labels, :string do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -484,7 +489,7 @@ defmodule WandererApp.Character.Tracker do
|
||||
|
||||
defp maybe_update_location(
|
||||
%{
|
||||
character_id: character_id
|
||||
character_id: character_id,
|
||||
} =
|
||||
state,
|
||||
location
|
||||
|
||||
@@ -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),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user