Initial commit

This commit is contained in:
Dmitry Popov
2024-09-18 01:55:30 +04:00
parent 6a96a5f56e
commit 4136aaad76
1675 changed files with 124664 additions and 1 deletions

View 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;
};

View File

@@ -0,0 +1,6 @@
export * from './useMapInit';
export * from './useMapUpdated';
export * from './useRoutes';
export * from './useCommandsConnections';
export * from './useCommandsSystems';
export * from './useCommandsCharacters';

View File

@@ -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 };
};

View File

@@ -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 };
};

View File

@@ -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 };
};

View File

@@ -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],
);
};

View File

@@ -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);
}, []);
};

View File

@@ -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 });
}, []);
};

View File

@@ -0,0 +1 @@
export * from './useMapRootHandlers.ts';

View File

@@ -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 };
};

View File

@@ -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;
}
},
};
},
[],
);
};

View File

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