Initial commit

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

View File

@@ -0,0 +1,15 @@
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { renderIcon } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
export interface SignatureViewProps {}
export const SignatureView = (sig: SignatureViewProps & SystemSignature) => {
return (
<div className="flex gap-2 items-center">
{renderIcon(sig)}
<div>{sig?.eve_id}</div>
<div>{sig?.group ?? SignatureGroup.CosmicSignature}</div>
<div>{sig?.name}</div>
</div>
);
};

View File

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

View File

@@ -0,0 +1,55 @@
import { Dialog } from 'primereact/dialog';
import { useCallback, useState } from 'react';
import { Button } from 'primereact/button';
import { Checkbox } from 'primereact/checkbox';
export type Setting = { key: string; name: string; value: boolean };
interface SystemSignatureSettingsDialogProps {
settings: Setting[];
onSave: (settings: Setting[]) => void;
onCancel: () => void;
}
export const SystemSignatureSettingsDialog = ({
settings: defaultSettings,
onSave,
onCancel,
}: SystemSignatureSettingsDialogProps) => {
const [settings, setSettings] = useState<Setting[]>(defaultSettings);
const handleSettingsChange = (key: string) => {
setSettings(prevState => prevState.map(item => (item.key === key ? { ...item, value: !item.value } : item)));
};
const handleSave = useCallback(() => {
onSave(settings);
}, [onSave, settings]);
return (
<Dialog header="Filter signatures" visible draggable={false} style={{ width: '300px' }} onHide={onCancel}>
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-2">
{settings.map(setting => {
return (
<div key={setting.key} className="flex items-center">
<Checkbox
inputId={setting.key}
checked={setting.value}
onChange={() => handleSettingsChange(setting.key)}
/>
<label htmlFor={setting.key} className="ml-2">
{setting.name}
</label>
</div>
);
})}
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
</div>
</div>
</Dialog>
);
};

View File

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

View File

@@ -0,0 +1,120 @@
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
import { InfoDrawer, LayoutEventBlocker, TooltipPosition, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { SystemSignaturesContent } from './SystemSignaturesContent';
import { Setting, SystemSignatureSettingsDialog } from './SystemSignatureSettingsDialog';
import React, { useCallback, useEffect, useState } from 'react';
import { PrimeIcons } from 'primereact/api';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
export const COSMIC_SIGNATURE = 'Cosmic Signature';
export const COSMIC_ANOMALY = 'Cosmic Anomaly';
export const DEPLOYABLE = 'Deployable';
export const STRUCTURE = 'Structure';
export const STARBASE = 'Starbase';
export const SHIP = 'Ship';
export const DRONE = 'Drone';
const settings: Setting[] = [
{ key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true },
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true },
{ key: DEPLOYABLE, name: 'Show Deployables', value: true },
{ key: STRUCTURE, name: 'Show Structures', value: true },
{ key: STARBASE, name: 'Show Starbase', value: true },
{ key: SHIP, name: 'Show Ships', value: true },
{ key: DRONE, name: 'Show Drones And Charges', value: true },
];
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings';
const defaultSettings = () => {
return [...settings];
};
export const SystemSignatures = () => {
const {
data: { selectedSystems },
} = useMapRootState();
const [visible, setVisible] = useState(false);
const [settings, setSettings] = useState<Setting[]>(defaultSettings);
const [systemId] = selectedSystems;
const isNotSelectedSystem = selectedSystems.length !== 1;
const handleSettingsChange = useCallback((settings: Setting[]) => {
setSettings(settings);
localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(settings));
setVisible(false);
}, []);
useEffect(() => {
const restoredSettings = localStorage.getItem(SIGNATURE_SETTINGS_KEY);
if (restoredSettings) {
setSettings(JSON.parse(restoredSettings));
}
}, []);
return (
<Widget
label={
<div className="flex justify-between items-center text-xs w-full h-full">
<div className="flex gap-1">System Signatures</div>
<LayoutEventBlocker className="flex gap-2.5">
<WdImgButton
className={PrimeIcons.QUESTION_CIRCLE}
tooltip={{
position: TooltipPosition.left,
// @ts-ignore
content: (
<div className="flex flex-col gap-1">
<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{' '}
<b className="text-sky-500">Probe scanner</b>. <br /> Use next hotkeys:
<br />
<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 />
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>
</InfoDrawer>
<InfoDrawer title={<b className="text-slate-50">How to select?</b>}>
For select any signature need click on that, <br /> with hotkeys{' '}
<b className="text-sky-500">Shift + LMB</b> or <b className="text-sky-500">Ctrl + LMB</b>
</InfoDrawer>
<InfoDrawer title={<b className="text-slate-50">How to delete?</b>}>
For delete any signature first of all you need select before
<br /> and then use <b className="text-sky-500">Del</b> or{' '}
<b className="text-sky-500">Backspace</b>
</InfoDrawer>
</div>
) as React.ReactNode,
}}
/>
<WdImgButton className={PrimeIcons.SLIDERS_H} onClick={() => setVisible(true)} />
</LayoutEventBlocker>
</div>
}
>
{isNotSelectedSystem ? (
<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>
) : (
<SystemSignaturesContent systemId={systemId} settings={settings} />
)}
{visible && (
<SystemSignatureSettingsDialog
settings={settings}
onCancel={() => setVisible(false)}
onSave={handleSettingsChange}
/>
)}
</Widget>
);
};

View File

@@ -0,0 +1,6 @@
.TableRowCompact {
height: 8px;
max-height: 8px;
font-size: 12px !important;
line-height: 8px;
}

View File

@@ -0,0 +1,241 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useClipboard } from '@/hooks/Mapper/hooks/useClipboard';
import { parseSignatures } from '@/hooks/Mapper/helpers';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit';
import { DataTable, DataTableRowMouseEvent } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import useRefState from 'react-usestateref';
import { Setting } from '../SystemSignatureSettingsDialog';
import { useHotkey } from '@/hooks/Mapper/hooks';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
import classes from './SystemSignaturesContent.module.scss';
import clsx from 'clsx';
import { SystemSignature } from '@/hooks/Mapper/types';
import { SignatureView } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SignatureView';
import {
getActualSigs,
getRowColorByTimeLeft,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers';
import {
renderIcon,
renderName,
renderTimeLeft,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
interface SystemSignaturesContentProps {
systemId: string;
settings: Setting[];
}
export const SystemSignaturesContent = ({ systemId, settings }: SystemSignaturesContentProps) => {
const { outCommand } = useMapRootState();
const [signatures, setSignatures, signaturesRef] = useRefState<SystemSignature[]>([]);
const [selectedSignatures, setSelectedSignatures] = useState<SystemSignature[]>([]);
const [nameColumnWidth, setNameColumnWidth] = useState('auto');
const [hoveredSig, setHoveredSig] = useState<SystemSignature | null>(null);
const tableRef = useRef<HTMLDivElement>(null);
const compact = useMaxWidth(tableRef, 260);
const medium = useMaxWidth(tableRef, 380);
const tooltipRef = useRef<WdTooltipHandlers>(null);
const { clipboardContent } = useClipboard();
const handleResize = useCallback(() => {
if (tableRef.current) {
const tableWidth = tableRef.current.offsetWidth;
const otherColumnsWidth = 265;
const availableWidth = tableWidth - otherColumnsWidth;
setNameColumnWidth(`${availableWidth}px`);
}
}, []);
const filteredSignatures = useMemo(() => {
return signatures
.filter(x => 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]);
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[]) => {
const { added, updated, removed } = getActualSigs(signaturesRef.current, newSignatures);
const { signatures: updatedSignatures } = await outCommand({
type: OutCommand.updateSignatures,
data: {
system_id: systemId,
added,
updated,
removed,
},
});
setSignatures(() => updatedSignatures);
setSelectedSignatures([]);
},
[outCommand, systemId],
);
const handleDeleteSelected = useCallback(async () => {
if (selectedSignatures.length === 0) {
return;
}
const selectedSignaturesEveIds = selectedSignatures.map(x => x.eve_id);
await handleUpdateSignatures(signatures.filter(x => !selectedSignaturesEveIds.includes(x.eve_id)));
}, [handleUpdateSignatures, signatures, selectedSignatures]);
const handleSelectAll = useCallback(() => {
setSelectedSignatures(signatures);
}, [signatures]);
useHotkey(true, ['a'], handleSelectAll);
useHotkey(false, ['Backspace', 'Delete'], handleDeleteSelected);
useEffect(() => {
if (!clipboardContent) {
return;
}
const signatures = parseSignatures(
clipboardContent,
settings.map(x => x.key),
);
handleUpdateSignatures(signatures);
}, [clipboardContent]);
useEffect(() => {
if (!systemId) {
setSignatures([]);
return;
}
handleGetSignatures();
}, [systemId]);
useEffect(() => {
const observer = new ResizeObserver(handleResize);
if (tableRef.current) {
observer.observe(tableRef.current);
}
handleResize(); // Call on mount to set initial width
return () => {
if (tableRef.current) {
observer.unobserve(tableRef.current);
}
};
}, []);
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);
}, []);
return (
<div ref={tableRef} className="h-full">
{filteredSignatures.length === 0 ? (
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">
No signatures
</div>
) : (
<>
<DataTable
value={filteredSignatures}
size="small"
selectionMode="multiple"
selection={selectedSignatures}
onSelectionChange={e => setSelectedSignatures(e.value)}
dataKey="eve_id"
tableClassName="w-full select-none"
resizableColumns
rowHover
selectAll
showHeaders={false}
onRowMouseEnter={handleEnterRow}
onRowMouseLeave={handleLeaveRow}
rowClassName={row => {
if (selectedSignatures.some(x => x.eve_id === row.eve_id)) {
return clsx(classes.TableRowCompact, 'bg-amber-500/50 hover:bg-amber-500/70 transition duration-200');
}
const dateClass = getRowColorByTimeLeft(row.updated_at ? new Date(row.updated_at) : undefined);
if (!dateClass) {
return clsx(classes.TableRowCompact, 'hover:bg-purple-400/20 transition duration-200');
}
return clsx(classes.TableRowCompact, dateClass);
}}
>
<Column
bodyClassName="p-0 px-1"
field="group"
body={renderIcon}
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
></Column>
<Column
field="eve_id"
header="Id"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
style={{ maxWidth: 72, minWidth: 72, width: 72 }}
></Column>
<Column
field="group"
header="Group"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
hidden={compact}
></Column>
<Column
field="name"
header="Name"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
body={renderName}
style={{ maxWidth: nameColumnWidth }}
hidden={compact || medium}
></Column>
<Column
field="updated_at"
header="Updated"
dataType="date"
bodyClassName="w-[80px] text-ellipsis overflow-hidden whitespace-nowrap"
body={renderTimeLeft}
></Column>
</DataTable>
</>
)}
<WdTooltip
className="bg-stone-900/95 text-slate-50"
ref={tooltipRef}
content={hoveredSig ? <SignatureView {...hoveredSig} /> : null}
/>
</div>
);
};

View File

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

View File

@@ -0,0 +1,26 @@
import { GroupType, SignatureGroup } from '@/hooks/Mapper/types';
export const TIME_ONE_MINUTE = 1000 * 60;
export const TIME_TEN_MINUTES = 1000 * 60 * 10;
export const GROUPS_LIST = [
SignatureGroup.GasSite,
SignatureGroup.RelicSite,
SignatureGroup.DataSite,
SignatureGroup.OreSite,
SignatureGroup.CombatSite,
SignatureGroup.Wormhole,
SignatureGroup.CosmicSignature,
];
const wh = { w: 14, h: 14 };
export const GROUPS: Record<SignatureGroup, GroupType> = {
[SignatureGroup.GasSite]: { id: SignatureGroup.GasSite, icon: '/icons/brackets/harvestableCloud.png', ...wh },
[SignatureGroup.RelicSite]: { id: SignatureGroup.RelicSite, icon: '/icons/brackets/relic_Site_16.png', ...wh },
[SignatureGroup.DataSite]: { id: SignatureGroup.DataSite, icon: '/icons/brackets/data_Site_16.png', ...wh },
[SignatureGroup.OreSite]: { id: SignatureGroup.OreSite, icon: '/icons/brackets/ore_Site_16.png', ...wh },
[SignatureGroup.CombatSite]: { id: SignatureGroup.CombatSite, icon: '/icons/brackets/combatSite_16.png', ...wh },
[SignatureGroup.Wormhole]: { id: SignatureGroup.Wormhole, icon: '/icons/brackets/wormhole.png', ...wh },
[SignatureGroup.CosmicSignature]: { id: SignatureGroup.CosmicSignature, icon: '/icons/x_close14.png', w: 9, h: 9 },
};

View File

@@ -0,0 +1,31 @@
import { SystemSignature } from '@/hooks/Mapper/types';
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
import { getState } from './getState.ts';
export const getActualSigs = (
oldSignatures: SystemSignature[],
newSignatures: SystemSignature[],
): { added: SystemSignature[]; updated: SystemSignature[]; removed: SystemSignature[] } => {
const updated: SystemSignature[] = [];
const removed: SystemSignature[] = [];
oldSignatures.forEach(oldSig => {
// if old sigs is not contains in newSigs we need mark it as removed
// otherwise we check
const newSig = newSignatures.find(s => s.eve_id === oldSig.eve_id);
if (newSig) {
// we take new sig and now we need check that sig has been updated
const isNeedUpgrade = getState(GROUPS_LIST, newSig) > getState(GROUPS_LIST, oldSig);
if (isNeedUpgrade) {
updated.push({ ...oldSig, group: newSig.group, name: newSig.name });
}
} else {
removed.push(oldSig);
}
});
const oldSignaturesIds = oldSignatures.map(x => x.eve_id);
const added = newSignatures.filter(s => !oldSignaturesIds.includes(s.eve_id));
return { added, updated, removed };
};

View File

@@ -0,0 +1,21 @@
import {
TIME_ONE_MINUTE,
TIME_TEN_MINUTES,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
export const getRowColorByTimeLeft = (date: Date | undefined) => {
if (!date) {
return null;
}
const currentDate = new Date();
const diff = currentDate.getTime() + currentDate.getTimezoneOffset() * TIME_ONE_MINUTE - date.getTime();
if (diff < TIME_ONE_MINUTE) {
return 'bg-lime-600/50 transition hover:bg-lime-600/60';
}
if (diff < TIME_TEN_MINUTES) {
return 'bg-lime-700/40 transition hover:bg-lime-700/50';
}
};

View File

@@ -0,0 +1,16 @@
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) => {
let state = -1;
if (!newSig.group || newSig.group === '') {
state = 0;
} else if (!!newSig.group && newSig.group !== '' && newSig.name === '') {
state = 1;
} else if (!!newSig.group && newSig.group !== '' && newSig.name !== '') {
state = 2;
}
return state;
};

View File

@@ -0,0 +1,3 @@
export * from './getState';
export * from './getRowColorByTimeLeft';
export * from './getActualSigs';

View File

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

View File

@@ -0,0 +1,3 @@
export * from './renderIcon';
export * from './renderName';
export * from './renderTimeLeft';

View File

@@ -0,0 +1,19 @@
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { GROUPS } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
export const renderIcon = (row: SystemSignature) => {
if (row.group == null) {
return null;
}
const group = GROUPS[row.group as SignatureGroup];
if (!group) {
return null;
}
return (
<div className="flex justify-center items-center">
<img src={group.icon} style={{ width: group.w, height: group.h }} />
</div>
);
};

View File

@@ -0,0 +1,5 @@
import { SystemSignature } from '@/hooks/Mapper/types';
export const renderName = (row: SystemSignature) => {
return <span title={row.name}>{row.name}</span>;
};

View File

@@ -0,0 +1,10 @@
import { SystemSignature } from '@/hooks/Mapper/types';
import { TimeLeft } from '@/hooks/Mapper/components/ui-kit';
export const renderTimeLeft = (row: SystemSignature) => {
return (
<div className="flex justify-end w-full items-center">
<TimeLeft cDate={row.updated_at ? new Date(row.updated_at) : undefined} />
</div>
);
};