mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-12 02:35:42 +00:00
Initial commit
This commit is contained in:
9
assets/js/hooks/Mapper/components/map/hooks/api/index.ts
Normal file
9
assets/js/hooks/Mapper/components/map/hooks/api/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export * from './useMapInit';
|
||||
export * from './useMapAddSystems';
|
||||
export * from './useMapUpdateSystems';
|
||||
export * from './useMapRemoveSystems';
|
||||
export * from './useCommandsCharacters';
|
||||
export * from './useCommandsConnections';
|
||||
export * from './useCommandsConnections';
|
||||
export * from './useSelectSystem';
|
||||
export * from './useMapCommands';
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import {
|
||||
CommandCharacterAdded,
|
||||
CommandCharacterRemoved,
|
||||
CommandCharactersUpdated,
|
||||
CommandCharacterUpdated,
|
||||
CommandPresentCharacters,
|
||||
} from '@/hooks/Mapper/types';
|
||||
|
||||
export const useCommandsCharacters = () => {
|
||||
const { update } = useMapState();
|
||||
|
||||
const ref = useRef({ update });
|
||||
ref.current = { update };
|
||||
|
||||
const charactersUpdated = useCallback((characters: CommandCharactersUpdated) => {
|
||||
ref.current.update(() => ({ characters: characters.slice() }));
|
||||
}, []);
|
||||
|
||||
const characterAdded = useCallback((value: CommandCharacterAdded) => {
|
||||
ref.current.update(state => {
|
||||
return { characters: [...state.characters.filter(x => x.eve_id !== value.eve_id), value] };
|
||||
});
|
||||
}, []);
|
||||
|
||||
const characterRemoved = useCallback((value: CommandCharacterRemoved) => {
|
||||
ref.current.update(state => {
|
||||
return { characters: [...state.characters.filter(x => x.eve_id !== value.eve_id)] };
|
||||
});
|
||||
}, []);
|
||||
|
||||
const characterUpdated = useCallback((value: CommandCharacterUpdated) => {
|
||||
ref.current.update(state => {
|
||||
return { characters: [...state.characters.filter(x => x.eve_id !== value.eve_id), value] };
|
||||
});
|
||||
}, []);
|
||||
|
||||
const presentCharacters = useCallback((value: CommandPresentCharacters) => {
|
||||
ref.current.update(() => ({ presentCharacters: value }));
|
||||
}, []);
|
||||
|
||||
return { charactersUpdated, presentCharacters, characterAdded, characterRemoved, characterUpdated };
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { useReactFlow } from 'reactflow';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandAddConnections, CommandRemoveConnections, CommandUpdateConnection } from '@/hooks/Mapper/types';
|
||||
import { convertConnection2Edge } from '@/hooks/Mapper/components/map/helpers';
|
||||
|
||||
export const useCommandsConnections = () => {
|
||||
const rf = useReactFlow();
|
||||
const ref = useRef({ rf });
|
||||
ref.current = { rf };
|
||||
|
||||
const addConnections = useCallback((systems: CommandAddConnections) => {
|
||||
ref.current.rf.addEdges(systems.map(convertConnection2Edge));
|
||||
}, []);
|
||||
|
||||
const removeConnections = useCallback((connections: CommandRemoveConnections) => {
|
||||
ref.current.rf.deleteElements({ edges: connections.map(x => ({ id: x })) });
|
||||
}, []);
|
||||
|
||||
const updateConnection = useCallback((value: CommandUpdateConnection) => {
|
||||
ref.current.rf.deleteElements({ edges: [value] });
|
||||
ref.current.rf.addEdges([convertConnection2Edge(value)]);
|
||||
}, []);
|
||||
|
||||
return { addConnections, removeConnections, updateConnection };
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Node, useReactFlow } from 'reactflow';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandAddSystems } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { convertSystem2Node } from '../../helpers';
|
||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
|
||||
export const useMapAddSystems = () => {
|
||||
const rf = useReactFlow();
|
||||
const {
|
||||
data: { systems },
|
||||
update,
|
||||
} = useMapState();
|
||||
|
||||
const ref = useRef({ rf, systems, update });
|
||||
ref.current = { update, systems, rf };
|
||||
|
||||
return useCallback(
|
||||
(systems: CommandAddSystems) => {
|
||||
const nodes = rf.getNodes();
|
||||
const prepared: Node[] = systems.filter(x => !nodes.some(y => x.id === y.id)).map(convertSystem2Node);
|
||||
rf.addNodes(prepared);
|
||||
|
||||
ref.current.update({
|
||||
systems: [...ref.current.systems.filter(sys => systems.some(x => sys.id !== x.id)), ...systems],
|
||||
});
|
||||
},
|
||||
[rf],
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { MapData, useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandKillsUpdated, CommandMapUpdated } from '@/hooks/Mapper/types';
|
||||
|
||||
export const useMapCommands = () => {
|
||||
const { update } = useMapState();
|
||||
|
||||
const ref = useRef({ update });
|
||||
ref.current = { update };
|
||||
|
||||
const mapUpdated = useCallback(({ hubs }: CommandMapUpdated) => {
|
||||
const out: Partial<MapData> = {};
|
||||
|
||||
if (hubs) {
|
||||
out.hubs = hubs;
|
||||
}
|
||||
|
||||
ref.current.update(out);
|
||||
}, []);
|
||||
|
||||
const killsUpdated = useCallback((updated_kills: CommandKillsUpdated) => {
|
||||
ref.current.update(({ kills }) => {
|
||||
updated_kills.forEach(kill => {
|
||||
kills[kill.solar_system_id] = kill.kills;
|
||||
});
|
||||
|
||||
return { kills: { ...kills } };
|
||||
});
|
||||
}, []);
|
||||
|
||||
return { mapUpdated, killsUpdated };
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
import { useReactFlow } from 'reactflow';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandInit } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { convertConnection2Edge, convertSystem2Node } from '../../helpers';
|
||||
import { MapData, useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
|
||||
export const useMapInit = () => {
|
||||
const rf = useReactFlow();
|
||||
const { data, update } = useMapState();
|
||||
|
||||
const ref = useRef({ rf, data, update });
|
||||
ref.current = { update, data, rf };
|
||||
|
||||
return useCallback(
|
||||
({
|
||||
systems,
|
||||
kills,
|
||||
connections,
|
||||
wormholes,
|
||||
characters,
|
||||
user_characters,
|
||||
present_characters,
|
||||
hubs,
|
||||
}: CommandInit) => {
|
||||
const { update } = ref.current;
|
||||
const { rf } = ref.current;
|
||||
|
||||
const updateData: Partial<MapData> = {};
|
||||
|
||||
if (wormholes) {
|
||||
updateData.wormholesData = wormholes.reduce((acc, x) => ({ ...acc, [x.name]: x }), {});
|
||||
}
|
||||
|
||||
if (characters) {
|
||||
updateData.characters = characters.slice();
|
||||
}
|
||||
|
||||
if (user_characters) {
|
||||
updateData.userCharacters = user_characters;
|
||||
}
|
||||
|
||||
if (present_characters) {
|
||||
updateData.presentCharacters = present_characters;
|
||||
}
|
||||
|
||||
if (hubs) {
|
||||
updateData.hubs = hubs;
|
||||
}
|
||||
|
||||
if (systems) {
|
||||
updateData.systems = systems;
|
||||
}
|
||||
|
||||
if (kills) {
|
||||
updateData.kills = kills.reduce((acc, x) => ({ ...acc, [x.solar_system_id]: x.kills }), {});
|
||||
}
|
||||
|
||||
update(updateData);
|
||||
|
||||
if (systems) {
|
||||
rf.setNodes(systems.map(convertSystem2Node));
|
||||
}
|
||||
|
||||
if (connections) {
|
||||
rf.setEdges(connections.map(convertConnection2Edge));
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useReactFlow } from 'reactflow';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandRemoveSystems } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||
|
||||
export const useMapRemoveSystems = (onSelectionChange: OnMapSelectionChange) => {
|
||||
const rf = useReactFlow();
|
||||
const ref = useRef({ onSelectionChange });
|
||||
ref.current = { onSelectionChange };
|
||||
|
||||
return useCallback(
|
||||
(systems: CommandRemoveSystems) => {
|
||||
rf.deleteElements({ nodes: systems.map(x => ({ id: `${x}` })) });
|
||||
|
||||
const newSelection = rf
|
||||
.getNodes()
|
||||
.filter(x => !systems.includes(parseInt(x.id)))
|
||||
.filter(x => x.selected)
|
||||
.map(x => x.id);
|
||||
|
||||
ref.current.onSelectionChange({
|
||||
systems: newSelection,
|
||||
connections: [],
|
||||
});
|
||||
},
|
||||
[rf],
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Node, useReactFlow } from 'reactflow';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandUpdateSystems } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { convertSystem2Node } from '../../helpers/index.ts';
|
||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
|
||||
export const useMapUpdateSystems = () => {
|
||||
const rf = useReactFlow();
|
||||
|
||||
const {
|
||||
update,
|
||||
data: { systems },
|
||||
} = useMapState();
|
||||
|
||||
const ref = useRef({ systems, update });
|
||||
ref.current = { systems, update };
|
||||
|
||||
return useCallback(
|
||||
(systems: CommandUpdateSystems) => {
|
||||
const nodes = rf.getNodes();
|
||||
const prepared: Node[] = nodes.map(node => {
|
||||
const system = systems.find(s => s.id === node.id);
|
||||
|
||||
if (system) {
|
||||
return {
|
||||
...node,
|
||||
...convertSystem2Node(system),
|
||||
};
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
});
|
||||
|
||||
rf.setNodes(prepared);
|
||||
|
||||
const out = ref.current.systems.map(current => {
|
||||
const newSystem = systems.find(x => current.id === x.id);
|
||||
if (!newSystem) {
|
||||
return current;
|
||||
}
|
||||
|
||||
return newSystem;
|
||||
});
|
||||
|
||||
update({ systems: out });
|
||||
},
|
||||
[rf, update],
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { useReactFlow } from 'reactflow';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandSelectSystem } from '@/hooks/Mapper/types';
|
||||
|
||||
export const useSelectSystem = () => {
|
||||
const rf = useReactFlow();
|
||||
const ref = useRef({ rf });
|
||||
ref.current = { rf };
|
||||
|
||||
return useCallback((systemId: CommandSelectSystem) => {
|
||||
if (!ref.current?.rf) {
|
||||
return;
|
||||
}
|
||||
const systemNode = ref.current.rf.getNodes().find(x => x.data.id === systemId);
|
||||
if (!systemNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.current.rf.setCenter(systemNode.position.x, systemNode.position.y, { duration: 1000 });
|
||||
}, []);
|
||||
};
|
||||
2
assets/js/hooks/Mapper/components/map/hooks/index.ts
Normal file
2
assets/js/hooks/Mapper/components/map/hooks/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './useMapHandlers';
|
||||
export * from './useUpdateNodes';
|
||||
111
assets/js/hooks/Mapper/components/map/hooks/useMapHandlers.ts
Normal file
111
assets/js/hooks/Mapper/components/map/hooks/useMapHandlers.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { ForwardedRef, useImperativeHandle } from 'react';
|
||||
import {
|
||||
CommandAddConnections,
|
||||
CommandAddSystems,
|
||||
CommandCharacterAdded,
|
||||
CommandCharacterRemoved,
|
||||
CommandCharactersUpdated,
|
||||
CommandCharacterUpdated,
|
||||
CommandInit,
|
||||
CommandKillsUpdated,
|
||||
CommandMapUpdated,
|
||||
CommandPresentCharacters,
|
||||
CommandRemoveConnections,
|
||||
CommandRemoveSystems,
|
||||
Commands,
|
||||
CommandSelectSystem,
|
||||
CommandUpdateConnection,
|
||||
CommandUpdateSystems,
|
||||
MapHandlers,
|
||||
} from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import {
|
||||
useCommandsCharacters,
|
||||
useCommandsConnections,
|
||||
useMapAddSystems,
|
||||
useMapCommands,
|
||||
useMapInit,
|
||||
useMapRemoveSystems,
|
||||
useMapUpdateSystems,
|
||||
useSelectSystem,
|
||||
} from './api';
|
||||
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||
|
||||
export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange: OnMapSelectionChange) => {
|
||||
const mapInit = useMapInit();
|
||||
const mapAddSystems = useMapAddSystems();
|
||||
const mapUpdateSystems = useMapUpdateSystems();
|
||||
const removeSystems = useMapRemoveSystems(onSelectionChange);
|
||||
const selectSystem = useSelectSystem();
|
||||
|
||||
const { addConnections, removeConnections, updateConnection } = useCommandsConnections();
|
||||
const { mapUpdated, killsUpdated } = useMapCommands();
|
||||
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:
|
||||
mapAddSystems(data as CommandAddSystems);
|
||||
break;
|
||||
case Commands.updateSystems:
|
||||
mapUpdateSystems(data as CommandUpdateSystems);
|
||||
break;
|
||||
case Commands.removeSystems:
|
||||
removeSystems(data as CommandRemoveSystems);
|
||||
break;
|
||||
case Commands.addConnections:
|
||||
addConnections(data as CommandAddConnections);
|
||||
break;
|
||||
case Commands.removeConnections:
|
||||
removeConnections(data as CommandRemoveConnections);
|
||||
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.selectSystem:
|
||||
selectSystem(data as CommandSelectSystem);
|
||||
break;
|
||||
|
||||
case Commands.routes:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`Map handlers: Unknown command: ${type}`, data);
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { Node, useOnViewportChange, useReactFlow } from 'reactflow';
|
||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
|
||||
const useThrottle = () => {
|
||||
const throttleSeed = useRef<number | null>(null);
|
||||
|
||||
const throttleFunction = useRef((func: any, delay = 200) => {
|
||||
if (!throttleSeed.current) {
|
||||
// Call the callback immediately for the first time
|
||||
func();
|
||||
throttleSeed.current = setTimeout(() => {
|
||||
throttleSeed.current = null;
|
||||
}, delay);
|
||||
}
|
||||
});
|
||||
|
||||
return throttleFunction.current;
|
||||
};
|
||||
|
||||
const X_OFFSET = 50;
|
||||
const Y_OFFSET = 50;
|
||||
|
||||
const isNodeVisible = (node: Node, viewport: { x: number; y: number; width: number; height: number }) => {
|
||||
const { x: nodeX, y: nodeY } = node.position;
|
||||
const { width, height } = node;
|
||||
|
||||
return (
|
||||
nodeX + (width ?? 0) + X_OFFSET > viewport.x &&
|
||||
nodeX - X_OFFSET < viewport.x + viewport.width &&
|
||||
nodeY + (height ?? 0) + Y_OFFSET > viewport.y &&
|
||||
nodeY - Y_OFFSET < viewport.y + viewport.height
|
||||
);
|
||||
};
|
||||
|
||||
export const useUpdateNodes = (nodes: Node<SolarSystemRawType>[]) => {
|
||||
const { screenToFlowPosition } = useReactFlow();
|
||||
const throttle = useThrottle();
|
||||
const { update } = useMapState();
|
||||
|
||||
const ref = useRef({ screenToFlowPosition });
|
||||
ref.current = { screenToFlowPosition };
|
||||
|
||||
const getViewport = useCallback(() => {
|
||||
const clientRect = document.querySelector('.react-flow__renderer')?.getBoundingClientRect();
|
||||
|
||||
if (!clientRect) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { screenToFlowPosition } = ref.current;
|
||||
|
||||
const topLeft = screenToFlowPosition({ x: clientRect.left, y: clientRect.top });
|
||||
const bottomRight = screenToFlowPosition({ x: clientRect.right, y: clientRect.bottom });
|
||||
return {
|
||||
x: topLeft.x,
|
||||
y: topLeft.y,
|
||||
width: bottomRight.x - topLeft.x,
|
||||
height: bottomRight.y - topLeft.y,
|
||||
};
|
||||
}, []);
|
||||
|
||||
const updateNodesVisibility = useCallback(() => {
|
||||
if (!nodes) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewport = getViewport();
|
||||
if (!viewport) {
|
||||
const visibleNodes = new Set(nodes.map(x => x.id));
|
||||
update({ visibleNodes });
|
||||
return;
|
||||
}
|
||||
|
||||
const visibleNodes = new Set(nodes.filter(x => isNodeVisible(x, viewport)).map(x => x.id));
|
||||
update({ visibleNodes });
|
||||
}, [nodes]);
|
||||
|
||||
useOnViewportChange({
|
||||
onChange: () => throttle(updateNodesVisibility.bind(this)),
|
||||
onEnd: () => throttle(updateNodesVisibility.bind(this)),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
updateNodesVisibility();
|
||||
}, [nodes]);
|
||||
};
|
||||
Reference in New Issue
Block a user