Merge branch 'develop' into guarzo/undo

This commit is contained in:
guarzo
2025-05-09 18:59:46 -04:00
committed by GitHub
118 changed files with 4532 additions and 1317 deletions

View File

@@ -22,7 +22,7 @@ export const LocalCharactersItemTemplate = ({ showShipName, ...options }: LocalC
)}
style={{ height: `${options.props.itemSize}px` }}
>
<CharacterCard showShipName={showShipName} {...options} />
<CharacterCard showShipName={showShipName} showTicker {...options} />
</div>
);
};

View File

@@ -1,79 +1,34 @@
import React, { createContext, useContext, useEffect } from 'react';
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
import React, { createContext, forwardRef, useContext, useImperativeHandle, useState } from 'react';
import {
RoutesImperativeHandle,
RoutesProviderInnerProps,
RoutesWidgetProps,
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
export type RoutesType = {
path_type: 'shortest' | 'secure' | 'insecure';
include_mass_crit: boolean;
include_eol: boolean;
include_frig: boolean;
include_cruise: boolean;
include_thera: boolean;
avoid_wormholes: boolean;
avoid_pochven: boolean;
avoid_edencom: boolean;
avoid_triglavian: boolean;
avoid: number[];
};
interface MapProviderProps {
type MapProviderProps = {
children: React.ReactNode;
}
} & RoutesWidgetProps;
export const DEFAULT_SETTINGS: RoutesType = {
path_type: 'shortest',
include_mass_crit: true,
include_eol: true,
include_frig: true,
include_cruise: true,
include_thera: true,
avoid_wormholes: false,
avoid_pochven: false,
avoid_edencom: false,
avoid_triglavian: false,
avoid: [],
};
export interface MapContextProps {
update: ContextStoreDataUpdate<RoutesType>;
data: RoutesType;
}
const RoutesContext = createContext<MapContextProps>({
const RoutesContext = createContext<RoutesProviderInnerProps>({
update: () => {},
data: { ...DEFAULT_SETTINGS },
// @ts-ignore
data: {},
});
export const RoutesProvider: React.FC<MapProviderProps> = ({ children }) => {
const { update, ref } = useContextStore<RoutesType>(
{ ...DEFAULT_SETTINGS },
{
onAfterAUpdate: values => {
localStorage.setItem(SESSION_KEY.routes, JSON.stringify(values));
},
export const RoutesProvider = forwardRef<RoutesImperativeHandle, MapProviderProps>(({ children, ...props }, ref) => {
const [loading, setLoading] = useState(false);
useImperativeHandle(ref, () => ({
stopLoading() {
setLoading(false);
},
);
}));
useEffect(() => {
const items = localStorage.getItem(SESSION_KEY.routes);
if (items) {
update(JSON.parse(items));
}
}, [update]);
return (
<RoutesContext.Provider
value={{
update,
data: ref,
}}
>
{children}
</RoutesContext.Provider>
);
};
return <RoutesContext.Provider value={{ ...props, loading, setLoading }}>{children}</RoutesContext.Provider>;
});
RoutesProvider.displayName = 'RoutesProvider';
export const useRouteProvider = () => {
const context = useContext<MapContextProps>(RoutesContext);
const context = useContext<RoutesProviderInnerProps>(RoutesContext);
return context;
};

View File

@@ -2,13 +2,14 @@ import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import {
LayoutEventBlocker,
LoadingWrapper,
SystemViewStandalone,
TooltipPosition,
WdCheckbox,
WdImgButton,
} from '@/hooks/Mapper/components/ui-kit';
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
import { MouseEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { forwardRef, MouseEvent, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { getSystemById } from '@/hooks/Mapper/helpers/getSystemById.ts';
import classes from './RoutesWidget.module.scss';
import { useLoadRoutes } from './hooks';
@@ -25,7 +26,10 @@ import {
AddSystemDialog,
SearchOnSubmitCallback,
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
import { OutCommand } from '@/hooks/Mapper/types';
import {
RoutesImperativeHandle,
RoutesWidgetProps,
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
const sortByDist = (a: Route, b: Route) => {
const distA = a.has_connection ? a.systems?.length || 0 : Infinity;
@@ -36,19 +40,16 @@ const sortByDist = (a: Route, b: Route) => {
export const RoutesWidgetContent = () => {
const {
data: { selectedSystems, hubs = [], systems, routes },
outCommand,
data: { selectedSystems, systems, isSubscriptionActive },
} = useMapRootState();
const { hubs = [], routesList, isRestricted } = useRouteProvider();
const [systemId] = selectedSystems;
const { loading } = useLoadRoutes();
const { systems: systemStatics, loadSystems, lastUpdateKey } = useLoadSystemStatic({ systems: hubs ?? [] });
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers({
outCommand,
hubs,
});
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers();
const preparedHubs = useMemo(() => {
return hubs.map(x => {
@@ -61,20 +62,20 @@ export const RoutesWidgetContent = () => {
const preparedRoutes: Route[] = useMemo(() => {
return (
routes?.routes
routesList?.routes
.sort(sortByDist)
.filter(x => x.destination.toString() !== systemId)
// .filter(x => x.destination.toString() !== systemId)
.map(route => ({
...route,
mapped_systems:
route.systems?.map(solar_system_id =>
routes?.systems_static_data.find(
routesList?.systems_static_data.find(
system_static_data => system_static_data.solar_system_id === solar_system_id,
),
) ?? [],
})) ?? []
);
}, [routes?.routes, routes?.systems_static_data, systemId]);
}, [routesList?.routes, routesList?.systems_static_data, systemId]);
const refData = useRef({ open, loadSystems, preparedRoutes });
refData.current = { open, loadSystems, preparedRoutes };
@@ -97,9 +98,13 @@ export const RoutesWidgetContent = () => {
[handleClick],
);
if (loading) {
if (isRestricted && !isSubscriptionActive) {
return (
<div className="w-full h-full flex justify-center items-center select-none text-center">Loading routes...</div>
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-stone-400/80 text-sm">
User Routes available with &#39;Active&#39; map subscription only (contact map administrators)
</span>
</div>
);
}
@@ -117,7 +122,7 @@ export const RoutesWidgetContent = () => {
return (
<>
{systemId !== undefined && routes && (
<LoadingWrapper loading={loading}>
<div className={clsx(classes.RoutesGrid, 'px-2 py-2')}>
{preparedRoutes.map(route => {
const sys = preparedHubs.find(x => x.solar_system_id === route.destination)!;
@@ -132,7 +137,11 @@ export const RoutesWidgetContent = () => {
<WdImgButton
className={clsx(PrimeIcons.BARS, classes.RemoveBtn)}
onClick={e => handleClick(e, route.destination.toString())}
tooltip={{ content: 'Click here to open system menu', position: TooltipPosition.top, offset: 10 }}
tooltip={{
content: 'Click here to open system menu',
position: TooltipPosition.top,
offset: 10,
}}
/>
<SystemViewStandalone
@@ -151,7 +160,7 @@ export const RoutesWidgetContent = () => {
);
})}
</div>
)}
</LoadingWrapper>
<ContextMenuSystemInfo
hubs={hubs}
@@ -165,15 +174,13 @@ export const RoutesWidgetContent = () => {
);
};
export const RoutesWidgetComp = () => {
const [routeSettingsVisible, setRouteSettingsVisible] = useState(false);
const { data, update } = useRouteProvider();
const {
data: { hubs = [] },
outCommand,
} = useMapRootState();
type RoutesWidgetCompProps = {
title: ReactNode | string;
};
const preparedHubs = useMemo(() => hubs.map(x => parseInt(x)), [hubs]);
export const RoutesWidgetComp = ({ title }: RoutesWidgetCompProps) => {
const [routeSettingsVisible, setRouteSettingsVisible] = useState(false);
const { data, update, addHubCommand } = useRouteProvider();
const isSecure = data.path_type === 'secure';
const handleSecureChange = useCallback(() => {
@@ -190,24 +197,15 @@ export const RoutesWidgetComp = () => {
const onAddSystem = useCallback(() => setOpenAddSystem(true), []);
const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback(
async item => {
if (preparedHubs.includes(item.value)) {
return;
}
await outCommand({
type: OutCommand.addHub,
data: { system_id: item.value },
});
},
[hubs, outCommand],
async item => addHubCommand(item.value.toString()),
[addHubCommand],
);
return (
<Widget
label={
<div className="flex justify-between items-center text-xs w-full" ref={ref}>
<span className="select-none">Routes</span>
<span className="select-none">{title}</span>
<LayoutEventBlocker className="flex items-center gap-2">
<WdImgButton
className={PrimeIcons.PLUS_CIRCLE}
@@ -231,6 +229,7 @@ export const RoutesWidgetComp = () => {
className={PrimeIcons.SLIDERS_H}
onClick={() => setRouteSettingsVisible(true)}
tooltip={{
position: TooltipPosition.top,
content: 'Click here to open Routes settings',
}}
/>
@@ -251,10 +250,13 @@ export const RoutesWidgetComp = () => {
);
};
export const RoutesWidget = () => {
return (
<RoutesProvider>
<RoutesWidgetComp />
</RoutesProvider>
);
};
export const RoutesWidget = forwardRef<RoutesImperativeHandle, RoutesWidgetProps & RoutesWidgetCompProps>(
({ title, ...props }, ref) => {
return (
<RoutesProvider {...props} ref={ref}>
<RoutesWidgetComp title={title} />
</RoutesProvider>
);
},
);
RoutesWidget.displayName = 'RoutesWidget';

View File

@@ -1,10 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { OutCommand } from '@/hooks/Mapper/types';
import { useCallback, useEffect, useRef } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import {
RoutesType,
useRouteProvider,
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
import { useRouteProvider } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
@@ -17,12 +14,10 @@ function usePrevious<T>(value: T): T | undefined {
}
export const useLoadRoutes = () => {
const [loading, setLoading] = useState(false);
const { data: routesSettings } = useRouteProvider();
const { data: routesSettings, loadRoutesCommand, hubs, routesList, loading, setLoading } = useRouteProvider();
const {
outCommand,
data: { selectedSystems, hubs, systems, connections },
data: { selectedSystems, systems, connections },
} = useMapRootState();
const prevSys = usePrevious(systems);
@@ -31,17 +26,16 @@ export const useLoadRoutes = () => {
const loadRoutes = useCallback(
(systemId: string, routesSettings: RoutesType) => {
outCommand({
type: OutCommand.getRoutes,
data: {
system_id: systemId,
routes_settings: routesSettings,
},
});
loadRoutesCommand(systemId, routesSettings);
setLoading(true);
},
[outCommand],
[loadRoutesCommand],
);
useEffect(() => {
setLoading(false);
}, [routesList]);
useEffect(() => {
if (selectedSystems.length !== 1) {
return;

View File

@@ -0,0 +1,27 @@
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
export type LoadRoutesCommand = (systemId: string, routesSettings: RoutesType) => Promise<void>;
export type AddHubCommand = (systemId: string) => Promise<void>;
export type ToggleHubCommand = (systemId: string) => Promise<void>;
export type RoutesWidgetProps = {
data: RoutesType;
update: (d: RoutesType) => void;
hubs: string[];
routesList: RoutesList | undefined;
loadRoutesCommand: LoadRoutesCommand;
addHubCommand: AddHubCommand;
toggleHubCommand: ToggleHubCommand;
isRestricted?: boolean;
};
export type RoutesProviderInnerProps = RoutesWidgetProps & {
loading: boolean;
setLoading(loading: boolean): void;
};
export type RoutesImperativeHandle = {
stopLoading: () => void;
};

View File

@@ -0,0 +1,83 @@
import { Commands, OutCommand } from '@/hooks/Mapper/types';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import {
AddHubCommand,
LoadRoutesCommand,
RoutesImperativeHandle,
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
import { useCallback, useRef } from 'react';
import { RoutesWidget } from '@/hooks/Mapper/components/mapInterface/widgets';
import { useMapEventListener } from '@/hooks/Mapper/events';
export const WRoutesPublic = () => {
const {
outCommand,
storedSettings: { settingsRoutes, settingsRoutesUpdate },
data: { hubs, routes },
} = useMapRootState();
const ref = useRef<RoutesImperativeHandle>(null);
const loadRoutesCommand: LoadRoutesCommand = useCallback(
async (systemId, routesSettings) => {
outCommand({
type: OutCommand.getRoutes,
data: {
system_id: systemId,
routes_settings: routesSettings,
},
});
},
[outCommand],
);
const addHubCommand: AddHubCommand = useCallback(
async systemId => {
if (hubs.includes(systemId)) {
return;
}
await outCommand({
type: OutCommand.addHub,
data: { system_id: systemId },
});
},
[hubs, outCommand],
);
const toggleHubCommand: AddHubCommand = useCallback(
async (systemId: string | undefined) => {
if (!systemId) {
return;
}
outCommand({
type: !hubs.includes(systemId) ? OutCommand.addHub : OutCommand.deleteHub,
data: {
system_id: systemId,
},
});
},
[hubs, outCommand],
);
useMapEventListener(event => {
if (event.name === Commands.routes) {
ref.current?.stopLoading();
}
});
return (
<RoutesWidget
ref={ref}
title="Routes"
data={settingsRoutes}
update={settingsRoutesUpdate}
hubs={hubs}
routesList={routes}
loadRoutesCommand={loadRoutesCommand}
addHubCommand={addHubCommand}
toggleHubCommand={toggleHubCommand}
/>
);
};

View File

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

View File

@@ -0,0 +1,85 @@
import { Commands, OutCommand } from '@/hooks/Mapper/types';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import {
AddHubCommand,
LoadRoutesCommand,
RoutesImperativeHandle,
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
import { useCallback, useRef } from 'react';
import { RoutesWidget } from '@/hooks/Mapper/components/mapInterface/widgets';
import { useMapEventListener } from '@/hooks/Mapper/events';
export const WRoutesUser = () => {
const {
outCommand,
storedSettings: { settingsRoutes, settingsRoutesUpdate },
data: { userHubs, userRoutes },
} = useMapRootState();
const ref = useRef<RoutesImperativeHandle>(null);
const loadRoutesCommand: LoadRoutesCommand = useCallback(
async (systemId, routesSettings) => {
outCommand({
type: OutCommand.getUserRoutes,
data: {
system_id: systemId,
routes_settings: routesSettings,
},
});
},
[outCommand],
);
const addHubCommand: AddHubCommand = useCallback(
async systemId => {
if (userHubs.includes(systemId)) {
return;
}
await outCommand({
type: OutCommand.addUserHub,
data: { system_id: systemId },
});
},
[userHubs, outCommand],
);
const toggleHubCommand: AddHubCommand = useCallback(
async (systemId: string | undefined) => {
if (!systemId) {
return;
}
outCommand({
type: !userHubs.includes(systemId) ? OutCommand.addUserHub : OutCommand.deleteUserHub,
data: {
system_id: systemId,
},
});
},
[userHubs, outCommand],
);
useMapEventListener(event => {
if (event.name === Commands.userRoutes) {
ref.current?.stopLoading();
}
return true;
});
return (
<RoutesWidget
ref={ref}
title="User Routes"
data={settingsRoutes}
update={settingsRoutesUpdate}
hubs={userHubs}
routesList={userRoutes}
loadRoutesCommand={loadRoutesCommand}
addHubCommand={addHubCommand}
toggleHubCommand={toggleHubCommand}
isRestricted
/>
);
};

View File

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

View File

@@ -4,3 +4,6 @@ export * from './RoutesWidget';
export * from './SystemSignatures';
export * from './SystemStructures';
export * from './WSystemKills';
export * from './WRoutesUser';
export * from './WRoutesPublic';
export * from './CommentsWidget';