Compare commits
69 Commits
update-lif
...
v1.81.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69eb888469 | ||
|
|
748347df9a | ||
|
|
aa4d49027c | ||
|
|
a9d7387e40 | ||
|
|
dc4d260c9b | ||
|
|
dc430491bf | ||
|
|
42cd261ea7 | ||
|
|
35af4fdc09 | ||
|
|
8bb4998e59 | ||
|
|
c825a3f4c4 | ||
|
|
5343c34488 | ||
|
|
4878be1a53 | ||
|
|
1ff689c26c | ||
|
|
79b660e899 | ||
|
|
665a679bd5 | ||
|
|
7bd634eb95 | ||
|
|
c3b5a77a86 | ||
|
|
8498846d9c | ||
|
|
12f39a0133 | ||
|
|
ffc2a86e95 | ||
|
|
82babf41a2 | ||
|
|
81055b4fbd | ||
|
|
5070a59f88 | ||
|
|
65d5bf960d | ||
|
|
8fc4cb190e | ||
|
|
c6c065dbb9 | ||
|
|
8ba34533d7 | ||
|
|
095a4b2362 | ||
|
|
fafc631e49 | ||
|
|
e56383c8b1 | ||
|
|
b9c26bdb04 | ||
|
|
8aeaa81752 | ||
|
|
b16ec0490f | ||
|
|
eceaf1d73b | ||
|
|
34cf668a33 | ||
|
|
c22d410c9f | ||
|
|
fc6af867f2 | ||
|
|
2d96114984 | ||
|
|
fd7e19e490 | ||
|
|
f7d996f5b2 | ||
|
|
f8ab1383ab | ||
|
|
e1559aac94 | ||
|
|
2e17cce5cd | ||
|
|
8fb831f171 | ||
|
|
cb84f34515 | ||
|
|
272cce1a77 | ||
|
|
e0e3ed1580 | ||
|
|
c4c848cf37 | ||
|
|
d33a2e3a5b | ||
|
|
5c8753fb96 | ||
|
|
32d25d86eb | ||
|
|
863adccac1 | ||
|
|
2d527e1d16 | ||
|
|
9a64ad6fa7 | ||
|
|
5ce472ebff | ||
|
|
76588af12f | ||
|
|
134f169eb9 | ||
|
|
7c2d731c4c | ||
|
|
c7e2a290cf | ||
|
|
5ea966892a | ||
|
|
b879db76b7 | ||
|
|
d13a628029 | ||
|
|
7c1e2595e3 | ||
|
|
a99e8a915e | ||
|
|
f89cd5f44f | ||
|
|
abe4951251 | ||
|
|
5508fbee2f | ||
|
|
51ff4e7f36 | ||
|
|
34d3d92afd |
@@ -13,4 +13,4 @@ export WANDERER_KILLS_BASE_URL="ws://host.docker.internal:4004"
|
||||
export WANDERER_SSE_ENABLED="true"
|
||||
export WANDERER_WEBHOOKS_ENABLED="true"
|
||||
export WANDERER_SSE_MAX_CONNECTIONS="1000"
|
||||
export WANDERER_WEBHOOK_TIMEOUT_MS="15000"
|
||||
export WANDERER_WEBHOOK_TIMEOUT_MS="15000"
|
||||
|
||||
169
CHANGELOG.md
@@ -2,6 +2,175 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v1.81.8](https://github.com/wanderer-industries/wanderer/compare/v1.81.7...v1.81.8) (2025-10-11)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix problem with restoring settings on widgets
|
||||
|
||||
## [v1.81.7](https://github.com/wanderer-industries/wanderer/compare/v1.81.6...v1.81.7) (2025-10-10)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed problem with rendering dropdown classes in signatures
|
||||
|
||||
## [v1.81.6](https://github.com/wanderer-industries/wanderer/compare/v1.81.5...v1.81.6) (2025-10-10)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed problem with a lot unnecessary loads zkb data on resize map
|
||||
|
||||
* Map: Added ability to see focused element
|
||||
|
||||
* Map: Removed unnecessary vertical scroller in Character Tracking dialog. Main always first in list of tracking characters, following next after main, another characters sorting by name
|
||||
|
||||
* Map: Added Search tool for systems what on the map
|
||||
|
||||
* Map: Added migration mechanism
|
||||
|
||||
* Map: Remove settings some default values if migration from very old settings system
|
||||
|
||||
* Map: MIGRATION: support from old store settings import
|
||||
|
||||
* Map: Add common migration mechanism. ATTENTION! This is a non-reversible stored map settings commit â it means we do not guarantee that settings will work if you check out back. Weâve tried to migrate old settings, but it may not work well or may NOT work at all.
|
||||
|
||||
* Map: Add front-end migrations for local store settings
|
||||
|
||||
## [v1.81.5](https://github.com/wanderer-industries/wanderer/compare/v1.81.4...v1.81.5) (2025-10-09)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Update connection ship size based on linked signature type
|
||||
|
||||
## [v1.81.4](https://github.com/wanderer-industries/wanderer/compare/v1.81.3...v1.81.4) (2025-10-09)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed signature to system link issues
|
||||
|
||||
## [v1.81.3](https://github.com/wanderer-industries/wanderer/compare/v1.81.2...v1.81.3) (2025-10-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed cancel ping errors
|
||||
|
||||
## [v1.81.2](https://github.com/wanderer-industries/wanderer/compare/v1.81.1...v1.81.2) (2025-10-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* api dropping custom name
|
||||
|
||||
## [v1.81.1](https://github.com/wanderer-industries/wanderer/compare/v1.81.0...v1.81.1) (2025-10-02)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed characters tracking updates.
|
||||
|
||||
## [v1.81.0](https://github.com/wanderer-industries/wanderer/compare/v1.80.0...v1.81.0) (2025-10-02)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* core: fix pwa icons + add screen in manifest
|
||||
|
||||
## [v1.80.0](https://github.com/wanderer-industries/wanderer/compare/v1.79.6...v1.80.0) (2025-10-02)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: Added PWA web manifest
|
||||
|
||||
## [v1.79.6](https://github.com/wanderer-industries/wanderer/compare/v1.79.5...v1.79.6) (2025-10-01)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed modals auto-save on Enter.
|
||||
|
||||
## [v1.79.5](https://github.com/wanderer-industries/wanderer/compare/v1.79.4...v1.79.5) (2025-10-01)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed system details modal auto-save on Enter.
|
||||
|
||||
## [v1.79.4](https://github.com/wanderer-industries/wanderer/compare/v1.79.3...v1.79.4) (2025-09-30)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed updating connection time status based on linked signature data. Fixed FR gas sites parsing.
|
||||
|
||||
## [v1.79.3](https://github.com/wanderer-industries/wanderer/compare/v1.79.2...v1.79.3) (2025-09-27)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed connection passages count
|
||||
|
||||
## [v1.79.2](https://github.com/wanderer-industries/wanderer/compare/v1.79.1...v1.79.2) (2025-09-26)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.79.1](https://github.com/wanderer-industries/wanderer/compare/v1.79.0...v1.79.1) (2025-09-26)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.79.0](https://github.com/wanderer-industries/wanderer/compare/v1.78.1...v1.79.0) (2025-09-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: Updated connections EOL logic
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed eslint problems
|
||||
|
||||
* Map: Update lifetime design and buttons
|
||||
|
||||
* Map: Update wormhole lifetime UI and removed unnecessary code
|
||||
|
||||
## [v1.78.1](https://github.com/wanderer-industries/wanderer/compare/v1.78.0...v1.78.1) (2025-09-24)
|
||||
|
||||
|
||||
|
||||
@@ -284,3 +284,7 @@
|
||||
border-left-color: #e67e22;
|
||||
}
|
||||
|
||||
.p-dialog-header-icon.p-dialog-header-close.p-link {
|
||||
position: relative;
|
||||
left: 6px;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.vertical-tabs-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
min-height: 200px;
|
||||
|
||||
.p-tabview {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MapUserSettings, SettingsWithVersion } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { MapUserSettings, SettingsWrapper } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export const REQUIRED_KEYS = [
|
||||
'widgets',
|
||||
@@ -19,11 +19,8 @@ export class MapUserSettingsParseError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
const isNumber = (v: unknown): v is number => typeof v === 'number' && !Number.isNaN(v);
|
||||
|
||||
/** Minimal check that an object matches SettingsWithVersion<*> */
|
||||
const isSettingsWithVersion = (v: unknown): v is SettingsWithVersion<unknown> =>
|
||||
typeof v === 'object' && v !== null && isNumber((v as any).version) && 'settings' in (v as any);
|
||||
/** Minimal check that an object matches SettingsWrapper<*> */
|
||||
const isSettings = (v: unknown): v is SettingsWrapper<unknown> => typeof v === 'object' && v !== null;
|
||||
|
||||
/** Ensure every required key is present */
|
||||
const hasAllRequiredKeys = (v: unknown): v is Record<RequiredKeys, unknown> =>
|
||||
@@ -52,8 +49,8 @@ export const parseMapUserSettings = (json: unknown): MapUserSettings => {
|
||||
}
|
||||
|
||||
for (const key of REQUIRED_KEYS) {
|
||||
if (!isSettingsWithVersion((data as any)[key])) {
|
||||
throw new MapUserSettingsParseError(`"${key}" must match SettingsWithVersion<T>`);
|
||||
if (!isSettings((data as any)[key])) {
|
||||
throw new MapUserSettingsParseError(`"${key}" must match SettingsWrapper<T>`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
26
assets/js/hooks/Mapper/components/hooks/useLocalCounter.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useMemo } from 'react';
|
||||
import { CharacterTypeRaw } from '@/hooks/Mapper/types';
|
||||
|
||||
export type UseLocalCounterProps = {
|
||||
charactersInSystem: Array<CharacterTypeRaw>;
|
||||
userCharacters: string[];
|
||||
};
|
||||
|
||||
export const getLocalCharacters = ({ charactersInSystem, userCharacters }: UseLocalCounterProps) => {
|
||||
return charactersInSystem
|
||||
.map(char => ({
|
||||
...char,
|
||||
compact: true,
|
||||
isOwn: userCharacters.includes(char.eve_id),
|
||||
}))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
};
|
||||
|
||||
export const useLocalCounter = ({ charactersInSystem, userCharacters }: UseLocalCounterProps) => {
|
||||
const localCounterCharacters = useMemo(
|
||||
() => getLocalCharacters({ charactersInSystem, userCharacters }),
|
||||
[charactersInSystem, userCharacters],
|
||||
);
|
||||
|
||||
return { localCounterCharacters };
|
||||
};
|
||||
@@ -1,11 +1,10 @@
|
||||
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
|
||||
import { PingData, SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import type { PanelPosition } from '@reactflow/core';
|
||||
import clsx from 'clsx';
|
||||
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useMemo } from 'react';
|
||||
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
Edge,
|
||||
@@ -33,19 +32,9 @@ import {
|
||||
import { getBehaviorForTheme } from './helpers/getThemeBehavior';
|
||||
import { useEdgesState, useMapHandlers, useNodesState, useUpdateNodes } from './hooks';
|
||||
import { useBackgroundVars } from './hooks/useBackgroundVars';
|
||||
import { OnMapAddSystemCallback, OnMapSelectionChange } from './map.types';
|
||||
|
||||
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
|
||||
|
||||
const getViewPortFromStore = () => {
|
||||
const restored = localStorage.getItem(SESSION_KEY.viewPort);
|
||||
|
||||
if (!restored) {
|
||||
return { ...DEFAULT_VIEW_PORT };
|
||||
}
|
||||
|
||||
return JSON.parse(restored);
|
||||
};
|
||||
import { MapViewport, OnMapAddSystemCallback, OnMapSelectionChange } from './map.types';
|
||||
import type { Viewport } from '@reactflow/core/dist/esm/types';
|
||||
import { usePrevious } from 'primereact/hooks';
|
||||
|
||||
const initialNodes: Node<SolarSystemRawType>[] = [
|
||||
// {
|
||||
@@ -88,6 +77,7 @@ interface MapCompProps {
|
||||
onConnectionInfoClick?(e: SolarSystemConnection): void;
|
||||
onAddSystem?: OnMapAddSystemCallback;
|
||||
onSelectionContextMenu?: NodeSelectionMouseHandler;
|
||||
onChangeViewport?: (viewport: MapViewport) => void;
|
||||
minimapClasses?: string;
|
||||
isShowMinimap?: boolean;
|
||||
onSystemContextMenu: (event: MouseEvent<Element>, systemId: string) => void;
|
||||
@@ -99,6 +89,7 @@ interface MapCompProps {
|
||||
pings: PingData[];
|
||||
minimapPlacement?: PanelPosition;
|
||||
localShowShipName?: boolean;
|
||||
defaultViewport?: Viewport;
|
||||
}
|
||||
|
||||
const MapComp = ({
|
||||
@@ -119,19 +110,25 @@ const MapComp = ({
|
||||
pings,
|
||||
minimapPlacement = 'bottom-right',
|
||||
localShowShipName = false,
|
||||
onChangeViewport,
|
||||
defaultViewport,
|
||||
}: MapCompProps) => {
|
||||
const { getNodes } = useReactFlow();
|
||||
const { getNodes, setViewport } = useReactFlow();
|
||||
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
|
||||
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges);
|
||||
|
||||
useMapHandlers(refn, onSelectionChange);
|
||||
useUpdateNodes(nodes);
|
||||
|
||||
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem });
|
||||
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
|
||||
const { update } = useMapState();
|
||||
const { variant, gap, size, color } = useBackgroundVars(theme);
|
||||
const { isPanAndDrag, nodeComponent, connectionMode } = getBehaviorForTheme(theme || 'default');
|
||||
|
||||
const refVars = useRef({ onChangeViewport });
|
||||
refVars.current = { onChangeViewport };
|
||||
|
||||
const nodeTypes = useMemo(() => {
|
||||
return {
|
||||
custom: nodeComponent,
|
||||
@@ -187,9 +184,10 @@ const MapComp = ({
|
||||
[onSelectionChange],
|
||||
);
|
||||
|
||||
const handleMoveEnd: OnMoveEnd = (_, viewport) => {
|
||||
localStorage.setItem(SESSION_KEY.viewPort, JSON.stringify(viewport));
|
||||
};
|
||||
const handleMoveEnd: OnMoveEnd = useCallback((_, viewport) => {
|
||||
// @ts-ignore
|
||||
refVars.current.onChangeViewport?.(viewport);
|
||||
}, []);
|
||||
|
||||
const handleNodesChange = useCallback(
|
||||
(changes: NodeChange[]) => {
|
||||
@@ -218,6 +216,19 @@ const MapComp = ({
|
||||
}));
|
||||
}, [showKSpaceBG, isThickConnections, pings, update, localShowShipName]);
|
||||
|
||||
const prevViewport = usePrevious(defaultViewport);
|
||||
useEffect(() => {
|
||||
if (defaultViewport == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (prevViewport == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setViewport(defaultViewport);
|
||||
}, [defaultViewport, prevViewport, setViewport]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -232,7 +243,7 @@ const MapComp = ({
|
||||
onConnect={onConnect}
|
||||
// TODO we need save into session all of this
|
||||
// and on any action do either
|
||||
defaultViewport={getViewPortFromStore()}
|
||||
defaultViewport={defaultViewport}
|
||||
edgeTypes={edgeTypes}
|
||||
nodeTypes={nodeTypes}
|
||||
connectionMode={connectionMode}
|
||||
|
||||
@@ -11,6 +11,7 @@ export type MapData = MapUnionTypes & {
|
||||
isThickConnections: boolean;
|
||||
linkedSigEveId: string;
|
||||
localShowShipName: boolean;
|
||||
systemHighlighted: string | undefined;
|
||||
};
|
||||
|
||||
interface MapProviderProps {
|
||||
@@ -44,6 +45,7 @@ const INITIAL_DATA: MapData = {
|
||||
userHubs: [],
|
||||
pings: [],
|
||||
localShowShipName: false,
|
||||
systemHighlighted: undefined,
|
||||
};
|
||||
|
||||
export interface MapContextProps {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
}
|
||||
|
||||
.hoverTarget {
|
||||
padding: 0.5rem;
|
||||
margin: -0.5rem;
|
||||
padding: 2px;
|
||||
margin: -2px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,19 @@ interface LocalCounterProps {
|
||||
localCounterCharacters: Array<CharItemProps>;
|
||||
hasUserCharacters: boolean;
|
||||
showIcon?: boolean;
|
||||
disableInteractive?: boolean;
|
||||
className?: string;
|
||||
contentClassName?: string;
|
||||
}
|
||||
|
||||
export const LocalCounter = ({ localCounterCharacters, hasUserCharacters, showIcon = true }: LocalCounterProps) => {
|
||||
export const LocalCounter = ({
|
||||
className,
|
||||
contentClassName,
|
||||
localCounterCharacters,
|
||||
hasUserCharacters,
|
||||
showIcon = true,
|
||||
disableInteractive,
|
||||
}: LocalCounterProps) => {
|
||||
const {
|
||||
data: { localShowShipName },
|
||||
} = useMapState();
|
||||
@@ -42,22 +52,30 @@ export const LocalCounter = ({ localCounterCharacters, hasUserCharacters, showIc
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(classes.TooltipActive, {
|
||||
[classes.Pathfinder]: theme === AvailableThemes.pathfinder,
|
||||
})}
|
||||
className={clsx(
|
||||
classes.TooltipActive,
|
||||
{
|
||||
[classes.Pathfinder]: theme === AvailableThemes.pathfinder,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<WdTooltipWrapper
|
||||
content={pilotTooltipContent}
|
||||
position={TooltipPosition.right}
|
||||
offset={0}
|
||||
interactive={true}
|
||||
interactive={!disableInteractive}
|
||||
smallPaddings
|
||||
>
|
||||
<div className={clsx(classes.hoverTarget)}>
|
||||
<div
|
||||
className={clsx(classes.localCounter, {
|
||||
[classes.hasUserCharacters]: hasUserCharacters,
|
||||
})}
|
||||
className={clsx(
|
||||
classes.localCounter,
|
||||
{
|
||||
[classes.hasUserCharacters]: hasUserCharacters,
|
||||
},
|
||||
contentClassName,
|
||||
)}
|
||||
>
|
||||
{showIcon && <i className="pi pi-users" />}
|
||||
<span>{localCounterCharacters.length}</span>
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
@use "sass:color";
|
||||
@use '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
||||
|
||||
$pastel-blue: #5a7d9a;
|
||||
$pastel-pink: rgb(30, 161, 255);
|
||||
$dark-bg: #2d2d2d;
|
||||
$text-color: #ffffff;
|
||||
$tooltip-bg: #202020;
|
||||
|
||||
$neon-color-1: rgb(27, 132, 236);
|
||||
$neon-color-3: rgba(27, 132, 236, 0.40);
|
||||
@import '@/hooks/Mapper/components/map/styles/solar-system-node';
|
||||
|
||||
@keyframes move-stripes {
|
||||
from {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Handle, NodeProps, Position } from 'reactflow';
|
||||
import clsx from 'clsx';
|
||||
import classes from './SolarSystemNodeDefault.module.scss';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks';
|
||||
import { useNodeKillsCount, useSolarSystemNode } from '../../hooks';
|
||||
import {
|
||||
EFFECT_BACKGROUND_STYLES,
|
||||
MARKER_BOOKMARK_BG_STYLES,
|
||||
@@ -17,10 +17,12 @@ import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-
|
||||
import { Tag } from 'primereact/tag';
|
||||
import { LocalCounter } from '@/hooks/Mapper/components/map/components/LocalCounter';
|
||||
import { KillsCounter } from '@/hooks/Mapper/components/map/components/KillsCounter';
|
||||
import { useLocalCounter } from '@/hooks/Mapper/components/hooks/useLocalCounter.ts';
|
||||
|
||||
// let render = 0;
|
||||
export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>) => {
|
||||
const nodeVars = useSolarSystemNode(props);
|
||||
|
||||
const { localCounterCharacters } = useLocalCounter(nodeVars);
|
||||
const { killsCount: localKillsCount, killsActivityType: localKillsActivityType } = useNodeKillsCount(
|
||||
nodeVars.solarSystemId,
|
||||
@@ -139,12 +141,26 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
|
||||
|
||||
{nodeVars.isWormhole && !nodeVars.customName && <div />}
|
||||
|
||||
<div className="flex items-center gap-1 justify-end">
|
||||
<div className={clsx('flex items-center gap-1')}>
|
||||
<div className="flex items-center gap-0.5 justify-end">
|
||||
<div className={clsx('flex items-center gap-0.5')}>
|
||||
{nodeVars.locked && <i className={clsx(PrimeIcons.LOCK, classes.lockIcon)} />}
|
||||
{nodeVars.hubs.includes(nodeVars.solarSystemId) && (
|
||||
<i className={clsx(PrimeIcons.MAP_MARKER, classes.mapMarker)} />
|
||||
)}
|
||||
{nodeVars.description != null && nodeVars.description !== '' && (
|
||||
<WdTooltipWrapper
|
||||
className="h-[15px] transform -translate-y-[6%]"
|
||||
position={TooltipPosition.top}
|
||||
content={`System have description`}
|
||||
>
|
||||
<i
|
||||
className={clsx(
|
||||
'pi hero-chat-bubble-bottom-center-text w-[10px] h-[10px]',
|
||||
'text-[8px] relative top-[1px]',
|
||||
)}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<LocalCounter
|
||||
@@ -177,6 +193,17 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
|
||||
</>
|
||||
)}
|
||||
|
||||
{nodeVars.systemHighlighted === nodeVars.solarSystemId && (
|
||||
<div
|
||||
className={clsx('absolute top-[-4px] left-[-4px]', 'w-[calc(100%+8px)] h-[calc(100%+8px)]', 'animate-pulse')}
|
||||
>
|
||||
<div className="absolute left-0 top-0 w-3 h-2 border-t-2 border-l-2 border-sky-300"></div>
|
||||
<div className="absolute right-0 top-0 w-3 h-2 border-t-2 border-r-2 border-sky-300"></div>
|
||||
<div className="absolute left-0 bottom-0 w-3 h-2 border-b-2 border-l-2 border-sky-300"></div>
|
||||
<div className="absolute right-0 bottom-0 w-3 h-2 border-b-2 border-r-2 border-sky-300"></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={classes.Handlers}>
|
||||
<Handle
|
||||
type="source"
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Handle, NodeProps, Position } from 'reactflow';
|
||||
import clsx from 'clsx';
|
||||
import classes from './SolarSystemNodeTheme.module.scss';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks';
|
||||
import { useNodeKillsCount, useSolarSystemNode } from '../../hooks';
|
||||
import {
|
||||
EFFECT_BACKGROUND_STYLES,
|
||||
MARKER_BOOKMARK_BG_STYLES,
|
||||
@@ -16,6 +16,7 @@ import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-
|
||||
import { TooltipSize } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper/utils.ts';
|
||||
import { LocalCounter } from '@/hooks/Mapper/components/map/components/LocalCounter';
|
||||
import { KillsCounter } from '@/hooks/Mapper/components/map/components/KillsCounter';
|
||||
import { useLocalCounter } from '@/hooks/Mapper/components/hooks/useLocalCounter.ts';
|
||||
|
||||
// let render = 0;
|
||||
export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>) => {
|
||||
@@ -172,6 +173,17 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
|
||||
</>
|
||||
)}
|
||||
|
||||
{nodeVars.systemHighlighted === nodeVars.solarSystemId && (
|
||||
<div
|
||||
className={clsx('absolute top-[-4px] left-[-4px]', 'w-[calc(100%+8px)] h-[calc(100%+8px)]', 'animate-pulse')}
|
||||
>
|
||||
<div className="absolute left-0 top-0 w-3 h-2 border-t-2 border-l-2 border-sky-300"></div>
|
||||
<div className="absolute right-0 top-0 w-3 h-2 border-t-2 border-r-2 border-sky-300"></div>
|
||||
<div className="absolute left-0 bottom-0 w-3 h-2 border-b-2 border-l-2 border-sky-300"></div>
|
||||
<div className="absolute right-0 bottom-0 w-3 h-2 border-b-2 border-r-2 border-sky-300"></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={classes.Handlers}>
|
||||
<Handle
|
||||
type="source"
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { useReactFlow } from 'reactflow';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandCenterSystem } from '@/hooks/Mapper/types';
|
||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
import { SYSTEM_FOCUSED_LIFETIME } from '@/hooks/Mapper/constants.ts';
|
||||
|
||||
export const useCenterSystem = () => {
|
||||
const rf = useReactFlow();
|
||||
|
||||
const ref = useRef({ rf });
|
||||
ref.current = { rf };
|
||||
const { update } = useMapState();
|
||||
|
||||
const ref = useRef({ rf, update });
|
||||
ref.current = { rf, update };
|
||||
|
||||
const highlightTimeout = useRef<number>();
|
||||
|
||||
return useCallback((systemId: CommandCenterSystem) => {
|
||||
const systemNode = ref.current.rf.getNodes().find(x => x.data.id === systemId);
|
||||
@@ -14,5 +20,16 @@ export const useCenterSystem = () => {
|
||||
return;
|
||||
}
|
||||
ref.current.rf.setCenter(systemNode.position.x, systemNode.position.y, { duration: 1000 });
|
||||
|
||||
ref.current.update({ systemHighlighted: systemId });
|
||||
|
||||
if (highlightTimeout.current !== undefined) {
|
||||
clearTimeout(highlightTimeout.current);
|
||||
}
|
||||
|
||||
highlightTimeout.current = setTimeout(() => {
|
||||
highlightTimeout.current = undefined;
|
||||
ref.current.update({ systemHighlighted: undefined });
|
||||
}, SYSTEM_FOCUSED_LIFETIME);
|
||||
}, []);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider';
|
||||
import { useDoubleClick } from '@/hooks/Mapper/hooks/useDoubleClick';
|
||||
import { Regions, REGIONS_MAP, Spaces } from '@/hooks/Mapper/constants';
|
||||
import { Regions, REGIONS_MAP, SPACE_TO_CLASS } from '@/hooks/Mapper/constants';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace';
|
||||
import { getSystemClassStyles } from '@/hooks/Mapper/components/map/helpers';
|
||||
import { sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||
@@ -50,27 +50,9 @@ export interface SolarSystemNodeVars {
|
||||
isRally: boolean;
|
||||
classTitle: string | null;
|
||||
temporaryName?: string | null;
|
||||
}
|
||||
|
||||
const SpaceToClass: Record<string, string> = {
|
||||
[Spaces.Caldari]: 'Caldaria',
|
||||
[Spaces.Matar]: 'Mataria',
|
||||
[Spaces.Amarr]: 'Amarria',
|
||||
[Spaces.Gallente]: 'Gallente',
|
||||
[Spaces.Pochven]: 'Pochven',
|
||||
};
|
||||
|
||||
export function useLocalCounter(nodeVars: SolarSystemNodeVars) {
|
||||
const localCounterCharacters = useMemo(() => {
|
||||
return nodeVars.charactersInSystem
|
||||
.map(char => ({
|
||||
...char,
|
||||
compact: true,
|
||||
isOwn: nodeVars.userCharacters.includes(char.eve_id),
|
||||
}))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}, [nodeVars.charactersInSystem, nodeVars.userCharacters]);
|
||||
return { localCounterCharacters };
|
||||
description: string | null;
|
||||
comments_count: number | null;
|
||||
systemHighlighted: string | undefined;
|
||||
}
|
||||
|
||||
export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarSystemNodeVars => {
|
||||
@@ -84,6 +66,8 @@ export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarS
|
||||
labels,
|
||||
temporary_name,
|
||||
linked_sig_eve_id: linkedSigEveId = '',
|
||||
description,
|
||||
comments_count,
|
||||
} = data;
|
||||
|
||||
const {
|
||||
@@ -125,6 +109,7 @@ export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarS
|
||||
showKSpaceBG,
|
||||
isThickConnections,
|
||||
pings,
|
||||
systemHighlighted,
|
||||
},
|
||||
outCommand,
|
||||
} = useMapState();
|
||||
@@ -169,7 +154,7 @@ export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarS
|
||||
const showHandlers = isConnecting || hoverNodeId === id;
|
||||
|
||||
const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
|
||||
const regionClass = showKSpaceBG ? SpaceToClass[space] || null : null;
|
||||
const regionClass = showKSpaceBG ? SPACE_TO_CLASS[space] || null : null;
|
||||
|
||||
const { systemName, computedTemporaryName, customName } = useSystemName({
|
||||
isTempSystemNameEnabled,
|
||||
@@ -232,6 +217,9 @@ export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarS
|
||||
regionName,
|
||||
solarSystemName: solar_system_name,
|
||||
isRally,
|
||||
description,
|
||||
comments_count,
|
||||
systemHighlighted,
|
||||
};
|
||||
|
||||
return nodeVars;
|
||||
|
||||
@@ -10,3 +10,5 @@ export type OnMapSelectionChange = (event: {
|
||||
}) => void;
|
||||
|
||||
export type OnMapAddSystemCallback = (props: { coordinates: XYPosition | null }) => void;
|
||||
|
||||
export type MapViewport = { zoom: 1; x: 0; y: 0 };
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
$pastel-blue: #5a7d9a;
|
||||
$pastel-pink: rgb(30, 161, 255);
|
||||
$dark-bg: #2d2d2d;
|
||||
$text-color: #ffffff;
|
||||
$tooltip-bg: #202020;
|
||||
|
||||
$neon-color-1: rgb(27, 132, 236);
|
||||
$neon-color-3: rgba(27, 132, 236, 0.40);
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { IconField } from 'primereact/iconfield';
|
||||
import { AutoComplete } from 'primereact/autocomplete';
|
||||
import { OutCommand, SearchSystemItem } from '@/hooks/Mapper/types';
|
||||
import { SystemViewStandalone, WdButton, WHClassView, WHEffectView } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OutCommand, SearchSystemItem } from '@/hooks/Mapper/types';
|
||||
import { AutoComplete } from 'primereact/autocomplete';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { IconField } from 'primereact/iconfield';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import classes from './AddSystemDialog.module.scss';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||
import { sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export type SearchOnSubmitCallback = (item: SearchSystemItem) => void;
|
||||
|
||||
@@ -115,90 +115,93 @@ export const AddSystemDialog = ({
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-3 px-1.5">
|
||||
<div className="flex flex-col gap-2 py-3.5">
|
||||
<div className="flex flex-col gap-1">
|
||||
<IconField>
|
||||
<AutoComplete
|
||||
ref={inputRef}
|
||||
multiple
|
||||
showEmptyMessage
|
||||
scrollHeight="300px"
|
||||
value={selectedItem}
|
||||
suggestions={filteredItems}
|
||||
completeMethod={searchItems}
|
||||
onChange={e => {
|
||||
setSelectedItem(e.value.length < 2 ? e.value : [e.value[e.value.length - 1]]);
|
||||
}}
|
||||
emptyMessage="Not found any system..."
|
||||
placeholder="Type here..."
|
||||
field="label"
|
||||
id="value"
|
||||
className="w-full"
|
||||
itemTemplate={(item: SearchSystemItem) => {
|
||||
const { security, system_class, effect_power, effect_name, statics } = item.system_static_info;
|
||||
const sortedStatics = sortWHClasses(wormholesData, statics);
|
||||
const isWH = isWormholeSpace(system_class);
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col gap-3 px-1.5">
|
||||
<div className="flex flex-col gap-2 py-3.5">
|
||||
<div className="flex flex-col gap-1">
|
||||
<IconField>
|
||||
<AutoComplete
|
||||
ref={inputRef}
|
||||
multiple
|
||||
showEmptyMessage
|
||||
scrollHeight="300px"
|
||||
value={selectedItem}
|
||||
suggestions={filteredItems}
|
||||
completeMethod={searchItems}
|
||||
onChange={e => {
|
||||
setSelectedItem(e.value.length < 2 ? e.value : [e.value[e.value.length - 1]]);
|
||||
}}
|
||||
emptyMessage="Not found any system..."
|
||||
placeholder="Type here..."
|
||||
field="label"
|
||||
id="value"
|
||||
className="w-full"
|
||||
itemTemplate={(item: SearchSystemItem) => {
|
||||
const { security, system_class, effect_power, effect_name, statics } = item.system_static_info;
|
||||
const sortedStatics = sortWHClasses(wormholesData, statics);
|
||||
const isWH = isWormholeSpace(system_class);
|
||||
|
||||
return (
|
||||
<div className={clsx('flex gap-1.5', classes.SearchItem)}>
|
||||
<SystemViewStandalone
|
||||
security={security}
|
||||
system_class={system_class}
|
||||
solar_system_id={item.value}
|
||||
class_title={item.class_title}
|
||||
solar_system_name={item.label}
|
||||
region_name={item.region_name}
|
||||
/>
|
||||
|
||||
{effect_name && isWH && (
|
||||
<WHEffectView
|
||||
effectName={effect_name}
|
||||
effectPower={effect_power}
|
||||
className={classes.SearchItemEffect}
|
||||
return (
|
||||
<div className={clsx('flex gap-1.5', classes.SearchItem)}>
|
||||
<SystemViewStandalone
|
||||
security={security}
|
||||
system_class={system_class}
|
||||
solar_system_id={item.value}
|
||||
class_title={item.class_title}
|
||||
solar_system_name={item.label}
|
||||
region_name={item.region_name}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isWH && (
|
||||
<div className="flex gap-1 grow justify-between">
|
||||
<div></div>
|
||||
<div className="flex gap-1">
|
||||
{sortedStatics.map(x => (
|
||||
<WHClassView key={x} whClassName={x} />
|
||||
))}
|
||||
{effect_name && isWH && (
|
||||
<WHEffectView
|
||||
effectName={effect_name}
|
||||
effectPower={effect_power}
|
||||
className={classes.SearchItemEffect}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isWH && (
|
||||
<div className="flex gap-1 grow justify-between">
|
||||
<div></div>
|
||||
<div className="flex gap-1">
|
||||
{sortedStatics.map(x => (
|
||||
<WHClassView key={x} whClassName={x} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
selectedItemTemplate={(item: SearchSystemItem) => (
|
||||
<SystemViewStandalone
|
||||
security={item.system_static_info.security}
|
||||
system_class={item.system_static_info.system_class}
|
||||
solar_system_id={item.value}
|
||||
class_title={item.class_title}
|
||||
solar_system_name={item.label}
|
||||
region_name={item.region_name}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</IconField>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
selectedItemTemplate={(item: SearchSystemItem) => (
|
||||
<SystemViewStandalone
|
||||
security={item.system_static_info.security}
|
||||
system_class={item.system_static_info.system_class}
|
||||
solar_system_id={item.value}
|
||||
class_title={item.class_title}
|
||||
solar_system_name={item.label}
|
||||
region_name={item.region_name}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</IconField>
|
||||
|
||||
<span className="text-[12px] text-stone-400 ml-1">*to search type at least 2 symbols.</span>
|
||||
<span className="text-[12px] text-stone-400 ml-1">*to search type at least 2 symbols.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
<WdButton
|
||||
type="submit"
|
||||
onClick={handleSubmit}
|
||||
outlined
|
||||
disabled={!selectedItem || selectedItem.length !== 1}
|
||||
size="small"
|
||||
label="Submit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
<WdButton
|
||||
onClick={handleSubmit}
|
||||
outlined
|
||||
disabled={!selectedItem || selectedItem.length !== 1}
|
||||
size="small"
|
||||
label="Submit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { TooltipPosition, WdButton, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { IconField } from 'primereact/iconfield';
|
||||
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
|
||||
import { TooltipPosition, WdButton, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { IconField } from 'primereact/iconfield';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface SystemCustomLabelDialog {
|
||||
systemId: string;
|
||||
@@ -125,7 +125,7 @@ export const SystemCustomLabelDialog = ({ systemId, visible, setVisible }: Syste
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
<WdButton onClick={handleSave} outlined size="small" label="Save"></WdButton>
|
||||
<WdButton type="submit" onClick={handleSave} outlined size="small" label="Save"></WdButton>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { InputTextarea } from 'primereact/inputtextarea';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { SystemView, WdButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { PingType } from '@/hooks/Mapper/types/ping.ts';
|
||||
import { SystemView, WdButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import clsx from 'clsx';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { InputTextarea } from 'primereact/inputtextarea';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
const PING_TITLES = {
|
||||
[PingType.Rally]: 'RALLY',
|
||||
@@ -62,7 +62,7 @@ export const SystemPingDialog = ({ systemId, type, visible, setVisible }: System
|
||||
</div>
|
||||
}
|
||||
visible={visible}
|
||||
draggable={false}
|
||||
draggable={true}
|
||||
style={{ width: '450px' }}
|
||||
onShow={onShow}
|
||||
onHide={() => {
|
||||
@@ -91,7 +91,7 @@ export const SystemPingDialog = ({ systemId, type, visible, setVisible }: System
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
<WdButton onClick={handleSave} size="small" severity="danger" label="Ping!" />
|
||||
<WdButton type="submit" onClick={handleSave} size="small" severity="danger" label="Ping!" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { InputTextarea } from 'primereact/inputtextarea';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { TooltipPosition, WdButton, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { IconField } from 'primereact/iconfield';
|
||||
import { TooltipPosition, WdButton, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
|
||||
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { IconField } from 'primereact/iconfield';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { InputTextarea } from 'primereact/inputtextarea';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface SystemSettingsDialog {
|
||||
systemId: string;
|
||||
@@ -125,7 +125,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
}}
|
||||
>
|
||||
<form onSubmit={handleSave}>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-3 px-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="username">Custom name</label>
|
||||
@@ -225,7 +225,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
<WdButton onClick={handleSave} outlined size="small" label="Save" />
|
||||
<WdButton onClick={handleSave} outlined size="small" label="Save" type="submit" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useRouteProvider } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
|
||||
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
|
||||
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface RoutesSettingsDialog {
|
||||
visible: boolean;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useCallback, useMemo, useState, useEffect, useRef } from 'react';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { DetailedKill } from '@/hooks/Mapper/types/kills';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useStableValue } from '@/hooks/Mapper/hooks';
|
||||
|
||||
interface UseSystemKillsProps {
|
||||
systemId?: string;
|
||||
@@ -30,9 +30,8 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
|
||||
update,
|
||||
storedSettings: { settingsKills },
|
||||
} = useMapRootState();
|
||||
const { excludedSystems } = settingsKills;
|
||||
|
||||
const effectiveSinceHours = sinceHours;
|
||||
const excludedSystems = useStableValue(settingsKills.excludedSystems);
|
||||
|
||||
const effectiveSystemIds = useMemo(() => {
|
||||
if (showAllVisible) {
|
||||
@@ -76,13 +75,13 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
|
||||
eventType = OutCommand.getSystemsKills;
|
||||
requestData = {
|
||||
system_ids: effectiveSystemIds,
|
||||
since_hours: effectiveSinceHours,
|
||||
since_hours: sinceHours,
|
||||
};
|
||||
} else if (systemId) {
|
||||
eventType = OutCommand.getSystemKills;
|
||||
requestData = {
|
||||
system_id: systemId,
|
||||
since_hours: effectiveSinceHours,
|
||||
since_hours: sinceHours,
|
||||
};
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
@@ -110,16 +109,7 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[showAllVisible, systemId, outCommand, effectiveSystemIds, effectiveSinceHours, mergeKillsIntoGlobal],
|
||||
);
|
||||
|
||||
const debouncedFetchKills = useMemo(
|
||||
() =>
|
||||
debounce(fetchKills, 500, {
|
||||
leading: true,
|
||||
trailing: false,
|
||||
}),
|
||||
[fetchKills],
|
||||
[showAllVisible, systemId, outCommand, effectiveSystemIds, sinceHours, mergeKillsIntoGlobal],
|
||||
);
|
||||
|
||||
const finalKills = useMemo(() => {
|
||||
@@ -141,27 +131,22 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
|
||||
useEffect(() => {
|
||||
if (!systemId && !showAllVisible && !didFallbackFetch.current) {
|
||||
didFallbackFetch.current = true;
|
||||
debouncedFetchKills.cancel();
|
||||
fetchKills(true);
|
||||
}
|
||||
}, [systemId, showAllVisible, debouncedFetchKills, fetchKills]);
|
||||
}, [systemId, showAllVisible, fetchKills]);
|
||||
|
||||
useEffect(() => {
|
||||
if (effectiveSystemIds.length === 0) return;
|
||||
|
||||
if (showAllVisible || systemId) {
|
||||
// Cancel any pending debounced fetch
|
||||
debouncedFetchKills.cancel();
|
||||
// Fetch kills immediately
|
||||
fetchKills();
|
||||
return () => debouncedFetchKills.cancel();
|
||||
return;
|
||||
}
|
||||
}, [showAllVisible, systemId, effectiveSystemIds, debouncedFetchKills, fetchKills]);
|
||||
}, [showAllVisible, systemId, effectiveSystemIds, fetchKills]);
|
||||
|
||||
const refetch = useCallback(() => {
|
||||
debouncedFetchKills.cancel();
|
||||
fetchKills();
|
||||
}, [debouncedFetchKills, fetchKills]);
|
||||
}, [fetchKills]);
|
||||
|
||||
return {
|
||||
kills: finalKills,
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { Commands } from '@/hooks/Mapper/types';
|
||||
import { PingsInterface } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { OldSettingsDialog } from '@/hooks/Mapper/components/mapRootContent/components/OldSettingsDialog.tsx';
|
||||
import { TopSearch } from '@/hooks/Mapper/components/mapRootContent/components/TopSearch';
|
||||
|
||||
export interface MapRootContentProps {}
|
||||
|
||||
@@ -72,6 +73,7 @@ export const MapRootContent = ({}: MapRootContentProps) => {
|
||||
<div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none">
|
||||
<Topbar>
|
||||
<div className="flex items-center ml-1">
|
||||
<TopSearch />
|
||||
<PingsInterface />
|
||||
<MapContextMenu
|
||||
onShowOnTheMap={handleShowOnTheMap}
|
||||
|
||||
@@ -62,7 +62,7 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => {
|
||||
header="Map user settings"
|
||||
visible
|
||||
draggable={false}
|
||||
style={{ width: '600px' }}
|
||||
className="w-[600px] h-[400px]"
|
||||
onShow={handleShow}
|
||||
onHide={handleHide}
|
||||
>
|
||||
|
||||
@@ -5,6 +5,8 @@ import { parseMapUserSettings } from '@/hooks/Mapper/components/helpers';
|
||||
import { saveTextFile } from '@/hooks/Mapper/utils/saveToFile.ts';
|
||||
import { SplitButton } from 'primereact/splitbutton';
|
||||
import { loadTextFile } from '@/hooks/Mapper/utils';
|
||||
import { applyMigrations } from '@/hooks/Mapper/mapRootProvider/migrations';
|
||||
import { createDefaultStoredSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultStoredSettings.ts';
|
||||
|
||||
export const ImportExport = () => {
|
||||
const {
|
||||
@@ -22,8 +24,9 @@ export const ImportExport = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
// INFO: WE NOT SUPPORT MIGRATIONS FOR OLD FILES AND Clipboard
|
||||
const parsed = parseMapUserSettings(text);
|
||||
if (applySettings(parsed)) {
|
||||
if (applySettings(applyMigrations(parsed) || createDefaultStoredSettings())) {
|
||||
toast.current?.show({
|
||||
severity: 'success',
|
||||
summary: 'Import',
|
||||
@@ -59,8 +62,9 @@ export const ImportExport = () => {
|
||||
try {
|
||||
const text = await loadTextFile();
|
||||
|
||||
// INFO: WE NOT SUPPORT MIGRATIONS FOR OLD FILES AND Clipboard
|
||||
const parsed = parseMapUserSettings(text);
|
||||
if (applySettings(parsed)) {
|
||||
if (applySettings(applyMigrations(parsed) || createDefaultStoredSettings())) {
|
||||
toast.current?.show({
|
||||
severity: 'success',
|
||||
summary: 'Import',
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Toast } from 'primereact/toast';
|
||||
import { parseMapUserSettings } from '@/hooks/Mapper/components/helpers';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { createDefaultWidgetSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultWidgetSettings.ts';
|
||||
import { createDefaultStoredSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultStoredSettings.ts';
|
||||
import { callToastSuccess } from '@/hooks/Mapper/helpers';
|
||||
import { ConfirmPopup } from 'primereact/confirmpopup';
|
||||
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
|
||||
import { RemoteAdminSettingsResponse } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { applyMigrations } from '@/hooks/Mapper/mapRootProvider/migrations';
|
||||
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
export const ServerSettings = () => {
|
||||
@@ -29,15 +29,16 @@ export const ServerSettings = () => {
|
||||
}
|
||||
|
||||
if (res?.default_settings == null) {
|
||||
applySettings(createDefaultWidgetSettings());
|
||||
applySettings(createDefaultStoredSettings());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
applySettings(parseMapUserSettings(res.default_settings));
|
||||
//INFO: INSTEAD CHECK WE WILL TRY TO APPLY MIGRATION
|
||||
applySettings(applyMigrations(JSON.parse(res.default_settings)) || createDefaultStoredSettings());
|
||||
callToastSuccess(toast.current, 'Settings synchronized successfully');
|
||||
} catch (error) {
|
||||
applySettings(createDefaultWidgetSettings());
|
||||
applySettings(createDefaultStoredSettings());
|
||||
}
|
||||
}, [applySettings, outCommand]);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SettingsListItem, UserSettingsRemoteProps } from './types.ts';
|
||||
import { InterfaceStoredSettingsProps } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { AvailableThemes, MiniMapPlacement, PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { SettingsListItem, UserSettingsRemoteProps } from './types.ts';
|
||||
|
||||
export const DEFAULT_REMOTE_SETTINGS = {
|
||||
[UserSettingsRemoteProps.link_signature_on_splash]: false,
|
||||
@@ -51,7 +51,7 @@ export const SIGNATURES_CHECKBOXES_PROPS: SettingsListItem[] = [
|
||||
export const CONNECTIONS_CHECKBOXES_PROPS: SettingsListItem[] = [
|
||||
{
|
||||
prop: UserSettingsRemoteProps.delete_connection_with_sigs,
|
||||
label: 'Delete connections to linked signatures',
|
||||
label: 'Delete connections with linked signatures',
|
||||
type: 'checkbox',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
import { DEFAULT_SIGNATURE_SETTINGS } from '@/hooks/Mapper/constants/signatures.ts';
|
||||
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import {
|
||||
DEFAULT_KILLS_WIDGET_SETTINGS,
|
||||
DEFAULT_ON_THE_MAP_SETTINGS,
|
||||
DEFAULT_ROUTES_SETTINGS,
|
||||
DEFAULT_WIDGET_LOCAL_SETTINGS,
|
||||
getDefaultWidgetProps,
|
||||
STORED_INTERFACE_DEFAULT_VALUES,
|
||||
} from '@/hooks/Mapper/mapRootProvider/constants.ts';
|
||||
import { MapUserSettings } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { saveTextFile } from '@/hooks/Mapper/utils';
|
||||
import { ConfirmPopup } from 'primereact/confirmpopup';
|
||||
@@ -18,10 +9,7 @@ import { useCallback, useRef } from 'react';
|
||||
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
const createSettings = function <T>(lsSettings: string | null, defaultValues: T) {
|
||||
return {
|
||||
version: -1,
|
||||
settings: lsSettings ? JSON.parse(lsSettings) : defaultValues,
|
||||
};
|
||||
return lsSettings ? JSON.parse(lsSettings) : defaultValues;
|
||||
};
|
||||
|
||||
export const OldSettingsDialog = () => {
|
||||
@@ -44,13 +32,15 @@ export const OldSettingsDialog = () => {
|
||||
const signatures = localStorage.getItem('wanderer_system_signature_settings_v6_6');
|
||||
|
||||
const out: MapUserSettings = {
|
||||
killsWidget: createSettings(widgetKills, DEFAULT_KILLS_WIDGET_SETTINGS),
|
||||
localWidget: createSettings(widgetLocal, DEFAULT_WIDGET_LOCAL_SETTINGS),
|
||||
widgets: createSettings(widgetsOld, getDefaultWidgetProps()),
|
||||
routes: createSettings(widgetRoutes, DEFAULT_ROUTES_SETTINGS),
|
||||
onTheMap: createSettings(onTheMapOld, DEFAULT_ON_THE_MAP_SETTINGS),
|
||||
signaturesWidget: createSettings(signatures, DEFAULT_SIGNATURE_SETTINGS),
|
||||
interface: createSettings(interfaceSettings, STORED_INTERFACE_DEFAULT_VALUES),
|
||||
version: 0,
|
||||
migratedFromOld: false,
|
||||
killsWidget: createSettings(widgetKills, {}),
|
||||
localWidget: createSettings(widgetLocal, {}),
|
||||
widgets: createSettings(widgetsOld, {}),
|
||||
routes: createSettings(widgetRoutes, {}),
|
||||
onTheMap: createSettings(onTheMapOld, {}),
|
||||
signaturesWidget: createSettings(signatures, {}),
|
||||
interface: createSettings(interfaceSettings, {}),
|
||||
};
|
||||
|
||||
if (asFile) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
||||
import { TopSearch } from '@/hooks/Mapper/components/mapRootContent/components/TopSearch';
|
||||
// import { DebugComponent } from '@/hooks/Mapper/components/mapRootContent/components/RightBar/DebugComponent.tsx';
|
||||
|
||||
interface RightBarProps {
|
||||
@@ -48,7 +49,7 @@ export const RightBar = ({
|
||||
classes.RightBarRoot,
|
||||
'w-full h-full',
|
||||
'text-gray-200 shadow-lg border-l border-zinc-800 border-opacity-70 bg-opacity-70 bg-neutral-900',
|
||||
'flex flex-col items-center justify-between',
|
||||
'flex flex-col items-center justify-between pt-1',
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-2 items-center mt-1">
|
||||
@@ -65,15 +66,31 @@ export const RightBar = ({
|
||||
</button>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
<WdTooltipWrapper content="Show on the map" position={TooltipPosition.left}>
|
||||
<button
|
||||
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
|
||||
type="button"
|
||||
onClick={onShowOnTheMap}
|
||||
>
|
||||
<i className="pi pi-hashtag"></i>
|
||||
</button>
|
||||
</WdTooltipWrapper>
|
||||
<div className="flex flex-col gap-1">
|
||||
<TopSearch
|
||||
customBtn={open => (
|
||||
<WdTooltipWrapper content="Show on the map" position={TooltipPosition.left}>
|
||||
<button
|
||||
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
|
||||
type="button"
|
||||
onClick={open}
|
||||
>
|
||||
<i className="pi pi-search"></i>
|
||||
</button>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
/>
|
||||
|
||||
<WdTooltipWrapper content="Show on the map" position={TooltipPosition.left}>
|
||||
<button
|
||||
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
|
||||
type="button"
|
||||
onClick={onShowOnTheMap}
|
||||
>
|
||||
<i className="pi pi-hashtag"></i>
|
||||
</button>
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{additionalContent}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
@use "sass:color";
|
||||
@use '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
||||
@import '@/hooks/Mapper/components/map/styles/solar-system-node';
|
||||
|
||||
:root {
|
||||
--rf-has-user-characters: #ffc75d;
|
||||
}
|
||||
|
||||
.Sidebar {
|
||||
:global {
|
||||
.p-sidebar-header {
|
||||
padding-left: 14px;
|
||||
padding-right: 14px;
|
||||
}
|
||||
|
||||
.p-sidebar-content {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Content {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&.Pochven,
|
||||
&.Mataria,
|
||||
&.Amarria,
|
||||
&.Gallente,
|
||||
&.Caldaria {
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-size: cover;
|
||||
background-position: 50% 50%;
|
||||
z-index: -1;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&.Mataria {
|
||||
&::after {
|
||||
background-image: url('/images/mataria-180.png');
|
||||
opacity: 0.6;
|
||||
//background-position-x: 52px;
|
||||
//background-position-y: -83px;
|
||||
background-position-x: 331px;
|
||||
background-position-y: -77px;
|
||||
transform: scale(-1);
|
||||
}
|
||||
}
|
||||
|
||||
&.Caldaria {
|
||||
&::after {
|
||||
background-image: url('/images/caldaria-180.png');
|
||||
opacity: 0.6;
|
||||
//background-position-x: 41px;
|
||||
//background-position-y: -45px;
|
||||
|
||||
background-position-x: 360px;
|
||||
background-position-y: -16px;
|
||||
background-size: 30%;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
&.Amarria {
|
||||
&::after {
|
||||
opacity: 0.45;
|
||||
background-image: url('/images/amarr-small.png');
|
||||
//background-position-x: 23px;
|
||||
//background-position-y: -74px;
|
||||
|
||||
background-position-x: -1px;
|
||||
background-position-y: -75px;
|
||||
background-origin: border-box;
|
||||
background-size: auto;
|
||||
transform: scale(1, -1);
|
||||
}
|
||||
}
|
||||
|
||||
&.Gallente {
|
||||
&::after {
|
||||
opacity: 0.5;
|
||||
background-image: url('/images/gallente-180.png');
|
||||
//background-position-x: 1px;
|
||||
//background-position-y: -55px;
|
||||
|
||||
background-position-x: 294px;
|
||||
background-position-y: -53px;
|
||||
transform: scale(-1);
|
||||
}
|
||||
}
|
||||
|
||||
&.Pochven {
|
||||
&::after {
|
||||
opacity: 0.8;
|
||||
background-image: url('/images/pochven.webp');
|
||||
background-position-x: 0;
|
||||
background-position-y: -88px;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: $pastel-pink;
|
||||
box-shadow: 0 0 10px #9a1af1c2;
|
||||
}
|
||||
|
||||
&.rally {
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
|
||||
border-color: $neon-color-1;
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
$neon-color-3 0px,
|
||||
$neon-color-3 8px,
|
||||
transparent 8px,
|
||||
transparent 21px
|
||||
);
|
||||
background-size: 30px 30px;
|
||||
animation: move-stripes 3s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
&.eve-system-status-home {
|
||||
background-image: linear-gradient(45deg, var(--eve-solar-system-status-color-background), transparent);
|
||||
&.selected {
|
||||
border-color: var(--eve-solar-system-status-color-home);
|
||||
}
|
||||
}
|
||||
|
||||
&.eve-system-status-friendly {
|
||||
background-image: linear-gradient(275deg, var(--eve-solar-system-status-friendly-dark30), transparent);
|
||||
&.selected {
|
||||
border-color: var(--eve-solar-system-status-color-friendly-dark5);
|
||||
}
|
||||
}
|
||||
|
||||
&.eve-system-status-lookingFor {
|
||||
background-image: linear-gradient(275deg, #45ff8f2f, #457fff2f);
|
||||
&.selected {
|
||||
border-color: $pastel-pink;
|
||||
}
|
||||
}
|
||||
|
||||
&.eve-system-status-warning {
|
||||
background-image: linear-gradient(275deg, var(--eve-solar-system-status-warning), transparent);
|
||||
}
|
||||
|
||||
&.eve-system-status-dangerous {
|
||||
background-image: linear-gradient(275deg, var(--eve-solar-system-status-dangerous), transparent);
|
||||
}
|
||||
|
||||
&.eve-system-status-target {
|
||||
background-image: linear-gradient(275deg, var(--eve-solar-system-status-target), transparent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
import classes from './TopSearch.module.scss';
|
||||
import { Sidebar } from 'primereact/sidebar';
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
||||
import clsx from 'clsx';
|
||||
import { Commands, SolarSystemRawType, SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||
import {
|
||||
SystemViewStandalone,
|
||||
TooltipPosition,
|
||||
WdImageSize,
|
||||
WdImgButton,
|
||||
WdTooltipWrapper,
|
||||
WHClassView,
|
||||
WHEffectView,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { IconField } from 'primereact/iconfield';
|
||||
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
||||
import { sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||
import { STATUS_CLASSES } from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import { REGIONS_MAP, SPACE_TO_CLASS } from '@/hooks/Mapper/constants.ts';
|
||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
import { LocalCounter } from '@/hooks/Mapper/components/map/components/LocalCounter';
|
||||
import { getLocalCharacters } from '@/hooks/Mapper/components/hooks/useLocalCounter.ts';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
|
||||
type CompiledSystem = {
|
||||
dynamic: SolarSystemRawType;
|
||||
static: SolarSystemStaticInfoRaw | undefined;
|
||||
};
|
||||
|
||||
const useItemTemplate = () => {
|
||||
const {
|
||||
data: { wormholesData, characters, userCharacters, hubs },
|
||||
} = useMapRootState();
|
||||
|
||||
return useCallback(
|
||||
(item: CompiledSystem, options: VirtualScrollerTemplateOptions) => {
|
||||
if (!item.static) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
security,
|
||||
system_class,
|
||||
class_title,
|
||||
effect_power,
|
||||
region_name,
|
||||
solar_system_name,
|
||||
solar_system_id,
|
||||
effect_name,
|
||||
statics,
|
||||
region_id,
|
||||
} = item.static;
|
||||
|
||||
const onlineCharactersInSystem = characters.filter(
|
||||
c => c.location?.solar_system_id === solar_system_id && c.online,
|
||||
);
|
||||
const hasOnlineUserCharacters = onlineCharactersInSystem.some(x => userCharacters.includes(x.eve_id));
|
||||
const onlineCharacters = getLocalCharacters({ charactersInSystem: onlineCharactersInSystem, userCharacters });
|
||||
|
||||
const offlineCharactersInSystem = characters.filter(
|
||||
c => c.location?.solar_system_id === solar_system_id && !c.online,
|
||||
);
|
||||
const hasOfflineUserCharacters = offlineCharactersInSystem.some(x => userCharacters.includes(x.eve_id));
|
||||
const offlineCharacters = getLocalCharacters({ charactersInSystem: offlineCharactersInSystem, userCharacters });
|
||||
|
||||
const handleSelect = () => {
|
||||
emitMapEvent({
|
||||
name: Commands.centerSystem,
|
||||
data: solar_system_id.toString(),
|
||||
});
|
||||
};
|
||||
|
||||
const sortedStatics = sortWHClasses(wormholesData, statics);
|
||||
const isWH = isWormholeSpace(system_class);
|
||||
|
||||
const regionClass = SPACE_TO_CLASS[REGIONS_MAP[region_id]] || null;
|
||||
const showTempName = item.dynamic.temporary_name != null;
|
||||
const showCustomName = item.dynamic.name != null && item.dynamic.name !== solar_system_name;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'w-full box-border px-3.5 py-1 h-[48px] cursor-pointer',
|
||||
'bg-transparent hover:bg-stone-800/30 transition-all !duration-250 ease-in-out',
|
||||
{
|
||||
'surface-hover': options.odd,
|
||||
['border-b border-gray-600 border-opacity-20']: !options.last,
|
||||
},
|
||||
classes.Content,
|
||||
regionClass && classes[regionClass],
|
||||
item.dynamic.status !== undefined && classes[STATUS_CLASSES[item.dynamic.status]],
|
||||
)}
|
||||
onClick={handleSelect}
|
||||
>
|
||||
<div className={clsx('w-full')}>
|
||||
<div className={clsx('grid grid-cols-[1fr_auto] gap-1.5 w-full')}>
|
||||
<div className="flex [&>*]:!text-[13px] gap-1.5">
|
||||
<SystemViewStandalone
|
||||
className="!text-[13px]"
|
||||
security={security}
|
||||
system_class={system_class}
|
||||
solar_system_id={parseInt(item.dynamic.id)}
|
||||
class_title={class_title}
|
||||
solar_system_name={`${solar_system_name}`}
|
||||
region_name={region_name}
|
||||
nameClassName="font-semibold"
|
||||
/>
|
||||
|
||||
{(showTempName || showCustomName) && (
|
||||
<div className="grid grid-cols-[auto_1fr] gap-1.5 text-stone-400 text-[12px]">
|
||||
{showTempName && (
|
||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{item.dynamic.temporary_name}
|
||||
</span>
|
||||
)}
|
||||
{showCustomName && (
|
||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap">{item.dynamic.name}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{effect_name && isWH && (
|
||||
<WHEffectView
|
||||
effectName={effect_name}
|
||||
effectPower={effect_power}
|
||||
className={classes.SearchItemEffect}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
{isWH && (
|
||||
<div className="flex gap-1 grow justify-between !text-[13px]">
|
||||
<div></div>
|
||||
<div className="flex gap-1">
|
||||
{sortedStatics.map(x => (
|
||||
<WHClassView key={x} whClassName={x} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-1.5 text-[13px] pl-[2px] items-center h-[20px]">
|
||||
<LocalCounter
|
||||
disableInteractive
|
||||
className="[&_span]:!text-[12px] [&_i]:!text-[13px]"
|
||||
hasUserCharacters={hasOnlineUserCharacters}
|
||||
localCounterCharacters={onlineCharacters}
|
||||
/>
|
||||
<LocalCounter
|
||||
disableInteractive
|
||||
className="[&_span]:!text-[12px] [&_i]:!text-[13px] text-stone-[400]"
|
||||
contentClassName={clsx('!text-stone-500', { ['!text-yellow-600']: hasOfflineUserCharacters })}
|
||||
hasUserCharacters={hasOfflineUserCharacters}
|
||||
localCounterCharacters={offlineCharacters}
|
||||
/>
|
||||
{item.dynamic.locked && <i className={clsx(PrimeIcons.LOCK, 'text-[11px] relative top-[1px]')} />}
|
||||
{hubs.includes(solar_system_id.toString()) && (
|
||||
<i className={clsx(PrimeIcons.MAP_MARKER, 'text-[11px] relative top-[1px]')} />
|
||||
)}
|
||||
{item.dynamic.comments_count != null && item.dynamic.comments_count > 0 && (
|
||||
<WdTooltipWrapper
|
||||
position={TooltipPosition.top}
|
||||
content={`[${item.dynamic.comments_count}] Comments in System - click to system to see it in Comments Widget`}
|
||||
>
|
||||
<i className={clsx(PrimeIcons.COMMENT, 'text-[11px] relative top-[1px]')} />
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
{item.dynamic.description != null && item.dynamic.description !== '' && (
|
||||
<WdTooltipWrapper
|
||||
position={TooltipPosition.top}
|
||||
content={`System have description - click to system to see it in Info Widget`}
|
||||
>
|
||||
<i
|
||||
className={clsx(
|
||||
'pi hero-chat-bubble-bottom-center-text w-[14px] h-[14px]',
|
||||
'text-[8px] relative top-[-1px]',
|
||||
)}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
{/*kek*/}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[characters, hubs, userCharacters, wormholesData],
|
||||
);
|
||||
};
|
||||
|
||||
export interface TopSearchSidebarProps {
|
||||
show: boolean;
|
||||
onHide: () => void;
|
||||
}
|
||||
|
||||
export const TopSearchSidebar = ({ show, onHide }: TopSearchSidebarProps) => {
|
||||
const [searchVal, setSearchVal] = useState('');
|
||||
|
||||
// eslint-disable-next-line
|
||||
const inputRef = useRef<any>();
|
||||
|
||||
const {
|
||||
data: { systems },
|
||||
} = useMapRootState();
|
||||
|
||||
const itemTemplate = useItemTemplate();
|
||||
|
||||
const systemsCompiled = useMemo<CompiledSystem[]>(() => {
|
||||
return systems.map(x => ({
|
||||
dynamic: x,
|
||||
static: getSystemStaticInfo(x.id),
|
||||
}));
|
||||
}, [systems]);
|
||||
|
||||
const onShow = useCallback(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
let out = systemsCompiled;
|
||||
|
||||
out = out.sort((a, b) => {
|
||||
// 1. Status > 0 — always on top and ASC
|
||||
if (a.dynamic.status > 0 && b.dynamic.status > 0) return a.dynamic.status - b.dynamic.status;
|
||||
if (a.dynamic.status > 0) return -1;
|
||||
if (b.dynamic.status > 0) return 1;
|
||||
|
||||
// 2. IF status = 0, J priority
|
||||
const aStartsWithJ = a.dynamic.name?.startsWith('J') ?? false;
|
||||
const bStartsWithJ = b.dynamic.name?.startsWith('J') ?? false;
|
||||
|
||||
if (aStartsWithJ && !bStartsWithJ) return -1;
|
||||
if (!aStartsWithJ && bStartsWithJ) return 1;
|
||||
|
||||
// 3. IF both starts with J or not - sort by name
|
||||
const nameA = a.dynamic.name ?? '';
|
||||
const nameB = b.dynamic.name ?? '';
|
||||
return nameA.localeCompare(nameB);
|
||||
});
|
||||
|
||||
const normalized = searchVal.toLowerCase();
|
||||
|
||||
if (searchVal !== '') {
|
||||
out = out.filter(x => {
|
||||
if (x.static?.solar_system_name.toLowerCase().includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (x.dynamic.name?.toLowerCase().includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (x.dynamic.temporary_name?.toLowerCase().includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
return out;
|
||||
}, [searchVal, systemsCompiled]);
|
||||
|
||||
return (
|
||||
<Sidebar
|
||||
className={clsx(classes.Sidebar, 'bg-neutral-900 !p-[0px] w-[500px]')}
|
||||
visible={show}
|
||||
position="right"
|
||||
onShow={onShow}
|
||||
onHide={onHide}
|
||||
modal={false}
|
||||
header={`Search [${filtered.length}]`}
|
||||
icons={<></>}
|
||||
>
|
||||
<div className={clsx('grid grid-rows-[auto_1fr] gap-y-[8px] h-full')}>
|
||||
<div className={'flex justify-between items-center gap-2 px-2 pt-1'}>
|
||||
<IconField className="w-full">
|
||||
{searchVal.length > 0 && (
|
||||
<WdImgButton
|
||||
className="pi pi-trash"
|
||||
textSize={WdImageSize.large}
|
||||
tooltip={{
|
||||
content: 'Clear',
|
||||
className: 'pi p-input-icon',
|
||||
position: TooltipPosition.top,
|
||||
}}
|
||||
onClick={() => setSearchVal('')}
|
||||
/>
|
||||
)}
|
||||
<InputText
|
||||
id="label"
|
||||
className="w-full"
|
||||
aria-describedby="label"
|
||||
ref={inputRef}
|
||||
autoComplete="off"
|
||||
value={searchVal}
|
||||
placeholder="Type To Search"
|
||||
onChange={e => setSearchVal(e.target.value)}
|
||||
/>
|
||||
</IconField>
|
||||
</div>
|
||||
|
||||
<VirtualScroller
|
||||
items={filtered}
|
||||
itemSize={48}
|
||||
itemTemplate={itemTemplate}
|
||||
className={clsx(
|
||||
classes.VirtualScroller,
|
||||
'w-full h-full overflow-x-hidden overflow-y-auto custom-scrollbar select-none',
|
||||
'[&>div]:w-full',
|
||||
)}
|
||||
autoSize={false}
|
||||
/>
|
||||
</div>
|
||||
</Sidebar>
|
||||
);
|
||||
};
|
||||
|
||||
interface TopSearchProps {
|
||||
customBtn?: (open: () => void) => React.ReactNode;
|
||||
}
|
||||
|
||||
export const TopSearch = ({ customBtn }: TopSearchProps) => {
|
||||
const [openAddSystem, setOpenAddSystem] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
{customBtn != null && customBtn(() => setOpenAddSystem(true))}
|
||||
{customBtn == null && (
|
||||
<button
|
||||
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent px-2 relative left-1"
|
||||
type="button"
|
||||
onClick={() => setOpenAddSystem(true)}
|
||||
>
|
||||
<i className="pi pi-search text-lg"></i>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<TopSearchSidebar show={openAddSystem} onHide={() => setOpenAddSystem(false)} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './TopSearch.tsx';
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Column } from 'primereact/column';
|
||||
import { CharacterCard } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { DataTable } from 'primereact/datatable';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { TrackingCharacter } from '@/hooks/Mapper/types';
|
||||
import { useTracking } from '@/hooks/Mapper/components/mapRootContent/components/TrackingDialog/TrackingProvider.tsx';
|
||||
|
||||
export const TrackingCharactersList = () => {
|
||||
const [selected, setSelected] = useState<TrackingCharacter[]>([]);
|
||||
const { trackingCharacters, updateTracking } = useTracking();
|
||||
const { trackingCharacters, main, following, updateTracking } = useTracking();
|
||||
const refVars = useRef({ trackingCharacters });
|
||||
refVars.current = { trackingCharacters };
|
||||
|
||||
@@ -20,9 +20,31 @@ export const TrackingCharactersList = () => {
|
||||
[updateTracking],
|
||||
);
|
||||
|
||||
const items = useMemo(() => {
|
||||
let out = trackingCharacters;
|
||||
|
||||
out = out.sort((a, b) => {
|
||||
const aId = a.character.eve_id;
|
||||
const bId = b.character.eve_id;
|
||||
|
||||
// 1. main always first
|
||||
if (aId === main && bId !== main) return -1;
|
||||
if (bId === main && aId !== main) return 1;
|
||||
|
||||
// 2. following after main
|
||||
if (aId === following && bId !== following) return -1;
|
||||
if (bId === following && aId !== following) return 1;
|
||||
|
||||
// 3. sort by name
|
||||
return a.character.name.localeCompare(b.character.name);
|
||||
});
|
||||
|
||||
return out;
|
||||
}, [trackingCharacters, main, following]);
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
value={trackingCharacters}
|
||||
value={items}
|
||||
size="small"
|
||||
selectionMode={null}
|
||||
selection={selected}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Connections } from '@/hooks/Mapper/components/mapRootContent/components
|
||||
import { ContextMenuSystemMultiple, useContextMenuSystemMultipleHandlers } from '../contexts/ContextMenuSystemMultiple';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { Node, useReactFlow, XYPosition } from 'reactflow';
|
||||
import { Node, useReactFlow, Viewport, XYPosition } from 'reactflow';
|
||||
|
||||
import { useCommandsSystems } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { emitMapEvent, useMapEventListener } from '@/hooks/Mapper/events';
|
||||
@@ -48,7 +48,7 @@ export const MapWrapper = () => {
|
||||
linkSignatureToSystem,
|
||||
systemSignatures,
|
||||
},
|
||||
storedSettings: { interfaceSettings, settingsLocal },
|
||||
storedSettings: { interfaceSettings, settingsLocal, mapSettings, mapSettingsUpdate },
|
||||
} = useMapRootState();
|
||||
|
||||
const {
|
||||
@@ -83,8 +83,17 @@ export const MapWrapper = () => {
|
||||
systems,
|
||||
systemSignatures,
|
||||
deleteSystems,
|
||||
mapSettingsUpdate,
|
||||
});
|
||||
ref.current = { selectedConnections, selectedSystems, systemContextProps, systems, systemSignatures, deleteSystems };
|
||||
ref.current = {
|
||||
selectedConnections,
|
||||
selectedSystems,
|
||||
systemContextProps,
|
||||
systems,
|
||||
systemSignatures,
|
||||
deleteSystems,
|
||||
mapSettingsUpdate,
|
||||
};
|
||||
|
||||
useMapEventListener(event => {
|
||||
runCommand(event);
|
||||
@@ -121,6 +130,10 @@ export const MapWrapper = () => {
|
||||
[update],
|
||||
);
|
||||
|
||||
const handleChangeViewport = useCallback((viewport: Viewport) => {
|
||||
ref.current.mapSettingsUpdate({ viewport });
|
||||
}, []);
|
||||
|
||||
const handleCommand: OutCommandHandler = useCallback(
|
||||
event => {
|
||||
switch (event.type) {
|
||||
@@ -259,6 +272,7 @@ export const MapWrapper = () => {
|
||||
onConnectionInfoClick={handleConnectionDbClick}
|
||||
onSystemContextMenu={handleSystemContextMenu}
|
||||
onSelectionContextMenu={handleSystemMultipleContext}
|
||||
onChangeViewport={handleChangeViewport}
|
||||
minimapClasses={minimapClasses}
|
||||
isShowMinimap={showMinimap}
|
||||
showKSpaceBG={isShowKSpace}
|
||||
@@ -270,6 +284,7 @@ export const MapWrapper = () => {
|
||||
onAddSystem={onAddSystem}
|
||||
minimapPlacement={minimapPosition}
|
||||
localShowShipName={settingsLocal.showShipName}
|
||||
defaultViewport={mapSettings.viewport}
|
||||
/>
|
||||
|
||||
{openSettings != null && (
|
||||
|
||||
@@ -14,6 +14,7 @@ export type SystemViewStandaloneStatic = Pick<
|
||||
export type SystemViewStandaloneProps = {
|
||||
hideRegion?: boolean;
|
||||
customName?: string;
|
||||
nameClassName?: string;
|
||||
compact?: boolean;
|
||||
onContextMenu?(e: MouseEvent, systemId: string): void;
|
||||
} & WithClassName &
|
||||
@@ -23,6 +24,7 @@ export type SystemViewStandaloneProps = {
|
||||
|
||||
export const SystemViewStandalone = ({
|
||||
className,
|
||||
nameClassName,
|
||||
hideRegion,
|
||||
customName,
|
||||
class_title,
|
||||
@@ -57,10 +59,14 @@ export const SystemViewStandalone = ({
|
||||
>
|
||||
<span className={clsx(classTitleColor)}>{class_title}</span>
|
||||
<span
|
||||
className={clsx('text-gray-200 whitespace-nowrap', {
|
||||
['overflow-hidden text-ellipsis']: compact,
|
||||
[classes.CompactName]: compact,
|
||||
})}
|
||||
className={clsx(
|
||||
'text-gray-200 whitespace-nowrap',
|
||||
{
|
||||
['overflow-hidden text-ellipsis']: compact,
|
||||
[classes.CompactName]: compact,
|
||||
},
|
||||
nameClassName,
|
||||
)}
|
||||
>
|
||||
{customName ?? solar_system_name}
|
||||
</span>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import classes from './WHClassView.module.scss';
|
||||
import { Tooltip } from 'primereact/tooltip';
|
||||
import clsx from 'clsx';
|
||||
import { InfoDrawer } from '@/hooks/Mapper/components/ui-kit/InfoDrawer';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { WORMHOLE_CLASS_STYLES, WORMHOLES_ADDITIONAL_INFO } from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import { useMemo } from 'react';
|
||||
import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
const prepareMass = (mass: number) => {
|
||||
if (mass === 0) {
|
||||
@@ -45,19 +45,28 @@ export const WHClassView = ({
|
||||
const whClass = useMemo(() => WORMHOLES_ADDITIONAL_INFO[whData.dest], [whData.dest]);
|
||||
const whClassStyle = WORMHOLE_CLASS_STYLES[whClass?.wormholeClassID] ?? '';
|
||||
|
||||
const uid = useMemo(() => new Date().getTime().toString(), []);
|
||||
const content = (
|
||||
<div
|
||||
className={clsx(classes.WHClassViewContent, { [classes.NoOffset]: noOffset }, 'wh-name select-none cursor-help')}
|
||||
>
|
||||
{!hideWhClassName && <span className={clsx({ [whClassStyle]: highlightName })}>{whClassName}</span>}
|
||||
{!hideWhClass && whClass && (
|
||||
<span className={clsx(classes.WHClassName, whClassStyle, classNameWh)}>
|
||||
{useShortTitle ? whClass.shortTitle : whClass.shortName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (hideTooltip) {
|
||||
return <div className={clsx(classes.WHClassViewRoot, className)}>{content}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx(classes.WHClassViewRoot, className)}>
|
||||
{!hideTooltip && (
|
||||
<Tooltip
|
||||
target={`.wh-name${whClassName}${uid}`}
|
||||
position="right"
|
||||
mouseTrack
|
||||
mouseTrackLeft={20}
|
||||
mouseTrackTop={30}
|
||||
className="border border-green-300 rounded border-opacity-10 bg-stone-900 bg-opacity-90 "
|
||||
>
|
||||
<WdTooltipWrapper
|
||||
position={TooltipPosition.bottom}
|
||||
content={
|
||||
<div className="flex gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<InfoDrawer title="Total mass">{prepareMass(whData.total_mass)}</InfoDrawer>
|
||||
@@ -68,24 +77,10 @@ export const WHClassView = ({
|
||||
<InfoDrawer title="Mass regen">{prepareMass(whData.mass_regen)}</InfoDrawer>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={clsx(
|
||||
classes.WHClassViewContent,
|
||||
{ [classes.NoOffset]: noOffset },
|
||||
'wh-name select-none cursor-help',
|
||||
`wh-name${whClassName}${uid}`,
|
||||
)}
|
||||
}
|
||||
>
|
||||
{!hideWhClassName && <span className={clsx({ [whClassStyle]: highlightName })}>{whClassName}</span>}
|
||||
{!hideWhClass && whClass && (
|
||||
<span className={clsx(classes.WHClassName, whClassStyle, classNameWh)}>
|
||||
{useShortTitle ? whClass.shortTitle : whClass.shortName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{content}
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,6 +7,8 @@ export enum SESSION_KEY {
|
||||
routes = 'routes',
|
||||
}
|
||||
|
||||
export const SYSTEM_FOCUSED_LIFETIME = 10000;
|
||||
|
||||
export const GRADIENT_MENU_ACTIVE_CLASSES = 'bg-gradient-to-br from-transparent/10 to-fuchsia-300/10';
|
||||
|
||||
export enum Regions {
|
||||
@@ -151,3 +153,11 @@ export const MINIMAP_PLACEMENT_MAP = {
|
||||
[PingsPlacement.rightBottom]: 'bottom-right',
|
||||
[PingsPlacement.leftBottom]: 'bottom-left',
|
||||
};
|
||||
|
||||
export const SPACE_TO_CLASS: Record<string, string> = {
|
||||
[Spaces.Caldari]: 'Caldaria',
|
||||
[Spaces.Matar]: 'Mataria',
|
||||
[Spaces.Amarr]: 'Amarria',
|
||||
[Spaces.Gallente]: 'Gallente',
|
||||
[Spaces.Pochven]: 'Pochven',
|
||||
};
|
||||
|
||||
@@ -5,3 +5,4 @@ export * from './useHotkey';
|
||||
export * from './usePageVisibility';
|
||||
export * from './useSkipContextMenu';
|
||||
export * from './useThrottle';
|
||||
export * from './useStableValue';
|
||||
|
||||
8
assets/js/hooks/Mapper/hooks/useStableValue.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { useRef } from 'react';
|
||||
import fastDeepEuqal from 'fast-deep-equal';
|
||||
|
||||
export const useStableValue = <T>(value: T): T => {
|
||||
const ref = useRef(value);
|
||||
if (!fastDeepEuqal(ref.current, value)) ref.current = value;
|
||||
return ref.current;
|
||||
};
|
||||
@@ -23,12 +23,14 @@ import {
|
||||
InterfaceStoredSettings,
|
||||
KillsWidgetSettings,
|
||||
LocalWidgetSettings,
|
||||
MapSettings,
|
||||
MapUserSettings,
|
||||
OnTheMapSettingsType,
|
||||
RoutesType,
|
||||
} from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import {
|
||||
DEFAULT_KILLS_WIDGET_SETTINGS,
|
||||
DEFAULT_MAP_SETTINGS,
|
||||
DEFAULT_ON_THE_MAP_SETTINGS,
|
||||
DEFAULT_ROUTES_SETTINGS,
|
||||
DEFAULT_WIDGET_LOCAL_SETTINGS,
|
||||
@@ -127,6 +129,8 @@ export interface MapRootContextProps {
|
||||
settingsOnTheMapUpdate: Dispatch<SetStateAction<OnTheMapSettingsType>>;
|
||||
settingsKills: KillsWidgetSettings;
|
||||
settingsKillsUpdate: Dispatch<SetStateAction<KillsWidgetSettings>>;
|
||||
mapSettings: MapSettings;
|
||||
mapSettingsUpdate: Dispatch<SetStateAction<MapSettings>>;
|
||||
isReady: boolean;
|
||||
hasOldSettings: boolean;
|
||||
getSettingsForExport(): string | undefined;
|
||||
@@ -172,6 +176,8 @@ const MapRootContext = createContext<MapRootContextProps>({
|
||||
settingsOnTheMapUpdate: () => null,
|
||||
settingsKills: DEFAULT_KILLS_WIDGET_SETTINGS,
|
||||
settingsKillsUpdate: () => null,
|
||||
mapSettings: DEFAULT_MAP_SETTINGS,
|
||||
mapSettingsUpdate: () => null,
|
||||
isReady: false,
|
||||
hasOldSettings: false,
|
||||
getSettingsForExport: () => '',
|
||||
|
||||
@@ -3,16 +3,13 @@ import {
|
||||
InterfaceStoredSettings,
|
||||
KillsWidgetSettings,
|
||||
LocalWidgetSettings,
|
||||
MapSettings,
|
||||
MiniMapPlacement,
|
||||
OnTheMapSettingsType,
|
||||
PingsPlacement,
|
||||
RoutesType,
|
||||
} from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import {
|
||||
CURRENT_WINDOWS_VERSION,
|
||||
DEFAULT_WIDGETS,
|
||||
STORED_VISIBLE_WIDGETS_DEFAULT,
|
||||
} from '@/hooks/Mapper/components/mapInterface/constants.tsx';
|
||||
import { DEFAULT_WIDGETS, STORED_VISIBLE_WIDGETS_DEFAULT } from '@/hooks/Mapper/components/mapInterface/constants.tsx';
|
||||
|
||||
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
|
||||
isShowMenu: false,
|
||||
@@ -43,25 +40,25 @@ export const DEFAULT_ROUTES_SETTINGS: RoutesType = {
|
||||
export const DEFAULT_WIDGET_LOCAL_SETTINGS: LocalWidgetSettings = {
|
||||
compact: true,
|
||||
showOffline: false,
|
||||
version: 0,
|
||||
showShipName: false,
|
||||
};
|
||||
|
||||
export const DEFAULT_ON_THE_MAP_SETTINGS: OnTheMapSettingsType = {
|
||||
hideOffline: false,
|
||||
version: 0,
|
||||
};
|
||||
|
||||
export const DEFAULT_KILLS_WIDGET_SETTINGS: KillsWidgetSettings = {
|
||||
showAll: false,
|
||||
whOnly: true,
|
||||
excludedSystems: [],
|
||||
version: 2,
|
||||
timeRange: 4,
|
||||
};
|
||||
|
||||
export const DEFAULT_MAP_SETTINGS: MapSettings = {
|
||||
viewport: { zoom: 1, x: 0, y: 0 },
|
||||
};
|
||||
|
||||
export const getDefaultWidgetProps = () => ({
|
||||
version: CURRENT_WINDOWS_VERSION,
|
||||
visible: STORED_VISIBLE_WIDGETS_DEFAULT,
|
||||
windows: DEFAULT_WIDGETS,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { MapUserSettings, SettingsTypes, SettingsWrapper } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import {
|
||||
DEFAULT_KILLS_WIDGET_SETTINGS,
|
||||
DEFAULT_MAP_SETTINGS,
|
||||
DEFAULT_ON_THE_MAP_SETTINGS,
|
||||
DEFAULT_ROUTES_SETTINGS,
|
||||
DEFAULT_WIDGET_LOCAL_SETTINGS,
|
||||
getDefaultWidgetProps,
|
||||
STORED_INTERFACE_DEFAULT_VALUES,
|
||||
} from '@/hooks/Mapper/mapRootProvider/constants.ts';
|
||||
import { DEFAULT_SIGNATURE_SETTINGS } from '@/hooks/Mapper/constants/signatures.ts';
|
||||
import { STORED_SETTINGS_VERSION } from '@/hooks/Mapper/mapRootProvider/version.ts';
|
||||
|
||||
// TODO - we need provide and compare version
|
||||
export const createWidgetSettings = <T>(settings: T) => {
|
||||
return settings;
|
||||
};
|
||||
|
||||
export const createDefaultStoredSettings = (): MapUserSettings => {
|
||||
return {
|
||||
version: STORED_SETTINGS_VERSION,
|
||||
migratedFromOld: true,
|
||||
killsWidget: createWidgetSettings(DEFAULT_KILLS_WIDGET_SETTINGS),
|
||||
localWidget: createWidgetSettings(DEFAULT_WIDGET_LOCAL_SETTINGS),
|
||||
widgets: createWidgetSettings(getDefaultWidgetProps()),
|
||||
routes: createWidgetSettings(DEFAULT_ROUTES_SETTINGS),
|
||||
onTheMap: createWidgetSettings(DEFAULT_ON_THE_MAP_SETTINGS),
|
||||
signaturesWidget: createWidgetSettings(DEFAULT_SIGNATURE_SETTINGS),
|
||||
interface: createWidgetSettings(STORED_INTERFACE_DEFAULT_VALUES),
|
||||
map: createWidgetSettings(DEFAULT_MAP_SETTINGS),
|
||||
};
|
||||
};
|
||||
|
||||
// INFO - in another case need to generate complex type - but looks like it unnecessary
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const getDefaultSettingsByType = (type: SettingsTypes): SettingsWrapper<any> => {
|
||||
switch (type) {
|
||||
case SettingsTypes.killsWidget:
|
||||
return createWidgetSettings(DEFAULT_KILLS_WIDGET_SETTINGS);
|
||||
case SettingsTypes.localWidget:
|
||||
return createWidgetSettings(DEFAULT_WIDGET_LOCAL_SETTINGS);
|
||||
case SettingsTypes.widgets:
|
||||
return createWidgetSettings(getDefaultWidgetProps());
|
||||
case SettingsTypes.routes:
|
||||
return createWidgetSettings(DEFAULT_ROUTES_SETTINGS);
|
||||
case SettingsTypes.onTheMap:
|
||||
return createWidgetSettings(DEFAULT_ON_THE_MAP_SETTINGS);
|
||||
case SettingsTypes.signaturesWidget:
|
||||
return createWidgetSettings(DEFAULT_SIGNATURE_SETTINGS);
|
||||
case SettingsTypes.interface:
|
||||
return createWidgetSettings(STORED_INTERFACE_DEFAULT_VALUES);
|
||||
case SettingsTypes.map:
|
||||
return createWidgetSettings(DEFAULT_MAP_SETTINGS);
|
||||
}
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
import { MapUserSettings } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import {
|
||||
DEFAULT_KILLS_WIDGET_SETTINGS,
|
||||
DEFAULT_ON_THE_MAP_SETTINGS,
|
||||
DEFAULT_ROUTES_SETTINGS,
|
||||
DEFAULT_WIDGET_LOCAL_SETTINGS,
|
||||
getDefaultWidgetProps,
|
||||
STORED_INTERFACE_DEFAULT_VALUES,
|
||||
} from '@/hooks/Mapper/mapRootProvider/constants.ts';
|
||||
import { DEFAULT_SIGNATURE_SETTINGS } from '@/hooks/Mapper/constants/signatures.ts';
|
||||
|
||||
// TODO - we need provide and compare version
|
||||
const createWidgetSettingsWithVersion = <T>(settings: T) => {
|
||||
return {
|
||||
version: 0,
|
||||
settings,
|
||||
};
|
||||
};
|
||||
|
||||
export const createDefaultWidgetSettings = (): MapUserSettings => {
|
||||
return {
|
||||
killsWidget: createWidgetSettingsWithVersion(DEFAULT_KILLS_WIDGET_SETTINGS),
|
||||
localWidget: createWidgetSettingsWithVersion(DEFAULT_WIDGET_LOCAL_SETTINGS),
|
||||
widgets: createWidgetSettingsWithVersion(getDefaultWidgetProps()),
|
||||
routes: createWidgetSettingsWithVersion(DEFAULT_ROUTES_SETTINGS),
|
||||
onTheMap: createWidgetSettingsWithVersion(DEFAULT_ON_THE_MAP_SETTINGS),
|
||||
signaturesWidget: createWidgetSettingsWithVersion(DEFAULT_SIGNATURE_SETTINGS),
|
||||
interface: createWidgetSettingsWithVersion(STORED_INTERFACE_DEFAULT_VALUES),
|
||||
};
|
||||
};
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
MapUserSettingsStructure,
|
||||
RemoteAdminSettingsResponse,
|
||||
} from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { createDefaultWidgetSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultWidgetSettings.ts';
|
||||
import { parseMapUserSettings } from '@/hooks/Mapper/components/helpers';
|
||||
import { createDefaultStoredSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultStoredSettings.ts';
|
||||
import { applyMigrations } from '@/hooks/Mapper/mapRootProvider/migrations';
|
||||
|
||||
interface UseActualizeRemoteMapSettingsProps {
|
||||
outCommand: OutCommandHandler;
|
||||
@@ -37,14 +37,14 @@ export const useActualizeRemoteMapSettings = ({
|
||||
}
|
||||
|
||||
if (res?.default_settings == null) {
|
||||
applySettings(createDefaultWidgetSettings());
|
||||
applySettings(createDefaultStoredSettings());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
applySettings(parseMapUserSettings(res.default_settings));
|
||||
applySettings(applyMigrations(JSON.parse(res.default_settings) || createDefaultStoredSettings()));
|
||||
} catch (error) {
|
||||
applySettings(createDefaultWidgetSettings());
|
||||
applySettings(createDefaultStoredSettings());
|
||||
}
|
||||
}, [outCommand]);
|
||||
|
||||
|
||||
@@ -63,123 +63,127 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
const { pingAdded, pingCancelled } = useCommandPings();
|
||||
const { characterActivityData, trackingCharactersData, userSettingsUpdated } = useCommandsActivity();
|
||||
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
command(type, data) {
|
||||
switch (type) {
|
||||
case Commands.init: // USED
|
||||
mapInit(data as CommandInit);
|
||||
break;
|
||||
case Commands.addSystems: // USED
|
||||
addSystems(data as CommandAddSystems);
|
||||
break;
|
||||
case Commands.updateSystems: // USED
|
||||
updateSystems(data as CommandUpdateSystems);
|
||||
break;
|
||||
case Commands.removeSystems: // USED
|
||||
removeSystems(data as CommandRemoveSystems);
|
||||
break;
|
||||
case Commands.addConnections: // USED
|
||||
addConnections(data as CommandAddConnections);
|
||||
break;
|
||||
case Commands.removeConnections: // USED
|
||||
removeConnections(data as CommandRemoveConnections);
|
||||
break;
|
||||
case Commands.updateConnection: // USED
|
||||
updateConnection(data as CommandUpdateConnection);
|
||||
break;
|
||||
case Commands.charactersUpdated: // USED
|
||||
charactersUpdated(data as CommandCharactersUpdated);
|
||||
break;
|
||||
case Commands.characterAdded: // USED
|
||||
characterAdded(data as CommandCharacterAdded);
|
||||
break;
|
||||
case Commands.characterRemoved: // USED
|
||||
characterRemoved(data as CommandCharacterRemoved);
|
||||
break;
|
||||
case Commands.characterUpdated: // USED
|
||||
characterUpdated(data as CommandCharacterUpdated);
|
||||
break;
|
||||
case Commands.presentCharacters: // USED
|
||||
presentCharacters(data as CommandPresentCharacters);
|
||||
break;
|
||||
case Commands.mapUpdated: // USED
|
||||
mapUpdated(data as CommandMapUpdated);
|
||||
break;
|
||||
case Commands.routes:
|
||||
mapRoutes(data as CommandRoutes);
|
||||
break;
|
||||
case Commands.userRoutes:
|
||||
mapUserRoutes(data as CommandRoutes);
|
||||
break;
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => {
|
||||
return {
|
||||
command(type, data) {
|
||||
switch (type) {
|
||||
case Commands.init: // USED
|
||||
mapInit(data as CommandInit);
|
||||
break;
|
||||
case Commands.addSystems: // USED
|
||||
addSystems(data as CommandAddSystems);
|
||||
break;
|
||||
case Commands.updateSystems: // USED
|
||||
updateSystems(data as CommandUpdateSystems);
|
||||
break;
|
||||
case Commands.removeSystems: // USED
|
||||
removeSystems(data as CommandRemoveSystems);
|
||||
break;
|
||||
case Commands.addConnections: // USED
|
||||
addConnections(data as CommandAddConnections);
|
||||
break;
|
||||
case Commands.removeConnections: // USED
|
||||
removeConnections(data as CommandRemoveConnections);
|
||||
break;
|
||||
case Commands.updateConnection: // USED
|
||||
updateConnection(data as CommandUpdateConnection);
|
||||
break;
|
||||
case Commands.charactersUpdated: // USED
|
||||
charactersUpdated(data as CommandCharactersUpdated);
|
||||
break;
|
||||
case Commands.characterAdded: // USED
|
||||
characterAdded(data as CommandCharacterAdded);
|
||||
break;
|
||||
case Commands.characterRemoved: // USED
|
||||
characterRemoved(data as CommandCharacterRemoved);
|
||||
break;
|
||||
case Commands.characterUpdated: // USED
|
||||
characterUpdated(data as CommandCharacterUpdated);
|
||||
break;
|
||||
case Commands.presentCharacters: // USED
|
||||
presentCharacters(data as CommandPresentCharacters);
|
||||
break;
|
||||
case Commands.mapUpdated: // USED
|
||||
mapUpdated(data as CommandMapUpdated);
|
||||
break;
|
||||
case Commands.routes:
|
||||
mapRoutes(data as CommandRoutes);
|
||||
break;
|
||||
case Commands.userRoutes:
|
||||
mapUserRoutes(data as CommandRoutes);
|
||||
break;
|
||||
|
||||
case Commands.signaturesUpdated: // USED
|
||||
updateSystemSignatures(data as CommandSignaturesUpdated);
|
||||
break;
|
||||
case Commands.signaturesUpdated: // USED
|
||||
updateSystemSignatures(data as CommandSignaturesUpdated);
|
||||
break;
|
||||
|
||||
case Commands.linkSignatureToSystem: // USED
|
||||
setTimeout(() => {
|
||||
updateLinkSignatureToSystem(data as CommandLinkSignatureToSystem);
|
||||
}, 200);
|
||||
break;
|
||||
case Commands.linkSignatureToSystem: // USED
|
||||
setTimeout(() => {
|
||||
updateLinkSignatureToSystem(data as CommandLinkSignatureToSystem);
|
||||
}, 200);
|
||||
break;
|
||||
|
||||
case Commands.centerSystem: // USED
|
||||
// do nothing here
|
||||
break;
|
||||
case Commands.centerSystem: // USED
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.selectSystem: // USED
|
||||
// do nothing here
|
||||
break;
|
||||
case Commands.selectSystem: // USED
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.killsUpdated:
|
||||
// do nothing here
|
||||
break;
|
||||
case Commands.killsUpdated:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.detailedKillsUpdated:
|
||||
updateDetailedKills(data as Record<string, DetailedKill[]>);
|
||||
break;
|
||||
case Commands.detailedKillsUpdated:
|
||||
updateDetailedKills(data as Record<string, DetailedKill[]>);
|
||||
break;
|
||||
|
||||
case Commands.characterActivityData:
|
||||
characterActivityData(data as CommandCharacterActivityData);
|
||||
break;
|
||||
case Commands.characterActivityData:
|
||||
characterActivityData(data as CommandCharacterActivityData);
|
||||
break;
|
||||
|
||||
case Commands.trackingCharactersData:
|
||||
trackingCharactersData(data as CommandTrackingCharactersData);
|
||||
break;
|
||||
case Commands.trackingCharactersData:
|
||||
trackingCharactersData(data as CommandTrackingCharactersData);
|
||||
break;
|
||||
|
||||
case Commands.updateActivity:
|
||||
break;
|
||||
case Commands.updateActivity:
|
||||
break;
|
||||
|
||||
case Commands.updateTracking:
|
||||
break;
|
||||
case Commands.updateTracking:
|
||||
break;
|
||||
|
||||
case Commands.userSettingsUpdated:
|
||||
userSettingsUpdated(data as CommandUserSettingsUpdated);
|
||||
break;
|
||||
case Commands.userSettingsUpdated:
|
||||
userSettingsUpdated(data as CommandUserSettingsUpdated);
|
||||
break;
|
||||
|
||||
case Commands.systemCommentAdded:
|
||||
addComment(data as CommandCommentAdd);
|
||||
break;
|
||||
case Commands.systemCommentAdded:
|
||||
addComment(data as CommandCommentAdd);
|
||||
break;
|
||||
|
||||
case Commands.systemCommentRemoved:
|
||||
removeComment(data as CommandCommentRemoved);
|
||||
break;
|
||||
case Commands.systemCommentRemoved:
|
||||
removeComment(data as CommandCommentRemoved);
|
||||
break;
|
||||
|
||||
case Commands.pingAdded:
|
||||
pingAdded(data as CommandPingAdded);
|
||||
break;
|
||||
case Commands.pingAdded:
|
||||
pingAdded(data as CommandPingAdded);
|
||||
break;
|
||||
|
||||
case Commands.pingCancelled:
|
||||
pingCancelled(data as CommandPingCancelled);
|
||||
break;
|
||||
case Commands.pingCancelled:
|
||||
pingCancelled(data as CommandPingCancelled);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
|
||||
break;
|
||||
}
|
||||
|
||||
emitMapEvent({ name: type, data });
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
emitMapEvent({ name: type, data });
|
||||
},
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,9 @@ import { useSettingsValueAndSetter } from '@/hooks/Mapper/mapRootProvider/hooks/
|
||||
import fastDeepEqual from 'fast-deep-equal';
|
||||
import { OutCommandHandler } from '@/hooks/Mapper/types';
|
||||
import { useActualizeRemoteMapSettings } from '@/hooks/Mapper/mapRootProvider/hooks/useActualizeRemoteMapSettings.ts';
|
||||
import { createDefaultWidgetSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultWidgetSettings.ts';
|
||||
import { createDefaultStoredSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultStoredSettings.ts';
|
||||
import { applyMigrations, extractData } from '@/hooks/Mapper/mapRootProvider/migrations';
|
||||
import { LS_KEY, LS_KEY_LEGASY } from '@/hooks/Mapper/mapRootProvider/version.ts';
|
||||
|
||||
const EMPTY_OBJ = {};
|
||||
|
||||
@@ -14,7 +16,7 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const [hasOldSettings, setHasOldSettings] = useState(false);
|
||||
|
||||
const [mapUserSettings, setMapUserSettings] = useLocalStorageState<MapUserSettingsStructure>('map-user-settings', {
|
||||
const [mapUserSettings, setMapUserSettings] = useLocalStorageState<MapUserSettingsStructure>(LS_KEY, {
|
||||
defaultValue: EMPTY_OBJ,
|
||||
});
|
||||
|
||||
@@ -83,13 +85,20 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
|
||||
'killsWidget',
|
||||
);
|
||||
|
||||
const [windowsSettings, setWindowsSettings] = useSettingsValueAndSetter(
|
||||
const [windowsSettings, windowsSettingsUpdate] = useSettingsValueAndSetter(
|
||||
mapUserSettings,
|
||||
setMapUserSettings,
|
||||
map_slug,
|
||||
'widgets',
|
||||
);
|
||||
|
||||
const [mapSettings, mapSettingsUpdate] = useSettingsValueAndSetter(
|
||||
mapUserSettings,
|
||||
setMapUserSettings,
|
||||
map_slug,
|
||||
'map',
|
||||
);
|
||||
|
||||
// HERE we MUST work with migrations
|
||||
useEffect(() => {
|
||||
if (isReady) {
|
||||
@@ -100,36 +109,28 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
|
||||
return;
|
||||
}
|
||||
|
||||
if (mapUserSettings[map_slug] == null) {
|
||||
const currentMapUserSettings = mapUserSettings[map_slug];
|
||||
if (currentMapUserSettings == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO !!!! FROM this date 06.07.2025 - we must work only with migrations
|
||||
// actualizeSettings(STORED_INTERFACE_DEFAULT_VALUES, interfaceSettings, setInterfaceSettings);
|
||||
// actualizeSettings(DEFAULT_ROUTES_SETTINGS, settingsRoutes, settingsRoutesUpdate);
|
||||
// actualizeSettings(DEFAULT_WIDGET_LOCAL_SETTINGS, settingsLocal, settingsLocalUpdate);
|
||||
// actualizeSettings(DEFAULT_SIGNATURE_SETTINGS, settingsSignatures, settingsSignaturesUpdate);
|
||||
// actualizeSettings(DEFAULT_ON_THE_MAP_SETTINGS, settingsOnTheMap, settingsOnTheMapUpdate);
|
||||
// actualizeSettings(DEFAULT_KILLS_WIDGET_SETTINGS, settingsKills, settingsKillsUpdate);
|
||||
try {
|
||||
// INFO: after migrations migratedFromOld always will be true
|
||||
const migratedResult = applyMigrations(
|
||||
!currentMapUserSettings.migratedFromOld ? extractData(LS_KEY_LEGASY) : currentMapUserSettings,
|
||||
);
|
||||
|
||||
setIsReady(true);
|
||||
}, [
|
||||
map_slug,
|
||||
mapUserSettings,
|
||||
interfaceSettings,
|
||||
setInterfaceSettings,
|
||||
settingsRoutes,
|
||||
settingsRoutesUpdate,
|
||||
settingsLocal,
|
||||
settingsLocalUpdate,
|
||||
settingsSignatures,
|
||||
settingsSignaturesUpdate,
|
||||
settingsOnTheMap,
|
||||
settingsOnTheMapUpdate,
|
||||
settingsKills,
|
||||
settingsKillsUpdate,
|
||||
isReady,
|
||||
]);
|
||||
if (!migratedResult) {
|
||||
setIsReady(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setMapUserSettings({ ...mapUserSettings, [map_slug]: migratedResult });
|
||||
setIsReady(true);
|
||||
} catch (error) {
|
||||
setIsReady(true);
|
||||
}
|
||||
}, [isReady, mapUserSettings, map_slug, setMapUserSettings]);
|
||||
|
||||
const checkOldSettings = useCallback(() => {
|
||||
const interfaceSettings = localStorage.getItem('window:interface:settings');
|
||||
@@ -157,7 +158,7 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
|
||||
}, []);
|
||||
|
||||
const resetSettings = useCallback(() => {
|
||||
applySettings(createDefaultWidgetSettings());
|
||||
applySettings(createDefaultStoredSettings());
|
||||
}, [applySettings]);
|
||||
|
||||
return {
|
||||
@@ -177,7 +178,9 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
|
||||
settingsKills,
|
||||
settingsKillsUpdate,
|
||||
windowsSettings,
|
||||
setWindowsSettings,
|
||||
windowsSettingsUpdate,
|
||||
mapSettings,
|
||||
mapSettingsUpdate,
|
||||
|
||||
getSettingsForExport,
|
||||
applySettings,
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { Dispatch, SetStateAction, useCallback, useMemo, useRef } from 'react';
|
||||
import {
|
||||
MapUserSettings,
|
||||
MapUserSettingsStructure,
|
||||
SettingsWithVersion,
|
||||
} from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { MapUserSettings, MapUserSettingsStructure, SettingsWrapper } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
type ExtractSettings<S extends keyof MapUserSettings> =
|
||||
MapUserSettings[S] extends SettingsWithVersion<infer U> ? U : never;
|
||||
type ExtractSettings<S extends keyof MapUserSettings> = MapUserSettings[S] extends SettingsWrapper<infer U> ? U : never;
|
||||
|
||||
type Setter<S extends keyof MapUserSettings> = (
|
||||
value: Partial<ExtractSettings<S>> | ((prev: ExtractSettings<S>) => Partial<ExtractSettings<S>>),
|
||||
version?: number,
|
||||
) => void;
|
||||
|
||||
type GenerateSettingsReturn<S extends keyof MapUserSettings> = [ExtractSettings<S>, Setter<S>];
|
||||
@@ -24,7 +20,7 @@ export const useSettingsValueAndSetter = <S extends keyof MapUserSettings>(
|
||||
if (!mapId) return {} as ExtractSettings<S>;
|
||||
|
||||
const mapSettings = settings[mapId];
|
||||
return (mapSettings?.[setting]?.settings ?? ({} as ExtractSettings<S>)) as ExtractSettings<S>;
|
||||
return (mapSettings?.[setting] ?? ({} as ExtractSettings<S>)) as ExtractSettings<S>;
|
||||
}, [mapId, setting, settings]);
|
||||
|
||||
const refData = useRef({ mapId, setting, setSettings });
|
||||
@@ -37,8 +33,7 @@ export const useSettingsValueAndSetter = <S extends keyof MapUserSettings>(
|
||||
|
||||
setSettings(all => {
|
||||
const currentMap = all[mapId];
|
||||
const prev = currentMap[setting].settings as ExtractSettings<S>;
|
||||
const version = currentMap[setting].version;
|
||||
const prev = currentMap[setting] as ExtractSettings<S>;
|
||||
|
||||
const patch =
|
||||
typeof value === 'function' ? (value as (p: ExtractSettings<S>) => Partial<ExtractSettings<S>>)(prev) : value;
|
||||
@@ -47,10 +42,7 @@ export const useSettingsValueAndSetter = <S extends keyof MapUserSettings>(
|
||||
...all,
|
||||
[mapId]: {
|
||||
...currentMap,
|
||||
[setting]: {
|
||||
version,
|
||||
settings: { ...(prev as any), ...patch } as ExtractSettings<S>,
|
||||
},
|
||||
[setting]: { ...(prev as any), ...patch } as ExtractSettings<S>,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ import { getDefaultWidgetProps } from '@/hooks/Mapper/mapRootProvider/constants.
|
||||
|
||||
export type StoredWindowProps = Omit<WindowProps, 'content'>;
|
||||
export type WindowStoreInfo = {
|
||||
version: number;
|
||||
windows: StoredWindowProps[];
|
||||
visible: WidgetsIds[];
|
||||
viewPort?: { w: number; h: number } | undefined;
|
||||
@@ -16,19 +15,18 @@ export type ToggleWidgetVisibility = (widgetId: WidgetsIds) => void;
|
||||
|
||||
interface UseStoreWidgetsProps {
|
||||
windowsSettings: WindowStoreInfo;
|
||||
setWindowsSettings: Dispatch<SetStateAction<WindowStoreInfo>>;
|
||||
windowsSettingsUpdate: Dispatch<SetStateAction<WindowStoreInfo>>;
|
||||
}
|
||||
|
||||
export const useStoreWidgets = ({ windowsSettings, setWindowsSettings }: UseStoreWidgetsProps) => {
|
||||
const ref = useRef({ windowsSettings, setWindowsSettings });
|
||||
ref.current = { windowsSettings, setWindowsSettings };
|
||||
export const useStoreWidgets = ({ windowsSettings, windowsSettingsUpdate }: UseStoreWidgetsProps) => {
|
||||
const ref = useRef({ windowsSettings, windowsSettingsUpdate });
|
||||
ref.current = { windowsSettings, windowsSettingsUpdate };
|
||||
|
||||
const updateWidgetSettings: WindowsManagerOnChange = useCallback(({ windows, viewPort }) => {
|
||||
const { setWindowsSettings } = ref.current;
|
||||
const { windowsSettingsUpdate } = ref.current;
|
||||
|
||||
setWindowsSettings(({ version, visible /*, windows*/ }: WindowStoreInfo) => {
|
||||
windowsSettingsUpdate(({ visible /*, windows*/ }: WindowStoreInfo) => {
|
||||
return {
|
||||
version,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
windows: DEFAULT_WIDGETS.map(({ content, ...x }) => {
|
||||
const windowProp = windows.find(j => j.id === x.id);
|
||||
@@ -45,9 +43,9 @@ export const useStoreWidgets = ({ windowsSettings, setWindowsSettings }: UseStor
|
||||
}, []);
|
||||
|
||||
const toggleWidgetVisibility: ToggleWidgetVisibility = useCallback(widgetId => {
|
||||
const { setWindowsSettings } = ref.current;
|
||||
const { windowsSettingsUpdate } = ref.current;
|
||||
|
||||
setWindowsSettings(({ visible, windows, ...x }) => {
|
||||
windowsSettingsUpdate(({ visible, windows, ...x }) => {
|
||||
const isCheckedPrev = visible.includes(widgetId);
|
||||
if (!isCheckedPrev) {
|
||||
const maxZIndex = Math.max(...windows.map(w => w.zIndex));
|
||||
@@ -72,7 +70,7 @@ export const useStoreWidgets = ({ windowsSettings, setWindowsSettings }: UseStor
|
||||
});
|
||||
}, []);
|
||||
|
||||
const resetWidgets = useCallback(() => ref.current.setWindowsSettings(getDefaultWidgetProps()), []);
|
||||
const resetWidgets = useCallback(() => ref.current.windowsSettingsUpdate(getDefaultWidgetProps()), []);
|
||||
|
||||
return {
|
||||
windowsSettings,
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { MapUserSettingsStructure } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { STORED_SETTINGS_VERSION } from '@/hooks/Mapper/mapRootProvider/version.ts';
|
||||
import { migrations } from '@/hooks/Mapper/mapRootProvider/migrations/index.ts';
|
||||
import { createDefaultStoredSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultStoredSettings.ts';
|
||||
|
||||
export const extractData = (localStoreKey = 'map-user-settings'): MapUserSettingsStructure | null => {
|
||||
const val = localStorage.getItem(localStoreKey);
|
||||
if (!val) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSON.parse(val);
|
||||
};
|
||||
|
||||
export const applyMigrations = (mapSettings: any) => {
|
||||
let currentMapSettings = { ...mapSettings };
|
||||
|
||||
// INFO if we have NO any data in store expected that we will use default
|
||||
if (!currentMapSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
const direction = STORED_SETTINGS_VERSION - (currentMapSettings.version || 0);
|
||||
if (direction === 0) {
|
||||
if (currentMapSettings.version == null) {
|
||||
return { ...currentMapSettings, version: STORED_SETTINGS_VERSION, migratedFromOld: true };
|
||||
}
|
||||
|
||||
return currentMapSettings;
|
||||
}
|
||||
|
||||
const cmVersion = currentMapSettings.version || 0;
|
||||
|
||||
// downgrade
|
||||
// INFO: when we downgrading - if diff between >= 1 it means was major version
|
||||
if (direction < 0) {
|
||||
// If was minor version - we do nothing
|
||||
if (Math.abs(direction) < 1) {
|
||||
return currentMapSettings;
|
||||
}
|
||||
|
||||
// if was major version - we set default settings
|
||||
return createDefaultStoredSettings();
|
||||
}
|
||||
|
||||
const preparedMigrations = migrations
|
||||
.sort((a, b) => a.to - b.to)
|
||||
.filter(x => x.to > cmVersion && x.to <= STORED_SETTINGS_VERSION);
|
||||
|
||||
for (const migration of preparedMigrations) {
|
||||
const { to, up } = migration;
|
||||
|
||||
const next = up(currentMapSettings);
|
||||
currentMapSettings = { ...next, version: to, migratedFromOld: true };
|
||||
}
|
||||
|
||||
return currentMapSettings;
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
import list from './list';
|
||||
export * from './applyMigrations.ts';
|
||||
|
||||
export const migrations = [...list];
|
||||
@@ -0,0 +1,5 @@
|
||||
import { to_1 } from './to_1.ts';
|
||||
import { to_2 } from './to_2.ts';
|
||||
import { MigrationStructure } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export default [to_1, to_2] as MigrationStructure[];
|
||||
@@ -0,0 +1,10 @@
|
||||
import { MigrationStructure } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export const to_1: MigrationStructure = {
|
||||
to: 1,
|
||||
up: (prev: any) => {
|
||||
return Object.keys(prev).reduce((acc, k) => {
|
||||
return { ...acc, [k]: prev[k].settings };
|
||||
}, Object.create(null));
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { MigrationStructure } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
const IN_V1_STORE_KEY = 'viewPort';
|
||||
const IN_V1_DEFAULT_VIEWPORT = { zoom: 1, x: 0, y: 0 };
|
||||
|
||||
export const to_2: MigrationStructure = {
|
||||
to: 2,
|
||||
up: (prev: any) => {
|
||||
const restored = localStorage.getItem(IN_V1_STORE_KEY);
|
||||
let current = IN_V1_DEFAULT_VIEWPORT;
|
||||
if (restored != null) {
|
||||
try {
|
||||
current = JSON.parse(restored);
|
||||
} catch (err) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
localStorage.removeItem(IN_V1_STORE_KEY);
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
map: {
|
||||
viewport: current,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -50,36 +50,39 @@ export type RoutesType = {
|
||||
export type LocalWidgetSettings = {
|
||||
compact: boolean;
|
||||
showOffline: boolean;
|
||||
version: number;
|
||||
showShipName: boolean;
|
||||
};
|
||||
|
||||
export type OnTheMapSettingsType = {
|
||||
hideOffline: boolean;
|
||||
version: number;
|
||||
};
|
||||
|
||||
export type KillsWidgetSettings = {
|
||||
showAll: boolean;
|
||||
whOnly: boolean;
|
||||
excludedSystems: number[];
|
||||
version: number;
|
||||
timeRange: number;
|
||||
};
|
||||
|
||||
export type SettingsWithVersion<T> = {
|
||||
version: number;
|
||||
settings: T;
|
||||
export type MapViewPort = { zoom: number; x: number; y: number };
|
||||
|
||||
export type MapSettings = {
|
||||
viewport: MapViewPort;
|
||||
};
|
||||
|
||||
export type SettingsWrapper<T> = T;
|
||||
|
||||
export type MapUserSettings = {
|
||||
widgets: SettingsWithVersion<WindowStoreInfo>;
|
||||
interface: SettingsWithVersion<InterfaceStoredSettings>;
|
||||
onTheMap: SettingsWithVersion<OnTheMapSettingsType>;
|
||||
routes: SettingsWithVersion<RoutesType>;
|
||||
localWidget: SettingsWithVersion<LocalWidgetSettings>;
|
||||
signaturesWidget: SettingsWithVersion<SignatureSettingsType>;
|
||||
killsWidget: SettingsWithVersion<KillsWidgetSettings>;
|
||||
migratedFromOld: boolean;
|
||||
version: number;
|
||||
widgets: SettingsWrapper<WindowStoreInfo>;
|
||||
interface: SettingsWrapper<InterfaceStoredSettings>;
|
||||
onTheMap: SettingsWrapper<OnTheMapSettingsType>;
|
||||
routes: SettingsWrapper<RoutesType>;
|
||||
localWidget: SettingsWrapper<LocalWidgetSettings>;
|
||||
signaturesWidget: SettingsWrapper<SignatureSettingsType>;
|
||||
killsWidget: SettingsWrapper<KillsWidgetSettings>;
|
||||
map: SettingsWrapper<MapSettings>;
|
||||
};
|
||||
|
||||
export type MapUserSettingsStructure = {
|
||||
@@ -89,3 +92,20 @@ export type MapUserSettingsStructure = {
|
||||
export type WdResponse<T> = T;
|
||||
|
||||
export type RemoteAdminSettingsResponse = { default_settings?: string };
|
||||
|
||||
export enum SettingsTypes {
|
||||
killsWidget = 'killsWidget',
|
||||
localWidget = 'localWidget',
|
||||
widgets = 'widgets',
|
||||
routes = 'routes',
|
||||
onTheMap = 'onTheMap',
|
||||
signaturesWidget = 'signaturesWidget',
|
||||
interface = 'interface',
|
||||
map = 'map',
|
||||
}
|
||||
|
||||
export type MigrationFunc = (prev: any) => any;
|
||||
export type MigrationStructure = {
|
||||
to: number;
|
||||
up: MigrationFunc;
|
||||
};
|
||||
|
||||
4
assets/js/hooks/Mapper/mapRootProvider/version.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const STORED_SETTINGS_VERSION = 2;
|
||||
|
||||
export const LS_KEY_LEGASY = 'map-user-settings';
|
||||
export const LS_KEY = 'map-user-settings-v2';
|
||||
@@ -122,7 +122,7 @@ export enum SignatureGroupRU {
|
||||
export enum SignatureGroupFR {
|
||||
CosmicSignature = 'Signature cosmique (groupe)',
|
||||
Wormhole = 'Trou de ver',
|
||||
GasSite = 'Site de gaz',
|
||||
GasSite = 'Site de collecte de gaz',
|
||||
RelicSite = 'Site de reliques',
|
||||
DataSite = 'Site de données',
|
||||
OreSite = 'Site de minerai',
|
||||
|
||||
@@ -118,6 +118,7 @@ export type SolarSystemRawType = {
|
||||
name: string | null;
|
||||
temporary_name: string | null;
|
||||
linked_sig_eve_id: string | null;
|
||||
comments_count: number | null;
|
||||
|
||||
system_static_info: SolarSystemStaticInfoRaw;
|
||||
system_signatures: SystemSignature[];
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from './contextStore';
|
||||
export * from './getQueryVariable';
|
||||
export * from './loadTextFile';
|
||||
export * from './saveToFile';
|
||||
export * from './omit';
|
||||
|
||||
8
assets/js/hooks/Mapper/utils/omit.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const omit = <T extends Record<string, any>, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> => {
|
||||
const result = { ...obj };
|
||||
for (const key of keys) {
|
||||
delete result[key];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
@@ -4,7 +4,7 @@ import 'phoenix_html';
|
||||
import './live_reload.css';
|
||||
|
||||
const animateBg = function (bgCanvas) {
|
||||
const { TweenMax, _ } = window;
|
||||
const { TweenMax } = window;
|
||||
/**
|
||||
* Utility function for returning a random integer in a given range
|
||||
* @param {Int} max
|
||||
@@ -212,12 +212,6 @@ const animateBg = function (bgCanvas) {
|
||||
};
|
||||
}
|
||||
window.myJump = new JumpToHyperspace(bgCanvas);
|
||||
window.addEventListener(
|
||||
'resize',
|
||||
_.debounce(() => {
|
||||
window.myJump.reset();
|
||||
}, 250),
|
||||
);
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
BIN
assets/static/apple-touch-icon.png
Executable file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
assets/static/favicon-96x96.png
Executable file
|
After Width: | Height: | Size: 977 B |
BIN
assets/static/images/amarr-small.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
33
assets/static/site.webmanifest
Executable file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "Wanderer",
|
||||
"short_name": "Wanderer",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/web-app-manifest-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/web-app-manifest-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#171717",
|
||||
"background_color": "#171717",
|
||||
"display": "standalone",
|
||||
"start_url": "/",
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "web-app-manifest.webp",
|
||||
"sizes": "720x1280",
|
||||
"type": "image/webp"
|
||||
},
|
||||
{
|
||||
"src": "web-app-manifest-wide.webp",
|
||||
"sizes": "1280x720",
|
||||
"type": "image/webp",
|
||||
"form_factor": "wide"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
assets/static/web-app-manifest-192x192.png
Executable file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
assets/static/web-app-manifest-512x512.png
Executable file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
assets/static/web-app-manifest-wide.webp
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
assets/static/web-app-manifest.webp
Normal file
|
After Width: | Height: | Size: 61 KiB |
@@ -203,7 +203,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
end
|
||||
end
|
||||
end,
|
||||
max_concurrency: System.schedulers_online(),
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task,
|
||||
timeout: :timer.seconds(60)
|
||||
)
|
||||
@@ -256,7 +256,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
WandererApp.Character.update_character_state(character_id, character_state)
|
||||
WandererApp.Map.Server.Impl.broadcast!(map_id, :untrack_character, character_id)
|
||||
end,
|
||||
max_concurrency: System.schedulers_online(),
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task,
|
||||
timeout: :timer.seconds(30)
|
||||
)
|
||||
|
||||
@@ -24,7 +24,7 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
@check_location_errors_interval :timer.minutes(1)
|
||||
@update_ship_interval :timer.seconds(2)
|
||||
@update_info_interval :timer.minutes(2)
|
||||
@update_wallet_interval :timer.minutes(1)
|
||||
@update_wallet_interval :timer.minutes(10)
|
||||
|
||||
@logger Application.compile_env(:wanderer_app, :logger)
|
||||
|
||||
@@ -180,7 +180,7 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
fn character_id ->
|
||||
WandererApp.Character.Tracker.update_online(character_id)
|
||||
end,
|
||||
max_concurrency: System.schedulers_online(),
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task,
|
||||
timeout: :timer.seconds(5)
|
||||
)
|
||||
@@ -241,12 +241,12 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
WandererApp.Character.Tracker.check_offline(character_id)
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> @logger.error("Error in check_offline: #{inspect(reason)}")
|
||||
error -> @logger.error("Error in check_offline: #{inspect(error)}")
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
@@ -281,12 +281,12 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
)
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> @logger.error("Error in check_online_errors: #{inspect(reason)}")
|
||||
error -> @logger.error("Error in check_online_errors: #{inspect(error)}")
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
@@ -321,12 +321,12 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
)
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> @logger.error("Error in check_ship_errors: #{inspect(reason)}")
|
||||
error -> @logger.error("Error in check_ship_errors: #{inspect(error)}")
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
@@ -361,12 +361,12 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
)
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> @logger.error("Error in check_location_errors: #{inspect(reason)}")
|
||||
error -> @logger.error("Error in check_location_errors: #{inspect(error)}")
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
@@ -395,7 +395,7 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
fn character_id ->
|
||||
WandererApp.Character.Tracker.update_location(character_id)
|
||||
end,
|
||||
max_concurrency: System.schedulers_online(),
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task,
|
||||
timeout: :timer.seconds(5)
|
||||
)
|
||||
@@ -436,7 +436,7 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
fn character_id ->
|
||||
WandererApp.Character.Tracker.update_ship(character_id)
|
||||
end,
|
||||
max_concurrency: System.schedulers_online(),
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task,
|
||||
timeout: :timer.seconds(5)
|
||||
)
|
||||
@@ -478,12 +478,12 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
WandererApp.Character.Tracker.update_info(character_id)
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> Logger.error("Error in update_info: #{inspect(reason)}")
|
||||
error -> Logger.error("Error in update_info: #{inspect(error)}")
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
@@ -521,13 +521,13 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
fn character_id ->
|
||||
WandererApp.Character.Tracker.update_wallet(character_id)
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
timeout: :timer.minutes(5),
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, _result} -> :ok
|
||||
{:error, reason} -> Logger.error("Error in update_wallet: #{inspect(reason)}")
|
||||
error -> Logger.error("Error in update_wallet: #{inspect(error)}")
|
||||
end)
|
||||
rescue
|
||||
e ->
|
||||
|
||||
@@ -253,7 +253,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
fn destination ->
|
||||
get_routes(origin, destination, params, opts)
|
||||
end,
|
||||
max_concurrency: 20,
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
timeout: :timer.seconds(30),
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|
||||
@@ -254,7 +254,7 @@ defmodule WandererApp.Map.Manager do
|
||||
|
||||
:timer.sleep(@maps_start_interval)
|
||||
end,
|
||||
max_concurrency: System.schedulers_online(),
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task,
|
||||
timeout: :timer.seconds(60)
|
||||
)
|
||||
|
||||
@@ -49,7 +49,7 @@ defmodule WandererApp.Map.ZkbDataFetcher do
|
||||
@logger.error(Exception.message(e))
|
||||
end
|
||||
end,
|
||||
max_concurrency: 10,
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn _ -> :ok end)
|
||||
|
||||
@@ -19,6 +19,7 @@ defmodule WandererApp.Map.Operations.Connections do
|
||||
@medium_ship_size 1
|
||||
@large_ship_size 2
|
||||
@xlarge_ship_size 3
|
||||
@capital_ship_size 4
|
||||
|
||||
# System class constants
|
||||
@c1_system_class 1
|
||||
@@ -35,6 +36,12 @@ defmodule WandererApp.Map.Operations.Connections do
|
||||
do_create(attrs, map_id, char_id)
|
||||
end
|
||||
|
||||
def small_ship_size(), do: @small_ship_size
|
||||
def medium_ship_size(), do: @medium_ship_size
|
||||
def large_ship_size(), do: @large_ship_size
|
||||
def freight_ship_size(), do: @xlarge_ship_size
|
||||
def capital_ship_size(), do: @capital_ship_size
|
||||
|
||||
defp do_create(attrs, map_id, char_id) do
|
||||
with {:ok, source} <- parse_int(attrs["solar_system_source"], "solar_system_source"),
|
||||
{:ok, target} <- parse_int(attrs["solar_system_target"], "solar_system_target"),
|
||||
|
||||
@@ -22,7 +22,7 @@ defmodule WandererApp.Map.Server.AclsImpl do
|
||||
fn acl_id ->
|
||||
update_acl(acl_id)
|
||||
end,
|
||||
max_concurrency: 10,
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
timeout: :timer.seconds(15)
|
||||
)
|
||||
|> Enum.reduce(
|
||||
|
||||
@@ -122,14 +122,22 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
[]
|
||||
)
|
||||
|
||||
{:ok, %{acls: acls}} =
|
||||
WandererApp.MapRepo.get(map_id,
|
||||
acls: [
|
||||
:owner_id,
|
||||
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
|
||||
]
|
||||
)
|
||||
if Enum.empty?(invalidate_character_ids) do
|
||||
:ok
|
||||
else
|
||||
{:ok, %{acls: acls}} =
|
||||
WandererApp.MapRepo.get(map_id,
|
||||
acls: [
|
||||
:owner_id,
|
||||
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
|
||||
]
|
||||
)
|
||||
|
||||
process_invalidate_characters(invalidate_character_ids, map_id, owner_id, acls)
|
||||
end
|
||||
end
|
||||
|
||||
defp process_invalidate_characters(invalidate_character_ids, map_id, owner_id, acls) do
|
||||
invalidate_character_ids
|
||||
|> Task.async_stream(
|
||||
fn character_id ->
|
||||
@@ -166,20 +174,24 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
end
|
||||
end,
|
||||
timeout: :timer.seconds(60),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
{:ok, {:remove_character, character_id}} ->
|
||||
remove_and_untrack_characters(map_id, [character_id])
|
||||
:ok
|
||||
|> Enum.reduce([], fn
|
||||
{:ok, {:remove_character, character_id}}, acc ->
|
||||
[character_id | acc]
|
||||
|
||||
{:ok, _result} ->
|
||||
:ok
|
||||
{:ok, _result}, acc ->
|
||||
acc
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}, acc ->
|
||||
Logger.error("Error in cleanup_characters: #{inspect(reason)}")
|
||||
acc
|
||||
end)
|
||||
|> case do
|
||||
[] -> :ok
|
||||
character_ids_to_remove -> remove_and_untrack_characters(map_id, character_ids_to_remove)
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_and_untrack_characters(map_id, character_ids) do
|
||||
@@ -293,7 +305,7 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
:ok
|
||||
end,
|
||||
timeout: :timer.seconds(15),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.each(fn
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
|
||||
require Logger
|
||||
|
||||
alias WandererApp.Map.Server.Impl
|
||||
alias WandererApp.Map.Server.SignaturesImpl
|
||||
|
||||
# @ccp1 -1
|
||||
@c1 1
|
||||
@@ -214,7 +215,8 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
|
||||
),
|
||||
do:
|
||||
update_connection(state, :update_time_status, [:time_status], connection_update, fn
|
||||
%{time_status: old_time_status}, %{id: connection_id, time_status: time_status} ->
|
||||
%{time_status: old_time_status},
|
||||
%{id: connection_id, time_status: time_status} = updated_connection ->
|
||||
case time_status == @connection_time_status_eol do
|
||||
true ->
|
||||
if old_time_status != @connection_time_status_eol do
|
||||
@@ -232,6 +234,10 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
|
||||
set_start_time(map_id, connection_id, DateTime.utc_now())
|
||||
end
|
||||
end
|
||||
|
||||
if time_status != old_time_status do
|
||||
maybe_update_linked_signature_time_status(map_id, updated_connection)
|
||||
end
|
||||
end)
|
||||
|
||||
def update_connection_type(
|
||||
@@ -360,10 +366,105 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
|
||||
state
|
||||
end
|
||||
|
||||
defp maybe_update_linked_signature_time_status(
|
||||
map_id,
|
||||
%{
|
||||
time_status: time_status,
|
||||
solar_system_source: solar_system_source,
|
||||
solar_system_target: solar_system_target
|
||||
} = updated_connection
|
||||
) do
|
||||
source_system =
|
||||
WandererApp.Map.find_system_by_location(
|
||||
map_id,
|
||||
%{solar_system_id: solar_system_source}
|
||||
)
|
||||
|
||||
target_system =
|
||||
WandererApp.Map.find_system_by_location(
|
||||
map_id,
|
||||
%{solar_system_id: solar_system_target}
|
||||
)
|
||||
|
||||
source_linked_signatures =
|
||||
find_linked_signatures(source_system, target_system)
|
||||
|
||||
target_linked_signatures = find_linked_signatures(target_system, source_system)
|
||||
|
||||
update_signatures_time_status(
|
||||
map_id,
|
||||
source_system.solar_system_id,
|
||||
source_linked_signatures,
|
||||
time_status
|
||||
)
|
||||
|
||||
update_signatures_time_status(
|
||||
map_id,
|
||||
target_system.solar_system_id,
|
||||
target_linked_signatures,
|
||||
time_status
|
||||
)
|
||||
end
|
||||
|
||||
defp find_linked_signatures(
|
||||
%{id: source_system_id} = _source_system,
|
||||
%{solar_system_id: solar_system_id, linked_sig_eve_id: linked_sig_eve_id} =
|
||||
_target_system
|
||||
)
|
||||
when not is_nil(linked_sig_eve_id) do
|
||||
{:ok, signatures} =
|
||||
WandererApp.Api.MapSystemSignature.by_linked_system_id(solar_system_id)
|
||||
|
||||
signatures |> Enum.filter(fn sig -> sig.system_id == source_system_id end)
|
||||
end
|
||||
|
||||
defp find_linked_signatures(_source_system, _target_system), do: []
|
||||
|
||||
defp update_signatures_time_status(_map_id, _solar_system_id, [], _time_status), do: :ok
|
||||
|
||||
defp update_signatures_time_status(map_id, solar_system_id, signatures, time_status) do
|
||||
signatures
|
||||
|> Enum.each(fn %{custom_info: custom_info_json} = sig ->
|
||||
update_params =
|
||||
if not is_nil(custom_info_json) do
|
||||
updated_custom_info =
|
||||
custom_info_json
|
||||
|> Jason.decode!()
|
||||
|> Map.merge(%{"time_status" => time_status})
|
||||
|> Jason.encode!()
|
||||
|
||||
%{custom_info: updated_custom_info}
|
||||
else
|
||||
updated_custom_info = Jason.encode!(%{"time_status" => time_status})
|
||||
%{custom_info: updated_custom_info}
|
||||
end
|
||||
|
||||
SignaturesImpl.apply_update_signature(%{map_id: map_id}, sig, update_params)
|
||||
end)
|
||||
|
||||
Impl.broadcast!(map_id, :signatures_updated, solar_system_id)
|
||||
end
|
||||
|
||||
def maybe_add_connection(map_id, location, old_location, character_id, is_manual)
|
||||
when not is_nil(location) and not is_nil(old_location) and
|
||||
not is_nil(old_location.solar_system_id) and
|
||||
location.solar_system_id != old_location.solar_system_id do
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
|
||||
if not is_manual do
|
||||
: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 =
|
||||
@@ -429,22 +530,6 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
|
||||
time_status: connection.time_status
|
||||
})
|
||||
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
|
||||
if not is_manual do
|
||||
:telemetry.execute([:wanderer_app, :map, :character, :jump], %{count: 1}, %{})
|
||||
|
||||
{:ok, _} =
|
||||
WandererApp.Api.MapChainPassages.new(%{
|
||||
map_id: map_id,
|
||||
character_id: character_id,
|
||||
ship_type_id: character.ship,
|
||||
ship_name: character.ship_name,
|
||||
solar_system_source_id: old_location.solar_system_id,
|
||||
solar_system_target_id: location.solar_system_id
|
||||
})
|
||||
end
|
||||
|
||||
{:ok, _} =
|
||||
WandererApp.User.ActivityTracker.track_map_event(:map_connection_added, %{
|
||||
character_id: character_id,
|
||||
|
||||
@@ -17,51 +17,53 @@ defmodule WandererApp.Map.Server.PingsImpl do
|
||||
user_id: user_id
|
||||
} = ping_info
|
||||
) do
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
with {:ok, character} <- WandererApp.Character.get_character(character_id),
|
||||
system <-
|
||||
WandererApp.Map.find_system_by_location(map_id, %{
|
||||
solar_system_id: solar_system_id |> String.to_integer()
|
||||
}),
|
||||
{:ok, ping} <-
|
||||
WandererApp.MapPingsRepo.create(%{
|
||||
map_id: map_id,
|
||||
character_id: character_id,
|
||||
system_id: system.id,
|
||||
message: message,
|
||||
type: type
|
||||
}) do
|
||||
Impl.broadcast!(
|
||||
map_id,
|
||||
:ping_added,
|
||||
ping |> Map.merge(%{character_eve_id: character.eve_id, solar_system_id: solar_system_id})
|
||||
)
|
||||
|
||||
system =
|
||||
WandererApp.Map.find_system_by_location(map_id, %{
|
||||
solar_system_id: solar_system_id |> String.to_integer()
|
||||
})
|
||||
# Broadcast rally point events to external clients (webhooks/SSE)
|
||||
if type == 1 do
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :rally_point_added, %{
|
||||
rally_point_id: ping.id,
|
||||
solar_system_id: solar_system_id,
|
||||
system_id: system.id,
|
||||
character_id: character_id,
|
||||
character_name: character.name,
|
||||
character_eve_id: character.eve_id,
|
||||
system_name: system.name,
|
||||
message: message,
|
||||
created_at: ping.inserted_at
|
||||
})
|
||||
end
|
||||
|
||||
{:ok, ping} =
|
||||
WandererApp.MapPingsRepo.create(%{
|
||||
WandererApp.User.ActivityTracker.track_map_event(:map_rally_added, %{
|
||||
character_id: character_id,
|
||||
user_id: user_id,
|
||||
map_id: map_id,
|
||||
character_id: character_id,
|
||||
system_id: system.id,
|
||||
message: message,
|
||||
type: type
|
||||
solar_system_id: "#{solar_system_id}"
|
||||
})
|
||||
|
||||
Impl.broadcast!(
|
||||
map_id,
|
||||
:ping_added,
|
||||
ping |> Map.merge(%{character_eve_id: character.eve_id, solar_system_id: solar_system_id})
|
||||
)
|
||||
|
||||
# Broadcast rally point events to external clients (webhooks/SSE)
|
||||
if type == 1 do
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :rally_point_added, %{
|
||||
rally_point_id: ping.id,
|
||||
solar_system_id: solar_system_id,
|
||||
system_id: system.id,
|
||||
character_id: character_id,
|
||||
character_name: character.name,
|
||||
character_eve_id: character.eve_id,
|
||||
system_name: system.name,
|
||||
message: message,
|
||||
created_at: ping.inserted_at
|
||||
})
|
||||
state
|
||||
else
|
||||
error ->
|
||||
Logger.error("Failed to add_ping: #{inspect(error, pretty: true)}")
|
||||
state
|
||||
end
|
||||
|
||||
WandererApp.User.ActivityTracker.track_map_event(:map_rally_added, %{
|
||||
character_id: character_id,
|
||||
user_id: user_id,
|
||||
map_id: map_id,
|
||||
solar_system_id: "#{solar_system_id}"
|
||||
})
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def cancel_ping(
|
||||
@@ -73,39 +75,42 @@ defmodule WandererApp.Map.Server.PingsImpl do
|
||||
type: type
|
||||
} = ping_info
|
||||
) do
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
|
||||
{:ok, %{system: %{id: system_id, name: system_name, solar_system_id: solar_system_id}} = ping} =
|
||||
WandererApp.MapPingsRepo.get_by_id(ping_id)
|
||||
|
||||
:ok = WandererApp.MapPingsRepo.destroy(ping)
|
||||
|
||||
Impl.broadcast!(map_id, :ping_cancelled, %{
|
||||
id: ping_id,
|
||||
solar_system_id: solar_system_id,
|
||||
type: type
|
||||
})
|
||||
|
||||
# Broadcast rally point removal events to external clients (webhooks/SSE)
|
||||
if type == 1 do
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :rally_point_removed, %{
|
||||
with {:ok, character} <- WandererApp.Character.get_character(character_id),
|
||||
{:ok,
|
||||
%{system: %{id: system_id, name: system_name, solar_system_id: solar_system_id}} = ping} <-
|
||||
WandererApp.MapPingsRepo.get_by_id(ping_id),
|
||||
:ok <- WandererApp.MapPingsRepo.destroy(ping) do
|
||||
Impl.broadcast!(map_id, :ping_cancelled, %{
|
||||
id: ping_id,
|
||||
solar_system_id: solar_system_id,
|
||||
system_id: system_id,
|
||||
character_id: character_id,
|
||||
character_name: character.name,
|
||||
character_eve_id: character.eve_id,
|
||||
system_name: system_name
|
||||
type: type
|
||||
})
|
||||
|
||||
# Broadcast rally point removal events to external clients (webhooks/SSE)
|
||||
if type == 1 do
|
||||
WandererApp.ExternalEvents.broadcast(map_id, :rally_point_removed, %{
|
||||
id: ping_id,
|
||||
solar_system_id: solar_system_id,
|
||||
system_id: system_id,
|
||||
character_id: character_id,
|
||||
character_name: character.name,
|
||||
character_eve_id: character.eve_id,
|
||||
system_name: system_name
|
||||
})
|
||||
end
|
||||
|
||||
WandererApp.User.ActivityTracker.track_map_event(:map_rally_cancelled, %{
|
||||
character_id: character_id,
|
||||
user_id: user_id,
|
||||
map_id: map_id,
|
||||
solar_system_id: solar_system_id
|
||||
})
|
||||
|
||||
state
|
||||
else
|
||||
error ->
|
||||
Logger.error("Failed to cancel_ping: #{inspect(error, pretty: true)}")
|
||||
state
|
||||
end
|
||||
|
||||
WandererApp.User.ActivityTracker.track_map_event(:map_rally_cancelled, %{
|
||||
character_id: character_id,
|
||||
user_id: user_id,
|
||||
map_id: map_id,
|
||||
solar_system_id: solar_system_id
|
||||
})
|
||||
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,6 +7,7 @@ defmodule WandererApp.Map.Server.SignaturesImpl do
|
||||
alias WandererApp.Character
|
||||
alias WandererApp.User.ActivityTracker
|
||||
alias WandererApp.Map.Server.{Impl, ConnectionsImpl, SystemsImpl}
|
||||
alias WandererApp.Utils.EVEUtil
|
||||
|
||||
@doc """
|
||||
Public entrypoint for updating signatures on a map system.
|
||||
@@ -92,7 +93,7 @@ defmodule WandererApp.Map.Server.SignaturesImpl do
|
||||
|> Enum.filter(&(&1.eve_id in updated_ids))
|
||||
|> Enum.each(fn existing ->
|
||||
update = Enum.find(updated_sigs, &(&1.eve_id == existing.eve_id))
|
||||
apply_update_signature(existing, update)
|
||||
apply_update_signature(state, existing, update)
|
||||
end)
|
||||
|
||||
# 3. Additions & restorations
|
||||
@@ -209,13 +210,19 @@ defmodule WandererApp.Map.Server.SignaturesImpl do
|
||||
MapSystemSignature.update!(sig, %{deleted: true})
|
||||
end
|
||||
|
||||
defp apply_update_signature(%MapSystemSignature{} = existing, update_params)
|
||||
when not is_nil(update_params) do
|
||||
def apply_update_signature(
|
||||
state,
|
||||
%MapSystemSignature{} = existing,
|
||||
update_params
|
||||
)
|
||||
when not is_nil(update_params) do
|
||||
case MapSystemSignature.update(
|
||||
existing,
|
||||
update_params |> Map.put(:update_forced_at, DateTime.utc_now())
|
||||
) do
|
||||
{:ok, _updated} ->
|
||||
{:ok, updated} ->
|
||||
maybe_update_connection_time_status(state, existing, updated)
|
||||
maybe_update_connection_mass_status(state, existing, updated)
|
||||
:ok
|
||||
|
||||
{:error, reason} ->
|
||||
@@ -223,6 +230,52 @@ defmodule WandererApp.Map.Server.SignaturesImpl do
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_update_connection_time_status(
|
||||
state,
|
||||
%{custom_info: old_custom_info} = old_sig,
|
||||
%{custom_info: new_custom_info, system_id: system_id, linked_system_id: linked_system_id} =
|
||||
updated_sig
|
||||
)
|
||||
when not is_nil(linked_system_id) do
|
||||
old_time_status = get_time_status(old_custom_info)
|
||||
new_time_status = get_time_status(new_custom_info)
|
||||
|
||||
if old_time_status != new_time_status do
|
||||
{:ok, source_system} = MapSystem.by_id(system_id)
|
||||
|
||||
ConnectionsImpl.update_connection_time_status(state, %{
|
||||
solar_system_source_id: source_system.solar_system_id,
|
||||
solar_system_target_id: linked_system_id,
|
||||
time_status: new_time_status
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_update_connection_time_status(_state, _old_sig, _updated_sig), do: :ok
|
||||
|
||||
defp maybe_update_connection_mass_status(
|
||||
state,
|
||||
%{type: old_type} = old_sig,
|
||||
%{type: new_type, system_id: system_id, linked_system_id: linked_system_id} =
|
||||
updated_sig
|
||||
)
|
||||
when not is_nil(linked_system_id) do
|
||||
if old_type != new_type do
|
||||
{:ok, source_system} = MapSystem.by_id(system_id)
|
||||
signature_ship_size_type = EVEUtil.get_wh_size(new_type)
|
||||
|
||||
if not is_nil(signature_ship_size_type) do
|
||||
ConnectionsImpl.update_connection_ship_size_type(state, %{
|
||||
solar_system_source_id: source_system.solar_system_id,
|
||||
solar_system_target_id: linked_system_id,
|
||||
ship_size_type: signature_ship_size_type
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_update_connection_mass_status(_state, _old_sig, _updated_sig), do: :ok
|
||||
|
||||
defp track_activity(event, map_id, solar_system_id, user_id, character_id, signatures) do
|
||||
ActivityTracker.track_map_event(event, %{
|
||||
map_id: map_id,
|
||||
@@ -251,4 +304,12 @@ defmodule WandererApp.Map.Server.SignaturesImpl do
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
defp get_time_status(nil), do: nil
|
||||
|
||||
defp get_time_status(custom_info_json) do
|
||||
custom_info_json
|
||||
|> Jason.decode!()
|
||||
|> Map.get("time_status")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -41,7 +41,7 @@ defmodule WandererApp.Maps do
|
||||
system |> Map.take(@minimum_route_attrs)
|
||||
end
|
||||
end,
|
||||
max_concurrency: 10
|
||||
max_concurrency: System.schedulers_online() * 4
|
||||
)
|
||||
|> Enum.map(fn {:ok, val} -> val end)
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ defmodule WandererApp.Utils.EVEUtil do
|
||||
Utility functions for EVE Online related operations.
|
||||
"""
|
||||
|
||||
alias WandererApp.Map.Operations.Connections
|
||||
|
||||
@doc """
|
||||
Generates a URL for a character portrait.
|
||||
|
||||
@@ -28,4 +30,28 @@ defmodule WandererApp.Utils.EVEUtil do
|
||||
def get_portrait_url(eve_id, size) do
|
||||
"https://images.evetech.net/characters/#{eve_id}/portrait?size=#{size}"
|
||||
end
|
||||
|
||||
def get_wh_size(nil), do: nil
|
||||
def get_wh_size("K162"), do: nil
|
||||
|
||||
def get_wh_size(wh_type_name) do
|
||||
{:ok, wormhole_types} = WandererApp.CachedInfo.get_wormhole_types()
|
||||
|
||||
wormhole_types
|
||||
|> Enum.find(fn wh_type_data -> wh_type_data.name == wh_type_name end)
|
||||
|> case do
|
||||
%{max_mass_per_jump: max_mass_per_jump} when not is_nil(max_mass_per_jump) ->
|
||||
get_connection_size_status(max_mass_per_jump)
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp get_connection_size_status(5_000_000), do: Connections.small_ship_size()
|
||||
defp get_connection_size_status(62_000_000), do: Connections.medium_ship_size()
|
||||
defp get_connection_size_status(375_000_000), do: Connections.large_ship_size()
|
||||
defp get_connection_size_status(1_000_000_000), do: Connections.freight_ship_size()
|
||||
defp get_connection_size_status(2_000_000_000), do: Connections.capital_ship_size()
|
||||
defp get_connection_size_status(_max_mass_per_jump), do: Connections.large_ship_size()
|
||||
end
|
||||
|
||||
@@ -17,7 +17,9 @@ defmodule WandererAppWeb do
|
||||
those modules here.
|
||||
"""
|
||||
|
||||
def static_paths, do: ~w(assets fonts images icons favicon.ico robots.txt woff woff2 lottie)
|
||||
def static_paths,
|
||||
do:
|
||||
~w(assets fonts images icons favicon.ico site.webmanifest apple-touch-icon.png web-app-manifest-192x192.png web-app-manifest-512x512.png web-app-manifest.webp web-app-manifest-wide.webp robots.txt woff woff2 lottie)
|
||||
|
||||
def router do
|
||||
quote do
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
{assigns[:page_title] || "Welcome"}
|
||||
</.live_title>
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
||||
|
||||
<link
|
||||
|
||||
@@ -80,6 +80,11 @@ defmodule WandererAppWeb.MapSystemAPIController do
|
||||
properties: %{
|
||||
solar_system_id: %Schema{type: :integer, description: "EVE solar system ID"},
|
||||
solar_system_name: %Schema{type: :string, description: "EVE solar system name"},
|
||||
custom_name: %Schema{
|
||||
type: :string,
|
||||
nullable: true,
|
||||
description: "Custom name for the system"
|
||||
},
|
||||
position_x: %Schema{type: :integer, description: "X coordinate"},
|
||||
position_y: %Schema{type: :integer, description: "Y coordinate"},
|
||||
status: %Schema{
|
||||
@@ -98,6 +103,7 @@ defmodule WandererAppWeb.MapSystemAPIController do
|
||||
example: %{
|
||||
solar_system_id: 30_000_142,
|
||||
solar_system_name: "Jita",
|
||||
custom_name: "Trade Hub",
|
||||
position_x: 100,
|
||||
position_y: 200,
|
||||
visible: true,
|
||||
@@ -113,6 +119,11 @@ defmodule WandererAppWeb.MapSystemAPIController do
|
||||
description: "EVE solar system name",
|
||||
nullable: true
|
||||
},
|
||||
custom_name: %Schema{
|
||||
type: :string,
|
||||
nullable: true,
|
||||
description: "Custom name for the system"
|
||||
},
|
||||
position_x: %Schema{type: :integer, description: "X coordinate", nullable: true},
|
||||
position_y: %Schema{type: :integer, description: "Y coordinate", nullable: true},
|
||||
status: %Schema{
|
||||
@@ -130,6 +141,7 @@ defmodule WandererAppWeb.MapSystemAPIController do
|
||||
},
|
||||
example: %{
|
||||
solar_system_name: "Jita",
|
||||
custom_name: "Trade Hub",
|
||||
position_x: 101,
|
||||
position_y: 202,
|
||||
visible: false,
|
||||
|
||||
@@ -118,6 +118,7 @@ defmodule WandererAppWeb.Helpers.APIUtils do
|
||||
|
||||
optional = [
|
||||
"solar_system_name",
|
||||
"custom_name",
|
||||
"position_x",
|
||||
"position_y",
|
||||
"coordinates",
|
||||
@@ -151,6 +152,7 @@ defmodule WandererAppWeb.Helpers.APIUtils do
|
||||
def extract_update_params(params) when is_map(params) do
|
||||
allowed = [
|
||||
"solar_system_name",
|
||||
"custom_name",
|
||||
"position_x",
|
||||
"position_y",
|
||||
"coordinates",
|
||||
|
||||
@@ -339,7 +339,7 @@ defmodule WandererAppWeb.MapRoutesEventHandler do
|
||||
destination_id
|
||||
)
|
||||
end,
|
||||
max_concurrency: System.schedulers_online(),
|
||||
max_concurrency: System.schedulers_online() * 4,
|
||||
on_timeout: :kill_task,
|
||||
timeout: :timer.minutes(1)
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
|
||||
require Logger
|
||||
|
||||
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler}
|
||||
alias WandererApp.Utils.EVEUtil
|
||||
|
||||
def handle_server_event(
|
||||
%{
|
||||
@@ -178,44 +179,69 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
|
||||
}
|
||||
} = socket
|
||||
)
|
||||
when not is_nil(main_character_id) do
|
||||
when not is_nil(main_character_id) and not is_nil(solar_system_source) and
|
||||
not is_nil(solar_system_target) do
|
||||
with solar_system_source <- get_integer(solar_system_source),
|
||||
solar_system_target <- get_integer(solar_system_target),
|
||||
{:ok, source_system} <-
|
||||
WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{
|
||||
map_id: map_id,
|
||||
solar_system_id: solar_system_source
|
||||
}),
|
||||
signature <-
|
||||
source_system when not is_nil(source_system) <-
|
||||
WandererApp.Map.find_system_by_location(
|
||||
map_id,
|
||||
%{solar_system_id: solar_system_source}
|
||||
),
|
||||
signature when not is_nil(signature) <-
|
||||
WandererApp.Api.MapSystemSignature.by_system_id!(source_system.id)
|
||||
|> Enum.find(fn s -> s.eve_id == signature_eve_id end),
|
||||
target_system <-
|
||||
target_system when not is_nil(target_system) <-
|
||||
WandererApp.Map.find_system_by_location(
|
||||
map_id,
|
||||
%{solar_system_id: solar_system_target}
|
||||
) do
|
||||
if not is_nil(signature) do
|
||||
signature
|
||||
|> WandererApp.Api.MapSystemSignature.update_group!(%{group: "Wormhole"})
|
||||
|> WandererApp.Api.MapSystemSignature.update_linked_system(%{
|
||||
linked_system_id: solar_system_target
|
||||
signature
|
||||
|> WandererApp.Api.MapSystemSignature.update_group!(%{group: "Wormhole"})
|
||||
|> WandererApp.Api.MapSystemSignature.update_linked_system(%{
|
||||
linked_system_id: solar_system_target
|
||||
})
|
||||
|
||||
if is_nil(target_system.linked_sig_eve_id) do
|
||||
map_id
|
||||
|> WandererApp.Map.Server.update_system_linked_sig_eve_id(%{
|
||||
solar_system_id: solar_system_target,
|
||||
linked_sig_eve_id: signature_eve_id
|
||||
})
|
||||
|
||||
if not is_nil(target_system) &&
|
||||
is_nil(target_system.linked_sig_eve_id) do
|
||||
if not is_nil(signature.temporary_name) do
|
||||
map_id
|
||||
|> WandererApp.Map.Server.update_system_linked_sig_eve_id(%{
|
||||
|> WandererApp.Map.Server.update_system_temporary_name(%{
|
||||
solar_system_id: solar_system_target,
|
||||
linked_sig_eve_id: signature_eve_id
|
||||
temporary_name: signature.temporary_name
|
||||
})
|
||||
end
|
||||
|
||||
if not is_nil(signature.temporary_name) do
|
||||
map_id
|
||||
|> WandererApp.Map.Server.update_system_temporary_name(%{
|
||||
solar_system_id: solar_system_target,
|
||||
temporary_name: signature.temporary_name
|
||||
})
|
||||
signature_time_status =
|
||||
if not is_nil(signature.custom_info) do
|
||||
signature.custom_info |> Jason.decode!() |> Map.get("time_status")
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
if not is_nil(signature_time_status) do
|
||||
map_id
|
||||
|> WandererApp.Map.Server.update_connection_time_status(%{
|
||||
solar_system_source_id: solar_system_source,
|
||||
solar_system_target_id: solar_system_target,
|
||||
time_status: signature_time_status
|
||||
})
|
||||
end
|
||||
|
||||
signature_ship_size_type = EVEUtil.get_wh_size(signature.type)
|
||||
|
||||
if not is_nil(signature_ship_size_type) do
|
||||
map_id
|
||||
|> WandererApp.Map.Server.update_connection_ship_size_type(%{
|
||||
solar_system_source_id: solar_system_source,
|
||||
solar_system_target_id: solar_system_target,
|
||||
ship_size_type: signature_ship_size_type
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
2
mix.exs
@@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do
|
||||
|
||||
@source_url "https://github.com/wanderer-industries/wanderer"
|
||||
|
||||
@version "1.78.1"
|
||||
@version "1.81.8"
|
||||
|
||||
def project do
|
||||
[
|
||||
|
||||
BIN
priv/static/apple-touch-icon.png
Executable file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
priv/static/favicon-96x96.png
Executable file
|
After Width: | Height: | Size: 977 B |
33
priv/static/site.webmanifest
Executable file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "Wanderer",
|
||||
"short_name": "Wanderer",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/web-app-manifest-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/web-app-manifest-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#171717",
|
||||
"background_color": "#171717",
|
||||
"display": "standalone",
|
||||
"start_url": "/",
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "web-app-manifest.webp",
|
||||
"sizes": "720x1280",
|
||||
"type": "image/webp"
|
||||
},
|
||||
{
|
||||
"src": "web-app-manifest-wide.webp",
|
||||
"sizes": "1280x720",
|
||||
"type": "image/webp",
|
||||
"form_factor": "wide"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
priv/static/web-app-manifest-192x192.png
Executable file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
priv/static/web-app-manifest-512x512.png
Executable file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
priv/static/web-app-manifest-wide.webp
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
priv/static/web-app-manifest.webp
Normal file
|
After Width: | Height: | Size: 61 KiB |
@@ -127,6 +127,31 @@ defmodule WandererAppWeb.MapSystemAPIControllerSuccessTest do
|
||||
assert updated_system["position_y"] == 400.0
|
||||
end
|
||||
|
||||
test "UPDATE: successfully updates custom_name", %{conn: conn, map: map} do
|
||||
system =
|
||||
insert(:map_system, %{
|
||||
map_id: map.id,
|
||||
solar_system_id: 30_000_142,
|
||||
name: "Jita",
|
||||
position_x: 100,
|
||||
position_y: 200
|
||||
})
|
||||
|
||||
update_params = %{
|
||||
"custom_name" => "My Trade Hub"
|
||||
}
|
||||
|
||||
conn = put(conn, ~p"/api/maps/#{map.slug}/systems/#{system.id}", update_params)
|
||||
|
||||
response = json_response(conn, 200)
|
||||
|
||||
assert %{
|
||||
"data" => updated_system
|
||||
} = response
|
||||
|
||||
assert updated_system["custom_name"] == "My Trade Hub"
|
||||
end
|
||||
|
||||
test "DELETE: successfully deletes a system", %{conn: conn, map: map} do
|
||||
system =
|
||||
insert(:map_system, %{
|
||||
|
||||