Compare commits

..

4 Commits

Author SHA1 Message Date
CI
b6495504f8 chore: release version v1.45.3
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64/v8) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-02-05 17:22:36 +00:00
guarzo
2f07ec1b74 fix: color and formatting fixes for local character (#150) 2025-02-05 21:22:10 +04:00
CI
7073a0e8e6 chore: release version v1.45.2
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64/v8) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-02-05 15:55:30 +00:00
guarzo
bb0d91a3c7 fix: fix route list hover and on the map character list (#149)
* fix: correct formatting for on the map character list

* fix: fix hover for route list
2025-02-05 19:55:05 +04:00
4 changed files with 159 additions and 153 deletions

View File

@@ -2,6 +2,28 @@
<!-- changelog --> <!-- changelog -->
## [v1.45.3](https://github.com/wanderer-industries/wanderer/compare/v1.45.2...v1.45.3) (2025-02-05)
### Bug Fixes:
* color and formatting fixes for local character (#150)
## [v1.45.2](https://github.com/wanderer-industries/wanderer/compare/v1.45.1...v1.45.2) (2025-02-05)
### Bug Fixes:
* fix route list hover and on the map character list (#149)
* correct formatting for on the map character list
* fix hover for route list
## [v1.45.1](https://github.com/wanderer-industries/wanderer/compare/v1.45.0...v1.45.1) (2025-02-05) ## [v1.45.1](https://github.com/wanderer-industries/wanderer/compare/v1.45.0...v1.45.1) (2025-02-05)

View File

@@ -18,17 +18,14 @@ const SHIP_NAME_RX = /u'|'/g;
export const getShipName = (name: string) => { export const getShipName = (name: string) => {
return name return name
.replace(SHIP_NAME_RX, '') .replace(SHIP_NAME_RX, '')
.replace(/\\u([\dA-Fa-f]{4})/g, (_, grp) => String.fromCharCode(parseInt(grp, 16))) .replace(/\\u([\dA-Fa-f]{4})/g, (_, grp) =>
.replace(/\\x([\dA-Fa-f]{2})/g, (_, grp) => String.fromCharCode(parseInt(grp, 16))); String.fromCharCode(parseInt(grp, 16))
)
.replace(/\\x([\dA-Fa-f]{2})/g, (_, grp) =>
String.fromCharCode(parseInt(grp, 16))
);
}; };
// A small divider between fields:
const Divider = () => (
<span className="mx-1 text-gray-400" aria-hidden="true">
|
</span>
);
export const CharacterCard = ({ export const CharacterCard = ({
compact = false, compact = false,
isOwn, isOwn,
@@ -44,135 +41,105 @@ export const CharacterCard = ({
}); });
}, [char]); }, [char]);
// Precompute the ship name (decoded):
const shipNameText = char.ship?.ship_name ? getShipName(char.ship.ship_name) : ''; const shipNameText = char.ship?.ship_name ? getShipName(char.ship.ship_name) : '';
const tickerText = char.alliance_id ? char.alliance_ticker : char.corporation_ticker;
const shipType = char.ship?.ship_type_info?.name;
const locationShown = showSystem && char.location?.solar_system_id;
// ----------------------------------------------------------------------------- if (compact) {
// COMPACT MODE: Main line = return (
// if (showShipName & haveShipName) => name | shipName (skip ticker)
// else => name | [ticker]
// -----------------------------------------------------------------------------
const compactLine = (
<>
{/* Character Name (lighter shade) */}
<span className="text-gray-200">{char.name}</span>
<Divider />
{showShipName && shipNameText ? (
// Show the ship name in place of the ticker (use indigo color to match corp/alliance)
<span className="text-indigo-300">{shipNameText}</span>
) : (
// Show the [ticker] (indigo)
<span className="text-indigo-300">[{char.alliance_id ? char.alliance_ticker : char.corporation_ticker}]</span>
)}
</>
);
// -----------------------------------------------------------------------------
// NON-COMPACT MODE:
// Line 1 => name | [ticker]
// Line 2 => (shipName) always, if it exists
// -----------------------------------------------------------------------------
const nonCompactLine1 = (
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
{/* Character Name (lighter shade) */}
<span className="text-gray-200">{char.name}</span>
<Divider />
<span className="text-indigo-300">[{char.alliance_id ? char.alliance_ticker : char.corporation_ticker}]</span>
</div>
);
const nonCompactLine2 = (
<>
{shipNameText && (
<div className="overflow-hidden text-ellipsis whitespace-nowrap text-gray-300">{shipNameText}</div>
)}
</>
);
return (
<div className={clsx(classes.CharacterCard, 'w-full text-xs box-border')} onClick={handleSelect}>
<div <div
className={clsx( className={clsx(classes.CharacterCard, 'w-full text-xs box-border')}
'w-full px-2 py-1 overflow-hidden gap-1', onClick={handleSelect}
compact ? 'grid items-center' : 'flex flex-col md:flex-row items-start',
)}
style={compact ? { gridTemplateColumns: 'auto 1fr auto', minWidth: 0 } : undefined}
> >
{compact ? ( <div className="w-full px-2 py-1 flex items-center gap-2" style={{ minWidth: 0 }}>
<img <img
src={`https://images.evetech.net/characters/${char.eve_id}/portrait`} src={`https://images.evetech.net/characters/${char.eve_id}/portrait`}
alt={`${char.name} portrait`} alt={`${char.name} portrait`}
style={{ style={{
width: '18px', width: '18px',
height: '18px', height: '18px',
// Remove circle shape for a square image:
borderRadius: 0, borderRadius: 0,
marginRight: '4px',
flexShrink: 0, flexShrink: 0,
// Slightly lighter than typical dark background:
border: '1px solid #2b2b2b', border: '1px solid #2b2b2b',
}} }}
/> />
) : ( <div className="flex flex-grow overflow-hidden text-left" style={{ minWidth: 0 }}>
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
<span className={clsx(isOwn ? 'text-orange-400' : 'text-gray-200')}>
{char.name}
</span>{" "}
<span className="text-gray-400">
{(!locationShown && showShipName && shipNameText)
? `- ${shipNameText}`
: `[${tickerText}]`}
</span>
</div>
</div>
{shipType && (
<div
className="text-gray-300 overflow-hidden text-ellipsis whitespace-nowrap flex-shrink-0"
style={{ maxWidth: '120px' }}
title={shipType}
>
{shipType}
</div>
)}
</div>
</div>
);
} else {
return (
<div
className={clsx(classes.CharacterCard, 'w-full text-xs box-border')}
onClick={handleSelect}
>
<div className="w-full px-2 py-1 flex items-center gap-2" style={{ minWidth: 0 }}>
<span <span
className={clsx(classes.EveIcon, classes.CharIcon, 'wd-bg-default')} className={clsx(classes.EveIcon, classes.CharIcon, 'wd-bg-default')}
style={{ style={{
// The SCSS below ensures the image is square with a border.
backgroundImage: `url(https://images.evetech.net/characters/${char.eve_id}/portrait)`, backgroundImage: `url(https://images.evetech.net/characters/${char.eve_id}/portrait)`,
minWidth: '33px',
minHeight: '33px',
width: '33px',
height: '33px',
}} }}
/> />
)} <div className="flex flex-col flex-grow overflow-hidden" style={{ minWidth: 0 }}>
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
{/* <span className={clsx(isOwn ? 'text-orange-400' : 'text-gray-200')}>
Middle section: {char.name}
- In compact mode, everything is on one line (Name + possibly ShipName or ticker). </span>{" "}
- In non-compact mode, line 1 has (Name | Ticker), line 2 has shipName if it exists. <span className="text-gray-400">[{tickerText}]</span>
*/} </div>
<div {locationShown ? (
className={clsx('overflow-hidden text-ellipsis', { <div className="text-gray-300 text-xs overflow-hidden text-ellipsis whitespace-nowrap">
'text-left px-1': compact, <SystemView
'flex-grow': !compact, systemId={char?.location?.solar_system_id?.toString() || ''}
})} useSystemsCache={useSystemsCache}
style={{ minWidth: 0 }} />
> </div>
{/* This left border highlights "isOwn" in the same way as older code. */} ) : (
<div shipNameText && (
className={clsx('overflow-hidden whitespace-nowrap', { <div className="text-gray-300 text-xs overflow-hidden text-ellipsis whitespace-nowrap">
[classes.CardBorderLeftIsOwn]: isOwn, {shipNameText}
})} </div>
> )
{compact ? compactLine : nonCompactLine1} )}
</div> </div>
{/* Non-compact second line always shows shipName if available */} {shipType && (
{!compact && nonCompactLine2} <div className="flex-shrink-0 self-start">
<div
className="text-gray-300 overflow-hidden text-ellipsis whitespace-nowrap"
style={{ maxWidth: '200px' }}
title={shipType}
>
{shipType}
</div>
</div>
)}
</div> </div>
{/*
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 && (
<div
className={clsx('text-yellow-400 text-ellipsis overflow-hidden whitespace-nowrap', {
'text-right px-1 flex-shrink-0': compact,
'mt-1 md:mt-0 ml-auto': !compact,
})}
style={{ maxWidth: compact ? '120px' : '200px' }}
title={char.ship.ship_type_info.name}
>
{char.ship.ship_type_info.name}
</div>
)}
</div> </div>
);
{/* }
System row at the bottom if `showSystem && system exists`.
*/}
{showSystem && char.location?.solar_system_id && (
<div className="px-2 pb-1">
<SystemView systemId={char.location.solar_system_id.toString()} useSystemsCache={useSystemsCache} />
</div>
)}
</div>
);
}; };

View File

@@ -1,4 +1,13 @@
import React, { ForwardedRef, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; import React, {
ForwardedRef,
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useLayoutEffect,
useRef,
useState,
} from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import clsx from 'clsx'; import clsx from 'clsx';
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce';
@@ -31,6 +40,12 @@ export interface WdTooltipHandlers {
getIsMouseInside: () => boolean; getIsMouseInside: () => boolean;
} }
interface TriggerInfo {
clientX: number;
clientY: number;
rect: DOMRect;
}
const LEAVE_DELAY = 100; const LEAVE_DELAY = 100;
export const WdTooltip = forwardRef(function WdTooltip( export const WdTooltip = forwardRef(function WdTooltip(
@@ -43,15 +58,16 @@ export const WdTooltip = forwardRef(function WdTooltip(
className, className,
...restProps ...restProps
}: TooltipProps, }: TooltipProps,
ref: ForwardedRef<WdTooltipHandlers>, ref: ForwardedRef<WdTooltipHandlers>
) { ) {
// Always initialize position so we never have a null value.
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [pos, setPos] = useState<OffsetPosition | null>(null); const [pos, setPos] = useState<OffsetPosition>({ left: 0, top: 0 });
const tooltipRef = useRef<HTMLDivElement>(null); const tooltipRef = useRef<HTMLDivElement>(null);
const [isMouseInsideTooltip, setIsMouseInsideTooltip] = useState(false); const [isMouseInsideTooltip, setIsMouseInsideTooltip] = useState(false);
const [reactEvt, setReactEvt] = useState<React.MouseEvent>(); const [triggerInfo, setTriggerInfo] = useState<TriggerInfo | null>(null);
const hideTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); const hideTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -98,10 +114,14 @@ export const WdTooltip = forwardRef(function WdTooltip(
clearTimeout(hideTimeoutRef.current); clearTimeout(hideTimeoutRef.current);
hideTimeoutRef.current = null; hideTimeoutRef.current = null;
} }
if (e && tooltipRef.current) { if (e) {
const { clientX, clientY } = e; // Use e.currentTarget (or fallback to e.target) to determine the trigger element.
setPos(calcTooltipPosition({ x: clientX, y: clientY })); const triggerEl = (e.currentTarget as HTMLElement) || (e.target as HTMLElement);
setReactEvt(e); if (triggerEl) {
const rect = triggerEl.getBoundingClientRect();
setTriggerInfo({ clientX: e.clientX, clientY: e.clientY, rect });
setPos(calcTooltipPosition({ x: e.clientX, y: e.clientY }));
}
} }
setVisible(true); setVisible(true);
}, },
@@ -114,48 +134,46 @@ export const WdTooltip = forwardRef(function WdTooltip(
getIsMouseInside: () => isMouseInsideTooltip, getIsMouseInside: () => isMouseInsideTooltip,
})); }));
useEffect(() => { // Recalculate position once the tooltip element has been rendered.
if (!tooltipRef.current || !reactEvt) return; useLayoutEffect(() => {
if (!tooltipRef.current || !triggerInfo) return;
const { clientX, clientY, target } = reactEvt;
const tooltipEl = tooltipRef.current; const tooltipEl = tooltipRef.current;
const triggerEl = target as HTMLElement; const { rect } = triggerInfo;
const triggerBounds = triggerEl.getBoundingClientRect(); let x = triggerInfo.clientX;
let y = triggerInfo.clientY;
let x = clientX;
let y = clientY;
if (tPosition === TooltipPosition.left) { if (tPosition === TooltipPosition.left) {
const tooltipBounds = tooltipEl.getBoundingClientRect(); const tooltipBounds = tooltipEl.getBoundingClientRect();
x = triggerBounds.left - tooltipBounds.width - offset; x = rect.left - tooltipBounds.width - offset;
y = triggerBounds.y + triggerBounds.height / 2 - tooltipBounds.height / 2; y = rect.top + rect.height / 2 - tooltipBounds.height / 2;
if (x <= 0) { if (x <= 0) {
x = triggerBounds.left + triggerBounds.width + offset; x = rect.left + rect.width + offset;
} }
setPos(calcTooltipPosition({ x, y })); setPos(calcTooltipPosition({ x, y }));
return; return;
} }
if (tPosition === TooltipPosition.right) { if (tPosition === TooltipPosition.right) {
x = triggerBounds.left + triggerBounds.width + offset; x = rect.left + rect.width + offset;
y = triggerBounds.y + triggerBounds.height / 2 - tooltipEl.offsetHeight / 2; y = rect.top + rect.height / 2 - tooltipEl.offsetHeight / 2;
setPos(calcTooltipPosition({ x, y })); setPos(calcTooltipPosition({ x, y }));
return; return;
} }
if (tPosition === TooltipPosition.top) { if (tPosition === TooltipPosition.top) {
x = triggerBounds.x + triggerBounds.width / 2 - tooltipEl.offsetWidth / 2; x = rect.left + rect.width / 2 - tooltipEl.offsetWidth / 2;
y = triggerBounds.top - tooltipEl.offsetHeight - offset; y = rect.top - tooltipEl.offsetHeight - offset;
setPos(calcTooltipPosition({ x, y })); setPos(calcTooltipPosition({ x, y }));
return; return;
} }
if (tPosition === TooltipPosition.bottom) { if (tPosition === TooltipPosition.bottom) {
x = triggerBounds.x + triggerBounds.width / 2 - tooltipEl.offsetWidth / 2; x = rect.left + rect.width / 2 - tooltipEl.offsetWidth / 2;
y = triggerBounds.bottom + offset; y = rect.bottom + offset;
setPos(calcTooltipPosition({ x, y })); setPos(calcTooltipPosition({ x, y }));
return; return;
} }
// Default case: use stored coordinates.
setPos(calcTooltipPosition({ x, y })); setPos(calcTooltipPosition({ x, y }));
}, [calcTooltipPosition, reactEvt, tPosition, offset]); }, [calcTooltipPosition, triggerInfo, tPosition, offset]);
useEffect(() => { useEffect(() => {
if (!targetSelector) return; if (!targetSelector) return;
@@ -190,7 +208,7 @@ export const WdTooltip = forwardRef(function WdTooltip(
switch (tPosition) { switch (tPosition) {
case TooltipPosition.left: { case TooltipPosition.left: {
x = rect.left - tooltipEl.offsetWidth - offset; x = rect.left - tooltipEl.offsetWidth - offset;
y = rect.y + rect.height / 2 - tooltipEl.offsetHeight / 2; y = rect.top + rect.height / 2 - tooltipEl.offsetHeight / 2;
if (x <= 0) { if (x <= 0) {
x = rect.left + rect.width + offset; x = rect.left + rect.width + offset;
} }
@@ -198,16 +216,16 @@ export const WdTooltip = forwardRef(function WdTooltip(
} }
case TooltipPosition.right: { case TooltipPosition.right: {
x = rect.left + rect.width + offset; x = rect.left + rect.width + offset;
y = rect.y + rect.height / 2 - tooltipEl.offsetHeight / 2; y = rect.top + rect.height / 2 - tooltipEl.offsetHeight / 2;
break; break;
} }
case TooltipPosition.top: { case TooltipPosition.top: {
x = rect.x + rect.width / 2 - tooltipEl.offsetWidth / 2; x = rect.left + rect.width / 2 - tooltipEl.offsetWidth / 2;
y = rect.top - tooltipEl.offsetHeight - offset; y = rect.top - tooltipEl.offsetHeight - offset;
break; break;
} }
case TooltipPosition.bottom: { case TooltipPosition.bottom: {
x = rect.x + rect.width / 2 - tooltipEl.offsetWidth / 2; x = rect.left + rect.width / 2 - tooltipEl.offsetWidth / 2;
y = rect.bottom + offset; y = rect.bottom + offset;
break; break;
} }
@@ -247,12 +265,11 @@ export const WdTooltip = forwardRef(function WdTooltip(
classes.tooltip, classes.tooltip,
interactive ? 'pointer-events-auto' : 'pointer-events-none', interactive ? 'pointer-events-auto' : 'pointer-events-none',
'absolute p-1 border rounded-sm border-green-300 border-opacity-10 bg-stone-900 bg-opacity-90', 'absolute p-1 border rounded-sm border-green-300 border-opacity-10 bg-stone-900 bg-opacity-90',
pos === null ? 'invisible' : '', className
className,
)} )}
style={{ style={{
top: pos?.top ?? 0, top: pos.top,
left: pos?.left ?? 0, left: pos.left,
zIndex: 10000, zIndex: 10000,
}} }}
onMouseEnter={() => { onMouseEnter={() => {
@@ -272,7 +289,7 @@ export const WdTooltip = forwardRef(function WdTooltip(
> >
{typeof content === 'function' ? content() : content} {typeof content === 'function' ? content() : content}
</div>, </div>,
document.body, document.body
); );
}); });

View File

@@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do
@source_url "https://github.com/wanderer-industries/wanderer" @source_url "https://github.com/wanderer-industries/wanderer"
@version "1.45.1" @version "1.45.3"
def project do def project do
[ [