mirror of
https://github.com/wanderer-industries/wanderer
synced 2026-08-24 14:56:49 +00:00
Merge pull request #608 from Drewsif/add-auto-bookmark-copy
Feat: Configurable Wormhole Bookmark Formatting and Auto-Copy
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
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
|
||||
);
|
||||
};
|
||||
+19
-38
@@ -1,20 +1,21 @@
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
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';
|
||||
import { SETTINGS_KEYS, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
|
||||
import { getSystemClassGroup } from '@/hooks/Mapper/components/map/helpers/getSystemClassGroup.ts';
|
||||
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo';
|
||||
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);
|
||||
|
||||
@@ -23,7 +24,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,
|
||||
@@ -38,13 +39,9 @@ interface ExtendedSignatureCustomInfo {
|
||||
|
||||
export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => {
|
||||
const {
|
||||
outCommand,
|
||||
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}`,
|
||||
@@ -53,17 +50,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,32 +102,26 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
||||
[targetSystemClassGroup, wormholes],
|
||||
);
|
||||
|
||||
const { signatures } = useSystemSignaturesData({
|
||||
systemId: `${data.solar_system_source}`,
|
||||
settings: LINK_SIGNATURE_SETTINGS,
|
||||
});
|
||||
|
||||
const { handleLinkSignature } = useLinkSignature({ data, targetSystemClassGroup });
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(signature: SystemSignature) => {
|
||||
async (signature: SystemSignature) => {
|
||||
if (!signature) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { outCommand } = ref.current;
|
||||
|
||||
outCommand({
|
||||
type: OutCommand.linkSignatureToSystem,
|
||||
data: {
|
||||
...data,
|
||||
signature_eve_id: signature.eve_id,
|
||||
},
|
||||
});
|
||||
await handleLinkSignature(signature);
|
||||
|
||||
setVisible(false);
|
||||
},
|
||||
[data, setVisible],
|
||||
[handleLinkSignature, setVisible],
|
||||
);
|
||||
|
||||
const { signatures } = useSystemSignaturesData({
|
||||
systemId: `${data.solar_system_source}`,
|
||||
settings: LINK_SIGNTATURE_SETTINGS,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!targetSystemDynamicInfo) {
|
||||
handleHide();
|
||||
@@ -160,7 +141,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}
|
||||
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
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, SignatureGroup, 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<any>(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 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(
|
||||
signatureToLink,
|
||||
userSettings,
|
||||
systemSignatures,
|
||||
systemUuid,
|
||||
data.solar_system_source.toString(),
|
||||
wormholesData,
|
||||
targetSystemClassGroup,
|
||||
targetSystemUuid,
|
||||
targetSolarSystemIdStr,
|
||||
);
|
||||
|
||||
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);
|
||||
|
||||
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':
|
||||
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 (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 };
|
||||
};
|
||||
+11
-5
@@ -11,6 +11,7 @@ import {
|
||||
} from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx';
|
||||
import { WidgetsSettings } from './components/WidgetsSettings';
|
||||
import { CommonSettings } from './components/CommonSettings';
|
||||
import { BookmarksSettings } from './components/BookmarksSettings';
|
||||
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 });
|
||||
@@ -62,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}
|
||||
>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-3 h-full">
|
||||
<div className="flex flex-col gap-2 h-full">
|
||||
<TabView
|
||||
activeIndex={activeIndex}
|
||||
className="vertical-tabs-container"
|
||||
className="vertical-tabs-container h-full"
|
||||
onTabChange={e => setActiveIndex(e.index)}
|
||||
>
|
||||
<TabPanel header="Common" headerClassName={styles.verticalTabHeader}>
|
||||
@@ -89,6 +91,10 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => {
|
||||
{renderSettingsList(SIGNATURES_CHECKBOXES_PROPS)}
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel header="Bookmarks" className="h-full" headerClassName={styles.verticalTabHeader}>
|
||||
<BookmarksSettings />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel header="Widgets" className="h-full" headerClassName={styles.verticalTabHeader}>
|
||||
<WidgetsSettings />
|
||||
</TabPanel>
|
||||
|
||||
+50
-20
@@ -21,12 +21,15 @@ 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';
|
||||
|
||||
type MapSettingsContextType = {
|
||||
renderSettingItem: (item: SettingsListItem) => ReactNode;
|
||||
updateSetting: (prop: keyof UserSettings, value: boolean | string | Record<string, string>) => Promise<void>;
|
||||
setUserRemoteSettings: Dispatch<SetStateAction<UserSettingsRemote>>;
|
||||
settings: UserSettings;
|
||||
};
|
||||
|
||||
const MapSettingsContext = createContext<MapSettingsContextType | undefined>(undefined);
|
||||
@@ -51,29 +54,39 @@ 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 { userRemoteSettings, interfaceSettings, outCommand, setInterfaceSettings } = refVars.current;
|
||||
const handleSettingChange = useCallback(
|
||||
async (prop: keyof UserSettings, value: boolean | string | Record<string, string>) => {
|
||||
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) => {
|
||||
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') {
|
||||
@@ -103,13 +116,30 @@ export const MapSettingsProvider = ({ children }: WithChildren) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (item.type === 'text') {
|
||||
return (
|
||||
<div key={item.prop.toString()} className="flex flex-col gap-1 w-full mt-2 mb-2">
|
||||
{item.label && <label className="text-[var(--gray-200)] text-[13px] select-none">{item.label}</label>}
|
||||
<InputText
|
||||
className="text-sm w-full"
|
||||
defaultValue={(currentValue as string) || ''}
|
||||
onBlur={e => handleSettingChange(item.prop, e.target.value)}
|
||||
placeholder={item.placeholder}
|
||||
/>
|
||||
{item.helperText && <small className="text-gray-400 text-xs mt-1">{item.helperText}</small>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
[handleSettingChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<MapSettingsContext.Provider value={{ renderSettingItem, setUserRemoteSettings }}>
|
||||
<MapSettingsContext.Provider
|
||||
value={{ renderSettingItem, updateSetting: handleSettingChange, setUserRemoteSettings, settings: mergedSettings }}
|
||||
>
|
||||
{children}
|
||||
</MapSettingsContext.Provider>
|
||||
);
|
||||
|
||||
+352
@@ -0,0 +1,352 @@
|
||||
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, 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';
|
||||
|
||||
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: '{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., 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' },
|
||||
];
|
||||
|
||||
interface CustomMappingInputProps {
|
||||
mappingKey: string;
|
||||
label: string;
|
||||
defaultVal: string;
|
||||
localMapping: Record<string, string>;
|
||||
setLocalMapping: React.Dispatch<React.SetStateAction<Record<string, string>>>;
|
||||
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<HTMLInputElement>) => {
|
||||
setLocalMapping(prev => {
|
||||
const newMapping = { ...prev };
|
||||
newMapping[mappingKey] = e.target.value;
|
||||
return newMapping;
|
||||
});
|
||||
}, [mappingKey, setLocalMapping]);
|
||||
|
||||
const handleBlur = useCallback((e: React.FocusEvent<HTMLInputElement>) => {
|
||||
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 (
|
||||
<div className="flex flex-col gap-1 w-[120px]">
|
||||
<label className="text-stone-400 text-[10px]">{label}</label>
|
||||
<InputText
|
||||
className="text-xs w-full py-1 px-2"
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
placeholder="(empty)"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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 || '';
|
||||
const customMapping = settings.bookmark_custom_mapping || {};
|
||||
const inputRef = useRef<HTMLInputElement>(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 ? `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.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,
|
||||
dummyWormholesData,
|
||||
isZero,
|
||||
localMapping,
|
||||
{ preview_sys: [otherDummySig] },
|
||||
'preview_sys',
|
||||
);
|
||||
}, [localFormat, settings.bookmark_wormholes_start_at_zero, localMapping]);
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
if (localFormat !== formatStr) {
|
||||
updateSetting(UserSettingsRemoteProps.bookmark_name_format, localFormat);
|
||||
}
|
||||
}, [localFormat, formatStr, updateSetting]);
|
||||
|
||||
const insertVariable = useCallback((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);
|
||||
}
|
||||
}, [localFormat, updateSetting]);
|
||||
|
||||
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 renderCustomMappingInputs = (options: { key: string; label: string; defaultVal: string }[]) => {
|
||||
return options.map(opt => (
|
||||
<CustomMappingInput
|
||||
key={opt.key}
|
||||
mappingKey={opt.key}
|
||||
label={opt.label}
|
||||
defaultVal={opt.defaultVal}
|
||||
localMapping={localMapping}
|
||||
setLocalMapping={setLocalMapping}
|
||||
updateSetting={updateSetting}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3 mt-2">
|
||||
<div className="flex justify-between items-end">
|
||||
<label className="text-[var(--gray-200)] text-[13px] select-none">Bookmark Name Format</label>
|
||||
<WdButton size="small" outlined onClick={resetToDefault} className="text-xs py-1 px-2 h-auto min-h-[24px]">
|
||||
Reset to Default
|
||||
</WdButton>
|
||||
</div>
|
||||
<InputText
|
||||
ref={inputRef}
|
||||
className="text-sm w-full"
|
||||
value={localFormat}
|
||||
onChange={e => setLocalFormat(e.target.value)}
|
||||
onBlur={handleBlur}
|
||||
placeholder="e.g. {chain_index} {sig_letters} {dest_type} {size} {mass_status} {time_status}"
|
||||
/>
|
||||
<div className="text-sm p-2 bg-stone-800 rounded border border-stone-700 flex flex-col gap-1">
|
||||
<span className="text-stone-400 text-xs">Live Preview:</span>
|
||||
<span className="text-stone-200 font-mono">
|
||||
{preview || <span className="italic text-stone-500">Empty</span>}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto custom-scrollbar pr-1 text-xs text-stone-400 p-2 bg-stone-800/50 rounded border border-stone-800 mt-2 max-h-[160px]">
|
||||
<h4 className="text-stone-300 font-semibold mb-2">Available Variables (Click to insert)</h4>
|
||||
<ul className="space-y-1">
|
||||
{VARIABLES.map(v => (
|
||||
<li key={v.id}>
|
||||
<code
|
||||
className="text-stone-200 cursor-pointer hover:bg-stone-700 px-1 rounded transition-colors inline-block"
|
||||
onClick={() => insertVariable(v.id)}
|
||||
>
|
||||
{v.id}
|
||||
</code>{' '}
|
||||
- {v.desc}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="mt-2">
|
||||
<WdButton
|
||||
className="text-xs w-full justify-center bg-stone-800 hover:bg-stone-700 border-stone-700 text-stone-300 py-1"
|
||||
onClick={() => setShowAdvanced(!showAdvanced)}
|
||||
>
|
||||
{showAdvanced ? 'Hide Advanced String Customization' : 'Show Advanced String Customization'}
|
||||
</WdButton>
|
||||
|
||||
{showAdvanced && (
|
||||
<div className="p-3 bg-stone-900 rounded border border-stone-800 mt-2 flex flex-col gap-4">
|
||||
<div className="flex justify-between items-start">
|
||||
<p className="text-stone-400 text-xs italic">Override the default output of specific format variables.</p>
|
||||
<WdButton
|
||||
size="small"
|
||||
outlined
|
||||
className="text-xs py-1 px-2 h-auto min-h-[24px]"
|
||||
onClick={() => {
|
||||
setLocalMapping({});
|
||||
updateSetting(UserSettingsRemoteProps.bookmark_custom_mapping, {});
|
||||
}}
|
||||
>
|
||||
Reset Mappings
|
||||
</WdButton>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<h5 className="text-stone-300 text-xs font-semibold uppercase tracking-wider">Time</h5>
|
||||
<div className="flex flex-wrap gap-2">{renderCustomMappingInputs(TIME_OPTIONS)}</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<h5 className="text-stone-300 text-xs font-semibold uppercase tracking-wider">Mass</h5>
|
||||
<div className="flex flex-wrap gap-2">{renderCustomMappingInputs(MASS_OPTIONS)}</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<h5 className="text-stone-300 text-xs font-semibold uppercase tracking-wider">Other / Formatting</h5>
|
||||
<div className="flex flex-wrap gap-2">{renderCustomMappingInputs(OTHER_OPTIONS)}</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<h5 className="text-stone-300 text-xs font-semibold uppercase tracking-wider">Hole Sizes</h5>
|
||||
<div className="flex flex-wrap gap-2">{renderCustomMappingInputs(SIZE_OPTIONS)}</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<h5 className="text-stone-300 text-xs font-semibold uppercase tracking-wider">Destination Classes</h5>
|
||||
<div className="flex flex-wrap gap-2">{renderCustomMappingInputs(CLASS_OPTIONS)}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+33
@@ -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 (
|
||||
<div className="w-full h-full flex flex-col gap-3 overflow-y-auto custom-scrollbar pr-1">
|
||||
{!settings.link_signature_on_splash && !interfaceSettings.hideBookmarkWarning && (
|
||||
<div className="relative p-2 pr-6 bg-yellow-900/30 border border-yellow-700/50 rounded text-yellow-500/90 text-sm">
|
||||
⚠️ It is highly recommended to enable 'Link signature on splash' (in the Signatures tab) to fully utilize
|
||||
automatic bookmark naming.
|
||||
<button
|
||||
type="button"
|
||||
className="absolute top-1 right-2 text-yellow-700 hover:text-yellow-500 transition-colors"
|
||||
onClick={() => setInterfaceSettings(prev => ({ ...prev, hideBookmarkWarning: true }))}
|
||||
>
|
||||
<i className="pi pi-times text-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-1 mt-2">{BOOKMARKS_SETTINGS_PROPS.map(renderSettingItem)}</div>
|
||||
|
||||
<BookmarkNameFormatSetting />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -6,12 +6,38 @@ 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]: '',
|
||||
[UserSettingsRemoteProps.bookmark_custom_mapping]: {},
|
||||
[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]: '',
|
||||
[UserSettingsRemoteProps.bookmark_return_hole_ignore]: false,
|
||||
[UserSettingsRemoteProps.bookmark_return_hole_symbol]: '',
|
||||
};
|
||||
|
||||
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,
|
||||
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,
|
||||
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[] = [
|
||||
@@ -48,6 +74,48 @@ export const SIGNATURES_CHECKBOXES_PROPS: SettingsListItem[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const BOOKMARKS_SETTINGS_PROPS: SettingsListItem[] = [
|
||||
{
|
||||
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_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',
|
||||
type: 'dropdown',
|
||||
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,
|
||||
},
|
||||
];
|
||||
|
||||
export const CONNECTIONS_CHECKBOXES_PROPS: SettingsListItem[] = [
|
||||
{
|
||||
prop: UserSettingsRemoteProps.delete_connection_with_sigs,
|
||||
|
||||
@@ -4,12 +4,30 @@ 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',
|
||||
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',
|
||||
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 = {
|
||||
link_signature_on_splash: boolean;
|
||||
select_on_spash: boolean;
|
||||
delete_connection_with_sigs: boolean;
|
||||
bookmark_name_format: string;
|
||||
bookmark_custom_mapping?: Record<string, string>;
|
||||
bookmark_wormholes_start_at_zero: boolean;
|
||||
bookmark_auto_copy: boolean;
|
||||
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;
|
||||
@@ -17,6 +35,9 @@ 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;
|
||||
dependsOn?: keyof UserSettings;
|
||||
};
|
||||
|
||||
+77
-6
@@ -2,18 +2,22 @@ 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 { 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<SystemSignature, 'linked_system'> & {
|
||||
linked_system: string;
|
||||
destType: string;
|
||||
k162Type?: string;
|
||||
time_status: TimeStatus;
|
||||
mass_status: MassState;
|
||||
};
|
||||
@@ -26,11 +30,23 @@ 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<Partial<SystemSignaturePrepared>>({});
|
||||
|
||||
const [userSettings, setUserSettings] = useState<any>(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) => {
|
||||
@@ -44,10 +60,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 +122,45 @@ 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 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;
|
||||
|
||||
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);
|
||||
|
||||
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,
|
||||
userSettings,
|
||||
systemSignatures,
|
||||
systemId,
|
||||
solarSystemIdStr,
|
||||
wormholesData,
|
||||
targetSystemClassGroup,
|
||||
targetSystemUuid,
|
||||
targetSolarSystemIdStr,
|
||||
);
|
||||
out = updatedSignature;
|
||||
}
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: {
|
||||
@@ -131,7 +189,17 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
|
||||
signatureForm.reset();
|
||||
onHide();
|
||||
},
|
||||
[signatureData, signatureForm, outCommand, systemId, onHide],
|
||||
[
|
||||
signatureData,
|
||||
signatureForm,
|
||||
outCommand,
|
||||
systemId,
|
||||
onHide,
|
||||
systemSignatures,
|
||||
systems,
|
||||
wormholesData,
|
||||
userSettings,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -142,19 +210,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,
|
||||
|
||||
@@ -0,0 +1,491 @@
|
||||
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, MULTI_DEST_WHS } from '@/hooks/Mapper/constants';
|
||||
import { ShipSizeStatus } from '@/hooks/Mapper/types/connection';
|
||||
|
||||
const getTimeStatusString = (status?: TimeStatus, mapping?: Record<string, string>): string => {
|
||||
switch (status) {
|
||||
case TimeStatus._1h:
|
||||
return mapping?.time_1h !== undefined ? mapping.time_1h : '1H';
|
||||
case TimeStatus._4h:
|
||||
return mapping?.time_4h !== undefined ? mapping.time_4h : '4H';
|
||||
case TimeStatus._4h30m:
|
||||
return mapping?.time_4h30m !== undefined ? mapping.time_4h30m : '4.5H';
|
||||
case TimeStatus._16h:
|
||||
return mapping?.time_16h !== undefined ? mapping.time_16h : '16H';
|
||||
case TimeStatus._24h:
|
||||
return mapping?.time_24h !== undefined ? mapping.time_24h : '';
|
||||
case TimeStatus._48h:
|
||||
return mapping?.time_48h !== undefined ? mapping.time_48h : '';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const getMassStatusString = (status?: MassState, mapping?: Record<string, string>): string => {
|
||||
switch (status) {
|
||||
case MassState.normal:
|
||||
return mapping?.mass_normal !== undefined ? mapping.mass_normal : '';
|
||||
case MassState.half:
|
||||
return mapping?.mass_half !== undefined ? mapping.mass_half : 'Destab';
|
||||
case MassState.verge:
|
||||
return mapping?.mass_verge !== undefined ? mapping.mass_verge : 'Crit';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const DEST_CLASS_OVERRIDES: Record<string, string> = {
|
||||
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',
|
||||
drifter: 'Drifter',
|
||||
p: 'Pochven',
|
||||
pochven: 'Pochven',
|
||||
'c1/c2/c3': 'C1/C2/C3',
|
||||
'c4/c5': 'C4/C5',
|
||||
};
|
||||
|
||||
const formatDestString = (dest: string | null | undefined, mapping?: Record<string, string>): 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];
|
||||
}
|
||||
if (lowerDest.length <= 3) return dest.toUpperCase();
|
||||
return dest.charAt(0).toUpperCase() + dest.slice(1);
|
||||
};
|
||||
|
||||
export const numberToLetters = (num: number, startAtZero: boolean = false): string => {
|
||||
if (startAtZero) {
|
||||
num += 1;
|
||||
}
|
||||
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<string, SystemSignature[]>,
|
||||
currentSystemUuid: string,
|
||||
currentSolarSystemId: string,
|
||||
currentEveId: string,
|
||||
startAtZero: boolean = false,
|
||||
separator: 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 === currentSystemUuid || sysId === currentSolarSystemId) continue;
|
||||
|
||||
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);
|
||||
}
|
||||
} else if (parentInfo.bookmark_index != null) {
|
||||
if (!parentBookmarkIndex || String(parentInfo.bookmark_index).length < parentBookmarkIndex.length) {
|
||||
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 currentSigsRaw = [
|
||||
...(systemSignatures[currentSystemUuid] || []),
|
||||
...(systemSignatures[currentSolarSystemId] || []),
|
||||
];
|
||||
|
||||
// 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);
|
||||
|
||||
let i = startAtZero ? 0 : 1;
|
||||
while (existingIndices.includes(i)) {
|
||||
i++;
|
||||
}
|
||||
|
||||
const chained = parentBookmarkIndex !== undefined ? `${parentBookmarkIndex}${separator}${i}` : `${i}`;
|
||||
const chainedLetters =
|
||||
parentBookmarkIndexLetters !== undefined
|
||||
? `${parentBookmarkIndexLetters}${separator}${i}`
|
||||
: numberToLetters(i, startAtZero);
|
||||
|
||||
return { index: i, chained, chainedLetters };
|
||||
};
|
||||
|
||||
export const formatBookmarkName = (
|
||||
formatStr: string,
|
||||
signature: SystemSignature,
|
||||
destSystemClass: string | null,
|
||||
bookmarkIndex: number | string,
|
||||
wormholesData: Record<string, WormholeDataRaw> = {},
|
||||
startAtZero: boolean = false,
|
||||
mapping?: Record<string, string>,
|
||||
systemSignatures?: Record<string, SystemSignature[]>,
|
||||
currentSystemId?: string,
|
||||
currentSolarSystemId?: string,
|
||||
): string => {
|
||||
let result = formatStr;
|
||||
const info = parseSignatureCustomInfo(signature.custom_info);
|
||||
|
||||
// Replace {index}
|
||||
result = result.replace(/\{index\}/g, () => 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, () =>
|
||||
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 ||
|
||||
(typeof bookmarkIndex === 'number' ? numberToLetters(bookmarkIndex, startAtZero) : 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 && 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) {
|
||||
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, 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 = '';
|
||||
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) {
|
||||
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];
|
||||
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;
|
||||
const defaultSizeNames: Record<ShipSizeStatus, string> = {
|
||||
[ShipSizeStatus.small]: 'S',
|
||||
[ShipSizeStatus.medium]: 'M',
|
||||
[ShipSizeStatus.large]: '',
|
||||
[ShipSizeStatus.freight]: 'XL',
|
||||
[ShipSizeStatus.capital]: 'C',
|
||||
};
|
||||
const sizeMappingKeys: Record<ShipSizeStatus, string> = {
|
||||
[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) {
|
||||
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, mapping));
|
||||
|
||||
// Replace {mass_status} -> Parsed from custom_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 || '');
|
||||
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
|
||||
export const handleAutoBookmark = async (
|
||||
signature: SystemSignature,
|
||||
currentSettings: any,
|
||||
systemSignatures: Record<string, SystemSignature[]>,
|
||||
currentSystemId: string,
|
||||
currentSolarSystemId: string,
|
||||
wormholesData: Record<string, WormholeDataRaw>,
|
||||
targetSystemClassGroup: string | null,
|
||||
targetSystemUuid?: string,
|
||||
targetSolarSystemId?: string,
|
||||
): 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;
|
||||
let bookmarkIndexToUse: number | string = bookmarkIndex != null ? bookmarkIndex : '';
|
||||
|
||||
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,
|
||||
currentSystemId,
|
||||
currentSolarSystemId,
|
||||
signature.eve_id,
|
||||
currentSettings?.bookmark_wormholes_start_at_zero,
|
||||
separator,
|
||||
);
|
||||
bookmarkIndex = calculated.index;
|
||||
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;
|
||||
}
|
||||
|
||||
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 = bookmarkIndexToUse.toString();
|
||||
break;
|
||||
case 'index_letter':
|
||||
autoName =
|
||||
typeof bookmarkIndexToUse === 'number'
|
||||
? numberToLetters(bookmarkIndexToUse, currentSettings.bookmark_wormholes_start_at_zero)
|
||||
: bookmarkIndexToUse.toString();
|
||||
break;
|
||||
case 'chain_index':
|
||||
autoName = info.bookmark_index_chained || bookmarkIndexToUse.toString();
|
||||
break;
|
||||
case 'chain_index_letters':
|
||||
autoName = info.bookmark_index_chained_letters || info.bookmark_index_chained || bookmarkIndexToUse.toString();
|
||||
break;
|
||||
}
|
||||
if (autoName !== '' || isReturnHole) {
|
||||
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,
|
||||
bookmarkIndexToUse,
|
||||
wormholesData,
|
||||
currentSettings.bookmark_wormholes_start_at_zero,
|
||||
currentSettings.bookmark_custom_mapping,
|
||||
systemSignatures,
|
||||
currentSystemId,
|
||||
currentSolarSystemId,
|
||||
);
|
||||
|
||||
// Run this synchronously to avoid clipboard issues if possible
|
||||
await copyToClipboard(formattedStr);
|
||||
}
|
||||
|
||||
return { updatedSignature, shouldUpdate };
|
||||
};
|
||||
@@ -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 {};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -31,6 +31,7 @@ export type InterfaceStoredSettings = {
|
||||
theme: AvailableThemes;
|
||||
minimapPlacement: MiniMapPlacement;
|
||||
pingsPlacement: PingsPlacement;
|
||||
hideBookmarkWarning: boolean;
|
||||
};
|
||||
|
||||
export type RoutesType = {
|
||||
@@ -47,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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -29,9 +29,13 @@ export type GroupType = {
|
||||
|
||||
export type SignatureCustomInfo = {
|
||||
destType?: string;
|
||||
k162Type?: string;
|
||||
time_status?: number;
|
||||
isCrit?: boolean;
|
||||
mass_status?: number;
|
||||
bookmark_index?: number;
|
||||
bookmark_index_chained?: string;
|
||||
bookmark_index_chained_letters?: string;
|
||||
};
|
||||
|
||||
export type SystemSignature = {
|
||||
|
||||
@@ -5,7 +5,13 @@ 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" => "",
|
||||
"bookmark_custom_mapping" => %{},
|
||||
"system_auto_tag" => "",
|
||||
"system_custom_label_name" => "",
|
||||
"bookmark_return_hole_ignore" => false,
|
||||
"bookmark_return_hole_symbol" => ""
|
||||
}
|
||||
|
||||
def get(map_id, user_id) do
|
||||
|
||||
@@ -232,7 +232,20 @@ 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",
|
||||
"bookmark_custom_mapping",
|
||||
"bookmark_wormholes_start_at_zero",
|
||||
"bookmark_auto_copy",
|
||||
"bookmark_auto_temp_name",
|
||||
"system_auto_tag",
|
||||
"system_custom_label_name",
|
||||
"bookmark_return_hole_ignore",
|
||||
"bookmark_return_hole_symbol"
|
||||
])
|
||||
|> Jason.encode!()
|
||||
|
||||
{:ok, user_settings} =
|
||||
|
||||
Reference in New Issue
Block a user