Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80c14716eb | ||
|
|
8541fcd29b | ||
|
|
65d6acd7fb | ||
|
|
8b5f83d6b2 | ||
|
|
5e18891f4b | ||
|
|
d1959ca09f | ||
|
|
ec7a5ecf10 | ||
|
|
1dad9316bd | ||
|
|
4ce7160f79 | ||
|
|
2913bf19b0 | ||
|
|
7bd6be6fd0 | ||
|
|
705daa286b | ||
|
|
614d06be66 | ||
|
|
dec3e9a7ce | ||
|
|
0017ac3373 | ||
|
|
ae34744578 | ||
|
|
76885058ef | ||
|
|
fccb007036 | ||
|
|
a9f8901bd5 | ||
|
|
8ae968b5be | ||
|
|
beffd45e4f | ||
|
|
4488d81e8d | ||
|
|
618cc8c5f1 | ||
|
|
3fb22a877e | ||
|
|
8759409b82 | ||
|
|
245647ae6a | ||
|
|
eb7d33ea07 | ||
|
|
3575b16def | ||
|
|
a6fb680be8 | ||
|
|
9e17df5544 | ||
|
|
683fde7be4 | ||
|
|
ee68ce92a2 | ||
|
|
8b4e38d795 | ||
|
|
4995202627 | ||
|
|
986b997a6a | ||
|
|
9a957af759 | ||
|
|
c5a0a96016 | ||
|
|
8715a6c0ac | ||
|
|
c9810095aa | ||
|
|
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 | ||
|
|
d33a2e3a5b | ||
|
|
5c8753fb96 | ||
|
|
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"
|
||||
|
||||
209
CHANGELOG.md
@@ -2,6 +2,215 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v1.82.2](https://github.com/wanderer-industries/wanderer/compare/v1.82.1...v1.82.2) (2025-10-21)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix system static info - for J012635 add D382; for J015092 - changed from J244, Z060 to N110, J244; for J000487 removed C008
|
||||
|
||||
## [v1.82.1](https://github.com/wanderer-industries/wanderer/compare/v1.82.0...v1.82.1) (2025-10-20)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed 'viewer' map access & characters tracking
|
||||
|
||||
## [v1.82.0](https://github.com/wanderer-industries/wanderer/compare/v1.81.15...v1.82.0) (2025-10-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: Added an ability to copy/paste selected map area between maps
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Add ability to copy and past systems (UI part)
|
||||
|
||||
## [v1.81.15](https://github.com/wanderer-industries/wanderer/compare/v1.81.14...v1.81.15) (2025-10-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed problem with commit - for correct restore deprecated data - change config key
|
||||
|
||||
## [v1.81.14](https://github.com/wanderer-industries/wanderer/compare/v1.81.13...v1.81.14) (2025-10-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed problem with commit - for correct restore deprecated data
|
||||
|
||||
## [v1.81.13](https://github.com/wanderer-industries/wanderer/compare/v1.81.12...v1.81.13) (2025-10-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed system select after tab switch
|
||||
|
||||
## [v1.81.12](https://github.com/wanderer-industries/wanderer/compare/v1.81.11...v1.81.12) (2025-10-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map events buffering on tab switch
|
||||
|
||||
## [v1.81.11](https://github.com/wanderer-industries/wanderer/compare/v1.81.10...v1.81.11) (2025-10-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signatures: Fixed EOL indication for un-splashed and signatures list
|
||||
|
||||
## [v1.81.10](https://github.com/wanderer-industries/wanderer/compare/v1.81.9...v1.81.10) (2025-10-13)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signatures: Rework for lazy signatures deletion
|
||||
|
||||
## [v1.81.9](https://github.com/wanderer-industries/wanderer/compare/v1.81.8...v1.81.9) (2025-10-12)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signatures: Fixed issue with wrong linked signatures deletions
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useMapperHandlers } from './useMapperHandlers';
|
||||
import { MapRootContent } from '@/hooks/Mapper/components/mapRootContent/MapRootContent.tsx';
|
||||
import { MapRootProvider } from '@/hooks/Mapper/mapRootProvider';
|
||||
import './common-styles/main.scss';
|
||||
import { ToastProvider } from '@/hooks/Mapper/ToastProvider.tsx';
|
||||
|
||||
const ErrorFallback = () => {
|
||||
return <div className="!z-100 absolute w-screen h-screen bg-transparent"></div>;
|
||||
@@ -39,13 +40,15 @@ export default function MapRoot({ hooks }) {
|
||||
|
||||
return (
|
||||
<PrimeReactProvider>
|
||||
<MapRootProvider fwdRef={providerRef} outCommand={handleCommand}>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}>
|
||||
<ReactFlowProvider>
|
||||
<MapRootContent />
|
||||
</ReactFlowProvider>
|
||||
</ErrorBoundary>
|
||||
</MapRootProvider>
|
||||
<ToastProvider>
|
||||
<MapRootProvider fwdRef={providerRef} outCommand={handleCommand}>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}>
|
||||
<ReactFlowProvider>
|
||||
<MapRootContent />
|
||||
</ReactFlowProvider>
|
||||
</ErrorBoundary>
|
||||
</MapRootProvider>
|
||||
</ToastProvider>
|
||||
</PrimeReactProvider>
|
||||
);
|
||||
}
|
||||
|
||||
31
assets/js/hooks/Mapper/ToastProvider.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React, { createContext, useContext, useRef } from 'react';
|
||||
import { Toast } from 'primereact/toast';
|
||||
import type { ToastMessage } from 'primereact/toast';
|
||||
|
||||
interface ToastContextValue {
|
||||
toastRef: React.RefObject<Toast>;
|
||||
show: (message: ToastMessage | ToastMessage[]) => void;
|
||||
}
|
||||
|
||||
const ToastContext = createContext<ToastContextValue | null>(null);
|
||||
|
||||
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const toastRef = useRef<Toast>(null);
|
||||
|
||||
const show = (message: ToastMessage | ToastMessage[]) => {
|
||||
toastRef.current?.show(message);
|
||||
};
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={{ toastRef, show }}>
|
||||
<Toast ref={toastRef} position="top-right" />
|
||||
{children}
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useToast = (): ToastContextValue => {
|
||||
const context = useContext(ToastContext);
|
||||
if (!context) throw new Error('useToast must be used within a ToastProvider');
|
||||
return context;
|
||||
};
|
||||
@@ -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%;
|
||||
|
||||
@@ -6,21 +6,28 @@ import { MenuItem } from 'primereact/menuitem';
|
||||
export interface ContextMenuSystemMultipleProps {
|
||||
contextMenuRef: RefObject<ContextMenu>;
|
||||
onDeleteSystems(): void;
|
||||
onCopySystems(): void;
|
||||
}
|
||||
|
||||
export const ContextMenuSystemMultiple: React.FC<ContextMenuSystemMultipleProps> = ({
|
||||
contextMenuRef,
|
||||
onDeleteSystems,
|
||||
onCopySystems,
|
||||
}) => {
|
||||
const items: MenuItem[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
label: 'Copy',
|
||||
icon: PrimeIcons.COPY,
|
||||
command: onCopySystems,
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
icon: PrimeIcons.TRASH,
|
||||
command: onDeleteSystems,
|
||||
},
|
||||
];
|
||||
}, [onDeleteSystems]);
|
||||
}, [onCopySystems, onDeleteSystems]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -6,27 +6,34 @@ import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { encodeJsonToUriBase64 } from '@/hooks/Mapper/utils';
|
||||
import { useToast } from '@/hooks/Mapper/ToastProvider.tsx';
|
||||
|
||||
export const useContextMenuSystemMultipleHandlers = () => {
|
||||
const {
|
||||
data: { pings },
|
||||
data: { pings, connections },
|
||||
} = useMapRootState();
|
||||
|
||||
const { show } = useToast();
|
||||
|
||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||
const [systems, setSystems] = useState<Node<SolarSystemRawType>[]>();
|
||||
|
||||
const { deleteSystems } = useDeleteSystems();
|
||||
|
||||
const ping = useMemo(() => (pings.length === 1 ? pings[0] : undefined), [pings]);
|
||||
const refVars = useRef({ systems, ping, connections, deleteSystems });
|
||||
refVars.current = { systems, ping, connections, deleteSystems };
|
||||
|
||||
const handleSystemMultipleContext: NodeSelectionMouseHandler = (ev, systems_) => {
|
||||
const handleSystemMultipleContext = useCallback<NodeSelectionMouseHandler>((ev, systems_) => {
|
||||
setSystems(systems_);
|
||||
ev.preventDefault();
|
||||
ctxManager.next('ctxSysMult', contextMenuRef.current);
|
||||
contextMenuRef.current?.show(ev);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onDeleteSystems = useCallback(() => {
|
||||
const { systems, ping, deleteSystems } = refVars.current;
|
||||
|
||||
if (!systems) {
|
||||
return;
|
||||
}
|
||||
@@ -41,11 +48,34 @@ export const useContextMenuSystemMultipleHandlers = () => {
|
||||
}
|
||||
|
||||
deleteSystems(sysToDel);
|
||||
}, [deleteSystems, systems, ping]);
|
||||
}, []);
|
||||
|
||||
const onCopySystems = useCallback(async () => {
|
||||
const { systems, connections } = refVars.current;
|
||||
if (!systems) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connectionToCopy = connections.filter(
|
||||
c => systems.filter(s => [c.target, c.source].includes(s.id)).length == 2,
|
||||
);
|
||||
|
||||
await navigator.clipboard.writeText(
|
||||
encodeJsonToUriBase64({ systems: systems.map(x => x.data), connections: connectionToCopy }),
|
||||
);
|
||||
|
||||
show({
|
||||
severity: 'success',
|
||||
summary: 'Copied to clipboard',
|
||||
detail: `Successfully copied to clipboard - [${systems.length}] systems and [${connectionToCopy.length}] connections`,
|
||||
life: 3000,
|
||||
});
|
||||
}, [show]);
|
||||
|
||||
return {
|
||||
handleSystemMultipleContext,
|
||||
contextMenuRef,
|
||||
onDeleteSystems,
|
||||
onCopySystems,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -8,6 +8,4 @@ export type WaypointSetContextHandlerProps = {
|
||||
destination: string;
|
||||
};
|
||||
export type WaypointSetContextHandler = (props: WaypointSetContextHandlerProps) => void;
|
||||
export type NodeSelectionMouseHandler =
|
||||
| ((event: React.MouseEvent<Element, MouseEvent>, nodes: Node[]) => void)
|
||||
| undefined;
|
||||
export type NodeSelectionMouseHandler = (event: React.MouseEvent<Element, MouseEvent>, nodes: Node[]) => void;
|
||||
|
||||
@@ -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 { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem, onCommand });
|
||||
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 {
|
||||
|
||||
@@ -2,13 +2,21 @@ import React, { RefObject, useMemo } from 'react';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { MenuItem } from 'primereact/menuitem';
|
||||
import { PasteSystemsAndConnections } from '@/hooks/Mapper/components/map/components';
|
||||
|
||||
export interface ContextMenuRootProps {
|
||||
contextMenuRef: RefObject<ContextMenu>;
|
||||
pasteSystemsAndConnections: PasteSystemsAndConnections | undefined;
|
||||
onAddSystem(): void;
|
||||
onPasteSystemsAnsConnections(): void;
|
||||
}
|
||||
|
||||
export const ContextMenuRoot: React.FC<ContextMenuRootProps> = ({ contextMenuRef, onAddSystem }) => {
|
||||
export const ContextMenuRoot: React.FC<ContextMenuRootProps> = ({
|
||||
contextMenuRef,
|
||||
onAddSystem,
|
||||
onPasteSystemsAnsConnections,
|
||||
pasteSystemsAndConnections,
|
||||
}) => {
|
||||
const items: MenuItem[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@@ -16,8 +24,17 @@ export const ContextMenuRoot: React.FC<ContextMenuRootProps> = ({ contextMenuRef
|
||||
icon: PrimeIcons.PLUS,
|
||||
command: onAddSystem,
|
||||
},
|
||||
...(pasteSystemsAndConnections != null
|
||||
? [
|
||||
{
|
||||
label: 'Paste',
|
||||
icon: 'pi pi-clipboard',
|
||||
command: onPasteSystemsAnsConnections,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
}, [onAddSystem]);
|
||||
}, [onAddSystem, onPasteSystemsAnsConnections, pasteSystemsAndConnections]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,36 +1,76 @@
|
||||
import { useReactFlow, XYPosition } from 'reactflow';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { OnMapAddSystemCallback } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||
import { recenterSystemsByBounds } from '@/hooks/Mapper/helpers/recenterSystems.ts';
|
||||
import { OutCommand, OutCommandHandler, SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { decodeUriBase64ToJson } from '@/hooks/Mapper/utils';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { useReactFlow, XYPosition } from 'reactflow';
|
||||
|
||||
export type PasteSystemsAndConnections = {
|
||||
systems: SolarSystemRawType[];
|
||||
connections: SolarSystemConnection[];
|
||||
};
|
||||
|
||||
type UseContextMenuRootHandlers = {
|
||||
onAddSystem?: OnMapAddSystemCallback;
|
||||
onCommand?: OutCommandHandler;
|
||||
};
|
||||
|
||||
export const useContextMenuRootHandlers = ({ onAddSystem }: UseContextMenuRootHandlers = {}) => {
|
||||
export const useContextMenuRootHandlers = ({ onAddSystem, onCommand }: UseContextMenuRootHandlers = {}) => {
|
||||
const rf = useReactFlow();
|
||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||
const [position, setPosition] = useState<XYPosition | null>(null);
|
||||
const [pasteSystemsAndConnections, setPasteSystemsAndConnections] = useState<PasteSystemsAndConnections>();
|
||||
|
||||
const handleRootContext = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const handleRootContext = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
setPosition(rf.project({ x: e.clientX, y: e.clientY }));
|
||||
e.preventDefault();
|
||||
ctxManager.next('ctxRoot', contextMenuRef.current);
|
||||
contextMenuRef.current?.show(e);
|
||||
|
||||
try {
|
||||
const text = await navigator.clipboard.readText();
|
||||
const result = decodeUriBase64ToJson(text);
|
||||
setPasteSystemsAndConnections(result as PasteSystemsAndConnections);
|
||||
} catch (err) {
|
||||
setPasteSystemsAndConnections(undefined);
|
||||
// do nothing
|
||||
}
|
||||
};
|
||||
|
||||
const ref = useRef({ onAddSystem, position });
|
||||
ref.current = { onAddSystem, position };
|
||||
const ref = useRef({ onAddSystem, position, pasteSystemsAndConnections, onCommand });
|
||||
ref.current = { onAddSystem, position, pasteSystemsAndConnections, onCommand };
|
||||
|
||||
const onAddSystemCallback = useCallback(() => {
|
||||
ref.current.onAddSystem?.({ coordinates: position });
|
||||
}, [position]);
|
||||
|
||||
const onPasteSystemsAnsConnections = useCallback(async () => {
|
||||
const { pasteSystemsAndConnections, onCommand, position } = ref.current;
|
||||
if (!position || !onCommand || !pasteSystemsAndConnections) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { systems } = recenterSystemsByBounds(pasteSystemsAndConnections.systems);
|
||||
|
||||
await onCommand({
|
||||
type: OutCommand.manualPasteSystemsAndConnections,
|
||||
data: {
|
||||
systems: systems.map(({ position: srcPos, ...rest }) => ({
|
||||
position: { x: Math.round(srcPos.x + position.x), y: Math.round(srcPos.y + position.y) },
|
||||
...rest,
|
||||
})),
|
||||
connections: pasteSystemsAndConnections.connections,
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
handleRootContext,
|
||||
|
||||
pasteSystemsAndConnections,
|
||||
contextMenuRef,
|
||||
onAddSystem: onAddSystemCallback,
|
||||
onPasteSystemsAnsConnections,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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,15 +1,16 @@
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { InfoDrawer } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
|
||||
import classes from './UnsplashedSignature.module.scss';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { WORMHOLE_CLASS_STYLES, WORMHOLES_ADDITIONAL_INFO } from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import { useMemo } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { renderInfoColumn } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
||||
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
|
||||
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
|
||||
import clsx from 'clsx';
|
||||
import { useMemo } from 'react';
|
||||
import classes from './UnsplashedSignature.module.scss';
|
||||
|
||||
interface UnsplashedSignatureProps {
|
||||
signature: SystemSignature;
|
||||
@@ -35,7 +36,7 @@ export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) =>
|
||||
}, [customInfo]);
|
||||
|
||||
const isEOL = useMemo(() => {
|
||||
return customInfo?.isEOL;
|
||||
return customInfo?.time_status === TimeStatus._1h;
|
||||
}, [customInfo]);
|
||||
|
||||
const whClassStyle = useMemo(() => {
|
||||
|
||||
@@ -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);
|
||||
}, []);
|
||||
};
|
||||
|
||||
@@ -49,91 +49,87 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
||||
const { charactersUpdated, presentCharacters, characterAdded, characterRemoved, characterUpdated } =
|
||||
useCommandsCharacters();
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => {
|
||||
return {
|
||||
command(type, data) {
|
||||
switch (type) {
|
||||
case Commands.init:
|
||||
mapInit(data as CommandInit);
|
||||
break;
|
||||
case Commands.addSystems:
|
||||
setTimeout(() => mapAddSystems(data as CommandAddSystems), 100);
|
||||
break;
|
||||
case Commands.updateSystems:
|
||||
mapUpdateSystems(data as CommandUpdateSystems);
|
||||
break;
|
||||
case Commands.removeSystems:
|
||||
setTimeout(() => removeSystems(data as CommandRemoveSystems), 100);
|
||||
break;
|
||||
case Commands.addConnections:
|
||||
setTimeout(() => addConnections(data as CommandAddConnections), 100);
|
||||
break;
|
||||
case Commands.removeConnections:
|
||||
setTimeout(() => removeConnections(data as CommandRemoveConnections), 100);
|
||||
break;
|
||||
case Commands.charactersUpdated:
|
||||
charactersUpdated(data as CommandCharactersUpdated);
|
||||
break;
|
||||
case Commands.characterAdded:
|
||||
characterAdded(data as CommandCharacterAdded);
|
||||
break;
|
||||
case Commands.characterRemoved:
|
||||
characterRemoved(data as CommandCharacterRemoved);
|
||||
break;
|
||||
case Commands.characterUpdated:
|
||||
characterUpdated(data as CommandCharacterUpdated);
|
||||
break;
|
||||
case Commands.presentCharacters:
|
||||
presentCharacters(data as CommandPresentCharacters);
|
||||
break;
|
||||
case Commands.updateConnection:
|
||||
updateConnection(data as CommandUpdateConnection);
|
||||
break;
|
||||
case Commands.mapUpdated:
|
||||
mapUpdated(data as CommandMapUpdated);
|
||||
break;
|
||||
case Commands.killsUpdated:
|
||||
killsUpdated(data as CommandKillsUpdated);
|
||||
break;
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
command(type, data) {
|
||||
switch (type) {
|
||||
case Commands.init:
|
||||
mapInit(data as CommandInit);
|
||||
break;
|
||||
case Commands.addSystems:
|
||||
setTimeout(() => mapAddSystems(data as CommandAddSystems), 100);
|
||||
break;
|
||||
case Commands.updateSystems:
|
||||
mapUpdateSystems(data as CommandUpdateSystems);
|
||||
break;
|
||||
case Commands.removeSystems:
|
||||
setTimeout(() => removeSystems(data as CommandRemoveSystems), 100);
|
||||
break;
|
||||
case Commands.addConnections:
|
||||
setTimeout(() => addConnections(data as CommandAddConnections), 100);
|
||||
break;
|
||||
case Commands.removeConnections:
|
||||
setTimeout(() => removeConnections(data as CommandRemoveConnections), 100);
|
||||
break;
|
||||
case Commands.charactersUpdated:
|
||||
charactersUpdated(data as CommandCharactersUpdated);
|
||||
break;
|
||||
case Commands.characterAdded:
|
||||
characterAdded(data as CommandCharacterAdded);
|
||||
break;
|
||||
case Commands.characterRemoved:
|
||||
characterRemoved(data as CommandCharacterRemoved);
|
||||
break;
|
||||
case Commands.characterUpdated:
|
||||
characterUpdated(data as CommandCharacterUpdated);
|
||||
break;
|
||||
case Commands.presentCharacters:
|
||||
presentCharacters(data as CommandPresentCharacters);
|
||||
break;
|
||||
case Commands.updateConnection:
|
||||
updateConnection(data as CommandUpdateConnection);
|
||||
break;
|
||||
case Commands.mapUpdated:
|
||||
mapUpdated(data as CommandMapUpdated);
|
||||
break;
|
||||
case Commands.killsUpdated:
|
||||
killsUpdated(data as CommandKillsUpdated);
|
||||
break;
|
||||
|
||||
case Commands.centerSystem:
|
||||
setTimeout(() => {
|
||||
const systemId = `${data}`;
|
||||
centerSystem(systemId as CommandSelectSystem);
|
||||
}, 100);
|
||||
break;
|
||||
case Commands.centerSystem:
|
||||
setTimeout(() => {
|
||||
const systemId = `${data}`;
|
||||
centerSystem(systemId as CommandSelectSystem);
|
||||
}, 100);
|
||||
break;
|
||||
|
||||
case Commands.selectSystem:
|
||||
selectSystems({ systems: [data as string], delay: 500 });
|
||||
break;
|
||||
case Commands.selectSystem:
|
||||
selectSystems({ systems: [data as string], delay: 500 });
|
||||
break;
|
||||
|
||||
case Commands.selectSystems:
|
||||
selectSystems(data as CommandSelectSystems);
|
||||
break;
|
||||
case Commands.selectSystems:
|
||||
selectSystems(data as CommandSelectSystems);
|
||||
break;
|
||||
|
||||
case Commands.pingAdded:
|
||||
case Commands.pingCancelled:
|
||||
case Commands.routes:
|
||||
case Commands.signaturesUpdated:
|
||||
case Commands.linkSignatureToSystem:
|
||||
case Commands.detailedKillsUpdated:
|
||||
case Commands.characterActivityData:
|
||||
case Commands.trackingCharactersData:
|
||||
case Commands.updateActivity:
|
||||
case Commands.updateTracking:
|
||||
case Commands.userSettingsUpdated:
|
||||
// do nothing
|
||||
break;
|
||||
case Commands.pingAdded:
|
||||
case Commands.pingCancelled:
|
||||
case Commands.routes:
|
||||
case Commands.signaturesUpdated:
|
||||
case Commands.linkSignatureToSystem:
|
||||
case Commands.detailedKillsUpdated:
|
||||
case Commands.characterActivityData:
|
||||
case Commands.trackingCharactersData:
|
||||
case Commands.updateActivity:
|
||||
case Commands.updateTracking:
|
||||
case Commands.userSettingsUpdated:
|
||||
// do nothing
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`Map handlers: Unknown command: ${type}`, data);
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
default:
|
||||
console.warn(`Map handlers: Unknown command: ${type}`, data);
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -9,11 +9,12 @@ import {
|
||||
} from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
|
||||
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
|
||||
import { SETTINGS_KEYS, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
|
||||
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { SETTINGS_KEYS, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
|
||||
import { useSystemSignaturesData } from '../../widgets/SystemSignatures/hooks/useSystemSignaturesData';
|
||||
|
||||
const K162_SIGNATURE_TYPE = WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME['K162'].shortName;
|
||||
|
||||
@@ -135,6 +136,11 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
||||
[data, setVisible],
|
||||
);
|
||||
|
||||
const { signatures } = useSystemSignaturesData({
|
||||
systemId: `${data.solar_system_source}`,
|
||||
settings: LINK_SIGNTATURE_SETTINGS,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!targetSystemDynamicInfo) {
|
||||
handleHide();
|
||||
@@ -152,10 +158,12 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
||||
>
|
||||
<SystemSignaturesContent
|
||||
systemId={`${data.solar_system_source}`}
|
||||
hideLinkedSignatures
|
||||
signatures={signatures}
|
||||
hasUnsupportedLanguage={false}
|
||||
settings={LINK_SIGNTATURE_SETTINGS}
|
||||
hideLinkedSignatures
|
||||
selectable
|
||||
onSelect={handleSelect}
|
||||
selectable={true}
|
||||
filterSignature={filterSignature}
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,123 +1,16 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { SETTINGS_KEYS, SIGNATURE_WINDOW_ID, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks/useHotkey';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useSignatureUndo } from './hooks/useSignatureUndo';
|
||||
import { useSystemSignaturesData } from './hooks/useSystemSignaturesData';
|
||||
import { SystemSignaturesHeader } from './SystemSignatureHeader';
|
||||
import { SystemSignaturesContent } from './SystemSignaturesContent';
|
||||
import { SystemSignatureSettingsDialog } from './SystemSignatureSettingsDialog';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { SystemSignaturesHeader } from './SystemSignatureHeader';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks/useHotkey';
|
||||
import { getDeletionTimeoutMs } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
||||
import { SETTINGS_KEYS, SIGNATURE_WINDOW_ID, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
|
||||
|
||||
/**
|
||||
* Custom hook for managing pending signature deletions and undo countdown.
|
||||
*/
|
||||
function useSignatureUndo(
|
||||
systemId: string | undefined,
|
||||
settings: SignatureSettingsType,
|
||||
outCommand: OutCommandHandler,
|
||||
) {
|
||||
const [countdown, setCountdown] = useState<number>(0);
|
||||
const [pendingIds, setPendingIds] = useState<Set<string>>(new Set());
|
||||
const [deletedSignatures, setDeletedSignatures] = useState<ExtendedSystemSignature[]>([]);
|
||||
const intervalRef = useRef<number | null>(null);
|
||||
|
||||
const addDeleted = useCallback((signatures: ExtendedSystemSignature[]) => {
|
||||
const newIds = signatures.map(sig => sig.eve_id);
|
||||
setPendingIds(prev => {
|
||||
const next = new Set(prev);
|
||||
newIds.forEach(id => next.add(id));
|
||||
return next;
|
||||
});
|
||||
setDeletedSignatures(prev => [...prev, ...signatures]);
|
||||
}, []);
|
||||
|
||||
// Clear deleted signatures when system changes
|
||||
useEffect(() => {
|
||||
if (systemId) {
|
||||
setDeletedSignatures([]);
|
||||
setPendingIds(new Set());
|
||||
setCountdown(0);
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}
|
||||
}, [systemId]);
|
||||
|
||||
// kick off or clear countdown whenever pendingIds changes
|
||||
useEffect(() => {
|
||||
// clear any existing timer
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
|
||||
if (pendingIds.size === 0) {
|
||||
setCountdown(0);
|
||||
setDeletedSignatures([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// determine timeout from settings
|
||||
const timeoutMs = getDeletionTimeoutMs(settings);
|
||||
|
||||
// Ensure a minimum of 1 second for immediate deletion so the UI shows
|
||||
const effectiveTimeoutMs = timeoutMs === 0 ? 1000 : timeoutMs;
|
||||
|
||||
setCountdown(Math.ceil(effectiveTimeoutMs / 1000));
|
||||
|
||||
// start new interval
|
||||
intervalRef.current = window.setInterval(() => {
|
||||
setCountdown(prev => {
|
||||
if (prev <= 1) {
|
||||
clearInterval(intervalRef.current!);
|
||||
intervalRef.current = null;
|
||||
setPendingIds(new Set());
|
||||
setDeletedSignatures([]);
|
||||
return 0;
|
||||
}
|
||||
return prev - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [pendingIds, settings]);
|
||||
|
||||
// undo handler
|
||||
const handleUndo = useCallback(async () => {
|
||||
if (!systemId || pendingIds.size === 0) return;
|
||||
await outCommand({
|
||||
type: OutCommand.undoDeleteSignatures,
|
||||
data: { system_id: systemId, eve_ids: Array.from(pendingIds) },
|
||||
});
|
||||
setPendingIds(new Set());
|
||||
setDeletedSignatures([]);
|
||||
setCountdown(0);
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}, [systemId, pendingIds, outCommand]);
|
||||
|
||||
return {
|
||||
pendingIds,
|
||||
countdown,
|
||||
deletedSignatures,
|
||||
addDeleted,
|
||||
handleUndo,
|
||||
};
|
||||
}
|
||||
|
||||
export const SystemSignatures = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [sigCount, setSigCount] = useState(0);
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
|
||||
const {
|
||||
data: { selectedSystems },
|
||||
@@ -127,31 +20,6 @@ export const SystemSignatures = () => {
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
const isSystemSelected = useMemo(() => selectedSystems.length === 1, [selectedSystems.length]);
|
||||
const { pendingIds, countdown, deletedSignatures, addDeleted, handleUndo } = useSignatureUndo(
|
||||
systemId,
|
||||
settingsSignatures,
|
||||
outCommand,
|
||||
);
|
||||
|
||||
useHotkey(true, ['z', 'Z'], (event: KeyboardEvent) => {
|
||||
if (pendingIds.size > 0 && countdown > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleUndo();
|
||||
}
|
||||
});
|
||||
|
||||
const handleCountChange = useCallback((count: number) => {
|
||||
setSigCount(count);
|
||||
}, []);
|
||||
|
||||
const handleSettingsSave = useCallback(
|
||||
(newSettings: SignatureSettingsType) => {
|
||||
settingsSignaturesUpdate(newSettings);
|
||||
setVisible(false);
|
||||
},
|
||||
[settingsSignaturesUpdate],
|
||||
);
|
||||
|
||||
const handleLazyDeleteToggle = useCallback(
|
||||
(value: boolean) => {
|
||||
@@ -163,7 +31,42 @@ export const SystemSignatures = () => {
|
||||
[settingsSignaturesUpdate],
|
||||
);
|
||||
|
||||
const openSettings = useCallback(() => setVisible(true), []);
|
||||
const {
|
||||
signatures,
|
||||
selectedSignatures,
|
||||
setSelectedSignatures,
|
||||
handleDeleteSelected,
|
||||
handleSelectAll,
|
||||
handlePaste,
|
||||
hasUnsupportedLanguage,
|
||||
} = useSystemSignaturesData({
|
||||
systemId,
|
||||
settings: settingsSignatures,
|
||||
onLazyDeleteChange: handleLazyDeleteToggle,
|
||||
});
|
||||
|
||||
const sigCount = useMemo(() => signatures.length, [signatures]);
|
||||
const deletedSignatures = useMemo(() => signatures.filter(s => s.deleted), [signatures]);
|
||||
|
||||
const { countdown, handleUndo } = useSignatureUndo(systemId, settingsSignatures, deletedSignatures, outCommand);
|
||||
|
||||
useHotkey(true, ['z', 'Z'], (event: KeyboardEvent) => {
|
||||
if (deletedSignatures.length > 0 && countdown > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleUndo();
|
||||
}
|
||||
});
|
||||
|
||||
const handleSettingsSave = useCallback(
|
||||
(newSettings: SignatureSettingsType) => {
|
||||
settingsSignaturesUpdate(newSettings);
|
||||
setShowSettings(false);
|
||||
},
|
||||
[settingsSignaturesUpdate],
|
||||
);
|
||||
|
||||
const openSettings = useCallback(() => setShowSettings(true), []);
|
||||
|
||||
return (
|
||||
<Widget
|
||||
@@ -171,7 +74,7 @@ export const SystemSignatures = () => {
|
||||
<SystemSignaturesHeader
|
||||
sigCount={sigCount}
|
||||
lazyDeleteValue={settingsSignatures[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean}
|
||||
pendingCount={pendingIds.size}
|
||||
pendingCount={deletedSignatures.length}
|
||||
undoCountdown={countdown}
|
||||
onLazyDeleteChange={handleLazyDeleteToggle}
|
||||
onUndoClick={handleUndo}
|
||||
@@ -187,18 +90,21 @@ export const SystemSignatures = () => {
|
||||
) : (
|
||||
<SystemSignaturesContent
|
||||
systemId={systemId}
|
||||
signatures={signatures}
|
||||
selectedSignatures={selectedSignatures}
|
||||
onSelectSignatures={setSelectedSignatures}
|
||||
onDeleteSelected={handleDeleteSelected}
|
||||
onSelectAll={handleSelectAll}
|
||||
onPaste={handlePaste}
|
||||
hasUnsupportedLanguage={hasUnsupportedLanguage}
|
||||
settings={settingsSignatures}
|
||||
deletedSignatures={deletedSignatures}
|
||||
onLazyDeleteChange={handleLazyDeleteToggle}
|
||||
onCountChange={handleCountChange}
|
||||
onSignatureDeleted={addDeleted}
|
||||
/>
|
||||
)}
|
||||
|
||||
{visible && (
|
||||
{showSettings && (
|
||||
<SystemSignatureSettingsDialog
|
||||
settings={settingsSignatures}
|
||||
onCancel={() => setVisible(false)}
|
||||
onCancel={() => setShowSettings(false)}
|
||||
onSave={handleSettingsSave}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -33,34 +33,39 @@ import { useClipboard, useHotkey } from '@/hooks/Mapper/hooks';
|
||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { getSignatureRowClass } from '../helpers/rowStyles';
|
||||
import { useSystemSignaturesData } from '../hooks/useSystemSignaturesData';
|
||||
|
||||
const renderColIcon = (sig: SystemSignature) => renderIcon(sig);
|
||||
|
||||
interface SystemSignaturesContentProps {
|
||||
systemId: string;
|
||||
signatures: ExtendedSystemSignature[];
|
||||
selectedSignatures?: ExtendedSystemSignature[];
|
||||
onSelectSignatures?: (s: ExtendedSystemSignature[]) => void;
|
||||
onDeleteSelected?: () => Promise<void>;
|
||||
onSelectAll?: () => void;
|
||||
onPaste?: (clipboardString: string) => void;
|
||||
settings: SignatureSettingsType;
|
||||
hideLinkedSignatures?: boolean;
|
||||
hasUnsupportedLanguage?: boolean;
|
||||
selectable?: boolean;
|
||||
onSelect?: (signature: SystemSignature) => void;
|
||||
onLazyDeleteChange?: (value: boolean) => void;
|
||||
onCountChange?: (count: number) => void;
|
||||
filterSignature?: (signature: SystemSignature) => boolean;
|
||||
onSignatureDeleted?: (deletedSignatures: ExtendedSystemSignature[]) => void;
|
||||
deletedSignatures?: ExtendedSystemSignature[];
|
||||
}
|
||||
|
||||
export const SystemSignaturesContent = ({
|
||||
systemId,
|
||||
signatures,
|
||||
selectedSignatures,
|
||||
onSelectSignatures,
|
||||
onDeleteSelected,
|
||||
onSelectAll,
|
||||
onPaste,
|
||||
settings,
|
||||
hideLinkedSignatures,
|
||||
hasUnsupportedLanguage,
|
||||
selectable,
|
||||
onSelect,
|
||||
onLazyDeleteChange,
|
||||
onCountChange,
|
||||
filterSignature,
|
||||
onSignatureDeleted,
|
||||
deletedSignatures = [],
|
||||
}: SystemSignaturesContentProps) => {
|
||||
const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState<SystemSignature | null>(null);
|
||||
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
|
||||
@@ -79,32 +84,18 @@ export const SystemSignaturesContent = ({
|
||||
|
||||
const { clipboardContent, setClipboardContent } = useClipboard();
|
||||
|
||||
const {
|
||||
signatures,
|
||||
selectedSignatures,
|
||||
setSelectedSignatures,
|
||||
handleDeleteSelected,
|
||||
handleSelectAll,
|
||||
handlePaste,
|
||||
hasUnsupportedLanguage,
|
||||
} = useSystemSignaturesData({
|
||||
systemId,
|
||||
settings,
|
||||
onCountChange,
|
||||
onLazyDeleteChange,
|
||||
onSignatureDeleted,
|
||||
});
|
||||
const deletedSignatures = useMemo(() => signatures.filter(s => s.deleted), [signatures]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectable) return;
|
||||
if (!clipboardContent?.text) return;
|
||||
|
||||
handlePaste(clipboardContent.text);
|
||||
onPaste?.(clipboardContent.text);
|
||||
|
||||
setClipboardContent(null);
|
||||
}, [selectable, clipboardContent, handlePaste, setClipboardContent]);
|
||||
}, [selectable, clipboardContent, onPaste, setClipboardContent]);
|
||||
|
||||
useHotkey(true, ['a'], handleSelectAll);
|
||||
useHotkey(true, ['a'], () => onSelectAll?.());
|
||||
|
||||
useHotkey(false, ['Backspace', 'Delete'], (event: KeyboardEvent) => {
|
||||
const targetWindow = (event.target as HTMLHtmlElement)?.closest(`[data-window-id="${SIGNATURE_WINDOW_ID}"]`);
|
||||
@@ -117,7 +108,7 @@ export const SystemSignaturesContent = ({
|
||||
event.stopPropagation();
|
||||
|
||||
// Delete key should always immediately delete, never show pending deletions
|
||||
handleDeleteSelected();
|
||||
onDeleteSelected?.();
|
||||
});
|
||||
|
||||
const handleResize = useCallback(() => {
|
||||
@@ -152,9 +143,9 @@ export const SystemSignaturesContent = ({
|
||||
|
||||
selectable
|
||||
? onSelect?.(selectableSignatures[0])
|
||||
: setSelectedSignatures(selectableSignatures as ExtendedSystemSignature[]);
|
||||
: onSelectSignatures?.(selectableSignatures as ExtendedSystemSignature[]);
|
||||
},
|
||||
[onSelect, selectable, setSelectedSignatures, deletedSignatures],
|
||||
[onSelect, selectable, onSelectSignatures, deletedSignatures],
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -177,9 +168,6 @@ export const SystemSignaturesContent = ({
|
||||
);
|
||||
|
||||
const filteredSignatures = useMemo<ExtendedSystemSignature[]>(() => {
|
||||
// Get the set of deleted signature IDs for quick lookup
|
||||
const deletedIds = new Set(deletedSignatures.map(sig => sig.eve_id));
|
||||
|
||||
// Common filter function
|
||||
const shouldShowSignature = (sig: ExtendedSystemSignature): boolean => {
|
||||
if (filterSignature && !filterSignature(sig)) {
|
||||
@@ -213,24 +201,8 @@ export const SystemSignaturesContent = ({
|
||||
return settings[sig.kind] as boolean;
|
||||
};
|
||||
|
||||
// Filter active signatures, excluding any that are in the deleted list
|
||||
const activeSignatures = signatures.filter(sig => {
|
||||
// Skip if this signature is in the deleted list
|
||||
if (deletedIds.has(sig.eve_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return shouldShowSignature(sig);
|
||||
});
|
||||
|
||||
// Add deleted signatures with pending deletion flag, applying the same filters
|
||||
const deletedWithPendingFlag = deletedSignatures.filter(shouldShowSignature).map(sig => ({
|
||||
...sig,
|
||||
pendingDeletion: true,
|
||||
}));
|
||||
|
||||
return [...activeSignatures, ...deletedWithPendingFlag];
|
||||
}, [signatures, hideLinkedSignatures, settings, filterSignature, deletedSignatures]);
|
||||
return signatures.filter(sig => shouldShowSignature(sig));
|
||||
}, [signatures, hideLinkedSignatures, settings, filterSignature]);
|
||||
|
||||
const onRowMouseEnter = useCallback((e: DataTableRowMouseEvent) => {
|
||||
setHoveredSignature(e.data as SystemSignature);
|
||||
@@ -253,20 +225,18 @@ export const SystemSignaturesContent = ({
|
||||
|
||||
return getSignatureRowClass(
|
||||
rowData as ExtendedSystemSignature,
|
||||
refVars.current.selectedSignatures,
|
||||
refVars.current.selectedSignatures || [],
|
||||
refVars.current.settings[SETTINGS_KEYS.COLOR_BY_TYPE] as boolean,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleSortSettings = useCallback(
|
||||
(e: DataTableStateEvent) =>
|
||||
refVars.current.settingsSignaturesUpdate({
|
||||
...refVars.current.settingsSignatures,
|
||||
[SETTINGS_KEYS.SORT_FIELD]: e.sortField,
|
||||
[SETTINGS_KEYS.SORT_ORDER]: e.sortOrder,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const handleSortSettings = useCallback((e: DataTableStateEvent) => {
|
||||
refVars.current.settingsSignaturesUpdate({
|
||||
...refVars.current.settingsSignatures,
|
||||
[SETTINGS_KEYS.SORT_FIELD]: e.sortField,
|
||||
[SETTINGS_KEYS.SORT_ORDER]: e.sortOrder,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={tableRef} className="h-full">
|
||||
@@ -287,7 +257,7 @@ export const SystemSignaturesContent = ({
|
||||
value={filteredSignatures}
|
||||
size="small"
|
||||
selectionMode="multiple"
|
||||
selection={selectedSignatures}
|
||||
selection={selectedSignatures || []}
|
||||
metaKeySelection
|
||||
onSelectionChange={handleSelectSignatures}
|
||||
dataKey="eve_id"
|
||||
@@ -336,6 +306,8 @@ export const SystemSignaturesContent = ({
|
||||
style={{ maxWidth: nameColumnWidth }}
|
||||
hidden={isCompact || isMedium}
|
||||
body={renderInfoColumn}
|
||||
sortable
|
||||
sortField="name"
|
||||
/>
|
||||
{showDescriptionColumn && (
|
||||
<Column
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import { ExtendedSystemSignature, SignatureGroup } from '@/hooks/Mapper/types';
|
||||
import clsx from 'clsx';
|
||||
import { getRowBackgroundColor } from './getRowBackgroundColor';
|
||||
import classes from './rowStyles.module.scss';
|
||||
|
||||
@@ -20,7 +20,7 @@ export function getSignatureRowClass(
|
||||
return clsx([...baseCls, 'bg-violet-400/40 hover:bg-violet-300/40']);
|
||||
}
|
||||
|
||||
if (row.pendingDeletion) {
|
||||
if (row.deleted) {
|
||||
return clsx([...baseCls, 'bg-red-400/40 hover:bg-red-400/50']);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
||||
import { SignatureSettingsType } from '@/hooks/Mapper/constants/signatures.ts';
|
||||
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
export interface UseSystemSignaturesDataProps {
|
||||
systemId: string;
|
||||
settings: SignatureSettingsType;
|
||||
hideLinkedSignatures?: boolean;
|
||||
onCountChange?: (count: number) => void;
|
||||
onPendingChange?: (
|
||||
pending: React.MutableRefObject<Record<string, ExtendedSystemSignature>>,
|
||||
undo: () => void,
|
||||
) => void;
|
||||
onLazyDeleteChange?: (value: boolean) => void;
|
||||
deletionTiming?: number;
|
||||
}
|
||||
|
||||
export interface UseFetchingParams {
|
||||
systemId: string;
|
||||
settings: SignatureSettingsType;
|
||||
signaturesRef: React.MutableRefObject<ExtendedSystemSignature[]>;
|
||||
setSignatures: React.Dispatch<React.SetStateAction<ExtendedSystemSignature[]>>;
|
||||
pendingDeletionMapRef: React.MutableRefObject<Record<string, ExtendedSystemSignature>>;
|
||||
}
|
||||
|
||||
export interface UsePendingDeletionParams {
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { prepareUpdatePayload } from '../helpers';
|
||||
import { UsePendingDeletionParams } from './types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
export function usePendingDeletions({
|
||||
systemId,
|
||||
setSignatures,
|
||||
onPendingChange,
|
||||
}: Omit<UsePendingDeletionParams, 'deletionTiming'>) {
|
||||
const { outCommand } = useMapRootState();
|
||||
const pendingDeletionMapRef = useRef<Record<string, ExtendedSystemSignature>>({});
|
||||
|
||||
const processRemovedSignatures = useCallback(
|
||||
async (
|
||||
removed: ExtendedSystemSignature[],
|
||||
added: ExtendedSystemSignature[],
|
||||
updated: ExtendedSystemSignature[],
|
||||
) => {
|
||||
if (!removed.length) return;
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: prepareUpdatePayload(systemId, added, updated, removed),
|
||||
});
|
||||
},
|
||||
[systemId, outCommand],
|
||||
);
|
||||
|
||||
const clearPendingDeletions = useCallback(() => {
|
||||
pendingDeletionMapRef.current = {};
|
||||
setSignatures(prev => prev.map(x => (x.pendingDeletion ? { ...x, pendingDeletion: false } : x)));
|
||||
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
pendingDeletionMapRef,
|
||||
processRemovedSignatures,
|
||||
clearPendingDeletions,
|
||||
};
|
||||
}
|
||||
@@ -1,21 +1,27 @@
|
||||
import { useCallback } from 'react';
|
||||
import { SETTINGS_KEYS } from '@/hooks/Mapper/constants/signatures';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { ExtendedSystemSignature, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { prepareUpdatePayload, getActualSigs, mergeLocalPending } from '../helpers';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { getDeletionTimeoutMs } from '../constants';
|
||||
import { getActualSigs, prepareUpdatePayload } from '../helpers';
|
||||
import { UseFetchingParams } from './types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export const useSignatureFetching = ({
|
||||
systemId,
|
||||
signaturesRef,
|
||||
setSignatures,
|
||||
pendingDeletionMapRef,
|
||||
}: UseFetchingParams) => {
|
||||
export const useSignatureFetching = ({ systemId, settings, signaturesRef, setSignatures }: UseFetchingParams) => {
|
||||
const {
|
||||
data: { characters },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const deleteTimeout = useMemo(() => {
|
||||
const lazyDelete = settings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean;
|
||||
if (!lazyDelete) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return getDeletionTimeoutMs(settings);
|
||||
}, [settings]);
|
||||
|
||||
const handleGetSignatures = useCallback(async () => {
|
||||
if (!systemId) {
|
||||
setSignatures([]);
|
||||
@@ -32,24 +38,23 @@ export const useSignatureFetching = ({
|
||||
character_name: characters.find(c => c.eve_id === s.character_eve_id)?.name,
|
||||
})) as ExtendedSystemSignature[];
|
||||
|
||||
setSignatures(() => mergeLocalPending(pendingDeletionMapRef, extended));
|
||||
setSignatures(() => extended);
|
||||
}, [characters, systemId, outCommand]);
|
||||
|
||||
const handleUpdateSignatures = useCallback(
|
||||
async (newList: ExtendedSystemSignature[], updateOnly: boolean, skipUpdateUntouched?: boolean) => {
|
||||
const { added, updated, removed } = getActualSigs(
|
||||
signaturesRef.current,
|
||||
newList,
|
||||
updateOnly,
|
||||
skipUpdateUntouched,
|
||||
);
|
||||
const actualSigs = getActualSigs(signaturesRef.current, newList, updateOnly, skipUpdateUntouched);
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: prepareUpdatePayload(systemId, added, updated, removed),
|
||||
});
|
||||
const { added, updated, removed } = actualSigs;
|
||||
|
||||
if (updated.length !== 0 || added.length !== 0 || removed.length !== 0) {
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: { ...prepareUpdatePayload(systemId, added, updated, removed), deleteTimeout },
|
||||
});
|
||||
}
|
||||
},
|
||||
[systemId, outCommand, signaturesRef],
|
||||
[systemId, deleteTimeout, outCommand, signaturesRef],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
|
||||
import { ExtendedSystemSignature, OutCommandHandler } from '@/hooks/Mapper/types';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { getDeletionTimeoutMs } from '../constants';
|
||||
|
||||
/**
|
||||
* Custom hook for managing pending signature deletions and undo countdown.
|
||||
*/
|
||||
export function useSignatureUndo(
|
||||
systemId: string | undefined,
|
||||
settings: SignatureSettingsType,
|
||||
deletedSignatures: ExtendedSystemSignature[],
|
||||
outCommand: OutCommandHandler,
|
||||
) {
|
||||
const [countdown, setCountdown] = useState<number>(0);
|
||||
const intervalRef = useRef<number | null>(null);
|
||||
|
||||
// Clear deleted signatures when system changes
|
||||
useEffect(() => {
|
||||
if (systemId) {
|
||||
setCountdown(0);
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}
|
||||
}, [systemId]);
|
||||
|
||||
// kick off or clear countdown whenever pendingIds changes
|
||||
useEffect(() => {
|
||||
// clear any existing timer
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
|
||||
if (deletedSignatures.length === 0) {
|
||||
setCountdown(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// determine timeout from settings
|
||||
const timeoutMs = getDeletionTimeoutMs(settings);
|
||||
|
||||
// Ensure a minimum of 1 second for immediate deletion so the UI shows
|
||||
const effectiveTimeoutMs = timeoutMs === 0 ? 1000 : timeoutMs;
|
||||
|
||||
setCountdown(Math.ceil(effectiveTimeoutMs / 1000));
|
||||
|
||||
// start new interval
|
||||
intervalRef.current = window.setInterval(() => {
|
||||
setCountdown(prev => {
|
||||
if (prev <= 1) {
|
||||
clearInterval(intervalRef.current!);
|
||||
intervalRef.current = null;
|
||||
return 0;
|
||||
}
|
||||
return prev - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [deletedSignatures, settings]);
|
||||
|
||||
// undo handler
|
||||
const handleUndo = useCallback(async () => {
|
||||
if (!systemId || deletedSignatures.length === 0) return;
|
||||
await outCommand({
|
||||
type: OutCommand.undoDeleteSignatures,
|
||||
data: { system_id: systemId, eve_ids: deletedSignatures.map(s => s.eve_id) },
|
||||
});
|
||||
setCountdown(0);
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}, [systemId, deletedSignatures, outCommand]);
|
||||
|
||||
return {
|
||||
countdown,
|
||||
handleUndo,
|
||||
};
|
||||
}
|
||||
@@ -1,44 +1,29 @@
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { parseSignatures } from '@/hooks/Mapper/helpers';
|
||||
import { Commands, ExtendedSystemSignature, SignatureKind } from '@/hooks/Mapper/types';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import useRefState from 'react-usestateref';
|
||||
|
||||
import { getDeletionTimeoutMs } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { getActualSigs } from '../helpers';
|
||||
import { UseSystemSignaturesDataProps } from './types';
|
||||
import { usePendingDeletions } from './usePendingDeletions';
|
||||
import { useSignatureFetching } from './useSignatureFetching';
|
||||
import { SETTINGS_KEYS } from '@/hooks/Mapper/constants/signatures.ts';
|
||||
import { UseSystemSignaturesDataProps } from './types';
|
||||
import { useSignatureFetching } from './useSignatureFetching';
|
||||
|
||||
export const useSystemSignaturesData = ({
|
||||
systemId,
|
||||
settings,
|
||||
onCountChange,
|
||||
onPendingChange,
|
||||
onLazyDeleteChange,
|
||||
onSignatureDeleted,
|
||||
}: Omit<UseSystemSignaturesDataProps, 'deletionTiming'> & {
|
||||
onSignatureDeleted?: (deletedSignatures: ExtendedSystemSignature[]) => void;
|
||||
}) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
const [signatures, setSignatures, signaturesRef] = useRefState<ExtendedSystemSignature[]>([]);
|
||||
const [selectedSignatures, setSelectedSignatures] = useState<ExtendedSystemSignature[]>([]);
|
||||
const [hasUnsupportedLanguage, setHasUnsupportedLanguage] = useState<boolean>(false);
|
||||
|
||||
const { pendingDeletionMapRef, processRemovedSignatures, clearPendingDeletions } = usePendingDeletions({
|
||||
systemId,
|
||||
setSignatures,
|
||||
onPendingChange,
|
||||
});
|
||||
|
||||
const { handleGetSignatures, handleUpdateSignatures } = useSignatureFetching({
|
||||
systemId,
|
||||
settings,
|
||||
signaturesRef,
|
||||
setSignatures,
|
||||
pendingDeletionMapRef,
|
||||
});
|
||||
|
||||
const handlePaste = useCallback(
|
||||
@@ -67,40 +52,14 @@ export const useSystemSignaturesData = ({
|
||||
setHasUnsupportedLanguage(false);
|
||||
}
|
||||
|
||||
const currentNonPending = lazyDeleteValue
|
||||
? signaturesRef.current.filter(sig => !sig.pendingDeletion)
|
||||
: signaturesRef.current.filter(sig => !sig.pendingDeletion || !sig.pendingAddition);
|
||||
|
||||
const { added, updated, removed } = getActualSigs(currentNonPending, incomingSignatures, !lazyDeleteValue, false);
|
||||
|
||||
if (removed.length > 0) {
|
||||
await processRemovedSignatures(removed, added, updated);
|
||||
|
||||
// Show pending deletions if lazy deletion is enabled
|
||||
// The deletion timing controls how long the countdown lasts, not whether lazy delete is active
|
||||
if (onSignatureDeleted && lazyDeleteValue) {
|
||||
onSignatureDeleted(removed);
|
||||
}
|
||||
}
|
||||
|
||||
if (updated.length !== 0 || added.length !== 0) {
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
added,
|
||||
updated,
|
||||
removed: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
await handleUpdateSignatures(incomingSignatures, !lazyDeleteValue, false);
|
||||
|
||||
const keepLazy = settings[SETTINGS_KEYS.KEEP_LAZY_DELETE] as boolean;
|
||||
if (lazyDeleteValue && !keepLazy) {
|
||||
onLazyDeleteChange?.(false);
|
||||
}
|
||||
},
|
||||
[settings, signaturesRef, processRemovedSignatures, outCommand, systemId, onLazyDeleteChange, onSignatureDeleted],
|
||||
[settings, handleUpdateSignatures, onLazyDeleteChange],
|
||||
);
|
||||
|
||||
const handleDeleteSelected = useCallback(async () => {
|
||||
@@ -109,23 +68,15 @@ export const useSystemSignaturesData = ({
|
||||
const selectedIds = selectedSignatures.map(s => s.eve_id);
|
||||
const finalList = signatures.filter(s => !selectedIds.includes(s.eve_id));
|
||||
|
||||
// IMPORTANT: Send deletion to server BEFORE updating local state
|
||||
// Otherwise signaturesRef.current will be updated and getActualSigs won't detect removals
|
||||
await handleUpdateSignatures(finalList, false, true);
|
||||
|
||||
// Update local state after server call
|
||||
setSignatures(finalList);
|
||||
setSelectedSignatures([]);
|
||||
}, [handleUpdateSignatures, selectedSignatures, signatures, setSignatures]);
|
||||
|
||||
await handleUpdateSignatures(finalList, false, true);
|
||||
}, [handleUpdateSignatures, selectedSignatures, signatures]);
|
||||
|
||||
const handleSelectAll = useCallback(() => {
|
||||
setSelectedSignatures(signatures);
|
||||
}, [signatures]);
|
||||
|
||||
const undoPending = useCallback(() => {
|
||||
clearPendingDeletions();
|
||||
}, [clearPendingDeletions]);
|
||||
|
||||
useMapEventListener(event => {
|
||||
if (event.name === Commands.signaturesUpdated && String(event.data) === String(systemId)) {
|
||||
handleGetSignatures();
|
||||
@@ -136,18 +87,13 @@ export const useSystemSignaturesData = ({
|
||||
useEffect(() => {
|
||||
if (!systemId) {
|
||||
setSignatures([]);
|
||||
undoPending();
|
||||
return;
|
||||
}
|
||||
handleGetSignatures();
|
||||
}, [systemId]);
|
||||
|
||||
useEffect(() => {
|
||||
onCountChange?.(signatures.length);
|
||||
}, [signatures]);
|
||||
|
||||
return {
|
||||
signatures: signatures.filter(sig => !sig.deleted),
|
||||
signatures,
|
||||
selectedSignatures,
|
||||
setSelectedSignatures,
|
||||
handleDeleteSelected,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { SystemViewStandalone, TooltipPosition, WHClassView } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
|
||||
import { renderK162Type } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { renderName } from './renderName.tsx';
|
||||
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
|
||||
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo.ts';
|
||||
import clsx from 'clsx';
|
||||
import { renderName } from './renderName.tsx';
|
||||
|
||||
export const renderInfoColumn = (row: SystemSignature) => {
|
||||
if (!row.group || row.group === SignatureGroup.Wormhole) {
|
||||
@@ -18,7 +18,9 @@ export const renderInfoColumn = (row: SystemSignature) => {
|
||||
|
||||
return (
|
||||
<div className="flex justify-start items-center gap-[4px]">
|
||||
{customInfo.isEOL && (
|
||||
{row.temporary_name && <span className={clsx('text-[12px]')}>{row.temporary_name}</span>}
|
||||
|
||||
{customInfo.time_status === TimeStatus._1h && (
|
||||
<WdTooltipWrapper offset={5} position={TooltipPosition.top} content="Signature marked as EOL">
|
||||
<div className="pi pi-clock text-fuchsia-400 text-[11px] mr-[2px]"></div>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { OutCommand, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { Controller, FormProvider, useForm } from 'react-hook-form';
|
||||
import {
|
||||
SignatureGroupContent,
|
||||
SignatureGroupSelect,
|
||||
} from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { SystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OutCommand, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { Controller, FormProvider, useForm } from 'react-hook-form';
|
||||
|
||||
type SystemSignaturePrepared = Omit<SystemSignature, 'linked_system'> & {
|
||||
linked_system: string;
|
||||
@@ -119,6 +119,7 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
|
||||
added: [],
|
||||
updated: [out],
|
||||
removed: [],
|
||||
deleteTimeout: 0,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import { Map, MAP_ROOT_ID } from '@/hooks/Mapper/components/map/Map.tsx';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { CommandSelectSystems, OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OnMapAddSystemCallback, OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||
import isEqual from 'lodash.isequal';
|
||||
import { ContextMenuSystem, useContextMenuSystemHandlers } from '@/hooks/Mapper/components/contexts';
|
||||
import { Map, MAP_ROOT_ID } from '@/hooks/Mapper/components/map/Map.tsx';
|
||||
import { OnMapAddSystemCallback, OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||
import {
|
||||
SystemCustomLabelDialog,
|
||||
SystemLinkSignatureDialog,
|
||||
SystemSettingsDialog,
|
||||
} from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { Connections } from '@/hooks/Mapper/components/mapRootContent/components/Connections';
|
||||
import { ContextMenuSystemMultiple, useContextMenuSystemMultipleHandlers } from '../contexts/ContextMenuSystemMultiple';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CommandSelectSystems, OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { Node, useReactFlow, XYPosition } from 'reactflow';
|
||||
import isEqual from 'lodash.isequal';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Node, useReactFlow, Viewport, XYPosition } from 'reactflow';
|
||||
import { ContextMenuSystemMultiple, useContextMenuSystemMultipleHandlers } from '../contexts/ContextMenuSystemMultiple';
|
||||
|
||||
import { useCommandsSystems } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { emitMapEvent, useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { useCommandsSystems } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
|
||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
import { useCommonMapEventProcessor } from '@/hooks/Mapper/components/mapWrapper/hooks/useCommonMapEventProcessor.ts';
|
||||
import {
|
||||
AddSystemDialog,
|
||||
SearchOnSubmitCallback,
|
||||
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
|
||||
import { useHotkey } from '../../hooks/useHotkey';
|
||||
import { PingType } from '@/hooks/Mapper/types/ping.ts';
|
||||
import { SystemPingDialog } from '@/hooks/Mapper/components/mapInterface/components/SystemPingDialog';
|
||||
import { MiniMapPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { useCommonMapEventProcessor } from '@/hooks/Mapper/components/mapWrapper/hooks/useCommonMapEventProcessor.ts';
|
||||
import { MINIMAP_PLACEMENT_MAP } from '@/hooks/Mapper/constants.ts';
|
||||
import { MiniMapPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { PingType } from '@/hooks/Mapper/types/ping.ts';
|
||||
import type { PanelPosition } from '@reactflow/core';
|
||||
import { useHotkey } from '../../hooks/useHotkey';
|
||||
import { MINI_MAP_PLACEMENT_OFFSETS } from './constants.ts';
|
||||
|
||||
// TODO: INFO - this component needs for abstract work with Map instance
|
||||
@@ -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);
|
||||
@@ -97,7 +106,7 @@ export const MapWrapper = () => {
|
||||
|
||||
runCommand({
|
||||
name: Commands.selectSystems,
|
||||
data: { systems: selectedSystems } as CommandSelectSystems,
|
||||
data: { systems: selectedSystems, delay: 200 } as CommandSelectSystems,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
@@ -3,3 +3,4 @@ export * from './parseSignatures';
|
||||
export * from './getSystemById';
|
||||
export * from './getEveImageUrl';
|
||||
export * from './toastHelpers';
|
||||
export * from './recenterSystems';
|
||||
|
||||
39
assets/js/hooks/Mapper/helpers/recenterSystems.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { XYPosition } from 'reactflow';
|
||||
|
||||
export type WithPosition<T = unknown> = T & { position: XYPosition };
|
||||
|
||||
export const computeBoundsCenter = (items: Array<WithPosition>): XYPosition => {
|
||||
if (items.length === 0) return { x: 0, y: 0 };
|
||||
|
||||
let minX = Infinity;
|
||||
let maxX = -Infinity;
|
||||
let minY = Infinity;
|
||||
let maxY = -Infinity;
|
||||
|
||||
for (const { position } of items) {
|
||||
if (position.x < minX) minX = position.x;
|
||||
if (position.x > maxX) maxX = position.x;
|
||||
if (position.y < minY) minY = position.y;
|
||||
if (position.y > maxY) maxY = position.y;
|
||||
}
|
||||
|
||||
return {
|
||||
x: minX + (maxX - minX) / 2,
|
||||
y: minY + (maxY - minY) / 2,
|
||||
};
|
||||
};
|
||||
|
||||
/** Смещает все точки так, чтобы центр области стал (0,0) */
|
||||
export const recenterSystemsByBounds = <T extends WithPosition>(items: T[]): { center: XYPosition; systems: T[] } => {
|
||||
const center = computeBoundsCenter(items);
|
||||
|
||||
const systems = items.map(it => ({
|
||||
...it,
|
||||
position: {
|
||||
x: it.position.x - center.x,
|
||||
y: it.position.y - center.y,
|
||||
},
|
||||
}));
|
||||
|
||||
return { center, systems };
|
||||
};
|
||||
@@ -5,3 +5,4 @@ export * from './useHotkey';
|
||||
export * from './usePageVisibility';
|
||||
export * from './useSkipContextMenu';
|
||||
export * from './useThrottle';
|
||||
export * from './useStableValue';
|
||||
|
||||
@@ -28,14 +28,17 @@ export const useEventBuffer = <T>(handler: UseEventBufferHandler<T>) => {
|
||||
eventTickRef.current = eventTick;
|
||||
|
||||
// @ts-ignore
|
||||
const handleEvent = useCallback(event => {
|
||||
if (!eventTickRef.current) {
|
||||
return;
|
||||
}
|
||||
const handleEvent = useCallback(
|
||||
event => {
|
||||
if (!eventTickRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
eventsBufferRef.current.push(event);
|
||||
eventTickRef.current();
|
||||
}, []);
|
||||
eventsBufferRef.current.push(event);
|
||||
eventTickRef.current();
|
||||
},
|
||||
[eventTickRef.current],
|
||||
);
|
||||
|
||||
return { handleEvent };
|
||||
};
|
||||
|
||||
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: false,
|
||||
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,33 @@ 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 {
|
||||
// here we try to restore settings
|
||||
let oldMapData;
|
||||
if (!currentMapUserSettings.migratedFromOld) {
|
||||
const allData = extractData(LS_KEY_LEGASY);
|
||||
oldMapData = allData?.[map_slug];
|
||||
}
|
||||
|
||||
setIsReady(true);
|
||||
}, [
|
||||
map_slug,
|
||||
mapUserSettings,
|
||||
interfaceSettings,
|
||||
setInterfaceSettings,
|
||||
settingsRoutes,
|
||||
settingsRoutesUpdate,
|
||||
settingsLocal,
|
||||
settingsLocalUpdate,
|
||||
settingsSignatures,
|
||||
settingsSignaturesUpdate,
|
||||
settingsOnTheMap,
|
||||
settingsOnTheMapUpdate,
|
||||
settingsKills,
|
||||
settingsKillsUpdate,
|
||||
isReady,
|
||||
]);
|
||||
// INFO: after migrations migratedFromOld always will be true
|
||||
const migratedResult = applyMigrations(oldMapData ? oldMapData : currentMapUserSettings);
|
||||
|
||||
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 +163,7 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
|
||||
}, []);
|
||||
|
||||
const resetSettings = useCallback(() => {
|
||||
applySettings(createDefaultWidgetSettings());
|
||||
applySettings(createDefaultStoredSettings());
|
||||
}, [applySettings]);
|
||||
|
||||
return {
|
||||
@@ -177,7 +183,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-v3';
|
||||
@@ -247,6 +247,7 @@ export enum OutCommand {
|
||||
deleteSystems = 'delete_systems',
|
||||
manualAddSystem = 'manual_add_system',
|
||||
manualAddConnection = 'manual_add_connection',
|
||||
manualPasteSystemsAndConnections = 'manual_paste_systems_and_connections',
|
||||
manualDeleteConnection = 'manual_delete_connection',
|
||||
setAutopilotWaypoint = 'set_autopilot_waypoint',
|
||||
addSystem = 'add_system',
|
||||
|
||||
@@ -29,7 +29,7 @@ export type GroupType = {
|
||||
|
||||
export type SignatureCustomInfo = {
|
||||
k162Type?: string;
|
||||
isEOL?: boolean;
|
||||
time_status?: number;
|
||||
isCrit?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEventBuffer } from '@/hooks/Mapper/hooks';
|
||||
import usePageVisibility from '@/hooks/Mapper/hooks/usePageVisibility.ts';
|
||||
import debounce from 'lodash.debounce';
|
||||
|
||||
import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { RefObject, useCallback, useEffect, useRef } from 'react';
|
||||
@@ -16,23 +16,6 @@ export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRe
|
||||
const visibleRef = useRef(visible);
|
||||
visibleRef.current = visible;
|
||||
|
||||
// @ts-ignore
|
||||
const handleBufferedEvent = useCallback(({ type, body }) => {
|
||||
if (!visibleRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
handlerRefs.forEach(ref => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.current?.command(type, body);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { handleEvent: handleMapEvent } = useEventBuffer<any>(handleBufferedEvent);
|
||||
|
||||
// TODO - do not delete THIS code it needs for debug
|
||||
// const [record, setRecord] = useLocalStorageState<boolean>('record', {
|
||||
// defaultValue: false,
|
||||
@@ -73,6 +56,52 @@ export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRe
|
||||
[hooksRef.current],
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
const eventsBufferRef = useRef<{ type; body }[]>([]);
|
||||
|
||||
const eventTick = useCallback(
|
||||
debounce(() => {
|
||||
if (eventsBufferRef.current.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { type, body } = eventsBufferRef.current.shift()!;
|
||||
handlerRefs.forEach(ref => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.current?.command(type, body);
|
||||
});
|
||||
|
||||
// TODO - do not delete THIS code it needs for debug
|
||||
// console.log('JOipP', `Tick Buff`, eventsBufferRef.current.length);
|
||||
|
||||
if (eventsBufferRef.current.length > 0) {
|
||||
eventTick();
|
||||
}
|
||||
}, 10),
|
||||
[],
|
||||
);
|
||||
const eventTickRef = useRef(eventTick);
|
||||
eventTickRef.current = eventTick;
|
||||
|
||||
// @ts-ignore
|
||||
const handleMapEvent = useCallback(({ type, body }) => {
|
||||
// TODO - do not delete THIS code it needs for debug
|
||||
// const currentTime = +new Date();
|
||||
// const timeDiff = currentTime - prevEventTime;
|
||||
// prevEventTime = currentTime;
|
||||
// console.log('JOipP', `IN [${inIndex++}] [${timeDiff}] ${getFormattedTime()}`, { type, body });
|
||||
|
||||
if (!eventTickRef.current || !visibleRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
eventsBufferRef.current.push({ type, body });
|
||||
eventTickRef.current();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible && !wasHiddenOnce.current) {
|
||||
wasHiddenOnce.current = true;
|
||||
|
||||
@@ -2,3 +2,5 @@ export * from './contextStore';
|
||||
export * from './getQueryVariable';
|
||||
export * from './loadTextFile';
|
||||
export * from './saveToFile';
|
||||
export * from './omit';
|
||||
export * from './jsonToUriBase64';
|
||||
|
||||
26
assets/js/hooks/Mapper/utils/jsonToUriBase64.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export const encodeJsonToUriBase64 = (value: unknown): string => {
|
||||
const json = JSON.stringify(value);
|
||||
const uriEncoded = encodeURIComponent(json);
|
||||
|
||||
if (typeof window !== 'undefined' && typeof window.btoa === 'function') {
|
||||
return window.btoa(uriEncoded);
|
||||
}
|
||||
// Node.js
|
||||
// @ts-ignore
|
||||
return Buffer.from(uriEncoded, 'utf8').toString('base64');
|
||||
};
|
||||
|
||||
export const decodeUriBase64ToJson = <T = unknown>(base64: string): T => {
|
||||
let uriEncoded: string;
|
||||
|
||||
if (typeof window !== 'undefined' && typeof window.atob === 'function') {
|
||||
uriEncoded = window.atob(base64);
|
||||
} else {
|
||||
// Node.js
|
||||
// @ts-ignore
|
||||
uriEncoded = Buffer.from(base64, 'base64').toString('utf8');
|
||||
}
|
||||
|
||||
const json = decodeURIComponent(uriEncoded);
|
||||
return JSON.parse(json) as T;
|
||||
};
|
||||
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;
|
||||
};
|
||||
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 ->
|
||||
|
||||
@@ -173,12 +173,11 @@ defmodule WandererApp.Character.TrackingUtils do
|
||||
%{
|
||||
id: character_id,
|
||||
eve_id: eve_id
|
||||
},
|
||||
} = _character,
|
||||
map_id,
|
||||
is_track_allowed,
|
||||
caller_pid
|
||||
)
|
||||
when not is_nil(caller_pid) do
|
||||
) do
|
||||
WandererAppWeb.Presence.update(caller_pid, map_id, character_id, %{
|
||||
tracked: is_track_allowed,
|
||||
from: DateTime.utc_now()
|
||||
@@ -217,13 +216,16 @@ defmodule WandererApp.Character.TrackingUtils do
|
||||
end
|
||||
|
||||
defp track_character(
|
||||
_character,
|
||||
character,
|
||||
_map_id,
|
||||
_is_track_allowed,
|
||||
_caller_pid
|
||||
) do
|
||||
Logger.error("caller_pid is required for tracking characters")
|
||||
{:error, "caller_pid is required"}
|
||||
Logger.error(
|
||||
"Invalid character data for tracking - character must have :id and :eve_id fields, got: #{inspect(character)}"
|
||||
)
|
||||
|
||||
{:error, "Invalid character data"}
|
||||
end
|
||||
|
||||
def untrack(characters, map_id, caller_pid) do
|
||||
@@ -243,7 +245,7 @@ defmodule WandererApp.Character.TrackingUtils do
|
||||
:ok
|
||||
else
|
||||
true ->
|
||||
Logger.error("caller_pid is required for untracking characters")
|
||||
Logger.error("caller_pid is required for untracking characters 2")
|
||||
{:error, "caller_pid is required"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -9,15 +9,12 @@ defmodule WandererApp.Map.Manager do
|
||||
|
||||
alias WandererApp.Map.Server
|
||||
alias WandererApp.Map.ServerSupervisor
|
||||
alias WandererApp.Api.MapSystemSignature
|
||||
|
||||
@maps_start_per_second 10
|
||||
@maps_start_interval 1000
|
||||
@maps_queue :maps_queue
|
||||
@garbage_collection_interval :timer.hours(1)
|
||||
@check_maps_queue_interval :timer.seconds(1)
|
||||
@signatures_cleanup_interval :timer.minutes(30)
|
||||
@delete_after_minutes 30
|
||||
|
||||
@pings_cleanup_interval :timer.minutes(10)
|
||||
@pings_expire_minutes 60
|
||||
@@ -66,9 +63,6 @@ defmodule WandererApp.Map.Manager do
|
||||
{:ok, garbage_collector_timer} =
|
||||
:timer.send_interval(@garbage_collection_interval, :garbage_collect)
|
||||
|
||||
{:ok, signatures_cleanup_timer} =
|
||||
:timer.send_interval(@signatures_cleanup_interval, :cleanup_signatures)
|
||||
|
||||
{:ok, pings_cleanup_timer} =
|
||||
:timer.send_interval(@pings_cleanup_interval, :cleanup_pings)
|
||||
|
||||
@@ -80,7 +74,6 @@ defmodule WandererApp.Map.Manager do
|
||||
%{
|
||||
garbage_collector_timer: garbage_collector_timer,
|
||||
check_maps_queue_timer: check_maps_queue_timer,
|
||||
signatures_cleanup_timer: signatures_cleanup_timer,
|
||||
pings_cleanup_timer: pings_cleanup_timer
|
||||
}}
|
||||
end
|
||||
@@ -143,18 +136,6 @@ defmodule WandererApp.Map.Manager do
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:cleanup_signatures, state) do
|
||||
try do
|
||||
cleanup_deleted_signatures()
|
||||
{:noreply, state}
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("Failed to cleanup signatures: #{inspect(e)}")
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:cleanup_pings, state) do
|
||||
try do
|
||||
@@ -167,23 +148,6 @@ defmodule WandererApp.Map.Manager do
|
||||
end
|
||||
end
|
||||
|
||||
defp cleanup_deleted_signatures() do
|
||||
delete_after_date = DateTime.utc_now() |> DateTime.add(-1 * @delete_after_minutes, :minute)
|
||||
|
||||
case MapSystemSignature.by_deleted_and_updated_before!(true, delete_after_date) do
|
||||
{:ok, deleted_signatures} ->
|
||||
Enum.each(deleted_signatures, fn sig ->
|
||||
Ash.destroy!(sig)
|
||||
end)
|
||||
|
||||
:ok
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("Failed to fetch deleted signatures: #{inspect(error)}")
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
defp cleanup_expired_pings() do
|
||||
delete_after_date = DateTime.utc_now() |> DateTime.add(-1 * @pings_expire_minutes, :minute)
|
||||
|
||||
@@ -254,7 +218,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)
|
||||
)
|
||||
|
||||
@@ -107,6 +107,18 @@ defmodule WandererApp.Map.Server do
|
||||
|> map_pid!
|
||||
|> GenServer.cast({&Impl.add_system/4, [system_info, user_id, character_id]})
|
||||
|
||||
def paste_connections(map_id, connections, user_id, character_id) when is_binary(map_id),
|
||||
do:
|
||||
map_id
|
||||
|> map_pid!
|
||||
|> GenServer.cast({&Impl.paste_connections/4, [connections, user_id, character_id]})
|
||||
|
||||
def paste_systems(map_id, systems, user_id, character_id) when is_binary(map_id),
|
||||
do:
|
||||
map_id
|
||||
|> map_pid!
|
||||
|> GenServer.cast({&Impl.paste_systems/4, [systems, user_id, character_id]})
|
||||
|
||||
def add_system_comment(map_id, comment_info, user_id, character_id) when is_binary(map_id),
|
||||
do:
|
||||
map_id
|
||||
|
||||