mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-12 02:35:42 +00:00
Initial commit
This commit is contained in:
101
assets/js/hooks/Mapper/mapRootProvider/MapRootProvider.tsx
Normal file
101
assets/js/hooks/Mapper/mapRootProvider/MapRootProvider.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
|
||||
import { createContext, Dispatch, ForwardedRef, forwardRef, RefObject, SetStateAction, useContext } from 'react';
|
||||
import { MapHandlers, MapUnionTypes, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||
import { useMapRootHandlers } from '@/hooks/Mapper/mapRootProvider/hooks';
|
||||
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
import { DEFAULT_SETTINGS } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
|
||||
|
||||
export type MapRootData = MapUnionTypes & {
|
||||
selectedSystems: string[];
|
||||
selectedConnections: Pick<SolarSystemConnection, 'source' | 'target'>[];
|
||||
};
|
||||
|
||||
const INITIAL_DATA: MapRootData = {
|
||||
wormholesData: {},
|
||||
effects: {},
|
||||
characters: [],
|
||||
userCharacters: [],
|
||||
presentCharacters: [],
|
||||
systems: [],
|
||||
hubs: [],
|
||||
routes: undefined,
|
||||
kills: [],
|
||||
connections: [],
|
||||
|
||||
selectedSystems: [],
|
||||
selectedConnections: [],
|
||||
};
|
||||
|
||||
type InterfaceStoredSettings = {
|
||||
isShowMenu: boolean;
|
||||
isShowMinimap: boolean;
|
||||
};
|
||||
|
||||
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
|
||||
isShowMenu: false,
|
||||
isShowMinimap: true,
|
||||
};
|
||||
|
||||
export interface MapRootContextProps {
|
||||
update: ContextStoreDataUpdate<MapRootData>;
|
||||
data: MapRootData;
|
||||
mapRef: RefObject<MapHandlers>;
|
||||
outCommand: OutCommandHandler;
|
||||
interfaceSettings: InterfaceStoredSettings;
|
||||
setInterfaceSettings: Dispatch<SetStateAction<InterfaceStoredSettings>>;
|
||||
}
|
||||
|
||||
const MapRootContext = createContext<MapRootContextProps>({
|
||||
update: () => {},
|
||||
data: { ...INITIAL_DATA },
|
||||
mapRef: { current: null },
|
||||
outCommand: async () => void 0,
|
||||
interfaceSettings: STORED_INTERFACE_DEFAULT_VALUES,
|
||||
setInterfaceSettings: () => null,
|
||||
});
|
||||
|
||||
type MapRootProviderProps = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fwdRef: ForwardedRef<any>;
|
||||
mapRef: RefObject<MapHandlers>;
|
||||
outCommand: OutCommandHandler;
|
||||
} & WithChildren;
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
const MapRootHandlers = forwardRef(({ children }: WithChildren, fwdRef: ForwardedRef<any>) => {
|
||||
useMapRootHandlers(fwdRef);
|
||||
return <>{children}</>;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
export const MapRootProvider = ({ children, fwdRef, mapRef, outCommand }: MapRootProviderProps) => {
|
||||
const { update, ref } = useContextStore<MapRootData>({ ...INITIAL_DATA });
|
||||
|
||||
const [interfaceSettings, setInterfaceSettings] = useLocalStorageState<InterfaceStoredSettings>(
|
||||
'window:interface:settings',
|
||||
{
|
||||
defaultValue: STORED_INTERFACE_DEFAULT_VALUES,
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<MapRootContext.Provider
|
||||
value={{
|
||||
update,
|
||||
data: ref,
|
||||
outCommand: outCommand,
|
||||
mapRef: mapRef,
|
||||
setInterfaceSettings,
|
||||
interfaceSettings,
|
||||
}}
|
||||
>
|
||||
<MapRootHandlers ref={fwdRef}>{children}</MapRootHandlers>
|
||||
</MapRootContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMapRootState = () => {
|
||||
const context = useContext<MapRootContextProps>(MapRootContext);
|
||||
return context;
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
export * from './useMapInit';
|
||||
export * from './useMapUpdated';
|
||||
export * from './useRoutes';
|
||||
export * from './useCommandsConnections';
|
||||
export * from './useCommandsSystems';
|
||||
export * from './useCommandsCharacters';
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import {
|
||||
CommandCharacterAdded,
|
||||
CommandCharacterRemoved,
|
||||
CommandCharactersUpdated,
|
||||
CommandCharacterUpdated,
|
||||
CommandPresentCharacters,
|
||||
} from '@/hooks/Mapper/types';
|
||||
|
||||
export const useCommandsCharacters = () => {
|
||||
const { update } = useMapRootState();
|
||||
|
||||
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, characterAdded, characterRemoved, characterUpdated, presentCharacters };
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandAddConnections, CommandRemoveConnections, CommandUpdateConnection } from '@/hooks/Mapper/types';
|
||||
|
||||
export const useCommandsConnections = () => {
|
||||
const {
|
||||
update,
|
||||
data: { connections },
|
||||
} = useMapRootState();
|
||||
|
||||
const ref = useRef({ update, connections });
|
||||
ref.current = { update, connections };
|
||||
|
||||
const addConnections = useCallback((toAdd: CommandAddConnections) => {
|
||||
const { update, connections } = ref.current;
|
||||
update({
|
||||
connections: [...connections, ...toAdd],
|
||||
});
|
||||
}, []);
|
||||
|
||||
const removeConnections = useCallback((toRemove: CommandRemoveConnections) => {
|
||||
const { update, connections } = ref.current;
|
||||
update({
|
||||
connections: connections.filter(x => !toRemove.includes(x.id)),
|
||||
});
|
||||
}, []);
|
||||
|
||||
const updateConnection = useCallback((newConn: CommandUpdateConnection) => {
|
||||
const { update, connections } = ref.current;
|
||||
|
||||
update({
|
||||
connections: [...connections.filter(x => x.id !== newConn.id), newConn],
|
||||
});
|
||||
}, []);
|
||||
|
||||
return { addConnections, removeConnections, updateConnection };
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandAddSystems, CommandRemoveSystems, CommandUpdateSystems } from '@/hooks/Mapper/types';
|
||||
|
||||
export const useCommandsSystems = () => {
|
||||
const {
|
||||
update,
|
||||
data: { systems },
|
||||
} = useMapRootState();
|
||||
|
||||
const ref = useRef({ systems, update });
|
||||
ref.current = { systems, update };
|
||||
|
||||
const addSystems = useCallback(
|
||||
(addSystems: CommandAddSystems) => {
|
||||
update({
|
||||
systems: [...ref.current.systems.filter(sys => addSystems.some(x => sys.id !== x.id)), ...addSystems],
|
||||
});
|
||||
},
|
||||
[update],
|
||||
);
|
||||
|
||||
const removeSystems = useCallback((toRemove: CommandRemoveSystems) => {
|
||||
const { update, systems } = ref.current;
|
||||
update({
|
||||
systems: systems.filter(x => !toRemove.includes(parseInt(x.id))),
|
||||
});
|
||||
}, []);
|
||||
|
||||
const updateSystems = useCallback(
|
||||
(systems: CommandUpdateSystems) => {
|
||||
const out = ref.current.systems.map(current => {
|
||||
const newSystem = systems.find(x => current.id === x.id);
|
||||
if (!newSystem) {
|
||||
return current;
|
||||
}
|
||||
|
||||
return newSystem;
|
||||
});
|
||||
|
||||
update({ systems: out });
|
||||
},
|
||||
[update],
|
||||
);
|
||||
|
||||
return { addSystems, removeSystems, updateSystems };
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useCallback } from 'react';
|
||||
import { CommandInit } from '@/hooks/Mapper/types';
|
||||
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
||||
|
||||
export const useMapInit = () => {
|
||||
const { update } = useMapRootState();
|
||||
|
||||
const { addSystemStatic } = useLoadSystemStatic({ systems: [] });
|
||||
|
||||
return useCallback(
|
||||
({
|
||||
systems,
|
||||
connections,
|
||||
effects,
|
||||
wormholes,
|
||||
system_static_infos,
|
||||
characters,
|
||||
user_characters,
|
||||
present_characters,
|
||||
hubs,
|
||||
}: CommandInit) => {
|
||||
const updateData: Partial<MapRootData> = {};
|
||||
|
||||
if (wormholes) {
|
||||
updateData.wormholesData = wormholes.reduce((acc, x) => ({ ...acc, [x.name]: x }), {});
|
||||
}
|
||||
|
||||
if (effects) {
|
||||
updateData.effects = effects.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 (systems) {
|
||||
updateData.systems = systems;
|
||||
}
|
||||
|
||||
if (connections) {
|
||||
updateData.connections = connections;
|
||||
}
|
||||
|
||||
if (hubs) {
|
||||
updateData.hubs = hubs;
|
||||
}
|
||||
|
||||
if (system_static_infos) {
|
||||
system_static_infos.forEach(static_info => {
|
||||
addSystemStatic(static_info);
|
||||
});
|
||||
}
|
||||
|
||||
update(updateData);
|
||||
},
|
||||
[update, addSystemStatic],
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandMapUpdated } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export const useMapUpdated = () => {
|
||||
const { update } = useMapRootState();
|
||||
|
||||
const ref = useRef({ update });
|
||||
ref.current = { update };
|
||||
|
||||
return useCallback(({ hubs }: CommandMapUpdated) => {
|
||||
const { update } = ref.current;
|
||||
|
||||
const out: Partial<MapRootData> = {};
|
||||
|
||||
if (hubs) {
|
||||
out.hubs = hubs;
|
||||
}
|
||||
|
||||
update(out);
|
||||
}, []);
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandRoutes } from '@/hooks/Mapper/types';
|
||||
import { RoutesList, Route } from '@/hooks/Mapper/types/routes.ts';
|
||||
|
||||
export const sortRoutes = (routes: Route[]): Route[] => {
|
||||
return routes.sort((a, b) => {
|
||||
if (a.origin !== b.origin) {
|
||||
return a.origin - b.origin;
|
||||
}
|
||||
return a.destination - b.destination;
|
||||
});
|
||||
};
|
||||
|
||||
export const areIntegerArraysEqual = (arr1?: number[], arr2?: number[]): boolean => {
|
||||
if (arr1 === undefined || arr2 === undefined) {
|
||||
return arr1 === arr2;
|
||||
}
|
||||
// Sort both arrays
|
||||
const sortedArr1 = [...arr1].sort((a, b) => a - b);
|
||||
const sortedArr2 = [...arr2].sort((a, b) => a - b);
|
||||
|
||||
// Check if sorted arrays have the same length
|
||||
if (sortedArr1.length !== sortedArr2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if all elements in the sorted arrays are equal
|
||||
for (let i = 0; i < sortedArr1.length; i++) {
|
||||
if (sortedArr1[i] !== sortedArr2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const areRoutesEqual = (route1: Route, route2: Route): boolean => {
|
||||
return (
|
||||
route1.origin === route2.origin &&
|
||||
route1.destination === route2.destination &&
|
||||
route1.has_connection === route2.has_connection &&
|
||||
areIntegerArraysEqual(route1.systems, route2.systems) &&
|
||||
route1.success === route2.success
|
||||
);
|
||||
};
|
||||
|
||||
// Function to compare two RoutesList objects
|
||||
export const areRoutesListsEqual = (list1?: RoutesList, list2?: RoutesList): boolean => {
|
||||
if (list1 === undefined || list2 === undefined) {
|
||||
return list1 === list2;
|
||||
}
|
||||
// First, compare the solar_system_id
|
||||
if (list1.solar_system_id !== list2.solar_system_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sort the routes in each list
|
||||
const sortedRoutes1 = sortRoutes(list1.routes);
|
||||
const sortedRoutes2 = sortRoutes(list2.routes);
|
||||
|
||||
// Compare the sorted routes arrays
|
||||
if (sortedRoutes1.length !== sortedRoutes2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < sortedRoutes1.length; i++) {
|
||||
if (!areRoutesEqual(sortedRoutes1[i], sortedRoutes2[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const useRoutes = () => {
|
||||
const {
|
||||
update,
|
||||
data: { routes },
|
||||
} = useMapRootState();
|
||||
|
||||
const ref = useRef({ update, routes });
|
||||
ref.current = { update, routes };
|
||||
|
||||
return useCallback((value: CommandRoutes) => {
|
||||
const { update, routes } = ref.current;
|
||||
|
||||
if (areRoutesListsEqual(routes, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
update({ routes: value });
|
||||
}, []);
|
||||
};
|
||||
1
assets/js/hooks/Mapper/mapRootProvider/hooks/index.ts
Normal file
1
assets/js/hooks/Mapper/mapRootProvider/hooks/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './useMapRootHandlers.ts';
|
||||
@@ -0,0 +1,56 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { OutCommand, OutCommandHandler, SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||
|
||||
type SystemStaticResult = {
|
||||
system_static_infos: SolarSystemStaticInfoRaw[];
|
||||
};
|
||||
|
||||
// TODO maybe later we can store in Static data in provider
|
||||
const cache = new Map<number, SolarSystemStaticInfoRaw>();
|
||||
|
||||
export const loadSystemStaticInfo = async (outCommand: OutCommandHandler, systems: number[]) => {
|
||||
const result = await outCommand({
|
||||
type: OutCommand.getSystemStaticInfos,
|
||||
data: {
|
||||
solar_system_ids: systems,
|
||||
},
|
||||
});
|
||||
|
||||
return (result as SystemStaticResult).system_static_infos;
|
||||
};
|
||||
|
||||
interface UseLoadSystemStaticProps {
|
||||
systems: (number | string)[];
|
||||
}
|
||||
|
||||
export const useLoadSystemStatic = ({ systems }: UseLoadSystemStaticProps) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const ref = useRef({ outCommand });
|
||||
ref.current = { outCommand };
|
||||
|
||||
const addSystemStatic = useCallback((static_info: SolarSystemStaticInfoRaw) => {
|
||||
cache.set(static_info.solar_system_id, static_info);
|
||||
}, []);
|
||||
|
||||
const loadSystems = useCallback(async (systems: (number | string)[]) => {
|
||||
setLoading(true);
|
||||
const allSystems = systems.map(x => (typeof x == 'number' ? x : parseInt(x)));
|
||||
const toLoad = allSystems.filter(x => !cache.has(x));
|
||||
|
||||
if (toLoad.length > 0) {
|
||||
const res = await loadSystemStaticInfo(ref.current.outCommand, toLoad);
|
||||
res.forEach(x => cache.set(x.solar_system_id, x));
|
||||
}
|
||||
setLoading(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadSystems(systems);
|
||||
// eslint-disable-next-line
|
||||
}, [systems]);
|
||||
|
||||
return { addSystemStatic, systems: cache, loading, loadSystems };
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
import { ForwardedRef, useImperativeHandle } from 'react';
|
||||
import {
|
||||
CommandAddConnections,
|
||||
CommandAddSystems,
|
||||
CommandCharacterAdded,
|
||||
CommandCharacterRemoved,
|
||||
CommandCharactersUpdated,
|
||||
CommandCharacterUpdated,
|
||||
CommandInit,
|
||||
CommandMapUpdated,
|
||||
CommandPresentCharacters,
|
||||
CommandRemoveConnections,
|
||||
CommandRemoveSystems,
|
||||
CommandRoutes,
|
||||
Commands,
|
||||
CommandUpdateConnection,
|
||||
CommandUpdateSystems,
|
||||
MapHandlers,
|
||||
} from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
|
||||
import {
|
||||
useCommandsCharacters,
|
||||
useCommandsConnections,
|
||||
useCommandsSystems,
|
||||
useMapInit,
|
||||
useMapUpdated,
|
||||
useRoutes,
|
||||
} from './api';
|
||||
|
||||
export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
const mapInit = useMapInit();
|
||||
const { addSystems, removeSystems, updateSystems } = useCommandsSystems();
|
||||
const { addConnections, removeConnections, updateConnection } = useCommandsConnections();
|
||||
const { charactersUpdated, characterAdded, characterRemoved, characterUpdated, presentCharacters } =
|
||||
useCommandsCharacters();
|
||||
const mapUpdated = useMapUpdated();
|
||||
const mapRoutes = useRoutes();
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => {
|
||||
return {
|
||||
command(type, data) {
|
||||
switch (type) {
|
||||
case Commands.init:
|
||||
mapInit(data as CommandInit);
|
||||
break;
|
||||
case Commands.addSystems:
|
||||
addSystems(data as CommandAddSystems);
|
||||
break;
|
||||
case Commands.updateSystems:
|
||||
updateSystems(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.updateConnection:
|
||||
updateConnection(data as CommandUpdateConnection);
|
||||
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.mapUpdated:
|
||||
mapUpdated(data as CommandMapUpdated);
|
||||
break;
|
||||
case Commands.routes:
|
||||
mapRoutes(data as CommandRoutes);
|
||||
break;
|
||||
|
||||
case Commands.selectSystem:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.killsUpdated:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
};
|
||||
1
assets/js/hooks/Mapper/mapRootProvider/index.ts
Normal file
1
assets/js/hooks/Mapper/mapRootProvider/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './MapRootProvider';
|
||||
Reference in New Issue
Block a user