From f96cb01860002ad8ccd990e4c35a29ab3e87e5f0 Mon Sep 17 00:00:00 2001 From: guarzo Date: Fri, 7 Feb 2025 12:15:20 -0700 Subject: [PATCH] fix: restore styling for local characters list (#152) --- .../js/hooks/Mapper/components/hooks/index.ts | 1 + .../components/hooks/useElementWidth.ts | 43 +++ .../LocalCharacters/LocalCharacters.tsx | 82 +----- .../components/LocalCharacterList.module.scss | 7 +- .../components/LocalCharactersHeader.tsx | 91 +++++++ .../widgets/SystemKills/SystemKills.tsx | 99 +++---- .../components/AttackerRowSubInfo.tsx | 52 ---- .../SystemKills/components/FullKillRow.tsx | 253 +++++++++--------- .../components/SystemKillsHeader.tsx | 79 +++--- .../components/VictimRowSubInfo.tsx | 7 +- .../WdResponsiveCheckbox.tsx | 72 +++++ .../ui-kit/WdResponsiveCheckBox/index.ts | 1 + .../hooks/Mapper/components/ui-kit/index.ts | 2 + 13 files changed, 457 insertions(+), 332 deletions(-) create mode 100644 assets/js/hooks/Mapper/components/hooks/useElementWidth.ts create mode 100644 assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/LocalCharactersHeader.tsx delete mode 100644 assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/AttackerRowSubInfo.tsx create mode 100644 assets/js/hooks/Mapper/components/ui-kit/WdResponsiveCheckBox/WdResponsiveCheckbox.tsx create mode 100644 assets/js/hooks/Mapper/components/ui-kit/WdResponsiveCheckBox/index.ts diff --git a/assets/js/hooks/Mapper/components/hooks/index.ts b/assets/js/hooks/Mapper/components/hooks/index.ts index 8c0c3ab1..f5bba5bd 100644 --- a/assets/js/hooks/Mapper/components/hooks/index.ts +++ b/assets/js/hooks/Mapper/components/hooks/index.ts @@ -1,2 +1,3 @@ export * from './useSystemInfo'; export * from './useGetOwnOnlineCharacters'; +export * from './useElementWidth'; diff --git a/assets/js/hooks/Mapper/components/hooks/useElementWidth.ts b/assets/js/hooks/Mapper/components/hooks/useElementWidth.ts new file mode 100644 index 00000000..06d716a2 --- /dev/null +++ b/assets/js/hooks/Mapper/components/hooks/useElementWidth.ts @@ -0,0 +1,43 @@ +import { useState, useLayoutEffect, RefObject } from 'react'; + +/** + * useElementWidth + * + * A custom hook that accepts a ref to an HTML element and returns its current width. + * It uses a ResizeObserver and window resize listener to update the width when necessary. + * + * @param ref - A RefObject pointing to an HTML element. + * @returns The current width of the element. + */ +export function useElementWidth(ref: RefObject): number { + const [width, setWidth] = useState(0); + + useLayoutEffect(() => { + const updateWidth = () => { + if (ref.current) { + const newWidth = ref.current.getBoundingClientRect().width; + if (newWidth > 0) { + setWidth(newWidth); + } + } + }; + + updateWidth(); // Initial measurement + + const observer = new ResizeObserver(() => { + const id = setTimeout(updateWidth, 100); + return () => clearTimeout(id); + }); + + if (ref.current) { + observer.observe(ref.current); + } + window.addEventListener("resize", updateWidth); + return () => { + observer.disconnect(); + window.removeEventListener("resize", updateWidth); + }; + }, [ref]); + + return width; +} 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 729e6e10..77613677 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/LocalCharacters.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/LocalCharacters.tsx @@ -1,16 +1,13 @@ -import { useMemo, useRef } from 'react'; +import { useMemo } from 'react'; import { Widget } from '@/hooks/Mapper/components/mapInterface/components'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; -import clsx from 'clsx'; -import { LayoutEventBlocker, WdCheckbox } from '@/hooks/Mapper/components/ui-kit'; -import { sortCharacters } from '@/hooks/Mapper/components/mapInterface/helpers/sortCharacters.ts'; +import { sortCharacters } from '@/hooks/Mapper/components/mapInterface/helpers/sortCharacters'; 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'; +import { UserPermission } from '@/hooks/Mapper/types/permissions'; import { LocalCharactersList } from './components/LocalCharactersList'; import { useLocalCharactersItemTemplate } from './hooks/useLocalCharacters'; import { useLocalCharacterWidgetSettings } from './hooks/useLocalWidgetSettings'; +import { LocalCharactersHeader } from './components/LocalCharactersHeader'; export const LocalCharacters = () => { const { @@ -18,14 +15,12 @@ export const LocalCharacters = () => { } = useMapRootState(); const [settings, setSettings] = useLocalCharacterWidgetSettings(); - const [systemId] = selectedSystems; - const restrictOfflineShowing = useMapGetOption('restrict_offline_showing'); + const restrictOfflineShowing = useMapGetOption("restrict_offline_showing"); const isAdminOrManager = useMapCheckPermissions([UserPermission.MANAGE_MAP]); - const showOffline = useMemo( () => !restrictOfflineShowing || isAdminOrManager, - [isAdminOrManager, restrictOfflineShowing], + [isAdminOrManager, restrictOfflineShowing] ); const sorted = useMemo(() => { @@ -42,7 +37,6 @@ export const LocalCharacters = () => { if (!showOffline || !settings.showOffline) { return filtered.filter(c => c.online); } - return filtered; }, [ characters, @@ -58,64 +52,18 @@ export const LocalCharacters = () => { const isNotSelectedSystem = selectedSystems.length !== 1; const showList = sorted.length > 0 && selectedSystems.length === 1; - const ref = useRef(null); - const compact = useMaxWidth(ref, 145); - const itemTemplate = useLocalCharactersItemTemplate(settings.showShipName); return ( -
- Local{showList ? ` [${sorted.length}]` : ''} -
-
- - {showOffline && ( - -
- - setSettings(prev => ({ ...prev, showOffline: !prev.showOffline })) - } - /> -
-
- )} - - {settings.compact && ( - -
- - setSettings(prev => ({ ...prev, showShipName: !prev.showShipName })) - } - /> -
-
- )} - - setSettings(prev => ({ ...prev, compact: !prev.compact }))} - /> -
-
- + } > {isNotSelectedSystem && ( @@ -123,19 +71,17 @@ export const LocalCharacters = () => { System is not selected )} - {isNobodyHere && !isNotSelectedSystem && (
Nobody here
)} - {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 index 9ff5b73f..3c546f4e 100644 --- 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 @@ -1,4 +1,3 @@ -// .VirtualScroller { -// height: 100% !important; -// } - \ No newline at end of file +.VirtualScroller { + height: 100% !important; +} diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/LocalCharactersHeader.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/LocalCharactersHeader.tsx new file mode 100644 index 00000000..0034b974 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/LocalCharactersHeader.tsx @@ -0,0 +1,91 @@ +import React, { useRef } from 'react'; +import clsx from 'clsx'; +import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth'; +import { LayoutEventBlocker, WdResponsiveCheckbox, WdDisplayMode } from '@/hooks/Mapper/components/ui-kit'; +import { useElementWidth } from '@/hooks/Mapper/components/hooks'; + +interface LocalCharactersHeaderProps { + sortedCount: number; + showList: boolean; + showOffline: boolean; + settings: { + compact: boolean; + showOffline: boolean; + showShipName: boolean; + }; + setSettings: (fn: (prev: any) => any) => void; +} + +export const LocalCharactersHeader: React.FC = ({ + sortedCount, + showList, + showOffline, + settings, + setSettings, +}) => { + const headerRef = useRef(null); + const headerWidth = useElementWidth(headerRef) || 300; + + const reservedWidth = 100; + const availableWidthForCheckboxes = Math.max(headerWidth - reservedWidth, 0); + + let displayMode: WdDisplayMode = "full"; + if (availableWidthForCheckboxes >= 150) { + displayMode = "full"; + } else if (availableWidthForCheckboxes >= 100) { + displayMode = "abbr"; + } else { + displayMode = "checkbox"; + } + + const compact = useMaxWidth(headerRef, 145); + + return ( +
+
+ Local{showList ? ` [${sortedCount}]` : ""} +
+
+ +
+ {showOffline && ( + + setSettings((prev: any) => ({ ...prev, showOffline: !prev.showOffline })) + } + classNameLabel={clsx("whitespace-nowrap text-stone-400 hover:text-stone-200 transition duration-300", { truncate: compact })} + displayMode={displayMode} + /> + )} + {settings.compact && ( + + setSettings((prev: any) => ({ ...prev, showShipName: !prev.showShipName })) + } + classNameLabel={clsx("whitespace-nowrap text-stone-400 hover:text-stone-200 transition duration-300", { truncate: compact })} + displayMode={displayMode} + /> + )} +
+ setSettings((prev: any) => ({ ...prev, compact: !prev.compact }))} + /> +
+
+
+ ); +}; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/SystemKills.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/SystemKills.tsx index 667b9da1..a4e7c9f8 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/SystemKills.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/SystemKills.tsx @@ -15,7 +15,6 @@ export const SystemKills: React.FC = () => { } = useMapRootState(); const [systemId] = selectedSystems || []; - const [settingsDialogVisible, setSettingsDialogVisible] = useState(false); const systemNameMap = useMemo(() => { @@ -41,7 +40,9 @@ export const SystemKills: React.FC = () => { const filteredKills = useMemo(() => { if (!settings.whOnly || !visible) return kills; return kills.filter(kill => { - const system = systems.find(sys => sys.system_static_info.solar_system_id === kill.solar_system_id); + const system = systems.find( + sys => sys.system_static_info.solar_system_id === kill.solar_system_id + ); if (!system) { console.warn(`System with id ${kill.solar_system_id} not found.`); return false; @@ -55,60 +56,62 @@ export const SystemKills: React.FC = () => {
setSettingsDialogVisible(true)} /> + setSettingsDialogVisible(true)} + /> } > - {!isSubscriptionActive && ( -
- Kills available with 'Active' map subscription only (contact map administrators) -
- )} - {isSubscriptionActive && ( - <> - {isNothingSelected && ( -
+
+ {!isSubscriptionActive ? ( +
+ + Kills available with 'Active' map subscription only (contact map administrators) + +
+ ) : isNothingSelected ? ( +
+ No system selected (or toggle “Show all systems”) -
- )} - - {!isNothingSelected && showLoading && ( -
+ +
+ ) : showLoading ? ( +
+ Loading Kills... -
- )} - - {!isNothingSelected && !showLoading && error && ( -
+ +
+ ) : error ? ( +
+ {error} -
- )} - - {!isNothingSelected && - !showLoading && - !error && - (!filteredKills || filteredKills.length === 0) && ( -
- No kills found -
- )} - - {!isNothingSelected && !showLoading && !error && ( -
- -
- )} - - )} + +
+ ) : !filteredKills || filteredKills.length === 0 ? ( +
+ + No kills found + +
+ ) : ( +
+ +
+ )} +
- + ); }; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/AttackerRowSubInfo.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/AttackerRowSubInfo.tsx deleted file mode 100644 index d695769e..00000000 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/AttackerRowSubInfo.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import clsx from 'clsx'; -import { zkillLink } from '../helpers'; -import classes from './SystemKillRow.module.scss'; - -interface AttackerRowSubInfoProps { - finalBlowCharId: number | null | undefined; - finalBlowCharName?: string; - attackerPortraitUrl: string | null; - - finalBlowCorpId: number | null | undefined; - finalBlowCorpName?: string; - attackerCorpLogoUrl: string | null; - - finalBlowAllianceId: number | null | undefined; - finalBlowAllianceName?: string; - attackerAllianceLogoUrl: string | null; - - containerHeight?: number; -} - -export const AttackerRowSubInfo: React.FC = ({ - finalBlowCharId = 0, - finalBlowCharName, - attackerPortraitUrl, - containerHeight = 8, -}) => { - if (!attackerPortraitUrl || finalBlowCharId === null || finalBlowCharId <= 0) { - return null; - } - - const containerClass = `h-${containerHeight}`; - - return ( -
-
- - {finalBlowCharName - -
-
- ); -}; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/FullKillRow.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/FullKillRow.tsx index a002fedd..40ee237b 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/FullKillRow.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/FullKillRow.tsx @@ -29,8 +29,7 @@ export const FullKillRow: React.FC = ({ }) => { const { killmail_id = 0, - - // Victim + // Victim data victim_char_name = '', victim_alliance_ticker = '', victim_corp_ticker = '', @@ -41,8 +40,7 @@ export const FullKillRow: React.FC = ({ victim_ship_type_id = 0, victim_corp_name = '', victim_alliance_name = '', - - // Attacker + // Attacker data final_blow_char_id = 0, final_blow_char_name = '', final_blow_alliance_ticker = '', @@ -53,14 +51,12 @@ export const FullKillRow: React.FC = ({ final_blow_alliance_id = 0, final_blow_ship_name = '', final_blow_ship_type_id = 0, - total_value = 0, kill_time = '', } = killDetails || {}; const attackerIsNpc = final_blow_char_id === 0; - const victimAffiliation = - victim_alliance_ticker || victim_corp_ticker || null; + const victimAffiliation = victim_alliance_ticker || victim_corp_ticker || null; const attackerAffiliation = attackerIsNpc ? '' : final_blow_alliance_ticker || final_blow_corp_ticker || ''; @@ -69,7 +65,7 @@ export const FullKillRow: React.FC = ({ total_value != null && total_value > 0 ? `${formatISK(total_value)} ISK` : null; const killTimeAgo = kill_time ? formatTimeMixed(kill_time) : '0h ago'; - // Victim images, now also pulling victimShipUrl + // Build victim images const { victimPortraitUrl, victimCorpLogoUrl, @@ -81,7 +77,8 @@ export const FullKillRow: React.FC = ({ victim_corp_id, victim_alliance_id, }); - // Attacker images + + // Build attacker images const { attackerPortraitUrl, attackerCorpLogoUrl, @@ -92,7 +89,7 @@ export const FullKillRow: React.FC = ({ final_blow_alliance_id, }); - // Primary corp/alliance logo for victim + // Primary image for victim const { url: victimPrimaryImageUrl, tooltip: victimPrimaryTooltip } = getPrimaryLogoAndTooltip( victimAllianceLogoUrl, @@ -102,7 +99,7 @@ export const FullKillRow: React.FC = ({ 'Victim' ); - // Primary image for attacker => NPC => ship, else corp/alliance + // Primary image for attacker const { url: attackerPrimaryImageUrl, tooltip: attackerPrimaryTooltip } = getAttackerPrimaryImageAndTooltip( attackerIsNpc, @@ -119,34 +116,15 @@ export const FullKillRow: React.FC = ({
- {/* ---------------- Victim Side ---------------- */} -
- {victimShipUrl && ( -
- - VictimShip - -
- )} - - {victimPrimaryImageUrl && ( - -
+
+ {/* Victim Section */} +
+ {victimShipUrl && ( + - - )} - - - -
-
- {victim_char_name} - {victimAffiliation && ( - / {victimAffiliation} - )} -
-
- {victim_ship_name} - {killValueFormatted && ( - <> - / - - {killValueFormatted} - - - )} -
-
- {!onlyOneSystem && systemName && {systemName}} -
-
-
- -
-
- {!attackerIsNpc && ( + )} + {victimPrimaryImageUrl && ( + +
+ + VictimPrimaryLogo + +
+
+ )} + +
- {final_blow_char_name} - {attackerAffiliation && ( - / {attackerAffiliation} + {victim_char_name} + {victimAffiliation && ( + / {victimAffiliation} )}
- )} - {!attackerIsNpc && final_blow_ship_name && ( -
{final_blow_ship_name}
- )} -
{killTimeAgo}
-
- - {!attackerIsNpc && attackerPortraitUrl && final_blow_char_id && final_blow_char_id > 0 && ( -
- - AttackerPortrait - +
+ {victim_ship_name} + {killValueFormatted && ( + <> + / + {killValueFormatted} + + )} +
+
+ {!onlyOneSystem && systemName && {systemName}} +
- )} - - {attackerPrimaryImageUrl && ( - -
+
+ {/* Attacker Section */} +
+
+ {!attackerIsNpc && ( +
+ {final_blow_char_name} + {attackerAffiliation && ( + / {attackerAffiliation} + )} +
+ )} + {!attackerIsNpc && final_blow_ship_name && ( +
{final_blow_ship_name}
+ )} +
{killTimeAgo}
+
+ {(!attackerIsNpc && attackerPortraitUrl && final_blow_char_id > 0) && ( + - - )} + )} + {attackerPrimaryImageUrl && ( + + + + )} +
); diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/SystemKillsHeader.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/SystemKillsHeader.tsx index b8bed7d0..6bf4e219 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/SystemKillsHeader.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/SystemKillsHeader.tsx @@ -1,20 +1,22 @@ -import React from 'react'; +import React, { useRef } from 'react'; +import clsx from 'clsx'; import { LayoutEventBlocker, - WdCheckbox, + WdResponsiveCheckbox, WdImgButton, TooltipPosition, - SystemView, + WdDisplayMode, } from '@/hooks/Mapper/components/ui-kit'; import { useKillsWidgetSettings } from '../hooks/useKillsWidgetSettings'; import { PrimeIcons } from 'primereact/api'; +import { useElementWidth } from '@/hooks/Mapper/components/hooks'; -interface KillsWidgetHeaderProps { +interface KillsHeaderProps { systemId?: string; onOpenSettings: () => void; } -export const KillsHeader: React.FC = ({ systemId, onOpenSettings }) => { +export const KillsHeader: React.FC = ({ systemId, onOpenSettings }) => { const [settings, setSettings] = useKillsWidgetSettings(); const { showAll } = settings; @@ -22,35 +24,48 @@ export const KillsHeader: React.FC = ({ systemId, onOpen setSettings(prev => ({ ...prev, showAll: !prev.showAll })); }; + const headerRef = useRef(null); + const headerWidth = useElementWidth(headerRef) || 300; + + const reservedWidth = 100; + const availableWidth = Math.max(headerWidth - reservedWidth, 0); + + let displayMode: WdDisplayMode = "full"; + if (availableWidth >= 60) { + displayMode = "full"; + } else { + displayMode = "abbr"; + } + return ( -
-
-
- Kills - {systemId && !showAll && ' in '} -
- {systemId && !showAll && } +
+
+ Kills{systemId && !showAll && ' in '} +
+
+ +
+ + +
+
- - - - - -
); }; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/VictimRowSubInfo.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/VictimRowSubInfo.tsx index a4171f86..f16b2615 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/VictimRowSubInfo.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/VictimRowSubInfo.tsx @@ -1,4 +1,3 @@ -// VictimSubRowInfo.tsx import React from 'react'; import clsx from 'clsx'; import { zkillLink } from '../helpers'; @@ -15,13 +14,13 @@ export const VictimRowSubInfo: React.FC = ({ victimPortraitUrl, victimCharName, }) => { - if (!victimPortraitUrl || victimCharacterId === null || victimCharacterId <= 0) { + if (!victimPortraitUrl || !victimCharacterId || victimCharacterId <= 0) { return null; } return ( -
-
+
+
void; + classNameLabel?: string; + containerClassName?: string; + labelSide?: 'left' | 'right'; + displayMode: WdDisplayMode; +} + +export const WdResponsiveCheckbox: React.FC = ({ + tooltipContent, + size, + labelFull, + labelAbbreviated, + value, + onChange, + classNameLabel, + containerClassName, + labelSide = 'left', + displayMode, +}) => { + if (displayMode === "hide") { + return null; + } + + const label = + displayMode === "full" + ? labelFull + : displayMode === "abbr" + ? labelAbbreviated + : displayMode === "checkbox" + ? "" + : labelFull; + + const checkbox = ( +
+ +
+ ); + + return tooltipContent ? ( + {checkbox} + ) : ( + checkbox + ); +}; diff --git a/assets/js/hooks/Mapper/components/ui-kit/WdResponsiveCheckBox/index.ts b/assets/js/hooks/Mapper/components/ui-kit/WdResponsiveCheckBox/index.ts new file mode 100644 index 00000000..f01a7163 --- /dev/null +++ b/assets/js/hooks/Mapper/components/ui-kit/WdResponsiveCheckBox/index.ts @@ -0,0 +1 @@ +export * from './WdResponsiveCheckbox'; diff --git a/assets/js/hooks/Mapper/components/ui-kit/index.ts b/assets/js/hooks/Mapper/components/ui-kit/index.ts index 3d5dd3db..130fef59 100644 --- a/assets/js/hooks/Mapper/components/ui-kit/index.ts +++ b/assets/js/hooks/Mapper/components/ui-kit/index.ts @@ -11,3 +11,5 @@ export * from './WdImgButton'; export * from './WdTooltip'; export * from './WdCheckbox'; export * from './TimeAgo'; +export * from './WdTooltipWrapper'; +export * from './WdResponsiveCheckBox';