From 56d0d1625593207de45b8646ee5ece364b50d929 Mon Sep 17 00:00:00 2001 From: Drew Bonasera Date: Sun, 26 Apr 2026 23:45:49 -0400 Subject: [PATCH 01/11] Adds a feature to automatically copy formatted wormhole bookmark names to the clipboard. This is configurable via a new text input in the map settings and is triggered when a wormhole is linked or updated. - feat(signatures): Automatically copy formatted bookmark name to clipboard on WH link or update - feat(settings): Add support for text input fields in the settings UI for bookmark format - feat(signatures): Add `k162Type` to custom info to specify K162 destination type - refactor(map): Extract system class group logic into a `getSystemClassGroup` helper - fix(signatures): Add error handling for malformed `custom_info` JSON - fix(ui): Correct typo in `LINK_SIGNATURE_SETTINGS` constant - feat(backend): Add `bookmark_name_format` field to user settings --- .../map/helpers/getSystemClassGroup.ts | 15 ++ .../SystemLinkSignatureDialog.tsx | 77 ++++++-- .../MapSettings/MapSettingsProvider.tsx | 16 ++ .../components/MapSettings/constants.ts | 9 + .../components/MapSettings/types.ts | 6 +- .../SignatureSettings/SignatureSettings.tsx | 62 ++++++- .../Mapper/helpers/bookmarkFormatHelper.ts | 173 ++++++++++++++++++ .../helpers/parseSignatureCustomInfo.ts | 7 +- assets/js/hooks/Mapper/types/signatures.ts | 2 + .../repositories/map_user_settings_repo.ex | 3 +- .../event_handlers/map_core_event_handler.ex | 7 +- 11 files changed, 348 insertions(+), 29 deletions(-) create mode 100644 assets/js/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts create mode 100644 assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts diff --git a/assets/js/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts b/assets/js/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts new file mode 100644 index 00000000..9459c9bd --- /dev/null +++ b/assets/js/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts @@ -0,0 +1,15 @@ +import { SOLAR_SYSTEM_CLASS_IDS, SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS } from '@/hooks/Mapper/components/map/constants.ts'; + +export const getSystemClassGroup = (systemClassId: number | undefined | null): string | null => { + if (systemClassId == null) return null; + + 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 + ); +}; 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 38883825..e74cadca 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx @@ -10,6 +10,8 @@ import { import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent'; import { MULTI_DEST_WHS, ALL_DEST_TYPES_MAP, DEST_TYPES_MAP_MAP } from '@/hooks/Mapper/constants.ts'; import { SETTINGS_KEYS, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures'; +import { getSystemClassGroup } from '@/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts'; +import { calculateBookmarkIndex, copyToClipboard, formatBookmarkName } from '@/hooks/Mapper/helpers/bookmarkFormatHelper.ts'; import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; @@ -23,7 +25,7 @@ interface SystemLinkSignatureDialogProps { setVisible: (visible: boolean) => void; } -export const LINK_SIGNTATURE_SETTINGS: SignatureSettingsType = { +export const LINK_SIGNATURE_SETTINGS: SignatureSettingsType = { [SETTINGS_KEYS.COSMIC_SIGNATURE]: true, [SETTINGS_KEYS.WORMHOLE]: true, [SETTINGS_KEYS.SHOW_DESCRIPTION_COLUMN]: true, @@ -39,7 +41,7 @@ interface ExtendedSignatureCustomInfo { export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => { const { outCommand, - data: { wormholes }, + data: { wormholes, systemSignatures, systems, wormholesData }, } = useMapRootState(); const ref = useRef({ outCommand }); @@ -53,17 +55,7 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat // 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 - ); + return getSystemClassGroup(targetSystemInfo.system_class); }, [targetSystemInfo]); const handleHide = useCallback(() => { @@ -115,14 +107,63 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat [targetSystemClassGroup, wormholes], ); + const { signatures } = useSystemSignaturesData({ + systemId: `${data.solar_system_source}`, + settings: LINK_SIGNATURE_SETTINGS, + }); + const handleSelect = useCallback( - (signature: SystemSignature) => { + async (signature: SystemSignature) => { if (!signature) { return; } const { outCommand } = ref.current; + let currentSettings = null; + try { + const res = (await outCommand({ + type: OutCommand.getUserSettings, + data: null, + })) as any; + currentSettings = res?.user_settings; + } catch (e) { + console.warn('Failed to fetch user settings', e); + } + + if (signature.group === SignatureGroup.Wormhole && currentSettings?.bookmark_name_format) { + const info = parseSignatureCustomInfo(signature.custom_info); + let bookmarkIndex = info.bookmark_index; + + if (!bookmarkIndex) { + const sysSigs = systemSignatures[data.solar_system_source] || []; + bookmarkIndex = calculateBookmarkIndex(sysSigs, signature.eve_id); + info.bookmark_index = bookmarkIndex; + } + + const formattedStr = formatBookmarkName( + currentSettings.bookmark_name_format, + signature, + targetSystemClassGroup, + bookmarkIndex, + wormholesData, + ); + + await copyToClipboard(formattedStr); + + if (!parseSignatureCustomInfo(signature.custom_info).bookmark_index) { + await outCommand({ + type: OutCommand.updateSignatures, + data: { + system_id: `${data.solar_system_source}`, + updated: [{ ...signature, custom_info: JSON.stringify(info) }], + removed: [], + deleteTimeout: 0, + }, + }); + } + } + outCommand({ type: OutCommand.linkSignatureToSystem, data: { @@ -133,13 +174,9 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat setVisible(false); }, - [data, setVisible], + [data, setVisible, signatures, targetSystemClassGroup, systemSignatures, systems, wormholesData], ); - const { signatures } = useSystemSignaturesData({ - systemId: `${data.solar_system_source}`, - settings: LINK_SIGNTATURE_SETTINGS, - }); useEffect(() => { if (!targetSystemDynamicInfo) { @@ -160,7 +197,7 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat systemId={`${data.solar_system_source}`} signatures={signatures} hasUnsupportedLanguage={false} - settings={LINK_SIGNTATURE_SETTINGS} + settings={LINK_SIGNATURE_SETTINGS} hideLinkedSignatures selectable onSelect={handleSelect} diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx index 7b258617..46cb162a 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx @@ -21,6 +21,7 @@ import { import { OutCommand } from '@/hooks/Mapper/types'; import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components'; import { Dropdown } from 'primereact/dropdown'; +import { InputText } from 'primereact/inputtext'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { WithChildren } from '@/hooks/Mapper/types/common.ts'; @@ -103,6 +104,21 @@ export const MapSettingsProvider = ({ children }: WithChildren) => { ); } + if (item.type === 'text') { + return ( +
+ {item.label && } + handleSettingChange(item.prop, e.target.value)} + placeholder={item.placeholder} + /> + {item.helperText && {item.helperText}} +
+ ); + } + return null; }, [handleSettingChange], diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts index cf50f03b..0701466e 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts @@ -6,12 +6,14 @@ export const DEFAULT_REMOTE_SETTINGS = { [UserSettingsRemoteProps.link_signature_on_splash]: false, [UserSettingsRemoteProps.select_on_spash]: false, [UserSettingsRemoteProps.delete_connection_with_sigs]: false, + [UserSettingsRemoteProps.bookmark_name_format]: '', }; export const UserSettingsRemoteList = [ UserSettingsRemoteProps.link_signature_on_splash, UserSettingsRemoteProps.select_on_spash, UserSettingsRemoteProps.delete_connection_with_sigs, + UserSettingsRemoteProps.bookmark_name_format, ]; // export const COMMON_CHECKBOXES_PROPS: SettingsListItem[] = [ @@ -46,6 +48,13 @@ export const SIGNATURES_CHECKBOXES_PROPS: SettingsListItem[] = [ label: 'Show unsplashed signatures', type: 'checkbox', }, + { + prop: UserSettingsRemoteProps.bookmark_name_format, + label: 'Bookmark Name Format', + type: 'text', + placeholder: 'e.g. {i} {sig_letters} {dest_type} {size} {time_status} {mass_status}', + helperText: 'Variables: {i}, {sig_letters}, {sig}, {dest_type}, {type}, {size}, {mass}, {time_status}, {mass_status}, {temporary_name}, {description}', + }, ]; export const CONNECTIONS_CHECKBOXES_PROPS: SettingsListItem[] = [ diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts index 30d76809..4500e8e6 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts @@ -4,12 +4,14 @@ export enum UserSettingsRemoteProps { link_signature_on_splash = 'link_signature_on_splash', select_on_spash = 'select_on_spash', delete_connection_with_sigs = 'delete_connection_with_sigs', + bookmark_name_format = 'bookmark_name_format', } export type UserSettingsRemote = { link_signature_on_splash: boolean; select_on_spash: boolean; delete_connection_with_sigs: boolean; + bookmark_name_format: string; }; export type UserSettings = UserSettingsRemote & InterfaceStoredSettings; @@ -17,6 +19,8 @@ export type UserSettings = UserSettingsRemote & InterfaceStoredSettings; export type SettingsListItem = { prop: keyof UserSettings; label: string; - type: 'checkbox' | 'dropdown'; + type: 'checkbox' | 'dropdown' | 'text'; options?: { label: string; value: string }[]; + placeholder?: string; + helperText?: string; }; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx index 30ef9f8e..ba4cf941 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx @@ -2,8 +2,11 @@ import { SignatureGroupContent, SignatureGroupSelect, } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components'; +import { getSystemClassGroup } from '@/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts'; import { SystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx'; import { WdButton } from '@/hooks/Mapper/components/ui-kit'; +import { calculateBookmarkIndex, copyToClipboard, formatBookmarkName } from '@/hooks/Mapper/helpers/bookmarkFormatHelper.ts'; +import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { MassState, OutCommand, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types'; import { Dialog } from 'primereact/dialog'; @@ -14,6 +17,7 @@ import { Controller, FormProvider, useForm } from 'react-hook-form'; type SystemSignaturePrepared = Omit & { linked_system: string; destType: string; + k162Type?: string; time_status: TimeStatus; mass_status: MassState; }; @@ -26,9 +30,13 @@ export interface MapSettingsProps { } export const SignatureSettings = ({ systemId, show, onHide, signatureData }: MapSettingsProps) => { - const { outCommand } = useMapRootState(); + const { + outCommand, + data: { systemSignatures, systems, wormholesData }, + } = useMapRootState(); const handleShow = async () => {}; + const signatureForm = useForm>({}); const handleSave = useCallback( @@ -44,10 +52,13 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map switch (group) { case SignatureGroup.Wormhole: + const existingInfo = parseSignatureCustomInfo(signatureData.custom_info); out = { ...out, custom_info: JSON.stringify({ + ...existingInfo, destType: values.destType, + k162Type: values.k162Type, time_status: values.time_status, mass_status: values.mass_status, }), @@ -103,6 +114,44 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map // Note: despite groups have optional type - this will always set out = { ...out, group: group! }; + if (group === SignatureGroup.Wormhole) { + let currentSettings = null; + try { + const res = (await outCommand({ + type: OutCommand.getUserSettings, + data: null, + })) as any; + currentSettings = res?.user_settings; + } catch (e) { + console.warn('Failed to fetch user settings', e); + } + + if (currentSettings?.bookmark_name_format) { + const sysSigs = systemSignatures[systemId] || []; + const info = parseSignatureCustomInfo(out.custom_info); + + let bookmarkIndex = info.bookmark_index; + if (!bookmarkIndex) { + bookmarkIndex = calculateBookmarkIndex(sysSigs, out.eve_id); + info.bookmark_index = bookmarkIndex; + out.custom_info = JSON.stringify(info); + } + + const targetSystem = values.linked_system ? systems.find((s: any) => s.id === values.linked_system) : null; + const targetSystemClassGroup = targetSystem?.system_static_info ? getSystemClassGroup(targetSystem.system_static_info.system_class) : null; + + const formattedStr = formatBookmarkName( + currentSettings.bookmark_name_format, + out, + targetSystemClassGroup, + bookmarkIndex, + wormholesData, + ); + + await copyToClipboard(formattedStr); + } + } + await outCommand({ type: OutCommand.updateSignatures, data: { @@ -131,7 +180,7 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map signatureForm.reset(); onHide(); }, - [signatureData, signatureForm, outCommand, systemId, onHide], + [signatureData, signatureForm, outCommand, systemId, onHide, systemSignatures, systems, wormholesData], ); useEffect(() => { @@ -142,19 +191,22 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map const { linked_system, custom_info, ...rest } = signatureData; - let destType = null; + let destType: string | undefined = undefined; + let k162Type: string | undefined = undefined; let time_status = TimeStatus._24h; let mass_status = MassState.normal; if (custom_info) { - const customInfo = JSON.parse(custom_info); + const customInfo = parseSignatureCustomInfo(custom_info); destType = customInfo.destType; - time_status = customInfo.time_status; + k162Type = customInfo.k162Type; + time_status = customInfo.time_status ?? TimeStatus._24h; mass_status = customInfo.mass_status ?? MassState.normal; } signatureForm.reset({ linked_system: linked_system?.solar_system_id.toString() ?? undefined, destType: destType, + k162Type: k162Type, time_status: time_status, mass_status: mass_status, ...rest, diff --git a/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts new file mode 100644 index 00000000..7a72a49b --- /dev/null +++ b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts @@ -0,0 +1,173 @@ +import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; +import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo'; +import { MassState, TimeStatus } from '@/hooks/Mapper/types/connection'; +import { getSystemClassGroup } from '@/hooks/Mapper/components/map/helpers/getSystemClassGroup'; +import { WormholeDataRaw } from '@/hooks/Mapper/types/wormholes'; +import { WORMHOLES_ADDITIONAL_INFO, SHIP_MASSES_SIZE, SHIP_SIZES_NAMES_SHORT } from '@/hooks/Mapper/components/map/constants'; +import { ALL_DEST_TYPES_MAP } from '@/hooks/Mapper/constants'; +import { ShipSizeStatus } from '@/hooks/Mapper/types/connection'; + +const getTimeStatusString = (status?: TimeStatus): string => { + switch (status) { + case TimeStatus._1h: + return 'EoL'; // Or '1H', standard EVE mapping tends to use EoL for 1H + case TimeStatus._4h: + return '4H'; + case TimeStatus._4h30m: + return '4.5H'; + case TimeStatus._16h: + return '16H'; + case TimeStatus._24h: + return ''; + case TimeStatus._48h: + return ''; + default: + return ''; + } +}; + +const getMassStatusString = (status?: MassState): string => { + switch (status) { + case MassState.normal: + return ''; // Typically not specified if normal + case MassState.half: + return 'Destab'; + case MassState.verge: + return 'Crit'; + default: + return ''; + } +}; + +const DEST_CLASS_OVERRIDES: Record = { + h: 'HS', + l: 'LS', + n: 'NS', + t: 'Thera', + d: 'Drifter', + p: 'Pochven' +}; + +const formatDestString = (dest: string | null | undefined): string => { + if (!dest) return '?'; + const lowerDest = dest.toLowerCase(); + if (DEST_CLASS_OVERRIDES[lowerDest]) { + return DEST_CLASS_OVERRIDES[lowerDest]; + } + if (lowerDest.length <= 3) return dest.toUpperCase(); + return dest.charAt(0).toUpperCase() + dest.slice(1); +}; + +export const calculateBookmarkIndex = (signatures: SystemSignature[], currentEveId: string): number => { + const indices = signatures + .filter(sig => sig.eve_id !== currentEveId) + .map(sig => { + const info = parseSignatureCustomInfo(sig.custom_info); + return info.bookmark_index; + }) + .filter((i): i is number => typeof i === 'number' && i > 0); + + let i = 1; + while (indices.includes(i)) { + i++; + } + return i; +}; + +export const formatBookmarkName = ( + formatStr: string, + signature: SystemSignature, + destSystemClass: string | null, + bookmarkIndex: number, + wormholesData: Record = {}, +): string => { + let result = formatStr; + const info = parseSignatureCustomInfo(signature.custom_info); + + // Replace {i} + result = result.replace(/\{i\}/g, () => bookmarkIndex.toString()); + + // Replace {sig_letters} (first 3 chars of eve_id) + const sigLetters = signature.eve_id.substring(0, 3).toUpperCase(); + result = result.replace(/\{sig_letters\}/g, () => sigLetters); + + // Replace {sig} (full signature ID) + const fullSig = signature.eve_id.toUpperCase(); + result = result.replace(/\{sig\}/g, () => fullSig); + + // Replace {dest_type} + let destTypeStr = ''; + if (destSystemClass) { + destTypeStr = destSystemClass; + } else if (signature.type === 'K162' && info.k162Type) { + const k162Option = ALL_DEST_TYPES_MAP[info.k162Type]; + if (k162Option) { + destTypeStr = k162Option.label; + } + } else if (signature.type && wormholesData[signature.type]) { + const whData = wormholesData[signature.type]; + const whClass = whData?.dest?.length === 1 ? WORMHOLES_ADDITIONAL_INFO[whData.dest[0]] : null; + if (whClass) { + destTypeStr = whClass.shortName || whClass.shortTitle; + } + } else if (info.destType) { + const destOption = ALL_DEST_TYPES_MAP[info.destType]; + destTypeStr = destOption ? destOption.label : info.destType; + } + const finalDestTypeStr = formatDestString(destTypeStr); + result = result.replace(/\{dest_type\}/g, () => (finalDestTypeStr !== '?' ? finalDestTypeStr : '')); + + // Replace {size} and {mass} + let sizeStr = ''; + let massStr = ''; + let whDataForSize: WormholeDataRaw | null = null; + if (signature.type === 'K162' && info.k162Type) { + const k162Option = ALL_DEST_TYPES_MAP[info.k162Type]; + if (k162Option && k162Option.whClassName) { + const whName = k162Option.whClassName.split('_')[0]; + whDataForSize = wormholesData[whName]; + } + } else if (signature.type && wormholesData[signature.type]) { + whDataForSize = wormholesData[signature.type]; + } + + if (whDataForSize) { + if (whDataForSize.max_mass_per_jump) { + const sizeStatus = SHIP_MASSES_SIZE[whDataForSize.max_mass_per_jump] ?? ShipSizeStatus.large; + if (sizeStatus !== ShipSizeStatus.large) { + sizeStr = SHIP_SIZES_NAMES_SHORT[sizeStatus] || ''; + } + } + if (whDataForSize.total_mass) { + massStr = Number((whDataForSize.total_mass / 1_000_000_000).toFixed(2)).toString(); + } + } + result = result.replace(/\{size\}/g, () => sizeStr); + result = result.replace(/\{mass\}/g, () => massStr); + + // Replace {type} -> signature.type + result = result.replace(/\{type\}/g, () => signature.type || ''); + + // Replace {time_status} -> Parsed from custom_info.time_status + result = result.replace(/\{time_status\}/g, () => getTimeStatusString(info.time_status)); + + // Replace {mass_status} -> Parsed from custom_info.mass_status + result = result.replace(/\{mass_status\}/g, () => getMassStatusString(info.mass_status)); + + // Replace {temporary_name} -> signature.temporary_name + result = result.replace(/\{temporary_name\}/g, () => signature.temporary_name || ''); + + // Replace {description} -> signature.description + result = result.replace(/\{description\}/g, () => signature.description || ''); + + // Cleanup whitespace + return result.trim().replace(/\s+/g, ' '); +}; + +export const copyToClipboard = async (text: string) => { + try { + await navigator.clipboard.writeText(text); + } catch (err) { + console.warn('Failed to copy to clipboard', err); + } +}; diff --git a/assets/js/hooks/Mapper/helpers/parseSignatureCustomInfo.ts b/assets/js/hooks/Mapper/helpers/parseSignatureCustomInfo.ts index 0bb2b752..2e29c3e0 100644 --- a/assets/js/hooks/Mapper/helpers/parseSignatureCustomInfo.ts +++ b/assets/js/hooks/Mapper/helpers/parseSignatureCustomInfo.ts @@ -5,5 +5,10 @@ export const parseSignatureCustomInfo = (str: string | undefined): SignatureCust return {}; } - return JSON.parse(str); + try { + return JSON.parse(str); + } catch (e) { + console.warn('Failed to parse signature custom_info', e); + return {}; + } }; diff --git a/assets/js/hooks/Mapper/types/signatures.ts b/assets/js/hooks/Mapper/types/signatures.ts index 6a11cc7d..a28ddcfe 100644 --- a/assets/js/hooks/Mapper/types/signatures.ts +++ b/assets/js/hooks/Mapper/types/signatures.ts @@ -29,9 +29,11 @@ export type GroupType = { export type SignatureCustomInfo = { destType?: string; + k162Type?: string; time_status?: number; isCrit?: boolean; mass_status?: number; + bookmark_index?: number; }; export type SystemSignature = { diff --git a/lib/wanderer_app/repositories/map_user_settings_repo.ex b/lib/wanderer_app/repositories/map_user_settings_repo.ex index d0d5a00e..545daf06 100644 --- a/lib/wanderer_app/repositories/map_user_settings_repo.ex +++ b/lib/wanderer_app/repositories/map_user_settings_repo.ex @@ -5,7 +5,8 @@ defmodule WandererApp.MapUserSettingsRepo do "select_on_spash" => false, "link_signature_on_splash" => false, "delete_connection_with_sigs" => false, - "primary_character_id" => nil + "primary_character_id" => nil, + "bookmark_name_format" => "" } def get(map_id, user_id) do diff --git a/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex b/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex index 8b25a660..6fdd672b 100644 --- a/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex +++ b/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex @@ -232,7 +232,12 @@ defmodule WandererAppWeb.MapCoreEventHandler do ) do settings = user_settings_form - |> Map.take(["select_on_spash", "link_signature_on_splash", "delete_connection_with_sigs"]) + |> Map.take([ + "select_on_spash", + "link_signature_on_splash", + "delete_connection_with_sigs", + "bookmark_name_format" + ]) |> Jason.encode!() {:ok, user_settings} = From 46849238d9572a381329704725367159343caf3e Mon Sep 17 00:00:00 2001 From: Drew Bonasera Date: Mon, 27 Apr 2026 01:36:01 -0400 Subject: [PATCH 02/11] Add chained bookmark index `{I}` for hierarchical bookmark naming and improve formatting for multi-destination wormholes. This allows bookmark names to be based on the parent system's bookmark index. - feat(bookmarks): Introduce chained bookmark index for hierarchical naming via `{I}` - feat(bookmarks): Resolve destination type and size for multi-destination wormholes - feat(bookmarks): Add more aliases for destination class overrides in formatting - refactor(bookmarks): Rework `calculateBookmarkIndex` to find parent indices for chaining - fix(signatures): Await signature update and link commands in dialog - docs(settings): Update bookmark format helper text with new `{I}` variable --- .../SystemLinkSignatureDialog.tsx | 18 +++-- .../components/MapSettings/constants.ts | 2 +- .../SignatureSettings/SignatureSettings.tsx | 7 +- .../Mapper/helpers/bookmarkFormatHelper.ts | 74 ++++++++++++++++--- assets/js/hooks/Mapper/types/signatures.ts | 1 + 5 files changed, 80 insertions(+), 22 deletions(-) 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 e74cadca..2d0031c6 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx @@ -131,19 +131,23 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat console.warn('Failed to fetch user settings', e); } + let updatedSignature = signature; + if (signature.group === SignatureGroup.Wormhole && currentSettings?.bookmark_name_format) { const info = parseSignatureCustomInfo(signature.custom_info); let bookmarkIndex = info.bookmark_index; if (!bookmarkIndex) { - const sysSigs = systemSignatures[data.solar_system_source] || []; - bookmarkIndex = calculateBookmarkIndex(sysSigs, signature.eve_id); - info.bookmark_index = bookmarkIndex; + const calculated = calculateBookmarkIndex(systemSignatures, data.solar_system_source.toString(), signature.eve_id); + bookmarkIndex = calculated.index; + info.bookmark_index = calculated.index; + info.bookmark_index_chained = calculated.chained; + updatedSignature = { ...signature, custom_info: JSON.stringify(info) }; } const formattedStr = formatBookmarkName( currentSettings.bookmark_name_format, - signature, + updatedSignature, targetSystemClassGroup, bookmarkIndex, wormholesData, @@ -151,12 +155,12 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat await copyToClipboard(formattedStr); - if (!parseSignatureCustomInfo(signature.custom_info).bookmark_index) { + if (updatedSignature !== signature) { await outCommand({ type: OutCommand.updateSignatures, data: { system_id: `${data.solar_system_source}`, - updated: [{ ...signature, custom_info: JSON.stringify(info) }], + updated: [updatedSignature], removed: [], deleteTimeout: 0, }, @@ -164,7 +168,7 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat } } - outCommand({ + await outCommand({ type: OutCommand.linkSignatureToSystem, data: { ...data, diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts index 0701466e..152d180b 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts @@ -53,7 +53,7 @@ export const SIGNATURES_CHECKBOXES_PROPS: SettingsListItem[] = [ label: 'Bookmark Name Format', type: 'text', placeholder: 'e.g. {i} {sig_letters} {dest_type} {size} {time_status} {mass_status}', - helperText: 'Variables: {i}, {sig_letters}, {sig}, {dest_type}, {type}, {size}, {mass}, {time_status}, {mass_status}, {temporary_name}, {description}', + helperText: 'Variables: {i}, {I}, {sig_letters}, {sig}, {dest_type}, {type}, {size}, {mass}, {time_status}, {mass_status}, {temporary_name}, {description}', }, ]; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx index ba4cf941..88b59614 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx @@ -127,13 +127,14 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map } if (currentSettings?.bookmark_name_format) { - const sysSigs = systemSignatures[systemId] || []; const info = parseSignatureCustomInfo(out.custom_info); let bookmarkIndex = info.bookmark_index; if (!bookmarkIndex) { - bookmarkIndex = calculateBookmarkIndex(sysSigs, out.eve_id); - info.bookmark_index = bookmarkIndex; + const calculated = calculateBookmarkIndex(systemSignatures, systemId, out.eve_id); + bookmarkIndex = calculated.index; + info.bookmark_index = calculated.index; + info.bookmark_index_chained = calculated.chained; out.custom_info = JSON.stringify(info); } diff --git a/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts index 7a72a49b..b7d7d2a6 100644 --- a/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts +++ b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts @@ -4,7 +4,7 @@ import { MassState, TimeStatus } from '@/hooks/Mapper/types/connection'; import { getSystemClassGroup } from '@/hooks/Mapper/components/map/helpers/getSystemClassGroup'; import { WormholeDataRaw } from '@/hooks/Mapper/types/wormholes'; import { WORMHOLES_ADDITIONAL_INFO, SHIP_MASSES_SIZE, SHIP_SIZES_NAMES_SHORT } from '@/hooks/Mapper/components/map/constants'; -import { ALL_DEST_TYPES_MAP } from '@/hooks/Mapper/constants'; +import { ALL_DEST_TYPES_MAP, MULTI_DEST_WHS } from '@/hooks/Mapper/constants'; import { ShipSizeStatus } from '@/hooks/Mapper/types/connection'; const getTimeStatusString = (status?: TimeStatus): string => { @@ -41,11 +41,22 @@ const getMassStatusString = (status?: MassState): string => { const DEST_CLASS_OVERRIDES: Record = { h: 'HS', + hs: 'HS', + 'hi-sec': 'HS', l: 'LS', + ls: 'LS', + 'low-sec': 'LS', n: 'NS', + ns: 'NS', + 'null-sec': 'NS', t: 'Thera', + thera: 'Thera', d: 'Drifter', - p: 'Pochven' + drifter: 'Drifter', + p: 'Pochven', + pochven: 'Pochven', + 'c1/c2/c3': 'C1/C2/C3', + 'c4/c5': 'C4/C5' }; const formatDestString = (dest: string | null | undefined): string => { @@ -58,20 +69,46 @@ const formatDestString = (dest: string | null | undefined): string => { return dest.charAt(0).toUpperCase() + dest.slice(1); }; -export const calculateBookmarkIndex = (signatures: SystemSignature[], currentEveId: string): number => { - const indices = signatures +export const calculateBookmarkIndex = ( + systemSignatures: Record, + currentSystemId: string, + currentEveId: string, +): { index: number; chained: string } => { + let parentBookmarkIndex: string | undefined; + + for (const [sysId, sigs] of Object.entries(systemSignatures)) { + if (sysId === currentSystemId.toString()) continue; + + const parentSigs = sigs.filter(sig => sig.linked_system?.solar_system_id?.toString() === currentSystemId.toString()); + for (const parentSig of parentSigs) { + const parentInfo = parseSignatureCustomInfo(parentSig.custom_info); + if (parentInfo.bookmark_index_chained != null) { + if (!parentBookmarkIndex || String(parentInfo.bookmark_index_chained).length < parentBookmarkIndex.length) { + parentBookmarkIndex = String(parentInfo.bookmark_index_chained); + } + } else if (parentInfo.bookmark_index != null) { + if (!parentBookmarkIndex || String(parentInfo.bookmark_index).length < parentBookmarkIndex.length) { + parentBookmarkIndex = String(parentInfo.bookmark_index); + } + } + } + } + + const currentSigs = systemSignatures[currentSystemId.toString()] || []; + + const existingIndices = currentSigs .filter(sig => sig.eve_id !== currentEveId) - .map(sig => { - const info = parseSignatureCustomInfo(sig.custom_info); - return info.bookmark_index; - }) + .map(sig => parseSignatureCustomInfo(sig.custom_info).bookmark_index) .filter((i): i is number => typeof i === 'number' && i > 0); let i = 1; - while (indices.includes(i)) { + while (existingIndices.includes(i)) { i++; } - return i; + + const chained = parentBookmarkIndex !== undefined ? `${parentBookmarkIndex}${i}` : `${i}`; + + return { index: i, chained }; }; export const formatBookmarkName = ( @@ -87,6 +124,9 @@ export const formatBookmarkName = ( // Replace {i} result = result.replace(/\{i\}/g, () => bookmarkIndex.toString()); + // Replace {I} + result = result.replace(/\{I\}/g, () => info.bookmark_index_chained || bookmarkIndex.toString()); + // Replace {sig_letters} (first 3 chars of eve_id) const sigLetters = signature.eve_id.substring(0, 3).toUpperCase(); result = result.replace(/\{sig_letters\}/g, () => sigLetters); @@ -99,6 +139,11 @@ export const formatBookmarkName = ( let destTypeStr = ''; if (destSystemClass) { destTypeStr = destSystemClass; + } else if (signature.type && MULTI_DEST_WHS.includes(signature.type) && info.destType) { + const destOption = ALL_DEST_TYPES_MAP[info.destType]; + if (destOption) { + destTypeStr = destOption.label; + } } else if (signature.type === 'K162' && info.k162Type) { const k162Option = ALL_DEST_TYPES_MAP[info.k162Type]; if (k162Option) { @@ -121,7 +166,14 @@ export const formatBookmarkName = ( let sizeStr = ''; let massStr = ''; let whDataForSize: WormholeDataRaw | null = null; - if (signature.type === 'K162' && info.k162Type) { + + if (signature.type && MULTI_DEST_WHS.includes(signature.type) && info.destType) { + const destOption = ALL_DEST_TYPES_MAP[info.destType]; + if (destOption && destOption.whClassName) { + const whName = destOption.whClassName.split('_')[0]; + whDataForSize = wormholesData[whName]; + } + } else if (signature.type === 'K162' && info.k162Type) { const k162Option = ALL_DEST_TYPES_MAP[info.k162Type]; if (k162Option && k162Option.whClassName) { const whName = k162Option.whClassName.split('_')[0]; diff --git a/assets/js/hooks/Mapper/types/signatures.ts b/assets/js/hooks/Mapper/types/signatures.ts index a28ddcfe..ac4cf96c 100644 --- a/assets/js/hooks/Mapper/types/signatures.ts +++ b/assets/js/hooks/Mapper/types/signatures.ts @@ -34,6 +34,7 @@ export type SignatureCustomInfo = { isCrit?: boolean; mass_status?: number; bookmark_index?: number; + bookmark_index_chained?: string; }; export type SystemSignature = { From f4fc3a48e05dbd456cf0ccd804d2d58fc4f786fb Mon Sep 17 00:00:00 2001 From: Drew Bonasera Date: Mon, 27 Apr 2026 22:53:20 -0400 Subject: [PATCH 03/11] Adds new letter-based and chained-letter variables for automatic bookmark name formatting. The bookmark index calculation is updated to support this and correctly identify parent systems. - feat(bookmarks): Add letter-based and chained-letter format variables - feat(signatures): Calculate and store chained letter-based bookmark index - refactor(bookmarks): Update index calculation to use both system UUID and solar system ID - docs(settings): Update bookmark format placeholder and helper text with new variables --- .../SystemLinkSignatureDialog.tsx | 5 +- .../components/MapSettings/constants.ts | 4 +- .../SignatureSettings/SignatureSettings.tsx | 5 +- .../Mapper/helpers/bookmarkFormatHelper.ts | 53 +++++++++++++++---- assets/js/hooks/Mapper/types/signatures.ts | 1 + 5 files changed, 53 insertions(+), 15 deletions(-) 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 2d0031c6..aab55a43 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx @@ -138,10 +138,13 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat let bookmarkIndex = info.bookmark_index; if (!bookmarkIndex) { - const calculated = calculateBookmarkIndex(systemSignatures, data.solar_system_source.toString(), signature.eve_id); + const sourceSystem = systems.find((s: any) => s.system_static_info?.solar_system_id === data.solar_system_source); + const systemUuid = sourceSystem?.id || data.solar_system_source.toString(); + const calculated = calculateBookmarkIndex(systemSignatures, systemUuid, data.solar_system_source.toString(), signature.eve_id); bookmarkIndex = calculated.index; info.bookmark_index = calculated.index; info.bookmark_index_chained = calculated.chained; + info.bookmark_index_chained_letters = calculated.chainedLetters; updatedSignature = { ...signature, custom_info: JSON.stringify(info) }; } diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts index 152d180b..f177ab38 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts @@ -52,8 +52,8 @@ export const SIGNATURES_CHECKBOXES_PROPS: SettingsListItem[] = [ prop: UserSettingsRemoteProps.bookmark_name_format, label: 'Bookmark Name Format', type: 'text', - placeholder: 'e.g. {i} {sig_letters} {dest_type} {size} {time_status} {mass_status}', - helperText: 'Variables: {i}, {I}, {sig_letters}, {sig}, {dest_type}, {type}, {size}, {mass}, {time_status}, {mass_status}, {temporary_name}, {description}', + placeholder: 'e.g. {chain_index_letters} {sig_letters} {dest_type} {size} {time_status} {mass_status}', + helperText: 'Variables: {index}, {chain_index}, {index_letter}, {chain_index_letters}, {sig_letters}, {sig}, {dest_type}, {type}, {size}, {mass}, {time_status}, {mass_status}, {temporary_name}, {description}', }, ]; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx index 88b59614..cd9aa5d2 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx @@ -131,10 +131,13 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map let bookmarkIndex = info.bookmark_index; if (!bookmarkIndex) { - const calculated = calculateBookmarkIndex(systemSignatures, systemId, out.eve_id); + const currentSystem = systems.find((s: any) => s.id === systemId); + const solarSystemIdStr = currentSystem?.system_static_info?.solar_system_id?.toString() || systemId; + const calculated = calculateBookmarkIndex(systemSignatures, systemId, solarSystemIdStr, out.eve_id); bookmarkIndex = calculated.index; info.bookmark_index = calculated.index; info.bookmark_index_chained = calculated.chained; + info.bookmark_index_chained_letters = calculated.chainedLetters; out.custom_info = JSON.stringify(info); } diff --git a/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts index b7d7d2a6..668393e3 100644 --- a/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts +++ b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts @@ -69,17 +69,29 @@ const formatDestString = (dest: string | null | undefined): string => { return dest.charAt(0).toUpperCase() + dest.slice(1); }; +const numberToLetters = (num: number): string => { + let letters = ''; + while (num > 0) { + const mod = (num - 1) % 26; + letters = String.fromCharCode(65 + mod) + letters; + num = Math.floor((num - mod) / 26); + } + return letters; +}; + export const calculateBookmarkIndex = ( systemSignatures: Record, - currentSystemId: string, + currentSystemUuid: string, + currentSolarSystemId: string, currentEveId: string, -): { index: number; chained: string } => { +): { index: number; chained: string; chainedLetters: string } => { let parentBookmarkIndex: string | undefined; + let parentBookmarkIndexLetters: string | undefined; for (const [sysId, sigs] of Object.entries(systemSignatures)) { - if (sysId === currentSystemId.toString()) continue; + if (sysId === currentSystemUuid || sysId === currentSolarSystemId) continue; - const parentSigs = sigs.filter(sig => sig.linked_system?.solar_system_id?.toString() === currentSystemId.toString()); + const parentSigs = sigs.filter(sig => sig.linked_system?.solar_system_id?.toString() === currentSolarSystemId); for (const parentSig of parentSigs) { const parentInfo = parseSignatureCustomInfo(parentSig.custom_info); if (parentInfo.bookmark_index_chained != null) { @@ -91,12 +103,24 @@ export const calculateBookmarkIndex = ( parentBookmarkIndex = String(parentInfo.bookmark_index); } } + + if (parentInfo.bookmark_index_chained_letters != null) { + if (!parentBookmarkIndexLetters || String(parentInfo.bookmark_index_chained_letters).length < parentBookmarkIndexLetters.length) { + parentBookmarkIndexLetters = String(parentInfo.bookmark_index_chained_letters); + } + } } } - const currentSigs = systemSignatures[currentSystemId.toString()] || []; + const currentSigsRaw = [ + ...(systemSignatures[currentSystemUuid] || []), + ...(systemSignatures[currentSolarSystemId] || []) + ]; - const existingIndices = currentSigs + // Deduplicate in case both keys map to the same or overlapping arrays + const uniqueCurrentSigs = Array.from(new Map(currentSigsRaw.map(sig => [sig.eve_id, sig])).values()); + + const existingIndices = uniqueCurrentSigs .filter(sig => sig.eve_id !== currentEveId) .map(sig => parseSignatureCustomInfo(sig.custom_info).bookmark_index) .filter((i): i is number => typeof i === 'number' && i > 0); @@ -107,8 +131,9 @@ export const calculateBookmarkIndex = ( } const chained = parentBookmarkIndex !== undefined ? `${parentBookmarkIndex}${i}` : `${i}`; + const chainedLetters = parentBookmarkIndexLetters !== undefined ? `${parentBookmarkIndexLetters}${i}` : numberToLetters(i); - return { index: i, chained }; + return { index: i, chained, chainedLetters }; }; export const formatBookmarkName = ( @@ -121,11 +146,17 @@ export const formatBookmarkName = ( let result = formatStr; const info = parseSignatureCustomInfo(signature.custom_info); - // Replace {i} - result = result.replace(/\{i\}/g, () => bookmarkIndex.toString()); + // Replace {index} + result = result.replace(/\{index\}/g, () => bookmarkIndex.toString()); - // Replace {I} - result = result.replace(/\{I\}/g, () => info.bookmark_index_chained || bookmarkIndex.toString()); + // Replace {chain_index} + result = result.replace(/\{chain_index\}/g, () => info.bookmark_index_chained || bookmarkIndex.toString()); + + // Replace {index_letter} + result = result.replace(/\{index_letter\}/g, () => numberToLetters(bookmarkIndex)); + + // Replace {chain_index_letters} + result = result.replace(/\{chain_index_letters\}/g, () => info.bookmark_index_chained_letters || info.bookmark_index_chained || bookmarkIndex.toString()); // Replace {sig_letters} (first 3 chars of eve_id) const sigLetters = signature.eve_id.substring(0, 3).toUpperCase(); diff --git a/assets/js/hooks/Mapper/types/signatures.ts b/assets/js/hooks/Mapper/types/signatures.ts index ac4cf96c..eb68d693 100644 --- a/assets/js/hooks/Mapper/types/signatures.ts +++ b/assets/js/hooks/Mapper/types/signatures.ts @@ -35,6 +35,7 @@ export type SignatureCustomInfo = { mass_status?: number; bookmark_index?: number; bookmark_index_chained?: string; + bookmark_index_chained_letters?: string; }; export type SystemSignature = { From 292039c749dd5e826c6b5600ab631661edfcf1c7 Mon Sep 17 00:00:00 2001 From: Drew Bonasera Date: Tue, 28 Apr 2026 01:15:46 -0400 Subject: [PATCH 04/11] Adds a dedicated 'Bookmarks' settings tab with advanced options for automatic bookmark naming. This includes features for auto-filling temporary names, starting indices from 0, and toggling auto-copy. - feat(bookmarks): Add a dedicated 'Bookmarks' settings tab with advanced formatting options. - feat(bookmarks): Implement auto-filling of a wormhole's temporary name based on its index. - feat(bookmarks): Add setting to start wormhole bookmark indices from 0. - feat(bookmarks): Add setting to enable or disable automatic copying of bookmark names. - fix(bookmarks): Handle bookmark index 0 correctly in auto-naming calculations. - refactor(settings): Move bookmark format setting to the new 'Bookmarks' tab. - chore(api): Add new bookmark settings to the backend allowlist. --- .../SystemLinkSignatureDialog.tsx | 56 +++++-- .../components/MapSettings/MapSettings.tsx | 21 ++- .../MapSettings/MapSettingsProvider.tsx | 4 +- .../components/BookmarkNameFormatSetting.tsx | 147 ++++++++++++++++++ .../components/MapSettings/constants.ts | 34 +++- .../components/MapSettings/types.ts | 6 + .../SignatureSettings/SignatureSettings.tsx | 60 +++++-- .../Mapper/helpers/bookmarkFormatHelper.ts | 15 +- .../event_handlers/map_core_event_handler.ex | 5 +- 9 files changed, 306 insertions(+), 42 deletions(-) create mode 100644 assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx 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 aab55a43..de4fe7d1 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx @@ -11,7 +11,7 @@ import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/ import { MULTI_DEST_WHS, ALL_DEST_TYPES_MAP, DEST_TYPES_MAP_MAP } from '@/hooks/Mapper/constants.ts'; import { SETTINGS_KEYS, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures'; import { getSystemClassGroup } from '@/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts'; -import { calculateBookmarkIndex, copyToClipboard, formatBookmarkName } from '@/hooks/Mapper/helpers/bookmarkFormatHelper.ts'; +import { calculateBookmarkIndex, copyToClipboard, formatBookmarkName, numberToLetters } from '@/hooks/Mapper/helpers/bookmarkFormatHelper.ts'; import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; @@ -133,14 +133,20 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat let updatedSignature = signature; - if (signature.group === SignatureGroup.Wormhole && currentSettings?.bookmark_name_format) { + if (signature.group === SignatureGroup.Wormhole && (currentSettings?.bookmark_name_format || currentSettings?.bookmark_auto_temp_name)) { const info = parseSignatureCustomInfo(signature.custom_info); let bookmarkIndex = info.bookmark_index; - if (!bookmarkIndex) { + if (bookmarkIndex == null) { const sourceSystem = systems.find((s: any) => s.system_static_info?.solar_system_id === data.solar_system_source); const systemUuid = sourceSystem?.id || data.solar_system_source.toString(); - const calculated = calculateBookmarkIndex(systemSignatures, systemUuid, data.solar_system_source.toString(), signature.eve_id); + const calculated = calculateBookmarkIndex( + systemSignatures, + systemUuid, + data.solar_system_source.toString(), + signature.eve_id, + currentSettings?.bookmark_wormholes_start_at_zero, + ); bookmarkIndex = calculated.index; info.bookmark_index = calculated.index; info.bookmark_index_chained = calculated.chained; @@ -148,15 +154,39 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat updatedSignature = { ...signature, custom_info: JSON.stringify(info) }; } - const formattedStr = formatBookmarkName( - currentSettings.bookmark_name_format, - updatedSignature, - targetSystemClassGroup, - bookmarkIndex, - wormholesData, - ); - - await copyToClipboard(formattedStr); + if (currentSettings?.bookmark_auto_temp_name && !updatedSignature.temporary_name) { + let autoName = ''; + switch (currentSettings.bookmark_auto_temp_name) { + case 'index': + autoName = bookmarkIndex.toString(); + break; + case 'index_letter': + autoName = numberToLetters(bookmarkIndex, currentSettings.bookmark_wormholes_start_at_zero); + break; + case 'chain_index': + autoName = info.bookmark_index_chained || bookmarkIndex.toString(); + break; + case 'chain_index_letters': + autoName = info.bookmark_index_chained_letters || info.bookmark_index_chained || bookmarkIndex.toString(); + break; + } + if (autoName) { + updatedSignature = { ...updatedSignature, temporary_name: autoName }; + } + } + + if (currentSettings?.bookmark_name_format && currentSettings?.bookmark_auto_copy !== false) { + const formattedStr = formatBookmarkName( + currentSettings.bookmark_name_format, + updatedSignature, + targetSystemClassGroup, + bookmarkIndex, + wormholesData, + currentSettings.bookmark_wormholes_start_at_zero + ); + + await copyToClipboard(formattedStr); + } if (updatedSignature !== signature) { await outCommand({ diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettings.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettings.tsx index 3943c0cf..aa756a9a 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettings.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettings.tsx @@ -4,13 +4,14 @@ import { useCallback, useRef, useState } from 'react'; import { TabPanel, TabView } from 'primereact/tabview'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { OutCommand, UserPermission } from '@/hooks/Mapper/types'; -import { CONNECTIONS_CHECKBOXES_PROPS, SIGNATURES_CHECKBOXES_PROPS, SYSTEMS_CHECKBOXES_PROPS } from './constants.ts'; +import { BOOKMARKS_SETTINGS_PROPS, CONNECTIONS_CHECKBOXES_PROPS, SIGNATURES_CHECKBOXES_PROPS, SYSTEMS_CHECKBOXES_PROPS } from './constants.ts'; import { MapSettingsProvider, useMapSettings, } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx'; import { WidgetsSettings } from './components/WidgetsSettings'; import { CommonSettings } from './components/CommonSettings'; +import { BookmarkNameFormatSetting } from './components/BookmarkNameFormatSetting'; import { SettingsListItem } from './types.ts'; import { ImportExport } from './components/ImportExport.tsx'; import { ServerSettings } from './components/ServerSettings.tsx'; @@ -26,7 +27,7 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => { const [activeIndex, setActiveIndex] = useState(0); const { outCommand } = useMapRootState(); - const { renderSettingItem, setUserRemoteSettings } = useMapSettings(); + const { renderSettingItem, setUserRemoteSettings, settings } = useMapSettings(); const isAdmin = useMapCheckPermissions([UserPermission.ADMIN_MAP]); const refVars = useRef({ outCommand, onHide, visible }); @@ -89,6 +90,22 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => { {renderSettingsList(SIGNATURES_CHECKBOXES_PROPS)} + +
+ {!settings.link_signature_on_splash && ( +
+ ⚠️ It is highly recommended to enable 'Link signature on splash' (in the Signatures tab) to fully utilize automatic bookmark naming. +
+ )} + +
+ {renderSettingsList(BOOKMARKS_SETTINGS_PROPS)} +
+ + +
+
+ diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx index 46cb162a..3f84f92b 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx @@ -27,7 +27,9 @@ import { WithChildren } from '@/hooks/Mapper/types/common.ts'; type MapSettingsContextType = { renderSettingItem: (item: SettingsListItem) => ReactNode; + updateSetting: (prop: keyof UserSettings, value: boolean | string) => Promise; setUserRemoteSettings: Dispatch>; + settings: UserSettings; }; const MapSettingsContext = createContext(undefined); @@ -125,7 +127,7 @@ export const MapSettingsProvider = ({ children }: WithChildren) => { ); return ( - + {children} ); diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx new file mode 100644 index 00000000..c7687eed --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx @@ -0,0 +1,147 @@ +import { useMapSettings } from '../MapSettingsProvider'; +import { UserSettingsRemoteProps } from '../types'; +import { InputText } from 'primereact/inputtext'; +import { WdButton } from '@/hooks/Mapper/components/ui-kit'; +import { useMemo, useState, useRef, useEffect } from 'react'; +import { formatBookmarkName } from '@/hooks/Mapper/helpers/bookmarkFormatHelper'; +import { SignatureGroup, SignatureKind, SystemSignature } from '@/hooks/Mapper/types'; +import { MassState, TimeStatus } from '@/hooks/Mapper/types/connection'; + +const DUMMY_SIG_BASE: SystemSignature = { + eve_id: 'ABC-123', + name: 'ABC-123', + kind: SignatureKind.CosmicSignature, + type: 'K162', + description: 'To Jita', + temporary_name: 'Temp', + group: SignatureGroup.Wormhole, + custom_info: '', +}; + +const VARIABLES = [ + { id: '{index}', desc: 'Numeric index (e.g., 1, 2, 3)' }, + { id: '{index_letter}', desc: 'Letter index (e.g., A, B, C)' }, + { id: '{chain_index}', desc: 'Numeric chain path (e.g., 11, 12, 121)' }, + { id: '{chain_index_letters}', desc: 'Letter chain path (e.g., A, A1, A21)' }, + { id: '{sig_letters}', desc: 'First 3 chars of signature (e.g., ABC)' }, + { id: '{sig}', desc: 'Full signature ID (e.g., ABC-123)' }, + { id: '{dest_type}', desc: 'Destination class (e.g., C5, HS, Thera)' }, + { id: '{type}', desc: 'Wormhole type (e.g., K162, H900)' }, + { id: '{size}', desc: 'Hole size (e.g., S, M, XL)' }, + { id: '{mass}', desc: 'Total mass in bil (e.g., 3.3)' }, + { id: '{time_status}', desc: 'Time remaining (e.g., EoL, 4H, 16H)' }, + { id: '{mass_status}', desc: 'Mass remaining (e.g., Destab, Crit)' }, + { id: '{temporary_name}', desc: 'Temporary name if set' }, + { id: '{description}', desc: 'Custom description' }, +]; + +export const BookmarkNameFormatSetting = () => { + const { settings, updateSetting } = useMapSettings(); + const formatStr = settings.bookmark_name_format || ''; + const inputRef = useRef(null); + + const [localFormat, setLocalFormat] = useState(formatStr); + + useEffect(() => { + setLocalFormat(formatStr); + }, [formatStr]); + + const preview = useMemo(() => { + const isZero = settings.bookmark_wormholes_start_at_zero; + + const chainNum = isZero ? `001` : `112`; + const chainLet = isZero ? `A01` : `A12`; + const currentIndex = isZero ? 1 : 2; + + const dummySig: SystemSignature = { + ...DUMMY_SIG_BASE, + custom_info: JSON.stringify({ + time_status: TimeStatus._1h, + mass_status: MassState.half, + bookmark_index_chained: chainNum, + bookmark_index_chained_letters: chainLet, + }), + }; + + return formatBookmarkName( + localFormat, + dummySig, + 'HS', + currentIndex, + {}, + isZero + ); + }, [localFormat, settings.bookmark_wormholes_start_at_zero]); + + const handleBlur = () => { + if (localFormat !== formatStr) { + updateSetting(UserSettingsRemoteProps.bookmark_name_format, localFormat); + } + }; + + const insertVariable = (variable: string) => { + const input = inputRef.current; + if (input) { + const start = input.selectionStart || 0; + const end = input.selectionEnd || 0; + const newFormat = localFormat.substring(0, start) + variable + localFormat.substring(end); + setLocalFormat(newFormat); + updateSetting(UserSettingsRemoteProps.bookmark_name_format, newFormat); + + setTimeout(() => { + input.focus(); + input.setSelectionRange(start + variable.length, start + variable.length); + }, 0); + } else { + const newFormat = localFormat + variable; + setLocalFormat(newFormat); + updateSetting(UserSettingsRemoteProps.bookmark_name_format, newFormat); + } + }; + + const resetToDefault = () => { + const defaultFormat = '{chain_index} {sig_letters} {dest_type} {size} {mass_status} {time_status}'; + setLocalFormat(defaultFormat); + updateSetting(UserSettingsRemoteProps.bookmark_name_format, defaultFormat); + }; + + return ( +
+
+ + + Reset to Default + +
+ setLocalFormat(e.target.value)} + onBlur={handleBlur} + placeholder="e.g. {chain_index} {sig_letters} {dest_type} {size} {mass_status} {time_status}" + /> +
+ Live Preview: + {preview || Empty} +
+ +
+

Available Variables (Click to insert)

+
    + {VARIABLES.map(v => ( +
  • + insertVariable(v.id)} + > + {v.id} + {' '} + - {v.desc} +
  • + ))} +
+
+
+ ); +}; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts index f177ab38..a9251f12 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts @@ -7,6 +7,9 @@ export const DEFAULT_REMOTE_SETTINGS = { [UserSettingsRemoteProps.select_on_spash]: false, [UserSettingsRemoteProps.delete_connection_with_sigs]: false, [UserSettingsRemoteProps.bookmark_name_format]: '', + [UserSettingsRemoteProps.bookmark_wormholes_start_at_zero]: false, + [UserSettingsRemoteProps.bookmark_auto_copy]: true, + [UserSettingsRemoteProps.bookmark_auto_temp_name]: '', }; export const UserSettingsRemoteList = [ @@ -14,6 +17,9 @@ export const UserSettingsRemoteList = [ UserSettingsRemoteProps.select_on_spash, UserSettingsRemoteProps.delete_connection_with_sigs, UserSettingsRemoteProps.bookmark_name_format, + UserSettingsRemoteProps.bookmark_wormholes_start_at_zero, + UserSettingsRemoteProps.bookmark_auto_copy, + UserSettingsRemoteProps.bookmark_auto_temp_name, ]; // export const COMMON_CHECKBOXES_PROPS: SettingsListItem[] = [ @@ -48,12 +54,30 @@ export const SIGNATURES_CHECKBOXES_PROPS: SettingsListItem[] = [ label: 'Show unsplashed signatures', type: 'checkbox', }, +]; + +export const BOOKMARKS_SETTINGS_PROPS: SettingsListItem[] = [ { - prop: UserSettingsRemoteProps.bookmark_name_format, - label: 'Bookmark Name Format', - type: 'text', - placeholder: 'e.g. {chain_index_letters} {sig_letters} {dest_type} {size} {time_status} {mass_status}', - helperText: 'Variables: {index}, {chain_index}, {index_letter}, {chain_index_letters}, {sig_letters}, {sig}, {dest_type}, {type}, {size}, {mass}, {time_status}, {mass_status}, {temporary_name}, {description}', + prop: UserSettingsRemoteProps.bookmark_auto_copy, + label: 'Automatically copy bookmarks to clipboard', + type: 'checkbox', + }, + { + prop: UserSettingsRemoteProps.bookmark_wormholes_start_at_zero, + label: 'Start wormhole indices at 0', + type: 'checkbox', + }, + { + prop: UserSettingsRemoteProps.bookmark_auto_temp_name, + label: 'Auto-fill wormhole temporary name', + type: 'dropdown', + options: [ + { label: 'Disabled', value: '' }, + { label: 'Numeric index (1, 2, 3)', value: 'index' }, + { label: 'Letter index (A, B, C)', value: 'index_letter' }, + { label: 'Numeric chain (11, 12, 121)', value: 'chain_index' }, + { label: 'Letter chain (A, A1, A21)', value: 'chain_index_letters' }, + ], }, ]; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts index 4500e8e6..37cae0d8 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts @@ -5,6 +5,9 @@ export enum UserSettingsRemoteProps { select_on_spash = 'select_on_spash', delete_connection_with_sigs = 'delete_connection_with_sigs', bookmark_name_format = 'bookmark_name_format', + bookmark_wormholes_start_at_zero = 'bookmark_wormholes_start_at_zero', + bookmark_auto_copy = 'bookmark_auto_copy', + bookmark_auto_temp_name = 'bookmark_auto_temp_name', } export type UserSettingsRemote = { @@ -12,6 +15,9 @@ export type UserSettingsRemote = { select_on_spash: boolean; delete_connection_with_sigs: boolean; bookmark_name_format: string; + bookmark_wormholes_start_at_zero: boolean; + bookmark_auto_copy: boolean; + bookmark_auto_temp_name: string; }; export type UserSettings = UserSettingsRemote & InterfaceStoredSettings; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx index cd9aa5d2..3628ce94 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx @@ -5,7 +5,7 @@ import { import { getSystemClassGroup } from '@/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts'; import { SystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx'; import { WdButton } from '@/hooks/Mapper/components/ui-kit'; -import { calculateBookmarkIndex, copyToClipboard, formatBookmarkName } from '@/hooks/Mapper/helpers/bookmarkFormatHelper.ts'; +import { calculateBookmarkIndex, copyToClipboard, formatBookmarkName, numberToLetters } from '@/hooks/Mapper/helpers/bookmarkFormatHelper.ts'; import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { MassState, OutCommand, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types'; @@ -126,14 +126,20 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map console.warn('Failed to fetch user settings', e); } - if (currentSettings?.bookmark_name_format) { + if (currentSettings?.bookmark_name_format || currentSettings?.bookmark_auto_temp_name) { const info = parseSignatureCustomInfo(out.custom_info); let bookmarkIndex = info.bookmark_index; - if (!bookmarkIndex) { + if (bookmarkIndex == null) { const currentSystem = systems.find((s: any) => s.id === systemId); const solarSystemIdStr = currentSystem?.system_static_info?.solar_system_id?.toString() || systemId; - const calculated = calculateBookmarkIndex(systemSignatures, systemId, solarSystemIdStr, out.eve_id); + const calculated = calculateBookmarkIndex( + systemSignatures, + systemId, + solarSystemIdStr, + out.eve_id, + currentSettings?.bookmark_wormholes_start_at_zero, + ); bookmarkIndex = calculated.index; info.bookmark_index = calculated.index; info.bookmark_index_chained = calculated.chained; @@ -141,18 +147,42 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map out.custom_info = JSON.stringify(info); } - const targetSystem = values.linked_system ? systems.find((s: any) => s.id === values.linked_system) : null; - const targetSystemClassGroup = targetSystem?.system_static_info ? getSystemClassGroup(targetSystem.system_static_info.system_class) : null; + if (currentSettings?.bookmark_auto_temp_name && !out.temporary_name) { + let autoName = ''; + switch (currentSettings.bookmark_auto_temp_name) { + case 'index': + autoName = bookmarkIndex.toString(); + break; + case 'index_letter': + autoName = numberToLetters(bookmarkIndex, currentSettings.bookmark_wormholes_start_at_zero); + break; + case 'chain_index': + autoName = info.bookmark_index_chained || bookmarkIndex.toString(); + break; + case 'chain_index_letters': + autoName = info.bookmark_index_chained_letters || info.bookmark_index_chained || bookmarkIndex.toString(); + break; + } + if (autoName) { + out.temporary_name = autoName; + } + } - const formattedStr = formatBookmarkName( - currentSettings.bookmark_name_format, - out, - targetSystemClassGroup, - bookmarkIndex, - wormholesData, - ); - - await copyToClipboard(formattedStr); + if (currentSettings?.bookmark_name_format && currentSettings?.bookmark_auto_copy !== false) { + const targetSystem = values.linked_system ? systems.find((s: any) => s.id === values.linked_system) : null; + const targetSystemClassGroup = targetSystem?.system_static_info ? getSystemClassGroup(targetSystem.system_static_info.system_class) : null; + + const formattedStr = formatBookmarkName( + currentSettings.bookmark_name_format, + out, + targetSystemClassGroup, + bookmarkIndex, + wormholesData, + currentSettings.bookmark_wormholes_start_at_zero + ); + + await copyToClipboard(formattedStr); + } } } diff --git a/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts index 668393e3..49ba74ec 100644 --- a/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts +++ b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts @@ -69,7 +69,10 @@ const formatDestString = (dest: string | null | undefined): string => { return dest.charAt(0).toUpperCase() + dest.slice(1); }; -const numberToLetters = (num: number): string => { +export const numberToLetters = (num: number, startAtZero: boolean = false): string => { + if (startAtZero) { + num += 1; + } let letters = ''; while (num > 0) { const mod = (num - 1) % 26; @@ -84,6 +87,7 @@ export const calculateBookmarkIndex = ( currentSystemUuid: string, currentSolarSystemId: string, currentEveId: string, + startAtZero: boolean = false, ): { index: number; chained: string; chainedLetters: string } => { let parentBookmarkIndex: string | undefined; let parentBookmarkIndexLetters: string | undefined; @@ -123,15 +127,15 @@ export const calculateBookmarkIndex = ( const existingIndices = uniqueCurrentSigs .filter(sig => sig.eve_id !== currentEveId) .map(sig => parseSignatureCustomInfo(sig.custom_info).bookmark_index) - .filter((i): i is number => typeof i === 'number' && i > 0); + .filter((i): i is number => typeof i === 'number' && i >= 0); - let i = 1; + let i = startAtZero ? 0 : 1; while (existingIndices.includes(i)) { i++; } const chained = parentBookmarkIndex !== undefined ? `${parentBookmarkIndex}${i}` : `${i}`; - const chainedLetters = parentBookmarkIndexLetters !== undefined ? `${parentBookmarkIndexLetters}${i}` : numberToLetters(i); + const chainedLetters = parentBookmarkIndexLetters !== undefined ? `${parentBookmarkIndexLetters}${i}` : numberToLetters(i, startAtZero); return { index: i, chained, chainedLetters }; }; @@ -142,6 +146,7 @@ export const formatBookmarkName = ( destSystemClass: string | null, bookmarkIndex: number, wormholesData: Record = {}, + startAtZero: boolean = false, ): string => { let result = formatStr; const info = parseSignatureCustomInfo(signature.custom_info); @@ -153,7 +158,7 @@ export const formatBookmarkName = ( result = result.replace(/\{chain_index\}/g, () => info.bookmark_index_chained || bookmarkIndex.toString()); // Replace {index_letter} - result = result.replace(/\{index_letter\}/g, () => numberToLetters(bookmarkIndex)); + result = result.replace(/\{index_letter\}/g, () => numberToLetters(bookmarkIndex, startAtZero)); // Replace {chain_index_letters} result = result.replace(/\{chain_index_letters\}/g, () => info.bookmark_index_chained_letters || info.bookmark_index_chained || bookmarkIndex.toString()); diff --git a/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex b/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex index 6fdd672b..e53199a2 100644 --- a/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex +++ b/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex @@ -236,7 +236,10 @@ defmodule WandererAppWeb.MapCoreEventHandler do "select_on_spash", "link_signature_on_splash", "delete_connection_with_sigs", - "bookmark_name_format" + "bookmark_name_format", + "bookmark_wormholes_start_at_zero", + "bookmark_auto_copy", + "bookmark_auto_temp_name" ]) |> Jason.encode!() From b3f1db6c7bcc94df80f6082e184c8e024c3009d5 Mon Sep 17 00:00:00 2001 From: Drew Bonasera Date: Tue, 28 Apr 2026 02:07:26 -0400 Subject: [PATCH 05/11] Refactors automatic bookmark generation and clipboard copy logic into a shared helper function. This new helper is now used by the signature settings and system link dialogs. - feat(mapper): Add handleAutoBookmark helper for bookmark generation and copying - refactor(mapper): Use handleAutoBookmark helper in SystemLinkSignatureDialog - refactor(mapper): Use handleAutoBookmark helper in SignatureSettings --- .../SystemLinkSignatureDialog.tsx | 112 +++++------------- .../SignatureSettings/SignatureSettings.tsx | 95 ++++----------- .../Mapper/helpers/bookmarkFormatHelper.ts | 74 ++++++++++++ 3 files changed, 131 insertions(+), 150 deletions(-) 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 de4fe7d1..ffd4212a 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx @@ -1,5 +1,5 @@ import { Dialog } from 'primereact/dialog'; -import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useSystemInfo } from '@/hooks/Mapper/components/hooks'; import { @@ -11,7 +11,7 @@ import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/ import { MULTI_DEST_WHS, ALL_DEST_TYPES_MAP, DEST_TYPES_MAP_MAP } from '@/hooks/Mapper/constants.ts'; import { SETTINGS_KEYS, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures'; import { getSystemClassGroup } from '@/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts'; -import { calculateBookmarkIndex, copyToClipboard, formatBookmarkName, numberToLetters } from '@/hooks/Mapper/helpers/bookmarkFormatHelper.ts'; +import { handleAutoBookmark } from '@/hooks/Mapper/helpers/bookmarkFormatHelper.ts'; import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; @@ -112,6 +112,14 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat settings: LINK_SIGNATURE_SETTINGS, }); + const [userSettings, setUserSettings] = useState(null); + + useEffect(() => { + outCommand({ type: OutCommand.getUserSettings, data: null }) + .then((res: any) => setUserSettings(res?.user_settings)) + .catch((e: any) => console.warn('Failed to fetch user settings', e)); + }, [outCommand]); + const handleSelect = useCallback( async (signature: SystemSignature) => { if (!signature) { @@ -120,85 +128,29 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat const { outCommand } = ref.current; - let currentSettings = null; - try { - const res = (await outCommand({ - type: OutCommand.getUserSettings, - data: null, - })) as any; - currentSettings = res?.user_settings; - } catch (e) { - console.warn('Failed to fetch user settings', e); - } + const sourceSystem = systems.find((s: any) => s.system_static_info?.solar_system_id === data.solar_system_source); + const systemUuid = sourceSystem?.id || data.solar_system_source.toString(); - let updatedSignature = signature; + const { updatedSignature, shouldUpdate } = await handleAutoBookmark( + signature, + userSettings, + systemSignatures, + systemUuid, + data.solar_system_source.toString(), + wormholesData, + targetSystemClassGroup + ); - if (signature.group === SignatureGroup.Wormhole && (currentSettings?.bookmark_name_format || currentSettings?.bookmark_auto_temp_name)) { - const info = parseSignatureCustomInfo(signature.custom_info); - let bookmarkIndex = info.bookmark_index; - - if (bookmarkIndex == null) { - const sourceSystem = systems.find((s: any) => s.system_static_info?.solar_system_id === data.solar_system_source); - const systemUuid = sourceSystem?.id || data.solar_system_source.toString(); - const calculated = calculateBookmarkIndex( - systemSignatures, - systemUuid, - data.solar_system_source.toString(), - signature.eve_id, - currentSettings?.bookmark_wormholes_start_at_zero, - ); - bookmarkIndex = calculated.index; - info.bookmark_index = calculated.index; - info.bookmark_index_chained = calculated.chained; - info.bookmark_index_chained_letters = calculated.chainedLetters; - updatedSignature = { ...signature, custom_info: JSON.stringify(info) }; - } - - if (currentSettings?.bookmark_auto_temp_name && !updatedSignature.temporary_name) { - let autoName = ''; - switch (currentSettings.bookmark_auto_temp_name) { - case 'index': - autoName = bookmarkIndex.toString(); - break; - case 'index_letter': - autoName = numberToLetters(bookmarkIndex, currentSettings.bookmark_wormholes_start_at_zero); - break; - case 'chain_index': - autoName = info.bookmark_index_chained || bookmarkIndex.toString(); - break; - case 'chain_index_letters': - autoName = info.bookmark_index_chained_letters || info.bookmark_index_chained || bookmarkIndex.toString(); - break; - } - if (autoName) { - updatedSignature = { ...updatedSignature, temporary_name: autoName }; - } - } - - if (currentSettings?.bookmark_name_format && currentSettings?.bookmark_auto_copy !== false) { - const formattedStr = formatBookmarkName( - currentSettings.bookmark_name_format, - updatedSignature, - targetSystemClassGroup, - bookmarkIndex, - wormholesData, - currentSettings.bookmark_wormholes_start_at_zero - ); - - await copyToClipboard(formattedStr); - } - - if (updatedSignature !== signature) { - await outCommand({ - type: OutCommand.updateSignatures, - data: { - system_id: `${data.solar_system_source}`, - updated: [updatedSignature], - removed: [], - deleteTimeout: 0, - }, - }); - } + if (shouldUpdate) { + await outCommand({ + type: OutCommand.updateSignatures, + data: { + system_id: `${data.solar_system_source}`, + updated: [updatedSignature], + removed: [], + deleteTimeout: 0, + }, + }); } await outCommand({ @@ -211,7 +163,7 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat setVisible(false); }, - [data, setVisible, signatures, targetSystemClassGroup, systemSignatures, systems, wormholesData], + [data, setVisible, userSettings, targetSystemClassGroup, systemSignatures, systems, wormholesData], ); diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx index 3628ce94..f0a5ad49 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx @@ -5,13 +5,13 @@ import { import { getSystemClassGroup } from '@/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts'; import { SystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx'; import { WdButton } from '@/hooks/Mapper/components/ui-kit'; -import { calculateBookmarkIndex, copyToClipboard, formatBookmarkName, numberToLetters } from '@/hooks/Mapper/helpers/bookmarkFormatHelper.ts'; +import { handleAutoBookmark } from '@/hooks/Mapper/helpers/bookmarkFormatHelper.ts'; import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { MassState, OutCommand, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types'; import { Dialog } from 'primereact/dialog'; import { InputText } from 'primereact/inputtext'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { Controller, FormProvider, useForm } from 'react-hook-form'; type SystemSignaturePrepared = Omit & { @@ -39,6 +39,14 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map const signatureForm = useForm>({}); + const [userSettings, setUserSettings] = useState(null); + + useEffect(() => { + outCommand({ type: OutCommand.getUserSettings, data: null }) + .then((res: any) => setUserSettings(res?.user_settings)) + .catch((e: any) => console.warn('Failed to fetch user settings', e)); + }, [outCommand]); + const handleSave = useCallback( // TODO: need fix async (e: any) => { @@ -115,75 +123,22 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map out = { ...out, group: group! }; if (group === SignatureGroup.Wormhole) { - let currentSettings = null; - try { - const res = (await outCommand({ - type: OutCommand.getUserSettings, - data: null, - })) as any; - currentSettings = res?.user_settings; - } catch (e) { - console.warn('Failed to fetch user settings', e); - } + const targetSystem = values.linked_system ? systems.find((s: any) => s.system_static_info?.solar_system_id?.toString() === values.linked_system) : null; + const targetSystemClassGroup = targetSystem?.system_static_info ? getSystemClassGroup(targetSystem.system_static_info.system_class) : null; - if (currentSettings?.bookmark_name_format || currentSettings?.bookmark_auto_temp_name) { - const info = parseSignatureCustomInfo(out.custom_info); + const currentSystem = systems.find((s: any) => s.id === systemId); + const solarSystemIdStr = currentSystem?.system_static_info?.solar_system_id?.toString() || systemId; - let bookmarkIndex = info.bookmark_index; - if (bookmarkIndex == null) { - const currentSystem = systems.find((s: any) => s.id === systemId); - const solarSystemIdStr = currentSystem?.system_static_info?.solar_system_id?.toString() || systemId; - const calculated = calculateBookmarkIndex( - systemSignatures, - systemId, - solarSystemIdStr, - out.eve_id, - currentSettings?.bookmark_wormholes_start_at_zero, - ); - bookmarkIndex = calculated.index; - info.bookmark_index = calculated.index; - info.bookmark_index_chained = calculated.chained; - info.bookmark_index_chained_letters = calculated.chainedLetters; - out.custom_info = JSON.stringify(info); - } - - if (currentSettings?.bookmark_auto_temp_name && !out.temporary_name) { - let autoName = ''; - switch (currentSettings.bookmark_auto_temp_name) { - case 'index': - autoName = bookmarkIndex.toString(); - break; - case 'index_letter': - autoName = numberToLetters(bookmarkIndex, currentSettings.bookmark_wormholes_start_at_zero); - break; - case 'chain_index': - autoName = info.bookmark_index_chained || bookmarkIndex.toString(); - break; - case 'chain_index_letters': - autoName = info.bookmark_index_chained_letters || info.bookmark_index_chained || bookmarkIndex.toString(); - break; - } - if (autoName) { - out.temporary_name = autoName; - } - } - - if (currentSettings?.bookmark_name_format && currentSettings?.bookmark_auto_copy !== false) { - const targetSystem = values.linked_system ? systems.find((s: any) => s.id === values.linked_system) : null; - const targetSystemClassGroup = targetSystem?.system_static_info ? getSystemClassGroup(targetSystem.system_static_info.system_class) : null; - - const formattedStr = formatBookmarkName( - currentSettings.bookmark_name_format, - out, - targetSystemClassGroup, - bookmarkIndex, - wormholesData, - currentSettings.bookmark_wormholes_start_at_zero - ); - - await copyToClipboard(formattedStr); - } - } + const { updatedSignature } = await handleAutoBookmark( + out, + userSettings, + systemSignatures, + systemId, + solarSystemIdStr, + wormholesData, + targetSystemClassGroup + ); + out = updatedSignature; } await outCommand({ @@ -214,7 +169,7 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map signatureForm.reset(); onHide(); }, - [signatureData, signatureForm, outCommand, systemId, onHide, systemSignatures, systems, wormholesData], + [signatureData, signatureForm, outCommand, systemId, onHide, systemSignatures, systems, wormholesData, userSettings], ); useEffect(() => { diff --git a/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts index 49ba74ec..3b894064 100644 --- a/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts +++ b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts @@ -259,3 +259,77 @@ export const copyToClipboard = async (text: string) => { console.warn('Failed to copy to clipboard', err); } }; + +export const handleAutoBookmark = async ( + signature: SystemSignature, + currentSettings: any, + systemSignatures: Record, + currentSystemId: string, + currentSolarSystemId: string, + wormholesData: Record, + targetSystemClassGroup: string | null +): Promise<{ updatedSignature: SystemSignature; shouldUpdate: boolean }> => { + let updatedSignature = signature; + let shouldUpdate = false; + + if (signature.group !== SignatureGroup.Wormhole || (!currentSettings?.bookmark_name_format && !currentSettings?.bookmark_auto_temp_name)) { + return { updatedSignature, shouldUpdate }; + } + + const info = parseSignatureCustomInfo(signature.custom_info); + let bookmarkIndex = info.bookmark_index; + + if (bookmarkIndex == null) { + const calculated = calculateBookmarkIndex( + systemSignatures, + currentSystemId, + currentSolarSystemId, + signature.eve_id, + currentSettings?.bookmark_wormholes_start_at_zero, + ); + bookmarkIndex = calculated.index; + info.bookmark_index = calculated.index; + info.bookmark_index_chained = calculated.chained; + info.bookmark_index_chained_letters = calculated.chainedLetters; + updatedSignature = { ...signature, custom_info: JSON.stringify(info) }; + shouldUpdate = true; + } + + if (currentSettings?.bookmark_auto_temp_name && !updatedSignature.temporary_name) { + let autoName = ''; + switch (currentSettings.bookmark_auto_temp_name) { + case 'index': + autoName = bookmarkIndex.toString(); + break; + case 'index_letter': + autoName = numberToLetters(bookmarkIndex, currentSettings.bookmark_wormholes_start_at_zero); + break; + case 'chain_index': + autoName = info.bookmark_index_chained || bookmarkIndex.toString(); + break; + case 'chain_index_letters': + autoName = info.bookmark_index_chained_letters || info.bookmark_index_chained || bookmarkIndex.toString(); + break; + } + if (autoName) { + updatedSignature = { ...updatedSignature, temporary_name: autoName }; + shouldUpdate = true; + } + } + + if (currentSettings?.bookmark_name_format && currentSettings?.bookmark_auto_copy !== false) { + const formattedStr = formatBookmarkName( + currentSettings.bookmark_name_format, + updatedSignature, + targetSystemClassGroup, + bookmarkIndex, + wormholesData, + currentSettings.bookmark_wormholes_start_at_zero + ); + + // Run this synchronously to avoid clipboard issues if possible + await copyToClipboard(formattedStr); + } + + return { updatedSignature, shouldUpdate }; +}; From 0a19f248ae4bbcd98d9bcf3516ed4460c8484b62 Mon Sep 17 00:00:00 2001 From: Drew Bonasera Date: Tue, 28 Apr 2026 11:03:46 -0400 Subject: [PATCH 06/11] Introduces automatic tagging and labeling for destination systems upon wormhole jumps. Users can now configure the format for these tags and labels in the map settings. - feat(mapper): Implement auto-tagging and auto-labeling logic for destination systems - feat(settings): Add UI options to configure system auto-tag and auto-label formats - feat(backend): Add new system auto-tag and auto-label settings to user preferences - refactor(settings): Extract shared auto-format options into a constant --- .../SystemLinkSignatureDialog.tsx | 74 ++++++++++++++++++- .../components/MapSettings/constants.ts | 32 ++++++-- .../components/MapSettings/types.ts | 4 + .../repositories/map_user_settings_repo.ex | 4 +- .../event_handlers/map_core_event_handler.ex | 4 +- 5 files changed, 108 insertions(+), 10 deletions(-) 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 ffd4212a..e30c7f81 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx @@ -11,8 +11,9 @@ import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/ import { MULTI_DEST_WHS, ALL_DEST_TYPES_MAP, DEST_TYPES_MAP_MAP } from '@/hooks/Mapper/constants.ts'; import { SETTINGS_KEYS, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures'; import { getSystemClassGroup } from '@/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts'; -import { handleAutoBookmark } from '@/hooks/Mapper/helpers/bookmarkFormatHelper.ts'; +import { handleAutoBookmark, numberToLetters } from '@/hooks/Mapper/helpers/bookmarkFormatHelper.ts'; import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo'; +import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts'; @@ -161,6 +162,77 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat }, }); + const systemAutoTag = userSettings?.system_auto_tag; + const systemCustomLabelName = userSettings?.system_custom_label_name; + + if (systemAutoTag || systemCustomLabelName) { + const info = parseSignatureCustomInfo(updatedSignature.custom_info); + const bIndex = info.bookmark_index ?? 0; + const startAtZero = userSettings?.bookmark_wormholes_start_at_zero; + const letter = numberToLetters(bIndex, startAtZero); + + const targetSystem = systems.find((s: any) => s.system_static_info?.solar_system_id === data.solar_system_target); + + if (targetSystem) { + if (systemAutoTag) { + let tagValue = ''; + switch (systemAutoTag) { + case 'index': + case 'chain_index': + tagValue = bIndex.toString(); + break; + case 'index_letter': + tagValue = letter; + break; + case 'chain_index_letters': + tagValue = info.bookmark_index_chained_letters === letter ? letter : bIndex.toString(); + break; + } + + if (tagValue) { + await outCommand({ + type: OutCommand.updateSystemTag, + data: { + system_id: targetSystem.id, + value: tagValue, + }, + }); + } + } + + if (systemCustomLabelName) { + let labelValue = ''; + switch (systemCustomLabelName) { + case 'index': + labelValue = bIndex.toString(); + break; + case 'index_letter': + labelValue = letter; + break; + case 'chain_index': + labelValue = info.bookmark_index_chained as string || bIndex.toString(); + break; + case 'chain_index_letters': + labelValue = info.bookmark_index_chained_letters as string || letter; + break; + } + + if (labelValue) { + const outLabel = new LabelsManager(targetSystem.labels ?? ''); + outLabel.updateCustomLabel(labelValue); + + await outCommand({ + type: OutCommand.updateSystemLabels, + data: { + system_id: targetSystem.id, + value: outLabel.toString(), + }, + }); + } + } + } + } + setVisible(false); }, [data, setVisible, userSettings, targetSystemClassGroup, systemSignatures, systems, wormholesData], diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts index a9251f12..836e6b75 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts @@ -10,8 +10,18 @@ export const DEFAULT_REMOTE_SETTINGS = { [UserSettingsRemoteProps.bookmark_wormholes_start_at_zero]: false, [UserSettingsRemoteProps.bookmark_auto_copy]: true, [UserSettingsRemoteProps.bookmark_auto_temp_name]: '', + [UserSettingsRemoteProps.system_auto_tag]: '', + [UserSettingsRemoteProps.system_custom_label_name]: '', }; +export const AUTO_FORMAT_OPTIONS = [ + { label: 'Disabled', value: '' }, + { label: 'Numeric index (1, 2, 3)', value: 'index' }, + { label: 'Letter index (A, B, C)', value: 'index_letter' }, + { label: 'Numeric chain (11, 12, 121)', value: 'chain_index' }, + { label: 'Letter chain (A, A1, A21)', value: 'chain_index_letters' }, +]; + export const UserSettingsRemoteList = [ UserSettingsRemoteProps.link_signature_on_splash, UserSettingsRemoteProps.select_on_spash, @@ -20,6 +30,8 @@ export const UserSettingsRemoteList = [ UserSettingsRemoteProps.bookmark_wormholes_start_at_zero, UserSettingsRemoteProps.bookmark_auto_copy, UserSettingsRemoteProps.bookmark_auto_temp_name, + UserSettingsRemoteProps.system_auto_tag, + UserSettingsRemoteProps.system_custom_label_name, ]; // export const COMMON_CHECKBOXES_PROPS: SettingsListItem[] = [ @@ -71,13 +83,19 @@ export const BOOKMARKS_SETTINGS_PROPS: SettingsListItem[] = [ prop: UserSettingsRemoteProps.bookmark_auto_temp_name, label: 'Auto-fill wormhole temporary name', type: 'dropdown', - options: [ - { label: 'Disabled', value: '' }, - { label: 'Numeric index (1, 2, 3)', value: 'index' }, - { label: 'Letter index (A, B, C)', value: 'index_letter' }, - { label: 'Numeric chain (11, 12, 121)', value: 'chain_index' }, - { label: 'Letter chain (A, A1, A21)', value: 'chain_index_letters' }, - ], + options: AUTO_FORMAT_OPTIONS, + }, + { + prop: UserSettingsRemoteProps.system_auto_tag, + label: 'Auto-tag jumped system', + type: 'dropdown', + options: AUTO_FORMAT_OPTIONS, + }, + { + prop: UserSettingsRemoteProps.system_custom_label_name, + label: 'Auto-label jumped system', + type: 'dropdown', + options: AUTO_FORMAT_OPTIONS, }, ]; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts index 37cae0d8..3fcd35ff 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts @@ -8,6 +8,8 @@ export enum UserSettingsRemoteProps { bookmark_wormholes_start_at_zero = 'bookmark_wormholes_start_at_zero', bookmark_auto_copy = 'bookmark_auto_copy', bookmark_auto_temp_name = 'bookmark_auto_temp_name', + system_auto_tag = 'system_auto_tag', + system_custom_label_name = 'system_custom_label_name', } export type UserSettingsRemote = { @@ -18,6 +20,8 @@ export type UserSettingsRemote = { bookmark_wormholes_start_at_zero: boolean; bookmark_auto_copy: boolean; bookmark_auto_temp_name: string; + system_auto_tag: string; + system_custom_label_name: string; }; export type UserSettings = UserSettingsRemote & InterfaceStoredSettings; diff --git a/lib/wanderer_app/repositories/map_user_settings_repo.ex b/lib/wanderer_app/repositories/map_user_settings_repo.ex index 545daf06..6d023a5b 100644 --- a/lib/wanderer_app/repositories/map_user_settings_repo.ex +++ b/lib/wanderer_app/repositories/map_user_settings_repo.ex @@ -6,7 +6,9 @@ defmodule WandererApp.MapUserSettingsRepo do "link_signature_on_splash" => false, "delete_connection_with_sigs" => false, "primary_character_id" => nil, - "bookmark_name_format" => "" + "bookmark_name_format" => "", + "system_auto_tag" => "", + "system_custom_label_name" => "" } def get(map_id, user_id) do diff --git a/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex b/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex index e53199a2..acbd321c 100644 --- a/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex +++ b/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex @@ -239,7 +239,9 @@ defmodule WandererAppWeb.MapCoreEventHandler do "bookmark_name_format", "bookmark_wormholes_start_at_zero", "bookmark_auto_copy", - "bookmark_auto_temp_name" + "bookmark_auto_temp_name", + "system_auto_tag", + "system_custom_label_name" ]) |> Jason.encode!() From 4e75b31840705aab3fb2141a12c8660a7ae47578 Mon Sep 17 00:00:00 2001 From: Drew Bonasera Date: Wed, 29 Apr 2026 06:48:43 -0400 Subject: [PATCH 07/11] Refactors signature linking logic into a new hook and updates the bookmarks settings. The bookmarks settings tab now includes a new component with a dismissible warning. - refactor(dialog): Extract signature linking logic into useLinkSignature hook - feat(settings): Create BookmarksSettings component with dismissible warning - chore(settings): Add migration for hideBookmarkWarning setting and bump version - style(settings): Increase dialog height and improve scrollbars --- .../SystemLinkSignatureDialog.tsx | 129 +--------------- .../hooks/useLinkSignature.ts | 144 ++++++++++++++++++ .../components/MapSettings/MapSettings.tsx | 29 ++-- .../components/BookmarkNameFormatSetting.tsx | 2 +- .../components/BookmarksSettings.tsx | 33 ++++ .../hooks/Mapper/mapRootProvider/constants.ts | 1 + .../mapRootProvider/migrations/list/index.ts | 3 +- .../mapRootProvider/migrations/list/to_4.ts | 28 ++++ .../js/hooks/Mapper/mapRootProvider/types.ts | 1 + .../hooks/Mapper/mapRootProvider/version.ts | 2 +- 10 files changed, 226 insertions(+), 146 deletions(-) create mode 100644 assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts create mode 100644 assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarksSettings.tsx create mode 100644 assets/js/hooks/Mapper/mapRootProvider/migrations/list/to_4.ts 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 e30c7f81..246040b4 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx @@ -1,5 +1,5 @@ import { Dialog } from 'primereact/dialog'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { useSystemInfo } from '@/hooks/Mapper/components/hooks'; import { @@ -11,13 +11,11 @@ import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/ import { MULTI_DEST_WHS, ALL_DEST_TYPES_MAP, DEST_TYPES_MAP_MAP } from '@/hooks/Mapper/constants.ts'; import { SETTINGS_KEYS, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures'; import { getSystemClassGroup } from '@/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts'; -import { handleAutoBookmark, numberToLetters } from '@/hooks/Mapper/helpers/bookmarkFormatHelper.ts'; import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo'; -import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; -import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts'; import { useSystemSignaturesData } from '../../widgets/SystemSignatures/hooks/useSystemSignaturesData'; +import { useLinkSignature } from './hooks/useLinkSignature'; const MULTI_DEST_TYPES = MULTI_DEST_WHS.map((type: string) => WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME[type].shortName); @@ -41,13 +39,9 @@ interface ExtendedSignatureCustomInfo { export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => { const { - outCommand, - data: { wormholes, systemSignatures, systems, wormholesData }, + data: { wormholes }, } = useMapRootState(); - const ref = useRef({ outCommand }); - ref.current = { outCommand }; - // Get system info for the target system const { staticInfo: targetSystemInfo, dynamicInfo: targetSystemDynamicInfo } = useSystemInfo({ systemId: `${data.solar_system_target}`, @@ -113,13 +107,7 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat settings: LINK_SIGNATURE_SETTINGS, }); - const [userSettings, setUserSettings] = useState(null); - - useEffect(() => { - outCommand({ type: OutCommand.getUserSettings, data: null }) - .then((res: any) => setUserSettings(res?.user_settings)) - .catch((e: any) => console.warn('Failed to fetch user settings', e)); - }, [outCommand]); + const { handleLinkSignature } = useLinkSignature({ data, targetSystemClassGroup }); const handleSelect = useCallback( async (signature: SystemSignature) => { @@ -127,118 +115,13 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat return; } - const { outCommand } = ref.current; - - const sourceSystem = systems.find((s: any) => s.system_static_info?.solar_system_id === data.solar_system_source); - const systemUuid = sourceSystem?.id || data.solar_system_source.toString(); - - const { updatedSignature, shouldUpdate } = await handleAutoBookmark( - signature, - userSettings, - systemSignatures, - systemUuid, - data.solar_system_source.toString(), - wormholesData, - targetSystemClassGroup - ); - - if (shouldUpdate) { - await outCommand({ - type: OutCommand.updateSignatures, - data: { - system_id: `${data.solar_system_source}`, - updated: [updatedSignature], - removed: [], - deleteTimeout: 0, - }, - }); - } - - await outCommand({ - type: OutCommand.linkSignatureToSystem, - data: { - ...data, - signature_eve_id: signature.eve_id, - }, - }); - - const systemAutoTag = userSettings?.system_auto_tag; - const systemCustomLabelName = userSettings?.system_custom_label_name; - - if (systemAutoTag || systemCustomLabelName) { - const info = parseSignatureCustomInfo(updatedSignature.custom_info); - const bIndex = info.bookmark_index ?? 0; - const startAtZero = userSettings?.bookmark_wormholes_start_at_zero; - const letter = numberToLetters(bIndex, startAtZero); - - const targetSystem = systems.find((s: any) => s.system_static_info?.solar_system_id === data.solar_system_target); - - if (targetSystem) { - if (systemAutoTag) { - let tagValue = ''; - switch (systemAutoTag) { - case 'index': - case 'chain_index': - tagValue = bIndex.toString(); - break; - case 'index_letter': - tagValue = letter; - break; - case 'chain_index_letters': - tagValue = info.bookmark_index_chained_letters === letter ? letter : bIndex.toString(); - break; - } - - if (tagValue) { - await outCommand({ - type: OutCommand.updateSystemTag, - data: { - system_id: targetSystem.id, - value: tagValue, - }, - }); - } - } - - if (systemCustomLabelName) { - let labelValue = ''; - switch (systemCustomLabelName) { - case 'index': - labelValue = bIndex.toString(); - break; - case 'index_letter': - labelValue = letter; - break; - case 'chain_index': - labelValue = info.bookmark_index_chained as string || bIndex.toString(); - break; - case 'chain_index_letters': - labelValue = info.bookmark_index_chained_letters as string || letter; - break; - } - - if (labelValue) { - const outLabel = new LabelsManager(targetSystem.labels ?? ''); - outLabel.updateCustomLabel(labelValue); - - await outCommand({ - type: OutCommand.updateSystemLabels, - data: { - system_id: targetSystem.id, - value: outLabel.toString(), - }, - }); - } - } - } - } + await handleLinkSignature(signature); setVisible(false); }, - [data, setVisible, userSettings, targetSystemClassGroup, systemSignatures, systems, wormholesData], + [handleLinkSignature, setVisible], ); - useEffect(() => { if (!targetSystemDynamicInfo) { handleHide(); diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts new file mode 100644 index 00000000..6a30353a --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts @@ -0,0 +1,144 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { handleAutoBookmark, numberToLetters } from '@/hooks/Mapper/helpers/bookmarkFormatHelper.ts'; +import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo'; +import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; +import { CommandLinkSignatureToSystem, SystemSignature } from '@/hooks/Mapper/types'; +import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts'; +import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts'; + +export interface UseLinkSignatureProps { + data: CommandLinkSignatureToSystem; + targetSystemClassGroup: string | null; +} + +export const useLinkSignature = ({ data, targetSystemClassGroup }: UseLinkSignatureProps) => { + const { + outCommand, + data: { systemSignatures, systems, wormholesData }, + } = useMapRootState(); + + const ref = useRef({ outCommand }); + ref.current = { outCommand }; + + const [userSettings, setUserSettings] = useState(null); + + useEffect(() => { + outCommand({ type: OutCommand.getUserSettings, data: null }) + .then((res: any) => setUserSettings(res?.user_settings)) + .catch((e: any) => console.warn('Failed to fetch user settings', e)); + }, [outCommand]); + + const handleLinkSignature = useCallback( + async (signature: SystemSignature) => { + const { outCommand } = ref.current; + + const sourceSystem = systems.find((s: any) => s.system_static_info?.solar_system_id === data.solar_system_source); + const systemUuid = sourceSystem?.id || data.solar_system_source.toString(); + + const { updatedSignature, shouldUpdate } = await handleAutoBookmark( + signature, + userSettings, + systemSignatures, + systemUuid, + data.solar_system_source.toString(), + wormholesData, + targetSystemClassGroup + ); + + if (shouldUpdate) { + await outCommand({ + type: OutCommand.updateSignatures, + data: { + system_id: `${data.solar_system_source}`, + updated: [updatedSignature], + removed: [], + deleteTimeout: 0, + }, + }); + } + + await outCommand({ + type: OutCommand.linkSignatureToSystem, + data: { + ...data, + signature_eve_id: signature.eve_id, + }, + }); + + const systemAutoTag = userSettings?.system_auto_tag; + const systemCustomLabelName = userSettings?.system_custom_label_name; + + if (systemAutoTag || systemCustomLabelName) { + const info = parseSignatureCustomInfo(updatedSignature.custom_info); + const bIndex = info.bookmark_index ?? 0; + const startAtZero = userSettings?.bookmark_wormholes_start_at_zero; + const letter = numberToLetters(bIndex, startAtZero); + + const targetSystem = systems.find((s: any) => s.system_static_info?.solar_system_id === data.solar_system_target); + + if (targetSystem) { + if (systemAutoTag) { + let tagValue = ''; + switch (systemAutoTag) { + case 'index': + case 'chain_index': + tagValue = bIndex.toString(); + break; + case 'index_letter': + tagValue = letter; + break; + case 'chain_index_letters': + tagValue = info.bookmark_index_chained_letters === letter ? letter : bIndex.toString(); + break; + } + + if (tagValue) { + await outCommand({ + type: OutCommand.updateSystemTag, + data: { + system_id: targetSystem.id, + value: tagValue, + }, + }); + } + } + + if (systemCustomLabelName) { + let labelValue = ''; + switch (systemCustomLabelName) { + case 'index': + labelValue = bIndex.toString(); + break; + case 'index_letter': + labelValue = letter; + break; + case 'chain_index': + labelValue = (info.bookmark_index_chained as string) || bIndex.toString(); + break; + case 'chain_index_letters': + labelValue = (info.bookmark_index_chained_letters as string) || letter; + break; + } + + if (labelValue) { + const outLabel = new LabelsManager(targetSystem.labels ?? ''); + outLabel.updateCustomLabel(labelValue); + + await outCommand({ + type: OutCommand.updateSystemLabels, + data: { + system_id: targetSystem.id, + value: outLabel.toString(), + }, + }); + } + } + } + } + }, + [data, userSettings, targetSystemClassGroup, systemSignatures, systems, wormholesData], + ); + + return { handleLinkSignature }; +}; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettings.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettings.tsx index aa756a9a..62611b24 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettings.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettings.tsx @@ -4,14 +4,14 @@ import { useCallback, useRef, useState } from 'react'; import { TabPanel, TabView } from 'primereact/tabview'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { OutCommand, UserPermission } from '@/hooks/Mapper/types'; -import { BOOKMARKS_SETTINGS_PROPS, CONNECTIONS_CHECKBOXES_PROPS, SIGNATURES_CHECKBOXES_PROPS, SYSTEMS_CHECKBOXES_PROPS } from './constants.ts'; +import { CONNECTIONS_CHECKBOXES_PROPS, SIGNATURES_CHECKBOXES_PROPS, SYSTEMS_CHECKBOXES_PROPS } from './constants.ts'; import { MapSettingsProvider, useMapSettings, } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx'; import { WidgetsSettings } from './components/WidgetsSettings'; import { CommonSettings } from './components/CommonSettings'; -import { BookmarkNameFormatSetting } from './components/BookmarkNameFormatSetting'; +import { BookmarksSettings } from './components/BookmarksSettings'; import { SettingsListItem } from './types.ts'; import { ImportExport } from './components/ImportExport.tsx'; import { ServerSettings } from './components/ServerSettings.tsx'; @@ -63,15 +63,16 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => { header="Map user settings" visible draggable={false} - className="w-[600px] h-[400px]" + className="w-[600px] h-[460px]" + contentClassName="custom-scrollbar" onShow={handleShow} onHide={handleHide} > -
-
+
+
setActiveIndex(e.index)} > @@ -90,20 +91,8 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => { {renderSettingsList(SIGNATURES_CHECKBOXES_PROPS)} - -
- {!settings.link_signature_on_splash && ( -
- ⚠️ It is highly recommended to enable 'Link signature on splash' (in the Signatures tab) to fully utilize automatic bookmark naming. -
- )} - -
- {renderSettingsList(BOOKMARKS_SETTINGS_PROPS)} -
- - -
+ + diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx index c7687eed..b7d27f2b 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx @@ -126,7 +126,7 @@ export const BookmarkNameFormatSetting = () => { {preview || Empty}
-
+

Available Variables (Click to insert)

    {VARIABLES.map(v => ( diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarksSettings.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarksSettings.tsx new file mode 100644 index 00000000..88cc6eeb --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarksSettings.tsx @@ -0,0 +1,33 @@ +import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; +import { useMapSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx'; +import { BOOKMARKS_SETTINGS_PROPS } from '../constants.ts'; +import { BookmarkNameFormatSetting } from './BookmarkNameFormatSetting'; + +export const BookmarksSettings = () => { + const { + storedSettings: { interfaceSettings, setInterfaceSettings }, + } = useMapRootState(); + const { renderSettingItem, settings } = useMapSettings(); + + return ( +
    + {!settings.link_signature_on_splash && !interfaceSettings.hideBookmarkWarning && ( +
    + ⚠️ It is highly recommended to enable 'Link signature on splash' (in the Signatures tab) to fully utilize + automatic bookmark naming. + +
    + )} + +
    {BOOKMARKS_SETTINGS_PROPS.map(renderSettingItem)}
    + + +
    + ); +}; diff --git a/assets/js/hooks/Mapper/mapRootProvider/constants.ts b/assets/js/hooks/Mapper/mapRootProvider/constants.ts index f103efb5..4a2c4a5e 100644 --- a/assets/js/hooks/Mapper/mapRootProvider/constants.ts +++ b/assets/js/hooks/Mapper/mapRootProvider/constants.ts @@ -22,6 +22,7 @@ export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = { theme: AvailableThemes.default, pingsPlacement: PingsPlacement.rightTop, minimapPlacement: MiniMapPlacement.rightBottom, + hideBookmarkWarning: false, }; export const DEFAULT_ROUTES_SETTINGS: RoutesType = { diff --git a/assets/js/hooks/Mapper/mapRootProvider/migrations/list/index.ts b/assets/js/hooks/Mapper/mapRootProvider/migrations/list/index.ts index 882adaa6..e000151f 100644 --- a/assets/js/hooks/Mapper/mapRootProvider/migrations/list/index.ts +++ b/assets/js/hooks/Mapper/mapRootProvider/migrations/list/index.ts @@ -1,6 +1,7 @@ import { to_1 } from './to_1.ts'; import { to_2 } from './to_2.ts'; import { to_3 } from './to_3.ts'; +import { to_4 } from './to_4.ts'; import { MigrationStructure } from '@/hooks/Mapper/mapRootProvider/types.ts'; -export default [to_1, to_2, to_3] as MigrationStructure[]; +export default [to_1, to_2, to_3, to_4] as MigrationStructure[]; diff --git a/assets/js/hooks/Mapper/mapRootProvider/migrations/list/to_4.ts b/assets/js/hooks/Mapper/mapRootProvider/migrations/list/to_4.ts new file mode 100644 index 00000000..6d49c7a5 --- /dev/null +++ b/assets/js/hooks/Mapper/mapRootProvider/migrations/list/to_4.ts @@ -0,0 +1,28 @@ +import { MigrationStructure } from '@/hooks/Mapper/mapRootProvider/types.ts'; +import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/constants.ts'; + +export const to_4: MigrationStructure = { + to: 4, + up: (prev: any) => { + let hideBookmarkWarning = false; + + // Check if the old unmanaged setting exists + const unmanagedSetting = localStorage.getItem('hide_bookmark_warning'); + if (unmanagedSetting) { + hideBookmarkWarning = unmanagedSetting === 'true'; + localStorage.removeItem('hide_bookmark_warning'); + } + + const interfaceSettings = prev?.interface || {}; + + return { + ...prev, + interface: { + ...STORED_INTERFACE_DEFAULT_VALUES, + ...interfaceSettings, + // Carry over the unmanaged setting if it's true, or just use what interfaceSettings has + hideBookmarkWarning: hideBookmarkWarning || interfaceSettings.hideBookmarkWarning || false, + }, + }; + }, +}; diff --git a/assets/js/hooks/Mapper/mapRootProvider/types.ts b/assets/js/hooks/Mapper/mapRootProvider/types.ts index 290b4fc5..76e5db3f 100644 --- a/assets/js/hooks/Mapper/mapRootProvider/types.ts +++ b/assets/js/hooks/Mapper/mapRootProvider/types.ts @@ -31,6 +31,7 @@ export type InterfaceStoredSettings = { theme: AvailableThemes; minimapPlacement: MiniMapPlacement; pingsPlacement: PingsPlacement; + hideBookmarkWarning: boolean; }; export type RoutesType = { diff --git a/assets/js/hooks/Mapper/mapRootProvider/version.ts b/assets/js/hooks/Mapper/mapRootProvider/version.ts index 2d56ea8f..a9596f07 100644 --- a/assets/js/hooks/Mapper/mapRootProvider/version.ts +++ b/assets/js/hooks/Mapper/mapRootProvider/version.ts @@ -1,4 +1,4 @@ -export const STORED_SETTINGS_VERSION = 3; +export const STORED_SETTINGS_VERSION = 4; export const LS_KEY_LEGASY = 'map-user-settings'; export const LS_KEY = 'map-user-settings-v3'; From a3e096cc44986dacb5ad55445d85758107152958 Mon Sep 17 00:00:00 2001 From: Drew Bonasera Date: Wed, 29 Apr 2026 10:57:48 -0400 Subject: [PATCH 08/11] Adds advanced customization for auto-bookmark names and introduces a new `{dest_class_index}` variable. Users can now override the string output for format variables like time, mass, and destination class. - feat(mapper): Add UI for advanced customization of bookmark format variables - feat(mapper): Implement custom string mapping and `{dest_class_index}` logic for bookmark names - feat(settings): Add `bookmark_custom_mapping` to user settings - fix(mapper): Treat linked signatures as wormholes for auto-bookmarking --- .../hooks/useLinkSignature.ts | 6 +- .../MapSettings/MapSettingsProvider.tsx | 4 +- .../components/BookmarkNameFormatSetting.tsx | 173 +++++++++++++++++- .../components/MapSettings/constants.ts | 2 + .../components/MapSettings/types.ts | 2 + .../Mapper/helpers/bookmarkFormatHelper.ts | 125 +++++++++++-- .../repositories/map_user_settings_repo.ex | 1 + .../event_handlers/map_core_event_handler.ex | 1 + 8 files changed, 283 insertions(+), 31 deletions(-) diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts index 6a30353a..57470e5c 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts @@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { handleAutoBookmark, numberToLetters } from '@/hooks/Mapper/helpers/bookmarkFormatHelper.ts'; import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; -import { CommandLinkSignatureToSystem, SystemSignature } from '@/hooks/Mapper/types'; +import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts'; import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts'; @@ -36,8 +36,10 @@ export const useLinkSignature = ({ data, targetSystemClassGroup }: UseLinkSignat const sourceSystem = systems.find((s: any) => s.system_static_info?.solar_system_id === data.solar_system_source); const systemUuid = sourceSystem?.id || data.solar_system_source.toString(); + const signatureToLink = { ...signature, group: SignatureGroup.Wormhole }; + const { updatedSignature, shouldUpdate } = await handleAutoBookmark( - signature, + signatureToLink, userSettings, systemSignatures, systemUuid, diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx index 3f84f92b..96f14954 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx @@ -27,7 +27,7 @@ import { WithChildren } from '@/hooks/Mapper/types/common.ts'; type MapSettingsContextType = { renderSettingItem: (item: SettingsListItem) => ReactNode; - updateSetting: (prop: keyof UserSettings, value: boolean | string) => Promise; + updateSetting: (prop: keyof UserSettings, value: boolean | string | Record) => Promise; setUserRemoteSettings: Dispatch>; settings: UserSettings; }; @@ -54,7 +54,7 @@ export const MapSettingsProvider = ({ children }: WithChildren) => { const refVars = useRef({ mergedSettings, userRemoteSettings, interfaceSettings, outCommand, setInterfaceSettings }); refVars.current = { mergedSettings, userRemoteSettings, interfaceSettings, outCommand, setInterfaceSettings }; - const handleSettingChange = useCallback(async (prop: keyof UserSettings, value: boolean | string) => { + const handleSettingChange = useCallback(async (prop: keyof UserSettings, value: boolean | string | Record) => { const { userRemoteSettings, interfaceSettings, outCommand, setInterfaceSettings } = refVars.current; if (UserSettingsRemoteList.includes(prop as any)) { diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx index b7d27f2b..da60e5d9 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx @@ -26,10 +26,11 @@ const VARIABLES = [ { id: '{sig_letters}', desc: 'First 3 chars of signature (e.g., ABC)' }, { id: '{sig}', desc: 'Full signature ID (e.g., ABC-123)' }, { id: '{dest_type}', desc: 'Destination class (e.g., C5, HS, Thera)' }, + { id: '{dest_class_index}', desc: 'Letter index for multiple holes to same class (empty if only 1, otherwise a, b, c...)' }, { id: '{type}', desc: 'Wormhole type (e.g., K162, H900)' }, { id: '{size}', desc: 'Hole size (e.g., S, M, XL)' }, { id: '{mass}', desc: 'Total mass in bil (e.g., 3.3)' }, - { id: '{time_status}', desc: 'Time remaining (e.g., EoL, 4H, 16H)' }, + { id: '{time_status}', desc: 'Time remaining (e.g., 1H, 4H, 16H)' }, { id: '{mass_status}', desc: 'Mass remaining (e.g., Destab, Crit)' }, { id: '{temporary_name}', desc: 'Temporary name if set' }, { id: '{description}', desc: 'Custom description' }, @@ -38,40 +39,71 @@ const VARIABLES = [ export const BookmarkNameFormatSetting = () => { const { settings, updateSetting } = useMapSettings(); const formatStr = settings.bookmark_name_format || ''; + const customMapping = settings.bookmark_custom_mapping || {}; const inputRef = useRef(null); const [localFormat, setLocalFormat] = useState(formatStr); + const [localMapping, setLocalMapping] = useState(customMapping); + const [showAdvanced, setShowAdvanced] = useState(false); useEffect(() => { setLocalFormat(formatStr); }, [formatStr]); + useEffect(() => { + setLocalMapping(customMapping); + }, [customMapping]); + const preview = useMemo(() => { const isZero = settings.bookmark_wormholes_start_at_zero; + const sep = localMapping?.chain_separator || ''; - const chainNum = isZero ? `001` : `112`; - const chainLet = isZero ? `A01` : `A12`; + const chainNum = isZero ? `0${sep}0${sep}1` : `1${sep}1${sep}2`; + const chainLet = isZero ? `A${sep}0${sep}1` : `A${sep}1${sep}2`; const currentIndex = isZero ? 1 : 2; const dummySig: SystemSignature = { ...DUMMY_SIG_BASE, + type: 'V283', custom_info: JSON.stringify({ time_status: TimeStatus._1h, - mass_status: MassState.half, + mass_status: MassState.verge, bookmark_index_chained: chainNum, bookmark_index_chained_letters: chainLet, }), }; + const otherDummySig: SystemSignature = { + ...DUMMY_SIG_BASE, + type: 'V283', + eve_id: 'DEF-456', + name: 'DEF-456', + custom_info: JSON.stringify({ + time_status: TimeStatus._1h, + mass_status: MassState.verge, + }) + }; + + const dummyWormholesData = { + 'V283': { + total_mass: 3300000000, + max_mass_per_jump: 62000000, + dest: ['hs'] + } as any + }; + return formatBookmarkName( localFormat, dummySig, 'HS', currentIndex, - {}, - isZero + dummyWormholesData, + isZero, + localMapping, + { 'preview_sys': [otherDummySig] }, + 'preview_sys' ); - }, [localFormat, settings.bookmark_wormholes_start_at_zero]); + }, [localFormat, settings.bookmark_wormholes_start_at_zero, localMapping]); const handleBlur = () => { if (localFormat !== formatStr) { @@ -105,6 +137,43 @@ export const BookmarkNameFormatSetting = () => { updateSetting(UserSettingsRemoteProps.bookmark_name_format, defaultFormat); }; + const renderMappingInput = (key: string, label: string, defaultVal: string) => { + const value = localMapping[key] !== undefined ? localMapping[key] : defaultVal; + return ( +
    + + { + setLocalMapping(prev => { + const newMapping = { ...prev }; + newMapping[key] = e.target.value; + return newMapping; + }); + }} + onBlur={(e) => { + const val = e.target.value; + setLocalMapping(prev => { + const currentVal = prev[key] !== undefined ? prev[key] : defaultVal; + if (val === currentVal) return prev; + + const newMapping = { ...prev }; + if (val === defaultVal) { + delete newMapping[key]; + } else { + newMapping[key] = val; + } + updateSetting(UserSettingsRemoteProps.bookmark_custom_mapping, newMapping); + return newMapping; + }); + }} + placeholder="(empty)" + /> +
    + ); + }; + return (
    @@ -142,6 +211,96 @@ export const BookmarkNameFormatSetting = () => { ))}
+ +
+ setShowAdvanced(!showAdvanced)} + > + {showAdvanced ? 'Hide Advanced String Customization' : 'Show Advanced String Customization'} + + + {showAdvanced && ( +
+
+

+ Override the default output of specific format variables. +

+ { + setLocalMapping({}); + updateSetting(UserSettingsRemoteProps.bookmark_custom_mapping, {}); + }} + > + Reset Mappings + +
+ +
+
Time
+
+ {renderMappingInput('time_1h', '1 Hour', '1H')} + {renderMappingInput('time_4h', '4 Hours', '4H')} + {renderMappingInput('time_4h30m', '4.5 Hours', '4.5H')} + {renderMappingInput('time_16h', '16 Hours', '16H')} + {renderMappingInput('time_24h', '24 Hours', '')} + {renderMappingInput('time_48h', '48 Hours', '')} +
+
+ +
+
Mass
+
+ {renderMappingInput('mass_normal', 'Normal Mass', '')} + {renderMappingInput('mass_half', 'Destab', 'Destab')} + {renderMappingInput('mass_verge', 'Critical', 'Crit')} +
+
+ +
+
Other / Formatting
+
+ {renderMappingInput('chain_separator', 'Chain Separator', '')} +
+
+ +
+
Hole Sizes
+
+ {renderMappingInput('size_small', 'Small (Frigate)', 'S')} + {renderMappingInput('size_medium', 'Medium', 'M')} + {renderMappingInput('size_large', 'Large', '')} + {renderMappingInput('size_freight', 'Huge / Freight', 'XL')} + {renderMappingInput('size_capital', 'Capital', 'C')} +
+
+ +
+
Destination Classes
+
+ {renderMappingInput('class_c1', 'Class 1', 'C1')} + {renderMappingInput('class_c2', 'Class 2', 'C2')} + {renderMappingInput('class_c3', 'Class 3', 'C3')} + {renderMappingInput('class_c4', 'Class 4', 'C4')} + {renderMappingInput('class_c5', 'Class 5', 'C5')} + {renderMappingInput('class_c6', 'Class 6', 'C6')} + {renderMappingInput('class_c13', 'Class 13', 'C13')} + {renderMappingInput('class_c1c2c3', 'Class 1/2/3', 'C1/C2/C3')} + {renderMappingInput('class_c4c5', 'Class 4/5', 'C4/C5')} + {renderMappingInput('class_hs', 'High-Sec', 'HS')} + {renderMappingInput('class_ls', 'Low-Sec', 'LS')} + {renderMappingInput('class_ns', 'Null-Sec', 'NS')} + {renderMappingInput('class_thera', 'Thera', 'Thera')} + {renderMappingInput('class_pochven', 'Pochven', 'Pochven')} + {renderMappingInput('class_drifter', 'Drifter', 'Drifter')} +
+
+
+ )} +
); }; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts index 836e6b75..47ea2191 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts @@ -7,6 +7,7 @@ export const DEFAULT_REMOTE_SETTINGS = { [UserSettingsRemoteProps.select_on_spash]: false, [UserSettingsRemoteProps.delete_connection_with_sigs]: false, [UserSettingsRemoteProps.bookmark_name_format]: '', + [UserSettingsRemoteProps.bookmark_custom_mapping]: {}, [UserSettingsRemoteProps.bookmark_wormholes_start_at_zero]: false, [UserSettingsRemoteProps.bookmark_auto_copy]: true, [UserSettingsRemoteProps.bookmark_auto_temp_name]: '', @@ -27,6 +28,7 @@ export const UserSettingsRemoteList = [ UserSettingsRemoteProps.select_on_spash, UserSettingsRemoteProps.delete_connection_with_sigs, UserSettingsRemoteProps.bookmark_name_format, + UserSettingsRemoteProps.bookmark_custom_mapping, UserSettingsRemoteProps.bookmark_wormholes_start_at_zero, UserSettingsRemoteProps.bookmark_auto_copy, UserSettingsRemoteProps.bookmark_auto_temp_name, diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts index 3fcd35ff..64d9c679 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts @@ -5,6 +5,7 @@ export enum UserSettingsRemoteProps { select_on_spash = 'select_on_spash', delete_connection_with_sigs = 'delete_connection_with_sigs', bookmark_name_format = 'bookmark_name_format', + bookmark_custom_mapping = 'bookmark_custom_mapping', bookmark_wormholes_start_at_zero = 'bookmark_wormholes_start_at_zero', bookmark_auto_copy = 'bookmark_auto_copy', bookmark_auto_temp_name = 'bookmark_auto_temp_name', @@ -17,6 +18,7 @@ export type UserSettingsRemote = { select_on_spash: boolean; delete_connection_with_sigs: boolean; bookmark_name_format: string; + bookmark_custom_mapping?: Record; bookmark_wormholes_start_at_zero: boolean; bookmark_auto_copy: boolean; bookmark_auto_temp_name: string; diff --git a/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts index 3b894064..310c5399 100644 --- a/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts +++ b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts @@ -7,33 +7,33 @@ import { WORMHOLES_ADDITIONAL_INFO, SHIP_MASSES_SIZE, SHIP_SIZES_NAMES_SHORT } f import { ALL_DEST_TYPES_MAP, MULTI_DEST_WHS } from '@/hooks/Mapper/constants'; import { ShipSizeStatus } from '@/hooks/Mapper/types/connection'; -const getTimeStatusString = (status?: TimeStatus): string => { +const getTimeStatusString = (status?: TimeStatus, mapping?: Record): string => { switch (status) { case TimeStatus._1h: - return 'EoL'; // Or '1H', standard EVE mapping tends to use EoL for 1H + return mapping?.time_1h !== undefined ? mapping.time_1h : '1H'; case TimeStatus._4h: - return '4H'; + return mapping?.time_4h !== undefined ? mapping.time_4h : '4H'; case TimeStatus._4h30m: - return '4.5H'; + return mapping?.time_4h30m !== undefined ? mapping.time_4h30m : '4.5H'; case TimeStatus._16h: - return '16H'; + return mapping?.time_16h !== undefined ? mapping.time_16h : '16H'; case TimeStatus._24h: - return ''; + return mapping?.time_24h !== undefined ? mapping.time_24h : ''; case TimeStatus._48h: - return ''; + return mapping?.time_48h !== undefined ? mapping.time_48h : ''; default: return ''; } }; -const getMassStatusString = (status?: MassState): string => { +const getMassStatusString = (status?: MassState, mapping?: Record): string => { switch (status) { case MassState.normal: - return ''; // Typically not specified if normal + return mapping?.mass_normal !== undefined ? mapping.mass_normal : ''; case MassState.half: - return 'Destab'; + return mapping?.mass_half !== undefined ? mapping.mass_half : 'Destab'; case MassState.verge: - return 'Crit'; + return mapping?.mass_verge !== undefined ? mapping.mass_verge : 'Crit'; default: return ''; } @@ -59,9 +59,16 @@ const DEST_CLASS_OVERRIDES: Record = { 'c4/c5': 'C4/C5' }; -const formatDestString = (dest: string | null | undefined): string => { +const formatDestString = (dest: string | null | undefined, mapping?: Record): string => { if (!dest) return '?'; const lowerDest = dest.toLowerCase(); + + const normalizedDest = DEST_CLASS_OVERRIDES[lowerDest] ? DEST_CLASS_OVERRIDES[lowerDest].toLowerCase() : lowerDest; + const mappingKey = `class_${normalizedDest.replace(/[^a-z0-9]/g, '')}`; + if (mapping && mapping[mappingKey] !== undefined) { + return mapping[mappingKey]; + } + if (DEST_CLASS_OVERRIDES[lowerDest]) { return DEST_CLASS_OVERRIDES[lowerDest]; } @@ -88,6 +95,7 @@ export const calculateBookmarkIndex = ( currentSolarSystemId: string, currentEveId: string, startAtZero: boolean = false, + separator: string = '' ): { index: number; chained: string; chainedLetters: string } => { let parentBookmarkIndex: string | undefined; let parentBookmarkIndexLetters: string | undefined; @@ -134,8 +142,8 @@ export const calculateBookmarkIndex = ( i++; } - const chained = parentBookmarkIndex !== undefined ? `${parentBookmarkIndex}${i}` : `${i}`; - const chainedLetters = parentBookmarkIndexLetters !== undefined ? `${parentBookmarkIndexLetters}${i}` : numberToLetters(i, startAtZero); + const chained = parentBookmarkIndex !== undefined ? `${parentBookmarkIndex}${separator}${i}` : `${i}`; + const chainedLetters = parentBookmarkIndexLetters !== undefined ? `${parentBookmarkIndexLetters}${separator}${i}` : numberToLetters(i, startAtZero); return { index: i, chained, chainedLetters }; }; @@ -147,6 +155,10 @@ export const formatBookmarkName = ( bookmarkIndex: number, wormholesData: Record = {}, startAtZero: boolean = false, + mapping?: Record, + systemSignatures?: Record, + currentSystemId?: string, + currentSolarSystemId?: string ): string => { let result = formatStr; const info = parseSignatureCustomInfo(signature.custom_info); @@ -195,9 +207,59 @@ export const formatBookmarkName = ( const destOption = ALL_DEST_TYPES_MAP[info.destType]; destTypeStr = destOption ? destOption.label : info.destType; } - const finalDestTypeStr = formatDestString(destTypeStr); + const finalDestTypeStr = formatDestString(destTypeStr, mapping); result = result.replace(/\{dest_type\}/g, () => (finalDestTypeStr !== '?' ? finalDestTypeStr : '')); + // Calculate {dest_class_index} + let destClassIndexStr = ''; + if (result.includes('{dest_class_index}') && systemSignatures && (currentSystemId || currentSolarSystemId) && destTypeStr) { + const currentSigsRaw = [ + ...(systemSignatures[currentSystemId || ''] || []), + ...(systemSignatures[currentSolarSystemId || ''] || []) + ]; + // Deduplicate and ensure current signature is included + const sigsMap = new Map(currentSigsRaw.map(sig => [sig.eve_id, sig])); + sigsMap.set(signature.eve_id, signature); + const sigsInSystem = Array.from(sigsMap.values()); + + // Helper to get a simplified comparable class for a signature + const getSigDestClass = (sig: SystemSignature) => { + if (sig.eve_id === signature.eve_id) return finalDestTypeStr; + + const sigInfo = parseSignatureCustomInfo(sig.custom_info); + let sDestTypeStr = ''; + if (sig.type && MULTI_DEST_WHS.includes(sig.type) && sigInfo.destType) { + const destOption = ALL_DEST_TYPES_MAP[sigInfo.destType]; + if (destOption) sDestTypeStr = destOption.label; + } else if (sig.type === 'K162' && sigInfo.k162Type) { + const k162Option = ALL_DEST_TYPES_MAP[sigInfo.k162Type]; + if (k162Option) sDestTypeStr = k162Option.label; + } else if (sig.type && wormholesData[sig.type]) { + const whData = wormholesData[sig.type]; + const whClass = whData?.dest?.length === 1 ? WORMHOLES_ADDITIONAL_INFO[whData.dest[0]] : null; + if (whClass) sDestTypeStr = whClass.shortName || whClass.shortTitle; + } else if (sigInfo.destType) { + const destOption = ALL_DEST_TYPES_MAP[sigInfo.destType]; + sDestTypeStr = destOption ? destOption.label : sigInfo.destType; + } + return formatDestString(sDestTypeStr, mapping); + }; + + const sameClassSigs = sigsInSystem.filter(s => { + if (s.group !== SignatureGroup.Wormhole) return false; + return getSigDestClass(s) === finalDestTypeStr; + }); + + // Sort by eve_id to ensure consistent ordering across clients + sameClassSigs.sort((a, b) => a.eve_id.localeCompare(b.eve_id)); + + const indexInClass = sameClassSigs.findIndex(s => s.eve_id === signature.eve_id); + if (indexInClass > 0 || (indexInClass === 0 && sameClassSigs.length > 1)) { + destClassIndexStr = String.fromCharCode(97 + indexInClass); // 0->a, 1->b, 2->c... + } + } + result = result.replace(/\{dest_class_index\}/g, () => destClassIndexStr); + // Replace {size} and {mass} let sizeStr = ''; let massStr = ''; @@ -222,8 +284,25 @@ export const formatBookmarkName = ( if (whDataForSize) { if (whDataForSize.max_mass_per_jump) { const sizeStatus = SHIP_MASSES_SIZE[whDataForSize.max_mass_per_jump] ?? ShipSizeStatus.large; - if (sizeStatus !== ShipSizeStatus.large) { - sizeStr = SHIP_SIZES_NAMES_SHORT[sizeStatus] || ''; + const defaultSizeNames: Record = { + [ShipSizeStatus.small]: 'S', + [ShipSizeStatus.medium]: 'M', + [ShipSizeStatus.large]: '', + [ShipSizeStatus.freight]: 'XL', + [ShipSizeStatus.capital]: 'C' + }; + const sizeMappingKeys: Record = { + [ShipSizeStatus.small]: 'size_small', + [ShipSizeStatus.medium]: 'size_medium', + [ShipSizeStatus.large]: 'size_large', + [ShipSizeStatus.freight]: 'size_freight', + [ShipSizeStatus.capital]: 'size_capital' + }; + const mappingKey = sizeMappingKeys[sizeStatus]; + if (mapping && mapping[mappingKey] !== undefined) { + sizeStr = mapping[mappingKey]; + } else { + sizeStr = defaultSizeNames[sizeStatus] ?? SHIP_SIZES_NAMES_SHORT[sizeStatus] ?? ''; } } if (whDataForSize.total_mass) { @@ -237,10 +316,10 @@ export const formatBookmarkName = ( result = result.replace(/\{type\}/g, () => signature.type || ''); // Replace {time_status} -> Parsed from custom_info.time_status - result = result.replace(/\{time_status\}/g, () => getTimeStatusString(info.time_status)); + result = result.replace(/\{time_status\}/g, () => getTimeStatusString(info.time_status, mapping)); // Replace {mass_status} -> Parsed from custom_info.mass_status - result = result.replace(/\{mass_status\}/g, () => getMassStatusString(info.mass_status)); + result = result.replace(/\{mass_status\}/g, () => getMassStatusString(info.mass_status, mapping)); // Replace {temporary_name} -> signature.temporary_name result = result.replace(/\{temporary_name\}/g, () => signature.temporary_name || ''); @@ -280,12 +359,14 @@ export const handleAutoBookmark = async ( let bookmarkIndex = info.bookmark_index; if (bookmarkIndex == null) { + const separator = currentSettings?.bookmark_custom_mapping?.chain_separator || ''; const calculated = calculateBookmarkIndex( systemSignatures, currentSystemId, currentSolarSystemId, signature.eve_id, currentSettings?.bookmark_wormholes_start_at_zero, + separator ); bookmarkIndex = calculated.index; info.bookmark_index = calculated.index; @@ -324,7 +405,11 @@ export const handleAutoBookmark = async ( targetSystemClassGroup, bookmarkIndex, wormholesData, - currentSettings.bookmark_wormholes_start_at_zero + currentSettings.bookmark_wormholes_start_at_zero, + currentSettings.bookmark_custom_mapping, + systemSignatures, + currentSystemId, + currentSolarSystemId ); // Run this synchronously to avoid clipboard issues if possible diff --git a/lib/wanderer_app/repositories/map_user_settings_repo.ex b/lib/wanderer_app/repositories/map_user_settings_repo.ex index 6d023a5b..3dfe9f88 100644 --- a/lib/wanderer_app/repositories/map_user_settings_repo.ex +++ b/lib/wanderer_app/repositories/map_user_settings_repo.ex @@ -7,6 +7,7 @@ defmodule WandererApp.MapUserSettingsRepo do "delete_connection_with_sigs" => false, "primary_character_id" => nil, "bookmark_name_format" => "", + "bookmark_custom_mapping" => %{}, "system_auto_tag" => "", "system_custom_label_name" => "" } diff --git a/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex b/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex index acbd321c..5bc278c4 100644 --- a/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex +++ b/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex @@ -237,6 +237,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do "link_signature_on_splash", "delete_connection_with_sigs", "bookmark_name_format", + "bookmark_custom_mapping", "bookmark_wormholes_start_at_zero", "bookmark_auto_copy", "bookmark_auto_temp_name", From 702cb39679762c1d8a7b1a955f3b1083c66e0221 Mon Sep 17 00:00:00 2001 From: Drew Bonasera Date: Wed, 29 Apr 2026 11:07:11 -0400 Subject: [PATCH 09/11] Refactor code style for consistency across various mapper components. - style(map): Reformat code in map helpers and interface components - style(settings): Apply consistent code formatting to settings components - style(mapper): Reformat bookmark format helper and provider type definitions --- .../map/helpers/getSystemClassGroup.ts | 5 +- .../SystemLinkSignatureDialog.tsx | 6 +- .../hooks/useLinkSignature.ts | 6 +- .../MapSettings/MapSettingsProvider.tsx | 45 +++++++------- .../components/BookmarkNameFormatSetting.tsx | 37 ++++++------ .../SignatureSettings/SignatureSettings.tsx | 22 +++++-- .../Mapper/helpers/bookmarkFormatHelper.ts | 59 +++++++++++++------ .../js/hooks/Mapper/mapRootProvider/types.ts | 8 +-- 8 files changed, 114 insertions(+), 74 deletions(-) diff --git a/assets/js/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts b/assets/js/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts index 9459c9bd..6052a38c 100644 --- a/assets/js/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts +++ b/assets/js/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts @@ -1,4 +1,7 @@ -import { SOLAR_SYSTEM_CLASS_IDS, SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS } from '@/hooks/Mapper/components/map/constants.ts'; +import { + SOLAR_SYSTEM_CLASS_IDS, + SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS, +} from '@/hooks/Mapper/components/map/constants.ts'; export const getSystemClassGroup = (systemClassId: number | undefined | null): string | null => { if (systemClassId == null) return null; 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 246040b4..be95bc12 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx @@ -3,9 +3,9 @@ import { useCallback, useEffect, useMemo } from 'react'; import { useSystemInfo } from '@/hooks/Mapper/components/hooks'; import { - SOLAR_SYSTEM_CLASS_IDS, - SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS, - WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME, + SOLAR_SYSTEM_CLASS_IDS, + SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS, + WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME, } from '@/hooks/Mapper/components/map/constants.ts'; import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent'; import { MULTI_DEST_WHS, ALL_DEST_TYPES_MAP, DEST_TYPES_MAP_MAP } from '@/hooks/Mapper/constants.ts'; diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts index 57470e5c..937aff0f 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts @@ -45,7 +45,7 @@ export const useLinkSignature = ({ data, targetSystemClassGroup }: UseLinkSignat systemUuid, data.solar_system_source.toString(), wormholesData, - targetSystemClassGroup + targetSystemClassGroup, ); if (shouldUpdate) { @@ -77,7 +77,9 @@ export const useLinkSignature = ({ data, targetSystemClassGroup }: UseLinkSignat const startAtZero = userSettings?.bookmark_wormholes_start_at_zero; const letter = numberToLetters(bIndex, startAtZero); - const targetSystem = systems.find((s: any) => s.system_static_info?.solar_system_id === data.solar_system_target); + const targetSystem = systems.find( + (s: any) => s.system_static_info?.solar_system_id === data.solar_system_target, + ); if (targetSystem) { if (systemAutoTag) { diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx index 96f14954..df106003 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx @@ -54,26 +54,29 @@ export const MapSettingsProvider = ({ children }: WithChildren) => { const refVars = useRef({ mergedSettings, userRemoteSettings, interfaceSettings, outCommand, setInterfaceSettings }); refVars.current = { mergedSettings, userRemoteSettings, interfaceSettings, outCommand, setInterfaceSettings }; - const handleSettingChange = useCallback(async (prop: keyof UserSettings, value: boolean | string | Record) => { - const { userRemoteSettings, interfaceSettings, outCommand, setInterfaceSettings } = refVars.current; + const handleSettingChange = useCallback( + async (prop: keyof UserSettings, value: boolean | string | Record) => { + const { userRemoteSettings, interfaceSettings, outCommand, setInterfaceSettings } = refVars.current; - if (UserSettingsRemoteList.includes(prop as any)) { - const newRemoteSettings = { - ...userRemoteSettings, - [prop]: value, - }; - await outCommand({ - type: OutCommand.updateUserSettings, - data: newRemoteSettings, - }); - setUserRemoteSettings(newRemoteSettings); - } else { - setInterfaceSettings({ - ...interfaceSettings, - [prop]: value, - }); - } - }, []); + if (UserSettingsRemoteList.includes(prop as any)) { + const newRemoteSettings = { + ...userRemoteSettings, + [prop]: value, + }; + await outCommand({ + type: OutCommand.updateUserSettings, + data: newRemoteSettings, + }); + setUserRemoteSettings(newRemoteSettings); + } else { + setInterfaceSettings({ + ...interfaceSettings, + [prop]: value, + }); + } + }, + [], + ); const renderSettingItem = useCallback( (item: SettingsListItem) => { @@ -127,7 +130,9 @@ export const MapSettingsProvider = ({ children }: WithChildren) => { ); return ( - + {children} ); diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx index da60e5d9..079a7cea 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx @@ -26,7 +26,10 @@ const VARIABLES = [ { id: '{sig_letters}', desc: 'First 3 chars of signature (e.g., ABC)' }, { id: '{sig}', desc: 'Full signature ID (e.g., ABC-123)' }, { id: '{dest_type}', desc: 'Destination class (e.g., C5, HS, Thera)' }, - { id: '{dest_class_index}', desc: 'Letter index for multiple holes to same class (empty if only 1, otherwise a, b, c...)' }, + { + id: '{dest_class_index}', + desc: 'Letter index for multiple holes to same class (empty if only 1, otherwise a, b, c...)', + }, { id: '{type}', desc: 'Wormhole type (e.g., K162, H900)' }, { id: '{size}', desc: 'Hole size (e.g., S, M, XL)' }, { id: '{mass}', desc: 'Total mass in bil (e.g., 3.3)' }, @@ -57,7 +60,7 @@ export const BookmarkNameFormatSetting = () => { const preview = useMemo(() => { const isZero = settings.bookmark_wormholes_start_at_zero; const sep = localMapping?.chain_separator || ''; - + const chainNum = isZero ? `0${sep}0${sep}1` : `1${sep}1${sep}2`; const chainLet = isZero ? `A${sep}0${sep}1` : `A${sep}1${sep}2`; const currentIndex = isZero ? 1 : 2; @@ -81,15 +84,15 @@ export const BookmarkNameFormatSetting = () => { custom_info: JSON.stringify({ time_status: TimeStatus._1h, mass_status: MassState.verge, - }) + }), }; const dummyWormholesData = { - 'V283': { + V283: { total_mass: 3300000000, max_mass_per_jump: 62000000, - dest: ['hs'] - } as any + dest: ['hs'], + } as any, }; return formatBookmarkName( @@ -100,8 +103,8 @@ export const BookmarkNameFormatSetting = () => { dummyWormholesData, isZero, localMapping, - { 'preview_sys': [otherDummySig] }, - 'preview_sys' + { preview_sys: [otherDummySig] }, + 'preview_sys', ); }, [localFormat, settings.bookmark_wormholes_start_at_zero, localMapping]); @@ -152,7 +155,7 @@ export const BookmarkNameFormatSetting = () => { return newMapping; }); }} - onBlur={(e) => { + onBlur={e => { const val = e.target.value; setLocalMapping(prev => { const currentVal = prev[key] !== undefined ? prev[key] : defaultVal; @@ -192,7 +195,9 @@ export const BookmarkNameFormatSetting = () => { />
Live Preview: - {preview || Empty} + + {preview || Empty} +
@@ -219,13 +224,11 @@ export const BookmarkNameFormatSetting = () => { > {showAdvanced ? 'Hide Advanced String Customization' : 'Show Advanced String Customization'} - + {showAdvanced && (
-

- Override the default output of specific format variables. -

+

Override the default output of specific format variables.

{ Reset Mappings
- +
Time
@@ -262,9 +265,7 @@ export const BookmarkNameFormatSetting = () => {
Other / Formatting
-
- {renderMappingInput('chain_separator', 'Chain Separator', '')} -
+
{renderMappingInput('chain_separator', 'Chain Separator', '')}
diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx index f0a5ad49..84399140 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx @@ -123,8 +123,12 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map out = { ...out, group: group! }; if (group === SignatureGroup.Wormhole) { - const targetSystem = values.linked_system ? systems.find((s: any) => s.system_static_info?.solar_system_id?.toString() === values.linked_system) : null; - const targetSystemClassGroup = targetSystem?.system_static_info ? getSystemClassGroup(targetSystem.system_static_info.system_class) : null; + const targetSystem = values.linked_system + ? systems.find((s: any) => s.system_static_info?.solar_system_id?.toString() === values.linked_system) + : null; + const targetSystemClassGroup = targetSystem?.system_static_info + ? getSystemClassGroup(targetSystem.system_static_info.system_class) + : null; const currentSystem = systems.find((s: any) => s.id === systemId); const solarSystemIdStr = currentSystem?.system_static_info?.solar_system_id?.toString() || systemId; @@ -136,7 +140,7 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map systemId, solarSystemIdStr, wormholesData, - targetSystemClassGroup + targetSystemClassGroup, ); out = updatedSignature; } @@ -169,7 +173,17 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map signatureForm.reset(); onHide(); }, - [signatureData, signatureForm, outCommand, systemId, onHide, systemSignatures, systems, wormholesData, userSettings], + [ + signatureData, + signatureForm, + outCommand, + systemId, + onHide, + systemSignatures, + systems, + wormholesData, + userSettings, + ], ); useEffect(() => { diff --git a/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts index 310c5399..e260328e 100644 --- a/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts +++ b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts @@ -3,7 +3,11 @@ import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureC import { MassState, TimeStatus } from '@/hooks/Mapper/types/connection'; import { getSystemClassGroup } from '@/hooks/Mapper/components/map/helpers/getSystemClassGroup'; import { WormholeDataRaw } from '@/hooks/Mapper/types/wormholes'; -import { WORMHOLES_ADDITIONAL_INFO, SHIP_MASSES_SIZE, SHIP_SIZES_NAMES_SHORT } from '@/hooks/Mapper/components/map/constants'; +import { + WORMHOLES_ADDITIONAL_INFO, + SHIP_MASSES_SIZE, + SHIP_SIZES_NAMES_SHORT, +} from '@/hooks/Mapper/components/map/constants'; import { ALL_DEST_TYPES_MAP, MULTI_DEST_WHS } from '@/hooks/Mapper/constants'; import { ShipSizeStatus } from '@/hooks/Mapper/types/connection'; @@ -56,13 +60,13 @@ const DEST_CLASS_OVERRIDES: Record = { p: 'Pochven', pochven: 'Pochven', 'c1/c2/c3': 'C1/C2/C3', - 'c4/c5': 'C4/C5' + 'c4/c5': 'C4/C5', }; const formatDestString = (dest: string | null | undefined, mapping?: Record): string => { if (!dest) return '?'; const lowerDest = dest.toLowerCase(); - + const normalizedDest = DEST_CLASS_OVERRIDES[lowerDest] ? DEST_CLASS_OVERRIDES[lowerDest].toLowerCase() : lowerDest; const mappingKey = `class_${normalizedDest.replace(/[^a-z0-9]/g, '')}`; if (mapping && mapping[mappingKey] !== undefined) { @@ -95,7 +99,7 @@ export const calculateBookmarkIndex = ( currentSolarSystemId: string, currentEveId: string, startAtZero: boolean = false, - separator: string = '' + separator: string = '', ): { index: number; chained: string; chainedLetters: string } => { let parentBookmarkIndex: string | undefined; let parentBookmarkIndexLetters: string | undefined; @@ -117,7 +121,10 @@ export const calculateBookmarkIndex = ( } if (parentInfo.bookmark_index_chained_letters != null) { - if (!parentBookmarkIndexLetters || String(parentInfo.bookmark_index_chained_letters).length < parentBookmarkIndexLetters.length) { + if ( + !parentBookmarkIndexLetters || + String(parentInfo.bookmark_index_chained_letters).length < parentBookmarkIndexLetters.length + ) { parentBookmarkIndexLetters = String(parentInfo.bookmark_index_chained_letters); } } @@ -126,7 +133,7 @@ export const calculateBookmarkIndex = ( const currentSigsRaw = [ ...(systemSignatures[currentSystemUuid] || []), - ...(systemSignatures[currentSolarSystemId] || []) + ...(systemSignatures[currentSolarSystemId] || []), ]; // Deduplicate in case both keys map to the same or overlapping arrays @@ -143,7 +150,10 @@ export const calculateBookmarkIndex = ( } const chained = parentBookmarkIndex !== undefined ? `${parentBookmarkIndex}${separator}${i}` : `${i}`; - const chainedLetters = parentBookmarkIndexLetters !== undefined ? `${parentBookmarkIndexLetters}${separator}${i}` : numberToLetters(i, startAtZero); + const chainedLetters = + parentBookmarkIndexLetters !== undefined + ? `${parentBookmarkIndexLetters}${separator}${i}` + : numberToLetters(i, startAtZero); return { index: i, chained, chainedLetters }; }; @@ -158,7 +168,7 @@ export const formatBookmarkName = ( mapping?: Record, systemSignatures?: Record, currentSystemId?: string, - currentSolarSystemId?: string + currentSolarSystemId?: string, ): string => { let result = formatStr; const info = parseSignatureCustomInfo(signature.custom_info); @@ -173,7 +183,10 @@ export const formatBookmarkName = ( result = result.replace(/\{index_letter\}/g, () => numberToLetters(bookmarkIndex, startAtZero)); // Replace {chain_index_letters} - result = result.replace(/\{chain_index_letters\}/g, () => info.bookmark_index_chained_letters || info.bookmark_index_chained || bookmarkIndex.toString()); + result = result.replace( + /\{chain_index_letters\}/g, + () => info.bookmark_index_chained_letters || info.bookmark_index_chained || bookmarkIndex.toString(), + ); // Replace {sig_letters} (first 3 chars of eve_id) const sigLetters = signature.eve_id.substring(0, 3).toUpperCase(); @@ -212,16 +225,21 @@ export const formatBookmarkName = ( // Calculate {dest_class_index} let destClassIndexStr = ''; - if (result.includes('{dest_class_index}') && systemSignatures && (currentSystemId || currentSolarSystemId) && destTypeStr) { + if ( + result.includes('{dest_class_index}') && + systemSignatures && + (currentSystemId || currentSolarSystemId) && + destTypeStr + ) { const currentSigsRaw = [ ...(systemSignatures[currentSystemId || ''] || []), - ...(systemSignatures[currentSolarSystemId || ''] || []) + ...(systemSignatures[currentSolarSystemId || ''] || []), ]; // Deduplicate and ensure current signature is included const sigsMap = new Map(currentSigsRaw.map(sig => [sig.eve_id, sig])); sigsMap.set(signature.eve_id, signature); const sigsInSystem = Array.from(sigsMap.values()); - + // Helper to get a simplified comparable class for a signature const getSigDestClass = (sig: SystemSignature) => { if (sig.eve_id === signature.eve_id) return finalDestTypeStr; @@ -264,7 +282,7 @@ export const formatBookmarkName = ( let sizeStr = ''; let massStr = ''; let whDataForSize: WormholeDataRaw | null = null; - + if (signature.type && MULTI_DEST_WHS.includes(signature.type) && info.destType) { const destOption = ALL_DEST_TYPES_MAP[info.destType]; if (destOption && destOption.whClassName) { @@ -289,14 +307,14 @@ export const formatBookmarkName = ( [ShipSizeStatus.medium]: 'M', [ShipSizeStatus.large]: '', [ShipSizeStatus.freight]: 'XL', - [ShipSizeStatus.capital]: 'C' + [ShipSizeStatus.capital]: 'C', }; const sizeMappingKeys: Record = { [ShipSizeStatus.small]: 'size_small', [ShipSizeStatus.medium]: 'size_medium', [ShipSizeStatus.large]: 'size_large', [ShipSizeStatus.freight]: 'size_freight', - [ShipSizeStatus.capital]: 'size_capital' + [ShipSizeStatus.capital]: 'size_capital', }; const mappingKey = sizeMappingKeys[sizeStatus]; if (mapping && mapping[mappingKey] !== undefined) { @@ -346,12 +364,15 @@ export const handleAutoBookmark = async ( currentSystemId: string, currentSolarSystemId: string, wormholesData: Record, - targetSystemClassGroup: string | null + targetSystemClassGroup: string | null, ): Promise<{ updatedSignature: SystemSignature; shouldUpdate: boolean }> => { let updatedSignature = signature; let shouldUpdate = false; - if (signature.group !== SignatureGroup.Wormhole || (!currentSettings?.bookmark_name_format && !currentSettings?.bookmark_auto_temp_name)) { + if ( + signature.group !== SignatureGroup.Wormhole || + (!currentSettings?.bookmark_name_format && !currentSettings?.bookmark_auto_temp_name) + ) { return { updatedSignature, shouldUpdate }; } @@ -366,7 +387,7 @@ export const handleAutoBookmark = async ( currentSolarSystemId, signature.eve_id, currentSettings?.bookmark_wormholes_start_at_zero, - separator + separator, ); bookmarkIndex = calculated.index; info.bookmark_index = calculated.index; @@ -409,7 +430,7 @@ export const handleAutoBookmark = async ( currentSettings.bookmark_custom_mapping, systemSignatures, currentSystemId, - currentSolarSystemId + currentSolarSystemId, ); // Run this synchronously to avoid clipboard issues if possible diff --git a/assets/js/hooks/Mapper/mapRootProvider/types.ts b/assets/js/hooks/Mapper/mapRootProvider/types.ts index 76e5db3f..fa2a32dd 100644 --- a/assets/js/hooks/Mapper/mapRootProvider/types.ts +++ b/assets/js/hooks/Mapper/mapRootProvider/types.ts @@ -48,13 +48,7 @@ export type RoutesType = { avoid: number[]; }; -export type RoutesByCategoryType = - | 'blueLoot' - | 'redLoot' - | 'thera' - | 'turnur' - | 'so_cleaning' - | 'trade_hubs'; +export type RoutesByCategoryType = 'blueLoot' | 'redLoot' | 'thera' | 'turnur' | 'so_cleaning' | 'trade_hubs'; export type RoutesByScopeType = 'ALL' | 'HIGH'; From 0a44a1403e236de35e89f2a5b303c19fcc07801f Mon Sep 17 00:00:00 2001 From: Drew Bonasera Date: Fri, 1 May 2026 06:57:17 -0400 Subject: [PATCH 10/11] Adds an option to ignore return wormholes for auto-indexing and use a custom symbol instead. This also fixes chained index formatting for system auto-tags and labels. - feat(bookmarks): Add settings to ignore return holes and use a custom symbol for auto-indexing - feat(settings): Add a `dependsOn` property to conditionally render settings - fix(systems): Correctly apply chained index formats for auto-tag and custom label - refactor(bookmarks): Pass target system info to `handleAutoBookmark` for return hole detection --- .../hooks/useLinkSignature.ts | 121 +++++++++--------- .../MapSettings/MapSettingsProvider.tsx | 7 + .../components/MapSettings/constants.ts | 15 +++ .../components/MapSettings/types.ts | 5 + .../SignatureSettings/SignatureSettings.tsx | 5 + .../Mapper/helpers/bookmarkFormatHelper.ts | 72 +++++++++-- .../repositories/map_user_settings_repo.ex | 4 +- .../event_handlers/map_core_event_handler.ex | 4 +- 8 files changed, 163 insertions(+), 70 deletions(-) diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts index 937aff0f..bdb9a211 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/hooks/useLinkSignature.ts @@ -36,6 +36,10 @@ export const useLinkSignature = ({ data, targetSystemClassGroup }: UseLinkSignat const sourceSystem = systems.find((s: any) => s.system_static_info?.solar_system_id === data.solar_system_source); const systemUuid = sourceSystem?.id || data.solar_system_source.toString(); + const targetSystem = systems.find((s: any) => s.system_static_info?.solar_system_id === data.solar_system_target); + const targetSystemUuid = targetSystem?.id; + const targetSolarSystemIdStr = data.solar_system_target?.toString(); + const signatureToLink = { ...signature, group: SignatureGroup.Wormhole }; const { updatedSignature, shouldUpdate } = await handleAutoBookmark( @@ -46,6 +50,8 @@ export const useLinkSignature = ({ data, targetSystemClassGroup }: UseLinkSignat data.solar_system_source.toString(), wormholesData, targetSystemClassGroup, + targetSystemUuid, + targetSolarSystemIdStr, ); if (shouldUpdate) { @@ -73,69 +79,70 @@ export const useLinkSignature = ({ data, targetSystemClassGroup }: UseLinkSignat if (systemAutoTag || systemCustomLabelName) { const info = parseSignatureCustomInfo(updatedSignature.custom_info); - const bIndex = info.bookmark_index ?? 0; - const startAtZero = userSettings?.bookmark_wormholes_start_at_zero; - const letter = numberToLetters(bIndex, startAtZero); - const targetSystem = systems.find( - (s: any) => s.system_static_info?.solar_system_id === data.solar_system_target, - ); + if (info.bookmark_index !== undefined) { + const bIndex = info.bookmark_index; + const startAtZero = userSettings?.bookmark_wormholes_start_at_zero; + const letter = numberToLetters(bIndex, startAtZero); - if (targetSystem) { - if (systemAutoTag) { - let tagValue = ''; - switch (systemAutoTag) { - case 'index': - case 'chain_index': - tagValue = bIndex.toString(); - break; - case 'index_letter': - tagValue = letter; - break; - case 'chain_index_letters': - tagValue = info.bookmark_index_chained_letters === letter ? letter : bIndex.toString(); - break; + if (targetSystem) { + if (systemAutoTag) { + let tagValue = ''; + switch (systemAutoTag) { + case 'index': + tagValue = bIndex.toString(); + break; + case 'chain_index': + tagValue = (info.bookmark_index_chained as string) || bIndex.toString(); + break; + case 'index_letter': + tagValue = letter; + break; + case 'chain_index_letters': + tagValue = (info.bookmark_index_chained_letters as string) || letter; + break; + } + + if (tagValue) { + await outCommand({ + type: OutCommand.updateSystemTag, + data: { + system_id: targetSystem.id, + value: tagValue, + }, + }); + } } - if (tagValue) { - await outCommand({ - type: OutCommand.updateSystemTag, - data: { - system_id: targetSystem.id, - value: tagValue, - }, - }); - } - } + if (systemCustomLabelName) { + let labelValue = ''; + switch (systemCustomLabelName) { + case 'index': + labelValue = bIndex.toString(); + break; + case 'index_letter': + labelValue = letter; + break; + case 'chain_index': + labelValue = (info.bookmark_index_chained as string) || bIndex.toString(); + break; + case 'chain_index_letters': + labelValue = (info.bookmark_index_chained_letters as string) || letter; + break; + } - if (systemCustomLabelName) { - let labelValue = ''; - switch (systemCustomLabelName) { - case 'index': - labelValue = bIndex.toString(); - break; - case 'index_letter': - labelValue = letter; - break; - case 'chain_index': - labelValue = (info.bookmark_index_chained as string) || bIndex.toString(); - break; - case 'chain_index_letters': - labelValue = (info.bookmark_index_chained_letters as string) || letter; - break; - } + if (labelValue) { + const outLabel = new LabelsManager(targetSystem.labels ?? ''); + outLabel.updateCustomLabel(labelValue); - if (labelValue) { - const outLabel = new LabelsManager(targetSystem.labels ?? ''); - outLabel.updateCustomLabel(labelValue); - - await outCommand({ - type: OutCommand.updateSystemLabels, - data: { - system_id: targetSystem.id, - value: outLabel.toString(), - }, - }); + await outCommand({ + type: OutCommand.updateSystemLabels, + data: { + system_id: targetSystem.id, + value: outLabel.toString(), + }, + }); + } } } } diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx index df106003..2a05f55b 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx @@ -80,6 +80,13 @@ export const MapSettingsProvider = ({ children }: WithChildren) => { const renderSettingItem = useCallback( (item: SettingsListItem) => { + if (item.dependsOn) { + const dependsOnValue = refVars.current.mergedSettings[item.dependsOn]; + if (!dependsOnValue) { + return null; + } + } + const currentValue = refVars.current.mergedSettings[item.prop]; if (item.type === 'checkbox') { diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts index 47ea2191..078a08b9 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts @@ -13,6 +13,8 @@ export const DEFAULT_REMOTE_SETTINGS = { [UserSettingsRemoteProps.bookmark_auto_temp_name]: '', [UserSettingsRemoteProps.system_auto_tag]: '', [UserSettingsRemoteProps.system_custom_label_name]: '', + [UserSettingsRemoteProps.bookmark_return_hole_ignore]: false, + [UserSettingsRemoteProps.bookmark_return_hole_symbol]: '', }; export const AUTO_FORMAT_OPTIONS = [ @@ -34,6 +36,8 @@ export const UserSettingsRemoteList = [ UserSettingsRemoteProps.bookmark_auto_temp_name, UserSettingsRemoteProps.system_auto_tag, UserSettingsRemoteProps.system_custom_label_name, + UserSettingsRemoteProps.bookmark_return_hole_ignore, + UserSettingsRemoteProps.bookmark_return_hole_symbol, ]; // export const COMMON_CHECKBOXES_PROPS: SettingsListItem[] = [ @@ -81,6 +85,17 @@ export const BOOKMARKS_SETTINGS_PROPS: SettingsListItem[] = [ label: 'Start wormhole indices at 0', type: 'checkbox', }, + { + prop: UserSettingsRemoteProps.bookmark_return_hole_ignore, + label: 'Ignore return hole when creating indexes', + type: 'checkbox', + }, + { + prop: UserSettingsRemoteProps.bookmark_return_hole_symbol, + label: 'Return hole symbol (use space for empty)', + type: 'text', + dependsOn: UserSettingsRemoteProps.bookmark_return_hole_ignore, + }, { prop: UserSettingsRemoteProps.bookmark_auto_temp_name, label: 'Auto-fill wormhole temporary name', diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts index 64d9c679..196814fb 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts @@ -11,6 +11,8 @@ export enum UserSettingsRemoteProps { bookmark_auto_temp_name = 'bookmark_auto_temp_name', system_auto_tag = 'system_auto_tag', system_custom_label_name = 'system_custom_label_name', + bookmark_return_hole_ignore = 'bookmark_return_hole_ignore', + bookmark_return_hole_symbol = 'bookmark_return_hole_symbol', } export type UserSettingsRemote = { @@ -24,6 +26,8 @@ export type UserSettingsRemote = { bookmark_auto_temp_name: string; system_auto_tag: string; system_custom_label_name: string; + bookmark_return_hole_ignore: boolean; + bookmark_return_hole_symbol: string; }; export type UserSettings = UserSettingsRemote & InterfaceStoredSettings; @@ -35,4 +39,5 @@ export type SettingsListItem = { options?: { label: string; value: string }[]; placeholder?: string; helperText?: string; + dependsOn?: keyof UserSettings; }; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx index 84399140..0c36f236 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx @@ -129,6 +129,9 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map const targetSystemClassGroup = targetSystem?.system_static_info ? getSystemClassGroup(targetSystem.system_static_info.system_class) : null; + const targetSystemUuid = targetSystem?.id; + const targetSolarSystemIdStr = + targetSystem?.system_static_info?.solar_system_id?.toString() || values.linked_system; const currentSystem = systems.find((s: any) => s.id === systemId); const solarSystemIdStr = currentSystem?.system_static_info?.solar_system_id?.toString() || systemId; @@ -141,6 +144,8 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map solarSystemIdStr, wormholesData, targetSystemClassGroup, + targetSystemUuid, + targetSolarSystemIdStr, ); out = updatedSignature; } diff --git a/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts index e260328e..6849a536 100644 --- a/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts +++ b/assets/js/hooks/Mapper/helpers/bookmarkFormatHelper.ts @@ -110,6 +110,10 @@ export const calculateBookmarkIndex = ( const parentSigs = sigs.filter(sig => sig.linked_system?.solar_system_id?.toString() === currentSolarSystemId); for (const parentSig of parentSigs) { const parentInfo = parseSignatureCustomInfo(parentSig.custom_info); + + // Return holes have their bookmark_index deleted, so we skip them to avoid hijacking the chain + if (parentInfo.bookmark_index === undefined) continue; + if (parentInfo.bookmark_index_chained != null) { if (!parentBookmarkIndex || String(parentInfo.bookmark_index_chained).length < parentBookmarkIndex.length) { parentBookmarkIndex = String(parentInfo.bookmark_index_chained); @@ -162,7 +166,7 @@ export const formatBookmarkName = ( formatStr: string, signature: SystemSignature, destSystemClass: string | null, - bookmarkIndex: number, + bookmarkIndex: number | string, wormholesData: Record = {}, startAtZero: boolean = false, mapping?: Record, @@ -180,12 +184,17 @@ export const formatBookmarkName = ( result = result.replace(/\{chain_index\}/g, () => info.bookmark_index_chained || bookmarkIndex.toString()); // Replace {index_letter} - result = result.replace(/\{index_letter\}/g, () => numberToLetters(bookmarkIndex, startAtZero)); + result = result.replace(/\{index_letter\}/g, () => + typeof bookmarkIndex === 'number' ? numberToLetters(bookmarkIndex, startAtZero) : bookmarkIndex.toString(), + ); // Replace {chain_index_letters} result = result.replace( /\{chain_index_letters\}/g, - () => info.bookmark_index_chained_letters || info.bookmark_index_chained || bookmarkIndex.toString(), + () => + info.bookmark_index_chained_letters || + info.bookmark_index_chained || + (typeof bookmarkIndex === 'number' ? numberToLetters(bookmarkIndex, startAtZero) : bookmarkIndex.toString()), ); // Replace {sig_letters} (first 3 chars of eve_id) @@ -365,6 +374,8 @@ export const handleAutoBookmark = async ( currentSolarSystemId: string, wormholesData: Record, targetSystemClassGroup: string | null, + targetSystemUuid?: string, + targetSolarSystemId?: string, ): Promise<{ updatedSignature: SystemSignature; shouldUpdate: boolean }> => { let updatedSignature = signature; let shouldUpdate = false; @@ -378,8 +389,39 @@ export const handleAutoBookmark = async ( const info = parseSignatureCustomInfo(signature.custom_info); let bookmarkIndex = info.bookmark_index; + let bookmarkIndexToUse: number | string = bookmarkIndex != null ? bookmarkIndex : ''; - if (bookmarkIndex == null) { + let isReturnHole = false; + let symbol = ''; + + if (currentSettings?.bookmark_return_hole_ignore && (targetSystemUuid || targetSolarSystemId)) { + const targetSigsRaw = [ + ...(targetSystemUuid ? systemSignatures[targetSystemUuid] || [] : []), + ...(targetSolarSystemId ? systemSignatures[targetSolarSystemId] || [] : []), + ]; + + const uniqueTargetSigs = Array.from(new Map(targetSigsRaw.map(sig => [sig.eve_id, sig])).values()); + + isReturnHole = uniqueTargetSigs.some( + sig => sig.linked_system?.solar_system_id?.toString() === currentSolarSystemId.toString(), + ); + + if (isReturnHole) { + symbol = currentSettings.bookmark_return_hole_symbol || ''; + if (symbol === ' ') symbol = ''; + } + } + + if (isReturnHole) { + if (info.bookmark_index !== undefined) { + delete info.bookmark_index; + } + info.bookmark_index_chained = symbol; + info.bookmark_index_chained_letters = symbol; + bookmarkIndexToUse = symbol; + updatedSignature = { ...signature, custom_info: JSON.stringify(info) }; + shouldUpdate = true; + } else if (bookmarkIndex == null) { const separator = currentSettings?.bookmark_custom_mapping?.chain_separator || ''; const calculated = calculateBookmarkIndex( systemSignatures, @@ -393,27 +435,35 @@ export const handleAutoBookmark = async ( info.bookmark_index = calculated.index; info.bookmark_index_chained = calculated.chained; info.bookmark_index_chained_letters = calculated.chainedLetters; + bookmarkIndexToUse = calculated.index; updatedSignature = { ...signature, custom_info: JSON.stringify(info) }; shouldUpdate = true; } - if (currentSettings?.bookmark_auto_temp_name && !updatedSignature.temporary_name) { + const needsTempNameUpdate = + !updatedSignature.temporary_name || + (isReturnHole && updatedSignature.temporary_name !== symbol && currentSettings?.bookmark_auto_temp_name); + + if (currentSettings?.bookmark_auto_temp_name && needsTempNameUpdate) { let autoName = ''; switch (currentSettings.bookmark_auto_temp_name) { case 'index': - autoName = bookmarkIndex.toString(); + autoName = bookmarkIndexToUse.toString(); break; case 'index_letter': - autoName = numberToLetters(bookmarkIndex, currentSettings.bookmark_wormholes_start_at_zero); + autoName = + typeof bookmarkIndexToUse === 'number' + ? numberToLetters(bookmarkIndexToUse, currentSettings.bookmark_wormholes_start_at_zero) + : bookmarkIndexToUse.toString(); break; case 'chain_index': - autoName = info.bookmark_index_chained || bookmarkIndex.toString(); + autoName = info.bookmark_index_chained || bookmarkIndexToUse.toString(); break; case 'chain_index_letters': - autoName = info.bookmark_index_chained_letters || info.bookmark_index_chained || bookmarkIndex.toString(); + autoName = info.bookmark_index_chained_letters || info.bookmark_index_chained || bookmarkIndexToUse.toString(); break; } - if (autoName) { + if (autoName !== '' || isReturnHole) { updatedSignature = { ...updatedSignature, temporary_name: autoName }; shouldUpdate = true; } @@ -424,7 +474,7 @@ export const handleAutoBookmark = async ( currentSettings.bookmark_name_format, updatedSignature, targetSystemClassGroup, - bookmarkIndex, + bookmarkIndexToUse, wormholesData, currentSettings.bookmark_wormholes_start_at_zero, currentSettings.bookmark_custom_mapping, diff --git a/lib/wanderer_app/repositories/map_user_settings_repo.ex b/lib/wanderer_app/repositories/map_user_settings_repo.ex index 3dfe9f88..babbec42 100644 --- a/lib/wanderer_app/repositories/map_user_settings_repo.ex +++ b/lib/wanderer_app/repositories/map_user_settings_repo.ex @@ -9,7 +9,9 @@ defmodule WandererApp.MapUserSettingsRepo do "bookmark_name_format" => "", "bookmark_custom_mapping" => %{}, "system_auto_tag" => "", - "system_custom_label_name" => "" + "system_custom_label_name" => "", + "bookmark_return_hole_ignore" => false, + "bookmark_return_hole_symbol" => "" } def get(map_id, user_id) do diff --git a/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex b/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex index 5bc278c4..60db6be6 100644 --- a/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex +++ b/lib/wanderer_app_web/live/map/event_handlers/map_core_event_handler.ex @@ -242,7 +242,9 @@ defmodule WandererAppWeb.MapCoreEventHandler do "bookmark_auto_copy", "bookmark_auto_temp_name", "system_auto_tag", - "system_custom_label_name" + "system_custom_label_name", + "bookmark_return_hole_ignore", + "bookmark_return_hole_symbol" ]) |> Jason.encode!() From a33c93c5227cfd104c1a849b985c331e776cdbc4 Mon Sep 17 00:00:00 2001 From: Drew Bonasera Date: Wed, 20 May 2026 08:50:53 -0400 Subject: [PATCH 11/11] Refactor bookmark name format settings by separating mapping input logic into a new component and extracting static configuration arrays. Additionally, update signature settings to use explicit conditional assignments instead of optional chaining when handling system variables. - refactor(bookmarks): Extract `CustomMappingInput` component for custom mapping settings - refactor(bookmarks): Move hardcoded mapping options into constant arrays - refactor(bookmarks): Wrap format setting event handlers in `useCallback` hook - refactor(signatures): Replace optional chaining with explicit conditional assignments for target and solar system IDs --- .../components/BookmarkNameFormatSetting.tsx | 205 +++++++++++------- .../SignatureSettings/SignatureSettings.tsx | 29 ++- 2 files changed, 145 insertions(+), 89 deletions(-) diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx index 079a7cea..11a60100 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/components/BookmarkNameFormatSetting.tsx @@ -2,7 +2,7 @@ import { useMapSettings } from '../MapSettingsProvider'; import { UserSettingsRemoteProps } from '../types'; import { InputText } from 'primereact/inputtext'; import { WdButton } from '@/hooks/Mapper/components/ui-kit'; -import { useMemo, useState, useRef, useEffect } from 'react'; +import { useMemo, useState, useRef, useEffect, useCallback } from 'react'; import { formatBookmarkName } from '@/hooks/Mapper/helpers/bookmarkFormatHelper'; import { SignatureGroup, SignatureKind, SystemSignature } from '@/hooks/Mapper/types'; import { MassState, TimeStatus } from '@/hooks/Mapper/types/connection'; @@ -39,6 +39,107 @@ const VARIABLES = [ { id: '{description}', desc: 'Custom description' }, ]; +interface CustomMappingInputProps { + mappingKey: string; + label: string; + defaultVal: string; + localMapping: Record; + setLocalMapping: React.Dispatch>>; + updateSetting: (prop: any, value: any) => void; +} + +const CustomMappingInput = ({ + mappingKey, + label, + defaultVal, + localMapping, + setLocalMapping, + updateSetting, +}: CustomMappingInputProps) => { + const value = localMapping[mappingKey] !== undefined ? localMapping[mappingKey] : defaultVal; + + const handleChange = useCallback((e: React.ChangeEvent) => { + setLocalMapping(prev => { + const newMapping = { ...prev }; + newMapping[mappingKey] = e.target.value; + return newMapping; + }); + }, [mappingKey, setLocalMapping]); + + const handleBlur = useCallback((e: React.FocusEvent) => { + const val = e.target.value; + setLocalMapping(prev => { + const currentVal = prev[mappingKey] !== undefined ? prev[mappingKey] : defaultVal; + if (val === currentVal) return prev; + + const newMapping = { ...prev }; + if (val === defaultVal) { + delete newMapping[mappingKey]; + } else { + newMapping[mappingKey] = val; + } + updateSetting(UserSettingsRemoteProps.bookmark_custom_mapping, newMapping); + return newMapping; + }); + }, [mappingKey, defaultVal, setLocalMapping, updateSetting]); + + return ( +
+ + +
+ ); +}; + +const TIME_OPTIONS = [ + { key: 'time_1h', label: '1 Hour', defaultVal: '1H' }, + { key: 'time_4h', label: '4 Hours', defaultVal: '4H' }, + { key: 'time_4h30m', label: '4.5 Hours', defaultVal: '4.5H' }, + { key: 'time_16h', label: '16 Hours', defaultVal: '16H' }, + { key: 'time_24h', label: '24 Hours', defaultVal: '' }, + { key: 'time_48h', label: '48 Hours', defaultVal: '' }, +]; + +const MASS_OPTIONS = [ + { key: 'mass_normal', label: 'Normal Mass', defaultVal: '' }, + { key: 'mass_half', label: 'Destab', defaultVal: 'Destab' }, + { key: 'mass_verge', label: 'Critical', defaultVal: 'Crit' }, +]; + +const OTHER_OPTIONS = [{ key: 'chain_separator', label: 'Chain Separator', defaultVal: '' }]; + +const SIZE_OPTIONS = [ + { key: 'size_small', label: 'Small (Frigate)', defaultVal: 'S' }, + { key: 'size_medium', label: 'Medium', defaultVal: 'M' }, + { key: 'size_large', label: 'Large', defaultVal: '' }, + { key: 'size_freight', label: 'Huge / Freight', defaultVal: 'XL' }, + { key: 'size_capital', label: 'Capital', defaultVal: 'C' }, +]; + +const CLASS_OPTIONS = [ + { key: 'class_c1', label: 'Class 1', defaultVal: 'C1' }, + { key: 'class_c2', label: 'Class 2', defaultVal: 'C2' }, + { key: 'class_c3', label: 'Class 3', defaultVal: 'C3' }, + { key: 'class_c4', label: 'Class 4', defaultVal: 'C4' }, + { key: 'class_c5', label: 'Class 5', defaultVal: 'C5' }, + { key: 'class_c6', label: 'Class 6', defaultVal: 'C6' }, + { key: 'class_c13', label: 'Class 13', defaultVal: 'C13' }, + { key: 'class_c1c2c3', label: 'Class 1/2/3', defaultVal: 'C1/C2/C3' }, + { key: 'class_c4c5', label: 'Class 4/5', defaultVal: 'C4/C5' }, + { key: 'class_hs', label: 'High-Sec', defaultVal: 'HS' }, + { key: 'class_ls', label: 'Low-Sec', defaultVal: 'LS' }, + { key: 'class_ns', label: 'Null-Sec', defaultVal: 'NS' }, + { key: 'class_thera', label: 'Thera', defaultVal: 'Thera' }, + { key: 'class_pochven', label: 'Pochven', defaultVal: 'Pochven' }, + { key: 'class_drifter', label: 'Drifter', defaultVal: 'Drifter' }, +]; + export const BookmarkNameFormatSetting = () => { const { settings, updateSetting } = useMapSettings(); const formatStr = settings.bookmark_name_format || ''; @@ -108,13 +209,13 @@ export const BookmarkNameFormatSetting = () => { ); }, [localFormat, settings.bookmark_wormholes_start_at_zero, localMapping]); - const handleBlur = () => { + const handleBlur = useCallback(() => { if (localFormat !== formatStr) { updateSetting(UserSettingsRemoteProps.bookmark_name_format, localFormat); } - }; + }, [localFormat, formatStr, updateSetting]); - const insertVariable = (variable: string) => { + const insertVariable = useCallback((variable: string) => { const input = inputRef.current; if (input) { const start = input.selectionStart || 0; @@ -132,49 +233,26 @@ export const BookmarkNameFormatSetting = () => { setLocalFormat(newFormat); updateSetting(UserSettingsRemoteProps.bookmark_name_format, newFormat); } - }; + }, [localFormat, updateSetting]); - const resetToDefault = () => { + const resetToDefault = useCallback(() => { const defaultFormat = '{chain_index} {sig_letters} {dest_type} {size} {mass_status} {time_status}'; setLocalFormat(defaultFormat); updateSetting(UserSettingsRemoteProps.bookmark_name_format, defaultFormat); - }; + }, [updateSetting]); - const renderMappingInput = (key: string, label: string, defaultVal: string) => { - const value = localMapping[key] !== undefined ? localMapping[key] : defaultVal; - return ( -
- - { - setLocalMapping(prev => { - const newMapping = { ...prev }; - newMapping[key] = e.target.value; - return newMapping; - }); - }} - onBlur={e => { - const val = e.target.value; - setLocalMapping(prev => { - const currentVal = prev[key] !== undefined ? prev[key] : defaultVal; - if (val === currentVal) return prev; - - const newMapping = { ...prev }; - if (val === defaultVal) { - delete newMapping[key]; - } else { - newMapping[key] = val; - } - updateSetting(UserSettingsRemoteProps.bookmark_custom_mapping, newMapping); - return newMapping; - }); - }} - placeholder="(empty)" - /> -
- ); + const renderCustomMappingInputs = (options: { key: string; label: string; defaultVal: string }[]) => { + return options.map(opt => ( + + )); }; return ( @@ -244,60 +322,27 @@ export const BookmarkNameFormatSetting = () => {
Time
-
- {renderMappingInput('time_1h', '1 Hour', '1H')} - {renderMappingInput('time_4h', '4 Hours', '4H')} - {renderMappingInput('time_4h30m', '4.5 Hours', '4.5H')} - {renderMappingInput('time_16h', '16 Hours', '16H')} - {renderMappingInput('time_24h', '24 Hours', '')} - {renderMappingInput('time_48h', '48 Hours', '')} -
+
{renderCustomMappingInputs(TIME_OPTIONS)}
Mass
-
- {renderMappingInput('mass_normal', 'Normal Mass', '')} - {renderMappingInput('mass_half', 'Destab', 'Destab')} - {renderMappingInput('mass_verge', 'Critical', 'Crit')} -
+
{renderCustomMappingInputs(MASS_OPTIONS)}
Other / Formatting
-
{renderMappingInput('chain_separator', 'Chain Separator', '')}
+
{renderCustomMappingInputs(OTHER_OPTIONS)}
Hole Sizes
-
- {renderMappingInput('size_small', 'Small (Frigate)', 'S')} - {renderMappingInput('size_medium', 'Medium', 'M')} - {renderMappingInput('size_large', 'Large', '')} - {renderMappingInput('size_freight', 'Huge / Freight', 'XL')} - {renderMappingInput('size_capital', 'Capital', 'C')} -
+
{renderCustomMappingInputs(SIZE_OPTIONS)}
Destination Classes
-
- {renderMappingInput('class_c1', 'Class 1', 'C1')} - {renderMappingInput('class_c2', 'Class 2', 'C2')} - {renderMappingInput('class_c3', 'Class 3', 'C3')} - {renderMappingInput('class_c4', 'Class 4', 'C4')} - {renderMappingInput('class_c5', 'Class 5', 'C5')} - {renderMappingInput('class_c6', 'Class 6', 'C6')} - {renderMappingInput('class_c13', 'Class 13', 'C13')} - {renderMappingInput('class_c1c2c3', 'Class 1/2/3', 'C1/C2/C3')} - {renderMappingInput('class_c4c5', 'Class 4/5', 'C4/C5')} - {renderMappingInput('class_hs', 'High-Sec', 'HS')} - {renderMappingInput('class_ls', 'Low-Sec', 'LS')} - {renderMappingInput('class_ns', 'Null-Sec', 'NS')} - {renderMappingInput('class_thera', 'Thera', 'Thera')} - {renderMappingInput('class_pochven', 'Pochven', 'Pochven')} - {renderMappingInput('class_drifter', 'Drifter', 'Drifter')} -
+
{renderCustomMappingInputs(CLASS_OPTIONS)}
)} diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx index 0c36f236..4cd3b6cb 100644 --- a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx @@ -123,18 +123,29 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map out = { ...out, group: group! }; if (group === SignatureGroup.Wormhole) { - const targetSystem = values.linked_system - ? systems.find((s: any) => s.system_static_info?.solar_system_id?.toString() === values.linked_system) - : null; - const targetSystemClassGroup = targetSystem?.system_static_info - ? getSystemClassGroup(targetSystem.system_static_info.system_class) - : null; + let targetSystem = null; + if (values.linked_system) { + targetSystem = systems.find((s: any) => s.system_static_info?.solar_system_id?.toString() === values.linked_system); + } + + let targetSystemClassGroup = null; + if (targetSystem?.system_static_info) { + targetSystemClassGroup = getSystemClassGroup(targetSystem.system_static_info.system_class); + } + const targetSystemUuid = targetSystem?.id; - const targetSolarSystemIdStr = - targetSystem?.system_static_info?.solar_system_id?.toString() || values.linked_system; + + let targetSolarSystemIdStr = values.linked_system; + if (targetSystem?.system_static_info?.solar_system_id) { + targetSolarSystemIdStr = targetSystem.system_static_info.solar_system_id.toString(); + } const currentSystem = systems.find((s: any) => s.id === systemId); - const solarSystemIdStr = currentSystem?.system_static_info?.solar_system_id?.toString() || systemId; + + let solarSystemIdStr = systemId; + if (currentSystem?.system_static_info?.solar_system_id) { + solarSystemIdStr = currentSystem.system_static_info.solar_system_id.toString(); + } const { updatedSignature } = await handleAutoBookmark( out,