diff --git a/assets/js/hooks/Mapper/components/map/constants.ts b/assets/js/hooks/Mapper/components/map/constants.ts index f2557719..626d5cfb 100644 --- a/assets/js/hooks/Mapper/components/map/constants.ts +++ b/assets/js/hooks/Mapper/components/map/constants.ts @@ -322,6 +322,9 @@ export const WORMHOLES_ADDITIONAL_INFO: Record = WORMHOLES_ADDITIONAL_INFO_RAW.reduce((acc, x) => ({ ...acc, [x.wormholeClassID]: x }), {}); +export const WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME: Record = + WORMHOLES_ADDITIONAL_INFO_RAW.reduce((acc, x) => ({ ...acc, [x.shortName]: x }), {}); + // export const SOLAR_SYSTEM_CLASS_NAMES = { // ccp1 = , // c1 = , @@ -650,6 +653,7 @@ export enum LABELS { l3 = '3', } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export const LABELS_INFO: Record = { [LABELS.clear]: { id: 'clear', name: 'Clear', shortName: '', icon: '' }, [LABELS.la]: { id: 'la', name: 'Label A', shortName: 'A', icon: '' }, diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx index cd5f06bd..74270851 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef } from 'react'; +import { useCallback, useRef, useMemo } from 'react'; import { Dialog } from 'primereact/dialog'; import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts'; @@ -14,6 +14,15 @@ import { import { SignatureGroup } from '@/hooks/Mapper/types'; import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo'; import { getWhSize } from '@/hooks/Mapper/helpers/getWhSize'; +import { useSystemInfo } from '@/hooks/Mapper/components/hooks'; +import { + SOLAR_SYSTEM_CLASS_IDS, + SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS, + WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME, +} from '@/hooks/Mapper/components/map/constants.ts'; +import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts'; + +const K162_SIGNATURE_TYPE = WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME['K162'].shortName; interface SystemLinkSignatureDialogProps { data: CommandLinkSignatureToSystem; @@ -26,6 +35,13 @@ const signatureSettings: Setting[] = [ { key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: true, isFilter: false }, ]; +// Extend the SignatureCustomInfo type to include k162Type +interface ExtendedSignatureCustomInfo { + k162Type?: string; + isEOL?: boolean; + [key: string]: unknown; +} + export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => { const { outCommand, @@ -35,10 +51,74 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat const ref = useRef({ outCommand }); ref.current = { outCommand }; + // Get system info for the target system + const { staticInfo: targetSystemInfo } = useSystemInfo({ systemId: `${data.solar_system_target}` }); + + // Get the system class group for the target system + const targetSystemClassGroup = useMemo(() => { + if (!targetSystemInfo) return null; + const systemClassId = targetSystemInfo.system_class; + + const systemClassKey = Object.keys(SOLAR_SYSTEM_CLASS_IDS).find( + key => SOLAR_SYSTEM_CLASS_IDS[key as keyof typeof SOLAR_SYSTEM_CLASS_IDS] === systemClassId, + ); + + if (!systemClassKey) return null; + + return ( + SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS[systemClassKey as keyof typeof SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS] || null + ); + }, [targetSystemInfo]); + const handleHide = useCallback(() => { setVisible(false); }, [setVisible]); + const filterSignature = useCallback( + (signature: SystemSignature) => { + if (signature.group !== SignatureGroup.Wormhole || !targetSystemClassGroup) { + return true; + } + + if (!signature.type) { + return true; + } + + if (signature.type === K162_SIGNATURE_TYPE) { + // Parse the custom info to see if the user has specified what class this K162 leads to + const customInfo = parseSignatureCustomInfo(signature.custom_info) as ExtendedSignatureCustomInfo; + + // If the user has specified a k162Type for this K162 + if (customInfo.k162Type) { + // Get the K162 type information + const k162TypeInfo = K162_TYPES_MAP[customInfo.k162Type]; + + if (k162TypeInfo) { + // Check if the k162Type matches our target system class + return customInfo.k162Type === targetSystemClassGroup; + } + } + + // If no k162Type is specified or we couldn't find type info, allow it + return true; + } + + // Find the wormhole data for this signature type + const wormholeData = wormholes.find(wh => wh.name === signature.type); + if (!wormholeData) { + return true; // If we don't know the destination, don't filter it out + } + + // Get the destination system class from the wormhole data + const destinationClass = wormholeData.dest; + + // Check if the destination class matches the target system class + const isMatch = destinationClass === targetSystemClassGroup; + return isMatch; + }, + [targetSystemClassGroup, wormholes], + ); + const handleSelect = useCallback( async (signature: SystemSignature) => { if (!signature) { @@ -80,7 +160,7 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat setVisible(false); }, - [data, setVisible], + [data, setVisible, wormholes], ); return ( @@ -98,6 +178,7 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat settings={signatureSettings} onSelect={handleSelect} selectable={true} + filterSignature={filterSignature} /> ); diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/helpers/linkHelpers.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/helpers/linkHelpers.ts index fe745fc8..feae0ec1 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/helpers/linkHelpers.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/helpers/linkHelpers.ts @@ -1,5 +1,5 @@ const ZKILL_URL = 'https://zkillboard.com'; -const BASE_IMAGE_URL = 'https://images.evetech.net'; +import { getEveImageUrl } from '@/hooks/Mapper/helpers'; export function zkillLink(type: 'kill' | 'character' | 'corporation' | 'alliance', id?: number | null): string { if (!id) return `${ZKILL_URL}`; @@ -10,21 +10,7 @@ export function zkillLink(type: 'kill' | 'character' | 'corporation' | 'alliance return `${ZKILL_URL}`; } -export function eveImageUrl( - category: 'characters' | 'corporations' | 'alliances' | 'types', - id?: number | null, - variation: string = 'icon', - size?: number, -): string | null { - if (!id || id <= 0) { - return null; - } - let url = `${BASE_IMAGE_URL}/${category}/${id}/${variation}`; - if (size) { - url += `?size=${size}`; - } - return url; -} +export const eveImageUrl = getEveImageUrl; export function buildVictimImageUrls(args: { victim_char_id?: number | null; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SignatureView/SignatureView.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SignatureView/SignatureView.tsx index a6910813..1ee1a27a 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SignatureView/SignatureView.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SignatureView/SignatureView.tsx @@ -1,15 +1,36 @@ import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; import { renderIcon } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders'; +import { getCharacterPortraitUrl } from '@/hooks/Mapper/helpers'; -export interface SignatureViewProps {} +export interface SignatureViewProps { + signature: SystemSignature; + showCharacterPortrait?: boolean; +} + +export const SignatureView = ({ signature, showCharacterPortrait = false }: SignatureViewProps) => { + const isWormhole = signature?.group === SignatureGroup.Wormhole; + const hasCharacterInfo = showCharacterPortrait && signature.character_eve_id; + const groupDisplay = isWormhole ? SignatureGroup.Wormhole : signature?.group ?? SignatureGroup.CosmicSignature; + const characterName = signature.character_name || 'Unknown character'; -export const SignatureView = (sig: SignatureViewProps & SystemSignature) => { return ( -
- {renderIcon(sig)} -
{sig?.eve_id}
-
{sig?.group ?? SignatureGroup.CosmicSignature}
-
{sig?.name}
+
+
+ {renderIcon(signature)} +
{signature?.eve_id}
+
{groupDisplay}
+ {!isWormhole &&
{signature?.name}
} + {hasCharacterInfo && ( +
+ {characterName} +
{characterName}
+
+ )} +
); }; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureHeader/SystemSignatureHeader.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureHeader/SystemSignatureHeader.tsx index 7e940ebc..4df63ae8 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureHeader/SystemSignatureHeader.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureHeader/SystemSignatureHeader.tsx @@ -13,6 +13,7 @@ export type HeaderProps = { lazyDeleteValue: boolean; onLazyDeleteChange: (checked: boolean) => void; pendingCount: number; + pendingTimeRemaining?: number; // Time remaining in ms onUndoClick: () => void; onSettingsClick: () => void; }; @@ -25,9 +26,17 @@ function HeaderImpl({ lazyDeleteValue, onLazyDeleteChange, pendingCount, + pendingTimeRemaining, onUndoClick, onSettingsClick, }: HeaderProps) { + // Format time remaining as seconds + const formatTimeRemaining = () => { + if (!pendingTimeRemaining) return ''; + const seconds = Math.ceil(pendingTimeRemaining / 1000); + return ` (${seconds}s remaining)`; + }; + return (
@@ -55,7 +64,10 @@ function HeaderImpl({ )} diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog/SystemSignatureSettingsDialog.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog/SystemSignatureSettingsDialog.tsx index df0f4351..44d9afb4 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog/SystemSignatureSettingsDialog.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog/SystemSignatureSettingsDialog.tsx @@ -4,8 +4,15 @@ import { Button } from 'primereact/button'; import { TabPanel, TabView } from 'primereact/tabview'; import styles from './SystemSignatureSettingsDialog.module.scss'; import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components'; +import { Dropdown } from 'primereact/dropdown'; -export type Setting = { key: string; name: string; value: boolean; isFilter?: boolean }; +export type Setting = { + key: string; + name: string; + value: boolean | number; + isFilter?: boolean; + options?: { label: string; value: number }[]; +}; export const COSMIC_SIGNATURE = 'Cosmic Signature'; export const COSMIC_ANOMALY = 'Cosmic Anomaly'; @@ -33,13 +40,49 @@ export const SystemSignatureSettingsDialog = ({ const userSettings = settings.filter(setting => !setting.isFilter); const handleSettingsChange = (key: string) => { - setSettings(prevState => prevState.map(item => (item.key === key ? { ...item, value: !item.value } : item))); + setSettings(prevState => + prevState.map(item => + item.key === key ? { ...item, value: typeof item.value === 'boolean' ? !item.value : item.value } : item, + ), + ); + }; + + const handleDropdownChange = (key: string, value: number) => { + setSettings(prevState => prevState.map(item => (item.key === key ? { ...item, value } : item))); }; const handleSave = useCallback(() => { onSave(settings); }, [onSave, settings]); + const renderSetting = (setting: Setting) => { + if (setting.options) { + return ( +
+ + ({ + ...opt, + label: opt.label.split(' ')[0], // Just take the first part (e.g., "0s" from "Immediate (0s)") + }))} + onChange={e => handleDropdownChange(setting.key, e.value)} + className="w-40" + /> +
+ ); + } + + return ( + handleSettingsChange(setting.key)} + /> + ); + }; + return (
@@ -51,31 +94,15 @@ export const SystemSignatureSettingsDialog = ({ className={styles.verticalTabView} > -
- {filterSettings.map(setting => { - return ( - handleSettingsChange(setting.key)} - /> - ); - })} -
+
{filterSettings.map(renderSetting)}
- {userSettings.map(setting => { - return ( - handleSettingsChange(setting.key)} - /> - ); - })} + {userSettings.filter(setting => !setting.options).map(renderSetting)} + {userSettings.some(setting => setting.options) && ( +
+ )} + {userSettings.filter(setting => setting.options).map(renderSetting)}
diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatures.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatures.tsx index 4b458b65..ad0b50cf 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatures.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatures.tsx @@ -16,7 +16,13 @@ import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth'; import { useHotkey } from '@/hooks/Mapper/hooks'; -import { COMPACT_MAX_WIDTH } from './constants'; +import { + COMPACT_MAX_WIDTH, + DELETION_TIMING_DEFAULT, + DELETION_TIMING_EXTENDED, + DELETION_TIMING_IMMEDIATE, + DELETION_TIMING_SETTING_KEY, +} from './constants'; import { renderHeaderLabel } from './renders'; const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v5_5'; @@ -26,13 +32,35 @@ export const SHOW_UPDATED_COLUMN_SETTING = 'SHOW_UPDATED_COLUMN_SETTING'; export const SHOW_CHARACTER_COLUMN_SETTING = 'SHOW_CHARACTER_COLUMN_SETTING'; export const LAZY_DELETE_SIGNATURES_SETTING = 'LAZY_DELETE_SIGNATURES_SETTING'; export const KEEP_LAZY_DELETE_SETTING = 'KEEP_LAZY_DELETE_ENABLED_SETTING'; +// eslint-disable-next-line react-refresh/only-export-components +export const DELETION_TIMING_SETTING = DELETION_TIMING_SETTING_KEY; +export const COLOR_BY_TYPE_SETTING = 'COLOR_BY_TYPE_SETTING'; +export const SHOW_CHARACTER_PORTRAIT_SETTING = 'SHOW_CHARACTER_PORTRAIT_SETTING'; -const SETTINGS: Setting[] = [ +// Extend the Setting type to include options for dropdown settings +type ExtendedSetting = Setting & { + options?: { label: string; value: number }[]; +}; + +const SETTINGS: ExtendedSetting[] = [ { 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_CHARACTER_COLUMN_SETTING, name: 'Show Character Column', value: false, isFilter: false }, + { key: SHOW_CHARACTER_PORTRAIT_SETTING, name: 'Show Character Portrait in Tooltip', 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: COLOR_BY_TYPE_SETTING, name: 'Color Signatures by Type', value: false, isFilter: false }, + { + key: DELETION_TIMING_SETTING, + name: 'Deletion Timing', + value: DELETION_TIMING_DEFAULT, + isFilter: false, + options: [ + { label: '0s', value: DELETION_TIMING_IMMEDIATE }, + { label: '10s', value: DELETION_TIMING_DEFAULT }, + { label: '30s', value: DELETION_TIMING_EXTENDED }, + ], + }, { key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true, isFilter: true }, { key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true, isFilter: true }, @@ -49,10 +77,36 @@ const SETTINGS: Setting[] = [ { key: SignatureGroup.CombatSite, name: 'Show Combat Sites', value: true, isFilter: true }, ]; -function getDefaultSettings(): Setting[] { +function getDefaultSettings(): ExtendedSetting[] { return [...SETTINGS]; } +function getInitialSettings(): ExtendedSetting[] { + const stored = localStorage.getItem(SIGNATURE_SETTINGS_KEY); + if (stored) { + try { + const parsedSettings = JSON.parse(stored) as ExtendedSetting[]; + // Merge stored settings with default settings to ensure new settings are included + const defaultSettings = getDefaultSettings(); + const mergedSettings = defaultSettings.map(defaultSetting => { + const storedSetting = parsedSettings.find(s => s.key === defaultSetting.key); + if (storedSetting) { + // Keep the stored value but ensure options are from default settings + return { + ...defaultSetting, + value: storedSetting.value, + }; + } + return defaultSetting; + }); + return mergedSettings; + } catch (error) { + console.error('Error parsing stored settings', error); + } + } + return getDefaultSettings(); +} + export const SystemSignatures: React.FC = () => { const { data: { selectedSystems }, @@ -60,17 +114,7 @@ export const SystemSignatures: React.FC = () => { const [visible, setVisible] = useState(false); - const [currentSettings, setCurrentSettings] = useState(() => { - 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(); - }); + const [currentSettings, setCurrentSettings] = useState(getInitialSettings); useEffect(() => { localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(currentSettings)); @@ -78,6 +122,7 @@ export const SystemSignatures: React.FC = () => { const [sigCount, setSigCount] = useState(0); const [pendingSigs, setPendingSigs] = useState([]); + const [minPendingTimeRemaining, setMinPendingTimeRemaining] = useState(undefined); const undoPendingFnRef = useRef<() => void>(() => {}); @@ -88,13 +133,23 @@ export const SystemSignatures: React.FC = () => { const [systemId] = selectedSystems; const isNotSelectedSystem = selectedSystems.length !== 1; - const lazyDeleteValue = useMemo( - () => currentSettings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)?.value || false, - [currentSettings], - ); + const lazyDeleteValue = useMemo(() => { + const setting = currentSettings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING); + return typeof setting?.value === 'boolean' ? setting.value : false; + }, [currentSettings]); + + const deletionTimingValue = useMemo(() => { + const setting = currentSettings.find(setting => setting.key === DELETION_TIMING_SETTING); + return typeof setting?.value === 'number' ? setting.value : DELETION_TIMING_IMMEDIATE; + }, [currentSettings]); + + const colorByTypeValue = useMemo(() => { + const setting = currentSettings.find(setting => setting.key === COLOR_BY_TYPE_SETTING); + return typeof setting?.value === 'boolean' ? setting.value : false; + }, [currentSettings]); const handleSettingsChange = useCallback((newSettings: Setting[]) => { - setCurrentSettings(newSettings); + setCurrentSettings(newSettings as ExtendedSetting[]); setVisible(false); }, []); @@ -113,12 +168,14 @@ export const SystemSignatures: React.FC = () => { event.stopPropagation(); undoPendingFnRef.current(); setPendingSigs([]); + setMinPendingTimeRemaining(undefined); } }); const handleUndoClick = useCallback(() => { undoPendingFnRef.current(); setPendingSigs([]); + setMinPendingTimeRemaining(undefined); }, []); const handleSettingsButtonClick = useCallback(() => { @@ -135,6 +192,32 @@ export const SystemSignatures: React.FC = () => { undoPendingFnRef.current = newUndo; }, []); + // Calculate the minimum time remaining for any pending signature + useEffect(() => { + if (pendingSigs.length === 0) { + setMinPendingTimeRemaining(undefined); + return; + } + + const calculateTimeRemaining = () => { + const now = Date.now(); + let minTime: number | undefined = undefined; + + pendingSigs.forEach(sig => { + const extendedSig = sig as unknown as { pendingUntil?: number }; + if (extendedSig.pendingUntil && (minTime === undefined || extendedSig.pendingUntil - now < minTime)) { + minTime = extendedSig.pendingUntil - now; + } + }); + + setMinPendingTimeRemaining(minTime && minTime > 0 ? minTime : undefined); + }; + + calculateTimeRemaining(); + const interval = setInterval(calculateTimeRemaining, 1000); + return () => clearInterval(interval); + }, [pendingSigs]); + return ( { sigCount, lazyDeleteValue, pendingCount: pendingSigs.length, + pendingTimeRemaining: minPendingTimeRemaining, onLazyDeleteChange: handleLazyDeleteChange, onUndoClick: handleUndoClick, onSettingsClick: handleSettingsButtonClick, @@ -165,6 +249,8 @@ export const SystemSignatures: React.FC = () => { onLazyDeleteChange={handleLazyDeleteChange} onCountChange={handleSigCountChange} onPendingChange={handlePendingChange} + deletionTiming={deletionTimingValue} + colorByType={colorByTypeValue} /> )} {visible && ( diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent/SystemSignaturesContent.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent/SystemSignaturesContent.tsx index f43cf0a7..67c4973a 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent/SystemSignaturesContent.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent/SystemSignaturesContent.tsx @@ -20,6 +20,7 @@ import { SHOW_UPDATED_COLUMN_SETTING, SHOW_CHARACTER_COLUMN_SETTING, SIGNATURE_WINDOW_ID, + SHOW_CHARACTER_PORTRAIT_SETTING, } from '../SystemSignatures'; import { COSMIC_SIGNATURE } from '../SystemSignatureSettingsDialog'; @@ -48,13 +49,16 @@ const SORT_DEFAULT_VALUES: SystemSignaturesSortSettings = { interface SystemSignaturesContentProps { systemId: string; - settings: { key: string; value: boolean }[]; + settings: { key: string; value: boolean | number }[]; hideLinkedSignatures?: boolean; selectable?: boolean; onSelect?: (signature: SystemSignature) => void; onLazyDeleteChange?: (value: boolean) => void; onCountChange?: (count: number) => void; onPendingChange?: (pending: ExtendedSystemSignature[], undo: () => void) => void; + deletionTiming?: number; + colorByType?: boolean; + filterSignature?: (signature: SystemSignature) => boolean; } const headerInlineStyle = { padding: '2px', fontSize: '12px', lineHeight: '1.333' }; @@ -68,6 +72,9 @@ export function SystemSignaturesContent({ onLazyDeleteChange, onCountChange, onPendingChange, + deletionTiming, + colorByType, + filterSignature, }: SystemSignaturesContentProps) { const { signatures, selectedSignatures, setSelectedSignatures, handleDeleteSelected, handleSelectAll, handlePaste } = useSystemSignaturesData({ @@ -76,6 +83,7 @@ export function SystemSignaturesContent({ onCountChange, onPendingChange, onLazyDeleteChange, + deletionTiming, }); const [sortSettings, setSortSettings] = useLocalStorageState<{ sortField: string; sortOrder: SortOrder }>( @@ -98,7 +106,7 @@ export function SystemSignaturesContent({ handlePaste(clipboardContent.text); setClipboardContent(null); - }, [selectable, clipboardContent]); + }, [selectable, clipboardContent, handlePaste, setClipboardContent]); useHotkey(true, ['a'], handleSelectAll); useHotkey(false, ['Backspace', 'Delete'], (event: KeyboardEvent) => { @@ -152,6 +160,7 @@ export function SystemSignaturesContent({ 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 showCharacterColumn = settings.find(s => s.key === SHOW_CHARACTER_COLUMN_SETTING)?.value; + const showCharacterPortrait = settings.find(s => s.key === SHOW_CHARACTER_PORTRAIT_SETTING)?.value; const enabledGroups = settings .filter(s => GROUPS_LIST.includes(s.key as SignatureGroup) && s.value === true) @@ -159,6 +168,10 @@ export function SystemSignaturesContent({ const filteredSignatures = useMemo(() => { return signatures.filter(sig => { + if (filterSignature && !filterSignature(sig)) { + return false; + } + if (hideLinkedSignatures && sig.linked_system) { return false; } @@ -176,7 +189,7 @@ export function SystemSignaturesContent({ return settings.find(y => y.key === sig.kind)?.value; } }); - }, [signatures, hideLinkedSignatures, settings, enabledGroups]); + }, [signatures, hideLinkedSignatures, settings, enabledGroups, filterSignature]); return (
@@ -201,23 +214,17 @@ export function SystemSignaturesContent({ sortField={sortSettings.sortField} sortOrder={sortSettings.sortOrder} onSort={e => setSortSettings({ sortField: e.sortField, sortOrder: e.sortOrder })} - onRowMouseEnter={ - isCompact || isMedium - ? (e: DataTableRowMouseEvent) => { - setHoveredSignature(filteredSignatures[e.index]); - tooltipRef.current?.show(e.originalEvent); - } - : undefined + onRowMouseEnter={(e: DataTableRowMouseEvent) => { + setHoveredSignature(e.data as SystemSignature); + tooltipRef.current?.show(e.originalEvent); + }} + onRowMouseLeave={() => { + setHoveredSignature(null); + tooltipRef.current?.hide(); + }} + rowClassName={rowData => + getSignatureRowClass(rowData as ExtendedSystemSignature, selectedSignatures, colorByType) } - onRowMouseLeave={ - isCompact || isMedium - ? () => { - setHoveredSignature(null); - tooltipRef.current?.hide(); - } - : undefined - } - rowClassName={rowData => getSignatureRowClass(rowData as ExtendedSystemSignature, selectedSignatures)} > : null} + content={ + hoveredSignature ? ( + + ) : null + } /> {showSignatureSettings && ( diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts index 1e01b5c9..46d7f1c0 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts @@ -14,6 +14,12 @@ export const TIME_ONE_DAY = 24 * 60 * 60 * 1000; export const TIME_ONE_WEEK = 7 * TIME_ONE_DAY; export const FINAL_DURATION_MS = 10000; +// Signature deletion timing options +export const DELETION_TIMING_IMMEDIATE = 0; +export const DELETION_TIMING_DEFAULT = 10000; +export const DELETION_TIMING_EXTENDED = 30000; +export const DELETION_TIMING_SETTING_KEY = 'DELETION_TIMING_SETTING'; + export const COMPACT_MAX_WIDTH = 260; export const MEDIUM_MAX_WIDTH = 380; export const OTHER_COLUMNS_WIDTH = 276; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/getActualSigs.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/getActualSigs.ts index 80b53f1e..f42dd5ef 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/getActualSigs.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/getActualSigs.ts @@ -4,7 +4,7 @@ import { getState } from './getState'; /** * Compare two lists of signatures and return which are added, updated, or removed. - * + * * @param oldSignatures existing signatures (in memory or from server) * @param newSignatures newly parsed or incoming signatures from user input * @param updateOnly if true, do NOT remove old signatures not found in newSignatures diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/getRowBackgroundColor.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/getRowBackgroundColor.ts index f04a53de..a2cb5f8f 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/getRowBackgroundColor.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/getRowBackgroundColor.ts @@ -12,11 +12,11 @@ export const getRowBackgroundColor = (date: Date | undefined): string => { 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'; + return 'bg-lime-600/40 transition hover:bg-lime-600/50'; } if (diff < TIME_TEN_MINUTES) { - return 'bg-lime-700/40 transition hover:bg-lime-700/50'; + return 'bg-lime-700/30 transition hover:bg-lime-700/40'; } return ''; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/rowStyles.module.scss b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/rowStyles.module.scss index 23b3aa45..88836f86 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/rowStyles.module.scss +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/rowStyles.module.scss @@ -1,17 +1,19 @@ .pendingDeletion { - background-color: #f87171; + background-color: rgba(248, 113, 113, 0.4); transition: background-color 0.2s ease; } - .pendingDeletion td { - background-color: #f87171; + background-color: rgba(248, 113, 113, 0.4); transition: background-color 0.2s ease; } -.pendingDeletion { - background-color: #f87171; - transition: background-color 0.2s ease; +.pendingDeletion:hover { + background-color: rgba(248, 113, 113, 0.5); +} + +.pendingDeletion:hover td { + background-color: rgba(248, 113, 113, 0.5); } .Table thead tr { diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/rowStyles.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/rowStyles.ts index 86715dc4..f18fd1b6 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/rowStyles.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers/rowStyles.ts @@ -1,4 +1,5 @@ import clsx from 'clsx'; +import { SignatureGroup } from '@/hooks/Mapper/types'; import { ExtendedSystemSignature } from './contentHelpers'; import { getRowBackgroundColor } from './getRowBackgroundColor'; import classes from './rowStyles.module.scss'; @@ -6,15 +7,67 @@ import classes from './rowStyles.module.scss'; export function getSignatureRowClass( row: ExtendedSystemSignature, selectedSignatures: ExtendedSystemSignature[], + colorByType?: boolean, ): string { const isSelected = selectedSignatures.some(s => s.eve_id === row.eve_id); + if (isSelected) { + return clsx( + classes.TableRowCompact, + 'p-selectable-row', + 'bg-amber-500/50 hover:bg-amber-500/70 transition duration-200 text-xs', + ); + } + + if (row.pendingDeletion) { + return clsx(classes.TableRowCompact, 'p-selectable-row', classes.pendingDeletion); + } + + // Apply color by type styling if enabled + if (colorByType) { + if (row.group === SignatureGroup.Wormhole) { + return clsx( + classes.TableRowCompact, + 'p-selectable-row', + 'bg-blue-400/20 hover:bg-blue-400/20 transition duration-200 text-xs', + ); + } + + if (row.group === SignatureGroup.CosmicSignature) { + return clsx( + classes.TableRowCompact, + 'p-selectable-row', + 'bg-red-400/20 hover:bg-red-400/20 transition duration-200 text-xs', + ); + } + + if ( + row.group === SignatureGroup.RelicSite || + row.group === SignatureGroup.DataSite || + row.group === SignatureGroup.GasSite || + row.group === SignatureGroup.OreSite || + row.group === SignatureGroup.CombatSite + ) { + return clsx( + classes.TableRowCompact, + 'p-selectable-row', + 'bg-green-400/20 hover:bg-green-400/20 transition duration-200 text-xs', + ); + } + + // Default for color by type - apply same color as CosmicSignature (red) and small text size + return clsx( + classes.TableRowCompact, + 'p-selectable-row', + 'bg-red-400/20 hover:bg-red-400/20 transition duration-200 text-xs', + ); + } + + // Original styling when color by type is disabled 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', + !row.pendingDeletion && getRowBackgroundColor(row.inserted_at ? new Date(row.inserted_at) : undefined), + !row.pendingDeletion && 'hover:bg-purple-400/20 transition duration-200', ); } diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/types.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/types.ts index 8c4ca3da..faf371ee 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/types.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/types.ts @@ -2,11 +2,12 @@ import { ExtendedSystemSignature } from '../helpers/contentHelpers'; export interface UseSystemSignaturesDataProps { systemId: string; - settings: { key: string; value: boolean }[]; + settings: { key: string; value: boolean | number }[]; hideLinkedSignatures?: boolean; onCountChange?: (count: number) => void; onPendingChange?: (pending: ExtendedSystemSignature[], undo: () => void) => void; onLazyDeleteChange?: (value: boolean) => void; + deletionTiming?: number; } export interface UseFetchingParams { @@ -19,8 +20,10 @@ export interface UseFetchingParams { export interface UsePendingDeletionParams { systemId: string; setSignatures: React.Dispatch>; + deletionTiming?: number; } export interface UsePendingAdditionParams { setSignatures: React.Dispatch>; + deletionTiming?: number; } diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/usePendingAdditions.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/usePendingAdditions.ts index dbba5def..7e1e1e23 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/usePendingAdditions.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/usePendingAdditions.ts @@ -3,33 +3,49 @@ import { ExtendedSystemSignature, schedulePendingAdditionForSig } from '../helpe import { UsePendingAdditionParams } from './types'; import { FINAL_DURATION_MS } from '../constants'; -export function usePendingAdditions({ setSignatures }: UsePendingAdditionParams) { +export function usePendingAdditions({ setSignatures, deletionTiming }: UsePendingAdditionParams) { const [pendingUndoAdditions, setPendingUndoAdditions] = useState([]); const pendingAdditionMapRef = useRef>({}); + // Use the provided deletion timing or fall back to the default + const finalDuration = deletionTiming !== undefined ? deletionTiming : FINAL_DURATION_MS; + const processAddedSignatures = useCallback( (added: ExtendedSystemSignature[]) => { if (!added.length) return; + + // If duration is 0, don't show pending state + if (finalDuration === 0) { + setSignatures(prev => [ + ...prev, + ...added.map(sig => ({ + ...sig, + pendingAddition: false, + })), + ]); + return; + } + const now = Date.now(); setSignatures(prev => [ ...prev, ...added.map(sig => ({ ...sig, pendingAddition: true, - pendingUntil: now + FINAL_DURATION_MS, + pendingUntil: now + finalDuration, })), ]); added.forEach(sig => { schedulePendingAdditionForSig( sig, - FINAL_DURATION_MS, + finalDuration, setSignatures, pendingAdditionMapRef, setPendingUndoAdditions, ); }); }, - [setSignatures], + [setSignatures, finalDuration], ); const clearPendingAdditions = useCallback(() => { diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/usePendingDeletions.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/usePendingDeletions.ts index aeafcb58..df734790 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/usePendingDeletions.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/usePendingDeletions.ts @@ -5,13 +5,16 @@ import { UsePendingDeletionParams } from './types'; import { FINAL_DURATION_MS } from '../constants'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; -export function usePendingDeletions({ systemId, setSignatures }: UsePendingDeletionParams) { +export function usePendingDeletions({ systemId, setSignatures, deletionTiming }: UsePendingDeletionParams) { const { outCommand } = useMapRootState(); const [localPendingDeletions, setLocalPendingDeletions] = useState([]); const [pendingDeletionMap, setPendingDeletionMap] = useState< Record >({}); + // Use the provided deletion timing or fall back to the default + const finalDuration = deletionTiming !== undefined ? deletionTiming : FINAL_DURATION_MS; + const processRemovedSignatures = useCallback( async ( removed: ExtendedSystemSignature[], @@ -19,12 +22,22 @@ export function usePendingDeletions({ systemId, setSignatures }: UsePendingDelet updated: ExtendedSystemSignature[], ) => { if (!removed.length) return; + + // If deletion timing is 0, immediately delete without pending state + if (finalDuration === 0) { + await outCommand({ + type: OutCommand.updateSignatures, + data: prepareUpdatePayload(systemId, added, updated, removed), + }); + return; + } + const now = Date.now(); const processedRemoved = removed.map(r => ({ ...r, pendingDeletion: true, pendingAddition: false, - pendingUntil: now + FINAL_DURATION_MS, + pendingUntil: now + finalDuration, })); setLocalPendingDeletions(prev => [...prev, ...processedRemoved]); @@ -36,7 +49,7 @@ export function usePendingDeletions({ systemId, setSignatures }: UsePendingDelet setSignatures(prev => prev.map(sig => { if (processedRemoved.find(r => r.eve_id === sig.eve_id)) { - return { ...sig, pendingDeletion: true, pendingUntil: now + FINAL_DURATION_MS }; + return { ...sig, pendingDeletion: true, pendingUntil: now + finalDuration }; } return sig; }), @@ -53,10 +66,10 @@ export function usePendingDeletions({ systemId, setSignatures }: UsePendingDelet 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, + finalDuration, ); }, - [systemId, outCommand, setSignatures], + [systemId, outCommand, setSignatures, finalDuration], ); const clearPendingDeletions = useCallback(() => { diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/useSystemSignaturesData.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/useSystemSignaturesData.ts index 1fde420b..4f77f598 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/useSystemSignaturesData.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/hooks/useSystemSignaturesData.ts @@ -21,6 +21,7 @@ export function useSystemSignaturesData({ onCountChange, onPendingChange, onLazyDeleteChange, + deletionTiming, }: UseSystemSignaturesDataProps) { const { outCommand } = useMapRootState(); const [signatures, setSignatures, signaturesRef] = useRefState([]); @@ -30,10 +31,12 @@ export function useSystemSignaturesData({ usePendingDeletions({ systemId, setSignatures, + deletionTiming, }); const { pendingUndoAdditions, setPendingUndoAdditions, processAddedSignatures, clearPendingAdditions } = usePendingAdditions({ setSignatures, + deletionTiming, }); const { handleGetSignatures, handleUpdateSignatures } = useSignatureFetching({ diff --git a/assets/js/hooks/Mapper/helpers/getEveImageUrl.ts b/assets/js/hooks/Mapper/helpers/getEveImageUrl.ts new file mode 100644 index 00000000..b6744989 --- /dev/null +++ b/assets/js/hooks/Mapper/helpers/getEveImageUrl.ts @@ -0,0 +1,41 @@ +/** + * Constants for EVE Online image URLs + */ +const BASE_IMAGE_URL = 'https://images.evetech.net'; + +/** + * Generates a URL for any EVE Online image resource + * @param category - The category of the image (characters, corporations, alliances, types) + * @param id - The EVE Online ID of the entity + * @param variation - The variation of the image (icon, portrait, render, logo) + * @param size - The size of the image (optional) + * @returns The URL to the EVE Online image, or null if the ID is invalid + */ +export const getEveImageUrl = ( + category: 'characters' | 'corporations' | 'alliances' | 'types', + id?: number | string | null, + variation: string = 'icon', + size?: number, +): string | null => { + if (!id || (typeof id === 'number' && id <= 0)) { + return null; + } + + let url = `${BASE_IMAGE_URL}/${category}/${id}/${variation}`; + if (size) { + url += `?size=${size}`; + } + + return url; +}; + +/** + * Generates the URL for an EVE Online character portrait + * @param characterEveId - The EVE Online character ID + * @param size - The size of the portrait (default: 64) + * @returns The URL to the character's portrait, or an empty string if the ID is invalid + */ +export const getCharacterPortraitUrl = (characterEveId: string | number | undefined, size: number = 64): string => { + const portraitUrl = getEveImageUrl('characters', characterEveId, 'portrait', size); + return portraitUrl || ''; +}; diff --git a/assets/js/hooks/Mapper/helpers/index.ts b/assets/js/hooks/Mapper/helpers/index.ts index 38acbce4..2b55ee5a 100644 --- a/assets/js/hooks/Mapper/helpers/index.ts +++ b/assets/js/hooks/Mapper/helpers/index.ts @@ -1,3 +1,4 @@ export * from './sortWHClasses'; export * from './parseSignatures'; export * from './getSystemById'; +export * from './getEveImageUrl';