fix: Updated character tracking

This commit is contained in:
Dmitry Popov
2025-09-03 15:02:53 +02:00
parent d5c18b5de3
commit c433205e89
10 changed files with 297 additions and 263 deletions

View File

@@ -1,7 +1,7 @@
import { useReactFlow } from 'reactflow';
import { useCallback, useRef } from 'react';
import { CommandSelectSystems } from '@/hooks/Mapper/types';
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts'; import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
import { CommandSelectSystems } from '@/hooks/Mapper/types';
import { useCallback, useRef } from 'react';
import { useReactFlow } from 'reactflow';
export const useSelectSystems = (onSelectionChange: OnMapSelectionChange) => { export const useSelectSystems = (onSelectionChange: OnMapSelectionChange) => {
const rf = useReactFlow(); const rf = useReactFlow();

View File

@@ -1,4 +1,3 @@
import { ForwardedRef, useImperativeHandle, useRef } from 'react';
import { import {
CommandAddConnections, CommandAddConnections,
CommandAddSystems, CommandAddSystems,
@@ -19,8 +18,11 @@ import {
CommandUpdateSystems, CommandUpdateSystems,
MapHandlers, MapHandlers,
} from '@/hooks/Mapper/types/mapHandlers.ts'; } from '@/hooks/Mapper/types/mapHandlers.ts';
import { ForwardedRef, useImperativeHandle, useRef } from 'react';
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
import { import {
useCenterSystem,
useCommandsCharacters, useCommandsCharacters,
useCommandsConnections, useCommandsConnections,
useMapAddSystems, useMapAddSystems,
@@ -28,10 +30,8 @@ import {
useMapInit, useMapInit,
useMapRemoveSystems, useMapRemoveSystems,
useMapUpdateSystems, useMapUpdateSystems,
useCenterSystem,
useSelectSystems, useSelectSystems,
} from './api'; } from './api';
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange: OnMapSelectionChange) => { export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange: OnMapSelectionChange) => {
const mapInit = useMapInit(); const mapInit = useMapInit();
@@ -49,91 +49,87 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
const { charactersUpdated, presentCharacters, characterAdded, characterRemoved, characterUpdated } = const { charactersUpdated, presentCharacters, characterAdded, characterRemoved, characterUpdated } =
useCommandsCharacters(); useCommandsCharacters();
useImperativeHandle( useImperativeHandle(ref, () => {
ref, return {
() => { command(type, data) {
return { switch (type) {
command(type, data) { case Commands.init:
switch (type) { mapInit(data as CommandInit);
case Commands.init: break;
mapInit(data as CommandInit); case Commands.addSystems:
break; setTimeout(() => mapAddSystems(data as CommandAddSystems), 100);
case Commands.addSystems: break;
setTimeout(() => mapAddSystems(data as CommandAddSystems), 100); case Commands.updateSystems:
break; mapUpdateSystems(data as CommandUpdateSystems);
case Commands.updateSystems: break;
mapUpdateSystems(data as CommandUpdateSystems); case Commands.removeSystems:
break; setTimeout(() => removeSystems(data as CommandRemoveSystems), 100);
case Commands.removeSystems: break;
setTimeout(() => removeSystems(data as CommandRemoveSystems), 100); case Commands.addConnections:
break; setTimeout(() => addConnections(data as CommandAddConnections), 100);
case Commands.addConnections: break;
setTimeout(() => addConnections(data as CommandAddConnections), 100); case Commands.removeConnections:
break; setTimeout(() => removeConnections(data as CommandRemoveConnections), 100);
case Commands.removeConnections: break;
setTimeout(() => removeConnections(data as CommandRemoveConnections), 100); case Commands.charactersUpdated:
break; charactersUpdated(data as CommandCharactersUpdated);
case Commands.charactersUpdated: break;
charactersUpdated(data as CommandCharactersUpdated); case Commands.characterAdded:
break; characterAdded(data as CommandCharacterAdded);
case Commands.characterAdded: break;
characterAdded(data as CommandCharacterAdded); case Commands.characterRemoved:
break; characterRemoved(data as CommandCharacterRemoved);
case Commands.characterRemoved: break;
characterRemoved(data as CommandCharacterRemoved); case Commands.characterUpdated:
break; characterUpdated(data as CommandCharacterUpdated);
case Commands.characterUpdated: break;
characterUpdated(data as CommandCharacterUpdated); case Commands.presentCharacters:
break; presentCharacters(data as CommandPresentCharacters);
case Commands.presentCharacters: break;
presentCharacters(data as CommandPresentCharacters); case Commands.updateConnection:
break; updateConnection(data as CommandUpdateConnection);
case Commands.updateConnection: break;
updateConnection(data as CommandUpdateConnection); case Commands.mapUpdated:
break; mapUpdated(data as CommandMapUpdated);
case Commands.mapUpdated: break;
mapUpdated(data as CommandMapUpdated); case Commands.killsUpdated:
break; killsUpdated(data as CommandKillsUpdated);
case Commands.killsUpdated: break;
killsUpdated(data as CommandKillsUpdated);
break;
case Commands.centerSystem: case Commands.centerSystem:
setTimeout(() => { setTimeout(() => {
const systemId = `${data}`; const systemId = `${data}`;
centerSystem(systemId as CommandSelectSystem); centerSystem(systemId as CommandSelectSystem);
}, 100); }, 100);
break; break;
case Commands.selectSystem: case Commands.selectSystem:
selectSystems({ systems: [data as string], delay: 500 }); selectSystems({ systems: [data as string], delay: 500 });
break; break;
case Commands.selectSystems: case Commands.selectSystems:
selectSystems(data as CommandSelectSystems); selectSystems(data as CommandSelectSystems);
break; break;
case Commands.pingAdded: case Commands.pingAdded:
case Commands.pingCancelled: case Commands.pingCancelled:
case Commands.routes: case Commands.routes:
case Commands.signaturesUpdated: case Commands.signaturesUpdated:
case Commands.linkSignatureToSystem: case Commands.linkSignatureToSystem:
case Commands.detailedKillsUpdated: case Commands.detailedKillsUpdated:
case Commands.characterActivityData: case Commands.characterActivityData:
case Commands.trackingCharactersData: case Commands.trackingCharactersData:
case Commands.updateActivity: case Commands.updateActivity:
case Commands.updateTracking: case Commands.updateTracking:
case Commands.userSettingsUpdated: case Commands.userSettingsUpdated:
// do nothing // do nothing
break; break;
default: default:
console.warn(`Map handlers: Unknown command: ${type}`, data); console.warn(`Map handlers: Unknown command: ${type}`, data);
break; break;
} }
}, },
}; };
}, }, []);
[],
);
}; };

View File

@@ -1,6 +1,7 @@
export * from './useClipboard'; export * from './useClipboard';
export * from './useConfirmPopup';
export * from './useEventBuffer';
export * from './useHotkey'; export * from './useHotkey';
export * from './usePageVisibility'; export * from './usePageVisibility';
export * from './useSkipContextMenu'; export * from './useSkipContextMenu';
export * from './useThrottle'; export * from './useThrottle';
export * from './useConfirmPopup';

View File

@@ -0,0 +1,41 @@
import debounce from 'lodash.debounce';
import { useCallback, useRef } from 'react';
export type UseEventBufferHandler<T> = (event: T) => void;
export const useEventBuffer = <T>(handler: UseEventBufferHandler<T>) => {
// @ts-ignore
const eventsBufferRef = useRef<T[]>([]);
const eventTick = useCallback(
debounce(() => {
if (eventsBufferRef.current.length === 0) {
return;
}
const event = eventsBufferRef.current.shift()!;
handler(event);
// 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 handleEvent = useCallback(event => {
if (!eventTickRef.current) {
return;
}
eventsBufferRef.current.push(event);
eventTickRef.current();
}, []);
return { handleEvent };
};

View File

@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import { CommandInit } from '@/hooks/Mapper/types';
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts'; import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
import { CommandInit } from '@/hooks/Mapper/types';
import { useCallback } from 'react';
export const useMapInit = () => { export const useMapInit = () => {
const { update } = useMapRootState(); const { update } = useMapRootState();

View File

@@ -1,4 +1,3 @@
import { ForwardedRef, useImperativeHandle } from 'react';
import { import {
CommandAddConnections, CommandAddConnections,
CommandAddSystems, CommandAddSystems,
@@ -8,24 +7,25 @@ import {
CommandCharactersUpdated, CommandCharactersUpdated,
CommandCharacterUpdated, CommandCharacterUpdated,
CommandCommentAdd, CommandCommentAdd,
CommandCommentRemoved,
CommandInit, CommandInit,
CommandLinkSignatureToSystem, CommandLinkSignatureToSystem,
CommandMapUpdated, CommandMapUpdated,
CommandPingAdded,
CommandPingCancelled,
CommandPresentCharacters, CommandPresentCharacters,
CommandRemoveConnections, CommandRemoveConnections,
CommandRemoveSystems, CommandRemoveSystems,
CommandRoutes, CommandRoutes,
Commands,
CommandSignaturesUpdated, CommandSignaturesUpdated,
CommandTrackingCharactersData, CommandTrackingCharactersData,
CommandUpdateConnection, CommandUpdateConnection,
CommandUpdateSystems, CommandUpdateSystems,
CommandUserSettingsUpdated, CommandUserSettingsUpdated,
Commands,
MapHandlers, MapHandlers,
CommandCommentRemoved,
CommandPingAdded,
CommandPingCancelled,
} from '@/hooks/Mapper/types/mapHandlers.ts'; } from '@/hooks/Mapper/types/mapHandlers.ts';
import { ForwardedRef, useImperativeHandle } from 'react';
import { import {
useCommandComments, useCommandComments,
@@ -39,9 +39,9 @@ import {
useUserRoutes, useUserRoutes,
} from './api'; } from './api';
import { useCommandsActivity } from './api/useCommandsActivity';
import { emitMapEvent } from '@/hooks/Mapper/events'; import { emitMapEvent } from '@/hooks/Mapper/events';
import { DetailedKill } from '../../types/kills'; import { DetailedKill } from '../../types/kills';
import { useCommandsActivity } from './api/useCommandsActivity';
export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => { export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
const mapInit = useMapInit(); const mapInit = useMapInit();
@@ -63,127 +63,123 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
const { pingAdded, pingCancelled } = useCommandPings(); const { pingAdded, pingCancelled } = useCommandPings();
const { characterActivityData, trackingCharactersData, userSettingsUpdated } = useCommandsActivity(); const { characterActivityData, trackingCharactersData, userSettingsUpdated } = useCommandsActivity();
useImperativeHandle( useImperativeHandle(ref, () => {
ref, return {
() => { command(type, data) {
return { switch (type) {
command(type, data) { case Commands.init: // USED
switch (type) { mapInit(data as CommandInit);
case Commands.init: // USED break;
mapInit(data as CommandInit); case Commands.addSystems: // USED
break; addSystems(data as CommandAddSystems);
case Commands.addSystems: // USED break;
addSystems(data as CommandAddSystems); case Commands.updateSystems: // USED
break; updateSystems(data as CommandUpdateSystems);
case Commands.updateSystems: // USED break;
updateSystems(data as CommandUpdateSystems); case Commands.removeSystems: // USED
break; removeSystems(data as CommandRemoveSystems);
case Commands.removeSystems: // USED break;
removeSystems(data as CommandRemoveSystems); case Commands.addConnections: // USED
break; addConnections(data as CommandAddConnections);
case Commands.addConnections: // USED break;
addConnections(data as CommandAddConnections); case Commands.removeConnections: // USED
break; removeConnections(data as CommandRemoveConnections);
case Commands.removeConnections: // USED break;
removeConnections(data as CommandRemoveConnections); case Commands.updateConnection: // USED
break; updateConnection(data as CommandUpdateConnection);
case Commands.updateConnection: // USED break;
updateConnection(data as CommandUpdateConnection); case Commands.charactersUpdated: // USED
break; charactersUpdated(data as CommandCharactersUpdated);
case Commands.charactersUpdated: // USED break;
charactersUpdated(data as CommandCharactersUpdated); case Commands.characterAdded: // USED
break; characterAdded(data as CommandCharacterAdded);
case Commands.characterAdded: // USED break;
characterAdded(data as CommandCharacterAdded); case Commands.characterRemoved: // USED
break; characterRemoved(data as CommandCharacterRemoved);
case Commands.characterRemoved: // USED break;
characterRemoved(data as CommandCharacterRemoved); case Commands.characterUpdated: // USED
break; characterUpdated(data as CommandCharacterUpdated);
case Commands.characterUpdated: // USED break;
characterUpdated(data as CommandCharacterUpdated); case Commands.presentCharacters: // USED
break; presentCharacters(data as CommandPresentCharacters);
case Commands.presentCharacters: // USED break;
presentCharacters(data as CommandPresentCharacters); case Commands.mapUpdated: // USED
break; mapUpdated(data as CommandMapUpdated);
case Commands.mapUpdated: // USED break;
mapUpdated(data as CommandMapUpdated); case Commands.routes:
break; mapRoutes(data as CommandRoutes);
case Commands.routes: break;
mapRoutes(data as CommandRoutes); case Commands.userRoutes:
break; mapUserRoutes(data as CommandRoutes);
case Commands.userRoutes: break;
mapUserRoutes(data as CommandRoutes);
break;
case Commands.signaturesUpdated: // USED case Commands.signaturesUpdated: // USED
updateSystemSignatures(data as CommandSignaturesUpdated); updateSystemSignatures(data as CommandSignaturesUpdated);
break; break;
case Commands.linkSignatureToSystem: // USED case Commands.linkSignatureToSystem: // USED
setTimeout(() => { setTimeout(() => {
updateLinkSignatureToSystem(data as CommandLinkSignatureToSystem); updateLinkSignatureToSystem(data as CommandLinkSignatureToSystem);
}, 200); }, 200);
break; break;
case Commands.centerSystem: // USED case Commands.centerSystem: // USED
// do nothing here // do nothing here
break; break;
case Commands.selectSystem: // USED case Commands.selectSystem: // USED
// do nothing here // do nothing here
break; break;
case Commands.killsUpdated: case Commands.killsUpdated:
// do nothing here // do nothing here
break; break;
case Commands.detailedKillsUpdated: case Commands.detailedKillsUpdated:
updateDetailedKills(data as Record<string, DetailedKill[]>); updateDetailedKills(data as Record<string, DetailedKill[]>);
break; break;
case Commands.characterActivityData: case Commands.characterActivityData:
characterActivityData(data as CommandCharacterActivityData); characterActivityData(data as CommandCharacterActivityData);
break; break;
case Commands.trackingCharactersData: case Commands.trackingCharactersData:
trackingCharactersData(data as CommandTrackingCharactersData); trackingCharactersData(data as CommandTrackingCharactersData);
break; break;
case Commands.updateActivity: case Commands.updateActivity:
break; break;
case Commands.updateTracking: case Commands.updateTracking:
break; break;
case Commands.userSettingsUpdated: case Commands.userSettingsUpdated:
userSettingsUpdated(data as CommandUserSettingsUpdated); userSettingsUpdated(data as CommandUserSettingsUpdated);
break; break;
case Commands.systemCommentAdded: case Commands.systemCommentAdded:
addComment(data as CommandCommentAdd); addComment(data as CommandCommentAdd);
break; break;
case Commands.systemCommentRemoved: case Commands.systemCommentRemoved:
removeComment(data as CommandCommentRemoved); removeComment(data as CommandCommentRemoved);
break; break;
case Commands.pingAdded: case Commands.pingAdded:
pingAdded(data as CommandPingAdded); pingAdded(data as CommandPingAdded);
break; break;
case Commands.pingCancelled: case Commands.pingCancelled:
pingCancelled(data as CommandPingCancelled); pingCancelled(data as CommandPingCancelled);
break; break;
default: default:
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data); console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
break; break;
} }
emitMapEvent({ name: type, data }); emitMapEvent({ name: type, data });
}, },
}; };
}, }, []);
[],
);
}; };

View File

@@ -1,7 +1,8 @@
import { useEventBuffer } from '@/hooks/Mapper/hooks';
import usePageVisibility from '@/hooks/Mapper/hooks/usePageVisibility.ts';
import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts'; import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts';
import { RefObject, useCallback, useEffect, useRef } from 'react'; import { RefObject, useCallback, useEffect, useRef } from 'react';
import debounce from 'lodash.debounce';
import usePageVisibility from '@/hooks/Mapper/hooks/usePageVisibility.ts';
// const inIndex = 0; // const inIndex = 0;
// const prevEventTime = +new Date(); // const prevEventTime = +new Date();
@@ -10,10 +11,28 @@ const LAST_VERSION_KEY = 'wandererLastVersion';
// @ts-ignore // @ts-ignore
export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRef: RefObject<any>) => { export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRef: RefObject<any>) => {
const visible = usePageVisibility(); const visible = usePageVisibility();
const wasHiddenOnce = useRef(false); const wasHiddenOnce = useRef(false);
const visibleRef = useRef(visible); const visibleRef = useRef(visible);
visibleRef.current = 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 // TODO - do not delete THIS code it needs for debug
// const [record, setRecord] = useLocalStorageState<boolean>('record', { // const [record, setRecord] = useLocalStorageState<boolean>('record', {
// defaultValue: false, // defaultValue: false,
@@ -54,52 +73,6 @@ export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRe
[hooksRef.current], [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(() => { useEffect(() => {
if (!visible && !wasHiddenOnce.current) { if (!visible && !wasHiddenOnce.current) {
wasHiddenOnce.current = true; wasHiddenOnce.current = true;

View File

@@ -216,8 +216,7 @@ defmodule WandererApp.Map.Server.CharactersImpl do
{:ok, presence_character_ids} = {:ok, presence_character_ids} =
WandererApp.Cache.lookup("map_#{map_id}:presence_character_ids", []) WandererApp.Cache.lookup("map_#{map_id}:presence_character_ids", [])
WandererApp.Cache.lookup!("maps:#{map_id}:tracked_characters", []) presence_character_ids
|> Enum.filter(fn character_id -> character_id in presence_character_ids end)
|> Enum.map(fn character_id -> |> Enum.map(fn character_id ->
Task.start_link(fn -> Task.start_link(fn ->
character_updates = character_updates =

View File

@@ -703,6 +703,18 @@ defmodule WandererAppWeb.MapCoreEventHandler do
Process.send_after(self(), %{event: :load_map_pings}, 200) Process.send_after(self(), %{event: :load_map_pings}, 200)
Process.send_after(
self(),
%{
event: :maybe_select_system,
payload: %{
character_id: main_character_id,
solar_system_id: nil
}
},
200
)
if needs_tracking_setup do if needs_tracking_setup do
Process.send_after(self(), %{event: :show_tracking}, 10) Process.send_after(self(), %{event: :show_tracking}, 10)

View File

@@ -44,16 +44,24 @@ defmodule WandererAppWeb.MapSystemsEventHandler do
current_user: current_user, current_user: current_user,
tracked_characters: tracked_characters, tracked_characters: tracked_characters,
map_id: map_id, map_id: map_id,
map_user_settings: map_user_settings map_user_settings: map_user_settings,
main_character_eve_id: main_character_eve_id,
following_character_eve_id: following_character_eve_id
} }
} = socket } = socket
) do ) do
character = character =
tracked_characters if is_nil(character_id) do
|> Enum.find(fn tracked_character -> tracked_character.id == character_id end) tracked_characters
|> Enum.find(fn tracked_character ->
tracked_character.eve_id == (following_character_eve_id || main_character_eve_id)
end)
else
tracked_characters
|> Enum.find(fn tracked_character -> tracked_character.id == character_id end)
end
is_user_character = is_user_character = not is_nil(character)
not is_nil(character)
is_select_on_spash = is_select_on_spash =
map_user_settings map_user_settings
@@ -61,10 +69,9 @@ defmodule WandererAppWeb.MapSystemsEventHandler do
|> WandererApp.MapUserSettingsRepo.get_boolean_setting("select_on_spash") |> WandererApp.MapUserSettingsRepo.get_boolean_setting("select_on_spash")
is_following = is_following =
case WandererApp.MapUserSettingsRepo.get(map_id, current_user.id) do case is_user_character && not is_nil(following_character_eve_id) do
{:ok, %{following_character_eve_id: following_character_eve_id}} true ->
when not is_nil(following_character_eve_id) -> following_character_eve_id == character.eve_id
is_user_character && following_character_eve_id == character.eve_id
_ -> _ ->
false false
@@ -77,8 +84,17 @@ defmodule WandererAppWeb.MapSystemsEventHandler do
else else
# Always select the system when auto-select is enabled (following or select_on_spash). # Always select the system when auto-select is enabled (following or select_on_spash).
# The frontend will handle deselecting other systems # The frontend will handle deselecting other systems
#
select_solar_system_id =
if not is_nil(solar_system_id) do
"#{solar_system_id}"
else
{:ok, character} = WandererApp.Character.get_map_character(map_id, character.id)
"#{character.solar_system_id}"
end
socket socket
|> MapEventHandler.push_map_event("select_system", solar_system_id) |> MapEventHandler.push_map_event("select_system", select_solar_system_id)
end end
end end