mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-12 18:56:01 +00:00
179 lines
6.1 KiB
TypeScript
179 lines
6.1 KiB
TypeScript
import { useCallback } from 'react';
|
|
import clsx from 'clsx';
|
|
import classes from './CharacterCard.module.scss';
|
|
import { SystemView } from '@/hooks/Mapper/components/ui-kit/SystemView';
|
|
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
|
|
import { Commands } from '@/hooks/Mapper/types/mapHandlers';
|
|
import { emitMapEvent } from '@/hooks/Mapper/events';
|
|
|
|
type CharacterCardProps = {
|
|
compact?: boolean;
|
|
showSystem?: boolean;
|
|
showShipName?: boolean;
|
|
useSystemsCache?: boolean;
|
|
} & CharacterTypeRaw &
|
|
WithIsOwnCharacter;
|
|
|
|
const SHIP_NAME_RX = /u'|'/g;
|
|
export const getShipName = (name: string) => {
|
|
return name
|
|
.replace(SHIP_NAME_RX, '')
|
|
.replace(/\\u([\dA-Fa-f]{4})/g, (_, grp) => 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 = ({
|
|
compact = false,
|
|
isOwn,
|
|
showSystem,
|
|
showShipName,
|
|
useSystemsCache,
|
|
...char
|
|
}: CharacterCardProps) => {
|
|
const handleSelect = useCallback(() => {
|
|
emitMapEvent({
|
|
name: Commands.centerSystem,
|
|
data: char?.location?.solar_system_id?.toString(),
|
|
});
|
|
}, [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) */}
|
|
<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
|
|
className={clsx(
|
|
'w-full px-2 py-1 overflow-hidden gap-1',
|
|
compact ? 'grid items-center' : 'flex flex-col md:flex-row items-start',
|
|
)}
|
|
style={compact ? { gridTemplateColumns: 'auto 1fr auto', minWidth: 0 } : undefined}
|
|
>
|
|
{compact ? (
|
|
<img
|
|
src={`https://images.evetech.net/characters/${char.eve_id}/portrait`}
|
|
alt={`${char.name} portrait`}
|
|
style={{
|
|
width: '18px',
|
|
height: '18px',
|
|
// Remove circle shape for a square image:
|
|
borderRadius: 0,
|
|
marginRight: '4px',
|
|
flexShrink: 0,
|
|
// Slightly lighter than typical dark background:
|
|
border: '1px solid #2b2b2b',
|
|
}}
|
|
/>
|
|
) : (
|
|
<span
|
|
className={clsx(classes.EveIcon, classes.CharIcon, 'wd-bg-default')}
|
|
style={{
|
|
// The SCSS below ensures the image is square with a border.
|
|
backgroundImage: `url(https://images.evetech.net/characters/${char.eve_id}/portrait)`,
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{/*
|
|
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.
|
|
*/}
|
|
<div
|
|
className={clsx('overflow-hidden text-ellipsis', {
|
|
'text-left px-1': compact,
|
|
'flex-grow': !compact,
|
|
})}
|
|
style={{ minWidth: 0 }}
|
|
>
|
|
{/* This left border highlights "isOwn" in the same way as older code. */}
|
|
<div
|
|
className={clsx('overflow-hidden whitespace-nowrap', {
|
|
[classes.CardBorderLeftIsOwn]: isOwn,
|
|
})}
|
|
>
|
|
{compact ? compactLine : nonCompactLine1}
|
|
</div>
|
|
{/* Non-compact second line always shows shipName if available */}
|
|
{!compact && nonCompactLine2}
|
|
</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>
|
|
|
|
{/*
|
|
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>
|
|
);
|
|
};
|