diff --git a/assets/js/hooks/Mapper/components/map/Map.tsx b/assets/js/hooks/Mapper/components/map/Map.tsx index 49e00766..b06613e9 100644 --- a/assets/js/hooks/Mapper/components/map/Map.tsx +++ b/assets/js/hooks/Mapper/components/map/Map.tsx @@ -125,10 +125,9 @@ const MapComp = ({ const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem }); const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers(); const { update } = useMapState(); - const { variant, gap, size, color } = useBackgroundVars(theme); + const { variant, gap, size, color, snapSize } = useBackgroundVars(theme); const { isPanAndDrag, nodeComponent, connectionMode } = getBehaviorForTheme(theme || 'default'); - // You can create nodeTypes dynamically based on the node component const nodeTypes = useMemo(() => { return { custom: nodeComponent, @@ -256,7 +255,7 @@ const MapComp = ({ onEdgesChange(nextChanges); }, - [getEdge, getNode, onEdgesChange], + [canRemoveConnection, getEdge, getNode, onEdgesChange], ); useEffect(() => { @@ -283,6 +282,7 @@ const MapComp = ({ nodeTypes={nodeTypes} connectionMode={connectionMode} snapToGrid + snapGrid={[snapSize, snapSize]} nodeDragThreshold={10} onNodeDragStop={handleDragStop} onSelectionDragStop={handleSelectionDragStop} diff --git a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemKillsCounter.module.scss b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemKillsCounter.module.scss new file mode 100644 index 00000000..c3a74c23 --- /dev/null +++ b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemKillsCounter.module.scss @@ -0,0 +1,48 @@ +.KillsBookmark { + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 8px; + font-weight: 700; + border: 0; + border-radius: 5px 5px 0 0; + padding: 4px 3px; + + } + +.KillsBookmarkWithIcon { + display: flex; + align-items: center; + justify-content: center; + margin-top: -2px; + text-shadow: 0 0 3px #000; + padding-right: 2px; + height: 8px; + font-size: 8px; + line-height: 12px; + font-weight: 700; + text-size-adjust: 100%; + + .pi { + font-size: 9px; + } + + .text { + font-size: 9px; + font-family: var(--rf-node-font-family, inherit) !important; + font-weight: var(--rf-node-font-weight, inherit) !important; + } +} + +.TooltipContainer { + background-color: #1a1a1a; + color: #fff; + padding: 3px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); + border-radius: 2px; + pointer-events: auto; + max-width: 500px; + max-height: 300px; + overflow-y: auto; + overflow-x: hidden; + } diff --git a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemKillsCounter.tsx b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemKillsCounter.tsx new file mode 100644 index 00000000..7f319a38 --- /dev/null +++ b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemKillsCounter.tsx @@ -0,0 +1,32 @@ +import { SystemKillsContent } from '../../../mapInterface/widgets/SystemKills/SystemKillsContent/SystemKillsContent'; +import { useKillsCounter } from '../../hooks/useKillsCounter'; +import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper'; +import { WithChildren, WithClassName } from '@/hooks/Mapper/types/common.ts'; + +type TooltipSize = 'xs' | 'sm' | 'md' | 'lg'; + +type KillsBookmarkTooltipProps = { + killsCount: number; + killsActivityType: string | null; + systemId: string; + className?: string; + size?: TooltipSize; +} & WithChildren & + WithClassName; + +export const KillsCounter = ({ killsCount, systemId, className, children, size = 'xs' }: KillsBookmarkTooltipProps) => { + const { isLoading, kills: detailedKills, systemNameMap } = useKillsCounter({ realSystemId: systemId }); + + if (!killsCount || detailedKills.length === 0 || !systemId || isLoading) return null; + + const tooltipContent = ( + + ); + + return ( + // @ts-ignore + + {children} + + ); +}; diff --git a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemLocalCounter.tsx b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemLocalCounter.tsx new file mode 100644 index 00000000..9122f0d9 --- /dev/null +++ b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemLocalCounter.tsx @@ -0,0 +1,64 @@ +import { useMemo } from 'react'; +import clsx from 'clsx'; +import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper'; +import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit/WdTooltip'; +import { LocalCharactersList, CharItemProps } from '../../../mapInterface/widgets/LocalCharacters/components'; +import { useLocalCharactersItemTemplate } from '../../../mapInterface/widgets/LocalCharacters/hooks/useLocalCharacters'; +import { useLocalCharacterWidgetSettings } from '../../../mapInterface/widgets/LocalCharacters/hooks/useLocalWidgetSettings'; + +interface LocalCounterProps { + localCounterCharacters: Array; + classes: { [key: string]: string }; + hasUserCharacters: boolean; + showIcon?: boolean; +} + +export function LocalCounter({ + localCounterCharacters, + hasUserCharacters, + classes, + showIcon = true, +}: LocalCounterProps) { + const [settings] = useLocalCharacterWidgetSettings(); + const itemTemplate = useLocalCharactersItemTemplate(settings.showShipName); + + const pilotTooltipContent = useMemo(() => { + return ( +
+ +
+ ); + }, [localCounterCharacters, itemTemplate]); + + if (localCounterCharacters.length === 0) { + return null; + } + + return ( +
+ +
+ {showIcon && } + {localCounterCharacters.length} +
+
+
+ ); +} diff --git a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.module.scss b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.module.scss index afb94486..e91866b2 100644 --- a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.module.scss +++ b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.module.scss @@ -2,29 +2,25 @@ $pastel-blue: #5a7d9a; $pastel-pink: #d291bc; -$pastel-green: #88b04b; -$pastel-yellow: #ffdd59; $dark-bg: #2d2d2d; $text-color: #ffffff; $tooltip-bg: #202020; -$node-bg-color: #202020; -$node-soft-bg-color: #202020; -$text-color: #ffffff; -$tag-color: #38BDF8; -$region-name: #D6D3D1; -$custom-name: #93C5FD; - .RootCustomNode { display: flex; width: 130px; height: 34px; + font-family: var(--rf-node-font-family, inherit) !important; + font-weight: var(--rf-node-font-weight, inherit) !important; + flex-direction: column; padding: 2px 6px; font-size: 10px; - background-color: $tooltip-bg; + background-color: var(--rf-node-bg-color, #202020) !important; + color: var(--rf-text-color, #ffffff); + box-shadow: 0 0 5px rgba($dark-bg, 0.5); border: 1px solid darken($pastel-blue, 10%); border-radius: 5px; @@ -92,14 +88,6 @@ $custom-name: #93C5FD; box-shadow: 0 0 10px #9a1af1c2; } - &.tooltip { - background-color: $tooltip-bg; - color: $text-color; - padding: 5px 10px; - border-radius: 3px; - border: 1px solid $pastel-pink; - } - &.eve-system-status-home { border: 1px solid var(--eve-solar-system-status-color-home-dark30); background-image: linear-gradient( @@ -178,8 +166,6 @@ $custom-name: #93C5FD; padding-left: 3px; padding-right: 3px; - //background-color: #833ca4; - &:not(:first-child) { box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3); } @@ -266,26 +252,18 @@ $custom-name: #93C5FD; .TagTitle { font-size: 11px; - font-weight: medium; + font-weight: 500; text-shadow: 0 0 2px rgba(231, 146, 52, 0.73); - color: var(--rf-tag-color, #38BDF8); } /* Firefox kostyl */ @-moz-document url-prefix() { .classSystemName { - font-family: inherit !important; font-weight: bold; } } - .classSystemName { - //font-weight: bold; - } - - .solarSystemName { - } } .BottomRow { @@ -294,22 +272,23 @@ $custom-name: #93C5FD; align-items: center; height: 19px; - .localCounter { - display: flex; - //align-items: center; - gap: 2px; - - & > i { - position: relative; - top: 1px; + .hasLocalCounter { + margin-right: 1.25rem; + &.countAbove9 { + margin-right: 1.5rem; } + } - & > span { - font-size: 9px; - line-height: 9px; - font-weight: 500; - //margin-top: 1px; - } + .lockIcon { + font-size: 0.45rem; + font-weight: bold; + position: relative; + } + + .mapMarker { + font-size: 0.45rem; + font-weight: bold; + position: relative; } } @@ -395,3 +374,39 @@ $custom-name: #93C5FD; } } } + +.LocalCounterLayer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 9999; + padding: 8; + + .localCounter { + position: absolute; + pointer-events: auto; + top: 10.5px; + right: 8px; + mix-blend-mode: screen; + gap: 2px; + color: var(--rf-node-local-counter, #5cb85c); + + &.hasUserCharacters { + color: var(--rf-has-user-characters, #fbbf24); + } + + & > i { + position: relative; + top: 1px; + } + + & > span { + font-size: 9px; + line-height: 9px; + font-weight: var(--rf-local-counter-font-weight, 500); + } + } +} diff --git a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.tsx b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.tsx index 0bff0541..b12502f6 100644 --- a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.tsx +++ b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.tsx @@ -1,20 +1,23 @@ import { memo } from 'react'; import { MapSolarSystemType } from '../../map.types'; -import { Handle, Position, NodeProps } from 'reactflow'; +import { Handle, NodeProps, Position } from 'reactflow'; import clsx from 'clsx'; import classes from './SolarSystemNodeDefault.module.scss'; import { PrimeIcons } from 'primereact/api'; -import { useSolarSystemNode } from '../../hooks/useSolarSystemNode'; +import { useLocalCounter, useSolarSystemNode } from '../../hooks/useSolarSystemLogic'; import { + EFFECT_BACKGROUND_STYLES, MARKER_BOOKMARK_BG_STYLES, STATUS_CLASSES, - EFFECT_BACKGROUND_STYLES, } from '@/hooks/Mapper/components/map/constants'; import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp'; import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature'; +import { LocalCounter } from './SolarSystemLocalCounter'; +import { KillsCounter } from './SolarSystemKillsCounter'; export const SolarSystemNodeDefault = memo((props: NodeProps) => { const nodeVars = useSolarSystemNode(props); + const { localCounterCharacters } = useLocalCounter(nodeVars); return ( <> @@ -22,7 +25,7 @@ export const SolarSystemNodeDefault = memo((props: NodeProps
{nodeVars.labelCustom !== '' && (
- {nodeVars.labelCustom} + {nodeVars.labelCustom}
)} @@ -32,13 +35,19 @@ export const SolarSystemNodeDefault = memo((props: NodeProps
)} - {nodeVars.killsCount && ( -
+ {nodeVars.killsCount && nodeVars.killsCount > 0 && nodeVars.solarSystemId && ( +
{nodeVars.killsCount}
-
+ )} {nodeVars.labelsInfo.map(x => ( @@ -53,10 +62,8 @@ export const SolarSystemNodeDefault = memo((props: NodeProps className={clsx( classes.RootCustomNode, nodeVars.regionClass && classes[nodeVars.regionClass], - classes[STATUS_CLASSES[nodeVars.status]], - { - [classes.selected]: nodeVars.selected, - }, + nodeVars.status !== undefined ? classes[STATUS_CLASSES[nodeVars.status]] : '', + { [classes.selected]: nodeVars.selected }, )} > {nodeVars.visible && ( @@ -88,7 +95,7 @@ export const SolarSystemNodeDefault = memo((props: NodeProps {nodeVars.isWormhole && (
{nodeVars.sortedStatics.map(whClass => ( - + ))}
)} @@ -114,24 +121,15 @@ export const SolarSystemNodeDefault = memo((props: NodeProps {nodeVars.isWormhole && !nodeVars.customName &&
}
-
- {nodeVars.locked && ( - - )} - - {nodeVars.hubs.includes(nodeVars.solarSystemId.toString()) && ( - - )} - - {nodeVars.charactersInSystem.length > 0 && ( -
- - {nodeVars.charactersInSystem.length} -
+
0, + [classes.countAbove9]: nodeVars.charactersInSystem.length > 9, + })} + > + {nodeVars.locked && } + {nodeVars.hubs.includes(nodeVars.solarSystemId) && ( + )}
@@ -145,7 +143,7 @@ export const SolarSystemNodeDefault = memo((props: NodeProps {nodeVars.unsplashedLeft.length > 0 && (
{nodeVars.unsplashedLeft.map(sig => ( - + ))}
)} @@ -153,14 +151,14 @@ export const SolarSystemNodeDefault = memo((props: NodeProps {nodeVars.unsplashedRight.length > 0 && (
{nodeVars.unsplashedRight.map(sig => ( - + ))}
)} )} -
+
nodeVars.dbClick(e)} className={classes.Handlers}> id="d" />
+ ); }); diff --git a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.module.scss b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.module.scss index 2bceef0e..181b0006 100644 --- a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.module.scss +++ b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.module.scss @@ -1,91 +1,6 @@ @import './SolarSystemNodeDefault.module.scss'; -/* --------------------------- - Only override what's different ---------------------------- */ - -/* 1) .RootCustomNode: - - new background-color using CSS var - - plus color, font-family, and font-weight */ -.RootCustomNode { - background-color: var(--rf-node-bg-color, #202020) !important; - color: var(--rf-text-color, #ffffff); - font-family: var(--rf-node-font-family, inherit) !important; - font-weight: var(--rf-node-font-weight, inherit) !important; -} - -/* 2) .Bookmarks: - - add var-based font family/weight -*/ -.Bookmarks { - font-family: var(--rf-node-font-family, inherit) !important; - font-weight: var(--rf-node-font-weight, inherit) !important; -} - -/* 3) .HeadRow, .classTitle, .classSystemName: - - add new references to var-based font family/weight -*/ -.HeadRow { - font-family: var(--rf-node-font-family, inherit) !important; - font-weight: var(--rf-node-font-weight, inherit) !important; - - .classTitle { - font-family: var(--rf-node-font-family, inherit) !important; - font-weight: var(--rf-node-font-weight, inherit) !important; - } - - @-moz-document url-prefix() { - .classSystemName { - font-family: var(--rf-node-font-family, inherit) !important; - font-weight: var(--rf-node-font-weight, inherit) !important; - } - } - - .classSystemName { - font-family: var(--rf-node-font-family, inherit) !important; - font-weight: var(--rf-node-font-weight, inherit) !important; - } -} - -/* 4) .BottomRow: - - introduces .tagTitle, .regionName, .customName, .localCounter - referencing new CSS variables */ -.BottomRow { - font-family: var(--rf-node-font-family, inherit) !important; - font-weight: var(--rf-node-font-weight, inherit) !important; - - .tagTitle { - font-size: 11px; - font-weight: medium; - text-shadow: 0 0 2px rgba(231, 146, 52, 0.73); - font-family: var(--rf-node-font-family, inherit) !important; - font-weight: var(--rf-node-font-weight, inherit) !important; - color: var(--rf-tag-color, #38BDF8); - } - - .regionName { - color: var(--rf-region-name, #D6D3D1); - font-family: var(--rf-node-font-family, inherit) !important; - font-weight: var(--rf-node-font-weight, inherit) !important; - } - - .customName { - color: var(--rf-custom-name, #93C5FD); - font-family: var(--rf-node-font-family, inherit) !important; - font-weight: var(--rf-node-font-weight, inherit) !important; - } - - .localCounter { - display: flex; - color: var(--rf-has-user-characters, #fbbf24); - font-family: var(--rf-node-font-family, inherit) !important; - font-weight: var(--rf-node-font-weight, inherit) !important; - gap: 2px; - - .hasUserCharacters { - color: var(--rf-has-user-characters, #fbbf24); - font-family: var(--rf-node-font-family, inherit) !important; - font-weight: var(--rf-node-font-weight, inherit) !important; - } - } -} +/* --------------------------------------------- + Only override what's different from the base + Currently none required +---------------------------------------------- */ diff --git a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.tsx b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.tsx index d9283bbd..5e9539ed 100644 --- a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.tsx +++ b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.tsx @@ -1,20 +1,23 @@ import { memo } from 'react'; import { MapSolarSystemType } from '../../map.types'; -import { Handle, Position, NodeProps } from 'reactflow'; +import { Handle, NodeProps, Position } from 'reactflow'; import clsx from 'clsx'; import classes from './SolarSystemNodeTheme.module.scss'; import { PrimeIcons } from 'primereact/api'; -import { useSolarSystemNode } from '../../hooks/useSolarSystemNode'; +import { useLocalCounter, useSolarSystemNode } from '../../hooks/useSolarSystemLogic'; import { + EFFECT_BACKGROUND_STYLES, MARKER_BOOKMARK_BG_STYLES, STATUS_CLASSES, - EFFECT_BACKGROUND_STYLES, } from '@/hooks/Mapper/components/map/constants'; import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp'; import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature'; +import { LocalCounter } from './SolarSystemLocalCounter'; +import { KillsCounter } from './SolarSystemKillsCounter'; export const SolarSystemNodeTheme = memo((props: NodeProps) => { const nodeVars = useSolarSystemNode(props); + const { localCounterCharacters } = useLocalCounter(nodeVars); return ( <> @@ -32,13 +35,19 @@ export const SolarSystemNodeTheme = memo((props: NodeProps)
)} - {nodeVars.killsCount && ( -
+ {nodeVars.killsCount && nodeVars.killsCount > 0 && nodeVars.solarSystemId && ( +
{nodeVars.killsCount}
-
+ )} {nodeVars.labelsInfo.map(x => ( @@ -53,10 +62,8 @@ export const SolarSystemNodeTheme = memo((props: NodeProps) className={clsx( classes.RootCustomNode, nodeVars.regionClass && classes[nodeVars.regionClass], - classes[STATUS_CLASSES[nodeVars.status]], - { - [classes.selected]: nodeVars.selected, - }, + nodeVars.status !== undefined ? classes[STATUS_CLASSES[nodeVars.status]] : '', + { [classes.selected]: nodeVars.selected }, )} > {nodeVars.visible && ( @@ -88,7 +95,7 @@ export const SolarSystemNodeTheme = memo((props: NodeProps) {nodeVars.isWormhole && (
{nodeVars.sortedStatics.map(whClass => ( - + ))}
)} @@ -124,24 +131,15 @@ export const SolarSystemNodeTheme = memo((props: NodeProps) {nodeVars.isWormhole && !nodeVars.customName &&
}
-
- {nodeVars.locked && ( - - )} - - {nodeVars.hubs.includes(nodeVars.solarSystemId.toString()) && ( - - )} - - {nodeVars.charactersInSystem.length > 0 && ( -
- - {nodeVars.charactersInSystem.length} -
+
0, + [classes.countAbove9]: nodeVars.charactersInSystem.length > 9, + })} + > + {nodeVars.locked && } + {nodeVars.hubs.includes(nodeVars.solarSystemId) && ( + )}
@@ -155,7 +153,7 @@ export const SolarSystemNodeTheme = memo((props: NodeProps) {nodeVars.unsplashedLeft.length > 0 && (
{nodeVars.unsplashedLeft.map(sig => ( - + ))}
)} @@ -163,14 +161,14 @@ export const SolarSystemNodeTheme = memo((props: NodeProps) {nodeVars.unsplashedRight.length > 0 && (
{nodeVars.unsplashedRight.map(sig => ( - + ))}
)} )} -
+
nodeVars.dbClick(e)} className={classes.Handlers}> ) id="d" />
+ ); }); diff --git a/assets/js/hooks/Mapper/components/map/hooks/useBackgroundVars.ts b/assets/js/hooks/Mapper/components/map/hooks/useBackgroundVars.ts index 215c7c02..a204c09c 100644 --- a/assets/js/hooks/Mapper/components/map/hooks/useBackgroundVars.ts +++ b/assets/js/hooks/Mapper/components/map/hooks/useBackgroundVars.ts @@ -6,6 +6,7 @@ export function useBackgroundVars(themeName?: string) { const [gap, setGap] = useState(16); const [size, setSize] = useState(1); const [color, setColor] = useState('#81818b'); + const [snapSize, setSnapSize] = useState(25); useEffect(() => { // match any element whose entire `class` attribute ends with "-theme" @@ -29,16 +30,19 @@ export function useBackgroundVars(themeName?: string) { const cssVarGap = style.getPropertyValue('--rf-bg-gap'); const cssVarSize = style.getPropertyValue('--rf-bg-size'); + const cssVarSnapSize = style.getPropertyValue('--rf-snap-size'); const cssColor = style.getPropertyValue('--rf-bg-pattern-color'); const gapNum = parseInt(cssVarGap, 10) || 16; const sizeNum = parseInt(cssVarSize, 10) || 1; + const snapSize = parseInt(cssVarSnapSize, 10) || 25; //react-flow default setVariant(finalVariant); setGap(gapNum); setSize(sizeNum); setColor(cssColor); + setSnapSize(snapSize); }, [themeName]); - return { variant, gap, size, color }; + return { variant, gap, size, color, snapSize }; } diff --git a/assets/js/hooks/Mapper/components/map/hooks/useKillsCounter.ts b/assets/js/hooks/Mapper/components/map/hooks/useKillsCounter.ts new file mode 100644 index 00000000..4dbb8863 --- /dev/null +++ b/assets/js/hooks/Mapper/components/map/hooks/useKillsCounter.ts @@ -0,0 +1,44 @@ +import { useMemo } from 'react'; +import { useSystemKills } from '../../mapInterface/widgets/SystemKills/hooks/useSystemKills'; +import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; + +interface UseKillsCounterProps { + realSystemId: string; +} + +export function useKillsCounter({ realSystemId }: UseKillsCounterProps) { + const { data: mapData, outCommand } = useMapRootState(); + const { systems } = mapData; + + const systemNameMap = useMemo(() => { + const m: Record = {}; + systems.forEach(sys => { + m[sys.id] = sys.temporary_name || sys.name || '???'; + }); + return m; + }, [systems]); + + const { kills: allKills, isLoading } = useSystemKills({ + systemId: realSystemId, + outCommand, + showAllVisible: false, + }); + + const filteredKills = useMemo(() => { + if (!allKills || allKills.length === 0) return []; + + return [...allKills] + .sort((a, b) => { + const aTime = a.kill_time ? new Date(a.kill_time).getTime() : 0; + const bTime = b.kill_time ? new Date(b.kill_time).getTime() : 0; + return bTime - aTime; + }) + .slice(0, 10); + }, [allKills]); + + return { + isLoading, + kills: filteredKills, + systemNameMap, + }; +} diff --git a/assets/js/hooks/Mapper/components/map/hooks/useMapHandlers.ts b/assets/js/hooks/Mapper/components/map/hooks/useMapHandlers.ts index 849596e8..5a0f4148 100644 --- a/assets/js/hooks/Mapper/components/map/hooks/useMapHandlers.ts +++ b/assets/js/hooks/Mapper/components/map/hooks/useMapHandlers.ts @@ -127,6 +127,10 @@ export const useMapHandlers = (ref: ForwardedRef, onSelectionChange // do nothing here break; + case Commands.detailedKillsUpdated: + // do nothing here + break; + default: console.warn(`Map handlers: Unknown command: ${type}`, data); break; diff --git a/assets/js/hooks/Mapper/components/map/hooks/useSolarSystemNode.ts b/assets/js/hooks/Mapper/components/map/hooks/useSolarSystemLogic.tsx similarity index 74% rename from assets/js/hooks/Mapper/components/map/hooks/useSolarSystemNode.ts rename to assets/js/hooks/Mapper/components/map/hooks/useSolarSystemLogic.tsx index 5846184b..34ae67a1 100644 --- a/assets/js/hooks/Mapper/components/map/hooks/useSolarSystemNode.ts +++ b/assets/js/hooks/Mapper/components/map/hooks/useSolarSystemLogic.tsx @@ -10,10 +10,17 @@ import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormhol import { getSystemClassStyles, prepareUnsplashedChunks } from '@/hooks/Mapper/components/map/helpers'; import { sortWHClasses } from '@/hooks/Mapper/helpers'; import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager'; -import { CharacterTypeRaw, OutCommand } from '@/hooks/Mapper/types'; +import { CharacterTypeRaw, OutCommand, SystemSignature } from '@/hooks/Mapper/types'; import { LABELS_INFO, LABELS_ORDER } from '@/hooks/Mapper/components/map/constants'; -function getActivityType(count: number) { +export type LabelInfo = { + id: string; + shortName: string; +}; + +export type UnsplashedSignatureType = SystemSignature & { sig_id: string }; + +function getActivityType(count: number): string { if (count <= 5) return 'activityNormal'; if (count <= 30) return 'activityWarn'; return 'activityDanger'; @@ -26,12 +33,25 @@ const SpaceToClass: Record = { [Spaces.Gallente]: 'Gallente', }; -function sortedLabels(labels: string[]) { +function sortedLabels(labels: string[]): LabelInfo[] { if (!labels) return []; - return LABELS_ORDER.filter(x => labels.includes(x)).map(x => LABELS_INFO[x]); + return LABELS_ORDER.filter(x => labels.includes(x)).map(x => LABELS_INFO[x] as LabelInfo); } -export function useSolarSystemNode(props: NodeProps) { +export function useLocalCounter(nodeVars: SolarSystemNodeVars) { + const localCounterCharacters = useMemo(() => { + return nodeVars.charactersInSystem + .map(char => ({ + ...char, + compact: true, + isOwn: nodeVars.userCharacters.includes(char.eve_id), + })) + .sort((a, b) => a.name.localeCompare(b.name)); + }, [nodeVars.charactersInSystem, nodeVars.userCharacters]); + return { localCounterCharacters }; +} + +export function useSolarSystemNode(props: NodeProps): SolarSystemNodeVars { const { id, data, selected } = props; const { system_static_info, @@ -71,7 +91,6 @@ export function useSolarSystemNode(props: NodeProps) { const { data: { characters, - presentCharacters, wormholesData, hubs, kills, @@ -87,15 +106,14 @@ export function useSolarSystemNode(props: NodeProps) { const visible = useMemo(() => visibleNodes.has(id), [id, visibleNodes]); - const systemSignatures = useMemo( + const systemSigs = useMemo( () => mapSystemSignatures[solar_system_id] || system_signatures, [system_signatures, solar_system_id, mapSystemSignatures], ); const charactersInSystem = useMemo(() => { - return characters.filter(c => c.location?.solar_system_id === solar_system_id).filter(c => c.online); - // eslint-disable-next-line - }, [characters, presentCharacters, solar_system_id]); + return characters.filter(c => c.location?.solar_system_id === solar_system_id && c.online); + }, [characters, solar_system_id]); const isWormhole = isWormholeSpace(system_class); @@ -136,52 +154,65 @@ export function useSolarSystemNode(props: NodeProps) { const space = showKSpaceBG ? REGIONS_MAP[region_id] : ''; const regionClass = showKSpaceBG ? SpaceToClass[space] : null; - const temporaryName = useMemo(() => { + const computedTemporaryName = useMemo(() => { if (!isTempSystemNameEnabled) { return ''; } - if (isShowLinkedSigIdTempName && linkedSigPrefix) { return temporary_name ? `${linkedSigPrefix}・${temporary_name}` : `${linkedSigPrefix}・${solar_system_name}`; } - return temporary_name; }, [isShowLinkedSigIdTempName, isTempSystemNameEnabled, linkedSigPrefix, solar_system_name, temporary_name]); const systemName = useMemo(() => { - if (isTempSystemNameEnabled && temporaryName) { - return temporaryName; + if (isTempSystemNameEnabled && computedTemporaryName) { + return computedTemporaryName; } return solar_system_name; - }, [isTempSystemNameEnabled, solar_system_name, temporaryName]); + }, [isTempSystemNameEnabled, solar_system_name, computedTemporaryName]); - const customName = (isTempSystemNameEnabled && temporaryName && name) || (solar_system_name !== name && name) || null; + const customName = useMemo(() => { + if (isTempSystemNameEnabled && computedTemporaryName && name) { + return name; + } + if (solar_system_name !== name && name) { + return name; + } + return null; + }, [isTempSystemNameEnabled, computedTemporaryName, name, solar_system_name]); const [unsplashedLeft, unsplashedRight] = useMemo(() => { if (!isShowUnsplashedSignatures) { return [[], []]; } return prepareUnsplashedChunks( - systemSignatures + systemSigs .filter(s => s.group === 'Wormhole' && !s.linked_system) .map(s => ({ eve_id: s.eve_id, type: s.type, custom_info: s.custom_info, - })), + kind: s.kind, + name: s.name, + group: s.group, + sig_id: s.eve_id, // Add a unique key property + })) as UnsplashedSignatureType[], ); - }, [isShowUnsplashedSignatures, systemSignatures]); + }, [isShowUnsplashedSignatures, systemSigs]); - const nodeVars = { + // Ensure hubs are always strings. + const hubsAsStrings = useMemo(() => hubs.map(item => item.toString()), [hubs]); + + const nodeVars: SolarSystemNodeVars = { id, selected, - visible, isWormhole, classTitleColor, killsCount, killsActivityType, hasUserCharacters, + userCharacters, showHandlers, regionClass, systemName, @@ -195,10 +226,10 @@ export function useSolarSystemNode(props: NodeProps) { sortedStatics, effectName: effect_name, regionName: region_name, - solarSystemId: solar_system_id, + solarSystemId: solar_system_id.toString(), solarSystemName: solar_system_name, locked, - hubs, + hubs: hubsAsStrings, name: name, isConnecting, hoverNodeId, @@ -207,7 +238,7 @@ export function useSolarSystemNode(props: NodeProps) { unsplashedRight, isThickConnections, classTitle: class_title, - temporaryName: temporary_name, + temporaryName: computedTemporaryName, }; return nodeVars; @@ -230,24 +261,22 @@ export interface SolarSystemNodeVars { isShattered: boolean; tag?: string | null; status?: number; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - labelsInfo: Array; - dbClick: (event?: void) => void; + labelsInfo: LabelInfo[]; + dbClick: (event: React.MouseEvent) => void; sortedStatics: Array; effectName: string | null; regionName: string | null; - solarSystemId: number; + solarSystemId: string; solarSystemName: string | null; locked: boolean; - hubs: string[] | number[]; + hubs: string[]; name: string | null; isConnecting: boolean; hoverNodeId: string | null; charactersInSystem: Array; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - unsplashedLeft: Array; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - unsplashedRight: Array; + userCharacters: string[]; + unsplashedLeft: Array; + unsplashedRight: Array; isThickConnections: boolean; classTitle: string | null; temporaryName?: string | null; diff --git a/assets/js/hooks/Mapper/components/map/styles/default-theme.scss b/assets/js/hooks/Mapper/components/map/styles/default-theme.scss index b934d44c..4bd21821 100644 --- a/assets/js/hooks/Mapper/components/map/styles/default-theme.scss +++ b/assets/js/hooks/Mapper/components/map/styles/default-theme.scss @@ -11,7 +11,8 @@ --rf-tag-color: #38BDF8; --rf-region-name: #D6D3D1; --rf-custom-name: #93C5FD; - + --rf-node-font-family: 'Shentox', 'Rogan', sans-serif !important; + --rf-node-font-weight: 500; --rf-bg-variant: "dots"; --rf-bg-gap: 15; @@ -28,4 +29,8 @@ --tooltip-bg: #202020; --window-corner: #72716f; + + --rf-local-counter-font-weight: 500; + --rf-node-local-counter: #5cb85c; + --rf-has-user-characters: #fbbf24; } diff --git a/assets/js/hooks/Mapper/components/map/styles/pathfinder-theme.scss b/assets/js/hooks/Mapper/components/map/styles/pathfinder-theme.scss index 979b8e13..3f45ef8e 100644 --- a/assets/js/hooks/Mapper/components/map/styles/pathfinder-theme.scss +++ b/assets/js/hooks/Mapper/components/map/styles/pathfinder-theme.scss @@ -3,23 +3,26 @@ @import url('https://fonts.googleapis.com/css2?family=Oxygen:wght@300;400;700&display=swap'); .pathfinder-theme { + /* -- Override values from the default theme -- */ --rf-bg-color: #000000; --rf-soft-bg-color: #282828; - - --rf-node-bg-color: #202020; --rf-node-soft-bg-color: #313335; --rf-node-font-weight: bold; --rf-text-color: #adadad; --rf-region-name: var(--rf-text-color); --rf-custom-name: var(--rf-text-color); - - --tooltip-bg: #202020; - --rf-bg-variant: "lines"; - --rf-bg-gap: 32; - --rf-bg-size: 1; + --rf-bg-gap: 34; + --rf-snap-size: 17; --rf-bg-pattern-color: #313131; + --rf-local-counter-font-weight: 700; + /* Additional node-specific overrides */ + --rf-node-line-height: normal; + --rf-node-font-family: 'Oxygen', sans-serif; + --rf-tag-color: #fbbf24; + + /* -- theme-specific variables -- */ --eve-effect-pulsar: #428bca; --eve-effect-magnetar: #e06fdf; --eve-effect-wolfRayet: #e28a0d; @@ -38,14 +41,4 @@ --eve-wh-type-color-c6: #d9534f; --eve-wh-type-color-c13: #7986cb; --eve-wh-type-color-drifter: #44aa82; - - --rf-node-font-weight: bold; - --rf-node-line-height: normal; - --rf-node-font-family: 'Oxygen', sans-serif; - --rf-node-text-color: var(--pf-text-color); - - --rf-tag-color: #fbbf24; - --rf-has-user-characters: #5cb85c; - - --window-corner: #72716f; } diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/LocalCharacters.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/LocalCharacters.tsx index 39f66583..c6373635 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/LocalCharacters.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/LocalCharacters.tsx @@ -1,71 +1,25 @@ -import { useCallback, useMemo, useRef } from 'react'; +import { useMemo, useRef } from 'react'; import { Widget } from '@/hooks/Mapper/components/mapInterface/components'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; -import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller'; import clsx from 'clsx'; -import classes from './LocalCharacters.module.scss'; -import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types'; -import { CharacterCard, LayoutEventBlocker, WdCheckbox } from '@/hooks/Mapper/components/ui-kit'; +import { LayoutEventBlocker, WdCheckbox } from '@/hooks/Mapper/components/ui-kit'; import { sortCharacters } from '@/hooks/Mapper/components/mapInterface/helpers/sortCharacters.ts'; -import useLocalStorageState from 'use-local-storage-state'; import { useMapCheckPermissions, useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api'; import { UserPermission } from '@/hooks/Mapper/types/permissions.ts'; import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts'; import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper'; - -type CharItemProps = { - compact: boolean; -} & CharacterTypeRaw & - WithIsOwnCharacter; - -const useItemTemplate = () => { - const { - data: { presentCharacters }, - } = useMapRootState(); - - return useCallback( - (char: CharItemProps, options: VirtualScrollerTemplateOptions) => { - return ( -
- -
- ); - }, - // eslint-disable-next-line - [presentCharacters], - ); -}; - -type WindowLocalSettingsType = { - compact: boolean; - showOffline: boolean; - version: number; -}; - -const STORED_DEFAULT_VALUES: WindowLocalSettingsType = { - compact: true, - showOffline: false, - version: 0, -}; +import { LocalCharactersList } from './components/LocalCharactersList'; +import { useLocalCharactersItemTemplate } from './hooks/useLocalCharacters'; +import { useLocalCharacterWidgetSettings } from './hooks/useLocalWidgetSettings'; export const LocalCharacters = () => { const { - data: { characters, userCharacters, selectedSystems, presentCharacters }, + data: { characters, userCharacters, selectedSystems }, } = useMapRootState(); - const [settings, setSettings] = useLocalStorageState('window:local:settings', { - defaultValue: STORED_DEFAULT_VALUES, - }); + const [settings, setSettings] = useLocalCharacterWidgetSettings(); const [systemId] = selectedSystems; - const restrictOfflineShowing = useMapGetOption('restrict_offline_showing'); const isAdminOrManager = useMapCheckPermissions([UserPermission.MANAGE_MAP]); @@ -74,21 +28,31 @@ export const LocalCharacters = () => { [isAdminOrManager, restrictOfflineShowing], ); - const itemTemplate = useItemTemplate(); - const sorted = useMemo(() => { - const sorted = characters + const filtered = characters .filter(x => x.location?.solar_system_id?.toString() === systemId) - .map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id), compact: settings.compact })) + .map(x => ({ + ...x, + isOwn: userCharacters.includes(x.eve_id), + compact: settings.compact, + showShipName: settings.showShipName, + })) .sort(sortCharacters); if (!showOffline || !settings.showOffline) { - return sorted.filter(c => c.online); + return filtered.filter(c => c.online); } - return sorted; - // eslint-disable-next-line - }, [showOffline, characters, settings.showOffline, settings.compact, systemId, userCharacters, presentCharacters]); + return filtered; + }, [ + characters, + systemId, + userCharacters, + settings.compact, + settings.showOffline, + settings.showShipName, + showOffline, + ]); const isNobodyHere = sorted.length === 0; const isNotSelectedSystem = selectedSystems.length !== 1; @@ -97,6 +61,8 @@ export const LocalCharacters = () => { const ref = useRef(null); const compact = useMaxWidth(ref, 145); + const itemTemplate = useLocalCharactersItemTemplate(settings.showShipName); + return ( { label={compact ? '' : 'Show offline'} value={settings.showOffline} classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300" - onChange={() => setSettings(() => ({ ...settings, showOffline: !settings.showOffline }))} + onChange={() => setSettings(prev => ({ ...prev, showOffline: !prev.showOffline }))} + /> + + )} + + {settings.compact && ( + + setSettings(prev => ({ ...prev, showShipName: !prev.showShipName }))} /> )} @@ -121,8 +100,8 @@ export const LocalCharacters = () => { ['hero-bars-2']: settings.compact, ['hero-bars-3']: !settings.compact, })} - onClick={() => setSettings(() => ({ ...settings, compact: !settings.compact }))} - > + onClick={() => setSettings(prev => ({ ...prev, compact: !prev.compact }))} + />
} @@ -140,15 +119,11 @@ export const LocalCharacters = () => { )} {showList && ( - )} diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/LocalCharacterList.module.scss b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/LocalCharacterList.module.scss new file mode 100644 index 00000000..9ff5b73f --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/LocalCharacterList.module.scss @@ -0,0 +1,4 @@ +// .VirtualScroller { +// height: 100% !important; +// } + \ No newline at end of file diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/LocalCharactersList.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/LocalCharactersList.tsx new file mode 100644 index 00000000..20046825 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/LocalCharactersList.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller'; +import clsx from 'clsx'; +import { CharItemProps } from './types'; + +type LocalCharactersListProps = { + items: Array; + + itemSize: number; + + itemTemplate: (char: CharItemProps, options: VirtualScrollerTemplateOptions) => React.ReactNode; + + containerClassName?: string; +}; + +export function LocalCharactersList({ items, itemSize, itemTemplate, containerClassName }: LocalCharactersListProps) { + return ( + + ); +} diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/index.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/index.ts new file mode 100644 index 00000000..eb5527ff --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/index.ts @@ -0,0 +1,2 @@ +export * from './LocalCharactersList'; +export * from './types'; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/types.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/types.ts new file mode 100644 index 00000000..e567723b --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/types.ts @@ -0,0 +1,6 @@ +import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types'; + +export type CharItemProps = { + compact: boolean; +} & CharacterTypeRaw & + WithIsOwnCharacter; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/LocalCharacters.module.scss b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/hooks/useLocalCharacters.module.scss similarity index 100% rename from assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/LocalCharacters.module.scss rename to assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/hooks/useLocalCharacters.module.scss diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/hooks/useLocalCharacters.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/hooks/useLocalCharacters.tsx new file mode 100644 index 00000000..569bc4cd --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/hooks/useLocalCharacters.tsx @@ -0,0 +1,33 @@ +import { useCallback } from 'react'; +import { VirtualScrollerTemplateOptions } from 'primereact/virtualscroller'; +import clsx from 'clsx'; +import classes from './useLocalCharacters.module.scss'; +import { CharacterCard } from '@/hooks/Mapper/components/ui-kit'; +import { CharItemProps } from '../components'; + +export function useLocalCharactersItemTemplate(showShipName: boolean) { + return useCallback( + (char: CharItemProps, options: VirtualScrollerTemplateOptions) => { + return ( +
+ +
+ ); + }, + [showShipName], + ); +} diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/hooks/useLocalWidgetSettings.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/hooks/useLocalWidgetSettings.ts new file mode 100644 index 00000000..6b78a950 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/hooks/useLocalWidgetSettings.ts @@ -0,0 +1,21 @@ +import useLocalStorageState from 'use-local-storage-state'; + +export interface LocalCharacterWidgetSettings { + compact: boolean; + showOffline: boolean; + version: number; + showShipName: boolean; +} + +export const LOCAL_CHARACTER_WIDGET_DEFAULT: LocalCharacterWidgetSettings = { + compact: true, + showOffline: false, + version: 0, + showShipName: false, +}; + +export function useLocalCharacterWidgetSettings() { + return useLocalStorageState('kills:widget:settings', { + defaultValue: LOCAL_CHARACTER_WIDGET_DEFAULT, + }); +} diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/hooks/useKillsWidgetSettings.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/hooks/useKillsWidgetSettings.ts index dfaaec4e..3b338bcc 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/hooks/useKillsWidgetSettings.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/hooks/useKillsWidgetSettings.ts @@ -9,7 +9,7 @@ export interface KillsWidgetSettings { } export const DEFAULT_KILLS_WIDGET_SETTINGS: KillsWidgetSettings = { - compact: false, + compact: true, showAll: false, excludedSystems: [], version: 0, diff --git a/assets/js/hooks/Mapper/components/ui-kit/CharacterCard/CharacterCard.module.scss b/assets/js/hooks/Mapper/components/ui-kit/CharacterCard/CharacterCard.module.scss index 4d19233b..8506eb35 100644 --- a/assets/js/hooks/Mapper/components/ui-kit/CharacterCard/CharacterCard.module.scss +++ b/assets/js/hooks/Mapper/components/ui-kit/CharacterCard/CharacterCard.module.scss @@ -28,6 +28,7 @@ .CharIcon { border-radius: 0 !important; + border: 1px solid #2b2b2b; } .CharRow { diff --git a/assets/js/hooks/Mapper/components/ui-kit/CharacterCard/CharacterCard.tsx b/assets/js/hooks/Mapper/components/ui-kit/CharacterCard/CharacterCard.tsx index d2937720..046ed270 100644 --- a/assets/js/hooks/Mapper/components/ui-kit/CharacterCard/CharacterCard.tsx +++ b/assets/js/hooks/Mapper/components/ui-kit/CharacterCard/CharacterCard.tsx @@ -8,8 +8,8 @@ import { emitMapEvent } from '@/hooks/Mapper/events'; type CharacterCardProps = { compact?: boolean; - showShipName?: boolean; showSystem?: boolean; + showShipName?: boolean; useSystemsCache?: boolean; } & CharacterTypeRaw & WithIsOwnCharacter; @@ -22,8 +22,15 @@ export const getShipName = (name: string) => { .replace(/\\x([\dA-Fa-f]{2})/g, (_, grp) => String.fromCharCode(parseInt(grp, 16))); }; +// A small divider between fields: +const Divider = () => ( + +); + export const CharacterCard = ({ - compact, + compact = false, isOwn, showSystem, showShipName, @@ -37,59 +44,135 @@ export const CharacterCard = ({ }); }, [char]); + // Precompute the ship name (decoded): + const shipNameText = char.ship?.ship_name ? getShipName(char.ship.ship_name) : ''; + + // ----------------------------------------------------------------------------- + // COMPACT MODE: Main line = + // if (showShipName & haveShipName) => name | shipName (skip ticker) + // else => name | [ticker] + // ----------------------------------------------------------------------------- + const compactLine = ( + <> + {/* Character Name (lighter shade) */} + {char.name} + + {showShipName && shipNameText ? ( + // Show the ship name in place of the ticker (use indigo color to match corp/alliance) + {shipNameText} + ) : ( + // Show the [ticker] (indigo) + [{char.alliance_id ? char.alliance_ticker : char.corporation_ticker}] + )} + + ); + + // ----------------------------------------------------------------------------- + // NON-COMPACT MODE: + // Line 1 => name | [ticker] + // Line 2 => (shipName) always, if it exists + // ----------------------------------------------------------------------------- + const nonCompactLine1 = ( +
+ {/* Character Name (lighter shade) */} + {char.name} + + [{char.alliance_id ? char.alliance_ticker : char.corporation_ticker}] +
+ ); + + const nonCompactLine2 = ( + <> + {shipNameText && ( +
{shipNameText}
+ )} + + ); + return ( -
-
- {!compact && ( +
+
+ {compact ? ( + {`${char.name} + ) : ( )} -
+ + {/* + Middle section: + - In compact mode, everything is on one line (Name + possibly ShipName or ticker). + - In non-compact mode, line 1 has (Name | Ticker), line 2 has shipName if it exists. + */} +
+ {/* This left border highlights "isOwn" in the same way as older code. */}
- - {char.name} - - - {char.alliance_id && [{char.alliance_ticker}]} - {!char.alliance_id && [{char.corporation_ticker}]} - - {char.ship?.ship_type_info && ( -
- {char.ship.ship_type_info.name} -
- )} + {compact ? compactLine : nonCompactLine1}
- - {showShipName && !compact && char.ship?.ship_name && ( -
- - {getShipName(char.ship.ship_name)} - -
- )} - - {showSystem && !compact && char.location?.solar_system_id && ( - - )} + {/* Non-compact second line always shows shipName if available */} + {!compact && nonCompactLine2}
+ + {/* + Right column for Ship Type (compact) or "pushed" to the right (non-compact). + Ship Type remains text-yellow-400. + */} + {char.ship?.ship_type_info?.name && ( +
+ {char.ship.ship_type_info.name} +
+ )}
+ + {/* + System row at the bottom if `showSystem && system exists`. + */} + {showSystem && char.location?.solar_system_id && ( +
+ +
+ )}
); }; diff --git a/assets/js/hooks/Mapper/components/ui-kit/WdTooltip/WdTooltip.tsx b/assets/js/hooks/Mapper/components/ui-kit/WdTooltip/WdTooltip.tsx index 4acbf43a..77fdf4f6 100644 --- a/assets/js/hooks/Mapper/components/ui-kit/WdTooltip/WdTooltip.tsx +++ b/assets/js/hooks/Mapper/components/ui-kit/WdTooltip/WdTooltip.tsx @@ -12,7 +12,7 @@ export enum TooltipPosition { bottom = 'bottom', } -export interface TooltipProps { +export interface TooltipProps extends Omit, 'content'> { position?: TooltipPosition; offset?: number; content: (() => React.ReactNode) | React.ReactNode; @@ -27,183 +27,253 @@ export interface OffsetPosition { export interface WdTooltipHandlers { show: (e?: React.MouseEvent) => void; - hide: (e?: React.MouseEvent) => void; + hide: () => void; getIsMouseInside: () => boolean; } -export const WdTooltip = forwardRef( - (props: TooltipProps & { className?: string }, ref: ForwardedRef) => { - const { - content, - targetSelector, - position: tPosition = TooltipPosition.default, - className, - offset = 5, - interactive = false, - } = props; +const LEAVE_DELAY = 100; - const [visible, setVisible] = useState(false); - const [pos, setPos] = useState(null); - const [ev, setEv] = useState(); - const tooltipRef = useRef(null); - const [isMouseInsideTooltip, setIsMouseInsideTooltip] = useState(false); +export const WdTooltip = forwardRef(function WdTooltip( + { + content, + targetSelector, + position: tPosition = TooltipPosition.default, + offset = 5, + interactive = false, + className, + ...restProps + }: TooltipProps, + ref: ForwardedRef, +) { + const [visible, setVisible] = useState(false); + const [pos, setPos] = useState(null); + const tooltipRef = useRef(null); - const calcTooltipPosition = useCallback(({ x, y }: { x: number; y: number }) => { - if (!tooltipRef.current) return { left: x, top: y }; - const tooltipWidth = tooltipRef.current.offsetWidth; - const tooltipHeight = tooltipRef.current.offsetHeight; - let newLeft = x; - let newTop = y; + const [isMouseInsideTooltip, setIsMouseInsideTooltip] = useState(false); - if (newLeft < 0) newLeft = 10; - if (newTop < 0) newTop = 10; - if (newLeft + tooltipWidth + 10 > window.innerWidth) { - newLeft = window.innerWidth - tooltipWidth - 10; - } - if (newTop + tooltipHeight + 10 > window.innerHeight) { - newTop = window.innerHeight - tooltipHeight - 10; - } - return { left: newLeft, top: newTop }; - }, []); + const [reactEvt, setReactEvt] = useState(); - useImperativeHandle(ref, () => ({ - show: (mouseEvt?: React.MouseEvent) => { - if (mouseEvt) setEv(mouseEvt); - setPos(null); - setVisible(true); - }, - hide: () => { + const hideTimeoutRef = useRef | null>(null); + + const calcTooltipPosition = useCallback(({ x, y }: { x: number; y: number }) => { + if (!tooltipRef.current) return { left: x, top: y }; + + const tooltipWidth = tooltipRef.current.offsetWidth; + const tooltipHeight = tooltipRef.current.offsetHeight; + + let newLeft = x; + let newTop = y; + + if (newLeft < 0) newLeft = 10; + if (newTop < 0) newTop = 10; + + const rightEdge = newLeft + tooltipWidth + 10; + if (rightEdge > window.innerWidth) { + newLeft = window.innerWidth - tooltipWidth - 10; + } + + const bottomEdge = newTop + tooltipHeight + 10; + if (bottomEdge > window.innerHeight) { + newTop = window.innerHeight - tooltipHeight - 10; + } + + return { left: newLeft, top: newTop }; + }, []); + + const scheduleHide = useCallback(() => { + if (!interactive) { + setVisible(false); + return; + } + if (!hideTimeoutRef.current) { + hideTimeoutRef.current = setTimeout(() => { setVisible(false); - }, - getIsMouseInside: () => isMouseInsideTooltip, - })); + }, LEAVE_DELAY); + } + }, [interactive]); - useEffect(() => { - if (!tooltipRef.current || !ev) return; - const tooltipEl = tooltipRef.current; - const { clientX, clientY, target } = ev; - const targetBounds = (target as HTMLElement).getBoundingClientRect(); + useImperativeHandle(ref, () => ({ + show: (e?: React.MouseEvent) => { + if (hideTimeoutRef.current) { + clearTimeout(hideTimeoutRef.current); + hideTimeoutRef.current = null; + } + if (e && tooltipRef.current) { + const { clientX, clientY } = e; + setPos(calcTooltipPosition({ x: clientX, y: clientY })); + setReactEvt(e); + } + setVisible(true); + }, + hide: () => { + if (hideTimeoutRef.current) { + clearTimeout(hideTimeoutRef.current); + } + setVisible(false); + }, + getIsMouseInside: () => isMouseInsideTooltip, + })); - let offsetX = clientX; - let offsetY = clientY; + useEffect(() => { + if (!tooltipRef.current || !reactEvt) return; - if (tPosition === TooltipPosition.left) { - const tooltipBounds = tooltipEl.getBoundingClientRect(); - offsetX = targetBounds.left - tooltipBounds.width - offset; - offsetY = targetBounds.y + targetBounds.height / 2 - tooltipBounds.height / 2; - if (offsetX <= 0) { - offsetX = targetBounds.left + targetBounds.width + offset; - } - setPos(calcTooltipPosition({ x: offsetX, y: offsetY })); + const { clientX, clientY, target } = reactEvt; + const tooltipEl = tooltipRef.current; + const triggerEl = target as HTMLElement; + const triggerBounds = triggerEl.getBoundingClientRect(); + + let x = clientX; + let y = clientY; + + if (tPosition === TooltipPosition.left) { + const tooltipBounds = tooltipEl.getBoundingClientRect(); + x = triggerBounds.left - tooltipBounds.width - offset; + y = triggerBounds.y + triggerBounds.height / 2 - tooltipBounds.height / 2; + if (x <= 0) { + x = triggerBounds.left + triggerBounds.width + offset; + } + setPos(calcTooltipPosition({ x, y })); + return; + } + if (tPosition === TooltipPosition.right) { + x = triggerBounds.left + triggerBounds.width + offset; + y = triggerBounds.y + triggerBounds.height / 2 - tooltipEl.offsetHeight / 2; + setPos(calcTooltipPosition({ x, y })); + return; + } + if (tPosition === TooltipPosition.top) { + x = triggerBounds.x + triggerBounds.width / 2 - tooltipEl.offsetWidth / 2; + y = triggerBounds.top - tooltipEl.offsetHeight - offset; + setPos(calcTooltipPosition({ x, y })); + return; + } + if (tPosition === TooltipPosition.bottom) { + x = triggerBounds.x + triggerBounds.width / 2 - tooltipEl.offsetWidth / 2; + y = triggerBounds.bottom + offset; + setPos(calcTooltipPosition({ x, y })); + return; + } + + setPos(calcTooltipPosition({ x, y })); + }, [calcTooltipPosition, reactEvt, tPosition, offset]); + + useEffect(() => { + if (!targetSelector) return; + + const handleMouseMove = (evt: MouseEvent) => { + const targetEl = evt.target as HTMLElement | null; + if (!targetEl) { + scheduleHide(); + return; + } + const triggerEl = targetEl.closest(targetSelector); + const insideTooltip = interactive && tooltipRef.current?.contains(targetEl); + + if (!triggerEl && !insideTooltip) { + scheduleHide(); return; } - if (tPosition === TooltipPosition.right) { - offsetX = targetBounds.left + targetBounds.width + offset; - offsetY = targetBounds.y + targetBounds.height / 2 - tooltipEl.offsetHeight / 2; - setPos(calcTooltipPosition({ x: offsetX, y: offsetY })); - return; + if (hideTimeoutRef.current) { + clearTimeout(hideTimeoutRef.current); + hideTimeoutRef.current = null; } + setVisible(true); - if (tPosition === TooltipPosition.top) { - offsetY = targetBounds.top - tooltipEl.offsetHeight - offset; - offsetX = targetBounds.x + targetBounds.width / 2 - tooltipEl.offsetWidth / 2; - setPos(calcTooltipPosition({ x: offsetX, y: offsetY })); - return; - } + if (triggerEl && tooltipRef.current) { + const rect = triggerEl.getBoundingClientRect(); + const tooltipEl = tooltipRef.current; - if (tPosition === TooltipPosition.bottom) { - offsetY = targetBounds.bottom + offset; - offsetX = targetBounds.x + targetBounds.width / 2 - tooltipEl.offsetWidth / 2; - setPos(calcTooltipPosition({ x: offsetX, y: offsetY })); - return; - } + let x = evt.clientX; + let y = evt.clientY; - setPos(calcTooltipPosition({ x: offsetX, y: offsetY })); - }, [calcTooltipPosition, ev, tPosition, offset]); - - useEffect(() => { - if (!targetSelector) return; - - function handleMouseMove(nativeEvt: globalThis.MouseEvent) { - const targetEl = nativeEvt.target as HTMLElement | null; - if (!targetEl) { - setVisible(false); - return; - } - const triggerEl = targetEl.closest(targetSelector!); - const isInsideTooltip = interactive && tooltipRef.current?.contains(targetEl); - - if (!triggerEl && !isInsideTooltip) { - setVisible(false); - return; - } - setVisible(true); - - if (triggerEl && tooltipRef.current) { - const rect = triggerEl.getBoundingClientRect(); - const tooltipEl = tooltipRef.current; - let x = nativeEvt.clientX; - let y = nativeEvt.clientY; - - if (tPosition === TooltipPosition.left) { + switch (tPosition) { + case TooltipPosition.left: { x = rect.left - tooltipEl.offsetWidth - offset; y = rect.y + rect.height / 2 - tooltipEl.offsetHeight / 2; if (x <= 0) { x = rect.left + rect.width + offset; } - } else if (tPosition === TooltipPosition.right) { + break; + } + case TooltipPosition.right: { x = rect.left + rect.width + offset; y = rect.y + rect.height / 2 - tooltipEl.offsetHeight / 2; - } else if (tPosition === TooltipPosition.top) { + break; + } + case TooltipPosition.top: { x = rect.x + rect.width / 2 - tooltipEl.offsetWidth / 2; y = rect.top - tooltipEl.offsetHeight - offset; - } else if (tPosition === TooltipPosition.bottom) { + break; + } + case TooltipPosition.bottom: { x = rect.x + rect.width / 2 - tooltipEl.offsetWidth / 2; y = rect.bottom + offset; + break; } - - setPos(calcTooltipPosition({ x, y })); + default: } + + setPos(calcTooltipPosition({ x, y })); } + }; - const debounced = debounce(handleMouseMove, 10); + const debounced = debounce(handleMouseMove, 15); + const listener = (evt: Event) => { + debounced(evt as MouseEvent); + }; - const listener: EventListener = evt => { - debounced(evt as globalThis.MouseEvent); - }; + document.addEventListener('mousemove', listener); + return () => { + document.removeEventListener('mousemove', listener); + debounced.cancel(); + }; + }, [targetSelector, interactive, tPosition, offset, calcTooltipPosition, scheduleHide]); - document.addEventListener('mousemove', listener); - return () => { - document.removeEventListener('mousemove', listener); - }; - }, [targetSelector, interactive, tPosition, offset, calcTooltipPosition]); + useEffect(() => { + return () => { + if (hideTimeoutRef.current) { + clearTimeout(hideTimeoutRef.current); + } + }; + }, []); - return createPortal( - visible && ( -
interactive && setIsMouseInsideTooltip(true)} - onMouseLeave={() => interactive && setIsMouseInsideTooltip(false)} - > - {typeof content === 'function' ? content() : content} -
- ), - document.body, - ); - }, -); + if (!visible) return null; + + return createPortal( +
{ + if (interactive && hideTimeoutRef.current) { + clearTimeout(hideTimeoutRef.current); + hideTimeoutRef.current = null; + } + setIsMouseInsideTooltip(true); + }} + onMouseLeave={() => { + setIsMouseInsideTooltip(false); + if (interactive) { + scheduleHide(); + } + }} + {...restProps} + > + {typeof content === 'function' ? content() : content} +
, + document.body, + ); +}); WdTooltip.displayName = 'WdTooltip'; diff --git a/assets/js/hooks/Mapper/components/ui-kit/WdTooltipWrapper/WdTooltipWrapper.module.scss b/assets/js/hooks/Mapper/components/ui-kit/WdTooltipWrapper/WdTooltipWrapper.module.scss index e1c83759..aa722e67 100644 --- a/assets/js/hooks/Mapper/components/ui-kit/WdTooltipWrapper/WdTooltipWrapper.module.scss +++ b/assets/js/hooks/Mapper/components/ui-kit/WdTooltipWrapper/WdTooltipWrapper.module.scss @@ -1,3 +1,25 @@ +/* WdTooltipWrapper.module.scss */ + .WdTooltipWrapperRoot { display: inline-block; } + +.wdTooltipSizeXs { + font-size: 0.7rem; + max-width: 150px; +} + +.wdTooltipSizeSm { + font-size: 0.8rem; + max-width: 200px; +} + +.wdTooltipSizeMd { + font-size: 0.9rem; + max-width: 250px; +} + +.wdTooltipSizeLg { + font-size: 1rem !important; + min-width: 350px; +} \ No newline at end of file diff --git a/assets/package.json b/assets/package.json index 3e6c39e5..918d757d 100644 --- a/assets/package.json +++ b/assets/package.json @@ -82,5 +82,6 @@ }, "keywords": [], "author": "", - "license": "ISC" + "license": "ISC", + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/assets/yarn.lock b/assets/yarn.lock index 72bdb687..4a2f78de 100644 --- a/assets/yarn.lock +++ b/assets/yarn.lock @@ -1346,9 +1346,9 @@ camelcase-css@^2.0.1: integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599: - version "1.0.30001600" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz" - integrity sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ== + version "1.0.30001696" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz" + integrity sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ== chalk@^2.4.2: version "2.4.2" diff --git a/lib/wanderer_app/character/transactions_tracker.ex b/lib/wanderer_app/character/transactions_tracker.ex index b5389383..0df0ae8c 100644 --- a/lib/wanderer_app/character/transactions_tracker.ex +++ b/lib/wanderer_app/character/transactions_tracker.ex @@ -71,7 +71,7 @@ defmodule WandererApp.Character.TransactionsTracker do @impl true def handle_info(:shutdown, %Impl{} = state) do - Logger.debug("Shutting down character transaction tracker: #{inspect(state.character_id)}") + Logger.debug(fn -> "Shutting down character transaction tracker: #{inspect(state.character_id)}" end) {:stop, :normal, state} end diff --git a/lib/wanderer_app/map/map_zkb_data_fetcher.ex b/lib/wanderer_app/map/map_zkb_data_fetcher.ex index d8db1216..26744855 100644 --- a/lib/wanderer_app/map/map_zkb_data_fetcher.ex +++ b/lib/wanderer_app/map/map_zkb_data_fetcher.ex @@ -118,7 +118,7 @@ defmodule WandererApp.Map.ZkbDataFetcher do |> Enum.map(&elem(&1, 0)) if changed_systems == [] do - Logger.debug("[ZkbDataFetcher] No changes in detailed kills for map_id=#{map_id}") + Logger.debug(fn -> "[ZkbDataFetcher] No changes in detailed kills for map_id=#{map_id}" end) :ok else # Build new details for each changed system @@ -200,7 +200,7 @@ defmodule WandererApp.Map.ZkbDataFetcher do if WandererApp.Cache.lookup!("map_#{map_id}:started", false) do fun.() else - Logger.debug("[ZkbDataFetcher] Map #{map_id} not started => skipping #{label}") + Logger.debug(fn -> "[ZkbDataFetcher] Map #{map_id} not started => skipping #{label}" end) :ok end end diff --git a/lib/wanderer_app/map/server/map_server_systems_impl.ex b/lib/wanderer_app/map/server/map_server_systems_impl.ex index e1cc2a1c..387ba85c 100644 --- a/lib/wanderer_app/map/server/map_server_systems_impl.ex +++ b/lib/wanderer_app/map/server/map_server_systems_impl.ex @@ -393,7 +393,7 @@ defmodule WandererApp.Map.Server.SystemsImpl do end error -> - Logger.debug("Skip adding system: #{inspect(error, pretty: true)}") + Logger.debug(fn -> "Skip adding system: #{inspect(error, pretty: true)}" end) :ok end end diff --git a/lib/wanderer_app/structures.ex b/lib/wanderer_app/structures.ex index ef72f878..b839d10c 100644 --- a/lib/wanderer_app/structures.ex +++ b/lib/wanderer_app/structures.ex @@ -61,17 +61,15 @@ defmodule WandererApp.Structure do end defp parse_end_time(str) when is_binary(str) do - # Log everything we can about the incoming string - Logger.debug("[parse_end_time] raw input => #{inspect(str)} (length=#{String.length(str)})") + Logger.debug(fn -> "[parse_end_time] raw input => #{inspect(str)} (length=#{String.length(str)})" end) + if String.trim(str) == "" do - Logger.debug("[parse_end_time] It's empty (or whitespace only). Returning nil.") nil else # Attempt to parse case DateTime.from_iso8601(str) do {:ok, dt, _offset} -> - Logger.debug("[parse_end_time] Successfully parsed => #{inspect(dt)}") dt {:error, reason} -> diff --git a/lib/wanderer_app/zkb/zkb_kills_preloader.ex b/lib/wanderer_app/zkb/zkb_kills_preloader.ex index 69e50ad4..435a9385 100644 --- a/lib/wanderer_app/zkb/zkb_kills_preloader.ex +++ b/lib/wanderer_app/zkb/zkb_kills_preloader.ex @@ -71,12 +71,10 @@ defmodule WandererApp.Zkb.KillsPreloader do system_tuples = gather_visible_systems(active_maps_with_subscription) unique_systems = Enum.uniq(system_tuples) - Logger.debug(fn -> - """ - [KillsPreloader] Found #{length(unique_systems)} unique systems \ - across #{length(active_maps_with_subscription)} map(s) - """ - end) + Logger.debug(fn -> " + [KillsPreloader] Found #{length(unique_systems)} unique systems \ + across #{length(last_active_maps)} map(s) + " end) # ---- QUICK PASS ---- state_quick = %{state | phase: :quick_pass} @@ -185,9 +183,7 @@ defmodule WandererApp.Zkb.KillsPreloader do end defp fetch_kills_for_system(system_id, :quick, hours, limit, state) do - Logger.debug(fn -> - "[KillsPreloader] Quick fetch => system=#{system_id}, hours=#{hours}, limit=#{limit}" - end) + Logger.debug(fn -> "[KillsPreloader] Quick fetch => system=#{system_id}, hours=#{hours}, limit=#{limit}" end) case KillsProvider.Fetcher.fetch_kills_for_system(system_id, hours, state, limit: limit, @@ -206,9 +202,7 @@ defmodule WandererApp.Zkb.KillsPreloader do end defp fetch_kills_for_system(system_id, :expanded, hours, limit, state) do - Logger.debug(fn -> - "[KillsPreloader] Expanded fetch => system=#{system_id}, hours=#{hours}, limit=#{limit} (forcing refresh)" - end) + Logger.debug(fn -> "[KillsPreloader] Expanded fetch => system=#{system_id}, hours=#{hours}, limit=#{limit} (forcing refresh)" end) with {:ok, kills_1h, updated_state} <- KillsProvider.Fetcher.fetch_kills_for_system(system_id, hours, state, @@ -232,10 +226,7 @@ defmodule WandererApp.Zkb.KillsPreloader do defp maybe_fetch_more_if_needed(system_id, kills_1h, limit, state) do if length(kills_1h) < limit do needed = limit - length(kills_1h) - - Logger.debug(fn -> - "[KillsPreloader] Expanding to #{@expanded_hours}h => system=#{system_id}, need=#{needed} more kills" - end) + Logger.debug(fn -> "[KillsPreloader] Expanding to #{@expanded_hours}h => system=#{system_id}, need=#{needed} more kills" end) case KillsProvider.Fetcher.fetch_kills_for_system(system_id, @expanded_hours, state, limit: needed, diff --git a/lib/wanderer_app/zkb/zkills_provider/cache.ex b/lib/wanderer_app/zkb/zkills_provider/cache.ex index d63c03e5..409b7321 100644 --- a/lib/wanderer_app/zkb/zkills_provider/cache.ex +++ b/lib/wanderer_app/zkb/zkills_provider/cache.ex @@ -20,7 +20,7 @@ defmodule WandererApp.Zkb.KillsProvider.KillsCache do Store the killmail data, keyed by killmail_id, with a 24h TTL. """ def put_killmail(killmail_id, kill_data) do - Logger.debug("[KillsCache] Storing killmail => killmail_id=#{killmail_id}") + Logger.debug(fn -> "[KillsCache] Storing killmail => killmail_id=#{killmail_id}" end) Cache.put(killmail_key(killmail_id), kill_data, ttl: @killmail_ttl) end @@ -30,8 +30,7 @@ defmodule WandererApp.Zkb.KillsProvider.KillsCache do """ def fetch_cached_kills(system_id) do killmail_ids = get_system_killmail_ids(system_id) - # Debug-level log for performance checks - Logger.debug("[KillsCache] fetch_cached_kills => system_id=#{system_id}, count=#{length(killmail_ids)}") + Logger.debug(fn -> "[KillsCache] fetch_cached_kills => system_id=#{system_id}, count=#{length(killmail_ids)}" end) killmail_ids |> Enum.map(&get_killmail/1) @@ -130,7 +129,7 @@ defmodule WandererApp.Zkb.KillsProvider.KillsCache do final_expiry_ms = max(@base_full_fetch_expiry_ms + offset, 60_000) expires_at_ms = now_ms + final_expiry_ms - Logger.debug("[KillsCache] Marking system=#{system_id} recently_fetched? until #{expires_at_ms} (ms)") + Logger.debug(fn -> "[KillsCache] Marking system=#{system_id} recently_fetched? until #{expires_at_ms} (ms)" end) Cache.put(fetched_timestamp_key(system_id), expires_at_ms) end diff --git a/lib/wanderer_app/zkb/zkills_provider/fetcher.ex b/lib/wanderer_app/zkb/zkills_provider/fetcher.ex index 644017fe..2eb9c2dd 100644 --- a/lib/wanderer_app/zkb/zkills_provider/fetcher.ex +++ b/lib/wanderer_app/zkb/zkills_provider/fetcher.ex @@ -23,12 +23,12 @@ defmodule WandererApp.Zkb.KillsProvider.Fetcher do {Map.put(acc_map, sid, kills), new_st} {:error, reason, new_st} -> - Logger.debug("[Fetcher] system=#{sid} => error=#{inspect(reason)}") + Logger.debug(fn -> "[Fetcher] system=#{sid} => error=#{inspect(reason)}" end) {Map.put(acc_map, sid, {:error, reason}), new_st} end end) - Logger.debug("[Fetcher] fetch_kills_for_systems => done, final_map_size=#{map_size(final_map)} calls=#{final_state.calls_count}") + Logger.debug(fn -> "[Fetcher] fetch_kills_for_systems => done, final_map_size=#{map_size(final_map)} calls=#{final_state.calls_count}" end) {:ok, final_map} rescue e -> @@ -57,10 +57,10 @@ defmodule WandererApp.Zkb.KillsProvider.Fetcher do if not force? and KillsCache.recently_fetched?(system_id) do cached_kills = KillsCache.fetch_cached_kills(system_id) final = maybe_take(cached_kills, limit) - Logger.debug("#{log_prefix}, recently_fetched?=true => returning #{length(final)} cached kills") + Logger.debug(fn -> "#{log_prefix}, recently_fetched?=true => returning #{length(final)} cached kills" end) {:ok, final, state} else - Logger.debug("#{log_prefix}, hours=#{since_hours}, limit=#{inspect(limit)}, force=#{force?}") + Logger.debug(fn -> "#{log_prefix}, hours=#{since_hours}, limit=#{inspect(limit)}, force=#{force?}" end) cutoff_dt = hours_ago(since_hours) @@ -75,9 +75,9 @@ defmodule WandererApp.Zkb.KillsProvider.Fetcher do KillsCache.put_full_fetched_timestamp(system_id) final_kills = KillsCache.fetch_cached_kills(system_id) |> maybe_take(limit) - Logger.debug( + Logger.debug(fn -> "#{log_prefix}, total_fetched=#{total_fetched}, final_cached=#{length(final_kills)}, calls_count=#{new_st.calls_count}" - ) + end) {:ok, final_kills, new_st} @@ -117,7 +117,7 @@ defmodule WandererApp.Zkb.KillsProvider.Fetcher do with {:ok, st1} <- increment_calls_count(state), {:ok, st2, partials} <- ZkbApi.fetch_and_parse_page(system_id, page, st1) do - Logger.debug("[Fetcher] system=#{system_id}, page=#{page}, partials_count=#{length(partials)}") + Logger.debug(fn -> "[Fetcher] system=#{system_id}, page=#{page}, partials_count=#{length(partials)}" end) {_count_stored, older_found?, total_now} = Enum.reduce_while(partials, {0, false, total_so_far}, fn partial, {acc_count, had_older, acc_total} -> diff --git a/lib/wanderer_app/zkb/zkills_provider/websocket.ex b/lib/wanderer_app/zkb/zkills_provider/websocket.ex index 393b206e..c5e3b979 100644 --- a/lib/wanderer_app/zkb/zkills_provider/websocket.ex +++ b/lib/wanderer_app/zkb/zkills_provider/websocket.ex @@ -19,7 +19,7 @@ defmodule WandererApp.Zkb.KillsProvider.Websocket do # Called by `KillsProvider.handle_in` def handle_in({:text, frame}, state) do - Logger.debug("[KillsProvider.Websocket] Received frame => #{frame}") + Logger.debug(fn -> "[KillsProvider.Websocket] Received frame => #{frame}" end) partial = Jason.decode!(frame) parse_and_store_zkb_partial(partial) {:ok, state} @@ -61,14 +61,14 @@ defmodule WandererApp.Zkb.KillsProvider.Websocket do end defp handle_subscribe(channel, state) do - Logger.debug("[KillsProvider.Websocket] Subscribing to #{channel}") + Logger.debug(fn -> "[KillsProvider.Websocket] Subscribing to #{channel}" end) payload = Jason.encode!(%{"action" => "sub", "channel" => channel}) {:reply, {:text, payload}, state} end # The partial from zKillboard has killmail_id + zkb.hash, but no time/victim/attackers defp parse_and_store_zkb_partial(%{"killmail_id" => kill_id, "zkb" => %{"hash" => kill_hash}} = partial) do - Logger.debug("[KillsProvider.Websocket] parse_and_store_zkb_partial => kill_id=#{kill_id}") + Logger.debug(fn -> "[KillsProvider.Websocket] parse_and_store_zkb_partial => kill_id=#{kill_id}" end) case Esi.get_killmail(kill_id, kill_hash) do {:ok, full_esi_data} -> # Merge partial zKB fields (like totalValue) onto ESI data