Compare commits

..

17 Commits

Author SHA1 Message Date
CI
c1ecd3690e chore: release version v1.21.0
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-24 15:55:38 +00:00
Aleksei Chichenkov
3250fe1ec6 Merge pull request #74 from wanderer-industries/dessign-issues-2
feat(Map): add new gate design, change EOL placement
2024-11-24 18:55:12 +03:00
achichenkov
48e8cd93b9 feat(Map): add new gate design, change EOL placement 2024-11-24 18:30:40 +03:00
CI
afacbb16b6 chore: release version v1.20.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-22 12:42:02 +00:00
Dmitry Popov
dfad127f32 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-22 13:41:35 +01:00
Dmitry Popov
300c1b5a18 chore: release version v1.19.3 2024-11-22 13:41:30 +01:00
CI
bb38e1710b chore: release version v1.20.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-22 12:27:53 +00:00
Dmitry Popov
0857a82de5 feat(Core): Add connection type for Gates, add new Update logic
fixes #73
2024-11-22 13:27:17 +01:00
CI
da5afcc91c chore: release version v1.19.3
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-20 14:09:27 +00:00
Dmitry Popov
0002979fda fix(Core): Fix adding systems on splash (#71)
* fix(Core): Fix adding systems on splash
2024-11-20 18:08:59 +04:00
CI
080af16d41 chore: release version v1.19.2
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-19 21:27:05 +00:00
Dmitry Popov
d03a0b7083 chore: release version v1.19.1 2024-11-19 22:26:22 +01:00
CI
5ba21f5386 chore: release version v1.19.1
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-19 16:07:12 +00:00
Dmitry Popov
10eeae5295 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-19 17:06:39 +01:00
Dmitry Popov
a5bead15d0 chore: release version v1.18.1 2024-11-19 17:06:35 +01:00
CI
0de674adde chore: release version v1.19.0 2024-11-19 14:22:53 +00:00
Dmitry Popov
1db65965d0 feat(Signatures): Add user setting to show Inserted time in a separate column 2024-11-19 15:22:16 +01:00
51 changed files with 2264 additions and 739 deletions

View File

@@ -2,6 +2,59 @@
<!-- changelog -->
## [v1.21.0](https://github.com/wanderer-industries/wanderer/compare/v1.20.1...v1.21.0) (2024-11-24)
### Features:
* Map: add new gate design, change EOL placement
## [v1.20.1](https://github.com/wanderer-industries/wanderer/compare/v1.20.0...v1.20.1) (2024-11-22)
## [v1.20.0](https://github.com/wanderer-industries/wanderer/compare/v1.19.3...v1.20.0) (2024-11-22)
### Features:
* Core: Add connection type for Gates, add new Update logic
## [v1.19.3](https://github.com/wanderer-industries/wanderer/compare/v1.19.2...v1.19.3) (2024-11-20)
### Bug Fixes:
* Core: Fix adding systems on splash (#71)
* Core: Fix adding systems on splash
## [v1.19.2](https://github.com/wanderer-industries/wanderer/compare/v1.19.1...v1.19.2) (2024-11-19)
## [v1.19.1](https://github.com/wanderer-industries/wanderer/compare/v1.19.0...v1.19.1) (2024-11-19)
## [v1.19.0](https://github.com/wanderer-industries/wanderer/compare/v1.18.1...v1.19.0) (2024-11-19)
### Features:
* Signatures: Add user setting to show Inserted time in a separate column
## [v1.18.1](https://github.com/wanderer-industries/wanderer/compare/v1.18.0...v1.18.1) (2024-11-17)

View File

@@ -466,3 +466,407 @@ body {
transform: rotate(-360deg);
}
}
/* Map refresh */
.socket {
scale: 0.5;
width: 150px;
height: 150px;
left: 50%;
/* margin-left: -75px; */
top: 50%;
/* margin-top: -50px; */
}
.hex-brick {
background: #000;
width: 30px;
height: 17px;
position: absolute;
top: 5px;
animation-name: fade;
animation-duration: 2s;
animation-iteration-count: infinite;
-webkit-animation-name: fade;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
}
.hex-brick--active {
animation-name: fade-active;
-webkit-animation-name: fade-active;
}
.h2 {
transform: rotate(60deg);
-webkit-transform: rotate(60deg);
}
.h3 {
transform: rotate(-60deg);
-webkit-transform: rotate(-60deg);
}
.gel {
height: 30px;
width: 30px;
transition: all 0.3s;
-webkit-transition: all 0.3s;
position: absolute;
top: 50%;
left: 50%;
}
.center-gel {
margin-left: -15px;
margin-top: -15px;
animation-name: pulse-version;
animation-duration: 2s;
animation-iteration-count: infinite;
-webkit-animation-name: pulse-version;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
}
.c1 {
margin-left: -47px;
margin-top: -15px;
}
.c2 {
margin-left: -31px;
margin-top: -43px;
}
.c3 {
margin-left: 1px;
margin-top: -43px;
}
.c4 {
margin-left: 17px;
margin-top: -15px;
}
.c5 {
margin-left: -31px;
margin-top: 13px;
}
.c6 {
margin-left: 1px;
margin-top: 13px;
}
.c7 {
margin-left: -63px;
margin-top: -43px;
}
.c8 {
margin-left: 33px;
margin-top: -43px;
}
.c9 {
margin-left: -15px;
margin-top: 41px;
}
.c10 {
margin-left: -63px;
margin-top: 13px;
}
.c11 {
margin-left: 33px;
margin-top: 13px;
}
.c12 {
margin-left: -15px;
margin-top: -71px;
}
.c13 {
margin-left: -47px;
margin-top: -71px;
}
.c14 {
margin-left: 17px;
margin-top: -71px;
}
.c15 {
margin-left: -47px;
margin-top: 41px;
}
.c16 {
margin-left: 17px;
margin-top: 41px;
}
.c17 {
margin-left: -79px;
margin-top: -15px;
}
.c18 {
margin-left: 49px;
margin-top: -15px;
}
.c19 {
margin-left: -63px;
margin-top: -99px;
}
.c20 {
margin-left: 33px;
margin-top: -99px;
}
.c21 {
margin-left: 1px;
margin-top: -99px;
}
.c22 {
margin-left: -31px;
margin-top: -99px;
}
.c23 {
margin-left: -63px;
margin-top: 69px;
}
.c24 {
margin-left: 33px;
margin-top: 69px;
}
.c25 {
margin-left: 1px;
margin-top: 69px;
}
.c26 {
margin-left: -31px;
margin-top: 69px;
}
.c27 {
margin-left: -79px;
margin-top: -15px;
}
.c28 {
margin-left: -95px;
margin-top: -43px;
}
.c29 {
margin-left: -95px;
margin-top: 13px;
}
.c30 {
margin-left: 49px;
margin-top: 41px;
}
.c31 {
margin-left: -79px;
margin-top: -71px;
}
.c32 {
margin-left: -111px;
margin-top: -15px;
}
.c33 {
margin-left: 65px;
margin-top: -43px;
}
.c34 {
margin-left: 65px;
margin-top: 13px;
}
.c35 {
margin-left: -79px;
margin-top: 41px;
}
.c36 {
margin-left: 49px;
margin-top: -71px;
}
.c37 {
margin-left: 81px;
margin-top: -15px;
}
.r1 {
animation-name: pulse-version;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.2s;
-webkit-animation-name: pulse-version;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.2s;
}
.r2 {
animation-name: pulse-version;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.4s;
-webkit-animation-name: pulse-version;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.4s;
}
.r3 {
animation-name: pulse-version;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.6s;
-webkit-animation-name: pulse-version;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.6s;
}
.r1 > .hex-brick {
animation-name: fade;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.2s;
-webkit-animation-name: fade;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.2s;
}
.r1 > .hex-brick--active {
animation-name: fade-active;
-webkit-animation-name: fade-active;
}
.r2 > .hex-brick {
animation-name: fade;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.4s;
-webkit-animation-name: fade;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.4s;
}
.r2 > .hex-brick--active {
animation-name: fade-active;
-webkit-animation-name: fade-active;
}
.r3 > .hex-brick {
animation-name: fade;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.6s;
-webkit-animation-name: fade;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.6s;
}
.r3 > .hex-brick--active {
animation-name: fade-active;
-webkit-animation-name: fade-active;
}
@keyframes pulse-version {
0% {
-webkit-transform: scale(1);
transform: scale(1);
}
50% {
-webkit-transform: scale(0.01);
transform: scale(0.01);
}
100% {
-webkit-transform: scale(1);
transform: scale(1);
}
}
@keyframes fade {
0% {
background: #09d0e2;
}
50% {
background: #8ae6ee;
}
100% {
background: #09d0e2;
}
}
@keyframes fade-active {
0% {
background: #ff52d9;
}
50% {
background: #ff52d9;
}
100% {
background: #ff52d9;
}
}
@-webkit-keyframes pulse {
0% {
-webkit-transform: scale(1);
transform: scale(1);
}
50% {
-webkit-transform: scale(0.01);
transform: scale(0.01);
}
100% {
-webkit-transform: scale(1);
transform: scale(1);
}
}
@-webkit-keyframes fade {
0% {
background: #abf8ff;
}
50% {
background: #389ca6;
}
100% {
background: #abf8ff;
}
}
/* Map refresh END */

View File

@@ -12,8 +12,6 @@ import ReactFlow, {
OnSelectionChangeFunc,
SelectionDragHandler,
SelectionMode,
useEdgesState,
useNodesState,
useReactFlow,
} from 'reactflow';
import 'reactflow/dist/style.css';
@@ -21,7 +19,7 @@ import classes from './Map.module.scss';
import './styles/neon-theme.scss';
import './styles/eve-common.scss';
import { MapProvider, useMapState } from './MapProvider';
import { useMapHandlers, useUpdateNodes } from './hooks';
import { useNodesState, useEdgesState, useMapHandlers, useUpdateNodes } from './hooks';
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
import {
ContextMenuConnection,
@@ -115,8 +113,8 @@ const MapComp = ({
isThickConnections,
}: MapCompProps) => {
const { getNode } = useReactFlow();
const [nodes, , onNodesChange] = useNodesState<SolarSystemRawType>(initialNodes);
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>[]>(initialEdges);
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges);
useMapHandlers(refn, onSelectionChange);
useUpdateNodes(nodes);

View File

@@ -3,7 +3,7 @@ import { ContextMenu } from 'primereact/contextmenu';
import { PrimeIcons } from 'primereact/api';
import { MenuItem } from 'primereact/menuitem';
import { Edge } from '@reactflow/core/dist/esm/types/edges';
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import clsx from 'clsx';
import classes from './ContextMenuConnection.module.scss';
import { MASS_STATE_NAMES, MASS_STATE_NAMES_ORDER } from '@/hooks/Mapper/components/map/constants.ts';
@@ -35,36 +35,49 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
}
const isFrigateSize = edge.data?.ship_size_type === ShipSizeStatus.small;
const isWormhole = edge.data?.type !== ConnectionType.gate;
return [
{
label: `EOL`,
className: clsx({
[classes.ConnectionTimeEOL]: edge.data?.time_status === TimeStatus.eol,
}),
icon: PrimeIcons.CLOCK,
command: onChangeTimeState,
},
{
label: `Frigate`,
className: clsx({
[classes.ConnectionFrigate]: isFrigateSize,
}),
icon: PrimeIcons.CLOUD,
command: () =>
onChangeShipSizeStatus(
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.normal : ShipSizeStatus.small,
),
},
{
label: `Save mass`,
className: clsx({
[classes.ConnectionSave]: edge.data?.locked,
}),
icon: PrimeIcons.LOCK,
command: () => onToggleMassSave(!edge.data?.locked),
},
...(!isFrigateSize
...(isWormhole
? [
{
label: `EOL`,
className: clsx({
[classes.ConnectionTimeEOL]: edge.data?.time_status === TimeStatus.eol,
}),
icon: PrimeIcons.CLOCK,
command: onChangeTimeState,
},
]
: []),
...(isWormhole
? [
{
label: `Frigate`,
className: clsx({
[classes.ConnectionFrigate]: isFrigateSize,
}),
icon: PrimeIcons.CLOUD,
command: () =>
onChangeShipSizeStatus(
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.normal : ShipSizeStatus.small,
),
},
]
: []),
...(isWormhole
? [
{
label: `Save mass`,
className: clsx({
[classes.ConnectionSave]: edge.data?.locked,
}),
icon: PrimeIcons.LOCK,
command: () => onToggleMassSave(!edge.data?.locked),
},
]
: []),
...(isWormhole && !isFrigateSize
? [
{
label: `Mass status`,

View File

@@ -4,7 +4,7 @@ import { ContextMenu } from 'primereact/contextmenu';
import { useMapState } from '../../MapProvider.tsx';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { Edge } from '@reactflow/core/dist/esm/types/edges';
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
export const useContextMenuConnectionHandlers = () => {
@@ -47,6 +47,23 @@ export const useContextMenuConnectionHandlers = () => {
setEdge(undefined);
};
const onChangeType = useCallback((type: ConnectionType) => {
const { edge, outCommand } = ref.current;
if (!edge) {
return;
}
outCommand({
type: OutCommand.updateConnectionType,
data: {
source: edge.source,
target: edge.target,
value: type,
},
});
}, []);
const onChangeMassState = useCallback((status: MassState) => {
const { edge, outCommand } = ref.current;
@@ -118,6 +135,7 @@ export const useContextMenuConnectionHandlers = () => {
contextMenuRef,
onDeleteConnection,
onChangeTimeState,
onChangeType,
onChangeMassState,
onChangeShipSizeStatus,
onToggleMassSave,

View File

@@ -24,6 +24,10 @@
stroke: #d4f0ff;
}
&.Gate {
stroke: #1c1e15;
}
&.Hovered {
stroke: #4e5d6c;
stroke-width: 2px;
@@ -76,6 +80,11 @@
stroke-width: 6px;
}
}
&.Gate {
stroke: #9aff40;
}
}
.ClickPath {

View File

@@ -1,10 +1,10 @@
import { useCallback, useMemo, useState } from 'react';
import classes from './SolarSystemEdge.module.scss';
import { EdgeLabelRenderer, EdgeProps, getBezierPath, Position, useStore } from 'reactflow';
import { EdgeLabelRenderer, EdgeProps, getBezierPath, getSmoothStepPath, Position, useStore } from 'reactflow';
import { getEdgeParams } from '@/hooks/Mapper/components/map/utils.ts';
import clsx from 'clsx';
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { PrimeIcons } from 'primereact/api';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
@@ -33,6 +33,7 @@ const MAP_OFFSETS: Record<string, { x: number; y: number }> = {
export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }: EdgeProps<SolarSystemConnection>) => {
const sourceNode = useStore(useCallback(store => store.nodeInternals.get(source), [source]));
const targetNode = useStore(useCallback(store => store.nodeInternals.get(target), [target]));
const isWormhole = data?.type !== ConnectionType.gate;
const {
data: { isThickConnections },
@@ -45,7 +46,9 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
const offset = isThickConnections ? MAP_OFFSETS_TICK[targetPos] : MAP_OFFSETS[targetPos];
const [edgePath, labelX, labelY] = getBezierPath({
const method = isWormhole ? getBezierPath : getSmoothStepPath;
const [edgePath, labelX, labelY] = method({
sourceX: sx - offset.x,
sourceY: sy - offset.y,
sourcePosition: sourcePos,
@@ -53,8 +56,9 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
targetX: tx + offset.x,
targetY: ty + offset.y,
});
return [edgePath, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos];
}, [isThickConnections, sourceNode, targetNode]);
}, [isThickConnections, sourceNode, targetNode, isWormhole]);
if (!sourceNode || !targetNode || !data) {
return null;
@@ -66,8 +70,9 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
id={`back_${id}`}
className={clsx(classes.EdgePathBack, {
[classes.Tick]: isThickConnections,
[classes.TimeCrit]: data.time_status === TimeStatus.eol,
[classes.TimeCrit]: isWormhole && data.time_status === TimeStatus.eol,
[classes.Hovered]: hovered,
[classes.Gate]: !isWormhole,
})}
d={path}
markerEnd={markerEnd}
@@ -78,9 +83,10 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
className={clsx(classes.EdgePathFront, {
[classes.Tick]: isThickConnections,
[classes.Hovered]: hovered,
[classes.MassVerge]: data.mass_status === MassState.verge,
[classes.MassHalf]: data.mass_status === MassState.half,
[classes.Frigate]: data.ship_size_type === ShipSizeStatus.small,
[classes.MassVerge]: isWormhole && data.mass_status === MassState.verge,
[classes.MassHalf]: isWormhole && data.mass_status === MassState.half,
[classes.Frigate]: isWormhole && data.ship_size_type === ShipSizeStatus.small,
[classes.Gate]: !isWormhole,
})}
d={path}
markerEnd={markerEnd}
@@ -120,7 +126,7 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
}}
>
{data.locked && (
{isWormhole && data.locked && (
<WdTooltipWrapper
content="Save mass"
className={clsx(

View File

@@ -1,4 +1,4 @@
import { MassState } from '@/hooks/Mapper/types';
import { ConnectionType, MassState } from '@/hooks/Mapper/types';
export enum SOLAR_SYSTEM_CLASS_IDS {
ccp1 = -1,
@@ -712,6 +712,13 @@ export const STATUS_CLASSES: Record<number, string> = {
[STATUSES.dangerous]: 'eve-system-status-dangerous',
};
export const TYPE_NAMES_ORDER = [ConnectionType.wormhole, ConnectionType.gate];
export const TYPE_NAMES = {
[ConnectionType.wormhole]: 'Wormhole',
[ConnectionType.gate]: 'Gate',
};
export const MASS_STATE_NAMES_ORDER = [MassState.verge, MassState.half, MassState.normal];
export const MASS_STATE_NAMES = {

View File

@@ -12,6 +12,7 @@ export const useMapAddSystems = () => {
return useCallback((systems: CommandAddSystems) => {
const { rf } = ref.current;
const nodes = rf.getNodes();
const prepared: Node[] = systems.filter(x => !nodes.some(y => x.id === y.id)).map(convertSystem2Node);
rf.addNodes(prepared);
}, []);

View File

@@ -1,2 +1,3 @@
export * from './useMapHandlers';
export * from './useUpdateNodes';
export * from './useNodesEdgesState';

View File

@@ -112,13 +112,17 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
connections: [],
});
selectSystem(systemId as CommandSelectSystem);
}, 100);
}, 500);
break;
case Commands.routes:
// do nothing here
break;
case Commands.signaturesUpdated:
// do nothing here
break;
case Commands.linkSignatureToSystem:
// do nothing here
break;

View File

@@ -0,0 +1,36 @@
import { useState, useCallback, type Dispatch, type SetStateAction } from 'react';
import { applyNodeChanges, applyEdgeChanges } from '../utils/changes';
import { OnNodesChange, Edge, OnEdgesChange, Node } from 'reactflow';
/**
* Hook for managing the state of nodes - should only be used for prototyping / simple use cases.
*
* @public
* @param initialNodes
* @returns an array [nodes, setNodes, onNodesChange]
*/
export function useNodesState<NodeType extends Node>(
initialNodes: NodeType[],
): [NodeType[], Dispatch<SetStateAction<NodeType[]>>, OnNodesChange] {
const [nodes, setNodes] = useState(initialNodes);
const onNodesChange: OnNodesChange = useCallback(changes => setNodes(nds => applyNodeChanges(changes, nds)), []);
return [nodes, setNodes, onNodesChange];
}
/**
* Hook for managing the state of edges - should only be used for prototyping / simple use cases.
*
* @public
* @param initialEdges
* @returns an array [edges, setEdges, onEdgesChange]
*/
export function useEdgesState<EdgeType extends Edge = Edge>(
initialEdges: EdgeType[],
): [EdgeType[], Dispatch<SetStateAction<EdgeType[]>>, OnEdgesChange] {
const [edges, setEdges] = useState(initialEdges);
const onEdgesChange: OnEdgesChange = useCallback(changes => setEdges(eds => applyEdgeChanges(changes, eds)), []);
return [edges, setEdges, onEdgesChange];
}

View File

@@ -0,0 +1,174 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { EdgeChange, NodeChange, Node, Edge } from 'reactflow';
// This function applies changes to nodes or edges that are triggered by React Flow internally.
// When you drag a node for example, React Flow will send a position change update.
// This function then applies the changes and returns the updated elements.
function applyChanges(changes: any[], elements: any[]): any[] {
// we need this hack to handle the setNodes and setEdges function of the useReactFlow hook for controlled flows
if (changes.some(c => c.type === 'reset')) {
return changes.filter(c => c.type === 'reset').map(c => c.item);
}
const updatedElements: any[] = [];
// By storing a map of changes for each element, we can a quick lookup as we
// iterate over the elements array!
const changesMap = new Map<any, any[]>();
const addItemChanges: any[] = [];
for (const change of changes) {
if (change.type === 'add') {
addItemChanges.push(change);
continue;
} else if (change.type === 'remove' || change.type === 'replace') {
// For a 'remove' change we can safely ignore any other changes queued for
// the same element, it's going to be removed anyway!
changesMap.set(change.id, [change]);
} else {
const elementChanges = changesMap.get(change.id);
if (elementChanges) {
// If we have some changes queued already, we can do a mutable update of
// that array and save ourselves some copying.
elementChanges.push(change);
} else {
changesMap.set(change.id, [change]);
}
}
}
for (const element of elements) {
const changes = changesMap.get(element.id);
// When there are no changes for an element we can just push it unmodified,
// no need to copy it.
if (!changes) {
updatedElements.push(element);
continue;
}
// If we have a 'remove' change queued, it'll be the only change in the array
if (changes[0].type === 'remove') {
continue;
}
if (changes[0].type === 'replace') {
updatedElements.push({ ...changes[0].item });
continue;
}
// For other types of changes, we want to start with a shallow copy of the
// object so React knows this element has changed. Sequential changes will
/// each _mutate_ this object, so there's only ever one copy.
const updatedElement = { ...element };
for (const change of changes) {
applyChange(change, updatedElement);
}
updatedElements.push(updatedElement);
}
// we need to wait for all changes to be applied before adding new items
// to be able to add them at the correct index
if (addItemChanges.length) {
addItemChanges.forEach(change => {
if (change.index !== undefined) {
updatedElements.splice(change.index, 0, { ...change.item });
} else {
updatedElements.push({ ...change.item });
}
});
}
return updatedElements;
}
// Applies a single change to an element. This is a *mutable* update.
function applyChange(change: any, element: any): any {
switch (change.type) {
case 'select': {
element.selected = change.selected;
break;
}
case 'position': {
if (typeof change.position !== 'undefined') {
element.position = change.position;
}
if (typeof change.dragging !== 'undefined') {
element.dragging = change.dragging;
}
break;
}
case 'dimensions': {
if (typeof change.dimensions !== 'undefined') {
element.measured ??= {};
element.measured.width = change.dimensions.width;
element.measured.height = change.dimensions.height;
if (change.setAttributes) {
element.width = change.dimensions.width;
element.height = change.dimensions.height;
}
}
if (typeof change.resizing === 'boolean') {
element.resizing = change.resizing;
}
break;
}
}
}
/**
* Drop in function that applies node changes to an array of nodes.
* @public
* @remarks Various events on the <ReactFlow /> component can produce an {@link NodeChange} that describes how to update the edges of your flow in some way.
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
* @param changes - Array of changes to apply
* @param nodes - Array of nodes to apply the changes to
* @returns Array of updated nodes
* @example
* const onNodesChange = useCallback(
(changes) => {
setNodes((oldNodes) => applyNodeChanges(changes, oldNodes));
},
[setNodes],
);
return (
<ReactFLow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
);
*/
export function applyNodeChanges<NodeType extends Node = Node>(changes: NodeChange[], nodes: NodeType[]): NodeType[] {
return applyChanges(changes, nodes) as NodeType[];
}
/**
* Drop in function that applies edge changes to an array of edges.
* @public
* @remarks Various events on the <ReactFlow /> component can produce an {@link EdgeChange} that describes how to update the edges of your flow in some way.
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
* @param changes - Array of changes to apply
* @param edges - Array of edge to apply the changes to
* @returns Array of updated edges
* @example
* const onEdgesChange = useCallback(
(changes) => {
setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges));
},
[setEdges],
);
return (
<ReactFlow nodes={nodes} edges={edges} onEdgesChange={onEdgesChange} />
);
*/
export function applyEdgeChanges<EdgeType extends Edge = Edge>(changes: EdgeChange[], edges: EdgeType[]): EdgeType[] {
return applyChanges(changes, edges) as EdgeType[];
}

View File

@@ -48,6 +48,7 @@ const restoreWindowsFromLS = (): WidgetGridItem[] => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const raw = localStorage.getItem(SESSION_KEY.windows);
if (!raw) {
console.warn('No windows found in local storage!!');
return DEFAULT_WINDOWS;
}
@@ -63,7 +64,7 @@ const restoreWindowsFromLS = (): WidgetGridItem[] => {
};
export const MapInterface = () => {
const [items, setItems] = useState<WidgetGridItem[]>(restoreWindowsFromLS());
const [items, setItems] = useState<WidgetGridItem[]>(restoreWindowsFromLS);
return (
<WidgetsGrid

View File

@@ -20,10 +20,12 @@ import { PrimeIcons } from 'primereact/api';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v3';
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v4_1';
export const SHOW_DESCRIPTION_COLUMN_SETTING = 'show_description_column_setting';
export const SHOW_INSERTED_COLUMN_SETTING = 'show_inserted_column_setting';
const settings: Setting[] = [
{ key: SHOW_INSERTED_COLUMN_SETTING, name: 'Show Inserted Column', value: false, isFilter: false },
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: false, isFilter: false },
{ key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true, isFilter: true },
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true, isFilter: true },

View File

@@ -25,7 +25,8 @@ import {
renderDescription,
renderIcon,
renderInfoColumn,
renderTimeLeft,
renderInsertedTimeLeft,
renderUpdatedTimeLeft,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
import useLocalStorageState from 'use-local-storage-state';
import { PrimeIcons } from 'primereact/api';
@@ -33,7 +34,10 @@ import { SignatureSettings } from '@/hooks/Mapper/components/mapRootContent/comp
import { useMapEventListener } from '@/hooks/Mapper/events';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { COSMIC_SIGNATURE } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
import { SHOW_DESCRIPTION_COLUMN_SETTING } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures';
import {
SHOW_DESCRIPTION_COLUMN_SETTING,
SHOW_INSERTED_COLUMN_SETTING,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures';
type SystemSignaturesSortSettings = {
sortField: string;
sortOrder: SortOrder;
@@ -96,6 +100,11 @@ export const SystemSignaturesContent = ({
[settings],
);
const showInsertedColumn = useMemo(
() => settings.find(s => s.key === SHOW_INSERTED_COLUMN_SETTING)?.value,
[settings],
);
const filteredSignatures = useMemo(() => {
return signatures
.filter(x => {
@@ -368,12 +377,24 @@ export const SystemSignaturesContent = ({
sortable
></Column>
)}
{showInsertedColumn && (
<Column
field="inserted_at"
header="Inserted"
dataType="date"
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
body={renderInsertedTimeLeft}
sortable
></Column>
)}
<Column
field="updated_at"
header="Updated"
dataType="date"
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
body={renderTimeLeft}
body={renderUpdatedTimeLeft}
sortable
></Column>

View File

@@ -19,6 +19,8 @@ export const getActualSigs = (
const isNeedUpgrade = getState(GROUPS_LIST, newSig) > getState(GROUPS_LIST, oldSig);
if (isNeedUpgrade) {
updated.push({ ...oldSig, group: newSig.group, name: newSig.name });
} else {
updated.push({ ...oldSig });
}
} else {
if (!updateOnly) {

View File

@@ -1,6 +1,7 @@
export * from './renderIcon';
export * from './renderDescription';
export * from './renderName';
export * from './renderTimeLeft';
export * from './renderInsertedTimeLeft';
export * from './renderUpdatedTimeLeft';
export * from './renderLinkedSystem';
export * from './renderInfoColumn';

View File

@@ -0,0 +1,10 @@
import { SystemSignature } from '@/hooks/Mapper/types';
import { TimeLeft } from '@/hooks/Mapper/components/ui-kit';
export const renderInsertedTimeLeft = (row: SystemSignature) => {
return (
<div className="flex w-full items-center">
<TimeLeft cDate={row.inserted_at ? new Date(row.inserted_at) : undefined} />
</div>
);
};

View File

@@ -1,7 +1,7 @@
import { SystemSignature } from '@/hooks/Mapper/types';
import { TimeLeft } from '@/hooks/Mapper/components/ui-kit';
export const renderTimeLeft = (row: SystemSignature) => {
export const renderUpdatedTimeLeft = (row: SystemSignature) => {
return (
<div className="flex w-full items-center">
<TimeLeft cDate={row.updated_at ? new Date(row.updated_at) : undefined} />

View File

@@ -5,12 +5,14 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import clsx from 'clsx';
import {
ConnectionType,
ConnectionOutput,
ConnectionInfoOutput,
OutCommand,
Passage,
SolarSystemConnection,
} from '@/hooks/Mapper/types';
import { PassageCard } from './PassageCard';
import { InfoDrawer, SystemView } from '@/hooks/Mapper/components/ui-kit';
import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts';
@@ -75,8 +77,12 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
return connections.find(x => x.source === selectedConnection.source && x.target === selectedConnection.target);
}, [connections, selectedConnection]);
const isWormhole = useMemo(() => {
return cnInfo?.type !== ConnectionType.gate;
}, [cnInfo]);
const [passages, setPassages] = useState<Passage[]>([]);
const [info, setInfo] = useState<ConnectionInfoOutput>(null);
const [info, setInfo] = useState<ConnectionInfoOutput | null>(null);
const loadInfo = useCallback(
async (connection: SolarSystemConnection) => {
@@ -135,7 +141,7 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
>
<div className={clsx(classes.SidebarContent, '')}>
{/* Connection Info */}
<div className="px-2 pb-3 flex flex-col gap-2">
<div className="px-2 flex flex-col gap-2">
{/* Connection Info Row */}
<InfoDrawer title="Connection" rightSide>
<div className="flex justify-end gap-2 items-center">
@@ -153,14 +159,25 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
</div>
</InfoDrawer>
{/* Connection Info Row */}
<InfoDrawer title="Approximate mass of passages" rightSide>
{kgToTons(approximateMass)}
</InfoDrawer>
<div className="flex justify-between gap-2">
{/*Left column*/}
<div>
{isWormhole && info?.marl_eol_time && (
<InfoDrawer title="Mark EOL Time">
<TimeAgo timestamp={info.marl_eol_time} />
</InfoDrawer>
)}
</div>
<InfoDrawer title="Mark EOL Time" rightSide>
{info?.marl_eol_time ? <TimeAgo timestamp={info.marl_eol_time} /> : ' unknown '}
</InfoDrawer>
{/*Right column*/}
<div>
{isWormhole && (
<InfoDrawer title="Approximate mass of passages" rightSide>
{kgToTons(approximateMass)}
</InfoDrawer>
)}
</div>
</div>
<div className="flex gap-2"></div>
</div>

View File

@@ -1,6 +1,5 @@
import { Dialog } from 'primereact/dialog';
import { useCallback, useEffect } from 'react';
// import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OutCommand, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { Controller, FormProvider, useForm } from 'react-hook-form';
import {
@@ -25,102 +24,106 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
const { outCommand } = useMapRootState();
const handleShow = async () => {};
const form = useForm<Partial<SystemSignaturePrepared>>({});
const signatureForm = useForm<Partial<SystemSignaturePrepared>>({});
const handleSave = useCallback(async () => {
if (!signatureData) {
return;
}
const handleSave = useCallback(
async (e: any) => {
e?.preventDefault();
if (!signatureData) {
return;
}
const { group, ...values } = form.getValues();
let out = { ...signatureData };
const { group, ...values } = signatureForm.getValues();
let out = { ...signatureData };
switch (group) {
case SignatureGroup.Wormhole:
if (values.linked_system) {
await outCommand({
type: OutCommand.linkSignatureToSystem,
data: {
signature_eve_id: signatureData.eve_id,
solar_system_source: systemId,
solar_system_target: values.linked_system,
},
});
}
switch (group) {
case SignatureGroup.Wormhole:
if (values.linked_system) {
await outCommand({
type: OutCommand.linkSignatureToSystem,
data: {
signature_eve_id: signatureData.eve_id,
solar_system_source: systemId,
solar_system_target: values.linked_system,
},
});
}
if (values.type != null) {
out = { ...out, type: values.type };
}
if (values.type != null) {
out = { ...out, type: values.type };
}
if (signatureData.group !== SignatureGroup.Wormhole) {
out = { ...out, name: '' };
}
if (signatureData.group !== SignatureGroup.Wormhole) {
out = { ...out, name: '' };
}
break;
case SignatureGroup.CosmicSignature:
out = { ...out, type: '', name: '' };
break;
default:
if (values.name != null) {
out = { ...out, name: values.name ?? '' };
}
}
break;
case SignatureGroup.CosmicSignature:
out = { ...out, type: '', name: '' };
break;
default:
if (values.name != null) {
out = { ...out, name: values.name ?? '' };
}
}
if (values.description != null) {
out = { ...out, description: values.description };
}
if (values.description != null) {
out = { ...out, description: values.description };
}
// Note: when type of signature changed from WH to other type - we should drop name
if (
group !== SignatureGroup.Wormhole && // new
signatureData.group === SignatureGroup.Wormhole && // prev
signatureData.linked_system
) {
await outCommand({
type: OutCommand.unlinkSignature,
data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
});
out = { ...out, type: '' };
}
if (group === SignatureGroup.Wormhole && signatureData.linked_system != null && values.linked_system === null) {
await outCommand({
type: OutCommand.unlinkSignature,
data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
});
}
// Note: despite groups have optional type - this will always set
out = { ...out, group: group! };
// Note: when type of signature changed from WH to other type - we should drop name
if (
group !== SignatureGroup.Wormhole && // new
signatureData.group === SignatureGroup.Wormhole && // prev
signatureData.linked_system
) {
await outCommand({
type: OutCommand.unlinkSignature,
data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
type: OutCommand.updateSignatures,
data: {
system_id: systemId,
added: [],
updated: [out],
removed: [],
},
});
out = { ...out, type: '' };
}
if (group === SignatureGroup.Wormhole && signatureData.linked_system != null && values.linked_system === null) {
await outCommand({
type: OutCommand.unlinkSignature,
data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
});
}
// Note: despite groups have optional type - this will always set
out = { ...out, group: group! };
await outCommand({
type: OutCommand.updateSignatures,
data: {
system_id: systemId,
added: [],
updated: [out],
removed: [],
},
});
form.reset();
onHide();
}, [form, onHide, outCommand, signatureData, systemId]);
signatureForm.reset();
onHide();
},
[signatureForm, onHide, outCommand, signatureData, systemId],
);
useEffect(() => {
if (!signatureData) {
form.reset();
signatureForm.reset();
return;
}
const { linked_system, ...rest } = signatureData;
form.reset({
signatureForm.reset({
linked_system: linked_system?.solar_system_id.toString() ?? undefined,
...rest,
});
}, [form, signatureData]);
}, [signatureForm, signatureData]);
return (
<Dialog
@@ -138,32 +141,34 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
}}
>
<SystemsSettingsProvider initialValue={{ systemId }}>
<FormProvider {...form}>
<div className="flex flex-col gap-2 justify-between">
<div className="w-full flex flex-col gap-1 p-1 min-h-[150px]">
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Group:</span>
<SignatureGroupSelect name="group" />
</label>
<FormProvider {...signatureForm}>
<form onSubmit={handleSave}>
<div className="flex flex-col gap-2 justify-between">
<div className="w-full flex flex-col gap-1 p-1 min-h-[150px]">
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Group:</span>
<SignatureGroupSelect name="group" />
</label>
<SignatureGroupContent />
<SignatureGroupContent />
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Description:</span>
<Controller
name="description"
control={form.control}
render={({ field }) => (
<InputText placeholder="Type description" value={field.value} onChange={field.onChange} />
)}
/>
</label>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Description:</span>
<Controller
name="description"
control={signatureForm.control}
render={({ field }) => (
<InputText placeholder="Type description" value={field.value} onChange={field.onChange} />
)}
/>
</label>
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
</div>
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
</div>
</div>
</form>
</FormProvider>
</SystemsSettingsProvider>
</Dialog>

View File

@@ -1,6 +1,7 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useRef } from 'react';
import { CommandAddSystems, CommandRemoveSystems, CommandUpdateSystems } from '@/hooks/Mapper/types';
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
export const useCommandsSystems = () => {
const {
@@ -8,40 +9,52 @@ export const useCommandsSystems = () => {
data: { systems },
} = useMapRootState();
const ref = useRef({ systems, update });
ref.current = { systems, update };
const { addSystemStatic } = useLoadSystemStatic({ systems: [] });
const addSystems = useCallback(
(addSystems: CommandAddSystems) => {
update({
systems: [...ref.current.systems.filter(sys => addSystems.some(x => sys.id !== x.id)), ...addSystems],
});
},
[update],
);
const ref = useRef({ systems, update, addSystemStatic });
ref.current = { systems, update, addSystemStatic };
const addSystems = useCallback((systemsToAdd: CommandAddSystems) => {
const { update, addSystemStatic, systems } = ref.current;
systemsToAdd.forEach(sys => {
if (sys.system_static_info) {
addSystemStatic(sys.system_static_info);
}
});
update(
{
systems: [...systems.filter(sys => !systemsToAdd.some(x => sys.id === x.id)), ...systemsToAdd],
},
true,
);
}, []);
const removeSystems = useCallback((toRemove: CommandRemoveSystems) => {
const { update, systems } = ref.current;
update({
systems: systems.filter(x => !toRemove.includes(parseInt(x.id))),
});
update(
{
systems: systems.filter(x => !toRemove.includes(parseInt(x.id))),
},
true,
);
}, []);
const updateSystems = useCallback(
(systems: CommandUpdateSystems) => {
const out = ref.current.systems.map(current => {
const newSystem = systems.find(x => current.id === x.id);
if (!newSystem) {
return current;
}
const updateSystems = useCallback((updatedSystems: CommandUpdateSystems) => {
const { update, systems } = ref.current;
return newSystem;
});
const out = systems.map(current => {
const newSystem = updatedSystems.find(x => current.id === x.id);
if (!newSystem) {
return current;
}
update({ systems: out });
},
[update],
);
return newSystem;
});
update({ systems: out }, true);
}, []);
return { addSystems, removeSystems, updateSystems };
};

View File

@@ -24,7 +24,7 @@ interface UseLoadSystemStaticProps {
systems: (number | string)[];
}
export const useLoadSystemStatic = ({ systems }: UseLoadSystemStaticProps) => {
export const useLoadSystemStatic = ({ systems = [] }: UseLoadSystemStaticProps) => {
const { outCommand } = useMapRootState();
const [loading, setLoading] = useState(false);
const [lastUpdateKey, setLastUpdateKey] = useState(0);
@@ -51,6 +51,9 @@ export const useLoadSystemStatic = ({ systems }: UseLoadSystemStaticProps) => {
}, []);
useEffect(() => {
if (!systems.length) {
return;
}
loadSystems(systems);
// eslint-disable-next-line
}, [systems]);

View File

@@ -87,6 +87,14 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
mapRoutes(data as CommandRoutes);
break;
case Commands.signaturesUpdated: // USED
// do nothing here
break;
case Commands.linkSignatureToSystem: // USED
// do nothing here
break;
case Commands.centerSystem: // USED
// do nothing here
break;
@@ -95,22 +103,10 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
// do nothing here
break;
// case Commands.linkSignatureToSystem:
// // TODO command data type lost
// // @ts-ignore
// emitMapEvent({ name: Commands.linkSignatureToSystem, data });
// break;
case Commands.killsUpdated:
// do nothing here
break;
// case Commands.signaturesUpdated:
// // TODO command data type lost
// // @ts-ignore
// emitMapEvent({ name: Commands.signaturesUpdated, data });
// break;
default:
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
break;

View File

@@ -1,3 +1,8 @@
export enum ConnectionType {
wormhole,
gate,
}
export enum MassState {
normal,
half,
@@ -32,4 +37,6 @@ export type SolarSystemConnection = {
source: string;
target: string;
type?: ConnectionType;
};

View File

@@ -120,6 +120,7 @@ export enum OutCommand {
getSystemStaticInfos = 'get_system_static_infos',
getConnectionInfo = 'get_connection_info',
updateConnectionTimeStatus = 'update_connection_time_status',
updateConnectionType = 'update_connection_type',
updateConnectionMassStatus = 'update_connection_mass_status',
updateConnectionShipSizeType = 'update_connection_ship_size_type',
updateConnectionLocked = 'update_connection_locked',

View File

@@ -25,5 +25,6 @@ export type SystemSignature = {
group: SignatureGroup;
type: string;
linked_system?: SolarSystemStaticInfoRaw;
inserted_at?: string;
updated_at?: string;
};

View File

@@ -1,7 +1,6 @@
import { RefObject, useCallback } from 'react';
import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts';
import { getQueryVariable } from './utils';
export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRef: RefObject<any>) => {
const handleCommand = useCallback(
@@ -16,10 +15,6 @@ export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRe
);
const handleMapEvent = useCallback(({ type, body }) => {
if (getQueryVariable('debug') === 'true') {
console.log(type, body);
}
handlerRefs.forEach(ref => {
if (!ref.current) {
return;

View File

@@ -4,13 +4,26 @@ export default {
mounted() {
const hook = this;
const button = hook.el.querySelector('.update-button');
const refreshZone = hook.el.querySelector('#refresh-area');
button.addEventListener('click', function () {
const lastVersion = hook.el.dataset.version;
localStorage.setItem(LAST_VERSION_KEY, lastVersion);
window.location.reload();
});
const handleUpdate = function (e: Event) {
const hexBricks = hook.el.querySelectorAll('.hex-brick');
// Add a new class to each element
hexBricks.forEach(el => {
el.classList.add('hex-brick--active');
});
setTimeout(() => {
const lastVersion = hook.el.dataset.version;
localStorage.setItem(LAST_VERSION_KEY, lastVersion);
window.location.reload();
}, 2000);
};
refreshZone.addEventListener('click', handleUpdate);
refreshZone.addEventListener('mouseover', handleUpdate);
this.updated();
},

View File

@@ -13,8 +13,6 @@
},
"dependencies": {
"@formkit/auto-animate": "0.7.0",
"@react-rxjs/core": "^0.10.7",
"@react-rxjs/utils": "^0.9.7",
"@shopify/draggable": "^1.1.3",
"clsx": "^2.1.1",
"daisyui": "^4.11.1",
@@ -34,7 +32,6 @@
"react-hook-form": "^7.53.1",
"react-usestateref": "^1.0.9",
"reactflow": "^11.11.4",
"rxjs": "^7.8.1",
"tailwindcss": "^3.3.6",
"topbar": "^3.0.0",
"use-local-storage-state": "^19.3.1"

View File

@@ -469,19 +469,6 @@
resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz"
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
"@react-rxjs/core@^0.10.7":
version "0.10.7"
resolved "https://registry.npmjs.org/@react-rxjs/core/-/core-0.10.7.tgz"
integrity sha512-dornp8pUs9OcdqFKKRh9+I2FVe21gWufNun6RYU1ddts7kUy9i4Thvl0iqcPFbGY61cJQMAJF7dxixWMSD/A/A==
dependencies:
"@rx-state/core" "0.1.4"
use-sync-external-store "^1.0.0"
"@react-rxjs/utils@^0.9.7":
version "0.9.7"
resolved "https://registry.npmjs.org/@react-rxjs/utils/-/utils-0.9.7.tgz"
integrity sha512-m9CUTdRsglObvUAlYfB24QvN+QH4XqCGEKnCdSILIeOx7mMqSi9TTFp2zrj5XqtMiLnj4ReAdDxrXegLPB73bQ==
"@reactflow/background@11.3.14":
version "11.3.14"
resolved "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz"
@@ -650,11 +637,6 @@
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz#5a2d08b81e8064b34242d5cc9973ef8dd1e60503"
integrity sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==
"@rx-state/core@0.1.4":
version "0.1.4"
resolved "https://registry.npmjs.org/@rx-state/core/-/core-0.1.4.tgz"
integrity sha512-Z+3hjU2xh1HisLxt+W5hlYX/eGSDaXXP+ns82gq/PLZpkXLu0uwcNUh9RLY3Clq4zT+hSsA3vcpIGt6+UAb8rQ==
"@shopify/draggable@^1.1.3":
version "1.1.3"
resolved "https://registry.npmjs.org/@shopify/draggable/-/draggable-1.1.3.tgz"
@@ -3421,13 +3403,6 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
rxjs@^7.8.1:
version "7.8.1"
resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz"
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
dependencies:
tslib "^2.1.0"
safe-array-concat@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz"
@@ -3732,7 +3707,7 @@ ts-interface-checker@^0.1.9:
resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
tslib@^2.1.0, tslib@^2.6.2:
tslib@^2.6.2:
version "2.6.2"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
@@ -3838,7 +3813,7 @@ use-local-storage-state@^19.3.1:
resolved "https://registry.npmjs.org/use-local-storage-state/-/use-local-storage-state-19.3.1.tgz"
integrity sha512-y3Z1dODXvZXZB4qtLDNN8iuXbsYD6TAxz61K58GWB9/yKwrNG9ynI0GzCTHi/Je1rMiyOwMimz0oyFsZn+Kj7Q==
use-sync-external-store@1.2.0, use-sync-external-store@^1.0.0:
use-sync-external-store@1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==

View File

@@ -29,13 +29,16 @@ defmodule WandererApp.Api.MapConnection do
define(:update_ship_size_type, action: :update_ship_size_type)
define(:update_locked, action: :update_locked)
define(:update_custom_info, action: :update_custom_info)
define(:update_type, action: :update_type)
define(:update_wormhole_type, action: :update_wormhole_type)
end
actions do
default_accept [
:map_id,
:solar_system_source,
:solar_system_target
:solar_system_target,
:type
]
defaults [:create, :read, :update, :destroy]
@@ -92,6 +95,14 @@ defmodule WandererApp.Api.MapConnection do
update :update_custom_info do
accept [:custom_info]
end
update :update_type do
accept [:type]
end
update :update_wormhole_type do
accept [:wormhole_type]
end
end
attributes do
@@ -126,6 +137,14 @@ defmodule WandererApp.Api.MapConnection do
allow_nil?(true)
end
# where 0 - Wormhole
# where 1 - Gate
attribute :type, :integer do
default(0)
allow_nil?(true)
end
attribute :wormhole_type, :string
attribute :count_of_passage, :integer do

View File

@@ -12,6 +12,7 @@ defmodule WandererApp.Api.MapSystem do
code_interface do
define(:create, action: :create)
define(:destroy, action: :destroy)
define(:by_id,
get_by: [:id],

View File

@@ -72,7 +72,8 @@ defmodule WandererApp.Api.MapSystemSignature do
:description,
:kind,
:group,
:type
:type,
:updated
]
primary? true
@@ -128,6 +129,8 @@ defmodule WandererApp.Api.MapSystemSignature do
attribute :kind, :string
attribute :group, :string
attribute :updated, :integer
create_timestamp(:inserted_at)
update_timestamp(:updated_at)
end

View File

@@ -195,6 +195,12 @@ defmodule WandererApp.Map.Server do
|> map_pid!
|> GenServer.cast({&Impl.update_connection_time_status/2, [connection_info]})
def update_connection_type(map_id, connection_info) when is_binary(map_id),
do:
map_id
|> map_pid!
|> GenServer.cast({&Impl.update_connection_type/2, [connection_info]})
def update_connection_mass_status(map_id, connection_info) when is_binary(map_id),
do:
map_id

View File

@@ -4,6 +4,8 @@ defmodule WandererApp.Map.Server.Impl do
"""
require Logger
alias WandererApp.Map.Server.ConnectionsImpl
@enforce_keys [
:map_id
]
@@ -15,74 +17,10 @@ defmodule WandererApp.Map.Server.Impl do
map_opts: []
]
# @ccp1 -1
@c1 1
@c2 2
@c3 3
@c4 4
@c5 5
@c6 6
@hs 7
@ls 8
@ns 9
# @ccp2 10
# @ccp3 11
@thera 12
@c13 13
@sentinel 14
@baribican 15
@vidette 16
@conflux 17
@redoubt 18
@a1 19
@a2 20
@a3 21
@a4 22
@a5 23
@ccp4 24
# @pochven 25
# @zarzakh 10100
@jita 30_000_142
@wh_space [
@c1,
@c2,
@c3,
@c4,
@c5,
@c6,
@c13,
@thera,
@sentinel,
@baribican,
@vidette,
@conflux,
@redoubt
]
@known_space [@hs, @ls, @ns]
@prohibited_systems [@jita]
@prohibited_system_classes [
@a1,
@a2,
@a3,
@a4,
@a5,
@ccp4
]
# this class of systems will guaranty that no one real class will take that place
# @unknown 100_100
@systems_cleanup_timeout :timer.minutes(30)
@characters_cleanup_timeout :timer.minutes(1)
@connections_cleanup_timeout :timer.minutes(2)
@connection_time_status_eol 1
@connection_auto_eol_hours 21
@connection_auto_expire_hours 24
@system_auto_expire_minutes 15
@ddrt Application.compile_env(:wanderer_app, :ddrt)
@@ -90,7 +28,6 @@ defmodule WandererApp.Map.Server.Impl do
@pubsub_client Application.compile_env(:wanderer_app, :pubsub_client)
@backup_state_timeout :timer.minutes(1)
@system_inactive_timeout :timer.minutes(15)
@connection_eol_expire_timeout :timer.hours(3) + :timer.minutes(30)
@update_presence_timeout :timer.seconds(1)
@update_characters_timeout :timer.seconds(1)
@update_tracked_characters_timeout :timer.seconds(1)
@@ -410,106 +347,23 @@ defmodule WandererApp.Map.Server.Impl do
state
end
def add_connection(
%{map_id: map_id} = state,
%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id
} = _connection_info
) do
:ok =
maybe_add_connection(
map_id,
%{solar_system_id: solar_system_target_id},
%{
solar_system_id: solar_system_source_id
},
nil
)
defdelegate add_connection(state, connection_info), to: ConnectionsImpl
state
end
defdelegate delete_connection(state, connection_info), to: ConnectionsImpl
def delete_connection(
%{map_id: map_id} = state,
%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id
} = _connection_info
) do
:ok =
maybe_remove_connection(map_id, %{solar_system_id: solar_system_target_id}, %{
solar_system_id: solar_system_source_id
})
defdelegate get_connection_info(state, connection_info), to: ConnectionsImpl
state
end
defdelegate update_connection_time_status(state, connection_update), to: ConnectionsImpl
def get_connection_info(
%{map_id: map_id} = _state,
%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id
} = _connection_info
) do
WandererApp.Map.find_connection(
map_id,
solar_system_source_id,
solar_system_target_id
)
|> case do
{:ok, %{id: connection_id}} ->
connection_mark_eol_time = get_connection_mark_eol_time(map_id, connection_id, nil)
{:ok, %{marl_eol_time: connection_mark_eol_time}}
defdelegate update_connection_type(state, connection_update), to: ConnectionsImpl
_ ->
{:error, :not_found}
end
end
defdelegate update_connection_mass_status(state, connection_update), to: ConnectionsImpl
def update_connection_time_status(
%{map_id: map_id} = state,
connection_update
),
do:
update_connection(state, :update_time_status, [:time_status], connection_update, fn
%{id: connection_id, time_status: time_status} ->
case time_status == @connection_time_status_eol do
true ->
WandererApp.Cache.put(
"map_#{map_id}:conn_#{connection_id}:mark_eol_time",
DateTime.utc_now(),
ttl: @connection_eol_expire_timeout
)
defdelegate update_connection_ship_size_type(state, connection_update), to: ConnectionsImpl
_ ->
WandererApp.Cache.delete("map_#{map_id}:conn_#{connection_id}:mark_eol_time")
end
end)
defdelegate update_connection_locked(state, connection_update), to: ConnectionsImpl
def update_connection_mass_status(
state,
connection_update
),
do: update_connection(state, :update_mass_status, [:mass_status], connection_update)
def update_connection_ship_size_type(
state,
connection_update
),
do: update_connection(state, :update_ship_size_type, [:ship_size_type], connection_update)
def update_connection_locked(
state,
connection_update
),
do: update_connection(state, :update_locked, [:locked], connection_update)
def update_connection_custom_info(
state,
connection_update
),
do: update_connection(state, :update_custom_info, [:custom_info], connection_update)
defdelegate update_connection_custom_info(state, connection_update), to: ConnectionsImpl
def import_settings(%{map_id: map_id} = state, settings, user_id) do
WandererApp.Cache.put(
@@ -742,90 +596,10 @@ defmodule WandererApp.Map.Server.Impl do
end
end
def handle_event(:cleanup_connections, %{map_id: map_id} = state) do
def handle_event(:cleanup_connections, state) do
Process.send_after(self(), :cleanup_connections, @connections_cleanup_timeout)
state =
map_id
|> WandererApp.Map.list_connections!()
|> Enum.filter(fn %{
inserted_at: inserted_at,
solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id
} ->
DateTime.diff(DateTime.utc_now(), inserted_at, :hour) >=
@connection_auto_eol_hours and
is_connection_valid(
:wormholes,
solar_system_source_id,
solar_system_target_id
)
end)
|> Enum.reduce(state, fn %{
solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id
},
state ->
state
|> update_connection_time_status(%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id,
time_status: @connection_time_status_eol
})
end)
state =
map_id
|> WandererApp.Map.list_connections!()
|> Enum.filter(fn %{
id: connection_id,
inserted_at: inserted_at,
solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id
} ->
connection_mark_eol_time = get_connection_mark_eol_time(map_id, connection_id)
reverse_connection =
WandererApp.Map.get_connection(
map_id,
solar_system_target_id,
solar_system_source_id
)
is_connection_exist =
is_connection_exist(
map_id,
solar_system_source_id,
solar_system_target_id
) || not is_nil(reverse_connection)
is_connection_valid =
is_connection_valid(
:wormholes,
solar_system_source_id,
solar_system_target_id
)
not is_connection_exist ||
(is_connection_valid &&
(DateTime.diff(DateTime.utc_now(), inserted_at, :hour) >=
@connection_auto_expire_hours ||
DateTime.diff(DateTime.utc_now(), connection_mark_eol_time, :hour) >=
@connection_auto_expire_hours - @connection_auto_eol_hours))
end)
|> Enum.reduce(state, fn %{
solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id
},
state ->
state
|> delete_connection(%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id
})
end)
state
state |> ConnectionsImpl.cleanup_connections()
end
def handle_event(:cleanup_characters, %{map_id: map_id, map: %{owner_id: owner_id}} = state) do
@@ -1006,17 +780,6 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp get_connection_mark_eol_time(map_id, connection_id, default \\ DateTime.utc_now()) do
WandererApp.Cache.get("map_#{map_id}:conn_#{connection_id}:mark_eol_time")
|> case do
nil ->
default
value ->
value
end
end
defp can_broadcast?(map_id),
do:
not WandererApp.Cache.lookup!("map_#{map_id}:importing", false) and
@@ -1029,20 +792,23 @@ defmodule WandererApp.Map.Server.Impl do
%{map: map, map_id: map_id, rtree_name: rtree_name, map_opts: map_opts} = _state
) do
case is_nil(old_location.solar_system_id) and
can_add_location(map.scope, location.solar_system_id) do
ConnectionsImpl.can_add_location(map.scope, location.solar_system_id) do
true ->
:ok = maybe_add_system(map_id, location, nil, rtree_name, map_opts)
_ ->
case is_connection_valid(
map.scope,
old_location.solar_system_id,
location.solar_system_id
) do
ConnectionsImpl.is_connection_valid(
map.scope,
old_location.solar_system_id,
location.solar_system_id
)
|> case do
true ->
:ok = maybe_add_system(map_id, location, old_location, rtree_name, map_opts)
:ok = maybe_add_system(map_id, old_location, location, rtree_name, map_opts)
:ok = maybe_add_connection(map_id, location, old_location, character_id)
:ok =
ConnectionsImpl.maybe_add_connection(map_id, location, old_location, character_id)
_ ->
:ok
@@ -1205,48 +971,6 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp update_connection(
%{map_id: map_id} = state,
update_method,
attributes,
%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id
} = update,
callback_fn \\ nil
) do
with {:ok, connection} <-
WandererApp.Map.find_connection(
map_id,
solar_system_source_id,
solar_system_target_id
),
{:ok, update_map} <- get_update_map(update, attributes),
:ok <-
WandererApp.Map.update_connection(
map_id,
connection |> Map.merge(update_map)
),
{:ok, updated_connection} <-
apply(WandererApp.MapConnectionRepo, update_method, [
connection,
update_map
]) do
if not is_nil(callback_fn) do
callback_fn.(updated_connection)
end
broadcast!(map_id, :update_connection, updated_connection)
state
else
{:error, error} ->
@logger.error("Failed to update connection: #{inspect(error, pretty: true)}")
state
end
end
defp update_system(
%{map_id: map_id} = state,
update_method,
@@ -1281,7 +1005,7 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp get_update_map(update, attributes),
def get_update_map(update, attributes),
do:
{:ok,
Enum.reduce(attributes, Map.new(), fn attribute, map ->
@@ -1449,14 +1173,7 @@ defmodule WandererApp.Map.Server.Impl do
)
end)
connections_eol_time
|> Enum.each(fn {connection_id, connection_eol_time} ->
WandererApp.Cache.put(
"map_#{map_id}:conn_#{connection_id}:mark_eol_time",
connection_eol_time,
ttl: @connection_eol_expire_timeout
)
end)
ConnectionsImpl.init_eol_cache(map_id, connections_eol_time)
state
@@ -1621,87 +1338,6 @@ defmodule WandererApp.Map.Server.Impl do
broadcast!(map_id, :update_system, updated_system)
end
defp can_add_location(_scope, nil), do: false
defp can_add_location(:all, _solar_system_id), do: true
defp can_add_location(:none, _solar_system_id), do: false
defp can_add_location(scope, solar_system_id) do
system_static_info =
case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do
{:ok, system_static_info} when not is_nil(system_static_info) ->
system_static_info
_ ->
%{system_class: nil}
end
case scope do
:wormholes ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
not (@prohibited_systems |> Enum.member?(solar_system_id)) and
@wh_space |> Enum.member?(system_static_info.system_class)
:stargates ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
@known_space |> Enum.member?(system_static_info.system_class)
_ ->
false
end
end
defp is_connection_exist(map_id, from_solar_system_id, to_solar_system_id),
do:
not is_nil(
WandererApp.Map.find_system_by_location(
map_id,
%{solar_system_id: from_solar_system_id}
)
) &&
not is_nil(
WandererApp.Map.find_system_by_location(
map_id,
%{solar_system_id: to_solar_system_id}
)
)
defp is_connection_valid(_scope, nil, _to_solar_system_id), do: false
defp is_connection_valid(:all, _from_solar_system_id, _to_solar_system_id), do: true
defp is_connection_valid(:none, _from_solar_system_id, _to_solar_system_id), do: false
defp is_connection_valid(scope, from_solar_system_id, to_solar_system_id) do
{:ok, known_jumps} =
WandererApp.Api.MapSolarSystemJumps.find(%{
before_system_id: from_solar_system_id,
current_system_id: to_solar_system_id
})
system_static_info =
case WandererApp.CachedInfo.get_system_static_info(to_solar_system_id) do
{:ok, system_static_info} when not is_nil(system_static_info) ->
system_static_info
_ ->
%{system_class: nil}
end
case scope do
:wormholes ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
not (@prohibited_systems |> Enum.member?(to_solar_system_id)) and
known_jumps |> Enum.empty?() and to_solar_system_id != @jita and
from_solar_system_id != @jita
:stargates ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
not (known_jumps |> Enum.empty?())
end
end
defp update_presence(map_id) do
case WandererApp.Cache.lookup!("map_#{map_id}:started", false) and
WandererApp.Cache.get_and_remove!("map_#{map_id}:presence_updated", false) do
@@ -1781,85 +1417,6 @@ defmodule WandererApp.Map.Server.Impl do
end)
end
defp maybe_remove_connection(map_id, location, old_location)
when not is_nil(location) and not is_nil(old_location) and
location.solar_system_id != old_location.solar_system_id do
case WandererApp.Map.find_connection(
map_id,
location.solar_system_id,
old_location.solar_system_id
) do
{:ok, connection} when not is_nil(connection) ->
:ok = WandererApp.MapConnectionRepo.destroy(map_id, connection)
broadcast!(map_id, :remove_connections, [connection])
map_id |> WandererApp.Map.remove_connection(connection)
_error ->
:ok
end
end
defp maybe_remove_connection(_map_id, _location, _old_location), do: :ok
defp maybe_add_connection(map_id, location, old_location, character_id)
when not is_nil(location) and not is_nil(old_location) and
not is_nil(old_location.solar_system_id) and
location.solar_system_id != old_location.solar_system_id do
character_id
|> WandererApp.Character.get_character!()
|> case do
nil ->
:ok
character ->
:telemetry.execute([:wanderer_app, :map, :character, :jump], %{count: 1}, %{})
{:ok, _} =
WandererApp.Api.MapChainPassages.new(%{
map_id: map_id,
character_id: character_id,
ship_type_id: character.ship,
ship_name: character.ship_name,
solar_system_source_id: old_location.solar_system_id,
solar_system_target_id: location.solar_system_id
})
broadcast!(map_id, :maybe_select_system, %{
character_id: character_id,
solar_system_id: location.solar_system_id
})
end
case WandererApp.Map.check_connection(map_id, location, old_location) do
:ok ->
connection =
WandererApp.MapConnectionRepo.create!(%{
map_id: map_id,
solar_system_source: old_location.solar_system_id,
solar_system_target: location.solar_system_id
})
WandererApp.Map.add_connection(map_id, connection)
broadcast!(map_id, :add_connection, connection)
broadcast!(map_id, :maybe_link_signature, %{
character_id: character_id,
solar_system_source: old_location.solar_system_id,
solar_system_target: location.solar_system_id
})
:ok
{:error, error} ->
@logger.debug(fn -> "Failed to add connection: #{inspect(error, pretty: true)}" end)
:ok
end
end
defp maybe_add_connection(_map_id, _location, _old_location, _character_id), do: :ok
defp maybe_add_system(map_id, location, old_location, rtree_name, map_opts)
when not is_nil(location) do
case WandererApp.Map.check_location(map_id, location) do
@@ -1878,6 +1435,7 @@ defmodule WandererApp.Map.Server.Impl do
position_y: position.y
})
|> WandererApp.MapSystemRepo.cleanup_labels!(map_opts)
|> WandererApp.MapSystemRepo.update_visible!(%{visible: true})
|> WandererApp.MapSystemRepo.cleanup_tags()
@ddrt.insert(
@@ -1895,9 +1453,11 @@ defmodule WandererApp.Map.Server.Impl do
ttl: @system_inactive_timeout
)
broadcast!(map_id, :add_system, updated_system)
WandererApp.Map.add_system(map_id, updated_system)
broadcast!(map_id, :add_system, updated_system)
:ok
_ ->
{:ok, solar_system_info} =
WandererApp.CachedInfo.get_system_static_info(location.solar_system_id)
@@ -1923,11 +1483,13 @@ defmodule WandererApp.Map.Server.Impl do
ttl: @system_inactive_timeout
)
broadcast!(map_id, :add_system, new_system)
WandererApp.Map.add_system(map_id, new_system)
broadcast!(map_id, :add_system, new_system)
:ok
error ->
@logger.debug("Failed to create system: #{inspect(error, pretty: true)}")
@logger.warning("Failed to create system: #{inspect(error, pretty: true)}")
:ok
end
end

View File

@@ -237,7 +237,7 @@ defmodule WandererApp.Map.SubscriptionManager do
defp _renew_subscription(%{auto_renew?: true} = subscription) when is_map(subscription) do
with {:ok, %{map: map}} <-
subscription |> WandererApp.MapSubscriptionRepo.load_relationships([:map]),
{:ok, estimated_price} <- estimate_price(subscription, true),
{:ok, estimated_price, discount} <- estimate_price(subscription, true),
{:ok, map_balance} <- get_balance(map) do
case map_balance >= estimated_price do
true ->
@@ -245,7 +245,7 @@ defmodule WandererApp.Map.SubscriptionManager do
WandererApp.MapTransactionRepo.create(%{
map_id: map.id,
user_id: nil,
amount: estimated_price,
amount: estimated_price - discount,
type: :out
})
@@ -267,7 +267,7 @@ defmodule WandererApp.Map.SubscriptionManager do
:telemetry.execute([:wanderer_app, :map, :subscription, :renew], %{count: 1}, %{
map_id: map.id,
amount: estimated_price
amount: estimated_price - discount
})
:ok

View File

@@ -0,0 +1,512 @@
defmodule WandererApp.Map.Server.ConnectionsImpl do
@moduledoc """
Holds state for a map and exposes an interface to managing the map instance
"""
require Logger
alias WandererApp.Map.Server.Impl
# @ccp1 -1
@c1 1
@c2 2
@c3 3
@c4 4
@c5 5
@c6 6
@hs 7
@ls 8
@ns 9
# @ccp2 10
# @ccp3 11
@thera 12
@c13 13
@sentinel 14
@baribican 15
@vidette 16
@conflux 17
@redoubt 18
@a1 19
@a2 20
@a3 21
@a4 22
@a5 23
@ccp4 24
# @pochven 25
# @zarzakh 10100
@jita 30_000_142
@wh_space [
@c1,
@c2,
@c3,
@c4,
@c5,
@c6,
@c13,
@thera,
@sentinel,
@baribican,
@vidette,
@conflux,
@redoubt
]
@known_space [@hs, @ls, @ns]
@prohibited_systems [@jita]
@prohibited_system_classes [
@a1,
@a2,
@a3,
@a4,
@a5,
@ccp4
]
# this class of systems will guaranty that no one real class will take that place
# @unknown 100_100
#
@connection_time_status_eol 1
@connection_auto_eol_hours 21
@connection_auto_expire_hours 24
@connection_eol_expire_timeout :timer.hours(3) + :timer.minutes(30)
@connection_type_wormhole 0
@connection_type_stargate 1
def init_eol_cache(map_id, connections_eol_time) do
connections_eol_time
|> Enum.each(fn {connection_id, connection_eol_time} ->
WandererApp.Cache.put(
"map_#{map_id}:conn_#{connection_id}:mark_eol_time",
connection_eol_time,
ttl: @connection_eol_expire_timeout
)
end)
end
def add_connection(
%{map_id: map_id} = state,
%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id,
character_id: character_id
} = _connection_info
) do
:ok =
maybe_add_connection(
map_id,
%{solar_system_id: solar_system_target_id},
%{
solar_system_id: solar_system_source_id
},
character_id
)
state
end
def delete_connection(
%{map_id: map_id} = state,
%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id
} = _connection_info
) do
:ok =
maybe_remove_connection(map_id, %{solar_system_id: solar_system_target_id}, %{
solar_system_id: solar_system_source_id
})
state
end
def get_connection_info(
%{map_id: map_id} = _state,
%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id
} = _connection_info
) do
WandererApp.Map.find_connection(
map_id,
solar_system_source_id,
solar_system_target_id
)
|> case do
{:ok, %{id: connection_id}} ->
connection_mark_eol_time = get_connection_mark_eol_time(map_id, connection_id, nil)
{:ok, %{marl_eol_time: connection_mark_eol_time}}
_ ->
{:error, :not_found}
end
end
def update_connection_time_status(
%{map_id: map_id} = state,
connection_update
),
do:
update_connection(state, :update_time_status, [:time_status], connection_update, fn
%{id: connection_id, time_status: time_status} ->
case time_status == @connection_time_status_eol do
true ->
WandererApp.Cache.put(
"map_#{map_id}:conn_#{connection_id}:mark_eol_time",
DateTime.utc_now(),
ttl: @connection_eol_expire_timeout
)
_ ->
WandererApp.Cache.delete("map_#{map_id}:conn_#{connection_id}:mark_eol_time")
end
end)
def update_connection_type(
state,
connection_update
),
do: update_connection(state, :update_type, [:type], connection_update)
def update_connection_mass_status(
state,
connection_update
),
do: update_connection(state, :update_mass_status, [:mass_status], connection_update)
def update_connection_ship_size_type(
state,
connection_update
),
do: update_connection(state, :update_ship_size_type, [:ship_size_type], connection_update)
def update_connection_locked(
state,
connection_update
),
do: update_connection(state, :update_locked, [:locked], connection_update)
def update_connection_custom_info(
state,
connection_update
),
do: update_connection(state, :update_custom_info, [:custom_info], connection_update)
def cleanup_connections(%{map_id: map_id} = state) do
state =
map_id
|> WandererApp.Map.list_connections!()
|> Enum.filter(fn %{
inserted_at: inserted_at,
solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id,
type: type
} ->
type != @connection_type_stargate &&
DateTime.diff(DateTime.utc_now(), inserted_at, :hour) >=
@connection_auto_eol_hours &&
is_connection_valid(
:wormholes,
solar_system_source_id,
solar_system_target_id
)
end)
|> Enum.reduce(state, fn %{
solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id
},
state ->
state
|> update_connection_time_status(%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id,
time_status: @connection_time_status_eol
})
end)
state =
map_id
|> WandererApp.Map.list_connections!()
|> Enum.filter(fn %{
id: connection_id,
inserted_at: inserted_at,
solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id,
type: type
} ->
connection_mark_eol_time =
get_connection_mark_eol_time(map_id, connection_id)
reverse_connection =
WandererApp.Map.get_connection(
map_id,
solar_system_target_id,
solar_system_source_id
)
is_connection_exist =
is_connection_exist(
map_id,
solar_system_source_id,
solar_system_target_id
) || not is_nil(reverse_connection)
is_connection_valid =
is_connection_valid(
:wormholes,
solar_system_source_id,
solar_system_target_id
)
not is_connection_exist ||
(type != @connection_type_stargate && is_connection_valid &&
(DateTime.diff(DateTime.utc_now(), inserted_at, :hour) >=
@connection_auto_expire_hours ||
DateTime.diff(DateTime.utc_now(), connection_mark_eol_time, :hour) >=
@connection_auto_expire_hours - @connection_auto_eol_hours))
end)
|> Enum.reduce(state, fn %{
solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id
},
state ->
state
|> delete_connection(%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id
})
end)
state
end
def maybe_add_connection(map_id, location, old_location, character_id)
when not is_nil(location) and not is_nil(old_location) and
not is_nil(old_location.solar_system_id) and
location.solar_system_id != old_location.solar_system_id do
character_id
|> WandererApp.Character.get_character!()
|> case do
nil ->
:ok
character ->
:telemetry.execute([:wanderer_app, :map, :character, :jump], %{count: 1}, %{})
{:ok, _} =
WandererApp.Api.MapChainPassages.new(%{
map_id: map_id,
character_id: character_id,
ship_type_id: character.ship,
ship_name: character.ship_name,
solar_system_source_id: old_location.solar_system_id,
solar_system_target_id: location.solar_system_id
})
end
case WandererApp.Map.check_connection(map_id, location, old_location) do
:ok ->
connection_type =
is_connection_valid(
:stargates,
old_location.solar_system_id,
location.solar_system_id
)
|> case do
true ->
@connection_type_stargate
_ ->
@connection_type_wormhole
end
{:ok, connection} =
WandererApp.MapConnectionRepo.create(%{
map_id: map_id,
solar_system_source: old_location.solar_system_id,
solar_system_target: location.solar_system_id,
type: connection_type
})
WandererApp.Map.add_connection(map_id, connection)
Impl.broadcast!(map_id, :maybe_select_system, %{
character_id: character_id,
solar_system_id: location.solar_system_id
})
Impl.broadcast!(map_id, :add_connection, connection)
Impl.broadcast!(map_id, :maybe_link_signature, %{
character_id: character_id,
solar_system_source: old_location.solar_system_id,
solar_system_target: location.solar_system_id
})
:ok
{:error, error} ->
Logger.debug(fn -> "Failed to add connection: #{inspect(error, pretty: true)}" end)
:ok
end
end
def maybe_add_connection(_map_id, _location, _old_location, _character_id), do: :ok
def can_add_location(_scope, nil), do: false
def can_add_location(:all, _solar_system_id), do: true
def can_add_location(:none, _solar_system_id), do: false
def can_add_location(scope, solar_system_id) do
system_static_info =
case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do
{:ok, system_static_info} when not is_nil(system_static_info) ->
system_static_info
_ ->
%{system_class: nil}
end
case scope do
:wormholes ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
not (@prohibited_systems |> Enum.member?(solar_system_id)) and
@wh_space |> Enum.member?(system_static_info.system_class)
:stargates ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
@known_space |> Enum.member?(system_static_info.system_class)
_ ->
false
end
end
def is_connection_exist(map_id, from_solar_system_id, to_solar_system_id),
do:
not is_nil(
WandererApp.Map.find_system_by_location(
map_id,
%{solar_system_id: from_solar_system_id}
)
) &&
not is_nil(
WandererApp.Map.find_system_by_location(
map_id,
%{solar_system_id: to_solar_system_id}
)
)
def is_connection_valid(_scope, nil, _to_solar_system_id), do: false
def is_connection_valid(:all, _from_solar_system_id, _to_solar_system_id), do: true
def is_connection_valid(:none, _from_solar_system_id, _to_solar_system_id), do: false
def is_connection_valid(scope, from_solar_system_id, to_solar_system_id) do
{:ok, known_jumps} =
WandererApp.Api.MapSolarSystemJumps.find(%{
before_system_id: from_solar_system_id,
current_system_id: to_solar_system_id
})
system_static_info =
case WandererApp.CachedInfo.get_system_static_info(to_solar_system_id) do
{:ok, system_static_info} when not is_nil(system_static_info) ->
system_static_info
_ ->
%{system_class: nil}
end
case scope do
:wormholes ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
not (@prohibited_systems |> Enum.member?(to_solar_system_id)) and
known_jumps |> Enum.empty?() and to_solar_system_id != @jita and
from_solar_system_id != @jita
:stargates ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
not (known_jumps |> Enum.empty?())
end
end
def get_connection_mark_eol_time(map_id, connection_id, default \\ DateTime.utc_now()) do
WandererApp.Cache.get("map_#{map_id}:conn_#{connection_id}:mark_eol_time")
|> case do
nil ->
default
value ->
value
end
end
defp maybe_remove_connection(map_id, location, old_location)
when not is_nil(location) and not is_nil(old_location) and
location.solar_system_id != old_location.solar_system_id do
case WandererApp.Map.find_connection(
map_id,
location.solar_system_id,
old_location.solar_system_id
) do
{:ok, connection} when not is_nil(connection) ->
:ok = WandererApp.MapConnectionRepo.destroy(map_id, connection)
Impl.broadcast!(map_id, :remove_connections, [connection])
map_id |> WandererApp.Map.remove_connection(connection)
_error ->
:ok
end
end
defp maybe_remove_connection(_map_id, _location, _old_location), do: :ok
defp update_connection(
%{map_id: map_id} = state,
update_method,
attributes,
%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id
} = update,
callback_fn \\ nil
) do
with {:ok, connection} <-
WandererApp.Map.find_connection(
map_id,
solar_system_source_id,
solar_system_target_id
),
{:ok, update_map} <- Impl.get_update_map(update, attributes),
{:ok, updated_connection} <-
apply(WandererApp.MapConnectionRepo, update_method, [
connection,
update_map
]),
:ok <-
WandererApp.Map.update_connection(
map_id,
connection |> Map.merge(update_map)
) do
if not is_nil(callback_fn) do
callback_fn.(updated_connection)
end
Impl.broadcast!(map_id, :update_connection, updated_connection)
state
else
{:error, error} ->
Logger.error("Failed to update connection: #{inspect(error, pretty: true)}")
state
end
end
end

View File

@@ -27,6 +27,7 @@ defmodule WandererApp.MapConnectionRepo do
end
end
def create(connection), do: connection |> WandererApp.Api.MapConnection.create()
def create!(connection), do: connection |> WandererApp.Api.MapConnection.create!()
def destroy(map_id, connection) when not is_nil(connection) do
@@ -70,6 +71,11 @@ defmodule WandererApp.MapConnectionRepo do
connection
|> WandererApp.Api.MapConnection.update_time_status(update)
def update_type(connection, update),
do:
connection
|> WandererApp.Api.MapConnection.update_type(update)
def update_mass_status(connection, update),
do:
connection

View File

@@ -146,7 +146,12 @@ defmodule WandererAppWeb.CoreComponents do
class="flex flex-col p-4 items-center absolute bottom-16 left-1 gap-2 tooltip tooltip-right"
data-tip="server: Tranquility"
>
<div class={"block w-4 h-4 rounded-full shadow-inner #{if @online, do: " bg-green-500 animate-pulse", else: "bg-red-500"}"}>
<div
:if={@online}
class="absolute block w-4 h-4 rounded-full shadow-inner bg-green-500 animate-ping"
>
</div>
<div class={"block w-4 h-4 rounded-full shadow-inner #{if @online, do: " bg-green-500", else: "bg-red-500"}"}>
</div>
</div>
"""

View File

@@ -30,25 +30,35 @@ defmodule WandererAppWeb.Layouts do
phx-hook="NewVersionUpdate"
phx-update="ignore"
data-version={@app_version}
class="!z-100 hidden group alert items-center fixed bottom-52 left-2 fade-in-scale text-white !bg-opacity-70 w-10 h-10 hover:w-[250px] hover:h-[70px] rounded p-px overflow-hidden"
class="!z-1000 hidden absolute top-0 left-0 w-full h-full group items-center fade-in-scale text-white !bg-opacity-70 rounded p-px overflow-hidden flex items-center"
>
<div class="group animate-rotate absolute inset-0 h-full w-full rounded-full bg-[conic-gradient(#0ea5e9_20deg,transparent_120deg)] group-hover:bg-[#0ea5e9]" />
<div class="!bg-black rounded w-9 h-9 hover:m-0 group-hover:w-[246px] group-hover:h-[66px] flex items-center justify-center p-2 relative z-20">
<.icon name="hero-bell-alert" class="animate-pulse group-hover:hidden absolute top-2 h-5 w-5" />
<div class="opacity-0 group-hover:opacity-100 flex flex-col items-center justify-center w-[250px] h-full">
<div class="text-white text-nowrap text-sm">
New Version Available
</div>
<a href="/changelog" target="_blank" class="text-sm link-secondary">What's new?</a>
<div class="hs-overlay-backdrop transition duration absolute left-0 top-0 w-full h-full bg-gray-900 bg-opacity-50 dark:bg-opacity-80 dark:bg-neutral-900">
</div>
<div class="absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] flex items-center">
<div class="rounded w-9 h-9 w-[80px] h-[66px] flex items-center justify-center relative z-20">
<.icon name="hero-chevron-double-right" class="w-9 h-9 mr-[-40px]" />
</div>
<div id="refresh-area">
<.live_component module={WandererAppWeb.MapRefresh} id="map-refresh" />
</div>
<button
type="button"
class="invisible group-hover:visible update-button p-button p-component p-button-outlined p-button-sm p-0 px-1 w-[76px]"
>
Update
</button>
<div class="rounded h-[66px] flex items-center justify-center relative z-20">
<div class=" flex items-center w-[200px] h-full">
<.icon name="hero-chevron-double-left" class="w-9 h-9 mr-[20px]" />
<div class=" flex flex-col items-center justify-center h-full">
<div class="text-white text-nowrap text-sm [text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]">
Update Required
</div>
<a
href="/changelog"
target="_blank"
class="text-sm link-secondary [text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]"
>
What's new?
</a>
</div>
</div>
</div>
</div>
</div>
"""

View File

@@ -0,0 +1,197 @@
defmodule WandererAppWeb.MapRefresh do
use WandererAppWeb, :live_component
def render(assigns) do
~H"""
<div id="map-refresh" class="socket">
<div class="gel center-gel">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c1 r1">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c2 r1">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c3 r1">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c4 r1">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c5 r1">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c6 r1">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c7 r2">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c8 r2">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c9 r2">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c10 r2">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c11 r2">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c12 r2">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c13 r2">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c14 r2">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c15 r2">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c16 r2">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c17 r2">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c18 r2">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c19 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c20 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c21 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c22 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c23 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c24 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c25 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c26 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c28 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c29 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c30 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c31 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c32 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c33 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c34 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c35 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c36 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
<div class="gel c37 r3">
<div class="hex-brick h1"></div>
<div class="hex-brick h2"></div>
<div class="hex-brick h3"></div>
</div>
</div>
"""
end
end

View File

@@ -54,7 +54,8 @@ defmodule WandererAppWeb.MapConnectionsEventHandler do
map_id
|> WandererApp.Map.Server.add_connection(%{
solar_system_source_id: solar_system_source_id |> String.to_integer(),
solar_system_target_id: solar_system_target_id |> String.to_integer()
solar_system_target_id: solar_system_target_id |> String.to_integer(),
character_id: tracked_character_ids |> List.first()
})
{:ok, _} =
@@ -122,6 +123,7 @@ defmodule WandererAppWeb.MapConnectionsEventHandler do
method_atom =
case param do
"time_status" -> :update_connection_time_status
"type" -> :update_connection_type
"mass_status" -> :update_connection_mass_status
"ship_size_type" -> :update_connection_ship_size_type
"locked" -> :update_connection_locked
@@ -132,6 +134,7 @@ defmodule WandererAppWeb.MapConnectionsEventHandler do
key_atom =
case param do
"time_status" -> :time_status
"type" -> :type
"mass_status" -> :mass_status
"ship_size_type" -> :ship_size_type
"locked" -> :locked

View File

@@ -145,7 +145,10 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
if not is_nil(updated) do
s
|> WandererApp.Api.MapSystemSignature.update(updated)
|> WandererApp.Api.MapSystemSignature.update(
updated
|> Map.put(:updated, System.os_time())
)
end
end)
@@ -313,7 +316,11 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
do:
system_id
|> WandererApp.Api.MapSystemSignature.by_system_id!()
|> Enum.map(fn %{updated_at: updated_at, linked_system_id: linked_system_id} = s ->
|> Enum.map(fn %{
inserted_at: inserted_at,
updated_at: updated_at,
linked_system_id: linked_system_id
} = s ->
s
|> Map.take([
:eve_id,
@@ -321,10 +328,10 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
:description,
:kind,
:group,
:type,
:updated_at
:type
])
|> Map.put(:linked_system, MapEventHandler.get_system_static_info(linked_system_id))
|> Map.put(:inserted_at, inserted_at |> Calendar.strftime("%Y/%m/%d %H:%M:%S"))
|> Map.put(:updated_at, updated_at |> Calendar.strftime("%Y/%m/%d %H:%M:%S"))
end)

View File

@@ -64,6 +64,7 @@ defmodule WandererAppWeb.MapEventHandler do
"get_connection_info",
"get_passages",
"update_connection_time_status",
"update_connection_type",
"update_connection_mass_status",
"update_connection_ship_size_type",
"update_connection_locked",
@@ -215,6 +216,7 @@ defmodule WandererAppWeb.MapEventHandler do
solar_system_target: solar_system_target,
mass_status: mass_status,
time_status: time_status,
type: type,
ship_size_type: ship_size_type,
locked: locked
} = _connection
@@ -223,6 +225,7 @@ defmodule WandererAppWeb.MapEventHandler do
id: "#{solar_system_source}_#{solar_system_target}",
mass_status: mass_status,
time_status: time_status,
type: type,
ship_size_type: ship_size_type,
locked: locked,
source: "#{solar_system_source}",

View File

@@ -2,7 +2,7 @@ defmodule WandererApp.MixProject do
use Mix.Project
@source_url "https://github.com/wanderer-industries/wanderer"
@version "1.18.1"
@version "1.21.0"
def project do
[

View File

@@ -0,0 +1,21 @@
defmodule WandererApp.Repo.Migrations.AddSignatureUpdated do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
alter table(:map_system_signatures_v1) do
add :updated, :bigint
end
end
def down do
alter table(:map_system_signatures_v1) do
remove :updated
end
end
end

View File

@@ -0,0 +1,21 @@
defmodule WandererApp.Repo.Migrations.AddConnectionType do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
alter table(:map_chain_v1) do
add :type, :bigint, default: 0
end
end
def down do
alter table(:map_chain_v1) do
remove :type
end
end
end

View File

@@ -0,0 +1,178 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "solar_system_source",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "solar_system_target",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "0",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "mass_status",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "0",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "time_status",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "1",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship_size_type",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "0",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "type",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "wormhole_type",
"type": "text"
},
{
"allow_nil?": true,
"default": "0",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "count_of_passage",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "locked",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "custom_info",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "map_chain_v1_map_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "maps_v1"
},
"size": null,
"source": "map_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "D9587518CE16355CA4FB501321848465A3E1DDD7800563069242249DA0B4C389",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "map_chain_v1"
}

View File

@@ -0,0 +1,187 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_id",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "character_eve_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "description",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "type",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "linked_system_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "kind",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "group",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "map_system_signatures_v1_system_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "map_system_v1"
},
"size": null,
"source": "system_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "6A255286EC34371C26500578C2B991D604C154AB4751B8F1EFBE4E7E61AA6F2D",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "map_system_signatures_v1_uniq_system_eve_id_index",
"keys": [
{
"type": "atom",
"value": "system_id"
},
{
"type": "atom",
"value": "eve_id"
}
],
"name": "uniq_system_eve_id",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "map_system_signatures_v1"
}