Merge branch 'main' into pings

This commit is contained in:
Dmitry Popov
2025-05-22 09:40:24 +02:00
40 changed files with 1503 additions and 715 deletions

View File

@@ -10,7 +10,7 @@ export interface SignatureViewProps {
export const SignatureView = ({ signature, showCharacterPortrait = false }: SignatureViewProps) => {
const isWormhole = signature?.group === SignatureGroup.Wormhole;
const hasCharacterInfo = showCharacterPortrait && signature.character_eve_id;
const groupDisplay = isWormhole ? SignatureGroup.Wormhole : (signature?.group ?? SignatureGroup.CosmicSignature);
const groupDisplay = isWormhole ? SignatureGroup.Wormhole : signature?.group ?? SignatureGroup.CosmicSignature;
const characterName = signature.character_name || 'Unknown character';
return (

View File

@@ -19,7 +19,7 @@ export type HeaderProps = {
lazyDeleteValue: boolean;
onLazyDeleteChange: (checked: boolean) => void;
pendingCount: number;
pendingTimeRemaining?: number; // Time remaining in ms
undoCountdown?: number;
onUndoClick: () => void;
onSettingsClick: () => void;
};
@@ -29,7 +29,7 @@ export const SystemSignaturesHeader = ({
lazyDeleteValue,
onLazyDeleteChange,
pendingCount,
pendingTimeRemaining,
undoCountdown,
onUndoClick,
onSettingsClick,
}: HeaderProps) => {
@@ -43,13 +43,6 @@ export const SystemSignaturesHeader = ({
const containerRef = useRef<HTMLDivElement>(null);
const isCompact = useMaxWidth(containerRef, COMPACT_MAX_WIDTH);
// Format time remaining as seconds
const formatTimeRemaining = () => {
if (!pendingTimeRemaining) return '';
const seconds = Math.ceil(pendingTimeRemaining / 1000);
return ` (${seconds}s remaining)`;
};
return (
<div ref={containerRef} className="w-full">
<div className="flex justify-between items-center text-xs w-full h-full">
@@ -78,7 +71,9 @@ export const SystemSignaturesHeader = ({
<WdImgButton
className={PrimeIcons.UNDO}
style={{ color: 'red' }}
tooltip={{ content: `Undo pending changes (${pendingCount})${formatTimeRemaining()}` }}
tooltip={{
content: `Undo pending deletions (${pendingCount})${undoCountdown && undoCountdown > 0 ? `${undoCountdown}s left` : ''}`,
}}
onClick={onUndoClick}
/>
)}

View File

@@ -1,99 +1,156 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useState, useEffect, useRef, useMemo } from 'react';
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
import { SystemSignaturesContent } from './SystemSignaturesContent';
import { SystemSignatureSettingsDialog } from './SystemSignatureSettingsDialog';
import { ExtendedSystemSignature, SystemSignature } from '@/hooks/Mapper/types';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useHotkey } from '@/hooks/Mapper/hooks';
import { SystemSignaturesHeader } from './SystemSignatureHeader';
import useLocalStorageState from 'use-local-storage-state';
import { useHotkey } from '@/hooks/Mapper/hooks/useHotkey';
import {
SETTINGS_KEYS,
SETTINGS_VALUES,
SIGNATURE_DELETION_TIMEOUTS,
SIGNATURE_SETTING_STORE_KEY,
SIGNATURE_WINDOW_ID,
SIGNATURES_DELETION_TIMING,
SignatureSettingsType,
SIGNATURES_DELETION_TIMING,
SIGNATURE_DELETION_TIMEOUTS,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
import { calculateTimeRemaining } from './helpers';
import { OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers';
export const SystemSignatures = () => {
const [visible, setVisible] = useState(false);
const [sigCount, setSigCount] = useState<number>(0);
const [pendingSigs, setPendingSigs] = useState<SystemSignature[]>([]);
const [pendingTimeRemaining, setPendingTimeRemaining] = useState<number | undefined>();
const undoPendingFnRef = useRef<() => void>(() => {});
/**
* Custom hook for managing pending signature deletions and undo countdown.
*/
function useSignatureUndo(
systemId: string | undefined,
settings: SignatureSettingsType,
outCommand: OutCommandHandler,
) {
const [countdown, setCountdown] = useState<number>(0);
const [pendingIds, setPendingIds] = useState<Set<string>>(new Set());
const intervalRef = useRef<number | null>(null);
const {
data: { selectedSystems },
} = useMapRootState();
const [currentSettings, setCurrentSettings] = useLocalStorageState(SIGNATURE_SETTING_STORE_KEY, {
defaultValue: SETTINGS_VALUES,
});
const handleSigCountChange = useCallback((count: number) => {
setSigCount(count);
const addDeleted = useCallback((ids: string[]) => {
setPendingIds(prev => {
const next = new Set(prev);
ids.forEach(id => next.add(id));
return next;
});
}, []);
const [systemId] = selectedSystems;
const isNotSelectedSystem = selectedSystems.length !== 1;
const handleSettingsChange = useCallback((newSettings: SignatureSettingsType) => {
setCurrentSettings(newSettings);
setVisible(false);
}, []);
const handleLazyDeleteChange = useCallback((value: boolean) => {
setCurrentSettings(prev => ({ ...prev, [SETTINGS_KEYS.LAZY_DELETE_SIGNATURES]: value }));
}, []);
useHotkey(true, ['z'], event => {
if (pendingSigs.length > 0) {
event.preventDefault();
event.stopPropagation();
undoPendingFnRef.current();
setPendingSigs([]);
setPendingTimeRemaining(undefined);
}
});
const handleUndoClick = useCallback(() => {
undoPendingFnRef.current();
setPendingSigs([]);
setPendingTimeRemaining(undefined);
}, []);
const handleSettingsButtonClick = useCallback(() => {
setVisible(true);
}, []);
const handlePendingChange = useCallback(
(pending: React.MutableRefObject<Record<string, ExtendedSystemSignature>>, newUndo: () => void) => {
setPendingSigs(() => {
return Object.values(pending.current).filter(sig => sig.pendingDeletion);
});
undoPendingFnRef.current = newUndo;
},
[],
);
// Calculate the minimum time remaining for any pending signature
// kick off or clear countdown whenever pendingIds changes
useEffect(() => {
if (pendingSigs.length === 0) {
setPendingTimeRemaining(undefined);
// clear any existing timer
if (intervalRef.current != null) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
if (pendingIds.size === 0) {
setCountdown(0);
return;
}
const calculate = () => {
setPendingTimeRemaining(() => calculateTimeRemaining(pendingSigs));
};
// determine timeout from settings
const timingKey = Number(settings[SETTINGS_KEYS.DELETION_TIMING] ?? SIGNATURES_DELETION_TIMING.DEFAULT);
const timeoutMs =
Number(SIGNATURE_DELETION_TIMEOUTS[timingKey as keyof typeof SIGNATURE_DELETION_TIMEOUTS]) || 10000;
setCountdown(Math.ceil(timeoutMs / 1000));
calculate();
const interval = setInterval(calculate, 1000);
return () => clearInterval(interval);
}, [pendingSigs]);
// start new interval
intervalRef.current = window.setInterval(() => {
setCountdown(prev => {
if (prev <= 1) {
clearInterval(intervalRef.current!);
intervalRef.current = null;
setPendingIds(new Set());
return 0;
}
return prev - 1;
});
}, 1000);
return () => {
if (intervalRef.current != null) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
}, [pendingIds, settings[SETTINGS_KEYS.DELETION_TIMING]]);
// undo handler
const handleUndo = useCallback(async () => {
if (!systemId || pendingIds.size === 0) return;
await outCommand({
type: OutCommand.undoDeleteSignatures,
data: { system_id: systemId, eve_ids: Array.from(pendingIds) },
});
setPendingIds(new Set());
setCountdown(0);
if (intervalRef.current != null) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
}, [systemId, pendingIds, outCommand]);
return {
pendingIds,
countdown,
addDeleted,
handleUndo,
};
}
export const SystemSignatures = () => {
const [visible, setVisible] = useState(false);
const [sigCount, setSigCount] = useState(0);
const {
data: { selectedSystems },
outCommand,
} = useMapRootState();
const [currentSettings, setCurrentSettings] = useLocalStorageState<SignatureSettingsType>(
SIGNATURE_SETTING_STORE_KEY,
{
defaultValue: SETTINGS_VALUES,
},
);
const [systemId] = selectedSystems;
const isSystemSelected = useMemo(() => selectedSystems.length === 1, [selectedSystems.length]);
const { pendingIds, countdown, addDeleted, handleUndo } = useSignatureUndo(systemId, currentSettings, outCommand);
useHotkey(true, ['z', 'Z'], (event: KeyboardEvent) => {
if (pendingIds.size > 0 && countdown > 0) {
event.preventDefault();
event.stopPropagation();
handleUndo();
}
});
const handleCountChange = useCallback((count: number) => {
setSigCount(count);
}, []);
const handleSettingsSave = useCallback(
(newSettings: SignatureSettingsType) => {
setCurrentSettings(newSettings);
setVisible(false);
},
[setCurrentSettings],
);
const handleLazyDeleteToggle = useCallback(
(value: boolean) => {
setCurrentSettings(prev => ({
...prev,
[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES]: value,
}));
},
[setCurrentSettings],
);
const openSettings = useCallback(() => setVisible(true), []);
return (
<Widget
@@ -101,16 +158,16 @@ export const SystemSignatures = () => {
<SystemSignaturesHeader
sigCount={sigCount}
lazyDeleteValue={currentSettings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean}
pendingCount={pendingSigs.length}
pendingTimeRemaining={pendingTimeRemaining}
onLazyDeleteChange={handleLazyDeleteChange}
onUndoClick={handleUndoClick}
onSettingsClick={handleSettingsButtonClick}
pendingCount={pendingIds.size}
undoCountdown={countdown}
onLazyDeleteChange={handleLazyDeleteToggle}
onUndoClick={handleUndo}
onSettingsClick={openSettings}
/>
}
windowId={SIGNATURE_WINDOW_ID}
>
{isNotSelectedSystem ? (
{!isSystemSelected ? (
<div className="w-full h-full flex justify-center items-center select-none text-center text-stone-400/80 text-sm">
System is not selected
</div>
@@ -118,22 +175,17 @@ export const SystemSignatures = () => {
<SystemSignaturesContent
systemId={systemId}
settings={currentSettings}
deletionTiming={
SIGNATURE_DELETION_TIMEOUTS[
(currentSettings[SETTINGS_KEYS.DELETION_TIMING] as keyof typeof SIGNATURE_DELETION_TIMEOUTS) ||
SIGNATURES_DELETION_TIMING.DEFAULT
] as number
}
onLazyDeleteChange={handleLazyDeleteChange}
onCountChange={handleSigCountChange}
onPendingChange={handlePendingChange}
onLazyDeleteChange={handleLazyDeleteToggle}
onCountChange={handleCountChange}
onSignatureDeleted={addDeleted}
/>
)}
{visible && (
<SystemSignatureSettingsDialog
settings={currentSettings}
onCancel={() => setVisible(false)}
onSave={handleSettingsChange}
onSave={handleSettingsSave}
/>
)}
</Widget>

View File

@@ -57,12 +57,8 @@ interface SystemSignaturesContentProps {
onSelect?: (signature: SystemSignature) => void;
onLazyDeleteChange?: (value: boolean) => void;
onCountChange?: (count: number) => void;
onPendingChange?: (
pending: React.MutableRefObject<Record<string, ExtendedSystemSignature>>,
undo: () => void,
) => void;
deletionTiming?: number;
filterSignature?: (signature: SystemSignature) => boolean;
onSignatureDeleted?: (deletedIds: string[]) => void;
}
export const SystemSignaturesContent = ({
@@ -73,9 +69,8 @@ export const SystemSignaturesContent = ({
onSelect,
onLazyDeleteChange,
onCountChange,
onPendingChange,
deletionTiming,
filterSignature,
onSignatureDeleted,
}: SystemSignaturesContentProps) => {
const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState<SystemSignature | null>(null);
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
@@ -95,15 +90,21 @@ export const SystemSignaturesContent = ({
{ defaultValue: SORT_DEFAULT_VALUES },
);
const { signatures, selectedSignatures, setSelectedSignatures, handleDeleteSelected, handleSelectAll, handlePaste } =
useSystemSignaturesData({
systemId,
settings,
onCountChange,
onPendingChange,
onLazyDeleteChange,
deletionTiming,
});
const {
signatures,
selectedSignatures,
setSelectedSignatures,
handleDeleteSelected,
handleSelectAll,
handlePaste,
hasUnsupportedLanguage,
} = useSystemSignaturesData({
systemId,
settings,
onCountChange,
onLazyDeleteChange,
onSignatureDeleted,
});
useEffect(() => {
if (selectable) return;
@@ -125,6 +126,10 @@ export const SystemSignaturesContent = ({
event.preventDefault();
event.stopPropagation();
if (onSignatureDeleted && selectedSignatures.length > 0) {
const deletedIds = selectedSignatures.map(s => s.eve_id);
onSignatureDeleted(deletedIds);
}
handleDeleteSelected();
});
@@ -155,7 +160,7 @@ export const SystemSignaturesContent = ({
(e: { value: SystemSignature[] }) => {
selectable ? onSelect?.(e.value[0]) : setSelectedSignatures(e.value as ExtendedSystemSignature[]);
},
[selectable],
[onSelect, selectable, setSelectedSignatures],
);
const { showDescriptionColumn, showUpdatedColumn, showCharacterColumn, showCharacterPortrait } = useMemo(
@@ -188,7 +193,11 @@ export const SystemSignaturesContent = ({
x => GROUPS_LIST.includes(x as SignatureGroup) && settings[x as SETTINGS_KEYS],
);
return enabledGroups.includes(getGroupIdByRawGroup(sig.group));
const mappedGroup = getGroupIdByRawGroup(sig.group);
if (!mappedGroup) {
return true; // If we can't determine the group, still show it
}
return enabledGroups.includes(mappedGroup);
}
return true;
@@ -236,113 +245,121 @@ export const SystemSignaturesContent = ({
No signatures
</div>
) : (
<DataTable
value={filteredSignatures}
size="small"
selectionMode="multiple"
selection={selectedSignatures}
metaKeySelection
onSelectionChange={handleSelectSignatures}
dataKey="eve_id"
className="w-full select-none"
resizableColumns={false}
rowHover
selectAll
onRowDoubleClick={handleRowClick}
sortField={sortSettings.sortField}
sortOrder={sortSettings.sortOrder}
onSort={handleSortSettings}
onRowMouseEnter={onRowMouseEnter}
onRowMouseLeave={onRowMouseLeave}
// @ts-ignore
rowClassName={getRowClassName}
>
<Column
field="icon"
header=""
body={renderColIcon}
bodyClassName="p-0 px-1"
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
/>
<Column
field="eve_id"
header="Id"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
style={{ maxWidth: 72, minWidth: 72, width: 72 }}
sortable
/>
<Column
field="group"
header="Group"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
body={sig => sig.group ?? ''}
hidden={isCompact}
sortable
/>
<Column
field="info"
header="Info"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
style={{ maxWidth: nameColumnWidth }}
hidden={isCompact || isMedium}
body={renderInfoColumn}
/>
{showDescriptionColumn && (
<Column
field="description"
header="Description"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
hidden={isCompact}
body={renderDescription}
sortable
/>
<>
{hasUnsupportedLanguage && (
<div className="w-full flex justify-center items-center text-amber-500 text-xs p-1 bg-amber-950/20 border-b border-amber-800/30">
<i className={PrimeIcons.EXCLAMATION_TRIANGLE + ' mr-1'} />
Non-English signatures detected. Some signatures may not display correctly. Double-click to edit signature details.
</div>
)}
<Column
field="inserted_at"
header="Added"
dataType="date"
body={renderAddedTimeLeft}
style={{ minWidth: 70, maxWidth: 80 }}
bodyClassName="ssc-header text-ellipsis overflow-hidden whitespace-nowrap"
sortable
/>
{showUpdatedColumn && (
<Column
field="updated_at"
header="Updated"
dataType="date"
body={renderUpdatedTimeLeft}
style={{ minWidth: 70, maxWidth: 80 }}
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
sortable
/>
)}
{showCharacterColumn && (
<Column
field="character_name"
header="Character"
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
sortable
></Column>
)}
{!selectable && (
<DataTable
value={filteredSignatures}
size="small"
selectionMode="multiple"
selection={selectedSignatures}
metaKeySelection
onSelectionChange={handleSelectSignatures}
dataKey="eve_id"
className="w-full select-none"
resizableColumns={false}
rowHover
selectAll
onRowDoubleClick={handleRowClick}
sortField={sortSettings.sortField}
sortOrder={sortSettings.sortOrder}
onSort={handleSortSettings}
onRowMouseEnter={onRowMouseEnter}
onRowMouseLeave={onRowMouseLeave}
// @ts-ignore
rowClassName={getRowClassName}
>
<Column
field="icon"
header=""
body={() => (
<div className="flex justify-end items-center gap-2 mr-[4px]">
<WdTooltipWrapper content="Double-click a row to edit signature">
<span className={PrimeIcons.PENCIL + ' text-[10px]'} />
</WdTooltipWrapper>
</div>
)}
body={renderColIcon}
bodyClassName="p-0 px-1"
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
bodyClassName="p-0 pl-1 pr-2"
/>
)}
</DataTable>
<Column
field="eve_id"
header="Id"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
style={{ maxWidth: 72, minWidth: 72, width: 72 }}
sortable
/>
<Column
field="group"
header="Group"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
body={sig => sig.group ?? ''}
hidden={isCompact}
sortable
/>
<Column
field="info"
header="Info"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
style={{ maxWidth: nameColumnWidth }}
hidden={isCompact || isMedium}
body={renderInfoColumn}
/>
{showDescriptionColumn && (
<Column
field="description"
header="Description"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
hidden={isCompact}
body={renderDescription}
sortable
/>
)}
<Column
field="inserted_at"
header="Added"
dataType="date"
body={renderAddedTimeLeft}
style={{ minWidth: 70, maxWidth: 80 }}
bodyClassName="ssc-header text-ellipsis overflow-hidden whitespace-nowrap"
sortable
/>
{showUpdatedColumn && (
<Column
field="updated_at"
header="Updated"
dataType="date"
body={renderUpdatedTimeLeft}
style={{ minWidth: 70, maxWidth: 80 }}
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
sortable
/>
)}
{showCharacterColumn && (
<Column
field="character_name"
header="Character"
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
sortable
></Column>
)}
{!selectable && (
<Column
header=""
body={() => (
<div className="flex justify-end items-center gap-2 mr-[4px]">
<WdTooltipWrapper content="Double-click a row to edit signature">
<span className={PrimeIcons.PENCIL + ' text-[10px]'} />
</WdTooltipWrapper>
</div>
)}
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
bodyClassName="p-0 pl-1 pr-2"
/>
)}
</DataTable>
</>
)}
<WdTooltip

View File

@@ -1,10 +1,14 @@
import {
GroupType,
SignatureGroup,
SignatureGroupDE,
SignatureGroupENG,
SignatureGroupFR,
SignatureGroupRU,
SignatureKind,
SignatureKindDE,
SignatureKindENG,
SignatureKindFR,
SignatureKindRU,
} from '@/hooks/Mapper/types';
@@ -40,46 +44,58 @@ export const GROUPS: Record<SignatureGroup, GroupType> = {
[SignatureGroup.CosmicSignature]: { id: SignatureGroup.CosmicSignature, icon: '/icons/x_close14.png', w: 9, h: 9 },
};
export const MAPPING_GROUP_TO_ENG = {
// ENGLISH
[SignatureGroupENG.GasSite]: SignatureGroup.GasSite,
[SignatureGroupENG.RelicSite]: SignatureGroup.RelicSite,
[SignatureGroupENG.DataSite]: SignatureGroup.DataSite,
[SignatureGroupENG.OreSite]: SignatureGroup.OreSite,
[SignatureGroupENG.CombatSite]: SignatureGroup.CombatSite,
[SignatureGroupENG.Wormhole]: SignatureGroup.Wormhole,
[SignatureGroupENG.CosmicSignature]: SignatureGroup.CosmicSignature,
// RUSSIAN
[SignatureGroupRU.GasSite]: SignatureGroup.GasSite,
[SignatureGroupRU.RelicSite]: SignatureGroup.RelicSite,
[SignatureGroupRU.DataSite]: SignatureGroup.DataSite,
[SignatureGroupRU.OreSite]: SignatureGroup.OreSite,
[SignatureGroupRU.CombatSite]: SignatureGroup.CombatSite,
[SignatureGroupRU.Wormhole]: SignatureGroup.Wormhole,
[SignatureGroupRU.CosmicSignature]: SignatureGroup.CosmicSignature,
export const LANGUAGE_GROUP_MAPPINGS = {
EN: {
[SignatureGroupENG.GasSite]: SignatureGroup.GasSite,
[SignatureGroupENG.RelicSite]: SignatureGroup.RelicSite,
[SignatureGroupENG.DataSite]: SignatureGroup.DataSite,
[SignatureGroupENG.OreSite]: SignatureGroup.OreSite,
[SignatureGroupENG.CombatSite]: SignatureGroup.CombatSite,
[SignatureGroupENG.Wormhole]: SignatureGroup.Wormhole,
[SignatureGroupENG.CosmicSignature]: SignatureGroup.CosmicSignature,
},
RU: {
[SignatureGroupRU.GasSite]: SignatureGroup.GasSite,
[SignatureGroupRU.RelicSite]: SignatureGroup.RelicSite,
[SignatureGroupRU.DataSite]: SignatureGroup.DataSite,
[SignatureGroupRU.OreSite]: SignatureGroup.OreSite,
[SignatureGroupRU.CombatSite]: SignatureGroup.CombatSite,
[SignatureGroupRU.Wormhole]: SignatureGroup.Wormhole,
[SignatureGroupRU.CosmicSignature]: SignatureGroup.CosmicSignature,
},
FR: {
[SignatureGroupFR.GasSite]: SignatureGroup.GasSite,
[SignatureGroupFR.RelicSite]: SignatureGroup.RelicSite,
[SignatureGroupFR.DataSite]: SignatureGroup.DataSite,
[SignatureGroupFR.OreSite]: SignatureGroup.OreSite,
[SignatureGroupFR.CombatSite]: SignatureGroup.CombatSite,
[SignatureGroupFR.Wormhole]: SignatureGroup.Wormhole,
[SignatureGroupFR.CosmicSignature]: SignatureGroup.CosmicSignature,
},
DE: {
[SignatureGroupDE.GasSite]: SignatureGroup.GasSite,
[SignatureGroupDE.RelicSite]: SignatureGroup.RelicSite,
[SignatureGroupDE.DataSite]: SignatureGroup.DataSite,
[SignatureGroupDE.OreSite]: SignatureGroup.OreSite,
[SignatureGroupDE.CombatSite]: SignatureGroup.CombatSite,
[SignatureGroupDE.Wormhole]: SignatureGroup.Wormhole,
[SignatureGroupDE.CosmicSignature]: SignatureGroup.CosmicSignature,
},
};
export const MAPPING_TYPE_TO_ENG = {
// ENGLISH
[SignatureKindENG.CosmicSignature]: SignatureKind.CosmicSignature,
[SignatureKindENG.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
[SignatureKindENG.Structure]: SignatureKind.Structure,
[SignatureKindENG.Ship]: SignatureKind.Ship,
[SignatureKindENG.Deployable]: SignatureKind.Deployable,
[SignatureKindENG.Drone]: SignatureKind.Drone,
// Flatten the structure for backward compatibility
export const MAPPING_GROUP_TO_ENG: Record<string, SignatureGroup> = (() => {
const flattened: Record<string, SignatureGroup> = {};
for (const [, mappings] of Object.entries(LANGUAGE_GROUP_MAPPINGS)) {
Object.assign(flattened, mappings);
}
return flattened;
})();
// RUSSIAN
[SignatureKindRU.CosmicSignature]: SignatureKind.CosmicSignature,
[SignatureKindRU.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
[SignatureKindRU.Structure]: SignatureKind.Structure,
[SignatureKindRU.Ship]: SignatureKind.Ship,
[SignatureKindRU.Deployable]: SignatureKind.Deployable,
[SignatureKindRU.Drone]: SignatureKind.Drone,
export const getGroupIdByRawGroup = (val: string): SignatureGroup | undefined => {
return MAPPING_GROUP_TO_ENG[val] || undefined;
};
export const getGroupIdByRawGroup = (val: string) => MAPPING_GROUP_TO_ENG[val as SignatureGroup];
export const SIGNATURE_WINDOW_ID = 'system_signatures_window';
export const SIGNATURE_SETTING_STORE_KEY = 'wanderer_system_signature_settings_v6_5';
@@ -123,7 +139,7 @@ export type Setting = {
name: string;
type: SettingsTypes;
isSeparator?: boolean;
options?: { label: string; value: any }[];
options?: { label: string; value: number | string | boolean }[];
};
export enum SIGNATURES_DELETION_TIMING {
@@ -208,3 +224,52 @@ export const SIGNATURE_DELETION_TIMEOUTS: SignatureDeletionTimingType = {
[SIGNATURES_DELETION_TIMING.IMMEDIATE]: 0,
[SIGNATURES_DELETION_TIMING.EXTENDED]: 30_000,
};
// Replace the flat structure with a nested structure by language
export const LANGUAGE_TYPE_MAPPINGS = {
EN: {
[SignatureKindENG.CosmicSignature]: SignatureKind.CosmicSignature,
[SignatureKindENG.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
[SignatureKindENG.Structure]: SignatureKind.Structure,
[SignatureKindENG.Ship]: SignatureKind.Ship,
[SignatureKindENG.Deployable]: SignatureKind.Deployable,
[SignatureKindENG.Drone]: SignatureKind.Drone,
[SignatureKindENG.Starbase]: SignatureKind.Starbase,
},
RU: {
[SignatureKindRU.CosmicSignature]: SignatureKind.CosmicSignature,
[SignatureKindRU.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
[SignatureKindRU.Structure]: SignatureKind.Structure,
[SignatureKindRU.Ship]: SignatureKind.Ship,
[SignatureKindRU.Deployable]: SignatureKind.Deployable,
[SignatureKindRU.Drone]: SignatureKind.Drone,
[SignatureKindRU.Starbase]: SignatureKind.Starbase,
},
FR: {
[SignatureKindFR.CosmicSignature]: SignatureKind.CosmicSignature,
[SignatureKindFR.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
[SignatureKindFR.Structure]: SignatureKind.Structure,
[SignatureKindFR.Ship]: SignatureKind.Ship,
[SignatureKindFR.Deployable]: SignatureKind.Deployable,
[SignatureKindFR.Drone]: SignatureKind.Drone,
[SignatureKindFR.Starbase]: SignatureKind.Starbase,
},
DE: {
[SignatureKindDE.CosmicSignature]: SignatureKind.CosmicSignature,
[SignatureKindDE.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
[SignatureKindDE.Structure]: SignatureKind.Structure,
[SignatureKindDE.Ship]: SignatureKind.Ship,
[SignatureKindDE.Deployable]: SignatureKind.Deployable,
[SignatureKindDE.Drone]: SignatureKind.Drone,
[SignatureKindDE.Starbase]: SignatureKind.Starbase,
},
};
// Flatten the structure for backward compatibility
export const MAPPING_TYPE_TO_ENG: Record<string, SignatureKind> = (() => {
const flattened: Record<string, SignatureKind> = {};
for (const [, mappings] of Object.entries(LANGUAGE_TYPE_MAPPINGS)) {
Object.assign(flattened, mappings);
}
return flattened;
})();

View File

@@ -1,5 +1,5 @@
import { SystemSignature } from '@/hooks/Mapper/types';
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants';
import { SystemSignature } from '@/hooks/Mapper/types';
import { getState } from './getState';
/**
@@ -22,6 +22,7 @@ export const getActualSigs = (
oldSignatures.forEach(oldSig => {
const newSig = newSignatures.find(s => s.eve_id === oldSig.eve_id);
if (newSig) {
const needUpgrade = getState(GROUPS_LIST, newSig) > getState(GROUPS_LIST, oldSig);
const mergedSig = { ...oldSig };

View File

@@ -1,10 +1,11 @@
import { UNKNOWN_SIGNATURE_NAME } from '@/hooks/Mapper/helpers';
import { SystemSignature } from '@/hooks/Mapper/types';
export const getState = (_: string[], newSig: SystemSignature) => {
let state = -1;
if (!newSig.group) {
state = 0;
} else if (!newSig.name || newSig.name === '') {
} else if (!newSig.name || newSig.name === '' || newSig.name === UNKNOWN_SIGNATURE_NAME) {
state = 1;
} else if (newSig.name !== '') {
state = 2;

View File

@@ -1,23 +1,18 @@
import { useCallback, useRef, useEffect } from 'react';
import { useCallback, useRef } from 'react';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
import { prepareUpdatePayload, scheduleLazyTimers } from '../helpers';
import { prepareUpdatePayload } from '../helpers';
import { UsePendingDeletionParams } from './types';
import { FINAL_DURATION_MS } from '../constants';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
export function usePendingDeletions({
systemId,
setSignatures,
deletionTiming,
onPendingChange,
}: UsePendingDeletionParams) {
}: Omit<UsePendingDeletionParams, 'deletionTiming'>) {
const { outCommand } = useMapRootState();
const pendingDeletionMapRef = useRef<Record<string, ExtendedSystemSignature>>({});
// Use the provided deletion timing or fall back to the default
const finalDuration = deletionTiming !== undefined ? deletionTiming : FINAL_DURATION_MS;
const processRemovedSignatures = useCallback(
async (
removed: ExtendedSystemSignature[],
@@ -25,63 +20,15 @@ export function usePendingDeletions({
updated: ExtendedSystemSignature[],
) => {
if (!removed.length) return;
// If deletion timing is 0, immediately delete without pending state
if (finalDuration === 0) {
await outCommand({
type: OutCommand.updateSignatures,
data: prepareUpdatePayload(systemId, added, updated, removed),
});
return;
}
const now = Date.now();
const processedRemoved = removed.map(r => ({
...r,
pendingDeletion: true,
pendingUntil: now + finalDuration,
}));
pendingDeletionMapRef.current = {
...pendingDeletionMapRef.current,
...processedRemoved.reduce((acc: any, sig) => {
acc[sig.eve_id] = sig;
return acc;
}, {}),
};
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
setSignatures(prev =>
prev.map(sig => {
if (processedRemoved.find(r => r.eve_id === sig.eve_id)) {
return { ...sig, pendingDeletion: true, pendingUntil: now + finalDuration };
}
return sig;
}),
);
scheduleLazyTimers(
processedRemoved,
pendingDeletionMapRef,
async sig => {
await outCommand({
type: OutCommand.updateSignatures,
data: prepareUpdatePayload(systemId, [], [], [sig]),
});
delete pendingDeletionMapRef.current[sig.eve_id];
setSignatures(prev => prev.filter(x => x.eve_id !== sig.eve_id));
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
},
finalDuration,
);
await outCommand({
type: OutCommand.updateSignatures,
data: prepareUpdatePayload(systemId, added, updated, removed),
});
},
[systemId, outCommand, finalDuration],
[systemId, outCommand],
);
const clearPendingDeletions = useCallback(() => {
Object.values(pendingDeletionMapRef.current).forEach(({ finalTimeoutId }) => {
clearTimeout(finalTimeoutId);
});
pendingDeletionMapRef.current = {};
setSignatures(prev => prev.map(x => (x.pendingDeletion ? { ...x, pendingDeletion: false } : x)));
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);

View File

@@ -18,16 +18,18 @@ export const useSystemSignaturesData = ({
onCountChange,
onPendingChange,
onLazyDeleteChange,
deletionTiming,
}: UseSystemSignaturesDataProps) => {
onSignatureDeleted,
}: Omit<UseSystemSignaturesDataProps, 'deletionTiming'> & {
onSignatureDeleted?: (deletedIds: string[]) => void;
}) => {
const { outCommand } = useMapRootState();
const [signatures, setSignatures, signaturesRef] = useRefState<ExtendedSystemSignature[]>([]);
const [selectedSignatures, setSelectedSignatures] = useState<ExtendedSystemSignature[]>([]);
const [hasUnsupportedLanguage, setHasUnsupportedLanguage] = useState<boolean>(false);
const { pendingDeletionMapRef, processRemovedSignatures, clearPendingDeletions } = usePendingDeletions({
systemId,
setSignatures,
deletionTiming,
onPendingChange,
});
@@ -42,6 +44,7 @@ export const useSystemSignaturesData = ({
async (clipboardString: string) => {
const lazyDeleteValue = settings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean;
// Parse the incoming signatures
const incomingSignatures = parseSignatures(
clipboardString,
Object.keys(settings).filter(skey => skey in SignatureKind),
@@ -51,6 +54,18 @@ export const useSystemSignaturesData = ({
return;
}
// Check if any signatures might be using unsupported languages
// This is a basic heuristic: if we have signatures where the original group wasn't mapped
const clipboardRows = clipboardString.split('\n').filter(row => row.trim() !== '');
const detectedSignatureCount = clipboardRows.filter(row => row.match(/^[A-Z]{3}-\d{3}/)).length;
// If we detected valid IDs but got fewer parsed signatures, we might have language issues
if (detectedSignatureCount > 0 && incomingSignatures.length < detectedSignatureCount) {
setHasUnsupportedLanguage(true);
} else {
setHasUnsupportedLanguage(false);
}
const currentNonPending = lazyDeleteValue
? signaturesRef.current.filter(sig => !sig.pendingDeletion)
: signaturesRef.current.filter(sig => !sig.pendingDeletion || !sig.pendingAddition);
@@ -59,6 +74,10 @@ export const useSystemSignaturesData = ({
if (removed.length > 0) {
await processRemovedSignatures(removed, added, updated);
if (onSignatureDeleted) {
const deletedIds = removed.map(sig => sig.eve_id);
onSignatureDeleted(deletedIds);
}
}
if (updated.length !== 0 || added.length !== 0) {
@@ -78,17 +97,16 @@ export const useSystemSignaturesData = ({
onLazyDeleteChange?.(false);
}
},
[settings, signaturesRef, processRemovedSignatures, outCommand, systemId, onLazyDeleteChange],
[settings, signaturesRef, processRemovedSignatures, outCommand, systemId, onLazyDeleteChange, onSignatureDeleted],
);
const handleDeleteSelected = useCallback(async () => {
if (!selectedSignatures.length) return;
const selectedIds = selectedSignatures.map(s => s.eve_id);
const finalList = signatures.filter(s => !selectedIds.includes(s.eve_id));
await handleUpdateSignatures(finalList, false, true);
setSelectedSignatures([]);
}, [selectedSignatures, signatures]);
}, [handleUpdateSignatures, selectedSignatures, signatures]);
const handleSelectAll = useCallback(() => {
setSelectedSignatures(signatures);
@@ -119,11 +137,12 @@ export const useSystemSignaturesData = ({
}, [signatures]);
return {
signatures,
signatures: signatures.filter(sig => !sig.deleted),
selectedSignatures,
setSelectedSignatures,
handleDeleteSelected,
handleSelectAll,
handlePaste,
hasUnsupportedLanguage,
};
};