Compare commits

...

103 Commits

Author SHA1 Message Date
Dmitry Popov 1b4852d5b4 Merge pull request #517 from jackmurray/show-temp-name
Show system temp name when linking signatures
2025-10-10 00:00:41 +04:00
CI 5343c34488 chore: [skip ci] 2025-10-09 19:57:07 +00:00
CI 4878be1a53 chore: release version v1.81.5 2025-10-09 19:57:07 +00:00
Dmitry Popov 1ff689c26c fix(Core): Update connection ship size based on linked signature type 2025-10-09 21:56:23 +02:00
CI 79b660e899 chore: [skip ci] 2025-10-09 18:45:16 +00:00
CI 665a679bd5 chore: release version v1.81.4 2025-10-09 18:45:16 +00:00
Dmitry Popov 7bd634eb95 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-10-09 20:44:42 +02:00
Dmitry Popov c3b5a77a86 fix(Core): Fixed signature to system link issues 2025-10-09 20:44:33 +02:00
CI 12f39a0133 chore: [skip ci] 2025-10-07 20:59:47 +00:00
CI ffc2a86e95 chore: release version v1.81.3 2025-10-07 20:59:47 +00:00
Dmitry Popov 82babf41a2 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-10-07 22:59:10 +02:00
Dmitry Popov 81055b4fbd fix(Core): Fixed cancel ping errors 2025-10-07 22:59:01 +02:00
CI 5070a59f88 chore: [skip ci] 2025-10-07 20:49:34 +00:00
CI 65d5bf960d chore: release version v1.81.2 2025-10-07 20:49:34 +00:00
Dmitry Popov 8fc4cb190e Merge pull request #526 from guarzo/guarzo/apicustom
fix: api dropping custom name
2025-10-08 00:49:00 +04:00
Guarzo 095a4b2362 fix: api dropping custom name 2025-10-06 15:52:35 -04:00
CI fafc631e49 chore: [skip ci] 2025-10-02 21:37:58 +00:00
CI e56383c8b1 chore: release version v1.81.1 2025-10-02 21:37:58 +00:00
Dmitry Popov b9c26bdb04 fix(Core): Fixed characters tracking updates. 2025-10-02 23:37:23 +02:00
CI 8aeaa81752 chore: [skip ci] 2025-10-02 16:09:27 +00:00
CI b16ec0490f chore: release version v1.81.0 2025-10-02 16:09:27 +00:00
Dmitry Popov eceaf1d73b Merge pull request #523 from dedo1911/feat/pwa
feat(core): fix pwa icons + add screen in manifest
2025-10-02 20:08:57 +04:00
dedo1911 34cf668a33 feat(core): fix pwa icons + add screen in manifest 2025-10-02 17:57:52 +02:00
CI c22d410c9f chore: [skip ci] 2025-10-02 11:39:21 +00:00
CI fc6af867f2 chore: release version v1.80.0 2025-10-02 11:39:21 +00:00
Dmitry Popov 2d96114984 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-10-02 13:38:50 +02:00
Dmitry Popov fd7e19e490 feat(Core): Added PWA web manifest 2025-10-02 13:38:46 +02:00
CI f7d996f5b2 chore: [skip ci] 2025-10-01 14:32:20 +00:00
CI f8ab1383ab chore: release version v1.79.6 2025-10-01 14:32:20 +00:00
Dmitry Popov e1559aac94 fix(Core): Fixed modals auto-save on Enter. 2025-10-01 16:31:48 +02:00
CI 2e17cce5cd chore: [skip ci] 2025-10-01 13:57:58 +00:00
CI 8fb831f171 chore: release version v1.79.5 2025-10-01 13:57:58 +00:00
Dmitry Popov cb84f34515 fix(Core): Fixed system details modal auto-save on Enter. 2025-10-01 15:57:26 +02:00
CI 272cce1a77 chore: [skip ci] 2025-09-30 13:00:53 +00:00
CI e0e3ed1580 chore: release version v1.79.4 2025-09-30 13:00:53 +00:00
Dmitry Popov c4c848cf37 fix(Core): Fixed updating connection time status based on linked signature data. Fixed FR gas sites parsing.
Build Test / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build Test / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
🧪 Test Suite / Test Suite (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-09-30 15:00:14 +02:00
CI 32d25d86eb chore: [skip ci] 2025-09-27 15:37:16 +00:00
CI 863adccac1 chore: release version v1.79.3 2025-09-27 15:37:16 +00:00
Dmitry Popov 2d527e1d16 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-09-27 17:36:40 +02:00
Dmitry Popov 9a64ad6fa7 fix(Core): Fixed connection passages count 2025-09-27 17:36:35 +02:00
CI 5ce472ebff chore: [skip ci] 2025-09-26 18:28:30 +00:00
CI 76588af12f chore: release version v1.79.2 2025-09-26 18:28:30 +00:00
Dmitry Popov 134f169eb9 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-09-26 20:28:01 +02:00
Dmitry Popov 7c2d731c4c chore: fix 2025-09-26 20:27:54 +02:00
CI c7e2a290cf chore: [skip ci] 2025-09-26 18:27:38 +00:00
CI 5ea966892a chore: release version v1.79.1 2025-09-26 18:27:38 +00:00
Dmitry Popov b879db76b7 chore: fix 2025-09-26 20:27:06 +02:00
CI d13a628029 chore: [skip ci] 2025-09-26 18:18:51 +00:00
CI 7c1e2595e3 chore: release version v1.79.0 2025-09-26 18:18:51 +00:00
Dmitry Popov a99e8a915e Merge pull request #522 from wanderer-industries/update-lifetime
Update lifetime
2025-09-26 22:18:20 +04:00
Dmitry Popov 36f424da0b Merge branch 'main' into update-lifetime 2025-09-26 15:45:19 +02:00
Dmitry Popov c0a65d5a23 Merge branch 'main' into update-lifetime 2025-09-26 00:57:26 +02:00
Dmitry Popov 02e31333d2 chore: fix 2025-09-26 00:54:55 +02:00
Dmitry Popov d69616119d feat(Core): Updated connections EOL logic 2025-09-26 00:54:14 +02:00
Dmitry Popov dbc770d40b Merge branch 'update-lifetime' of github.com:wanderer-industries/wanderer into update-lifetime 2025-09-24 18:44:10 +02:00
CI e69a8fece5 chore: [skip ci] 2025-09-24 16:38:54 +00:00
CI cf20be8a77 chore: release version v1.78.1 2025-09-24 16:38:54 +00:00
Dmitry Popov 450bcb649c Merge pull request #521 from wanderer-industries/kills-fix
Kills fix
2025-09-24 20:38:29 +04:00
Dmitry Popov a00395351e Merge pull request #513 from guarzo/guarzo/killfilter
fix: removed wormhole only logic error
2025-09-24 20:33:00 +04:00
DanSylvest 3b24c760ff fix(Map): Fixed eslint problems 2025-09-24 13:12:32 +03:00
DanSylvest 3801f0be18 Merge branch 'main' into update-lifetime
# Conflicts:
#	assets/js/hooks/Mapper/components/map/components/ContextMenuConnection/ContextMenuConnection.tsx
#	assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx
2025-09-24 13:11:02 +03:00
Dmitry Popov f3104db2e4 Merge branch 'update-lifetime' of github.com:wanderer-industries/wanderer into update-lifetime 2025-09-23 20:19:53 +02:00
CI 602b1028c3 chore: [skip ci] 2025-09-23 16:27:20 +00:00
CI 4f98e979a2 chore: release version v1.78.0 2025-09-23 16:27:20 +00:00
Dmitry Popov e0f46c4af7 Merge pull request #519 from wanderer-industries/jumpgates
Jumpgates
2025-09-23 20:23:25 +04:00
Dmitry Popov bc8a9a2b85 Merge branch 'main' into jumpgates 2025-09-23 18:23:11 +02:00
Dmitry Popov c2b03f925d Merge pull request #518 from leesolway/drag-dialog
Drag signature dialog
2025-09-23 20:22:47 +04:00
Dmitry Popov efa2e52054 Merge branch 'main' into jumpgates 2025-09-23 18:18:32 +02:00
Dmitry Popov cedf5761f8 Merge branch 'main' into jumpgates 2025-09-23 17:51:45 +02:00
Dmitry Popov f601bb8751 Merge branch 'main' into jumpgates 2025-09-23 17:48:59 +02:00
Dmitry Popov c39d2a56d2 Merge branch 'main' into jumpgates 2025-09-23 17:19:05 +02:00
Lee Solway 805722bbe8 Merge branch 'wanderer-industries:main' into drag-dialog 2025-09-20 16:05:34 +01:00
Lee Solway fe3e38343b SystemSettingsDialog & SignatureSettings draggable 2025-09-19 17:35:28 +01:00
DanSylvest 616e82c497 fix(Map): Add support for Bridge. Made all tooltips left and right paddings. 2025-09-18 11:52:16 +03:00
guarzo ab7e47b91f pr feedback 2025-09-18 06:18:23 +00:00
guarzo cf1c103a46 fix: pr feedback 2025-09-17 15:50:26 +00:00
Jack 4bfe60b75c preferentially display the system's temporary name if it has one 2025-09-16 19:58:21 +00:00
Jack 6a44d10c56 enable display of custom name on the connection dropdown 2025-09-16 19:58:00 +00:00
CI 71202a4a29 chore: [skip ci] 2025-09-14 15:00:45 +00:00
CI a7e0ceac4c chore: release version v1.77.19 2025-09-14 15:00:45 +00:00
Aleksei Chichenkov 6bce701aab Merge pull request #515 from wanderer-industries/wh-db-fixed
fix(Map): Fixed for all Large wormholes jump mass from 300 to 375. Fi…
2025-09-14 18:00:20 +03:00
CI f8b9e206a5 chore: [skip ci] 2025-09-13 21:53:27 +00:00
CI 4c1ec2004b chore: release version v1.77.18 2025-09-13 21:53:27 +00:00
Dmitry Popov ebed74d239 Revert "fix: Updated ACL create/update APIs"
This reverts commit b6c680e802.
2025-09-13 23:52:02 +02:00
DanSylvest c789b69b54 fix(Map): Update lifetime design and buttons 2025-09-13 19:17:00 +03:00
Dmitry Popov 24c32511d8 Merge branch 'main' into jumpgates 2025-09-13 11:30:40 +02:00
Dmitry Popov 302fb0642d chore: update connection time values 2025-09-13 11:24:11 +02:00
DanSylvest 06e7b6e3eb fix(Map): Fixed for all Large wormholes jump mass from 300 to 375. Fixed jump mass and total mass for N290, K329. Fixed static for J005663 was H296 now Y790. Added J492 wormhole. Change lifetime for E587 from 16 to 48 2025-09-12 19:37:59 +03:00
DanSylvest 33acd55eaa fix(Map): Update wormhole lifetime UI and removed unnecessary code 2025-09-12 11:05:57 +03:00
CI dec82e89c2 chore: [skip ci] 2025-09-11 19:14:13 +00:00
CI f5ac5bc4ec chore: release version v1.77.17 2025-09-11 19:14:13 +00:00
Dmitry Popov b6c680e802 fix: Updated ACL create/update APIs 2025-09-11 21:13:41 +02:00
CI 5fa57c13b4 chore: [skip ci] 2025-09-11 17:56:11 +00:00
CI acc81fda44 chore: release version v1.77.16 2025-09-11 17:56:11 +00:00
Dmitry Popov 7ab5acf45f Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-09-11 19:55:37 +02:00
Dmitry Popov 0d4ffbcc22 fix: Fixed issue with ACL add members button for managers. Added WANDERER_RESTRICT_ACLS_CREATION env support. 2025-09-11 19:55:32 +02:00
CI a9253ac2df chore: [skip ci] 2025-09-10 07:29:33 +00:00
CI d00b4843a7 chore: release version v1.77.15 2025-09-10 07:29:33 +00:00
Aleksei Chichenkov 6068de2c71 Merge pull request #514 from wanderer-industries/unnecessary-rerenders
fix(Map): Fix problem with unnecessary rerenders and loads routes if …
2025-09-10 10:29:03 +03:00
DanSylvest 73da427c6b fix(Map): Fix problem with unnecessary rerenders and loads routes if move/positioning widgets. 2025-09-10 10:10:17 +03:00
CI 9b7ec0ddfe chore: [skip ci] 2025-09-08 22:07:20 +00:00
guarzo 873946a1a6 fix: removed wormhole only logic error 2025-09-05 01:43:47 +00:00
Dmitry Popov 854524a03c feat(Core): added support for jumpgates connection type 2025-03-23 21:51:26 +01:00
100 changed files with 1859 additions and 870 deletions
+1 -1
View File
@@ -13,4 +13,4 @@ export WANDERER_KILLS_BASE_URL="ws://host.docker.internal:4004"
export WANDERER_SSE_ENABLED="true"
export WANDERER_WEBHOOKS_ENABLED="true"
export WANDERER_SSE_MAX_CONNECTIONS="1000"
export WANDERER_WEBHOOK_TIMEOUT_MS="15000"
export WANDERER_WEBHOOK_TIMEOUT_MS="15000"
+191
View File
@@ -2,6 +2,197 @@
<!-- changelog -->
## [v1.81.5](https://github.com/wanderer-industries/wanderer/compare/v1.81.4...v1.81.5) (2025-10-09)
### Bug Fixes:
* Core: Update connection ship size based on linked signature type
## [v1.81.4](https://github.com/wanderer-industries/wanderer/compare/v1.81.3...v1.81.4) (2025-10-09)
### Bug Fixes:
* Core: Fixed signature to system link issues
## [v1.81.3](https://github.com/wanderer-industries/wanderer/compare/v1.81.2...v1.81.3) (2025-10-07)
### Bug Fixes:
* Core: Fixed cancel ping errors
## [v1.81.2](https://github.com/wanderer-industries/wanderer/compare/v1.81.1...v1.81.2) (2025-10-07)
### Bug Fixes:
* api dropping custom name
## [v1.81.1](https://github.com/wanderer-industries/wanderer/compare/v1.81.0...v1.81.1) (2025-10-02)
### Bug Fixes:
* Core: Fixed characters tracking updates.
## [v1.81.0](https://github.com/wanderer-industries/wanderer/compare/v1.80.0...v1.81.0) (2025-10-02)
### Features:
* core: fix pwa icons + add screen in manifest
## [v1.80.0](https://github.com/wanderer-industries/wanderer/compare/v1.79.6...v1.80.0) (2025-10-02)
### Features:
* Core: Added PWA web manifest
## [v1.79.6](https://github.com/wanderer-industries/wanderer/compare/v1.79.5...v1.79.6) (2025-10-01)
### Bug Fixes:
* Core: Fixed modals auto-save on Enter.
## [v1.79.5](https://github.com/wanderer-industries/wanderer/compare/v1.79.4...v1.79.5) (2025-10-01)
### Bug Fixes:
* Core: Fixed system details modal auto-save on Enter.
## [v1.79.4](https://github.com/wanderer-industries/wanderer/compare/v1.79.3...v1.79.4) (2025-09-30)
### Bug Fixes:
* Core: Fixed updating connection time status based on linked signature data. Fixed FR gas sites parsing.
## [v1.79.3](https://github.com/wanderer-industries/wanderer/compare/v1.79.2...v1.79.3) (2025-09-27)
### Bug Fixes:
* Core: Fixed connection passages count
## [v1.79.2](https://github.com/wanderer-industries/wanderer/compare/v1.79.1...v1.79.2) (2025-09-26)
## [v1.79.1](https://github.com/wanderer-industries/wanderer/compare/v1.79.0...v1.79.1) (2025-09-26)
## [v1.79.0](https://github.com/wanderer-industries/wanderer/compare/v1.78.1...v1.79.0) (2025-09-26)
### Features:
* Core: Updated connections EOL logic
### Bug Fixes:
* Map: Fixed eslint problems
* Map: Update lifetime design and buttons
* Map: Update wormhole lifetime UI and removed unnecessary code
## [v1.78.1](https://github.com/wanderer-industries/wanderer/compare/v1.78.0...v1.78.1) (2025-09-24)
### Bug Fixes:
* pr feedback
* removed wormhole only logic error
## [v1.78.0](https://github.com/wanderer-industries/wanderer/compare/v1.77.19...v1.78.0) (2025-09-23)
### Features:
* Core: added support for jumpgates connection type
### Bug Fixes:
* Map: Add support for Bridge. Made all tooltips left and right paddings.
## [v1.77.19](https://github.com/wanderer-industries/wanderer/compare/v1.77.18...v1.77.19) (2025-09-14)
### Bug Fixes:
* Map: Fixed for all Large wormholes jump mass from 300 to 375. Fixed jump mass and total mass for N290, K329. Fixed static for J005663 was H296 now Y790. Added J492 wormhole. Change lifetime for E587 from 16 to 48
## [v1.77.18](https://github.com/wanderer-industries/wanderer/compare/v1.77.17...v1.77.18) (2025-09-13)
## [v1.77.17](https://github.com/wanderer-industries/wanderer/compare/v1.77.16...v1.77.17) (2025-09-11)
### Bug Fixes:
* Updated ACL create/update APIs
## [v1.77.16](https://github.com/wanderer-industries/wanderer/compare/v1.77.15...v1.77.16) (2025-09-11)
### Bug Fixes:
* Fixed issue with ACL add members button for managers. Added WANDERER_RESTRICT_ACLS_CREATION env support.
## [v1.77.15](https://github.com/wanderer-industries/wanderer/compare/v1.77.14...v1.77.15) (2025-09-10)
### Bug Fixes:
* Map: Fix problem with unnecessary rerenders and loads routes if move/positioning widgets.
## [v1.77.14](https://github.com/wanderer-industries/wanderer/compare/v1.77.13...v1.77.14) (2025-09-08)
+23
View File
@@ -18,5 +18,28 @@ module.exports = {
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
"linebreak-style": "off",
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "primereact/button",
"importNames": ["Button"],
"message": "Use WdButton instead Button"
}
]
}
],
"react/forbid-elements": [
"error",
{
"forbid": [
{
"element": "Button",
"message": "Use WdButton instead Button"
}
]
}
]
},
};
@@ -5,8 +5,7 @@ import { SolarSystemRawType } from '@/hooks/Mapper/types';
import { getSystemById } from '@/hooks/Mapper/helpers';
import clsx from 'clsx';
import { GRADIENT_MENU_ACTIVE_CLASSES } from '@/hooks/Mapper/constants.ts';
import { LayoutEventBlocker } from '@/hooks/Mapper/components/ui-kit';
import { Button } from 'primereact/button';
import { LayoutEventBlocker, WdButton } from '@/hooks/Mapper/components/ui-kit';
const AVAILABLE_TAGS = [
'A',
@@ -61,7 +60,7 @@ export const useTagMenu = (
<LayoutEventBlocker className="flex flex-col gap-1 w-[200px] h-full px-2">
<div className="grid grid-cols-[auto_auto_auto_auto_auto_auto] gap-1">
{AVAILABLE_TAGS.map(x => (
<Button
<WdButton
outlined={system?.tag !== x}
severity="warning"
key={x}
@@ -71,9 +70,9 @@ export const useTagMenu = (
onClick={() => system?.tag !== x && onSystemTag(x)}
>
{x}
</Button>
</WdButton>
))}
<Button
<WdButton
disabled={!isSelectedTag}
icon="pi pi-ban"
size="small"
@@ -81,7 +80,7 @@ export const useTagMenu = (
outlined
severity="help"
onClick={() => onSystemTag()}
></Button>
></WdButton>
</div>
</LayoutEventBlocker>
);
@@ -8,6 +8,10 @@
background-image: linear-gradient(207deg, transparent, var(--conn-frigate));
}
.ConnectionBridge {
background-image: linear-gradient(207deg, transparent, var(--conn-bridge));
}
.ConnectionSave {
background-image: linear-gradient(207deg, transparent, var(--conn-save));
}
@@ -15,3 +19,14 @@
.SelectedItem {
background-color: var(--selected-item-bg);
}
.FastActions {
:global {
.p-menuitem-content {
background-color: initial !important;
}
.p-menuitem-content:hover {
background-color: initial !important;
}
}
}
@@ -1,10 +1,3 @@
import React, { RefObject, useMemo } from 'react';
import { ContextMenu } from 'primereact/contextmenu';
import { PrimeIcons } from 'primereact/api';
import { MenuItem } from 'primereact/menuitem';
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,
@@ -13,14 +6,25 @@ import {
SHIP_SIZES_NAMES_SHORT,
SHIP_SIZES_SIZE,
} from '@/hooks/Mapper/components/map/constants.ts';
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import clsx from 'clsx';
import { PrimeIcons } from 'primereact/api';
import { ContextMenu } from 'primereact/contextmenu';
import { MenuItem } from 'primereact/menuitem';
import React, { RefObject, useMemo } from 'react';
import { Edge } from 'reactflow';
import { LifetimeActionsWrapper } from '@/hooks/Mapper/components/map/components/ContextMenuConnection/LifetimeActionsWrapper.tsx';
import classes from './ContextMenuConnection.module.scss';
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
import { isNullsecSpace } from '@/hooks/Mapper/components/map/helpers/isKnownSpace.ts';
export interface ContextMenuConnectionProps {
contextMenuRef: RefObject<ContextMenu>;
onDeleteConnection(): void;
onChangeTimeState(): void;
onChangeTimeState(lifetime: TimeStatus): void;
onChangeMassState(state: MassState): void;
onChangeShipSizeStatus(state: ShipSizeStatus): void;
onChangeType(type: ConnectionType): void;
onToggleMassSave(isLocked: boolean): void;
onHide(): void;
edge?: Edge<SolarSystemConnection>;
@@ -32,6 +36,7 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
onChangeTimeState,
onChangeMassState,
onChangeShipSizeStatus,
onChangeType,
onToggleMassSave,
onHide,
edge,
@@ -41,88 +46,128 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
return [];
}
const sourceInfo = getSystemStaticInfo(edge.data?.source);
const targetInfo = getSystemStaticInfo(edge.data?.target);
const bothNullsec =
sourceInfo && targetInfo && isNullsecSpace(sourceInfo.system_class) && isNullsecSpace(targetInfo.system_class);
const isFrigateSize = edge.data?.ship_size_type === ShipSizeStatus.small;
const isWormhole = edge.data?.type !== ConnectionType.gate;
if (edge.data?.type === ConnectionType.bridge) {
return [
{
label: `Set as Wormhole`,
icon: 'pi hero-arrow-uturn-left',
command: () => onChangeType(ConnectionType.wormhole),
},
{
label: 'Disconnect',
icon: PrimeIcons.TRASH,
command: onDeleteConnection,
},
];
}
if (edge.data?.type === ConnectionType.gate) {
return [
{
label: 'Disconnect',
icon: PrimeIcons.TRASH,
command: onDeleteConnection,
},
];
}
return [
...(isWormhole
{
className: clsx(classes.FastActions, '!h-[54px]'),
template: () => {
return <LifetimeActionsWrapper lifetime={edge.data?.time_status} onChangeLifetime={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: `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
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?.ship_size_type === x,
[classes.SelectedItem]: edge.data?.mass_status === x,
}),
command: () => onChangeShipSizeStatus(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?.ship_size_type === x,
}),
command: () => onChangeShipSizeStatus(x),
})),
},
...(bothNullsec
? [
{
label: `Set as Bridge`,
icon: 'pi hero-forward',
command: () => onChangeType(ConnectionType.bridge),
},
]
: []),
{
label: 'Disconnect',
icon: PrimeIcons.TRASH,
command: onDeleteConnection,
},
];
}, [edge, onChangeTimeState, onDeleteConnection, onChangeShipSizeStatus, onToggleMassSave, onChangeMassState]);
}, [
edge,
onChangeTimeState,
onDeleteConnection,
onChangeType,
onChangeShipSizeStatus,
onToggleMassSave,
onChangeMassState,
]);
return (
<>
<ContextMenu model={items} ref={contextMenuRef} onHide={onHide} breakpoint="767px" />
<ContextMenu model={items} ref={contextMenuRef} onHide={onHide} breakpoint="767px" className="!w-[250px]" />
</>
);
};
@@ -0,0 +1,12 @@
import { LayoutEventBlocker } from '@/hooks/Mapper/components/ui-kit';
import { WdLifetimeSelector, WdLifetimeSelectorProps } from '@/hooks/Mapper/components/ui-kit/WdLifetimeSelector.tsx';
export const LifetimeActionsWrapper = (props: WdLifetimeSelectorProps) => {
return (
<LayoutEventBlocker className="flex flex-col gap-1 w-[100%] h-full px-2 pt-[4px]">
<div className="text-[12px] text-stone-500 font-semibold">Life time:</div>
<WdLifetimeSelector {...props} />
</LayoutEventBlocker>
);
};
@@ -30,7 +30,7 @@ export const useContextMenuConnectionHandlers = () => {
setEdge(undefined);
};
const onChangeTimeState = () => {
const onChangeTimeState = (lifetime: TimeStatus) => {
if (!edge || !edge.data) {
return;
}
@@ -40,7 +40,7 @@ export const useContextMenuConnectionHandlers = () => {
data: {
source: edge.source,
target: edge.target,
value: edge.data.time_status === TimeStatus.default ? TimeStatus.eol : TimeStatus.default,
value: lifetime,
},
});
setEdge(undefined);
@@ -56,7 +56,8 @@ export const KillsCounter = ({
className={className}
tooltipClassName="!px-0"
size={size}
interactive={true}
interactive
smallPaddings
>
{children}
</WdTooltipWrapper>
@@ -46,7 +46,13 @@ export const LocalCounter = ({ localCounterCharacters, hasUserCharacters, showIc
[classes.Pathfinder]: theme === AvailableThemes.pathfinder,
})}
>
<WdTooltipWrapper content={pilotTooltipContent} position={TooltipPosition.right} offset={0} interactive={true}>
<WdTooltipWrapper
content={pilotTooltipContent}
position={TooltipPosition.right}
offset={0}
interactive={true}
smallPaddings
>
<div className={clsx(classes.hoverTarget)}>
<div
className={clsx(classes.localCounter, {
@@ -5,6 +5,16 @@
stroke: #80a5c5;
stroke-width: 3px;
&.time1 {
stroke: #f11ab2;
stroke-width: 4px;
}
&.time4 {
stroke: #a654e3;
stroke-width: 4px;
}
&.TimeCrit {
stroke: #f11ab2;
stroke-width: 4px;
@@ -29,6 +39,13 @@
&.Gate {
stroke: #9aff40;
}
&.Bridge {
stroke: #9aff40;
stroke-dasharray: 10 5;
stroke-linecap: round;
}
}
.EdgePathFront {
@@ -9,6 +9,7 @@ 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';
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
const MAP_TRANSLATES: Record<string, string> = {
[Position.Top]: 'translate(-48%, 0%)',
@@ -42,7 +43,9 @@ export const SHIP_SIZES_COLORS = {
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 isWormhole = data?.type === ConnectionType.wormhole;
const isGate = data?.type === ConnectionType.gate;
const isBridge = data?.type === ConnectionType.bridge;
const {
data: { isThickConnections },
@@ -55,9 +58,7 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
const offset = isThickConnections ? MAP_OFFSETS_TICK[targetPos] : MAP_OFFSETS[targetPos];
const method = isWormhole ? getBezierPath : getBezierPath;
const [edgePath, labelX, labelY] = method({
const [edgePath, labelX, labelY] = getBezierPath({
sourceX: sx - offset.x,
sourceY: sy - offset.y,
sourcePosition: sourcePos,
@@ -67,7 +68,7 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
});
return [edgePath, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos];
}, [isThickConnections, sourceNode, targetNode, isWormhole]);
}, [isThickConnections, sourceNode, targetNode]);
if (!sourceNode || !targetNode || !data) {
return null;
@@ -79,9 +80,11 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
id={`back_${id}`}
className={clsx(classes.EdgePathBack, {
[classes.Tick]: isThickConnections,
[classes.TimeCrit]: isWormhole && data.time_status === TimeStatus.eol,
[classes.time1]: isWormhole && data.time_status === TimeStatus._1h,
[classes.time4]: isWormhole && data.time_status === TimeStatus._4h,
[classes.Hovered]: hovered,
[classes.Gate]: !isWormhole,
[classes.Gate]: isGate,
[classes.Bridge]: isBridge,
})}
d={path}
markerEnd={markerEnd}
@@ -95,7 +98,8 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
[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,
[classes.Gate]: isGate,
[classes.Bridge]: isBridge,
})}
d={path}
markerEnd={markerEnd}
@@ -147,6 +151,19 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
</WdTooltipWrapper>
)}
{isBridge && (
<WdTooltipWrapper
content="Ansiblex Jump Bridge"
position={TooltipPosition.top}
className={clsx(
classes.LinkLabel,
'pointer-events-auto bg-lime-300 rounded opacity-100 cursor-auto text-neutral-900',
)}
>
B
</WdTooltipWrapper>
)}
{isWormhole && data.ship_size_type !== ShipSizeStatus.large && (
<WdTooltipWrapper
content={SHIP_SIZES_DESCRIPTION[data.ship_size_type]}
@@ -58,6 +58,7 @@ export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) =>
</InfoDrawer>
</div>
}
smallPaddings
>
<div className={clsx(classes.Box, whClassStyle)}>
<svg width="13" height="8" viewBox="0 0 13 8" xmlns="http://www.w3.org/2000/svg">
@@ -716,11 +716,12 @@ 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_ORDER = [ConnectionType.wormhole, ConnectionType.gate, ConnectionType.bridge];
export const TYPE_NAMES = {
[ConnectionType.wormhole]: 'Wormhole',
[ConnectionType.gate]: 'Gate',
[ConnectionType.bridge]: 'Jumpgate',
};
export const MASS_STATE_NAMES_ORDER = [MassState.verge, MassState.half, MassState.normal];
@@ -15,3 +15,12 @@ export const isKnownSpace = (wormholeClassID: number) => {
export const isPossibleSpace = (spaces: number[], wormholeClassID: number) => {
return spaces.includes(wormholeClassID);
};
export const isNullsecSpace = (wormholeClassID: number) => {
switch (wormholeClassID) {
case SOLAR_SYSTEM_CLASS_IDS.ns:
return true;
}
return false;
};
@@ -49,87 +49,91 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
const { charactersUpdated, presentCharacters, characterAdded, characterRemoved, characterUpdated } =
useCommandsCharacters();
useImperativeHandle(ref, () => {
return {
command(type, data) {
switch (type) {
case Commands.init:
mapInit(data as CommandInit);
break;
case Commands.addSystems:
setTimeout(() => mapAddSystems(data as CommandAddSystems), 100);
break;
case Commands.updateSystems:
mapUpdateSystems(data as CommandUpdateSystems);
break;
case Commands.removeSystems:
setTimeout(() => removeSystems(data as CommandRemoveSystems), 100);
break;
case Commands.addConnections:
setTimeout(() => addConnections(data as CommandAddConnections), 100);
break;
case Commands.removeConnections:
setTimeout(() => removeConnections(data as CommandRemoveConnections), 100);
break;
case Commands.charactersUpdated:
charactersUpdated(data as CommandCharactersUpdated);
break;
case Commands.characterAdded:
characterAdded(data as CommandCharacterAdded);
break;
case Commands.characterRemoved:
characterRemoved(data as CommandCharacterRemoved);
break;
case Commands.characterUpdated:
characterUpdated(data as CommandCharacterUpdated);
break;
case Commands.presentCharacters:
presentCharacters(data as CommandPresentCharacters);
break;
case Commands.updateConnection:
updateConnection(data as CommandUpdateConnection);
break;
case Commands.mapUpdated:
mapUpdated(data as CommandMapUpdated);
break;
case Commands.killsUpdated:
killsUpdated(data as CommandKillsUpdated);
break;
useImperativeHandle(
ref,
() => {
return {
command(type, data) {
switch (type) {
case Commands.init:
mapInit(data as CommandInit);
break;
case Commands.addSystems:
setTimeout(() => mapAddSystems(data as CommandAddSystems), 100);
break;
case Commands.updateSystems:
mapUpdateSystems(data as CommandUpdateSystems);
break;
case Commands.removeSystems:
setTimeout(() => removeSystems(data as CommandRemoveSystems), 100);
break;
case Commands.addConnections:
setTimeout(() => addConnections(data as CommandAddConnections), 100);
break;
case Commands.removeConnections:
setTimeout(() => removeConnections(data as CommandRemoveConnections), 100);
break;
case Commands.charactersUpdated:
charactersUpdated(data as CommandCharactersUpdated);
break;
case Commands.characterAdded:
characterAdded(data as CommandCharacterAdded);
break;
case Commands.characterRemoved:
characterRemoved(data as CommandCharacterRemoved);
break;
case Commands.characterUpdated:
characterUpdated(data as CommandCharacterUpdated);
break;
case Commands.presentCharacters:
presentCharacters(data as CommandPresentCharacters);
break;
case Commands.updateConnection:
updateConnection(data as CommandUpdateConnection);
break;
case Commands.mapUpdated:
mapUpdated(data as CommandMapUpdated);
break;
case Commands.killsUpdated:
killsUpdated(data as CommandKillsUpdated);
break;
case Commands.centerSystem:
setTimeout(() => {
const systemId = `${data}`;
centerSystem(systemId as CommandSelectSystem);
}, 100);
break;
case Commands.centerSystem:
setTimeout(() => {
const systemId = `${data}`;
centerSystem(systemId as CommandSelectSystem);
}, 100);
break;
case Commands.selectSystem:
selectSystems({ systems: [data as string], delay: 500 });
break;
case Commands.selectSystem:
selectSystems({ systems: [data as string], delay: 500 });
break;
case Commands.selectSystems:
selectSystems(data as CommandSelectSystems);
break;
case Commands.selectSystems:
selectSystems(data as CommandSelectSystems);
break;
case Commands.pingAdded:
case Commands.pingCancelled:
case Commands.routes:
case Commands.signaturesUpdated:
case Commands.linkSignatureToSystem:
case Commands.detailedKillsUpdated:
case Commands.characterActivityData:
case Commands.trackingCharactersData:
case Commands.updateActivity:
case Commands.updateTracking:
case Commands.userSettingsUpdated:
// do nothing
break;
case Commands.pingAdded:
case Commands.pingCancelled:
case Commands.routes:
case Commands.signaturesUpdated:
case Commands.linkSignatureToSystem:
case Commands.detailedKillsUpdated:
case Commands.characterActivityData:
case Commands.trackingCharactersData:
case Commands.updateActivity:
case Commands.updateTracking:
case Commands.userSettingsUpdated:
// do nothing
break;
default:
console.warn(`Map handlers: Unknown command: ${type}`, data);
break;
}
},
};
}, []);
default:
console.warn(`Map handlers: Unknown command: ${type}`, data);
break;
}
},
};
},
[],
);
};
@@ -118,6 +118,7 @@ $homeDark30: color.adjust($homeBase, $lightness: -30%);
--conn-time-eol: #7452c3e3;
--conn-frigate: #325d88;
--conn-bridge: rgba(135, 185, 93, 0.85);
--conn-save: rgba(155, 102, 45, 0.85);
--selected-item-bg: rgba(98, 98, 98, 0.33);
}
@@ -1,16 +1,15 @@
import { Dialog } from 'primereact/dialog';
import { SystemViewStandalone, WdButton, WHClassView, WHEffectView } from '@/hooks/Mapper/components/ui-kit';
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 { AutoComplete } from 'primereact/autocomplete';
import { Dialog } from 'primereact/dialog';
import { IconField } from 'primereact/iconfield';
import { useCallback, useRef, useState } from 'react';
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';
import clsx from 'clsx';
export type SearchOnSubmitCallback = (item: SearchSystemItem) => void;
@@ -34,6 +33,7 @@ export const AddSystemDialog = ({
data: { wormholesData },
} = useMapRootState();
// TODO fix it
const inputRef = useRef<any>();
const onShow = useCallback(() => {
inputRef.current?.focus();
@@ -62,6 +62,7 @@ export const AddSystemDialog = ({
},
});
// TODO fix it
let prepared = (result.systems as SearchSystemItem[]).sort((a, b) => {
const amatch = a.label.indexOf(query);
const bmatch = b.label.indexOf(query);
@@ -114,90 +115,93 @@ export const AddSystemDialog = ({
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);
<form onSubmit={handleSubmit}>
<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}
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}
/>
)}
{isWH && (
<div className="flex gap-1 grow justify-between">
<div></div>
<div className="flex gap-1">
{sortedStatics.map(x => (
<WHClassView key={x} whClassName={x} />
))}
{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>
)}
</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>
)}
</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>
<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">
<WdButton
type="submit"
onClick={handleSubmit}
outlined
disabled={!selectedItem || selectedItem.length !== 1}
size="small"
label="Submit"
/>
</div>
</div>
<div className="flex gap-2 justify-end">
<Button
onClick={handleSubmit}
outlined
disabled={!selectedItem || selectedItem.length !== 1}
size="small"
label="Submit"
/>
</div>
</div>
</form>
</Dialog>
);
};
@@ -4,6 +4,7 @@ import {
SystemView,
TimeAgo,
TooltipPosition,
WdButton,
WdImgButton,
WdImgButtonTooltip,
} from '@/hooks/Mapper/components/ui-kit';
@@ -13,7 +14,6 @@ import { PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { Commands, OutCommand, PingType } from '@/hooks/Mapper/types';
import clsx from 'clsx';
import { PrimeIcons } from 'primereact/api';
import { Button } from 'primereact/button';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { Toast } from 'primereact/toast';
import { useCallback, useEffect, useMemo, useRef } from 'react';
@@ -256,7 +256,7 @@ export const PingsInterface = ({ hasLeftOffset }: PingsInterfaceProps) => {
)}
></Toast>
<Button
<WdButton
icon="pi pi-bell"
severity="warning"
aria-label="Notification"
@@ -1,13 +1,12 @@
import { InputText } from 'primereact/inputtext';
import { Dialog } from 'primereact/dialog';
import { TooltipPosition, WdButton, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { getSystemById } from '@/hooks/Mapper/helpers';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
import { IconField } from 'primereact/iconfield';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
import { WdImageSize, WdImgButton, TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
import { Dialog } from 'primereact/dialog';
import { IconField } from 'primereact/iconfield';
import { InputText } from 'primereact/inputtext';
import { useCallback, useEffect, useRef, useState } from 'react';
interface SystemCustomLabelDialog {
systemId: string;
@@ -126,7 +125,7 @@ export const SystemCustomLabelDialog = ({ systemId, visible, setVisible }: Syste
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
<WdButton type="submit" onClick={handleSave} outlined size="small" label="Save"></WdButton>
</div>
</div>
</form>
@@ -9,10 +9,9 @@ import {
} from '@/hooks/Mapper/components/map/constants.ts';
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
import { getWhSize } from '@/hooks/Mapper/helpers/getWhSize';
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { SETTINGS_KEYS, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
@@ -116,14 +115,14 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
);
const handleSelect = useCallback(
async (signature: SystemSignature) => {
(signature: SystemSignature) => {
if (!signature) {
return;
}
const { outCommand } = ref.current;
await outCommand({
outCommand({
type: OutCommand.linkSignatureToSystem,
data: {
...data,
@@ -131,32 +130,9 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
},
});
if (parseSignatureCustomInfo(signature.custom_info).isEOL === true) {
await outCommand({
type: OutCommand.updateConnectionTimeStatus,
data: {
source: data.solar_system_source,
target: data.solar_system_target,
value: TimeStatus.eol,
},
});
}
const whShipSize = getWhSize(wormholes, signature.type);
if (whShipSize !== undefined && whShipSize !== null) {
await outCommand({
type: OutCommand.updateConnectionShipSizeType,
data: {
source: data.solar_system_source,
target: data.solar_system_target,
value: whShipSize,
},
});
}
setVisible(false);
},
[data, setVisible, wormholes],
[data, setVisible],
);
useEffect(() => {
@@ -1,12 +1,11 @@
import { InputTextarea } from 'primereact/inputtextarea';
import { Dialog } from 'primereact/dialog';
import { SystemView, WdButton } from '@/hooks/Mapper/components/ui-kit';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useRef, useState } from 'react';
import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
import { PingType } from '@/hooks/Mapper/types/ping.ts';
import { SystemView } from '@/hooks/Mapper/components/ui-kit';
import clsx from 'clsx';
import { Dialog } from 'primereact/dialog';
import { InputTextarea } from 'primereact/inputtextarea';
import { useCallback, useRef, useState } from 'react';
const PING_TITLES = {
[PingType.Rally]: 'RALLY',
@@ -63,7 +62,7 @@ export const SystemPingDialog = ({ systemId, type, visible, setVisible }: System
</div>
}
visible={visible}
draggable={false}
draggable={true}
style={{ width: '450px' }}
onShow={onShow}
onHide={() => {
@@ -92,7 +91,7 @@ export const SystemPingDialog = ({ systemId, type, visible, setVisible }: System
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} size="small" severity="danger" label="Ping!"></Button>
<WdButton type="submit" onClick={handleSave} size="small" severity="danger" label="Ping!" />
</div>
</div>
</form>
@@ -1,16 +1,15 @@
import { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import { Dialog } from 'primereact/dialog';
import { TooltipPosition, WdButton, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
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';
import { IconField } from 'primereact/iconfield';
import { TooltipPosition, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
import { OutCommand } from '@/hooks/Mapper/types';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
import { Dialog } from 'primereact/dialog';
import { IconField } from 'primereact/iconfield';
import { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import { useCallback, useEffect, useRef, useState } from 'react';
interface SystemSettingsDialog {
systemId: string;
@@ -114,7 +113,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
<Dialog
header="System settings"
visible={visible}
draggable={false}
draggable={true}
style={{ width: '450px' }}
onShow={onShow}
onHide={() => {
@@ -226,7 +225,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
<WdButton onClick={handleSave} outlined size="small" label="Save" type="submit" />
</div>
</div>
</form>
@@ -1,11 +1,9 @@
import { useRouteProvider } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { Dialog } from 'primereact/dialog';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button } from 'primereact/button';
import {
RoutesType,
useRouteProvider,
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
interface RoutesSettingsDialog {
visible: boolean;
@@ -83,7 +81,7 @@ export const RoutesSettingsDialog = ({ visible, setVisible }: RoutesSettingsDial
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Apply"></Button>
<WdButton onClick={handleSave} outlined size="small" label="Apply"></WdButton>
</div>
</div>
</Dialog>
@@ -3,6 +3,7 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { LoadRoutesCommand } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
import { flattenValues } from '@/hooks/Mapper/utils/flattenValues.ts';
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
@@ -64,12 +65,8 @@ export const useLoadRoutes = ({
systems?.length,
connections,
hubs,
routesSettings,
...Object.keys(routesSettings)
.sort()
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
.map(x => routesSettings[x]),
// we need make it flat recursively
...flattenValues(routesSettings),
...deps,
]);
@@ -1,6 +1,5 @@
import { Dialog } from 'primereact/dialog';
import { useCallback, useState } from 'react';
import { Button } from 'primereact/button';
import { TabPanel, TabView } from 'primereact/tabview';
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
import { Dropdown } from 'primereact/dropdown';
@@ -10,6 +9,7 @@ import {
SIGNATURE_SETTINGS,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
import { SignatureSettingsType } from '@/hooks/Mapper/constants/signatures.ts';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
interface SystemSignatureSettingsDialogProps {
settings: SignatureSettingsType;
@@ -92,7 +92,7 @@ export const SystemSignatureSettingsDialog = ({
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
<WdButton onClick={handleSave} outlined size="small" label="Save" />
</div>
</div>
</Dialog>
@@ -1,13 +1,13 @@
import React, { useEffect, useState, useCallback } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { Dialog } from 'primereact/dialog';
import { Button } from 'primereact/button';
import { AutoComplete } from 'primereact/autocomplete';
import { Calendar } from 'primereact/calendar';
import clsx from 'clsx';
import { StructureItem, StructureStatus, statusesRequiringTimer, formatToISO } from '../helpers';
import { formatToISO, statusesRequiringTimer, StructureItem, StructureStatus } from '../helpers';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OutCommand } from '@/hooks/Mapper/types';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
interface StructuresEditDialogProps {
visible: boolean;
@@ -54,14 +54,13 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
// If user typed more text but we have partial match in prevResults
if (newQuery.startsWith(prevQuery) && prevResults.length > 0) {
const filtered = prevResults.filter(item =>
item.label.toLowerCase().includes(newQuery.toLowerCase()),
);
const filtered = prevResults.filter(item => item.label.toLowerCase().includes(newQuery.toLowerCase()));
setOwnerSuggestions(filtered);
return;
}
try {
// TODO fix it
const { results = [] } = await outCommand({
type: OutCommand.getCorporationNames,
data: { search: newQuery },
@@ -96,9 +95,7 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
// when user picks a corp from auto-complete
const handleSelectOwner = (selected: { label: string; value: string }) => {
setOwnerInput(selected.label);
setEditData(prev =>
prev ? { ...prev, ownerName: selected.label, ownerId: selected.value } : null,
);
setEditData(prev => (prev ? { ...prev, ownerName: selected.label, ownerId: selected.value } : null));
};
const handleStatusChange = (val: string) => {
@@ -125,6 +122,7 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
// fetch corporation ticker if we have an ownerId
if (editData.ownerId) {
try {
// TODO fix it
const { ticker } = await outCommand({
type: OutCommand.getCorporationTicker,
data: { corp_id: editData.ownerId },
@@ -157,11 +155,7 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
<div className="flex flex-col gap-2 text-[14px]">
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center">
<span>Type:</span>
<input
readOnly
className="p-inputtext p-component cursor-not-allowed"
value={editData.structureType ?? ''}
/>
<input readOnly className="p-inputtext p-component cursor-not-allowed" value={editData.structureType ?? ''} />
</label>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center">
<span>Name:</span>
@@ -204,10 +198,12 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
{statusesRequiringTimer.includes(editData.status) && (
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center">
<span>Timer <br /> (Eve Time):</span>
<span>
Timer <br /> (Eve Time):
</span>
<Calendar
value={editData.endTime ? new Date(editData.endTime) : undefined}
onChange={(e) => handleChange('endTime', e.value ?? '')}
onChange={e => handleChange('endTime', e.value ?? '')}
showTime
hourFormat="24"
dateFormat="yy-mm-dd"
@@ -227,8 +223,8 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
</div>
<div className="flex justify-end items-center gap-2 mt-4">
<Button label="Delete" severity="danger" className="p-button-sm" onClick={handleDeleteClick} />
<Button label="Save" className="p-button-sm" onClick={handleSaveClick} />
<WdButton label="Delete" severity="danger" className="p-button-sm" onClick={handleDeleteClick} />
<WdButton label="Save" className="p-button-sm" onClick={handleSaveClick} />
</div>
</Dialog>
);
@@ -16,8 +16,21 @@ const SystemKillsContent = () => {
} = useMapRootState();
const [systemId] = selectedSystems || [];
const whCacheRef = useMemo(() => new Map<number, boolean>(), []);
const systemStaticInfo = getSystemStaticInfo(systemId)!;
const isWormholeSystem = useCallback(
(systemId: number): boolean => {
const cached = whCacheRef.get(systemId);
if (cached !== undefined) return cached;
const info = getSystemStaticInfo(systemId);
const isWH = info?.system_class != null ? isWormholeSpace(Number(info.system_class)) : false;
whCacheRef.set(systemId, isWH);
return isWH;
},
[whCacheRef],
);
const { kills, isLoading, error } = useSystemKills({
systemId,
@@ -30,15 +43,9 @@ const SystemKillsContent = () => {
const showLoading = isLoading && kills.length === 0;
const filteredKills = useMemo(() => {
if (!settingsKills.whOnly || !settingsKills.showAll) return kills;
return kills.filter(kill => {
if (!systemStaticInfo) {
console.warn(`System with id ${kill.solar_system_id} not found.`);
return false;
}
return isWormholeSpace(systemStaticInfo.system_class);
});
}, [kills, settingsKills.whOnly, systemStaticInfo, settingsKills.showAll]);
if (!settingsKills.whOnly) return kills;
return kills.filter(kill => isWormholeSystem(Number(kill.solar_system_id)));
}, [kills, settingsKills.whOnly, isWormholeSystem]);
if (!isSubscriptionActive) {
return (
@@ -1,13 +1,11 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Dialog } from 'primereact/dialog';
import { Button } from 'primereact/button';
import { WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { SystemView, TooltipPosition, WdButton, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { PrimeIcons } from 'primereact/api';
import {
AddSystemDialog,
SearchOnSubmitCallback,
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
import { SystemView, TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
interface KillsSettingsDialogProps {
@@ -158,7 +156,7 @@ export const KillsSettingsDialog: React.FC<KillsSettingsDialogProps> = ({ visibl
</div>
<div className="flex gap-2 justify-end mt-4">
<Button onClick={handleApply} label="Apply" outlined size="small" />
<WdButton onClick={handleApply} label="Apply" outlined size="small" />
</div>
</div>
@@ -1,22 +1,21 @@
import classes from './Connections.module.scss';
import { Sidebar } from 'primereact/sidebar';
import { useEffect, useMemo, useState, useCallback } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import clsx from 'clsx';
import {
ConnectionType,
ConnectionOutput,
ConnectionInfoOutput,
ConnectionOutput,
ConnectionType,
OutCommand,
Passage,
SolarSystemConnection,
} from '@/hooks/Mapper/types';
import clsx from 'clsx';
import { Sidebar } from 'primereact/sidebar';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import { useCallback, useEffect, useMemo, useState } from 'react';
import classes from './Connections.module.scss';
import { PassageCard } from './PassageCard';
import { InfoDrawer, SystemView } from '@/hooks/Mapper/components/ui-kit';
import { InfoDrawer, SystemView, TimeAgo } from '@/hooks/Mapper/components/ui-kit';
import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts';
import { TimeAgo } from '@/hooks/Mapper/components/ui-kit';
import { PassageCard } from './PassageCard';
const sortByDate = (a: string, b: string) => new Date(a).getTime() - new Date(b).getTime();
@@ -78,7 +77,7 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
}, [connections, selectedConnection]);
const isWormhole = useMemo(() => {
return cnInfo?.type !== ConnectionType.gate;
return cnInfo?.type === ConnectionType.wormhole;
}, [cnInfo]);
const [passages, setPassages] = useState<Passage[]>([]);
@@ -1,7 +1,6 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Toast } from 'primereact/toast';
import { Button } from 'primereact/button';
import { callToastError, callToastSuccess, callToastWarn } from '@/hooks/Mapper/helpers';
import { OutCommand } from '@/hooks/Mapper/types';
import { ConfirmPopup } from 'primereact/confirmpopup';
@@ -10,6 +9,7 @@ import { MapUserSettings, RemoteAdminSettingsResponse } from '@/hooks/Mapper/map
import { parseMapUserSettings } from '@/hooks/Mapper/components/helpers';
import fastDeepEqual from 'fast-deep-equal';
import { useDetectSettingsChanged } from '@/hooks/Mapper/components/hooks';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
export const AdminSettings = () => {
const {
@@ -92,7 +92,7 @@ export const AdminSettings = () => {
<div className="w-full h-full flex flex-col gap-5">
<div className="flex flex-col gap-1">
<div>
<Button
<WdButton
// @ts-ignore
ref={cfRef}
onClick={cfShow}
@@ -7,8 +7,7 @@ import {
import { useMapSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx';
import { SettingsListItem } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts';
import { useCallback } from 'react';
import { Button } from 'primereact/button';
import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
import { TooltipPosition, WdButton, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
@@ -42,7 +41,7 @@ export const CommonSettings = () => {
<div className="grid grid-cols-[1fr_auto]">
<div />
<WdTooltipWrapper content="This dangerous action. And can not be undone" position={TooltipPosition.top}>
<Button
<WdButton
// @ts-ignore
ref={cfRef}
className="py-[4px]"
@@ -1,10 +1,10 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useRef, useState } from 'react';
import { Toast } from 'primereact/toast';
import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
import { Divider } from 'primereact/divider';
import { callToastError, callToastSuccess, callToastWarn } from '@/hooks/Mapper/helpers';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
type SaveDefaultSettingsReturn = { success: boolean; error: string };
@@ -65,7 +65,7 @@ export const DefaultSettings = () => {
<div className="flex flex-col gap-1">
<div>
<Button
<WdButton
onClick={handleSaveAsDefault}
icon="pi pi-save"
size="small"
@@ -2,13 +2,13 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Toast } from 'primereact/toast';
import { parseMapUserSettings } from '@/hooks/Mapper/components/helpers';
import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
import { createDefaultWidgetSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultWidgetSettings.ts';
import { callToastSuccess } from '@/hooks/Mapper/helpers';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
import { RemoteAdminSettingsResponse } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
export const ServerSettings = () => {
const {
@@ -64,7 +64,7 @@ export const ServerSettings = () => {
<div className="w-full h-full flex flex-col gap-5">
<div className="flex flex-col gap-1">
<div>
<Button
<WdButton
// @ts-ignore
ref={cfRef}
onClick={cfShow}
@@ -2,8 +2,7 @@ import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/compon
import { WIDGETS_CHECKBOXES_PROPS, WidgetsIds } from '@/hooks/Mapper/components/mapInterface/constants.tsx';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback } from 'react';
import { Button } from 'primereact/button';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
export interface WidgetsSettingsProps {}
@@ -33,7 +32,7 @@ export const WidgetsSettings = ({}: WidgetsSettingsProps) => {
<div className="grid grid-cols-[1fr_auto]">
<div />
<Button className="py-[4px]" onClick={resetWidgets} outlined size="small" label="Reset Widgets"></Button>
<WdButton className="py-[4px]" onClick={resetWidgets} outlined size="small" label="Reset Widgets" />
</div>
</div>
);
@@ -1,6 +1,6 @@
import { SettingsListItem, UserSettingsRemoteProps } from './types.ts';
import { InterfaceStoredSettingsProps } from '@/hooks/Mapper/mapRootProvider';
import { AvailableThemes, MiniMapPlacement, PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { SettingsListItem, UserSettingsRemoteProps } from './types.ts';
export const DEFAULT_REMOTE_SETTINGS = {
[UserSettingsRemoteProps.link_signature_on_splash]: false,
@@ -51,7 +51,7 @@ export const SIGNATURES_CHECKBOXES_PROPS: SettingsListItem[] = [
export const CONNECTIONS_CHECKBOXES_PROPS: SettingsListItem[] = [
{
prop: UserSettingsRemoteProps.delete_connection_with_sigs,
label: 'Delete connections to linked signatures',
label: 'Delete connections with linked signatures',
type: 'checkbox',
},
{
@@ -11,11 +11,11 @@ import {
} from '@/hooks/Mapper/mapRootProvider/constants.ts';
import { MapUserSettings } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { saveTextFile } from '@/hooks/Mapper/utils';
import { Button } from 'primereact/button';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { Dialog } from 'primereact/dialog';
import { Toast } from 'primereact/toast';
import { useCallback, useRef } from 'react';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
const createSettings = function <T>(lsSettings: string | null, defaultValues: T) {
return {
@@ -139,7 +139,7 @@ export const OldSettingsDialog = () => {
className="w-[640px] h-[400px] text-text-color min-h-0"
footer={
<div className="flex items-center justify-end">
<Button
<WdButton
// @ts-ignore
ref={cfRef}
onClick={cfShow}
@@ -168,7 +168,7 @@ export const OldSettingsDialog = () => {
<div className="h-[30px]"></div>
<div className="flex items-center gap-3">
<Button
<WdButton
onClick={handleExportClipboard}
icon="pi pi-copy"
size="small"
@@ -176,7 +176,7 @@ export const OldSettingsDialog = () => {
label="Export to Clipboard"
/>
<Button
<WdButton
onClick={handleExportAsFile}
icon="pi pi-download"
size="small"
@@ -8,11 +8,14 @@ import {
} from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components';
import { InputText } from 'primereact/inputtext';
import { SystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
import { Button } from 'primereact/button';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { getWhSize } from '@/hooks/Mapper/helpers/getWhSize';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
type SystemSignaturePrepared = Omit<SystemSignature, 'linked_system'> & { linked_system: string };
type SystemSignaturePrepared = Omit<SystemSignature, 'linked_system'> & {
linked_system: string;
k162Type: string;
time_status: TimeStatus;
};
export interface MapSettingsProps {
systemId: string;
@@ -22,10 +25,7 @@ export interface MapSettingsProps {
}
export const SignatureSettings = ({ systemId, show, onHide, signatureData }: MapSettingsProps) => {
const {
outCommand,
data: { wormholes },
} = useMapRootState();
const { outCommand } = useMapRootState();
const handleShow = async () => {};
const signatureForm = useForm<Partial<SystemSignaturePrepared>>({});
@@ -52,41 +52,13 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
solar_system_target: values.linked_system,
},
});
// TODO: need fix
if (values.isEOL) {
await outCommand({
type: OutCommand.updateConnectionTimeStatus,
data: {
source: systemId,
target: values.linked_system,
value: TimeStatus.eol,
},
});
}
if (values.type) {
const whShipSize = getWhSize(wormholes, values.type);
if (whShipSize !== undefined && whShipSize !== null) {
await outCommand({
type: OutCommand.updateConnectionShipSizeType,
data: {
source: systemId,
target: values.linked_system,
value: whShipSize,
},
});
}
}
}
out = {
...out,
custom_info: JSON.stringify({
// TODO: need fix
k162Type: values.k162Type,
// TODO: need fix
isEOL: values.isEOL,
time_status: values.time_status,
}),
};
@@ -153,7 +125,7 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
signatureForm.reset();
onHide();
},
[signatureData, signatureForm, outCommand, systemId, onHide, wormholes],
[signatureData, signatureForm, outCommand, systemId, onHide],
);
useEffect(() => {
@@ -165,18 +137,17 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
const { linked_system, custom_info, ...rest } = signatureData;
let k162Type = null;
let isEOL = false;
let time_status = TimeStatus._24h;
if (custom_info) {
const customInfo = JSON.parse(custom_info);
k162Type = customInfo.k162Type;
isEOL = customInfo.isEOL;
time_status = customInfo.time_status;
}
signatureForm.reset({
linked_system: linked_system?.solar_system_id.toString() ?? undefined,
// TODO: need fix
k162Type: k162Type,
isEOL: isEOL,
time_status: time_status,
...rest,
});
}, [signatureForm, signatureData]);
@@ -185,7 +156,8 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
<Dialog
header={`Signature Edit [${signatureData?.eve_id}]`}
visible={show}
draggable={false}
draggable
resizable={false}
style={{ width: '390px' }}
onShow={handleShow}
onHide={() => {
@@ -220,8 +192,8 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
</label>
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
<div className="flex gap-2 justify-end px-[0.75rem] pb-[0.5rem]">
<WdButton type="submit" outlined size="small" label="Save" />
</div>
</div>
</form>
@@ -1,24 +0,0 @@
import { InputSwitch } from 'primereact/inputswitch';
import { Controller, useFormContext } from 'react-hook-form';
import { SystemSignature } from '@/hooks/Mapper/types';
export interface SignatureEOLCheckboxProps {
name: string;
defaultValue?: boolean;
}
export const SignatureEOLCheckbox = ({ name, defaultValue = false }: SignatureEOLCheckboxProps) => {
const { control } = useFormContext<SystemSignature>();
return (
<Controller
// @ts-ignore
name={name}
control={control}
defaultValue={defaultValue}
render={({ field }) => {
return <InputSwitch className="my-1" checked={!!field.value} onChange={e => field.onChange(e.value)} />;
}}
/>
);
};
@@ -1 +0,0 @@
export * from './SignatureEOLCheckbox.tsx';
@@ -3,7 +3,7 @@ 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';
import { SignatureEOLCheckbox } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureEOLCheckbox';
import { SignatureLifetimeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLifetimeSelect.tsx';
import { SignatureTempName } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureTempName.tsx';
export const SignatureGroupContentWormholes = () => {
@@ -29,10 +29,10 @@ export const SignatureGroupContentWormholes = () => {
<SignatureLeadsToSelect name="linked_system" />
</label>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>EOL:</span>
<SignatureEOLCheckbox name="isEOL" />
</label>
<div className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Lifetime:</span>
<SignatureLifetimeSelect name="time_status" />
</div>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Temp. Name:</span>
@@ -20,7 +20,7 @@ const renderLinkedSystemItem = (option: { value: string }) => {
return (
<div className="flex gap-2 items-center">
<SystemView systemId={value} className={classes.SystemView} />
<SystemView systemId={value} className={classes.SystemView} showCustomName={true} />
</div>
);
};
@@ -37,7 +37,7 @@ const renderLinkedSystemValue = (option: { value: string }) => {
return (
<div className="flex gap-2 items-center">
<SystemView systemId={option.value} className={classes.SystemView} />
<SystemView systemId={option.value} className={classes.SystemView} showCustomName={true} />
</div>
);
};
@@ -0,0 +1,27 @@
import { Controller, useFormContext } from 'react-hook-form';
import { SystemSignature } from '@/hooks/Mapper/types';
import { WdLifetimeSelector } from '@/hooks/Mapper/components/ui-kit/WdLifetimeSelector.tsx';
export interface SignatureEOLCheckboxProps {
name: string;
defaultValue?: boolean;
}
export const SignatureLifetimeSelect = ({ name, defaultValue = false }: SignatureEOLCheckboxProps) => {
const { control } = useFormContext<SystemSignature>();
return (
<div className="my-1">
<Controller
// @ts-ignore
name={name}
control={control}
defaultValue={defaultValue}
render={({ field }) => {
// @ts-ignore
return <WdLifetimeSelector lifetime={field.value} onChangeLifetime={e => field.onChange(e)} />;
}}
/>
</div>
);
};
@@ -1,3 +1,4 @@
export * from './SignatureGroupSelect';
export * from './SignatureGroupContent';
export * from './SignatureK162TypeSelect';
export * from './SignatureLifetimeSelect';
@@ -42,5 +42,5 @@ export const SystemView = ({ systemId, systemInfo: customSystemInfo, showCustomN
return <SystemViewStandalone {...rest} {...systemInfo} />;
}
return <SystemViewStandalone customName={mapSystemInfo.name ?? undefined} {...rest} {...systemInfo} />;
return <SystemViewStandalone customName={mapSystemInfo.temporary_name ?? mapSystemInfo.name ?? undefined} {...rest} {...systemInfo} />;
};
@@ -0,0 +1,7 @@
// eslint-disable-next-line no-restricted-imports
import { Button, ButtonProps } from 'primereact/button';
export const WdButton = ({ type = 'button', ...props }: ButtonProps) => {
// eslint-disable-next-line react/forbid-elements
return <Button {...props} type={type} />;
};
@@ -0,0 +1,86 @@
import { WdButton } from '@/hooks/Mapper/components/ui-kit/WdButton.tsx';
import { TimeStatus } from '@/hooks/Mapper/types';
import clsx from 'clsx';
import { BUILT_IN_TOOLTIP_OPTIONS } from './constants.ts';
const LIFE_TIME = [
{
id: TimeStatus._1h,
label: '1H',
className: 'bg-purple-400 hover:!bg-purple-400',
inactiveClassName: 'bg-purple-400/30',
description: 'Less than one 1 hours remaining',
},
{
id: TimeStatus._4h,
label: '4H',
className: 'bg-purple-300 hover:!bg-purple-300',
inactiveClassName: 'bg-purple-300/30',
description: 'Less than one 4 hours remaining',
},
{
id: TimeStatus._4h30m,
label: '4.5H',
className: 'bg-indigo-300 hover:!bg-indigo-300',
inactiveClassName: 'bg-indigo-300/30',
description: 'Less than one 4.5 hours remaining. All small holes have such lifetime.',
},
{
id: TimeStatus._16h,
label: '16H',
className: 'bg-orange-300 hover:!bg-orange-300',
inactiveClassName: 'bg-orange-400/30',
description: 'Less than one 16 hours remaining',
},
{
id: TimeStatus._24h,
label: '24H',
className: 'bg-orange-300 hover:!bg-orange-300',
inactiveClassName: 'bg-orange-400/30',
description: 'Less than one 24 hours remaining',
},
{
id: TimeStatus._48h,
label: '48H',
className: 'bg-orange-300 hover:!bg-orange-300',
inactiveClassName: 'bg-orange-400/30',
description: 'Less than one 24 hours remaining. Related only with C6. B041, B520, U319, C391.',
},
];
export interface WdLifetimeSelectorProps {
lifetime?: TimeStatus;
onChangeLifetime(lifetime: TimeStatus): void;
className?: string;
}
export const WdLifetimeSelector = ({
lifetime = TimeStatus._24h,
onChangeLifetime,
className,
}: WdLifetimeSelectorProps) => {
return (
<form>
<div className={clsx('grid grid-cols-[1fr_1fr_1fr_1fr_1fr_1fr] gap-1', className)}>
{LIFE_TIME.map(x => (
<WdButton
key={x.id}
outlined={false}
value={x.label}
tooltip={x.description}
tooltipOptions={BUILT_IN_TOOLTIP_OPTIONS}
size="small"
className={clsx(
`py-[1px] justify-center min-w-auto w-auto border-0 text-[12px] font-bold leading-[20px]`,
{ [x.inactiveClassName]: lifetime !== x.id },
x.className,
)}
onClick={() => onChangeLifetime(x.id)}
>
{x.label}
</WdButton>
))}
</div>
</form>
);
};
@@ -18,6 +18,7 @@ export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
content: (() => React.ReactNode) | React.ReactNode;
targetSelector?: string;
interactive?: boolean;
smallPaddings?: boolean;
}
export interface OffsetPosition {
@@ -47,6 +48,7 @@ export const WdTooltip = forwardRef(
position: tPosition = TooltipPosition.default,
offset = 5,
interactive = false,
smallPaddings = false,
className,
...restProps
}: TooltipProps,
@@ -264,10 +266,14 @@ export const WdTooltip = forwardRef(
ref={tooltipRef}
className={clsx(
classes.tooltip,
interactive ? 'pointer-events-auto' : 'pointer-events-none',
'absolute px-1 py-1',
'absolute px-2 py-1',
'border rounded-sm border-green-300 border-opacity-10 bg-stone-900 bg-opacity-90',
pos == null && 'invisible',
{
'pointer-events-auto': interactive,
'pointer-events-none': !interactive,
invisible: pos == null,
'!px-1': smallPaddings,
},
className,
)}
style={{
@@ -8,13 +8,26 @@ export type WdTooltipWrapperProps = {
content?: (() => ReactNode) | ReactNode;
size?: TooltipSize;
interactive?: boolean;
smallPaddings?: boolean;
tooltipClassName?: string;
} & Omit<HTMLProps<HTMLDivElement>, 'content' | 'size'> &
Omit<TooltipProps, 'content'>;
export const WdTooltipWrapper = forwardRef<WdTooltipHandlers, WdTooltipWrapperProps>(
(
{ className, children, content, offset, position, targetSelector, interactive, size, tooltipClassName, ...props },
{
className,
children,
content,
offset,
position,
targetSelector,
interactive,
smallPaddings,
size,
tooltipClassName,
...props
},
forwardedRef,
) => {
const suffix = useMemo(() => Math.random().toString(36).slice(2, 7), []);
@@ -31,6 +44,7 @@ export const WdTooltipWrapper = forwardRef<WdTooltipHandlers, WdTooltipWrapperPr
position={position}
content={content}
interactive={interactive}
smallPaddings={smallPaddings}
targetSelector={finalTargetSelector}
className={clsx(size && sizeClass(size), tooltipClassName)}
/>
@@ -0,0 +1,6 @@
export const BUILT_IN_TOOLTIP_OPTIONS = {
mouseTrack: true,
mouseTrackLeft: 10,
className:
'rounded-[3px] bg-stone-900/90 px-1 py-1 [&_.p-tooltip-text]:!text-stone-300 text-[13px] [&_.p-tooltip-text]:!p-1',
};
@@ -21,3 +21,5 @@ export * from './LoadingWrapper';
export * from './WdMenuItem';
export * from './MenuItemWithInfo';
export * from './MarkdownTextViewer.tsx';
export * from './WdButton.tsx';
export * from './constants.ts';
+8 -2
View File
@@ -1,6 +1,7 @@
export enum ConnectionType {
wormhole,
gate,
bridge,
}
export enum MassState {
@@ -10,8 +11,13 @@ export enum MassState {
}
export enum TimeStatus {
default,
eol,
reserved, // TODO: this reserved for not broke prev solution
_1h,
_4h,
_4h30m,
_16h,
_24h,
_48h,
}
export enum ShipSizeStatus {
+1 -1
View File
@@ -122,7 +122,7 @@ export enum SignatureGroupRU {
export enum SignatureGroupFR {
CosmicSignature = 'Signature cosmique (groupe)',
Wormhole = 'Trou de ver',
GasSite = 'Site de gaz',
GasSite = 'Site de collecte de gaz',
RelicSite = 'Site de reliques',
DataSite = 'Site de données',
OreSite = 'Site de minerai',
@@ -0,0 +1,132 @@
const TYPE_ORDER = [
'undefined',
'null',
'boolean',
'number',
'bigint',
'string',
'symbol',
'function',
'date',
'regexp',
'other',
] as const;
type TypeTag = (typeof TYPE_ORDER)[number];
const getTypeTag = (v: unknown): TypeTag => {
if (v === undefined) return 'undefined';
if (v === null) return 'null';
const t = typeof v;
if (t === 'boolean' || t === 'number' || t === 'bigint' || t === 'string' || t === 'symbol' || t === 'function')
return t as TypeTag;
const tag = Object.prototype.toString.call(v);
if (tag === '[object Date]') return 'date';
if (tag === '[object RegExp]') return 'regexp';
return 'other';
};
const cmp = (a: unknown, b: unknown): number => {
const ta = getTypeTag(a);
const tb = getTypeTag(b);
if (ta !== tb) return TYPE_ORDER.indexOf(ta) - TYPE_ORDER.indexOf(tb);
switch (ta) {
case 'undefined':
case 'null':
return 0;
case 'boolean':
return (a as boolean) === (b as boolean) ? 0 : a ? 1 : -1;
case 'number': {
const na = a as number,
nb = b as number;
const aIsNaN = Number.isNaN(na),
bIsNaN = Number.isNaN(nb);
if (aIsNaN || bIsNaN) return aIsNaN && bIsNaN ? 0 : aIsNaN ? 1 : -1; // NaN в конец чисел
return na === nb ? 0 : na < nb ? -1 : 1;
}
case 'bigint': {
const ba = a as bigint,
bb = b as bigint;
return ba === bb ? 0 : ba < bb ? -1 : 1;
}
case 'string':
return (a as string).localeCompare(b as string);
case 'symbol': {
const da = (a as symbol).description ?? '';
const db = (b as symbol).description ?? '';
return da.localeCompare(db);
}
case 'function':
// @ts-ignore
return ((a as Function).name || '').localeCompare((b as Function).name || '');
case 'date':
return (a as Date).getTime() - (b as Date).getTime();
case 'regexp':
return a!.toString().localeCompare(b!.toString());
default:
return String(a).localeCompare(String(b));
}
};
const isIterable = (v: unknown): v is Iterable<unknown> =>
v != null && typeof (v as any)[Symbol.iterator] === 'function';
const pushTypedArrayValues = (v: unknown, out: unknown[]) => {
if (ArrayBuffer.isView(v) && !(v instanceof DataView)) {
// @ts-ignore
out.push(...(v as ArrayLike<number> as any));
return true;
}
return false;
};
/**
* Generate this func with ChatGPT 5. Cause it pure func and looks like what i need
* May be in net we can find smtng like that
* @param input
*/
export const flattenValues = (input: unknown): unknown[] => {
const out: unknown[] = [];
const seen = new WeakSet<object>();
const visit = (v: unknown): void => {
const tag = getTypeTag(v);
if (tag !== 'other') {
out.push(v);
return;
}
if (v && typeof v === 'object') {
if (seen.has(v)) return;
seen.add(v);
if (pushTypedArrayValues(v, out)) return;
if (v instanceof Map) {
for (const val of v.values()) visit(val);
return;
}
if (v instanceof Set) {
for (const val of v.values()) visit(val);
return;
}
if (Array.isArray(v) || isIterable(v)) {
for (const item of v as Iterable<unknown>) visit(item);
return;
}
for (const key of Object.keys(v)) {
// @ts-ignore
visit((v as never)[key]);
}
return;
}
out.push(v);
};
visit(input);
return out.sort(cmp);
};
+1 -7
View File
@@ -4,7 +4,7 @@ import 'phoenix_html';
import './live_reload.css';
const animateBg = function (bgCanvas) {
const { TweenMax, _ } = window;
const { TweenMax } = window;
/**
* Utility function for returning a random integer in a given range
* @param {Int} max
@@ -212,12 +212,6 @@ const animateBg = function (bgCanvas) {
};
}
window.myJump = new JumpToHyperspace(bgCanvas);
window.addEventListener(
'resize',
_.debounce(() => {
window.myJump.reset();
}, 250),
);
};
document.addEventListener('DOMContentLoaded', function () {
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 977 B

+33
View File
@@ -0,0 +1,33 @@
{
"name": "Wanderer",
"short_name": "Wanderer",
"icons": [
{
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#171717",
"background_color": "#171717",
"display": "standalone",
"start_url": "/",
"screenshots": [
{
"src": "web-app-manifest.webp",
"sizes": "720x1280",
"type": "image/webp"
},
{
"src": "web-app-manifest-wide.webp",
"sizes": "1280x720",
"type": "image/webp",
"form_factor": "wide"
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

+6
View File
@@ -121,6 +121,11 @@ restrict_maps_creation =
|> get_var_from_path_or_env("WANDERER_RESTRICT_MAPS_CREATION", "false")
|> String.to_existing_atom()
restrict_acls_creation =
config_dir
|> get_var_from_path_or_env("WANDERER_RESTRICT_ACLS_CREATION", "false")
|> String.to_existing_atom()
config :wanderer_app,
web_app_url: web_app_url,
git_sha: System.get_env("GIT_SHA", "111"),
@@ -150,6 +155,7 @@ config :wanderer_app,
map_connection_eol_expire_timeout_mins: map_connection_eol_expire_timeout_mins,
wallet_tracking_enabled: wallet_tracking_enabled,
restrict_maps_creation: restrict_maps_creation,
restrict_acls_creation: restrict_acls_creation,
subscription_settings: %{
plans: [
%{
+8 -2
View File
@@ -147,8 +147,13 @@ defmodule WandererApp.Api.MapConnection do
allow_nil?(true)
end
# where 0 - normal
# where 1 - end of life
# 0 - normal (env settings)
# 1 - EOL 1h
# 2 - EOL 4h
# 3 - EOL 4.5h
# 4 - EOL 16h
# 5 - EOL 24h
# 6 - EOL 48h
attribute :time_status, :integer do
default(0)
@@ -168,6 +173,7 @@ defmodule WandererApp.Api.MapConnection do
# where 0 - Wormhole
# where 1 - Gate
# where 2 - Bridge
attribute :type, :integer do
default(0)
@@ -203,7 +203,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do
end
end
end,
max_concurrency: System.schedulers_online(),
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task,
timeout: :timer.seconds(60)
)
@@ -256,7 +256,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do
WandererApp.Character.update_character_state(character_id, character_state)
WandererApp.Map.Server.Impl.broadcast!(map_id, :untrack_character, character_id)
end,
max_concurrency: System.schedulers_online(),
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task,
timeout: :timer.seconds(30)
)
+17 -17
View File
@@ -24,7 +24,7 @@ defmodule WandererApp.Character.TrackerPool do
@check_location_errors_interval :timer.minutes(1)
@update_ship_interval :timer.seconds(2)
@update_info_interval :timer.minutes(2)
@update_wallet_interval :timer.minutes(1)
@update_wallet_interval :timer.minutes(10)
@logger Application.compile_env(:wanderer_app, :logger)
@@ -180,7 +180,7 @@ defmodule WandererApp.Character.TrackerPool do
fn character_id ->
WandererApp.Character.Tracker.update_online(character_id)
end,
max_concurrency: System.schedulers_online(),
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task,
timeout: :timer.seconds(5)
)
@@ -241,12 +241,12 @@ defmodule WandererApp.Character.TrackerPool do
WandererApp.Character.Tracker.check_offline(character_id)
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, _result} -> :ok
{:error, reason} -> @logger.error("Error in check_offline: #{inspect(reason)}")
error -> @logger.error("Error in check_offline: #{inspect(error)}")
end)
rescue
e ->
@@ -281,12 +281,12 @@ defmodule WandererApp.Character.TrackerPool do
)
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, _result} -> :ok
{:error, reason} -> @logger.error("Error in check_online_errors: #{inspect(reason)}")
error -> @logger.error("Error in check_online_errors: #{inspect(error)}")
end)
rescue
e ->
@@ -321,12 +321,12 @@ defmodule WandererApp.Character.TrackerPool do
)
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, _result} -> :ok
{:error, reason} -> @logger.error("Error in check_ship_errors: #{inspect(reason)}")
error -> @logger.error("Error in check_ship_errors: #{inspect(error)}")
end)
rescue
e ->
@@ -361,12 +361,12 @@ defmodule WandererApp.Character.TrackerPool do
)
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, _result} -> :ok
{:error, reason} -> @logger.error("Error in check_location_errors: #{inspect(reason)}")
error -> @logger.error("Error in check_location_errors: #{inspect(error)}")
end)
rescue
e ->
@@ -395,7 +395,7 @@ defmodule WandererApp.Character.TrackerPool do
fn character_id ->
WandererApp.Character.Tracker.update_location(character_id)
end,
max_concurrency: System.schedulers_online(),
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task,
timeout: :timer.seconds(5)
)
@@ -436,7 +436,7 @@ defmodule WandererApp.Character.TrackerPool do
fn character_id ->
WandererApp.Character.Tracker.update_ship(character_id)
end,
max_concurrency: System.schedulers_online(),
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task,
timeout: :timer.seconds(5)
)
@@ -478,12 +478,12 @@ defmodule WandererApp.Character.TrackerPool do
WandererApp.Character.Tracker.update_info(character_id)
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, _result} -> :ok
{:error, reason} -> Logger.error("Error in update_info: #{inspect(reason)}")
error -> Logger.error("Error in update_info: #{inspect(error)}")
end)
rescue
e ->
@@ -521,13 +521,13 @@ defmodule WandererApp.Character.TrackerPool do
fn character_id ->
WandererApp.Character.Tracker.update_wallet(character_id)
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),
timeout: :timer.minutes(5),
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, _result} -> :ok
{:error, reason} -> Logger.error("Error in update_wallet: #{inspect(reason)}")
error -> Logger.error("Error in update_wallet: #{inspect(error)}")
end)
rescue
e ->
+6
View File
@@ -49,6 +49,12 @@ defmodule WandererApp.Env do
)
def restrict_maps_creation?(), do: get_key(:restrict_maps_creation, false)
@decorate cacheable(
cache: WandererApp.Cache,
key: "restrict_acls_creation"
)
def restrict_acls_creation?(), do: get_key(:restrict_acls_creation, false)
def sse_enabled?() do
Application.get_env(@app, :sse, [])
|> Keyword.get(:enabled, false)
+1 -1
View File
@@ -253,7 +253,7 @@ defmodule WandererApp.Esi.ApiClient do
fn destination ->
get_routes(origin, destination, params, opts)
end,
max_concurrency: 20,
max_concurrency: System.schedulers_online() * 4,
timeout: :timer.seconds(30),
on_timeout: :kill_task
)
+1 -1
View File
@@ -254,7 +254,7 @@ defmodule WandererApp.Map.Manager do
:timer.sleep(@maps_start_interval)
end,
max_concurrency: System.schedulers_online(),
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task,
timeout: :timer.seconds(60)
)
+1 -1
View File
@@ -49,7 +49,7 @@ defmodule WandererApp.Map.ZkbDataFetcher do
@logger.error(Exception.message(e))
end
end,
max_concurrency: 10,
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task
)
|> Enum.each(fn _ -> :ok end)
@@ -19,6 +19,7 @@ defmodule WandererApp.Map.Operations.Connections do
@medium_ship_size 1
@large_ship_size 2
@xlarge_ship_size 3
@capital_ship_size 4
# System class constants
@c1_system_class 1
@@ -35,6 +36,12 @@ defmodule WandererApp.Map.Operations.Connections do
do_create(attrs, map_id, char_id)
end
def small_ship_size(), do: @small_ship_size
def medium_ship_size(), do: @medium_ship_size
def large_ship_size(), do: @large_ship_size
def freight_ship_size(), do: @xlarge_ship_size
def capital_ship_size(), do: @capital_ship_size
defp do_create(attrs, map_id, char_id) do
with {:ok, source} <- parse_int(attrs["solar_system_source"], "solar_system_source"),
{:ok, target} <- parse_int(attrs["solar_system_target"], "solar_system_target"),
@@ -22,7 +22,7 @@ defmodule WandererApp.Map.Server.AclsImpl do
fn acl_id ->
update_acl(acl_id)
end,
max_concurrency: 10,
max_concurrency: System.schedulers_online() * 4,
timeout: :timer.seconds(15)
)
|> Enum.reduce(
@@ -122,14 +122,22 @@ defmodule WandererApp.Map.Server.CharactersImpl do
[]
)
{:ok, %{acls: acls}} =
WandererApp.MapRepo.get(map_id,
acls: [
:owner_id,
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
]
)
if Enum.empty?(invalidate_character_ids) do
:ok
else
{:ok, %{acls: acls}} =
WandererApp.MapRepo.get(map_id,
acls: [
:owner_id,
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
]
)
process_invalidate_characters(invalidate_character_ids, map_id, owner_id, acls)
end
end
defp process_invalidate_characters(invalidate_character_ids, map_id, owner_id, acls) do
invalidate_character_ids
|> Task.async_stream(
fn character_id ->
@@ -166,20 +174,24 @@ defmodule WandererApp.Map.Server.CharactersImpl do
end
end,
timeout: :timer.seconds(60),
max_concurrency: System.schedulers_online(),
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, {:remove_character, character_id}} ->
remove_and_untrack_characters(map_id, [character_id])
:ok
|> Enum.reduce([], fn
{:ok, {:remove_character, character_id}}, acc ->
[character_id | acc]
{:ok, _result} ->
:ok
{:ok, _result}, acc ->
acc
{:error, reason} ->
{:error, reason}, acc ->
Logger.error("Error in cleanup_characters: #{inspect(reason)}")
acc
end)
|> case do
[] -> :ok
character_ids_to_remove -> remove_and_untrack_characters(map_id, character_ids_to_remove)
end
end
defp remove_and_untrack_characters(map_id, character_ids) do
@@ -293,7 +305,7 @@ defmodule WandererApp.Map.Server.CharactersImpl do
:ok
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task
)
|> Enum.each(fn
@@ -4,6 +4,7 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
require Logger
alias WandererApp.Map.Server.Impl
alias WandererApp.Map.Server.SignaturesImpl
# @ccp1 -1
@c1 1
@@ -68,10 +69,38 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
# this class of systems will guaranty that no one real class will take that place
# @unknown 100_100
#
# default (env) setting, not EOL
@connection_time_status_default 0
# EOL 1h
@connection_time_status_eol 1
# EOL 4h
@connection_time_status_eol_4 2
# EOL 4.5h
@connection_time_status_eol_4_5 3
# EOL 16h
@connection_time_status_eol_16 4
# EOL 24h
@connection_time_status_eol_24 5
# EOL 48h
@connection_time_status_eol_48 6
# EOL 1h
@connection_eol_minutes 60
# EOL 4h
@connection_eol_4_minutes 4 * 60
# EOL 4.5h
@connection_eol_4_5_minutes 4.5 * 60
# EOL 16h
@connection_eol_16_minutes 16 * 60
# EOL 24h
@connection_eol_24_minutes 24 * 60
# EOL 48h
@connection_eol_48_minutes 48 * 60
@connection_type_wormhole 0
@connection_type_stargate 1
@connection_type_bridge 2
@medium_ship_size 1
def get_connection_auto_expire_hours(), do: WandererApp.Env.map_connection_auto_expire_hours()
@@ -146,6 +175,18 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
state
end
def update_connection_type(
%{map_id: map_id} = state,
%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id,
character_id: character_id
} = _connection_info,
type
) do
state
end
def get_connection_info(
%{map_id: map_id} = _state,
%{
@@ -174,7 +215,8 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
),
do:
update_connection(state, :update_time_status, [:time_status], connection_update, fn
%{time_status: old_time_status}, %{id: connection_id, time_status: time_status} ->
%{time_status: old_time_status},
%{id: connection_id, time_status: time_status} = updated_connection ->
case time_status == @connection_time_status_eol do
true ->
if old_time_status != @connection_time_status_eol do
@@ -182,6 +224,8 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
"map_#{map_id}:conn_#{connection_id}:mark_eol_time",
DateTime.utc_now()
)
set_start_time(map_id, connection_id, DateTime.utc_now())
end
_ ->
@@ -190,6 +234,10 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
set_start_time(map_id, connection_id, DateTime.utc_now())
end
end
if time_status != old_time_status do
maybe_update_linked_signature_time_status(map_id, updated_connection)
end
end)
def update_connection_type(
@@ -230,35 +278,38 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
state =
map_id
|> WandererApp.Map.list_connections!()
|> Enum.filter(fn %{
id: connection_id,
inserted_at: inserted_at,
solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id,
type: type
} ->
connection_start_time = get_start_time(map_id, connection_id)
type != @connection_type_stargate &&
DateTime.diff(DateTime.utc_now(), connection_start_time, :hour) >=
connection_auto_eol_hours &&
is_connection_valid(
:wormholes,
solar_system_source_id,
solar_system_target_id
)
end)
|> Enum.reduce(state, fn %{
id: connection_id,
solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id
solar_system_target: solar_system_target_id,
time_status: time_status,
type: type
},
state ->
state
|> update_connection_time_status(%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id,
time_status: @connection_time_status_eol
})
if type == @connection_type_wormhole do
connection_start_time = get_start_time(map_id, connection_id)
new_time_status = get_new_time_status(connection_start_time, time_status)
if new_time_status != time_status &&
is_connection_valid(
:wormholes,
solar_system_source_id,
solar_system_target_id
) do
set_start_time(map_id, connection_id, DateTime.utc_now())
state
|> update_connection_time_status(%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id,
time_status: new_time_status
})
else
state
end
else
state
end
end)
state =
@@ -268,37 +319,38 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
id: connection_id,
solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id,
time_status: time_status,
type: type
} ->
connection_mark_eol_time =
get_connection_mark_eol_time(map_id, connection_id)
reverse_connection =
WandererApp.Map.get_connection(
map_id,
solar_system_target_id,
solar_system_source_id
)
is_connection_exist =
is_connection_exist(
map_id,
solar_system_source_id,
solar_system_target_id
) || not is_nil(reverse_connection)
is_connection_valid =
is_connection_valid(
:wormholes,
solar_system_source_id,
solar_system_target_id
)
) ||
not is_nil(
WandererApp.Map.get_connection(
map_id,
solar_system_target_id,
solar_system_source_id
)
)
not is_connection_exist ||
(type != @connection_type_stargate && is_connection_valid &&
DateTime.diff(DateTime.utc_now(), connection_mark_eol_time, :hour) >=
(type == @connection_type_wormhole &&
time_status == @connection_time_status_eol &&
is_connection_valid(
:wormholes,
solar_system_source_id,
solar_system_target_id
) &&
DateTime.diff(
DateTime.utc_now(),
get_connection_mark_eol_time(map_id, connection_id),
:hour
) >=
connection_auto_expire_hours - connection_auto_eol_hours +
+connection_eol_expire_timeout_hours)
connection_eol_expire_timeout_hours)
end)
|> Enum.reduce(state, fn %{
solar_system_source: solar_system_source_id,
@@ -314,30 +366,103 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
state
end
defp maybe_update_linked_signature_time_status(
map_id,
%{
time_status: time_status,
solar_system_source: solar_system_source,
solar_system_target: solar_system_target
} = updated_connection
) do
source_system =
WandererApp.Map.find_system_by_location(
map_id,
%{solar_system_id: solar_system_source}
)
target_system =
WandererApp.Map.find_system_by_location(
map_id,
%{solar_system_id: solar_system_target}
)
source_linked_signatures =
find_linked_signatures(source_system, target_system)
target_linked_signatures = find_linked_signatures(target_system, source_system)
update_signatures_time_status(
map_id,
source_system.solar_system_id,
source_linked_signatures,
time_status
)
update_signatures_time_status(
map_id,
target_system.solar_system_id,
target_linked_signatures,
time_status
)
end
defp find_linked_signatures(
%{id: source_system_id} = _source_system,
%{solar_system_id: solar_system_id, linked_sig_eve_id: linked_sig_eve_id} =
_target_system
)
when not is_nil(linked_sig_eve_id) do
{:ok, signatures} =
WandererApp.Api.MapSystemSignature.by_linked_system_id(solar_system_id)
signatures |> Enum.filter(fn sig -> sig.system_id == source_system_id end)
end
defp find_linked_signatures(_source_system, _target_system), do: []
defp update_signatures_time_status(_map_id, _solar_system_id, [], _time_status), do: :ok
defp update_signatures_time_status(map_id, solar_system_id, signatures, time_status) do
signatures
|> Enum.each(fn %{custom_info: custom_info_json} = sig ->
update_params =
if not is_nil(custom_info_json) do
updated_custom_info =
custom_info_json
|> Jason.decode!()
|> Map.merge(%{"time_status" => time_status})
|> Jason.encode!()
%{custom_info: updated_custom_info}
else
updated_custom_info = Jason.encode!(%{"time_status" => time_status})
%{custom_info: updated_custom_info}
end
SignaturesImpl.apply_update_signature(%{map_id: map_id}, sig, update_params)
end)
Impl.broadcast!(map_id, :signatures_updated, solar_system_id)
end
def maybe_add_connection(map_id, location, old_location, character_id, is_manual)
when not is_nil(location) and not is_nil(old_location) and
not is_nil(old_location.solar_system_id) and
location.solar_system_id != old_location.solar_system_id do
{:ok, character} = WandererApp.Character.get_character(character_id)
if not is_manual do
character_id
|> WandererApp.Character.get_character!()
|> case do
nil ->
:ok
:telemetry.execute([:wanderer_app, :map, :character, :jump], %{count: 1}, %{})
character ->
:telemetry.execute([:wanderer_app, :map, :character, :jump], %{count: 1}, %{})
{:ok, _} =
WandererApp.Api.MapChainPassages.new(%{
map_id: map_id,
character_id: character_id,
ship_type_id: character.ship,
ship_name: character.ship_name,
solar_system_source_id: old_location.solar_system_id,
solar_system_target_id: location.solar_system_id
})
end
{:ok, _} =
WandererApp.Api.MapChainPassages.new(%{
map_id: map_id,
character_id: character_id,
ship_type_id: character.ship,
ship_name: character.ship_name,
solar_system_source_id: old_location.solar_system_id,
solar_system_target_id: location.solar_system_id
})
end
case WandererApp.Map.check_connection(map_id, location, old_location) do
@@ -356,37 +481,19 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
@connection_type_wormhole
end
# Check if either system is C1 before creating the connection
{:ok, source_system_info} = get_system_static_info(old_location.solar_system_id)
{:ok, target_system_info} = get_system_static_info(location.solar_system_id)
# Set ship size type based on system classes and special rules
ship_size_type =
get_ship_size_type(
old_location.solar_system_id,
location.solar_system_id,
connection_type
)
time_status =
if connection_type == @connection_type_wormhole do
cond do
# C1 systems always get medium
source_system_info.system_class == @c1 or target_system_info.system_class == @c1 ->
@medium_ship_size
# C13 systems always get frigate
source_system_info.system_class == @c13 or target_system_info.system_class == @c13 ->
@frigate_ship_size
# C4 to null gets frigate (unless C4 is shattered)
(source_system_info.system_class == @c4 and target_system_info.system_class == @ns and
not source_system_info.is_shattered) or
(target_system_info.system_class == @c4 and
source_system_info.system_class == @ns and
not target_system_info.is_shattered) ->
@frigate_ship_size
true ->
# Default to large for other wormhole connections
@large_ship_size
end
@connection_time_status_eol_24
else
# Default to large for non-wormhole connections
@large_ship_size
@connection_time_status_default
end
{:ok, connection} =
@@ -395,7 +502,8 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
solar_system_source: old_location.solar_system_id,
solar_system_target: location.solar_system_id,
type: connection_type,
ship_size_type: ship_size_type
ship_size_type: ship_size_type,
time_status: time_status
})
if connection_type == @connection_type_wormhole do
@@ -422,8 +530,6 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
time_status: connection.time_status
})
{:ok, character} = WandererApp.Character.get_character(character_id)
{:ok, _} =
WandererApp.User.ActivityTracker.track_map_event(:map_connection_added, %{
character_id: character_id,
@@ -470,12 +576,12 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
end
end
def set_start_time(map_id, connection_id, start_time) do
WandererApp.Cache.put(
"map_#{map_id}:conn_#{connection_id}:start_time",
start_time
)
end
def set_start_time(map_id, connection_id, start_time),
do:
WandererApp.Cache.put(
"map_#{map_id}:conn_#{connection_id}:start_time",
start_time
)
def can_add_location(_scope, nil), do: false
@@ -668,4 +774,79 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
state
end
end
defp get_ship_size_type(
source_solar_system_id,
target_solar_system_id,
@connection_type_wormhole
) do
# Check if either system is C1 before creating the connection
{:ok, source_system_info} = get_system_static_info(source_solar_system_id)
{:ok, target_system_info} = get_system_static_info(target_solar_system_id)
cond do
# C1 systems always get medium
source_system_info.system_class == @c1 or target_system_info.system_class == @c1 ->
@medium_ship_size
# C13 systems always get frigate
source_system_info.system_class == @c13 or target_system_info.system_class == @c13 ->
@frigate_ship_size
# C4 to null gets frigate (unless C4 is shattered)
(source_system_info.system_class == @c4 and target_system_info.system_class == @ns and
not source_system_info.is_shattered) or
(target_system_info.system_class == @c4 and
source_system_info.system_class == @ns and
not target_system_info.is_shattered) ->
@frigate_ship_size
true ->
# Default to large for other wormhole connections
@large_ship_size
end
end
# Default to large for non-wormhole connections
defp get_ship_size_type(_source_solar_system_id, _target_solar_system_id, _connection_type),
do: @large_ship_size
defp get_new_time_status(_start_time, @connection_time_status_default),
do: @connection_time_status_eol_24
defp get_new_time_status(start_time, old_time_status) do
left_minutes =
get_time_status_minutes(old_time_status) -
DateTime.diff(DateTime.utc_now(), start_time, :minute)
cond do
left_minutes <= @connection_eol_minutes ->
@connection_time_status_eol
left_minutes <= @connection_eol_4_minutes ->
@connection_time_status_eol_4
left_minutes <= @connection_eol_4_5_minutes ->
@connection_time_status_eol_4_5
left_minutes <= @connection_eol_16_minutes ->
@connection_time_status_eol_16
left_minutes <= @connection_eol_24_minutes ->
@connection_time_status_eol_24
left_minutes <= @connection_eol_48_minutes ->
@connection_time_status_eol_48
true ->
@connection_time_status_default
end
end
defp get_time_status_minutes(@connection_time_status_eol), do: @connection_eol_minutes
defp get_time_status_minutes(@connection_time_status_eol_4), do: @connection_eol_4_minutes
defp get_time_status_minutes(@connection_time_status_eol_4_5), do: @connection_eol_4_5_minutes
defp get_time_status_minutes(@connection_time_status_eol_16), do: @connection_eol_16_minutes
defp get_time_status_minutes(@connection_time_status_eol_24), do: @connection_eol_24_minutes
defp get_time_status_minutes(@connection_time_status_eol_48), do: @connection_eol_48_minutes
end
@@ -26,7 +26,7 @@ defmodule WandererApp.Map.Server.Impl do
@systems_cleanup_timeout :timer.minutes(30)
@characters_cleanup_timeout :timer.minutes(5)
@connections_cleanup_timeout :timer.minutes(2)
@connections_cleanup_timeout :timer.minutes(1)
@pubsub_client Application.compile_env(:wanderer_app, :pubsub_client)
@backup_state_timeout :timer.minutes(1)
@@ -104,7 +104,7 @@ defmodule WandererApp.Map.Server.Impl do
)
Process.send_after(self(), :update_presence, @update_presence_timeout)
Process.send_after(self(), :cleanup_connections, 5_000)
Process.send_after(self(), :cleanup_connections, @connections_cleanup_timeout)
Process.send_after(self(), :cleanup_systems, 10_000)
Process.send_after(self(), :cleanup_characters, @characters_cleanup_timeout)
Process.send_after(self(), :backup_state, @backup_state_timeout)
@@ -17,51 +17,53 @@ defmodule WandererApp.Map.Server.PingsImpl do
user_id: user_id
} = ping_info
) do
{:ok, character} = WandererApp.Character.get_character(character_id)
with {:ok, character} <- WandererApp.Character.get_character(character_id),
system <-
WandererApp.Map.find_system_by_location(map_id, %{
solar_system_id: solar_system_id |> String.to_integer()
}),
{:ok, ping} <-
WandererApp.MapPingsRepo.create(%{
map_id: map_id,
character_id: character_id,
system_id: system.id,
message: message,
type: type
}) do
Impl.broadcast!(
map_id,
:ping_added,
ping |> Map.merge(%{character_eve_id: character.eve_id, solar_system_id: solar_system_id})
)
system =
WandererApp.Map.find_system_by_location(map_id, %{
solar_system_id: solar_system_id |> String.to_integer()
})
# Broadcast rally point events to external clients (webhooks/SSE)
if type == 1 do
WandererApp.ExternalEvents.broadcast(map_id, :rally_point_added, %{
rally_point_id: ping.id,
solar_system_id: solar_system_id,
system_id: system.id,
character_id: character_id,
character_name: character.name,
character_eve_id: character.eve_id,
system_name: system.name,
message: message,
created_at: ping.inserted_at
})
end
{:ok, ping} =
WandererApp.MapPingsRepo.create(%{
WandererApp.User.ActivityTracker.track_map_event(:map_rally_added, %{
character_id: character_id,
user_id: user_id,
map_id: map_id,
character_id: character_id,
system_id: system.id,
message: message,
type: type
solar_system_id: "#{solar_system_id}"
})
Impl.broadcast!(
map_id,
:ping_added,
ping |> Map.merge(%{character_eve_id: character.eve_id, solar_system_id: solar_system_id})
)
# Broadcast rally point events to external clients (webhooks/SSE)
if type == 1 do
WandererApp.ExternalEvents.broadcast(map_id, :rally_point_added, %{
rally_point_id: ping.id,
solar_system_id: solar_system_id,
system_id: system.id,
character_id: character_id,
character_name: character.name,
character_eve_id: character.eve_id,
system_name: system.name,
message: message,
created_at: ping.inserted_at
})
state
else
error ->
Logger.error("Failed to add_ping: #{inspect(error, pretty: true)}")
state
end
WandererApp.User.ActivityTracker.track_map_event(:map_rally_added, %{
character_id: character_id,
user_id: user_id,
map_id: map_id,
solar_system_id: "#{solar_system_id}"
})
state
end
def cancel_ping(
@@ -73,39 +75,42 @@ defmodule WandererApp.Map.Server.PingsImpl do
type: type
} = ping_info
) do
{:ok, character} = WandererApp.Character.get_character(character_id)
{:ok, %{system: %{id: system_id, name: system_name, solar_system_id: solar_system_id}} = ping} =
WandererApp.MapPingsRepo.get_by_id(ping_id)
:ok = WandererApp.MapPingsRepo.destroy(ping)
Impl.broadcast!(map_id, :ping_cancelled, %{
id: ping_id,
solar_system_id: solar_system_id,
type: type
})
# Broadcast rally point removal events to external clients (webhooks/SSE)
if type == 1 do
WandererApp.ExternalEvents.broadcast(map_id, :rally_point_removed, %{
with {:ok, character} <- WandererApp.Character.get_character(character_id),
{:ok,
%{system: %{id: system_id, name: system_name, solar_system_id: solar_system_id}} = ping} <-
WandererApp.MapPingsRepo.get_by_id(ping_id),
:ok <- WandererApp.MapPingsRepo.destroy(ping) do
Impl.broadcast!(map_id, :ping_cancelled, %{
id: ping_id,
solar_system_id: solar_system_id,
system_id: system_id,
character_id: character_id,
character_name: character.name,
character_eve_id: character.eve_id,
system_name: system_name
type: type
})
# Broadcast rally point removal events to external clients (webhooks/SSE)
if type == 1 do
WandererApp.ExternalEvents.broadcast(map_id, :rally_point_removed, %{
id: ping_id,
solar_system_id: solar_system_id,
system_id: system_id,
character_id: character_id,
character_name: character.name,
character_eve_id: character.eve_id,
system_name: system_name
})
end
WandererApp.User.ActivityTracker.track_map_event(:map_rally_cancelled, %{
character_id: character_id,
user_id: user_id,
map_id: map_id,
solar_system_id: solar_system_id
})
state
else
error ->
Logger.error("Failed to cancel_ping: #{inspect(error, pretty: true)}")
state
end
WandererApp.User.ActivityTracker.track_map_event(:map_rally_cancelled, %{
character_id: character_id,
user_id: user_id,
map_id: map_id,
solar_system_id: solar_system_id
})
state
end
end
@@ -7,6 +7,7 @@ defmodule WandererApp.Map.Server.SignaturesImpl do
alias WandererApp.Character
alias WandererApp.User.ActivityTracker
alias WandererApp.Map.Server.{Impl, ConnectionsImpl, SystemsImpl}
alias WandererApp.Utils.EVEUtil
@doc """
Public entrypoint for updating signatures on a map system.
@@ -92,7 +93,7 @@ defmodule WandererApp.Map.Server.SignaturesImpl do
|> Enum.filter(&(&1.eve_id in updated_ids))
|> Enum.each(fn existing ->
update = Enum.find(updated_sigs, &(&1.eve_id == existing.eve_id))
apply_update_signature(existing, update)
apply_update_signature(state, existing, update)
end)
# 3. Additions & restorations
@@ -209,13 +210,19 @@ defmodule WandererApp.Map.Server.SignaturesImpl do
MapSystemSignature.update!(sig, %{deleted: true})
end
defp apply_update_signature(%MapSystemSignature{} = existing, update_params)
when not is_nil(update_params) do
def apply_update_signature(
state,
%MapSystemSignature{} = existing,
update_params
)
when not is_nil(update_params) do
case MapSystemSignature.update(
existing,
update_params |> Map.put(:update_forced_at, DateTime.utc_now())
) do
{:ok, _updated} ->
{:ok, updated} ->
maybe_update_connection_time_status(state, existing, updated)
maybe_update_connection_mass_status(state, existing, updated)
:ok
{:error, reason} ->
@@ -223,6 +230,52 @@ defmodule WandererApp.Map.Server.SignaturesImpl do
end
end
defp maybe_update_connection_time_status(
state,
%{custom_info: old_custom_info} = old_sig,
%{custom_info: new_custom_info, system_id: system_id, linked_system_id: linked_system_id} =
updated_sig
)
when not is_nil(linked_system_id) do
old_time_status = get_time_status(old_custom_info)
new_time_status = get_time_status(new_custom_info)
if old_time_status != new_time_status do
{:ok, source_system} = MapSystem.by_id(system_id)
ConnectionsImpl.update_connection_time_status(state, %{
solar_system_source_id: source_system.solar_system_id,
solar_system_target_id: linked_system_id,
time_status: new_time_status
})
end
end
defp maybe_update_connection_time_status(_state, _old_sig, _updated_sig), do: :ok
defp maybe_update_connection_mass_status(
state,
%{type: old_type} = old_sig,
%{type: new_type, system_id: system_id, linked_system_id: linked_system_id} =
updated_sig
)
when not is_nil(linked_system_id) do
if old_type != new_type do
{:ok, source_system} = MapSystem.by_id(system_id)
signature_ship_size_type = EVEUtil.get_wh_size(new_type)
if not is_nil(signature_ship_size_type) do
ConnectionsImpl.update_connection_ship_size_type(state, %{
solar_system_source_id: source_system.solar_system_id,
solar_system_target_id: linked_system_id,
ship_size_type: signature_ship_size_type
})
end
end
end
defp maybe_update_connection_mass_status(_state, _old_sig, _updated_sig), do: :ok
defp track_activity(event, map_id, solar_system_id, user_id, character_id, signatures) do
ActivityTracker.track_map_event(event, %{
map_id: map_id,
@@ -251,4 +304,12 @@ defmodule WandererApp.Map.Server.SignaturesImpl do
}
end)
end
defp get_time_status(nil), do: nil
defp get_time_status(custom_info_json) do
custom_info_json
|> Jason.decode!()
|> Map.get("time_status")
end
end
+1 -1
View File
@@ -41,7 +41,7 @@ defmodule WandererApp.Maps do
system |> Map.take(@minimum_route_attrs)
end
end,
max_concurrency: 10
max_concurrency: System.schedulers_online() * 4
)
|> Enum.map(fn {:ok, val} -> val end)
+26
View File
@@ -3,6 +3,8 @@ defmodule WandererApp.Utils.EVEUtil do
Utility functions for EVE Online related operations.
"""
alias WandererApp.Map.Operations.Connections
@doc """
Generates a URL for a character portrait.
@@ -28,4 +30,28 @@ defmodule WandererApp.Utils.EVEUtil do
def get_portrait_url(eve_id, size) do
"https://images.evetech.net/characters/#{eve_id}/portrait?size=#{size}"
end
def get_wh_size(nil), do: nil
def get_wh_size("K162"), do: nil
def get_wh_size(wh_type_name) do
{:ok, wormhole_types} = WandererApp.CachedInfo.get_wormhole_types()
wormhole_types
|> Enum.find(fn wh_type_data -> wh_type_data.name == wh_type_name end)
|> case do
%{max_mass_per_jump: max_mass_per_jump} when not is_nil(max_mass_per_jump) ->
get_connection_size_status(max_mass_per_jump)
_ ->
nil
end
end
defp get_connection_size_status(5_000_000), do: Connections.small_ship_size()
defp get_connection_size_status(62_000_000), do: Connections.medium_ship_size()
defp get_connection_size_status(375_000_000), do: Connections.large_ship_size()
defp get_connection_size_status(1_000_000_000), do: Connections.freight_ship_size()
defp get_connection_size_status(2_000_000_000), do: Connections.capital_ship_size()
defp get_connection_size_status(_max_mass_per_jump), do: Connections.large_ship_size()
end
+3 -1
View File
@@ -17,7 +17,9 @@ defmodule WandererAppWeb do
those modules here.
"""
def static_paths, do: ~w(assets fonts images icons favicon.ico robots.txt woff woff2 lottie)
def static_paths,
do:
~w(assets fonts images icons favicon.ico site.webmanifest apple-touch-icon.png web-app-manifest-192x192.png web-app-manifest-512x512.png web-app-manifest.webp web-app-manifest-wide.webp robots.txt woff woff2 lottie)
def router do
quote do
@@ -8,6 +8,9 @@
{assigns[:page_title] || "Welcome"}
</.live_title>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
<link
@@ -26,23 +26,74 @@
</div>
</div>
</div>
<div class="carousel carousel-center bg-neutral rounded-box max-w-[80%] space-x-4 p-4">
<%= for post <- @posts do %>
<.link class="group carousel-item relative" navigate={~p"/news/#{post.id}"}>
<div class="artboard-horizontal phone-1 relative hover:text-white mt-10">
<img
class="rounded-lg shadow-lg block !w-[400px] !h-[200px] opacity-75"
src={post.cover_image_uri}
/>
<div class="absolute top-0 left-0 w-full h-full bg-gradient-to-b from-transparent to-black opacity-75 group-hover:opacity-25 transition-opacity duration-300">
<div
id="posts-container"
class="bg-neutral rounded-box max-w-[90%] p-4 max-h-[60vh] overflow-y-auto"
>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<%= for post <- @posts do %>
<.link class="group carousel-item relative" navigate={~p"/news/#{post.id}"}>
<div class="artboard-horizontal phone-1 relative hover:text-white">
<img
class="rounded-lg shadow-lg block !w-[300px] !h-[180px] opacity-75"
src={post.cover_image_uri}
/>
<div class="absolute rounded-lg top-0 left-0 w-full h-full bg-gradient-to-b from-transparent to-black opacity-75 group-hover:opacity-25 transition-opacity duration-300">
</div>
<h3 class="absolute bottom-4 left-14 !text-md font-bold break-normal pt-6 pb-2 ccp-font text-white">
{post.title}
</h3>
</div>
<h3 class="absolute bottom-4 left-14 font-bold break-normal pt-6 pb-2 ccp-font text-white">
{post.title}
</h3>
</div>
</.link>
<% end %>
</.link>
<% end %>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const postsContainer = document.getElementById('posts-container');
if (!postsContainer) return;
let scrollSpeed = 0.5; // pixels per frame
let isScrolling = true;
let scrollDirection = 1; // 1 for down, -1 for up
function autoScroll() {
if (!isScrolling) return;
const maxScroll = postsContainer.scrollHeight - postsContainer.clientHeight;
if (maxScroll <= 0) return; // No need to scroll if content fits
postsContainer.scrollTop += scrollSpeed * scrollDirection;
// Reverse direction when reaching top or bottom
if (postsContainer.scrollTop >= maxScroll) {
scrollDirection = -1;
} else if (postsContainer.scrollTop <= 0) {
scrollDirection = 1;
}
requestAnimationFrame(autoScroll);
}
// Pause scrolling on hover
postsContainer.addEventListener('mouseenter', () => {
isScrolling = false;
});
// Resume scrolling when mouse leaves
postsContainer.addEventListener('mouseleave', () => {
isScrolling = true;
requestAnimationFrame(autoScroll);
});
// Start autoscroll after a delay
setTimeout(() => {
requestAnimationFrame(autoScroll);
}, 2000);
});
</script>
<%!-- <div class="carousel carousel-center !bg-neutral rounded-box max-w-4xl space-x-6 p-4">
</div> --%>
@@ -80,6 +80,11 @@ defmodule WandererAppWeb.MapSystemAPIController do
properties: %{
solar_system_id: %Schema{type: :integer, description: "EVE solar system ID"},
solar_system_name: %Schema{type: :string, description: "EVE solar system name"},
custom_name: %Schema{
type: :string,
nullable: true,
description: "Custom name for the system"
},
position_x: %Schema{type: :integer, description: "X coordinate"},
position_y: %Schema{type: :integer, description: "Y coordinate"},
status: %Schema{
@@ -98,6 +103,7 @@ defmodule WandererAppWeb.MapSystemAPIController do
example: %{
solar_system_id: 30_000_142,
solar_system_name: "Jita",
custom_name: "Trade Hub",
position_x: 100,
position_y: 200,
visible: true,
@@ -113,6 +119,11 @@ defmodule WandererAppWeb.MapSystemAPIController do
description: "EVE solar system name",
nullable: true
},
custom_name: %Schema{
type: :string,
nullable: true,
description: "Custom name for the system"
},
position_x: %Schema{type: :integer, description: "X coordinate", nullable: true},
position_y: %Schema{type: :integer, description: "Y coordinate", nullable: true},
status: %Schema{
@@ -130,6 +141,7 @@ defmodule WandererAppWeb.MapSystemAPIController do
},
example: %{
solar_system_name: "Jita",
custom_name: "Trade Hub",
position_x: 101,
position_y: 202,
visible: false,
@@ -118,6 +118,7 @@ defmodule WandererAppWeb.Helpers.APIUtils do
optional = [
"solar_system_name",
"custom_name",
"position_x",
"position_y",
"coordinates",
@@ -151,6 +152,7 @@ defmodule WandererAppWeb.Helpers.APIUtils do
def extract_update_params(params) when is_map(params) do
allowed = [
"solar_system_name",
"custom_name",
"position_x",
"position_y",
"coordinates",
@@ -20,6 +20,7 @@ defmodule WandererAppWeb.AccessListsLive do
|> assign(
selected_acl: nil,
selected_acl_id: "",
allow_acl_creation: not WandererApp.Env.restrict_acls_creation?(),
user_id: user_id,
access_lists: access_lists |> Enum.map(fn acl -> map_ui_acl(acl, nil) end),
characters: characters,
@@ -34,6 +35,7 @@ defmodule WandererAppWeb.AccessListsLive do
|> assign(
selected_acl: nil,
selected_acl_id: "",
allow_acl_creation: false,
access_lists: [],
characters: [],
members: []
@@ -188,7 +190,11 @@ defmodule WandererAppWeb.AccessListsLive do
{:noreply, assign(socket, form: form)}
end
def handle_event("create", %{"form" => form}, socket) do
def handle_event(
"create",
%{"form" => form},
%{assigns: %{allow_acl_creation: true}} = socket
) do
case WandererApp.Api.AccessList.new(form) do
{:ok, _acl} ->
{:ok, access_lists} = WandererApp.Acls.get_available_acls(socket.assigns.current_user)
@@ -408,9 +414,8 @@ defmodule WandererAppWeb.AccessListsLive do
current_user_has_role?(socket.assigns.current_user, access_list, :admin) ->
true
not is_nil(eve_character_id) and
(characters_has_role?([eve_character_id], access_list, :admin) or
characters_has_role?([eve_character_id], access_list, :manager)) and
not is_nil(eve_character_id) &&
characters_has_roles?([eve_character_id], access_list, [:admin, :manager]) &&
not current_user_has_role?(socket.assigns.current_user, access_list, :admin) ->
false
@@ -470,12 +475,12 @@ defmodule WandererAppWeb.AccessListsLive do
|> put_flash(:info, "Only Characters can have Admin or Manager roles")
|> push_navigate(to: ~p"/access-lists/#{socket.assigns.selected_acl_id}")
defp characters_has_role?(character_eve_ids, access_list, role_atom) do
access_list.members
|> Enum.any?(fn member ->
member.eve_character_id in character_eve_ids and member.role == role_atom
end)
end
defp characters_has_roles?(character_eve_ids, %{members: members} = _access_list, role_atoms),
do:
members
|> Enum.any?(fn %{eve_character_id: eve_character_id, role: role} = _member ->
eve_character_id in character_eve_ids and role in role_atoms
end)
defp current_user_is_owner?(current_user, access_list) do
character_ids = current_user.characters |> Enum.map(& &1.id)
@@ -486,18 +491,16 @@ defmodule WandererAppWeb.AccessListsLive do
defp current_user_has_role?(current_user, access_list, role_atom) do
character_eve_ids = current_user.characters |> Enum.map(& &1.eve_id)
characters_has_role?(character_eve_ids, access_list, role_atom)
characters_has_roles?(character_eve_ids, access_list, [role_atom])
end
defp can_add_members?(nil, _current_user), do: false
defp can_add_members?(access_list, current_user) do
character_eve_ids = current_user.characters |> Enum.map(& &1.eve_id)
user_character_eve_ids = current_user.characters |> Enum.map(& &1.eve_id)
member = access_list.members |> Enum.find(&(&1.eve_character_id in character_eve_ids))
current_user_is_owner?(current_user, access_list) or
(not is_nil(member) and member.role in [:admin, :manager])
current_user_is_owner?(current_user, access_list) ||
characters_has_roles?(user_character_eve_ids, access_list, [:admin, :manager])
end
defp can_delete_member?(
@@ -512,9 +515,8 @@ defmodule WandererAppWeb.AccessListsLive do
current_user_has_role?(current_user, access_list, :admin) ->
true
not is_nil(eve_character_id) and
(characters_has_role?([eve_character_id], access_list, :admin) or
characters_has_role?([eve_character_id], access_list, :manager)) and
not is_nil(eve_character_id) &&
characters_has_roles?([eve_character_id], access_list, [:admin, :manager]) &&
not current_user_has_role?(current_user, access_list, :admin) ->
false
@@ -34,7 +34,11 @@
</button>
</:action>
</.table>
<.link class="btn mt-2 w-full btn-neutral rounded-none" patch={~p"/access-lists/new"}>
<.link
:if={@allow_acl_creation}
class="btn mt-2 w-full btn-neutral rounded-none"
patch={~p"/access-lists/new"}
>
<.icon name="hero-plus-solid" class="w-6 h-6" />
<h3 class="card-title text-center text-md">New Access List</h3>
</.link>
@@ -195,78 +199,13 @@
</.modal>
<.modal
:if={@live_action in [:add_members]}
:if={@live_action in [:add_members] && can_add_members?(@access_list, @current_user)}
title="Add Member"
class="!w-[500px]"
id="add_member"
show
on_cancel={JS.patch(~p"/access-lists/#{@selected_acl_id}")}
>
<%!-- <div class="mt-4 mb-2 p-tabmenu p-component " data-pc-section="tabmenu">
<ul
class="p-tabmenu-nav border-none h-[25px] w-full flex"
role="menubar"
data-pc-section="menu"
>
<li
id="pr_id_17_0"
class="p-tabmenuitem p-highlight"
role="presentation"
data-p-highlight="true"
data-p-disabled="false"
data-pc-section="menuitem"
>
<a
href="#"
role="menuitem"
aria-label="Router Link"
tabindex="0"
class="p-menuitem-link"
data-pc-section="action"
>
<span class="p-menuitem-text" data-pc-section="label">Character</span>
</a>
</li>
<li
id="pr_id_17_1"
class="p-tabmenuitem"
role="presentation"
data-p-highlight="false"
data-p-disabled="false"
data-pc-section="menuitem"
>
<a
href="#"
role="menuitem"
aria-label="Programmatic"
tabindex="-1"
class="p-menuitem-link"
data-pc-section="action"
>
<span class="p-menuitem-text" data-pc-section="label">Corporation</span>
</a>
</li>
<li
id="pr_id_17_2"
class="p-tabmenuitem"
role="presentation"
data-p-highlight="false"
data-p-disabled="false"
data-pc-section="menuitem"
>
<a
href="#"
role="menuitem"
aria-label="External"
tabindex="-1"
class="p-menuitem-link"
data-pc-section="action"
>
<span class="p-menuitem-text" data-pc-section="label">Alliance</span>
</a>
</li>
</ul>
</div> --%>
<.form :let={f} for={@member_form} phx-submit={@live_action}>
<.live_select
field={f[:member_id]}
@@ -339,7 +339,7 @@ defmodule WandererAppWeb.MapRoutesEventHandler do
destination_id
)
end,
max_concurrency: System.schedulers_online(),
max_concurrency: System.schedulers_online() * 4,
on_timeout: :kill_task,
timeout: :timer.minutes(1)
)
@@ -4,6 +4,7 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
require Logger
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler}
alias WandererApp.Utils.EVEUtil
def handle_server_event(
%{
@@ -178,44 +179,69 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
}
} = socket
)
when not is_nil(main_character_id) do
when not is_nil(main_character_id) and not is_nil(solar_system_source) and
not is_nil(solar_system_target) do
with solar_system_source <- get_integer(solar_system_source),
solar_system_target <- get_integer(solar_system_target),
{:ok, source_system} <-
WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{
map_id: map_id,
solar_system_id: solar_system_source
}),
signature <-
source_system when not is_nil(source_system) <-
WandererApp.Map.find_system_by_location(
map_id,
%{solar_system_id: solar_system_source}
),
signature when not is_nil(signature) <-
WandererApp.Api.MapSystemSignature.by_system_id!(source_system.id)
|> Enum.find(fn s -> s.eve_id == signature_eve_id end),
target_system <-
target_system when not is_nil(target_system) <-
WandererApp.Map.find_system_by_location(
map_id,
%{solar_system_id: solar_system_target}
) do
if not is_nil(signature) do
signature
|> WandererApp.Api.MapSystemSignature.update_group!(%{group: "Wormhole"})
|> WandererApp.Api.MapSystemSignature.update_linked_system(%{
linked_system_id: solar_system_target
signature
|> WandererApp.Api.MapSystemSignature.update_group!(%{group: "Wormhole"})
|> WandererApp.Api.MapSystemSignature.update_linked_system(%{
linked_system_id: solar_system_target
})
if is_nil(target_system.linked_sig_eve_id) do
map_id
|> WandererApp.Map.Server.update_system_linked_sig_eve_id(%{
solar_system_id: solar_system_target,
linked_sig_eve_id: signature_eve_id
})
if not is_nil(target_system) &&
is_nil(target_system.linked_sig_eve_id) do
if not is_nil(signature.temporary_name) do
map_id
|> WandererApp.Map.Server.update_system_linked_sig_eve_id(%{
|> WandererApp.Map.Server.update_system_temporary_name(%{
solar_system_id: solar_system_target,
linked_sig_eve_id: signature_eve_id
temporary_name: signature.temporary_name
})
end
if not is_nil(signature.temporary_name) do
map_id
|> WandererApp.Map.Server.update_system_temporary_name(%{
solar_system_id: solar_system_target,
temporary_name: signature.temporary_name
})
signature_time_status =
if not is_nil(signature.custom_info) do
signature.custom_info |> Jason.decode!() |> Map.get("time_status")
else
nil
end
if not is_nil(signature_time_status) do
map_id
|> WandererApp.Map.Server.update_connection_time_status(%{
solar_system_source_id: solar_system_source,
solar_system_target_id: solar_system_target,
time_status: signature_time_status
})
end
signature_ship_size_type = EVEUtil.get_wh_size(signature.type)
if not is_nil(signature_ship_size_type) do
map_id
|> WandererApp.Map.Server.update_connection_ship_size_type(%{
solar_system_source_id: solar_system_source,
solar_system_target_id: solar_system_target,
ship_size_type: signature_ship_size_type
})
end
end
+1 -1
View File
@@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do
@source_url "https://github.com/wanderer-industries/wanderer"
@version "1.77.14"
@version "1.81.5"
def project do
[
@@ -1,7 +1,7 @@
%{
title: "Guide: Systems and Connections API",
author: "Wanderer Team",
cover_image_uri: "/images/news/05-07-systems/api-endpoints.png",
cover_image_uri: "/images/news/03-05-api/swagger-ui.png",
tags: ~w(api map systems connections documentation),
description: "Detailed guide for Wanderer's systems and connections API endpoints, including batch operations, updates, and deletions."
}
@@ -912,4 +912,4 @@ If you have questions about these endpoints or need assistance, please reach out
Fly safe,
**The Wanderer Team**
----
----
+1 -1
View File
@@ -23286,7 +23286,7 @@
"solarSystemID": 31002568,
"statics": [
"U210",
"H296"
"Y790"
],
"systemName": "J005663",
"effectName": null
+58 -47
View File
@@ -15,7 +15,7 @@
"dest": "ls",
"src": ["c2"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "24",
"total_mass": 2000000000,
"name": "A239",
@@ -37,7 +37,7 @@
"dest": "c6",
"src": ["c3", "thera"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "24",
"total_mass": 3000000000,
"name": "A982",
@@ -48,7 +48,7 @@
"dest": "c6",
"src": ["hs"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "48",
"total_mass": 3000000000,
"name": "B041",
@@ -59,7 +59,7 @@
"dest": "hs",
"src": ["c2"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "24",
"total_mass": 2000000000,
"name": "B274",
@@ -81,7 +81,7 @@
"dest": "hs",
"src": ["c6"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "48",
"total_mass": 3000000000,
"name": "B520",
@@ -92,7 +92,7 @@
"dest": "barbican",
"src": ["hs", "ls", "ns", "jove"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 750000000,
"name": "B735",
@@ -136,7 +136,7 @@
"dest": "c3",
"src": ["c4"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "C247",
@@ -169,7 +169,7 @@
"dest": "conflux",
"src": ["hs", "ls", "ns", "jove"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 750000000,
"name": "C414",
@@ -191,7 +191,7 @@
"dest": "c2",
"src": ["c5"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 1000000000,
"name": "D364",
@@ -202,7 +202,7 @@
"dest": "c2",
"src": ["c2", "drifter"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "D382",
@@ -224,7 +224,7 @@
"dest": "hs",
"src": ["c3", "c4-shattered"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "24",
"total_mass": 5000000000,
"name": "D845",
@@ -246,7 +246,7 @@
"dest": "c4",
"src": ["c5"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "E175",
@@ -257,7 +257,7 @@
"dest": "ns",
"src": ["c2"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "E545",
@@ -269,7 +269,7 @@
"src": ["thera"],
"static": true,
"max_mass_per_jump": 1000000000,
"lifetime": "16",
"lifetime": "48",
"total_mass": 3000000000,
"name": "E587",
"respawn": ["static"]
@@ -279,7 +279,7 @@
"dest": "thera",
"src": ["c2", "c3", "c4", "c5", "c6"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 750000000,
"name": "F135",
@@ -323,7 +323,7 @@
"dest": "c2",
"src": ["c6"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "G024",
@@ -356,7 +356,7 @@
"dest": "c5",
"src": ["c4"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "24",
"total_mass": 3000000000,
"name": "H900",
@@ -367,7 +367,7 @@
"dest": "c2",
"src": ["c3", "thera"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "I182",
@@ -396,13 +396,13 @@
"respawn": ["wandering", "reverse"]
},
{
"mass_regen": 500000000,
"mass_regen": 0,
"dest": "ns",
"src": ["c4"],
"static": false,
"max_mass_per_jump": 1800000000,
"max_mass_per_jump": 375000000,
"lifetime": "24",
"total_mass": 5000000000,
"total_mass": 3000000000,
"name": "K329",
"respawn": ["wandering"]
},
@@ -411,7 +411,7 @@
"dest": "ns",
"src": ["c3", "c4-shattered", "c5-shattered", "c6-shattered"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 3000000000,
"name": "K346",
@@ -444,7 +444,7 @@
"dest": "c3",
"src": ["c6"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "L477",
@@ -477,7 +477,7 @@
"dest": "thera",
"src": ["ls"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "M164",
@@ -488,7 +488,7 @@
"dest": "c3",
"src": ["c5"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 1000000000,
"name": "M267",
@@ -539,13 +539,13 @@
"respawn": ["static"]
},
{
"mass_regen": 500000000,
"mass_regen": 0,
"dest": "ls",
"src": ["c4"],
"static": false,
"max_mass_per_jump": 2000000000,
"max_mass_per_jump": 375000000,
"lifetime": "24",
"total_mass": 3300000000,
"total_mass": 3000000000,
"name": "N290",
"respawn": ["wandering"]
},
@@ -565,7 +565,7 @@
"dest": "c2",
"src": ["c4"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "N766",
@@ -576,7 +576,7 @@
"dest": "c5",
"src": ["c3", "thera"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "24",
"total_mass": 3000000000,
"name": "N770",
@@ -598,7 +598,7 @@
"dest": "c3",
"src": ["c3", "thera"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "N968",
@@ -609,7 +609,7 @@
"dest": "c4",
"src": ["hs", "ls", "ns"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "24",
"total_mass": 1000000000,
"name": "O128",
@@ -620,7 +620,7 @@
"dest": "c3",
"src": ["c2", "drifter"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "O477",
@@ -708,7 +708,7 @@
"dest": "redoubt",
"src": ["hs", "ls", "ns", "jove"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 750000000,
"name": "R259",
@@ -719,7 +719,7 @@
"dest": "c6",
"src": ["c2", "drifter"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "24",
"total_mass": 3000000000,
"name": "R474",
@@ -730,7 +730,7 @@
"dest": "c2",
"src": ["hs", "ls", "ns"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 750000000,
"name": "R943",
@@ -741,7 +741,7 @@
"dest": "hs",
"src": ["c4"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "24",
"total_mass": 3000000000,
"name": "S047",
@@ -774,7 +774,7 @@
"dest": "sentinel",
"src": ["hs", "ls", "ns", "jove"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 750000000,
"name": "S877",
@@ -785,7 +785,7 @@
"dest": "c4",
"src": ["c3", "thera"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "T405",
@@ -807,7 +807,7 @@
"dest": "ls",
"src": ["c3", "c4-shattered", "c5-shattered"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "24",
"total_mass": 3000000000,
"name": "U210",
@@ -840,7 +840,7 @@
"dest": "c6",
"src": ["c4"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "24",
"total_mass": 3000000000,
"name": "U574",
@@ -884,7 +884,7 @@
"dest": "ls",
"src": ["thera"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "V898",
@@ -906,7 +906,7 @@
"dest": "vidette",
"src": ["hs", "ls", "ns", "jove"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 750000000,
"name": "V928",
@@ -939,7 +939,7 @@
"dest": "c3",
"src": ["hs", "ls", "ns"],
"static": false,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "24",
"total_mass": 1000000000,
"name": "X702",
@@ -950,7 +950,7 @@
"dest": "c4",
"src": ["c4"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "X877",
@@ -961,7 +961,7 @@
"dest": "c4",
"src": ["c2", "drifter"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "Y683",
@@ -1016,7 +1016,7 @@
"dest": "c4",
"src": ["c6"],
"static": true,
"max_mass_per_jump": 300000000,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"total_mass": 2000000000,
"name": "Z457",
@@ -1044,6 +1044,17 @@
"name": "Z971",
"respawn": ["wandering"]
},
{
"mass_regen": 0,
"dest": "ls",
"src": ["c1", "c2", "c3", "c4", "c5", "c6"],
"static": false,
"max_mass_per_jump": 62000000,
"lifetime": "24",
"total_mass": 100000000,
"name": "J492",
"respawn": ["wandering", "reverse"]
},
{
"mass_regen": null,
"dest": null,
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 977 B

+33
View File
@@ -0,0 +1,33 @@
{
"name": "Wanderer",
"short_name": "Wanderer",
"icons": [
{
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#171717",
"background_color": "#171717",
"display": "standalone",
"start_url": "/",
"screenshots": [
{
"src": "web-app-manifest.webp",
"sizes": "720x1280",
"type": "image/webp"
},
{
"src": "web-app-manifest-wide.webp",
"sizes": "1280x720",
"type": "image/webp",
"form_factor": "wide"
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

@@ -127,6 +127,31 @@ defmodule WandererAppWeb.MapSystemAPIControllerSuccessTest do
assert updated_system["position_y"] == 400.0
end
test "UPDATE: successfully updates custom_name", %{conn: conn, map: map} do
system =
insert(:map_system, %{
map_id: map.id,
solar_system_id: 30_000_142,
name: "Jita",
position_x: 100,
position_y: 200
})
update_params = %{
"custom_name" => "My Trade Hub"
}
conn = put(conn, ~p"/api/maps/#{map.slug}/systems/#{system.id}", update_params)
response = json_response(conn, 200)
assert %{
"data" => updated_system
} = response
assert updated_system["custom_name"] == "My Trade Hub"
end
test "DELETE: successfully deletes a system", %{conn: conn, map: map} do
system =
insert(:map_system, %{