Files
Nitro-V3/src/common/UserIdentityView.tsx
T
2026-05-07 20:49:50 +02:00

120 lines
4.3 KiB
TypeScript

import { FC, Fragment, ReactNode, useMemo } from 'react';
import { GetNickIconUrl } from '../assets/images/user_custom/nick_icons';
import { PREFIX_EFFECT_KEYFRAMES, getPrefixEffectStyle, getPrefixFontStyle, parseFontSegments, parsePrefixColors } from '../api';
const renderInlineFontMarkup = (text: string): ReactNode =>
{
if(!text) return text;
if(text.indexOf('<font') === -1) return text;
const segments = parseFontSegments(text);
if(!segments.length) return text;
return segments.map((segment, index) =>
{
if(segment.color) return <span key={ index } style={ { color: segment.color } }>{ segment.text }</span>;
return <Fragment key={ index }>{ segment.text }</Fragment>;
});
};
interface UserIdentityViewProps
{
username: string;
nickIcon?: string;
prefixText?: string;
prefixColor?: string;
prefixIcon?: string;
prefixEffect?: string;
prefixFont?: string;
displayOrder?: string;
showColon?: boolean;
className?: string;
iconClassName?: string;
nameClassName?: string;
prefixClassName?: string;
}
const sanitizeDisplayOrder = (displayOrder?: string) =>
{
const fallback = [ 'icon', 'prefix', 'name' ];
if(!displayOrder?.length) return fallback;
const parts = displayOrder.toLowerCase().split('-');
if(parts.length !== 3) return fallback;
const unique = new Set(parts);
if(unique.size !== 3) return fallback;
if(parts.some(part => !fallback.includes(part))) return fallback;
return parts;
};
export const UserIdentityView: FC<UserIdentityViewProps> = ({
username = '',
nickIcon = '',
prefixText = '',
prefixColor = '',
prefixIcon = '',
prefixEffect = '',
prefixFont = '',
displayOrder = 'icon-prefix-name',
showColon = false,
className = '',
iconClassName = 'inline-block w-auto h-auto align-[-1px]',
nameClassName = 'username font-bold',
prefixClassName = ''
}) =>
{
const nickIconUrl = GetNickIconUrl(nickIcon);
const prefixColors = useMemo(() => parsePrefixColors(prefixText, prefixColor), [ prefixText, prefixColor ]);
const hasMultiColor = (prefixColors.length > 1) && (new Set(prefixColors).size > 1);
const prefixStyle = getPrefixEffectStyle(prefixEffect, prefixColors[0] || '#FFFFFF');
const prefixFontStyle = getPrefixFontStyle(prefixFont);
const displayParts = sanitizeDisplayOrder(displayOrder);
const parts = displayParts.map(part =>
{
switch(part)
{
case 'icon':
if(!nickIconUrl) return null;
return <img key="identity-icon" className={ `${ iconClassName } mr-1` } src={ nickIconUrl } alt="" />;
case 'prefix':
if(!prefixText?.length) return null;
return (
<span key="identity-prefix" className={ `prefix inline-block whitespace-nowrap font-bold mr-1 ${ prefixClassName }` } style={ { ...prefixFontStyle, ...prefixStyle } }>
{ prefixIcon && <span className="mr-0.5 text-[13px] leading-none">{ prefixIcon }</span> }
<span style={ hasMultiColor ? { ...prefixFontStyle, ...prefixStyle } : { ...prefixFontStyle, ...prefixStyle, color: prefixColors[0] || '#FFFFFF' } }>
{'{'}
{ hasMultiColor
? [ ...prefixText ].map((char, index) => (
<span key={ index } style={ { ...prefixFontStyle, color: prefixColors[index] || prefixColors[prefixColors.length - 1], ...getPrefixEffectStyle(prefixEffect, prefixColors[index]) } }>{ char }</span>
))
: prefixText }
{'}'}
</span>
</span>
);
case 'name':
return <span key="identity-name" className={ `${ nameClassName } whitespace-nowrap` }>{ renderInlineFontMarkup(username) }{ showColon ? ':' : '' }{ showColon ? ' ' : '' }</span>;
default:
return null;
}
}).filter(Boolean);
return (
<span className={ `inline-flex items-center whitespace-nowrap align-middle ${ className }` }>
{ !!prefixEffect && <style>{ PREFIX_EFFECT_KEYFRAMES }</style> }
{ parts }
</span>
);
};