Compare commits

..

3 Commits

Author SHA1 Message Date
Dmitry Popov
1b4852d5b4 Merge pull request #517 from jackmurray/show-temp-name
Show system temp name when linking signatures
2025-10-10 00:00:41 +04:00
Jack
4bfe60b75c preferentially display the system's temporary name if it has one 2025-09-16 19:58:21 +00:00
Jack
6a44d10c56 enable display of custom name on the connection dropdown 2025-09-16 19:58:00 +00:00
57 changed files with 398 additions and 1274 deletions

View File

@@ -2,40 +2,6 @@
<!-- changelog -->
## [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)

View File

@@ -284,7 +284,3 @@
border-left-color: #e67e22;
}
.p-dialog-header-icon.p-dialog-header-close.p-link {
position: relative;
left: 6px;
}

View File

@@ -1,7 +1,7 @@
.vertical-tabs-container {
display: flex;
width: 100%;
min-height: 200px;
min-height: 400px;
.p-tabview {
width: 100%;

View File

@@ -1,4 +1,4 @@
import { MapUserSettings, SettingsWrapper } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { MapUserSettings, SettingsWithVersion } from '@/hooks/Mapper/mapRootProvider/types.ts';
export const REQUIRED_KEYS = [
'widgets',
@@ -19,8 +19,11 @@ export class MapUserSettingsParseError extends Error {
}
}
/** Minimal check that an object matches SettingsWrapper<*> */
const isSettings = (v: unknown): v is SettingsWrapper<unknown> => typeof v === 'object' && v !== null;
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);
/** Ensure every required key is present */
const hasAllRequiredKeys = (v: unknown): v is Record<RequiredKeys, unknown> =>
@@ -49,8 +52,8 @@ export const parseMapUserSettings = (json: unknown): MapUserSettings => {
}
for (const key of REQUIRED_KEYS) {
if (!isSettings((data as any)[key])) {
throw new MapUserSettingsParseError(`"${key}" must match SettingsWrapper<T>`);
if (!isSettingsWithVersion((data as any)[key])) {
throw new MapUserSettingsParseError(`"${key}" must match SettingsWithVersion<T>`);
}
}

View File

@@ -1,26 +0,0 @@
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 };
};

View File

@@ -1,10 +1,11 @@
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, useRef } from 'react';
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useMemo } from 'react';
import ReactFlow, {
Background,
Edge,
@@ -32,9 +33,19 @@ import {
import { getBehaviorForTheme } from './helpers/getThemeBehavior';
import { useEdgesState, useMapHandlers, useNodesState, useUpdateNodes } from './hooks';
import { useBackgroundVars } from './hooks/useBackgroundVars';
import { MapViewport, OnMapAddSystemCallback, OnMapSelectionChange } from './map.types';
import type { Viewport } from '@reactflow/core/dist/esm/types';
import { usePrevious } from 'primereact/hooks';
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);
};
const initialNodes: Node<SolarSystemRawType>[] = [
// {
@@ -77,7 +88,6 @@ 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;
@@ -89,7 +99,6 @@ interface MapCompProps {
pings: PingData[];
minimapPlacement?: PanelPosition;
localShowShipName?: boolean;
defaultViewport?: Viewport;
}
const MapComp = ({
@@ -110,25 +119,19 @@ const MapComp = ({
pings,
minimapPlacement = 'bottom-right',
localShowShipName = false,
onChangeViewport,
defaultViewport,
}: MapCompProps) => {
const { getNodes, setViewport } = useReactFlow();
const { getNodes } = useReactFlow();
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges);
useMapHandlers(refn, onSelectionChange);
useUpdateNodes(nodes);
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ 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,
@@ -184,10 +187,9 @@ const MapComp = ({
[onSelectionChange],
);
const handleMoveEnd: OnMoveEnd = useCallback((_, viewport) => {
// @ts-ignore
refVars.current.onChangeViewport?.(viewport);
}, []);
const handleMoveEnd: OnMoveEnd = (_, viewport) => {
localStorage.setItem(SESSION_KEY.viewPort, JSON.stringify(viewport));
};
const handleNodesChange = useCallback(
(changes: NodeChange[]) => {
@@ -216,19 +218,6 @@ 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
@@ -243,7 +232,7 @@ const MapComp = ({
onConnect={onConnect}
// TODO we need save into session all of this
// and on any action do either
defaultViewport={defaultViewport}
defaultViewport={getViewPortFromStore()}
edgeTypes={edgeTypes}
nodeTypes={nodeTypes}
connectionMode={connectionMode}

View File

@@ -11,7 +11,6 @@ export type MapData = MapUnionTypes & {
isThickConnections: boolean;
linkedSigEveId: string;
localShowShipName: boolean;
systemHighlighted: string | undefined;
};
interface MapProviderProps {
@@ -45,7 +44,6 @@ const INITIAL_DATA: MapData = {
userHubs: [],
pings: [],
localShowShipName: false,
systemHighlighted: undefined,
};
export interface MapContextProps {

View File

@@ -5,8 +5,8 @@
}
.hoverTarget {
padding: 2px;
margin: -2px;
padding: 0.5rem;
margin: -0.5rem;
display: inline-block;
}

View File

@@ -13,19 +13,9 @@ interface LocalCounterProps {
localCounterCharacters: Array<CharItemProps>;
hasUserCharacters: boolean;
showIcon?: boolean;
disableInteractive?: boolean;
className?: string;
contentClassName?: string;
}
export const LocalCounter = ({
className,
contentClassName,
localCounterCharacters,
hasUserCharacters,
showIcon = true,
disableInteractive,
}: LocalCounterProps) => {
export const LocalCounter = ({ localCounterCharacters, hasUserCharacters, showIcon = true }: LocalCounterProps) => {
const {
data: { localShowShipName },
} = useMapState();
@@ -52,30 +42,22 @@ export const LocalCounter = ({
return (
<div
className={clsx(
classes.TooltipActive,
{
[classes.Pathfinder]: theme === AvailableThemes.pathfinder,
},
className,
)}
className={clsx(classes.TooltipActive, {
[classes.Pathfinder]: theme === AvailableThemes.pathfinder,
})}
>
<WdTooltipWrapper
content={pilotTooltipContent}
position={TooltipPosition.right}
offset={0}
interactive={!disableInteractive}
interactive={true}
smallPaddings
>
<div className={clsx(classes.hoverTarget)}>
<div
className={clsx(
classes.localCounter,
{
[classes.hasUserCharacters]: hasUserCharacters,
},
contentClassName,
)}
className={clsx(classes.localCounter, {
[classes.hasUserCharacters]: hasUserCharacters,
})}
>
{showIcon && <i className="pi pi-users" />}
<span>{localCounterCharacters.length}</span>

View File

@@ -1,6 +1,14 @@
@use "sass:color";
@use '@/hooks/Mapper/components/map/styles/eve-common-variables';
@import '@/hooks/Mapper/components/map/styles/solar-system-node';
$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);
@keyframes move-stripes {
from {

View File

@@ -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 { useNodeKillsCount, useSolarSystemNode } from '../../hooks';
import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks';
import {
EFFECT_BACKGROUND_STYLES,
MARKER_BOOKMARK_BG_STYLES,
@@ -17,12 +17,10 @@ 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,
@@ -141,26 +139,12 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
{nodeVars.isWormhole && !nodeVars.customName && <div />}
<div className="flex items-center gap-0.5 justify-end">
<div className={clsx('flex items-center gap-0.5')}>
<div className="flex items-center gap-1 justify-end">
<div className={clsx('flex items-center gap-1')}>
{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
@@ -193,17 +177,6 @@ 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"

View File

@@ -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 { useNodeKillsCount, useSolarSystemNode } from '../../hooks';
import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks';
import {
EFFECT_BACKGROUND_STYLES,
MARKER_BOOKMARK_BG_STYLES,
@@ -16,7 +16,6 @@ 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>) => {
@@ -173,17 +172,6 @@ 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"

View File

@@ -1,18 +1,12 @@
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 { update } = useMapState();
const ref = useRef({ rf, update });
ref.current = { rf, update };
const highlightTimeout = useRef<number>();
const ref = useRef({ rf });
ref.current = { rf };
return useCallback((systemId: CommandCenterSystem) => {
const systemNode = ref.current.rf.getNodes().find(x => x.data.id === systemId);
@@ -20,16 +14,5 @@ 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);
}, []);
};

View File

@@ -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, SPACE_TO_CLASS } from '@/hooks/Mapper/constants';
import { Regions, REGIONS_MAP, Spaces } 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,9 +50,27 @@ export interface SolarSystemNodeVars {
isRally: boolean;
classTitle: string | null;
temporaryName?: string | null;
description: string | null;
comments_count: number | null;
systemHighlighted: string | undefined;
}
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 };
}
export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarSystemNodeVars => {
@@ -66,8 +84,6 @@ export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarS
labels,
temporary_name,
linked_sig_eve_id: linkedSigEveId = '',
description,
comments_count,
} = data;
const {
@@ -109,7 +125,6 @@ export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarS
showKSpaceBG,
isThickConnections,
pings,
systemHighlighted,
},
outCommand,
} = useMapState();
@@ -154,7 +169,7 @@ export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarS
const showHandlers = isConnecting || hoverNodeId === id;
const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
const regionClass = showKSpaceBG ? SPACE_TO_CLASS[space] || null : null;
const regionClass = showKSpaceBG ? SpaceToClass[space] || null : null;
const { systemName, computedTemporaryName, customName } = useSystemName({
isTempSystemNameEnabled,
@@ -217,9 +232,6 @@ export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarS
regionName,
solarSystemName: solar_system_name,
isRally,
description,
comments_count,
systemHighlighted,
};
return nodeVars;

View File

@@ -10,5 +10,3 @@ export type OnMapSelectionChange = (event: {
}) => void;
export type OnMapAddSystemCallback = (props: { coordinates: XYPosition | null }) => void;
export type MapViewport = { zoom: 1; x: 0; y: 0 };

View File

@@ -1,8 +0,0 @@
$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);

View File

@@ -125,7 +125,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
}}
>
<form onSubmit={handleSave}>
<div className="flex flex-col gap-3 px-2">
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-1">
<label htmlFor="username">Custom name</label>

View File

@@ -1,8 +1,8 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useMemo, useState, useEffect, useRef } from 'react';
import debounce from 'lodash.debounce';
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,8 +30,9 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
update,
storedSettings: { settingsKills },
} = useMapRootState();
const { excludedSystems } = settingsKills;
const excludedSystems = useStableValue(settingsKills.excludedSystems);
const effectiveSinceHours = sinceHours;
const effectiveSystemIds = useMemo(() => {
if (showAllVisible) {
@@ -75,13 +76,13 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
eventType = OutCommand.getSystemsKills;
requestData = {
system_ids: effectiveSystemIds,
since_hours: sinceHours,
since_hours: effectiveSinceHours,
};
} else if (systemId) {
eventType = OutCommand.getSystemKills;
requestData = {
system_id: systemId,
since_hours: sinceHours,
since_hours: effectiveSinceHours,
};
} else {
setIsLoading(false);
@@ -109,7 +110,16 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
setIsLoading(false);
}
},
[showAllVisible, systemId, outCommand, effectiveSystemIds, sinceHours, mergeKillsIntoGlobal],
[showAllVisible, systemId, outCommand, effectiveSystemIds, effectiveSinceHours, mergeKillsIntoGlobal],
);
const debouncedFetchKills = useMemo(
() =>
debounce(fetchKills, 500, {
leading: true,
trailing: false,
}),
[fetchKills],
);
const finalKills = useMemo(() => {
@@ -131,22 +141,27 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
useEffect(() => {
if (!systemId && !showAllVisible && !didFallbackFetch.current) {
didFallbackFetch.current = true;
debouncedFetchKills.cancel();
fetchKills(true);
}
}, [systemId, showAllVisible, fetchKills]);
}, [systemId, showAllVisible, debouncedFetchKills, fetchKills]);
useEffect(() => {
if (effectiveSystemIds.length === 0) return;
if (showAllVisible || systemId) {
// Cancel any pending debounced fetch
debouncedFetchKills.cancel();
// Fetch kills immediately
fetchKills();
return;
return () => debouncedFetchKills.cancel();
}
}, [showAllVisible, systemId, effectiveSystemIds, fetchKills]);
}, [showAllVisible, systemId, effectiveSystemIds, debouncedFetchKills, fetchKills]);
const refetch = useCallback(() => {
debouncedFetchKills.cancel();
fetchKills();
}, [fetchKills]);
}, [debouncedFetchKills, fetchKills]);
return {
kills: finalKills,

View File

@@ -15,7 +15,6 @@ 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 {}
@@ -73,7 +72,6 @@ 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}

View File

@@ -62,7 +62,7 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => {
header="Map user settings"
visible
draggable={false}
className="w-[600px] h-[400px]"
style={{ width: '600px' }}
onShow={handleShow}
onHide={handleHide}
>

View File

@@ -5,8 +5,6 @@ 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 {
@@ -24,9 +22,8 @@ export const ImportExport = () => {
}
try {
// INFO: WE NOT SUPPORT MIGRATIONS FOR OLD FILES AND Clipboard
const parsed = parseMapUserSettings(text);
if (applySettings(applyMigrations(parsed) || createDefaultStoredSettings())) {
if (applySettings(parsed)) {
toast.current?.show({
severity: 'success',
summary: 'Import',
@@ -62,9 +59,8 @@ export const ImportExport = () => {
try {
const text = await loadTextFile();
// INFO: WE NOT SUPPORT MIGRATIONS FOR OLD FILES AND Clipboard
const parsed = parseMapUserSettings(text);
if (applySettings(applyMigrations(parsed))) {
if (applySettings(parsed)) {
toast.current?.show({
severity: 'success',
summary: 'Import',

View File

@@ -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 { createDefaultStoredSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultStoredSettings.ts';
import { createDefaultWidgetSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultWidgetSettings.ts';
import { callToastSuccess } from '@/hooks/Mapper/helpers';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
import { RemoteAdminSettingsResponse } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { applyMigrations } from '@/hooks/Mapper/mapRootProvider/migrations';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
export const ServerSettings = () => {
@@ -29,16 +29,15 @@ export const ServerSettings = () => {
}
if (res?.default_settings == null) {
applySettings(createDefaultStoredSettings());
applySettings(createDefaultWidgetSettings());
return;
}
try {
//INFO: INSTEAD CHECK WE WILL TRY TO APPLY MIGRATION
applySettings(applyMigrations(JSON.parse(res.default_settings)));
applySettings(parseMapUserSettings(res.default_settings));
callToastSuccess(toast.current, 'Settings synchronized successfully');
} catch (error) {
applySettings(createDefaultStoredSettings());
applySettings(createDefaultWidgetSettings());
}
}, [applySettings, outCommand]);

View File

@@ -1,5 +1,14 @@
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';
@@ -9,7 +18,10 @@ import { useCallback, useRef } from 'react';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
const createSettings = function <T>(lsSettings: string | null, defaultValues: T) {
return lsSettings ? JSON.parse(lsSettings) : defaultValues;
return {
version: -1,
settings: lsSettings ? JSON.parse(lsSettings) : defaultValues,
};
};
export const OldSettingsDialog = () => {
@@ -32,15 +44,13 @@ export const OldSettingsDialog = () => {
const signatures = localStorage.getItem('wanderer_system_signature_settings_v6_6');
const out: MapUserSettings = {
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, {}),
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),
};
if (asFile) {

View File

@@ -7,7 +7,6 @@ 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 {
@@ -49,7 +48,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 pt-1',
'flex flex-col items-center justify-between',
)}
>
<div className="flex flex-col gap-2 items-center mt-1">
@@ -66,31 +65,15 @@ export const RightBar = ({
</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>
<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>
</>
)}
{additionalContent}

View File

@@ -20,7 +20,7 @@ const renderLinkedSystemItem = (option: { value: string }) => {
return (
<div className="flex gap-2 items-center">
<SystemView systemId={value} className={classes.SystemView} />
<SystemView systemId={value} className={classes.SystemView} showCustomName={true} />
</div>
);
};
@@ -37,7 +37,7 @@ const renderLinkedSystemValue = (option: { value: string }) => {
return (
<div className="flex gap-2 items-center">
<SystemView systemId={option.value} className={classes.SystemView} />
<SystemView systemId={option.value} className={classes.SystemView} showCustomName={true} />
</div>
);
};

View File

@@ -1,170 +0,0 @@
@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);
}
}

View File

@@ -1,347 +0,0 @@
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)} />
</>
);
};

View File

@@ -1 +0,0 @@
export * from './TopSearch.tsx';

View File

@@ -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, useMemo, useRef, useState } from 'react';
import { useCallback, useEffect, 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, main, following, updateTracking } = useTracking();
const { trackingCharacters, updateTracking } = useTracking();
const refVars = useRef({ trackingCharacters });
refVars.current = { trackingCharacters };
@@ -20,31 +20,9 @@ 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={items}
value={trackingCharacters}
size="small"
selectionMode={null}
selection={selected}

View File

@@ -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, Viewport, XYPosition } from 'reactflow';
import { Node, useReactFlow, 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, mapSettings, mapSettingsUpdate },
storedSettings: { interfaceSettings, settingsLocal },
} = useMapRootState();
const {
@@ -83,17 +83,8 @@ export const MapWrapper = () => {
systems,
systemSignatures,
deleteSystems,
mapSettingsUpdate,
});
ref.current = {
selectedConnections,
selectedSystems,
systemContextProps,
systems,
systemSignatures,
deleteSystems,
mapSettingsUpdate,
};
ref.current = { selectedConnections, selectedSystems, systemContextProps, systems, systemSignatures, deleteSystems };
useMapEventListener(event => {
runCommand(event);
@@ -130,10 +121,6 @@ export const MapWrapper = () => {
[update],
);
const handleChangeViewport = useCallback((viewport: Viewport) => {
ref.current.mapSettingsUpdate({ viewport });
}, []);
const handleCommand: OutCommandHandler = useCallback(
event => {
switch (event.type) {
@@ -272,7 +259,6 @@ export const MapWrapper = () => {
onConnectionInfoClick={handleConnectionDbClick}
onSystemContextMenu={handleSystemContextMenu}
onSelectionContextMenu={handleSystemMultipleContext}
onChangeViewport={handleChangeViewport}
minimapClasses={minimapClasses}
isShowMinimap={showMinimap}
showKSpaceBG={isShowKSpace}
@@ -284,7 +270,6 @@ export const MapWrapper = () => {
onAddSystem={onAddSystem}
minimapPlacement={minimapPosition}
localShowShipName={settingsLocal.showShipName}
defaultViewport={mapSettings.viewport}
/>
{openSettings != null && (

View File

@@ -42,5 +42,5 @@ export const SystemView = ({ systemId, systemInfo: customSystemInfo, showCustomN
return <SystemViewStandalone {...rest} {...systemInfo} />;
}
return <SystemViewStandalone customName={mapSystemInfo.name ?? undefined} {...rest} {...systemInfo} />;
return <SystemViewStandalone customName={mapSystemInfo.temporary_name ?? mapSystemInfo.name ?? undefined} {...rest} {...systemInfo} />;
};

View File

@@ -14,7 +14,6 @@ export type SystemViewStandaloneStatic = Pick<
export type SystemViewStandaloneProps = {
hideRegion?: boolean;
customName?: string;
nameClassName?: string;
compact?: boolean;
onContextMenu?(e: MouseEvent, systemId: string): void;
} & WithClassName &
@@ -24,7 +23,6 @@ export type SystemViewStandaloneProps = {
export const SystemViewStandalone = ({
className,
nameClassName,
hideRegion,
customName,
class_title,
@@ -59,14 +57,10 @@ 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,
},
nameClassName,
)}
className={clsx('text-gray-200 whitespace-nowrap', {
['overflow-hidden text-ellipsis']: compact,
[classes.CompactName]: compact,
})}
>
{customName ?? solar_system_name}
</span>

View File

@@ -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,28 +45,19 @@ export const WHClassView = ({
const whClass = useMemo(() => WORMHOLES_ADDITIONAL_INFO[whData.dest], [whData.dest]);
const whClassStyle = WORMHOLE_CLASS_STYLES[whClass?.wormholeClassID] ?? '';
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>;
}
const uid = useMemo(() => new Date().getTime().toString(), []);
return (
<div className={clsx(classes.WHClassViewRoot, className)}>
<WdTooltipWrapper
position={TooltipPosition.bottom}
content={
{!hideTooltip && (
<Tooltip
target={`.wh-name${whClassName}${uid}`}
position="right"
mouseTrack
mouseTrackLeft={20}
mouseTrackTop={30}
className="border border-green-300 rounded border-opacity-10 bg-stone-900 bg-opacity-90 "
>
<div className="flex gap-3">
<div className="flex flex-col gap-1">
<InfoDrawer title="Total mass">{prepareMass(whData.total_mass)}</InfoDrawer>
@@ -77,10 +68,24 @@ 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}`,
)}
>
{content}
</WdTooltipWrapper>
{!hideWhClassName && <span className={clsx({ [whClassStyle]: highlightName })}>{whClassName}</span>}
{!hideWhClass && whClass && (
<span className={clsx(classes.WHClassName, whClassStyle, classNameWh)}>
{useShortTitle ? whClass.shortTitle : whClass.shortName}
</span>
)}
</div>
</div>
);
};

View File

@@ -7,8 +7,6 @@ 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 {
@@ -153,11 +151,3 @@ 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',
};

View File

@@ -5,4 +5,3 @@ export * from './useHotkey';
export * from './usePageVisibility';
export * from './useSkipContextMenu';
export * from './useThrottle';
export * from './useStableValue';

View File

@@ -1,8 +0,0 @@
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;
};

View File

@@ -23,14 +23,12 @@ 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,
@@ -129,8 +127,6 @@ 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;
@@ -176,8 +172,6 @@ const MapRootContext = createContext<MapRootContextProps>({
settingsOnTheMapUpdate: () => null,
settingsKills: DEFAULT_KILLS_WIDGET_SETTINGS,
settingsKillsUpdate: () => null,
mapSettings: DEFAULT_MAP_SETTINGS,
mapSettingsUpdate: () => null,
isReady: false,
hasOldSettings: false,
getSettingsForExport: () => '',

View File

@@ -3,13 +3,16 @@ import {
InterfaceStoredSettings,
KillsWidgetSettings,
LocalWidgetSettings,
MapSettings,
MiniMapPlacement,
OnTheMapSettingsType,
PingsPlacement,
RoutesType,
} from '@/hooks/Mapper/mapRootProvider/types.ts';
import { DEFAULT_WIDGETS, STORED_VISIBLE_WIDGETS_DEFAULT } from '@/hooks/Mapper/components/mapInterface/constants.tsx';
import {
CURRENT_WINDOWS_VERSION,
DEFAULT_WIDGETS,
STORED_VISIBLE_WIDGETS_DEFAULT,
} from '@/hooks/Mapper/components/mapInterface/constants.tsx';
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
isShowMenu: false,
@@ -40,25 +43,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,
});

View File

@@ -1,55 +0,0 @@
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);
}
};

View File

@@ -0,0 +1,30 @@
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),
};
};

View File

@@ -5,8 +5,8 @@ import {
MapUserSettingsStructure,
RemoteAdminSettingsResponse,
} from '@/hooks/Mapper/mapRootProvider/types.ts';
import { createDefaultStoredSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultStoredSettings.ts';
import { applyMigrations } from '@/hooks/Mapper/mapRootProvider/migrations';
import { createDefaultWidgetSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultWidgetSettings.ts';
import { parseMapUserSettings } from '@/hooks/Mapper/components/helpers';
interface UseActualizeRemoteMapSettingsProps {
outCommand: OutCommandHandler;
@@ -37,14 +37,14 @@ export const useActualizeRemoteMapSettings = ({
}
if (res?.default_settings == null) {
applySettings(createDefaultStoredSettings());
applySettings(createDefaultWidgetSettings());
return;
}
try {
applySettings(applyMigrations(JSON.parse(res.default_settings)));
applySettings(parseMapUserSettings(res.default_settings));
} catch (error) {
applySettings(createDefaultStoredSettings());
applySettings(createDefaultWidgetSettings());
}
}, [outCommand]);

View File

@@ -63,127 +63,123 @@ 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 });
},
};
}, []);
};

View File

@@ -6,9 +6,7 @@ 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 { 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';
import { createDefaultWidgetSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultWidgetSettings.ts';
const EMPTY_OBJ = {};
@@ -16,7 +14,7 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
const [isReady, setIsReady] = useState(false);
const [hasOldSettings, setHasOldSettings] = useState(false);
const [mapUserSettings, setMapUserSettings] = useLocalStorageState<MapUserSettingsStructure>(LS_KEY, {
const [mapUserSettings, setMapUserSettings] = useLocalStorageState<MapUserSettingsStructure>('map-user-settings', {
defaultValue: EMPTY_OBJ,
});
@@ -85,20 +83,13 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
'killsWidget',
);
const [windowsSettings, windowsSettingsUpdate] = useSettingsValueAndSetter(
const [windowsSettings, setWindowsSettings] = useSettingsValueAndSetter(
mapUserSettings,
setMapUserSettings,
map_slug,
'widgets',
);
const [mapSettings, mapSettingsUpdate] = useSettingsValueAndSetter(
mapUserSettings,
setMapUserSettings,
map_slug,
'map',
);
// HERE we MUST work with migrations
useEffect(() => {
if (isReady) {
@@ -109,28 +100,36 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
return;
}
const currentMapUserSettings = mapUserSettings[map_slug];
if (currentMapUserSettings == null) {
if (mapUserSettings[map_slug] == null) {
return;
}
try {
// INFO: after migrations migratedFromOld always will be true
const migratedResult = applyMigrations(
!currentMapUserSettings.migratedFromOld ? extractData(LS_KEY_LEGASY) : currentMapUserSettings,
);
// 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);
if (!migratedResult) {
setIsReady(true);
return;
}
setMapUserSettings({ ...mapUserSettings, [map_slug]: migratedResult });
setIsReady(true);
} catch (error) {
setIsReady(true);
}
}, [isReady, mapUserSettings, map_slug, setMapUserSettings]);
setIsReady(true);
}, [
map_slug,
mapUserSettings,
interfaceSettings,
setInterfaceSettings,
settingsRoutes,
settingsRoutesUpdate,
settingsLocal,
settingsLocalUpdate,
settingsSignatures,
settingsSignaturesUpdate,
settingsOnTheMap,
settingsOnTheMapUpdate,
settingsKills,
settingsKillsUpdate,
isReady,
]);
const checkOldSettings = useCallback(() => {
const interfaceSettings = localStorage.getItem('window:interface:settings');
@@ -158,7 +157,7 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
}, []);
const resetSettings = useCallback(() => {
applySettings(createDefaultStoredSettings());
applySettings(createDefaultWidgetSettings());
}, [applySettings]);
return {
@@ -178,9 +177,7 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
settingsKills,
settingsKillsUpdate,
windowsSettings,
windowsSettingsUpdate,
mapSettings,
mapSettingsUpdate,
setWindowsSettings,
getSettingsForExport,
applySettings,

View File

@@ -1,11 +1,15 @@
import { Dispatch, SetStateAction, useCallback, useMemo, useRef } from 'react';
import { MapUserSettings, MapUserSettingsStructure, SettingsWrapper } from '@/hooks/Mapper/mapRootProvider/types.ts';
import {
MapUserSettings,
MapUserSettingsStructure,
SettingsWithVersion,
} from '@/hooks/Mapper/mapRootProvider/types.ts';
type ExtractSettings<S extends keyof MapUserSettings> = MapUserSettings[S] extends SettingsWrapper<infer U> ? U : never;
type ExtractSettings<S extends keyof MapUserSettings> =
MapUserSettings[S] extends SettingsWithVersion<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>];
@@ -20,7 +24,7 @@ export const useSettingsValueAndSetter = <S extends keyof MapUserSettings>(
if (!mapId) return {} as ExtractSettings<S>;
const mapSettings = settings[mapId];
return (mapSettings?.[setting] ?? ({} as ExtractSettings<S>)) as ExtractSettings<S>;
return (mapSettings?.[setting]?.settings ?? ({} as ExtractSettings<S>)) as ExtractSettings<S>;
}, [mapId, setting, settings]);
const refData = useRef({ mapId, setting, setSettings });
@@ -33,7 +37,8 @@ export const useSettingsValueAndSetter = <S extends keyof MapUserSettings>(
setSettings(all => {
const currentMap = all[mapId];
const prev = currentMap[setting] as ExtractSettings<S>;
const prev = currentMap[setting].settings as ExtractSettings<S>;
const version = currentMap[setting].version;
const patch =
typeof value === 'function' ? (value as (p: ExtractSettings<S>) => Partial<ExtractSettings<S>>)(prev) : value;
@@ -42,7 +47,10 @@ export const useSettingsValueAndSetter = <S extends keyof MapUserSettings>(
...all,
[mapId]: {
...currentMap,
[setting]: { ...(prev as any), ...patch } as ExtractSettings<S>,
[setting]: {
version,
settings: { ...(prev as any), ...patch } as ExtractSettings<S>,
},
},
};
});

View File

@@ -6,6 +6,7 @@ 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;
@@ -15,18 +16,19 @@ export type ToggleWidgetVisibility = (widgetId: WidgetsIds) => void;
interface UseStoreWidgetsProps {
windowsSettings: WindowStoreInfo;
windowsSettingsUpdate: Dispatch<SetStateAction<WindowStoreInfo>>;
setWindowsSettings: Dispatch<SetStateAction<WindowStoreInfo>>;
}
export const useStoreWidgets = ({ windowsSettings, windowsSettingsUpdate }: UseStoreWidgetsProps) => {
const ref = useRef({ windowsSettings, windowsSettingsUpdate });
ref.current = { windowsSettings, windowsSettingsUpdate };
export const useStoreWidgets = ({ windowsSettings, setWindowsSettings }: UseStoreWidgetsProps) => {
const ref = useRef({ windowsSettings, setWindowsSettings });
ref.current = { windowsSettings, setWindowsSettings };
const updateWidgetSettings: WindowsManagerOnChange = useCallback(({ windows, viewPort }) => {
const { windowsSettingsUpdate } = ref.current;
const { setWindowsSettings } = ref.current;
windowsSettingsUpdate(({ visible /*, windows*/ }: WindowStoreInfo) => {
setWindowsSettings(({ version, 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);
@@ -43,9 +45,9 @@ export const useStoreWidgets = ({ windowsSettings, windowsSettingsUpdate }: UseS
}, []);
const toggleWidgetVisibility: ToggleWidgetVisibility = useCallback(widgetId => {
const { windowsSettingsUpdate } = ref.current;
const { setWindowsSettings } = ref.current;
windowsSettingsUpdate(({ visible, windows, ...x }) => {
setWindowsSettings(({ visible, windows, ...x }) => {
const isCheckedPrev = visible.includes(widgetId);
if (!isCheckedPrev) {
const maxZIndex = Math.max(...windows.map(w => w.zIndex));
@@ -70,7 +72,7 @@ export const useStoreWidgets = ({ windowsSettings, windowsSettingsUpdate }: UseS
});
}, []);
const resetWidgets = useCallback(() => ref.current.windowsSettingsUpdate(getDefaultWidgetProps()), []);
const resetWidgets = useCallback(() => ref.current.setWindowsSettings(getDefaultWidgetProps()), []);
return {
windowsSettings,

View File

@@ -1,58 +0,0 @@
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;
}
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;
};

View File

@@ -1,4 +0,0 @@
import list from './list';
export * from './applyMigrations.ts';
export const migrations = [...list];

View File

@@ -1,5 +0,0 @@
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[];

View File

@@ -1,10 +0,0 @@
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));
},
};

View File

@@ -1,28 +0,0 @@
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,
},
};
},
};

View File

@@ -50,39 +50,36 @@ 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 MapViewPort = { zoom: number; x: number; y: number };
export type MapSettings = {
viewport: MapViewPort;
export type SettingsWithVersion<T> = {
version: number;
settings: T;
};
export type SettingsWrapper<T> = T;
export type MapUserSettings = {
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>;
widgets: SettingsWithVersion<WindowStoreInfo>;
interface: SettingsWithVersion<InterfaceStoredSettings>;
onTheMap: SettingsWithVersion<OnTheMapSettingsType>;
routes: SettingsWithVersion<RoutesType>;
localWidget: SettingsWithVersion<LocalWidgetSettings>;
signaturesWidget: SettingsWithVersion<SignatureSettingsType>;
killsWidget: SettingsWithVersion<KillsWidgetSettings>;
};
export type MapUserSettingsStructure = {
@@ -92,20 +89,3 @@ 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;
};

View File

@@ -1,4 +0,0 @@
export const STORED_SETTINGS_VERSION = 2;
export const LS_KEY_LEGASY = 'map-user-settings';
export const LS_KEY = 'map-user-settings-v2';

View File

@@ -118,7 +118,6 @@ 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[];

View File

@@ -2,4 +2,3 @@ export * from './contextStore';
export * from './getQueryVariable';
export * from './loadTextFile';
export * from './saveToFile';
export * from './omit';

View File

@@ -1,8 +0,0 @@
// 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;
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do
@source_url "https://github.com/wanderer-industries/wanderer"
@version "1.81.7"
@version "1.81.5"
def project do
[