mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-12 10:45:54 +00:00
feat: add undo deletion for signatures (#155)
* feat: add undo for signature deletion and addition
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||||
import {
|
import {
|
||||||
InfoDrawer,
|
InfoDrawer,
|
||||||
@@ -19,28 +20,29 @@ import {
|
|||||||
STRUCTURE,
|
STRUCTURE,
|
||||||
SystemSignatureSettingsDialog,
|
SystemSignatureSettingsDialog,
|
||||||
} from './SystemSignatureSettingsDialog';
|
} from './SystemSignatureSettingsDialog';
|
||||||
import { SignatureGroup } from '@/hooks/Mapper/types';
|
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
import { PrimeIcons } from 'primereact/api';
|
import { PrimeIcons } from 'primereact/api';
|
||||||
|
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
import { CheckboxChangeEvent } from 'primereact/checkbox';
|
import { CheckboxChangeEvent } from 'primereact/checkbox';
|
||||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
|
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth';
|
||||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||||
|
import { useHotkey } from '@/hooks/Mapper/hooks';
|
||||||
|
import { COMPACT_MAX_WIDTH } from './constants';
|
||||||
|
|
||||||
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v5_2';
|
export const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v5_2';
|
||||||
export const SHOW_DESCRIPTION_COLUMN_SETTING = 'show_description_column_setting';
|
export const SHOW_DESCRIPTION_COLUMN_SETTING = 'show_description_column_setting';
|
||||||
export const SHOW_UPDATED_COLUMN_SETTING = 'SHOW_UPDATED_COLUMN_SETTING';
|
export const SHOW_UPDATED_COLUMN_SETTING = 'SHOW_UPDATED_COLUMN_SETTING';
|
||||||
export const LAZY_DELETE_SIGNATURES_SETTING = 'LAZY_DELETE_SIGNATURES_SETTING';
|
export const LAZY_DELETE_SIGNATURES_SETTING = 'LAZY_DELETE_SIGNATURES_SETTING';
|
||||||
export const KEEP_LAZY_DELETE_SETTING = 'KEEP_LAZY_DELETE_ENABLED_SETTING';
|
export const KEEP_LAZY_DELETE_SETTING = 'KEEP_LAZY_DELETE_ENABLED_SETTING';
|
||||||
|
export const HIDE_LINKED_SIGNATURES_SETTING = 'HIDE_LINKED_SIGNATURES_SETTING';
|
||||||
|
|
||||||
const settings: Setting[] = [
|
const SETTINGS: Setting[] = [
|
||||||
{ key: SHOW_UPDATED_COLUMN_SETTING, name: 'Show Updated Column', value: false, isFilter: false },
|
{ key: SHOW_UPDATED_COLUMN_SETTING, name: 'Show Updated Column', value: false, isFilter: false },
|
||||||
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: false, isFilter: false },
|
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: false, isFilter: false },
|
||||||
{ key: LAZY_DELETE_SIGNATURES_SETTING, name: 'Lazy Delete Signatures', value: false, isFilter: false },
|
{ key: LAZY_DELETE_SIGNATURES_SETTING, name: 'Lazy Delete Signatures', value: false, isFilter: false },
|
||||||
{ key: KEEP_LAZY_DELETE_SETTING, name: 'Keep "Lazy Delete" Enabled', value: false, isFilter: false },
|
{ key: KEEP_LAZY_DELETE_SETTING, name: 'Keep "Lazy Delete" Enabled', value: false, isFilter: false },
|
||||||
|
{ key: HIDE_LINKED_SIGNATURES_SETTING, name: 'Hide Linked Signatures', value: false, isFilter: false },
|
||||||
|
|
||||||
{ key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true, isFilter: true },
|
{ key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true, isFilter: true },
|
||||||
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true, isFilter: true },
|
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true, isFilter: true },
|
||||||
{ key: DEPLOYABLE, name: 'Show Deployables', value: true, isFilter: true },
|
{ key: DEPLOYABLE, name: 'Show Deployables', value: true, isFilter: true },
|
||||||
@@ -56,121 +58,160 @@ const settings: Setting[] = [
|
|||||||
{ key: SignatureGroup.CombatSite, name: 'Show Combat Sites', value: true, isFilter: true },
|
{ key: SignatureGroup.CombatSite, name: 'Show Combat Sites', value: true, isFilter: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
const defaultSettings = () => {
|
const getDefaultSettings = (): Setting[] => [...SETTINGS];
|
||||||
return [...settings];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SystemSignatures = () => {
|
export const SystemSignatures: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
data: { selectedSystems },
|
data: { selectedSystems },
|
||||||
} = useMapRootState();
|
} = useMapRootState();
|
||||||
|
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [settings, setSettings] = useState<Setting[]>(defaultSettings);
|
|
||||||
|
const [currentSettings, setCurrentSettings] = useState<Setting[]>(() => {
|
||||||
|
const stored = localStorage.getItem(SIGNATURE_SETTINGS_KEY);
|
||||||
|
if (stored) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(stored) as Setting[];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing stored settings', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getDefaultSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(currentSettings));
|
||||||
|
}, [currentSettings]);
|
||||||
|
|
||||||
|
const [sigCount, setSigCount] = useState<number>(0);
|
||||||
|
const [pendingSigs, setPendingSigs] = useState<SystemSignature[]>([]);
|
||||||
|
const [undoPending, setUndoPending] = useState<() => void>(() => () => {});
|
||||||
|
|
||||||
|
const handleSigCountChange = useCallback((count: number) => {
|
||||||
|
setSigCount(count);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [systemId] = selectedSystems;
|
const [systemId] = selectedSystems;
|
||||||
|
|
||||||
const isNotSelectedSystem = selectedSystems.length !== 1;
|
const isNotSelectedSystem = selectedSystems.length !== 1;
|
||||||
|
|
||||||
const lazyDeleteValue = useMemo(() => {
|
const lazyDeleteValue = useMemo(
|
||||||
return settings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)!.value;
|
() => currentSettings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)?.value || false,
|
||||||
}, [settings]);
|
[currentSettings],
|
||||||
|
);
|
||||||
|
|
||||||
const handleSettingsChange = useCallback((settings: Setting[]) => {
|
const handleSettingsChange = useCallback((newSettings: Setting[]) => {
|
||||||
setSettings(settings);
|
setCurrentSettings(newSettings);
|
||||||
localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(settings));
|
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleLazyDeleteChange = useCallback((value: boolean) => {
|
const handleLazyDeleteChange = useCallback((value: boolean) => {
|
||||||
setSettings(settings => {
|
setCurrentSettings(prevSettings =>
|
||||||
const lazyDelete = settings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)!;
|
prevSettings.map(setting => (setting.key === LAZY_DELETE_SIGNATURES_SETTING ? { ...setting, value } : setting)),
|
||||||
lazyDelete.value = value;
|
);
|
||||||
localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(settings));
|
|
||||||
return [...settings];
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const restoredSettings = localStorage.getItem(SIGNATURE_SETTINGS_KEY);
|
const isCompact = useMaxWidth(containerRef, COMPACT_MAX_WIDTH);
|
||||||
|
|
||||||
if (restoredSettings) {
|
useHotkey(true, ['z'], (event: KeyboardEvent) => {
|
||||||
setSettings(JSON.parse(restoredSettings));
|
if (pendingSigs.length > 0) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
undoPending();
|
||||||
|
setPendingSigs([]);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleUndoClick = useCallback(() => {
|
||||||
|
undoPending();
|
||||||
|
setPendingSigs([]);
|
||||||
|
}, [undoPending]);
|
||||||
|
|
||||||
|
const handleSettingsButtonClick = useCallback(() => {
|
||||||
|
setVisible(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const renderLabel = () => (
|
||||||
const compact = useMaxWidth(ref, 260);
|
<div className="flex justify-between items-center text-xs w-full h-full" ref={containerRef}>
|
||||||
|
|
||||||
return (
|
|
||||||
<Widget
|
|
||||||
label={
|
|
||||||
<div className="flex justify-between items-center text-xs w-full h-full" ref={ref}>
|
|
||||||
<div className="flex justify-between items-center gap-1">
|
<div className="flex justify-between items-center gap-1">
|
||||||
{!compact && (
|
{!isCompact && (
|
||||||
<div className="flex whitespace-nowrap text-ellipsis overflow-hidden text-stone-400">
|
<div className="flex whitespace-nowrap text-ellipsis overflow-hidden text-stone-400">
|
||||||
Signatures {isNotSelectedSystem ? '' : 'in'}
|
{sigCount ? `[${sigCount}] ` : ''}Signatures {isNotSelectedSystem ? '' : 'in'}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isNotSelectedSystem && <SystemView systemId={systemId} className="select-none text-center" hideRegion />}
|
{!isNotSelectedSystem && <SystemView systemId={systemId} className="select-none text-center" hideRegion />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<LayoutEventBlocker className="flex gap-2.5">
|
<LayoutEventBlocker className="flex gap-2.5">
|
||||||
<WdTooltipWrapper content="Enable Lazy delete">
|
<WdTooltipWrapper content="Enable Lazy delete">
|
||||||
<WdCheckbox
|
<WdCheckbox
|
||||||
size="xs"
|
size="xs"
|
||||||
labelSide="left"
|
labelSide="left"
|
||||||
label={compact ? '' : 'Lazy delete'}
|
label={isCompact ? '' : 'Lazy delete'}
|
||||||
value={lazyDeleteValue}
|
value={lazyDeleteValue}
|
||||||
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300 whitespace-nowrap text-ellipsis overflow-hidden"
|
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300 whitespace-nowrap text-ellipsis overflow-hidden"
|
||||||
onChange={(event: CheckboxChangeEvent) => handleLazyDeleteChange(!!event.checked)}
|
onChange={(event: CheckboxChangeEvent) => handleLazyDeleteChange(!!event.checked)}
|
||||||
/>
|
/>
|
||||||
</WdTooltipWrapper>
|
</WdTooltipWrapper>
|
||||||
|
{pendingSigs.length > 0 && (
|
||||||
|
<WdImgButton
|
||||||
|
className={PrimeIcons.UNDO}
|
||||||
|
style={{ color: 'red' }}
|
||||||
|
tooltip={{ content: `Undo pending changes (${pendingSigs.length})` }}
|
||||||
|
onClick={handleUndoClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<WdImgButton
|
<WdImgButton
|
||||||
className={PrimeIcons.QUESTION_CIRCLE}
|
className={PrimeIcons.QUESTION_CIRCLE}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
position: TooltipPosition.left,
|
position: TooltipPosition.left,
|
||||||
// @ts-ignore
|
|
||||||
content: (
|
content: (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<InfoDrawer title={<b className="text-slate-50">How to add/update signature?</b>}>
|
<InfoDrawer title={<b className="text-slate-50">How to add/update signature?</b>}>
|
||||||
In game you need select one or more signatures <br /> in list in{' '}
|
In game you need to select one or more signatures <br /> in the list in{' '}
|
||||||
<b className="text-sky-500">Probe scanner</b>. <br /> Use next hotkeys:
|
<b className="text-sky-500">Probe scanner</b>. <br /> Use next hotkeys:
|
||||||
<br />
|
<br />
|
||||||
<b className="text-sky-500">Shift + LMB</b> or <b className="text-sky-500">Ctrl + LMB</b>
|
<b className="text-sky-500">Shift + LMB</b> or <b className="text-sky-500">Ctrl + LMB</b>
|
||||||
<br /> or <b className="text-sky-500">Ctrl + A</b> for select all
|
<br /> or <b className="text-sky-500">Ctrl + A</b> for select all
|
||||||
<br />
|
<br /> and then use <b className="text-sky-500">Ctrl + C</b>, after you need to go <br />
|
||||||
and then use <b className="text-sky-500">Ctrl + C</b>, after you need to go <br />
|
here, select Solar system and paste it with <b className="text-sky-500">Ctrl + V</b>
|
||||||
here select Solar system and paste it with <b className="text-sky-500">Ctrl + V</b>
|
|
||||||
</InfoDrawer>
|
</InfoDrawer>
|
||||||
<InfoDrawer title={<b className="text-slate-50">How to select?</b>}>
|
<InfoDrawer title={<b className="text-slate-50">How to select?</b>}>
|
||||||
For select any signature need click on that, <br /> with hotkeys{' '}
|
For selecting any signature, click on it <br /> with hotkeys{' '}
|
||||||
<b className="text-sky-500">Shift + LMB</b> or <b className="text-sky-500">Ctrl + LMB</b>
|
<b className="text-sky-500">Shift + LMB</b> or <b className="text-sky-500">Ctrl + LMB</b>
|
||||||
</InfoDrawer>
|
</InfoDrawer>
|
||||||
<InfoDrawer title={<b className="text-slate-50">How to delete?</b>}>
|
<InfoDrawer title={<b className="text-slate-50">How to delete?</b>}>
|
||||||
For delete any signature first of all you need select before
|
To delete any signature, first select it <br /> and then press <b className="text-sky-500">Del</b>
|
||||||
<br /> and then use <b className="text-sky-500">Del</b>
|
|
||||||
</InfoDrawer>
|
</InfoDrawer>
|
||||||
</div>
|
</div>
|
||||||
) as React.ReactNode,
|
) as React.ReactNode,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<WdImgButton className={PrimeIcons.SLIDERS_H} onClick={() => setVisible(true)} />
|
<WdImgButton className={PrimeIcons.SLIDERS_H} onClick={handleSettingsButtonClick} />
|
||||||
</LayoutEventBlocker>
|
</LayoutEventBlocker>
|
||||||
</div>
|
</div>
|
||||||
}
|
);
|
||||||
>
|
|
||||||
|
return (
|
||||||
|
<Widget label={renderLabel()}>
|
||||||
{isNotSelectedSystem ? (
|
{isNotSelectedSystem ? (
|
||||||
<div className="w-full h-full flex justify-center items-center select-none text-center text-stone-400/80 text-sm">
|
<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
|
System is not selected
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<SystemSignaturesContent systemId={systemId} settings={settings} onLazyDeleteChange={handleLazyDeleteChange} />
|
<SystemSignaturesContent
|
||||||
|
systemId={systemId}
|
||||||
|
settings={currentSettings}
|
||||||
|
onLazyDeleteChange={handleLazyDeleteChange}
|
||||||
|
onCountChange={handleSigCountChange}
|
||||||
|
onPendingChange={(pending, undo) => {
|
||||||
|
setPendingSigs(pending);
|
||||||
|
setUndoPending(() => undo);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{visible && (
|
{visible && (
|
||||||
<SystemSignatureSettingsDialog
|
<SystemSignatureSettingsDialog
|
||||||
settings={settings}
|
settings={currentSettings}
|
||||||
onCancel={() => setVisible(false)}
|
onCancel={() => setVisible(false)}
|
||||||
onSave={handleSettingsChange}
|
onSave={handleSettingsChange}
|
||||||
/>
|
/>
|
||||||
@@ -178,3 +219,5 @@ export const SystemSignatures = () => {
|
|||||||
</Widget>
|
</Widget>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default SystemSignatures;
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
.TableRowCompact {
|
|
||||||
height: 8px;
|
|
||||||
max-height: 8px;
|
|
||||||
font-size: 12px !important;
|
|
||||||
line-height: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,29 +1,26 @@
|
|||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
||||||
import { parseSignatures } from '@/hooks/Mapper/helpers';
|
|
||||||
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
|
||||||
import { WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit';
|
|
||||||
import {
|
|
||||||
getGroupIdByRawGroup,
|
|
||||||
GROUPS_LIST,
|
|
||||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
|
||||||
|
|
||||||
import { DataTable, DataTableRowClickEvent, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable';
|
import { DataTable, DataTableRowClickEvent, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable';
|
||||||
import { Column } from 'primereact/column';
|
import { Column } from 'primereact/column';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { PrimeIcons } from 'primereact/api';
|
||||||
import useRefState from 'react-usestateref';
|
import useLocalStorageState from 'use-local-storage-state';
|
||||||
import { Setting } from '../SystemSignatureSettingsDialog';
|
|
||||||
import { useHotkey } from '@/hooks/Mapper/hooks';
|
|
||||||
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
|
|
||||||
import { useClipboard } from '@/hooks/Mapper/hooks/useClipboard';
|
|
||||||
|
|
||||||
import classes from './SystemSignaturesContent.module.scss';
|
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||||
import clsx from 'clsx';
|
import { SignatureSettings } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings';
|
||||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
import { WdTooltip, WdTooltipHandlers, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
||||||
import { SignatureView } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SignatureView';
|
import { SignatureView } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SignatureView';
|
||||||
import {
|
import {
|
||||||
getActualSigs,
|
COMPACT_MAX_WIDTH,
|
||||||
getRowColorByTimeLeft,
|
GROUPS_LIST,
|
||||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers';
|
MEDIUM_MAX_WIDTH,
|
||||||
|
OTHER_COLUMNS_WIDTH,
|
||||||
|
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants';
|
||||||
|
import {
|
||||||
|
KEEP_LAZY_DELETE_SETTING,
|
||||||
|
LAZY_DELETE_SIGNATURES_SETTING,
|
||||||
|
SHOW_DESCRIPTION_COLUMN_SETTING,
|
||||||
|
SHOW_UPDATED_COLUMN_SETTING,
|
||||||
|
} from '../SystemSignatures';
|
||||||
|
import { COSMIC_SIGNATURE } from '../SystemSignatureSettingsDialog';
|
||||||
import {
|
import {
|
||||||
renderAddedTimeLeft,
|
renderAddedTimeLeft,
|
||||||
renderDescription,
|
renderDescription,
|
||||||
@@ -31,404 +28,278 @@ import {
|
|||||||
renderInfoColumn,
|
renderInfoColumn,
|
||||||
renderUpdatedTimeLeft,
|
renderUpdatedTimeLeft,
|
||||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
||||||
import useLocalStorageState from 'use-local-storage-state';
|
import { ExtendedSystemSignature } from '../helpers/contentHelpers';
|
||||||
import { PrimeIcons } from 'primereact/api';
|
import { useSystemSignaturesData } from '../hooks/useSystemSignaturesData';
|
||||||
import { SignatureSettings } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings';
|
import { getSignatureRowClass } from '../helpers/rowStyles';
|
||||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth';
|
||||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
import { useClipboard, useHotkey } from '@/hooks/Mapper/hooks';
|
||||||
import { COSMIC_SIGNATURE } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
|
|
||||||
import {
|
|
||||||
SHOW_DESCRIPTION_COLUMN_SETTING,
|
|
||||||
SHOW_UPDATED_COLUMN_SETTING,
|
|
||||||
LAZY_DELETE_SIGNATURES_SETTING,
|
|
||||||
KEEP_LAZY_DELETE_SETTING,
|
|
||||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures';
|
|
||||||
type SystemSignaturesSortSettings = {
|
|
||||||
sortField: string;
|
|
||||||
sortOrder: SortOrder;
|
|
||||||
};
|
|
||||||
|
|
||||||
const SORT_DEFAULT_VALUES: SystemSignaturesSortSettings = {
|
|
||||||
sortField: 'inserted_at',
|
|
||||||
sortOrder: -1,
|
|
||||||
};
|
|
||||||
|
|
||||||
interface SystemSignaturesContentProps {
|
interface SystemSignaturesContentProps {
|
||||||
systemId: string;
|
systemId: string;
|
||||||
settings: Setting[];
|
settings: { key: string; value: boolean }[];
|
||||||
hideLinkedSignatures?: boolean;
|
hideLinkedSignatures?: boolean;
|
||||||
selectable?: boolean;
|
selectable?: boolean;
|
||||||
onSelect?: (signature: SystemSignature) => void;
|
onSelect?: (signature: SystemSignature) => void;
|
||||||
onLazyDeleteChange?: (value: boolean) => void;
|
onLazyDeleteChange?: (value: boolean) => void;
|
||||||
|
onCountChange: (count: number) => void;
|
||||||
|
onPendingChange?: (pending: ExtendedSystemSignature[], undo: () => void) => void;
|
||||||
}
|
}
|
||||||
export const SystemSignaturesContent = ({
|
|
||||||
|
const headerInlineStyle = { padding: '2px', fontSize: '12px', lineHeight: '1.333' };
|
||||||
|
|
||||||
|
export function SystemSignaturesContent({
|
||||||
systemId,
|
systemId,
|
||||||
settings,
|
settings,
|
||||||
hideLinkedSignatures,
|
hideLinkedSignatures,
|
||||||
selectable,
|
selectable,
|
||||||
onSelect,
|
onSelect,
|
||||||
onLazyDeleteChange,
|
onLazyDeleteChange,
|
||||||
}: SystemSignaturesContentProps) => {
|
onCountChange,
|
||||||
const { outCommand } = useMapRootState();
|
onPendingChange,
|
||||||
|
}: SystemSignaturesContentProps) {
|
||||||
const [signatures, setSignatures, signaturesRef] = useRefState<SystemSignature[]>([]);
|
const { signatures, selectedSignatures, setSelectedSignatures, handleDeleteSelected, handleSelectAll, handlePaste } =
|
||||||
const [selectedSignatures, setSelectedSignatures] = useState<SystemSignature[]>([]);
|
useSystemSignaturesData({
|
||||||
const [nameColumnWidth, setNameColumnWidth] = useState('auto');
|
systemId,
|
||||||
const [selectedSignature, setSelectedSignature] = useState<SystemSignature | null>(null);
|
settings,
|
||||||
|
onCountChange,
|
||||||
const [hoveredSig, setHoveredSig] = useState<SystemSignature | null>(null);
|
onPendingChange,
|
||||||
|
onLazyDeleteChange,
|
||||||
const [sortSettings, setSortSettings] = useLocalStorageState<SystemSignaturesSortSettings>('window:signatures:sort', {
|
|
||||||
defaultValue: SORT_DEFAULT_VALUES,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [sortSettings, setSortSettings] = useLocalStorageState<{ sortField: string; sortOrder: SortOrder }>(
|
||||||
|
'window:signatures:sort',
|
||||||
|
{ defaultValue: { sortField: 'inserted_at', sortOrder: -1 } },
|
||||||
|
);
|
||||||
|
|
||||||
const tableRef = useRef<HTMLDivElement>(null);
|
const tableRef = useRef<HTMLDivElement>(null);
|
||||||
const compact = useMaxWidth(tableRef, 260);
|
|
||||||
const medium = useMaxWidth(tableRef, 380);
|
|
||||||
const refData = useRef({ selectable });
|
|
||||||
refData.current = { selectable };
|
|
||||||
|
|
||||||
const tooltipRef = useRef<WdTooltipHandlers>(null);
|
const tooltipRef = useRef<WdTooltipHandlers>(null);
|
||||||
|
const [hoveredSignature, setHoveredSignature] = useState<SystemSignature | null>(null);
|
||||||
|
|
||||||
|
const isCompact = useMaxWidth(tableRef, COMPACT_MAX_WIDTH);
|
||||||
|
const isMedium = useMaxWidth(tableRef, MEDIUM_MAX_WIDTH);
|
||||||
|
|
||||||
|
const lazyDeleteEnabled = settings.find(s => s.key === LAZY_DELETE_SIGNATURES_SETTING)?.value ?? false;
|
||||||
|
const keepLazyDeleteEnabled = settings.find(s => s.key === KEEP_LAZY_DELETE_SETTING)?.value ?? false;
|
||||||
|
|
||||||
const { clipboardContent, setClipboardContent } = useClipboard();
|
const { clipboardContent, setClipboardContent } = useClipboard();
|
||||||
|
|
||||||
const lazyDeleteValue = useMemo(() => {
|
|
||||||
return settings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)?.value ?? false;
|
|
||||||
}, [settings]);
|
|
||||||
|
|
||||||
const keepLazyDeleteValue = useMemo(() => {
|
|
||||||
return settings.find(setting => setting.key === KEEP_LAZY_DELETE_SETTING)?.value ?? false;
|
|
||||||
}, [settings]);
|
|
||||||
|
|
||||||
const handleResize = useCallback(() => {
|
|
||||||
if (tableRef.current) {
|
|
||||||
const tableWidth = tableRef.current.offsetWidth;
|
|
||||||
const otherColumnsWidth = 276;
|
|
||||||
const availableWidth = tableWidth - otherColumnsWidth;
|
|
||||||
setNameColumnWidth(`${availableWidth}px`);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const groupSettings = useMemo(() => settings.filter(s => (GROUPS_LIST as string[]).includes(s.key)), [settings]);
|
|
||||||
const showDescriptionColumn = useMemo(
|
|
||||||
() => settings.find(s => s.key === SHOW_DESCRIPTION_COLUMN_SETTING)?.value,
|
|
||||||
[settings],
|
|
||||||
);
|
|
||||||
|
|
||||||
const showUpdatedColumn = useMemo(() => settings.find(s => s.key === SHOW_UPDATED_COLUMN_SETTING)?.value, [settings]);
|
|
||||||
|
|
||||||
const filteredSignatures = useMemo(() => {
|
|
||||||
return signatures
|
|
||||||
.filter(x => {
|
|
||||||
if (hideLinkedSignatures && !!x.linked_system) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isCosmicSignature = x.kind === COSMIC_SIGNATURE;
|
|
||||||
const preparedGroup = getGroupIdByRawGroup(x.group);
|
|
||||||
|
|
||||||
if (isCosmicSignature) {
|
|
||||||
const showCosmicSignatures = settings.find(y => y.key === COSMIC_SIGNATURE)?.value;
|
|
||||||
if (showCosmicSignatures) {
|
|
||||||
return !x.group || groupSettings.find(y => y.key === preparedGroup)?.value;
|
|
||||||
} else {
|
|
||||||
return !!x.group && groupSettings.find(y => y.key === preparedGroup)?.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.find(y => y.key === x.kind)?.value;
|
|
||||||
})
|
|
||||||
.sort((a, b) => {
|
|
||||||
return new Date(b.updated_at || 0).getTime() - new Date(a.updated_at || 0).getTime();
|
|
||||||
});
|
|
||||||
}, [signatures, settings, groupSettings, hideLinkedSignatures]);
|
|
||||||
|
|
||||||
const handleGetSignatures = useCallback(async () => {
|
|
||||||
const { signatures } = await outCommand({
|
|
||||||
type: OutCommand.getSignatures,
|
|
||||||
data: { system_id: systemId },
|
|
||||||
});
|
|
||||||
|
|
||||||
setSignatures(signatures);
|
|
||||||
}, [outCommand, systemId]);
|
|
||||||
|
|
||||||
const handleUpdateSignatures = useCallback(
|
|
||||||
async (newSignatures: SystemSignature[], updateOnly: boolean, skipUpdateUntouched?: boolean) => {
|
|
||||||
const { added, updated, removed } = getActualSigs(
|
|
||||||
signaturesRef.current,
|
|
||||||
newSignatures,
|
|
||||||
updateOnly,
|
|
||||||
skipUpdateUntouched,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { signatures: updatedSignatures } = await outCommand({
|
|
||||||
type: OutCommand.updateSignatures,
|
|
||||||
data: {
|
|
||||||
system_id: systemId,
|
|
||||||
added,
|
|
||||||
updated,
|
|
||||||
removed,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
setSignatures(() => updatedSignatures);
|
|
||||||
setSelectedSignatures([]);
|
|
||||||
},
|
|
||||||
[outCommand, systemId],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDeleteSelected = useCallback(
|
|
||||||
async (e: KeyboardEvent) => {
|
|
||||||
if (selectable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (selectedSignatures.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
const selectedSignaturesEveIds = selectedSignatures.map(x => x.eve_id);
|
|
||||||
await handleUpdateSignatures(
|
|
||||||
signatures.filter(x => !selectedSignaturesEveIds.includes(x.eve_id)),
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[handleUpdateSignatures, selectable, signatures, selectedSignatures],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSelectAll = useCallback(() => {
|
|
||||||
setSelectedSignatures(signatures);
|
|
||||||
}, [signatures]);
|
|
||||||
|
|
||||||
const handleSelectSignatures = useCallback(
|
|
||||||
// TODO still will be good to define types if we use typescript
|
|
||||||
// @ts-ignore
|
|
||||||
e => {
|
|
||||||
if (selectable) {
|
|
||||||
onSelect?.(e.value);
|
|
||||||
} else {
|
|
||||||
setSelectedSignatures(e.value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[onSelect, selectable],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePaste = async (clipboardContent: string) => {
|
|
||||||
const newSignatures = parseSignatures(
|
|
||||||
clipboardContent,
|
|
||||||
settings.map(x => x.key),
|
|
||||||
);
|
|
||||||
|
|
||||||
handleUpdateSignatures(newSignatures, !lazyDeleteValue);
|
|
||||||
|
|
||||||
if (lazyDeleteValue && !keepLazyDeleteValue) {
|
|
||||||
onLazyDeleteChange?.(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEnterRow = useCallback(
|
|
||||||
(e: DataTableRowMouseEvent) => {
|
|
||||||
setHoveredSig(filteredSignatures[e.index]);
|
|
||||||
tooltipRef.current?.show(e.originalEvent);
|
|
||||||
},
|
|
||||||
[filteredSignatures],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleLeaveRow = useCallback((e: DataTableRowMouseEvent) => {
|
|
||||||
tooltipRef.current?.hide(e.originalEvent);
|
|
||||||
setHoveredSig(null);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (refData.current.selectable) {
|
if (selectable) return;
|
||||||
return;
|
if (!clipboardContent?.text) return;
|
||||||
}
|
|
||||||
|
|
||||||
if (!clipboardContent?.text) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePaste(clipboardContent.text);
|
handlePaste(clipboardContent.text);
|
||||||
|
|
||||||
|
if (lazyDeleteEnabled && !keepLazyDeleteEnabled) {
|
||||||
|
onLazyDeleteChange?.(false);
|
||||||
|
}
|
||||||
setClipboardContent(null);
|
setClipboardContent(null);
|
||||||
}, [clipboardContent, selectable, lazyDeleteValue, keepLazyDeleteValue]);
|
}, [
|
||||||
|
selectable,
|
||||||
|
clipboardContent,
|
||||||
|
handlePaste,
|
||||||
|
setClipboardContent,
|
||||||
|
lazyDeleteEnabled,
|
||||||
|
keepLazyDeleteEnabled,
|
||||||
|
onLazyDeleteChange,
|
||||||
|
]);
|
||||||
|
|
||||||
useHotkey(true, ['a'], handleSelectAll);
|
useHotkey(true, ['a'], handleSelectAll);
|
||||||
useHotkey(false, ['Backspace', 'Delete'], handleDeleteSelected);
|
useHotkey(false, ['Backspace', 'Delete'], (event: KeyboardEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
useEffect(() => {
|
event.stopPropagation();
|
||||||
if (!systemId) {
|
handleDeleteSelected();
|
||||||
setSignatures([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleGetSignatures();
|
|
||||||
}, [systemId]);
|
|
||||||
|
|
||||||
useMapEventListener(event => {
|
|
||||||
switch (event.name) {
|
|
||||||
case Commands.signaturesUpdated:
|
|
||||||
if (event.data?.toString() !== systemId.toString()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleGetSignatures();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
const [nameColumnWidth, setNameColumnWidth] = useState('auto');
|
||||||
const observer = new ResizeObserver(handleResize);
|
const handleResize = useCallback(() => {
|
||||||
if (tableRef.current) {
|
if (!tableRef.current) return;
|
||||||
observer.observe(tableRef.current);
|
const tableWidth = tableRef.current.offsetWidth;
|
||||||
}
|
const otherColumnsWidth = OTHER_COLUMNS_WIDTH;
|
||||||
|
setNameColumnWidth(`${tableWidth - otherColumnsWidth}px`);
|
||||||
handleResize(); // Call on mount to set initial width
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (tableRef.current) {
|
|
||||||
observer.unobserve(tableRef.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
const renderToolbar = (/*row: SystemSignature*/) => {
|
if (!tableRef.current) return;
|
||||||
return (
|
const observer = new ResizeObserver(handleResize);
|
||||||
<div className="flex justify-end items-center gap-2 mr-[4px]">
|
observer.observe(tableRef.current);
|
||||||
<WdTooltipWrapper content="To Edit Signature do double click">
|
handleResize();
|
||||||
<span className={clsx(PrimeIcons.PENCIL, 'text-[10px]')}></span>
|
return () => {
|
||||||
</WdTooltipWrapper>
|
observer.disconnect();
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
}, [handleResize]);
|
||||||
|
|
||||||
|
const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState<SystemSignature | null>(null);
|
||||||
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
|
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
|
||||||
|
|
||||||
const handleRowClick = (e: DataTableRowClickEvent) => {
|
const handleRowClick = (e: DataTableRowClickEvent) => {
|
||||||
setSelectedSignature(e.data as SystemSignature);
|
setSelectedSignatureForDialog(e.data as SystemSignature);
|
||||||
setShowSignatureSettings(true);
|
setShowSignatureSettings(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSelectSignatures = useCallback(
|
||||||
|
(e: { value: SystemSignature[] }) => {
|
||||||
|
if (selectable) {
|
||||||
|
onSelect?.(e.value[0]);
|
||||||
|
} else {
|
||||||
|
setSelectedSignatures(e.value as ExtendedSystemSignature[]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[selectable, onSelect, setSelectedSignatures],
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupSettings = settings.filter(s => GROUPS_LIST.includes(s.key as SignatureGroup));
|
||||||
|
const showDescriptionColumn = settings.find(s => s.key === SHOW_DESCRIPTION_COLUMN_SETTING)?.value;
|
||||||
|
const showUpdatedColumn = settings.find(s => s.key === SHOW_UPDATED_COLUMN_SETTING)?.value;
|
||||||
|
|
||||||
|
const filteredSignatures = useMemo<ExtendedSystemSignature[]>(() => {
|
||||||
|
return signatures.filter(sig => {
|
||||||
|
if (hideLinkedSignatures && sig.linked_system) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (sig.kind === COSMIC_SIGNATURE) {
|
||||||
|
const showCosmic = settings.find(y => y.key === COSMIC_SIGNATURE)?.value;
|
||||||
|
if (!showCosmic) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (sig.group && groupSettings.find(y => y.key === sig.group)?.value === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return settings.find(y => y.key === sig.kind)?.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [signatures, settings, groupSettings, hideLinkedSignatures]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div ref={tableRef} className="h-full">
|
||||||
<div ref={tableRef} className={'h-full '}>
|
|
||||||
{filteredSignatures.length === 0 ? (
|
{filteredSignatures.length === 0 ? (
|
||||||
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">
|
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">
|
||||||
No signatures
|
No signatures
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
|
||||||
{/* @ts-ignore */}
|
|
||||||
<DataTable
|
<DataTable
|
||||||
className={classes.Table}
|
|
||||||
value={filteredSignatures}
|
value={filteredSignatures}
|
||||||
size="small"
|
size="small"
|
||||||
selectionMode={selectable ? 'single' : 'multiple'}
|
selectionMode="multiple"
|
||||||
selection={selectedSignatures}
|
selection={selectedSignatures}
|
||||||
metaKeySelection
|
metaKeySelection
|
||||||
onSelectionChange={handleSelectSignatures}
|
onSelectionChange={handleSelectSignatures}
|
||||||
dataKey="eve_id"
|
dataKey="eve_id"
|
||||||
tableClassName="w-full select-none"
|
className="w-full select-none"
|
||||||
resizableColumns={false}
|
resizableColumns={false}
|
||||||
onRowDoubleClick={handleRowClick}
|
|
||||||
rowHover
|
rowHover
|
||||||
selectAll
|
selectAll
|
||||||
|
onRowDoubleClick={handleRowClick}
|
||||||
sortField={sortSettings.sortField}
|
sortField={sortSettings.sortField}
|
||||||
sortOrder={sortSettings.sortOrder}
|
sortOrder={sortSettings.sortOrder}
|
||||||
onSort={event => setSortSettings(() => ({ sortField: event.sortField, sortOrder: event.sortOrder }))}
|
onSort={e => setSortSettings({ sortField: e.sortField, sortOrder: e.sortOrder })}
|
||||||
onRowMouseEnter={compact || medium ? handleEnterRow : undefined}
|
onRowMouseEnter={
|
||||||
onRowMouseLeave={compact || medium ? handleLeaveRow : undefined}
|
isCompact || isMedium
|
||||||
rowClassName={row => {
|
? (e: DataTableRowMouseEvent) => {
|
||||||
if (selectedSignatures.some(x => x.eve_id === row.eve_id)) {
|
setHoveredSignature(filteredSignatures[e.index]);
|
||||||
return clsx(classes.TableRowCompact, 'bg-amber-500/50 hover:bg-amber-500/70 transition duration-200');
|
tooltipRef.current?.show(e.originalEvent);
|
||||||
}
|
}
|
||||||
|
: undefined
|
||||||
const dateClass = getRowColorByTimeLeft(row.inserted_at ? new Date(row.inserted_at) : undefined);
|
|
||||||
if (!dateClass) {
|
|
||||||
return clsx(classes.TableRowCompact, 'hover:bg-purple-400/20 transition duration-200');
|
|
||||||
}
|
}
|
||||||
|
onRowMouseLeave={
|
||||||
return clsx(classes.TableRowCompact, dateClass);
|
isCompact || isMedium
|
||||||
}}
|
? () => {
|
||||||
|
setHoveredSignature(null);
|
||||||
|
tooltipRef.current?.hide();
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
rowClassName={rowData => getSignatureRowClass(rowData as ExtendedSystemSignature, selectedSignatures)}
|
||||||
>
|
>
|
||||||
<Column
|
<Column
|
||||||
|
field="icon"
|
||||||
|
header=""
|
||||||
|
headerStyle={headerInlineStyle}
|
||||||
|
body={sig => renderIcon(sig)}
|
||||||
bodyClassName="p-0 px-1"
|
bodyClassName="p-0 px-1"
|
||||||
field="group"
|
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||||
body={x => renderIcon(x)}
|
/>
|
||||||
style={{ maxWidth: 26, minWidth: 26, width: 26, height: 25 }}
|
|
||||||
></Column>
|
|
||||||
|
|
||||||
<Column
|
<Column
|
||||||
field="eve_id"
|
field="eve_id"
|
||||||
header="Id"
|
header="Id"
|
||||||
|
headerStyle={headerInlineStyle}
|
||||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||||
style={{ maxWidth: 72, minWidth: 72, width: 72 }}
|
style={{ maxWidth: 72, minWidth: 72, width: 72 }}
|
||||||
sortable
|
sortable
|
||||||
></Column>
|
/>
|
||||||
<Column
|
<Column
|
||||||
field="group"
|
field="group"
|
||||||
header="Group"
|
header="Group"
|
||||||
|
headerStyle={headerInlineStyle}
|
||||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||||
hidden={compact}
|
|
||||||
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
|
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
|
||||||
|
body={sig => sig.group ?? ''}
|
||||||
|
hidden={isCompact}
|
||||||
sortable
|
sortable
|
||||||
></Column>
|
/>
|
||||||
<Column
|
<Column
|
||||||
field="info"
|
field="info"
|
||||||
|
header="Info"
|
||||||
|
headerStyle={headerInlineStyle}
|
||||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||||
body={renderInfoColumn}
|
|
||||||
style={{ maxWidth: nameColumnWidth }}
|
style={{ maxWidth: nameColumnWidth }}
|
||||||
hidden={compact || medium}
|
hidden={isCompact || isMedium}
|
||||||
></Column>
|
body={renderInfoColumn}
|
||||||
|
/>
|
||||||
{showDescriptionColumn && (
|
{showDescriptionColumn && (
|
||||||
<Column
|
<Column
|
||||||
field="description"
|
field="description"
|
||||||
header="Description"
|
header="Description"
|
||||||
|
headerStyle={headerInlineStyle}
|
||||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||||
|
hidden={isCompact}
|
||||||
body={renderDescription}
|
body={renderDescription}
|
||||||
hidden={compact}
|
|
||||||
sortable
|
sortable
|
||||||
></Column>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Column
|
<Column
|
||||||
field="inserted_at"
|
field="inserted_at"
|
||||||
header="Added"
|
header="Added"
|
||||||
|
headerStyle={headerInlineStyle}
|
||||||
dataType="date"
|
dataType="date"
|
||||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
|
||||||
body={renderAddedTimeLeft}
|
body={renderAddedTimeLeft}
|
||||||
|
style={{ minWidth: 70, maxWidth: 80 }}
|
||||||
|
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||||
sortable
|
sortable
|
||||||
></Column>
|
/>
|
||||||
|
|
||||||
{showUpdatedColumn && (
|
{showUpdatedColumn && (
|
||||||
<Column
|
<Column
|
||||||
field="updated_at"
|
field="updated_at"
|
||||||
header="Updated"
|
header="Updated"
|
||||||
|
headerStyle={headerInlineStyle}
|
||||||
dataType="date"
|
dataType="date"
|
||||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
|
||||||
body={renderUpdatedTimeLeft}
|
body={renderUpdatedTimeLeft}
|
||||||
|
style={{ minWidth: 70, maxWidth: 80 }}
|
||||||
|
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||||
sortable
|
sortable
|
||||||
></Column>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!selectable && (
|
{!selectable && (
|
||||||
<Column
|
<Column
|
||||||
bodyClassName="p-0 pl-1 pr-2"
|
header=""
|
||||||
field="group"
|
headerStyle={headerInlineStyle}
|
||||||
body={renderToolbar}
|
body={() => (
|
||||||
// headerClassName={headerClasses}
|
<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 }}
|
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||||
></Column>
|
bodyClassName="p-0 pl-1 pr-2"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</DataTable>
|
</DataTable>
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<WdTooltip
|
<WdTooltip
|
||||||
className="bg-stone-900/95 text-slate-50"
|
className="bg-stone-900/95 text-slate-50"
|
||||||
ref={tooltipRef}
|
ref={tooltipRef}
|
||||||
content={hoveredSig ? <SignatureView {...hoveredSig} /> : null}
|
content={hoveredSignature ? <SignatureView {...hoveredSignature} /> : null}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{showSignatureSettings && (
|
{showSignatureSettings && (
|
||||||
@@ -436,10 +307,9 @@ export const SystemSignaturesContent = ({
|
|||||||
systemId={systemId}
|
systemId={systemId}
|
||||||
show
|
show
|
||||||
onHide={() => setShowSignatureSettings(false)}
|
onHide={() => setShowSignatureSettings(false)}
|
||||||
signatureData={selectedSignature}
|
signatureData={selectedSignatureForDialog || undefined}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ import {
|
|||||||
|
|
||||||
export const TIME_ONE_MINUTE = 1000 * 60;
|
export const TIME_ONE_MINUTE = 1000 * 60;
|
||||||
export const TIME_TEN_MINUTES = 1000 * 60 * 10;
|
export const TIME_TEN_MINUTES = 1000 * 60 * 10;
|
||||||
|
export const TIME_ONE_DAY = 24 * 60 * 60 * 1000;
|
||||||
|
export const TIME_ONE_WEEK = 7 * TIME_ONE_DAY;
|
||||||
|
export const FINAL_DURATION_MS = 10000;
|
||||||
|
|
||||||
|
export const COMPACT_MAX_WIDTH = 260;
|
||||||
|
export const MEDIUM_MAX_WIDTH = 380;
|
||||||
|
export const OTHER_COLUMNS_WIDTH = 276;
|
||||||
|
|
||||||
export const GROUPS_LIST = [
|
export const GROUPS_LIST = [
|
||||||
SignatureGroup.GasSite,
|
SignatureGroup.GasSite,
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||||
|
import { FINAL_DURATION_MS } from '../constants';
|
||||||
|
|
||||||
|
export interface ExtendedSystemSignature extends SystemSignature {
|
||||||
|
pendingDeletion?: boolean;
|
||||||
|
pendingAddition?: boolean;
|
||||||
|
pendingUntil?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareUpdatePayload(
|
||||||
|
systemId: string,
|
||||||
|
added: ExtendedSystemSignature[],
|
||||||
|
updated: ExtendedSystemSignature[],
|
||||||
|
removed: ExtendedSystemSignature[],
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
system_id: systemId,
|
||||||
|
added: added.map(s => ({ ...s })),
|
||||||
|
updated: updated.map(s => ({ ...s })),
|
||||||
|
removed: removed.map(s => ({ ...s })),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function schedulePendingAdditionForSig(
|
||||||
|
sig: ExtendedSystemSignature,
|
||||||
|
finalDuration: number,
|
||||||
|
setSignatures: React.Dispatch<React.SetStateAction<ExtendedSystemSignature[]>>,
|
||||||
|
pendingAdditionMapRef: React.MutableRefObject<Record<string, { finalUntil: number; finalTimeoutId: number }>>,
|
||||||
|
setPendingUndoAdditions: React.Dispatch<React.SetStateAction<ExtendedSystemSignature[]>>,
|
||||||
|
) {
|
||||||
|
setPendingUndoAdditions(prev => [...prev, sig]);
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const finalTimeoutId = window.setTimeout(() => {
|
||||||
|
setSignatures(prev =>
|
||||||
|
prev.map(x => (x.eve_id === sig.eve_id ? { ...x, pendingAddition: false, pendingUntil: undefined } : x)),
|
||||||
|
);
|
||||||
|
const clone = { ...pendingAdditionMapRef.current };
|
||||||
|
delete clone[sig.eve_id];
|
||||||
|
pendingAdditionMapRef.current = clone;
|
||||||
|
|
||||||
|
setPendingUndoAdditions(prev => prev.filter(x => x.eve_id !== sig.eve_id));
|
||||||
|
}, finalDuration);
|
||||||
|
|
||||||
|
pendingAdditionMapRef.current = {
|
||||||
|
...pendingAdditionMapRef.current,
|
||||||
|
[sig.eve_id]: {
|
||||||
|
finalUntil: now + finalDuration,
|
||||||
|
finalTimeoutId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
setSignatures(prev =>
|
||||||
|
prev.map(x => (x.eve_id === sig.eve_id ? { ...x, pendingAddition: true, pendingUntil: now + finalDuration } : x)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function scheduleLazyDeletionTimers(
|
||||||
|
toRemove: ExtendedSystemSignature[],
|
||||||
|
setPendingMap: React.Dispatch<React.SetStateAction<Record<string, { finalUntil: number; finalTimeoutId: number }>>>,
|
||||||
|
finalizeRemoval: (sig: ExtendedSystemSignature) => Promise<void>,
|
||||||
|
finalDuration = FINAL_DURATION_MS,
|
||||||
|
) {
|
||||||
|
const now = Date.now();
|
||||||
|
toRemove.forEach(sig => {
|
||||||
|
const finalTimeoutId = window.setTimeout(async () => {
|
||||||
|
await finalizeRemoval(sig);
|
||||||
|
}, finalDuration);
|
||||||
|
|
||||||
|
setPendingMap(prev => ({
|
||||||
|
...prev,
|
||||||
|
[sig.eve_id]: {
|
||||||
|
finalUntil: now + finalDuration,
|
||||||
|
finalTimeoutId,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
import { SystemSignature, SignatureKind, SignatureGroup } from '@/hooks/Mapper/types';
|
||||||
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants';
|
||||||
import { getState } from './getState.ts';
|
import { getState } from './getState';
|
||||||
|
|
||||||
export const getActualSigs = (
|
export const getActualSigs = (
|
||||||
oldSignatures: SystemSignature[],
|
oldSignatures: SystemSignature[],
|
||||||
@@ -10,16 +10,68 @@ export const getActualSigs = (
|
|||||||
): { added: SystemSignature[]; updated: SystemSignature[]; removed: SystemSignature[] } => {
|
): { added: SystemSignature[]; updated: SystemSignature[]; removed: SystemSignature[] } => {
|
||||||
const updated: SystemSignature[] = [];
|
const updated: SystemSignature[] = [];
|
||||||
const removed: SystemSignature[] = [];
|
const removed: SystemSignature[] = [];
|
||||||
|
const added: SystemSignature[] = [];
|
||||||
|
const mergedNewIds = new Set<string>();
|
||||||
|
|
||||||
oldSignatures.forEach(oldSig => {
|
oldSignatures.forEach(oldSig => {
|
||||||
// if old sigs is not contains in newSigs we need mark it as removed
|
let newSig: SystemSignature | undefined;
|
||||||
// otherwise we check
|
if (
|
||||||
const newSig = newSignatures.find(s => s.eve_id === oldSig.eve_id);
|
oldSig.kind === SignatureKind.CosmicSignature &&
|
||||||
|
oldSig.group === SignatureGroup.Wormhole &&
|
||||||
|
oldSig.eve_id.length !== 7
|
||||||
|
) {
|
||||||
|
newSig = newSignatures.find(
|
||||||
|
s =>
|
||||||
|
s.kind === SignatureKind.CosmicSignature &&
|
||||||
|
s.group === SignatureGroup.Wormhole &&
|
||||||
|
s.eve_id.toUpperCase().startsWith(oldSig.eve_id.toUpperCase() + '-'),
|
||||||
|
);
|
||||||
if (newSig) {
|
if (newSig) {
|
||||||
// we take new sig and now we need check that sig has been updated
|
const mergedSig: SystemSignature = { ...newSig, kind: oldSig.kind, name: oldSig.name };
|
||||||
const isNeedUpgrade = getState(GROUPS_LIST, newSig) > getState(GROUPS_LIST, oldSig);
|
added.push(mergedSig);
|
||||||
if (isNeedUpgrade) {
|
removed.push(oldSig);
|
||||||
updated.push({ ...oldSig, group: newSig.group, name: newSig.name });
|
mergedNewIds.add(newSig.eve_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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 };
|
||||||
|
let changed = false;
|
||||||
|
if (needUpgrade) {
|
||||||
|
mergedSig.group = newSig.group;
|
||||||
|
mergedSig.name = newSig.name;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (newSig.description && newSig.description !== oldSig.description) {
|
||||||
|
mergedSig.description = newSig.description;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const oldInfo = JSON.parse(oldSig.custom_info || '{}');
|
||||||
|
const newInfo = JSON.parse(newSig.custom_info || '{}');
|
||||||
|
let infoChanged = false;
|
||||||
|
for (const key in newInfo) {
|
||||||
|
if (oldInfo[key] !== newInfo[key]) {
|
||||||
|
oldInfo[key] = newInfo[key];
|
||||||
|
infoChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (infoChanged) {
|
||||||
|
mergedSig.custom_info = JSON.stringify(oldInfo);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`getActualSigs: Error merging custom_info for ${oldSig.eve_id}`, e);
|
||||||
|
}
|
||||||
|
if (newSig.updated_at !== oldSig.updated_at) {
|
||||||
|
mergedSig.updated_at = newSig.updated_at;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
updated.push(mergedSig);
|
||||||
} else if (!skipUpdateUntouched) {
|
} else if (!skipUpdateUntouched) {
|
||||||
updated.push({ ...oldSig });
|
updated.push({ ...oldSig });
|
||||||
}
|
}
|
||||||
@@ -30,8 +82,11 @@ export const getActualSigs = (
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const oldSignaturesIds = oldSignatures.map(x => x.eve_id);
|
const oldIds = new Set(oldSignatures.map(x => x.eve_id));
|
||||||
const added = newSignatures.filter(s => !oldSignaturesIds.includes(s.eve_id));
|
newSignatures.forEach(s => {
|
||||||
|
if (!oldIds.has(s.eve_id) && !mergedNewIds.has(s.eve_id)) {
|
||||||
|
added.push(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
return { added, updated, removed };
|
return { added, updated, removed };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import {
|
|||||||
TIME_TEN_MINUTES,
|
TIME_TEN_MINUTES,
|
||||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||||
|
|
||||||
export const getRowColorByTimeLeft = (date: Date | undefined) => {
|
export const getRowBackgroundColor = (date: Date | undefined): string => {
|
||||||
if (!date) {
|
if (!date) {
|
||||||
return null;
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
@@ -18,4 +18,6 @@ export const getRowColorByTimeLeft = (date: Date | undefined) => {
|
|||||||
if (diff < TIME_TEN_MINUTES) {
|
if (diff < TIME_TEN_MINUTES) {
|
||||||
return 'bg-lime-700/40 transition hover:bg-lime-700/50';
|
return 'bg-lime-700/40 transition hover:bg-lime-700/50';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
};
|
};
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||||
|
|
||||||
// also we need detect changes, we need understand that sigs has states
|
|
||||||
// first state when kind is Cosmic Signature or Cosmic Anomaly and group is empty
|
|
||||||
// and we should detect it for ungrade sigs
|
|
||||||
export const getState = (_: string[], newSig: SystemSignature) => {
|
export const getState = (_: string[], newSig: SystemSignature) => {
|
||||||
let state = -1;
|
let state = -1;
|
||||||
if (!newSig.group || newSig.group === '') {
|
if (!newSig.group) {
|
||||||
state = 0;
|
state = 0;
|
||||||
} else if (!newSig.name || newSig.name === '') {
|
} else if (!newSig.name || newSig.name === '') {
|
||||||
state = 1;
|
state = 1;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
export * from './getState';
|
export * from './getState';
|
||||||
export * from './getRowColorByTimeLeft';
|
export * from './getRowBackgroundColor';
|
||||||
export * from './getActualSigs';
|
export * from './getActualSigs';
|
||||||
|
export * from './contentHelpers';
|
||||||
|
export * from './rowStyles';
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
.pendingDeletion {
|
||||||
|
background-color: #f87171;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.pendingDeletion td {
|
||||||
|
background-color: #f87171;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pendingDeletion {
|
||||||
|
background-color: #f87171;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Table thead tr {
|
||||||
|
font-size: 12px !important;
|
||||||
|
line-height: 1.333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TableRowCompact {
|
||||||
|
font-size: 12px !important;
|
||||||
|
line-height: 1.333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Table td {
|
||||||
|
padding: 2px;
|
||||||
|
height: 25px;
|
||||||
|
border: 1px solid #383838;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import clsx from 'clsx';
|
||||||
|
import { ExtendedSystemSignature } from './contentHelpers';
|
||||||
|
import { getRowBackgroundColor } from './getRowBackgroundColor';
|
||||||
|
import classes from './rowStyles.module.scss';
|
||||||
|
|
||||||
|
export function getSignatureRowClass(
|
||||||
|
row: ExtendedSystemSignature,
|
||||||
|
selectedSignatures: ExtendedSystemSignature[],
|
||||||
|
): string {
|
||||||
|
const isSelected = selectedSignatures.some(s => s.eve_id === row.eve_id);
|
||||||
|
|
||||||
|
return clsx(
|
||||||
|
classes.TableRowCompact,
|
||||||
|
'p-selectable-row',
|
||||||
|
isSelected && 'bg-amber-500/50 hover:bg-amber-500/70 transition duration-200',
|
||||||
|
!isSelected && row.pendingDeletion && classes.pendingDeletion,
|
||||||
|
!isSelected && getRowBackgroundColor(row.inserted_at ? new Date(row.inserted_at) : undefined),
|
||||||
|
'hover:bg-purple-400/20 transition duration-200',
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// types.ts
|
||||||
|
import { ExtendedSystemSignature } from '../helpers/contentHelpers';
|
||||||
|
import { OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers'; // or your function type
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The aggregator’s props
|
||||||
|
*/
|
||||||
|
export interface UseSystemSignaturesDataProps {
|
||||||
|
systemId: string;
|
||||||
|
settings: { key: string; value: boolean }[];
|
||||||
|
hideLinkedSignatures?: boolean;
|
||||||
|
onCountChange: (count: number) => void;
|
||||||
|
onPendingChange?: (pending: ExtendedSystemSignature[], undo: () => void) => void;
|
||||||
|
onLazyDeleteChange?: (value: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimal fetch logic
|
||||||
|
*/
|
||||||
|
export interface UseFetchingParams {
|
||||||
|
systemId: string;
|
||||||
|
signaturesRef: React.MutableRefObject<ExtendedSystemSignature[]>;
|
||||||
|
setSignatures: React.Dispatch<React.SetStateAction<ExtendedSystemSignature[]>>;
|
||||||
|
outCommand: OutCommandHandler;
|
||||||
|
localPendingDeletions: ExtendedSystemSignature[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For the deletion sub-hook
|
||||||
|
*/
|
||||||
|
export interface UsePendingDeletionParams {
|
||||||
|
systemId: string;
|
||||||
|
outCommand: OutCommandHandler;
|
||||||
|
setSignatures: React.Dispatch<React.SetStateAction<ExtendedSystemSignature[]>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For the additions sub-hook
|
||||||
|
*/
|
||||||
|
export interface UsePendingAdditionParams {
|
||||||
|
setSignatures: React.Dispatch<React.SetStateAction<ExtendedSystemSignature[]>>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { useState, useCallback, useRef } from 'react';
|
||||||
|
import { ExtendedSystemSignature, schedulePendingAdditionForSig } from '../helpers/contentHelpers';
|
||||||
|
import { UsePendingAdditionParams } from './types';
|
||||||
|
import { FINAL_DURATION_MS } from '../constants';
|
||||||
|
|
||||||
|
export function usePendingAdditions({ setSignatures }: UsePendingAdditionParams) {
|
||||||
|
const [pendingUndoAdditions, setPendingUndoAdditions] = useState<ExtendedSystemSignature[]>([]);
|
||||||
|
|
||||||
|
const pendingAdditionMapRef = useRef<Record<string, { finalUntil: number; finalTimeoutId: number }>>({});
|
||||||
|
|
||||||
|
const processAddedSignatures = useCallback(
|
||||||
|
(added: ExtendedSystemSignature[]) => {
|
||||||
|
if (!added.length) return;
|
||||||
|
added.forEach(sig => {
|
||||||
|
schedulePendingAdditionForSig(
|
||||||
|
sig,
|
||||||
|
FINAL_DURATION_MS,
|
||||||
|
setSignatures,
|
||||||
|
pendingAdditionMapRef,
|
||||||
|
setPendingUndoAdditions,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setSignatures],
|
||||||
|
);
|
||||||
|
|
||||||
|
const clearPendingAdditions = useCallback(() => {
|
||||||
|
Object.values(pendingAdditionMapRef.current).forEach(({ finalTimeoutId }) => {
|
||||||
|
clearTimeout(finalTimeoutId);
|
||||||
|
});
|
||||||
|
pendingAdditionMapRef.current = {};
|
||||||
|
|
||||||
|
setSignatures(prev =>
|
||||||
|
prev.map(x => (x.pendingAddition ? { ...x, pendingAddition: false, pendingUntil: undefined } : x)),
|
||||||
|
);
|
||||||
|
setPendingUndoAdditions([]);
|
||||||
|
}, [setSignatures]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pendingUndoAdditions,
|
||||||
|
setPendingUndoAdditions,
|
||||||
|
pendingAdditionMapRef,
|
||||||
|
processAddedSignatures,
|
||||||
|
clearPendingAdditions,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||||
|
import { ExtendedSystemSignature, prepareUpdatePayload, scheduleLazyDeletionTimers } from '../helpers';
|
||||||
|
import { UsePendingDeletionParams } from './types';
|
||||||
|
import { FINAL_DURATION_MS } from '../constants';
|
||||||
|
|
||||||
|
export function usePendingDeletions({ systemId, outCommand, setSignatures }: UsePendingDeletionParams) {
|
||||||
|
const [localPendingDeletions, setLocalPendingDeletions] = useState<ExtendedSystemSignature[]>([]);
|
||||||
|
const [pendingDeletionMap, setPendingDeletionMap] = useState<
|
||||||
|
Record<string, { finalUntil: number; finalTimeoutId: number }>
|
||||||
|
>({});
|
||||||
|
|
||||||
|
const processRemovedSignatures = useCallback(
|
||||||
|
async (
|
||||||
|
removed: ExtendedSystemSignature[],
|
||||||
|
added: ExtendedSystemSignature[],
|
||||||
|
updated: ExtendedSystemSignature[],
|
||||||
|
) => {
|
||||||
|
if (!removed.length) return;
|
||||||
|
const processedRemoved = removed.map(r => ({ ...r, pendingDeletion: true, pendingAddition: false }));
|
||||||
|
setLocalPendingDeletions(prev => [...prev, ...processedRemoved]);
|
||||||
|
|
||||||
|
const resp = await outCommand({
|
||||||
|
type: OutCommand.updateSignatures,
|
||||||
|
data: prepareUpdatePayload(systemId, added, updated, []),
|
||||||
|
});
|
||||||
|
const updatedFromServer = resp.signatures as ExtendedSystemSignature[];
|
||||||
|
|
||||||
|
scheduleLazyDeletionTimers(
|
||||||
|
processedRemoved,
|
||||||
|
setPendingDeletionMap,
|
||||||
|
async sig => {
|
||||||
|
await outCommand({
|
||||||
|
type: OutCommand.updateSignatures,
|
||||||
|
data: prepareUpdatePayload(systemId, [], [], [sig]),
|
||||||
|
});
|
||||||
|
setLocalPendingDeletions(prev => prev.filter(x => x.eve_id !== sig.eve_id));
|
||||||
|
setSignatures(prev => prev.filter(x => x.eve_id !== sig.eve_id));
|
||||||
|
},
|
||||||
|
FINAL_DURATION_MS,
|
||||||
|
);
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const updatedWithRemoval = updatedFromServer.map(sig => {
|
||||||
|
const wasRemoved = processedRemoved.find(r => r.eve_id === sig.eve_id);
|
||||||
|
return wasRemoved ? { ...sig, pendingDeletion: true, pendingUntil: now + FINAL_DURATION_MS } : sig;
|
||||||
|
});
|
||||||
|
|
||||||
|
const extras = processedRemoved
|
||||||
|
.map(r => ({ ...r, pendingDeletion: true, pendingUntil: now + FINAL_DURATION_MS }))
|
||||||
|
.filter(r => !updatedWithRemoval.some(m => m.eve_id === r.eve_id));
|
||||||
|
|
||||||
|
setSignatures([...updatedWithRemoval, ...extras]);
|
||||||
|
},
|
||||||
|
[systemId, outCommand, setSignatures],
|
||||||
|
);
|
||||||
|
|
||||||
|
const clearPendingDeletions = useCallback(() => {
|
||||||
|
Object.values(pendingDeletionMap).forEach(({ finalTimeoutId }) => clearTimeout(finalTimeoutId));
|
||||||
|
setPendingDeletionMap({});
|
||||||
|
|
||||||
|
setSignatures(prev =>
|
||||||
|
prev.map(x => (x.pendingDeletion ? { ...x, pendingDeletion: false, pendingUntil: undefined } : x)),
|
||||||
|
);
|
||||||
|
setLocalPendingDeletions([]);
|
||||||
|
}, [pendingDeletionMap, setSignatures]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
localPendingDeletions,
|
||||||
|
setLocalPendingDeletions,
|
||||||
|
pendingDeletionMap,
|
||||||
|
setPendingDeletionMap,
|
||||||
|
|
||||||
|
processRemovedSignatures,
|
||||||
|
clearPendingDeletions,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||||
|
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||||
|
import { ExtendedSystemSignature, prepareUpdatePayload, getActualSigs } from '../helpers';
|
||||||
|
import { UseFetchingParams } from './types';
|
||||||
|
|
||||||
|
export function useSignatureFetching({
|
||||||
|
systemId,
|
||||||
|
signaturesRef,
|
||||||
|
setSignatures,
|
||||||
|
outCommand,
|
||||||
|
localPendingDeletions,
|
||||||
|
}: UseFetchingParams) {
|
||||||
|
const handleGetSignatures = useCallback(async () => {
|
||||||
|
if (!systemId) {
|
||||||
|
setSignatures([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (localPendingDeletions.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const resp = await outCommand({
|
||||||
|
type: OutCommand.getSignatures,
|
||||||
|
data: { system_id: systemId },
|
||||||
|
});
|
||||||
|
const serverSigs = (resp.signatures ?? []) as SystemSignature[];
|
||||||
|
const extended = serverSigs.map(x => ({ ...x })) as ExtendedSystemSignature[];
|
||||||
|
setSignatures(extended);
|
||||||
|
}, [systemId, localPendingDeletions, outCommand, setSignatures]);
|
||||||
|
|
||||||
|
const handleUpdateSignatures = useCallback(
|
||||||
|
async (newList: ExtendedSystemSignature[], updateOnly: boolean, skipUpdateUntouched?: boolean) => {
|
||||||
|
const { added, updated, removed } = getActualSigs(
|
||||||
|
signaturesRef.current,
|
||||||
|
newList,
|
||||||
|
updateOnly,
|
||||||
|
skipUpdateUntouched,
|
||||||
|
);
|
||||||
|
|
||||||
|
const resp = await outCommand({
|
||||||
|
type: OutCommand.updateSignatures,
|
||||||
|
data: prepareUpdatePayload(systemId, added, updated, removed),
|
||||||
|
});
|
||||||
|
const final = (resp.signatures ?? []) as SystemSignature[];
|
||||||
|
setSignatures(final.map(x => ({ ...x })) as ExtendedSystemSignature[]);
|
||||||
|
},
|
||||||
|
[systemId, signaturesRef, outCommand, setSignatures],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleGetSignatures,
|
||||||
|
handleUpdateSignatures,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import useRefState from 'react-usestateref';
|
||||||
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||||
|
import { Commands, SystemSignature } from '@/hooks/Mapper/types';
|
||||||
|
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||||
|
import { parseSignatures } from '@/hooks/Mapper/helpers';
|
||||||
|
import {
|
||||||
|
KEEP_LAZY_DELETE_SETTING,
|
||||||
|
LAZY_DELETE_SIGNATURES_SETTING,
|
||||||
|
} from '@/hooks/Mapper/components/mapInterface/widgets';
|
||||||
|
import { ExtendedSystemSignature, getActualSigs } from '../helpers';
|
||||||
|
import { useSignatureFetching } from './useSignatureFetching';
|
||||||
|
import { usePendingAdditions } from './usePendingAdditions';
|
||||||
|
import { usePendingDeletions } from './usePendingDeletions';
|
||||||
|
import { UseSystemSignaturesDataProps } from './types';
|
||||||
|
import { TIME_ONE_DAY, TIME_ONE_WEEK } from '../constants';
|
||||||
|
import { SignatureGroup } from '@/hooks/Mapper/types';
|
||||||
|
|
||||||
|
export function useSystemSignaturesData({
|
||||||
|
systemId,
|
||||||
|
settings,
|
||||||
|
onCountChange,
|
||||||
|
onPendingChange,
|
||||||
|
onLazyDeleteChange,
|
||||||
|
}: UseSystemSignaturesDataProps) {
|
||||||
|
const { outCommand } = useMapRootState();
|
||||||
|
|
||||||
|
const [signatures, setSignatures, signaturesRef] = useRefState<ExtendedSystemSignature[]>([]);
|
||||||
|
|
||||||
|
const [selectedSignatures, setSelectedSignatures] = useState<ExtendedSystemSignature[]>([]);
|
||||||
|
|
||||||
|
const { localPendingDeletions, setLocalPendingDeletions, processRemovedSignatures, clearPendingDeletions } =
|
||||||
|
usePendingDeletions({
|
||||||
|
systemId,
|
||||||
|
outCommand,
|
||||||
|
setSignatures,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { pendingUndoAdditions, setPendingUndoAdditions, processAddedSignatures, clearPendingAdditions } =
|
||||||
|
usePendingAdditions({
|
||||||
|
setSignatures,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { handleGetSignatures, handleUpdateSignatures } = useSignatureFetching({
|
||||||
|
systemId,
|
||||||
|
signaturesRef,
|
||||||
|
setSignatures,
|
||||||
|
outCommand,
|
||||||
|
localPendingDeletions,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handlePaste = useCallback(
|
||||||
|
async (clipboardString: string) => {
|
||||||
|
const lazyDeleteValue = settings.find(s => s.key === LAZY_DELETE_SIGNATURES_SETTING)?.value ?? false;
|
||||||
|
|
||||||
|
const incomingSignatures = parseSignatures(
|
||||||
|
clipboardString,
|
||||||
|
settings.map(s => s.key),
|
||||||
|
) as ExtendedSystemSignature[];
|
||||||
|
|
||||||
|
const current = signaturesRef.current;
|
||||||
|
const currentNonPending = lazyDeleteValue
|
||||||
|
? current.filter(sig => !sig.pendingDeletion)
|
||||||
|
: current.filter(sig => !sig.pendingDeletion && !sig.pendingAddition);
|
||||||
|
|
||||||
|
const { added, updated, removed } = getActualSigs(currentNonPending, incomingSignatures, !lazyDeleteValue, true);
|
||||||
|
|
||||||
|
if (added.length > 0) {
|
||||||
|
processAddedSignatures(added);
|
||||||
|
}
|
||||||
|
if (removed.length > 0) {
|
||||||
|
await processRemovedSignatures(removed, added, updated);
|
||||||
|
} else {
|
||||||
|
const resp = await outCommand({
|
||||||
|
type: OutCommand.updateSignatures,
|
||||||
|
data: {
|
||||||
|
system_id: systemId,
|
||||||
|
added,
|
||||||
|
updated,
|
||||||
|
removed: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const finalSigs = (resp.signatures ?? []) as SystemSignature[];
|
||||||
|
setSignatures(finalSigs.map(x => ({ ...x })));
|
||||||
|
}
|
||||||
|
|
||||||
|
const keepLazy = settings.find(s => s.key === KEEP_LAZY_DELETE_SETTING)?.value ?? false;
|
||||||
|
if (lazyDeleteValue && !keepLazy) {
|
||||||
|
onLazyDeleteChange?.(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
settings,
|
||||||
|
signaturesRef,
|
||||||
|
processAddedSignatures,
|
||||||
|
processRemovedSignatures,
|
||||||
|
outCommand,
|
||||||
|
systemId,
|
||||||
|
setSignatures,
|
||||||
|
onLazyDeleteChange,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
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]);
|
||||||
|
|
||||||
|
const handleSelectAll = useCallback(() => {
|
||||||
|
setSelectedSignatures(signatures);
|
||||||
|
}, [signatures]);
|
||||||
|
|
||||||
|
const undoPending = useCallback(() => {
|
||||||
|
clearPendingDeletions();
|
||||||
|
clearPendingAdditions();
|
||||||
|
|
||||||
|
setSignatures(prev =>
|
||||||
|
prev.map(x => {
|
||||||
|
if (x.pendingDeletion) {
|
||||||
|
return { ...x, pendingDeletion: false, pendingUntil: undefined };
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pendingUndoAdditions.length) {
|
||||||
|
pendingUndoAdditions.forEach(async sig => {
|
||||||
|
await outCommand({
|
||||||
|
type: OutCommand.updateSignatures,
|
||||||
|
data: {
|
||||||
|
system_id: systemId,
|
||||||
|
added: [],
|
||||||
|
updated: [],
|
||||||
|
removed: [sig],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setSignatures(prev => prev.filter(x => !pendingUndoAdditions.some(u => u.eve_id === x.eve_id)));
|
||||||
|
setPendingUndoAdditions([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setLocalPendingDeletions([]);
|
||||||
|
}, [
|
||||||
|
clearPendingDeletions,
|
||||||
|
clearPendingAdditions,
|
||||||
|
pendingUndoAdditions,
|
||||||
|
setPendingUndoAdditions,
|
||||||
|
setLocalPendingDeletions,
|
||||||
|
setSignatures,
|
||||||
|
outCommand,
|
||||||
|
systemId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const combined = [...localPendingDeletions, ...pendingUndoAdditions];
|
||||||
|
onPendingChange?.(combined, undoPending);
|
||||||
|
}, [localPendingDeletions, pendingUndoAdditions, onPendingChange, undoPending]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!systemId) return;
|
||||||
|
const now = Date.now();
|
||||||
|
const oldOnes = signaturesRef.current.filter(sig => {
|
||||||
|
if (!sig.inserted_at) return false;
|
||||||
|
const inserted = new Date(sig.inserted_at).getTime();
|
||||||
|
const threshold = sig.group === SignatureGroup.Wormhole ? TIME_ONE_DAY : TIME_ONE_WEEK;
|
||||||
|
return now - inserted > threshold;
|
||||||
|
});
|
||||||
|
if (oldOnes.length) {
|
||||||
|
const remain = signaturesRef.current.filter(x => !oldOnes.includes(x));
|
||||||
|
handleUpdateSignatures(remain, false, true);
|
||||||
|
}
|
||||||
|
}, [systemId, handleUpdateSignatures, signaturesRef]);
|
||||||
|
|
||||||
|
useMapEventListener(event => {
|
||||||
|
if (event.name === Commands.signaturesUpdated && String(event.data) === String(systemId)) {
|
||||||
|
handleGetSignatures();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!systemId) {
|
||||||
|
setSignatures([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleGetSignatures();
|
||||||
|
}, [systemId, handleGetSignatures, setSignatures]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onCountChange(signatures.length);
|
||||||
|
}, [signatures, onCountChange]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
signatures,
|
||||||
|
selectedSignatures,
|
||||||
|
setSelectedSignatures,
|
||||||
|
handleDeleteSelected,
|
||||||
|
handleSelectAll,
|
||||||
|
handlePaste,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -24,6 +24,12 @@ export const renderInfoColumn = (row: SystemSignature) => {
|
|||||||
</WdTooltipWrapper>
|
</WdTooltipWrapper>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{customInfo.isCrit && (
|
||||||
|
<WdTooltipWrapper offset={5} position={TooltipPosition.top} content="Signature marked as Crit">
|
||||||
|
<div className="pi pi-clock text-fuchsia-400 text-[11px] mr-[2px]"></div>
|
||||||
|
</WdTooltipWrapper>
|
||||||
|
)}
|
||||||
|
|
||||||
{row.type && (
|
{row.type && (
|
||||||
<WHClassView
|
<WHClassView
|
||||||
className="text-[11px]"
|
className="text-[11px]"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export interface MapSettingsProps {
|
|||||||
systemId: string;
|
systemId: string;
|
||||||
show: boolean;
|
show: boolean;
|
||||||
onHide: () => void;
|
onHide: () => void;
|
||||||
signatureData: SystemSignature | null;
|
signatureData: SystemSignature | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SignatureSettings = ({ systemId, show, onHide, signatureData }: MapSettingsProps) => {
|
export const SignatureSettings = ({ systemId, show, onHide, signatureData }: MapSettingsProps) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user