feat : add elemental ickn
This commit is contained in:
@@ -116,11 +116,13 @@ import {
|
||||
} from "@/lib/guides/schemas";
|
||||
import {
|
||||
normalizeTargetStat,
|
||||
recommendedStatElement,
|
||||
recommendedStatIconName,
|
||||
targetStatDefaults,
|
||||
targetStatIconName,
|
||||
targetStatOptions,
|
||||
} from "@/lib/guides/stats";
|
||||
import { elementIconFilter } from "@/lib/catalog/element";
|
||||
import { supportsWeaponType } from "@/lib/catalog/lunaris";
|
||||
import { rarityGradientClass } from "@/lib/catalog/rarity";
|
||||
import {
|
||||
@@ -158,6 +160,8 @@ type CatalogItem = {
|
||||
imageUrl: string;
|
||||
rarity?: number | null;
|
||||
weaponType?: string | null;
|
||||
element?: string | null;
|
||||
elementIconUrl?: string | null;
|
||||
};
|
||||
const teamMemberApplyOptions = [
|
||||
["characterKey", "Character"],
|
||||
@@ -391,12 +395,14 @@ function StatSelect({
|
||||
value,
|
||||
max,
|
||||
onChange,
|
||||
elementIconUrls,
|
||||
}: {
|
||||
label: string;
|
||||
options: readonly string[];
|
||||
value: string[];
|
||||
max: number;
|
||||
onChange: (value: string[]) => void;
|
||||
elementIconUrls: Record<string, string>;
|
||||
}) {
|
||||
return (
|
||||
<Field>
|
||||
@@ -409,7 +415,7 @@ function StatSelect({
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<StatIcons values={value} />
|
||||
<StatIcons values={value} elementIconUrls={elementIconUrls} />
|
||||
<SelectValue placeholder="Select recommended stats…" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -420,7 +426,7 @@ function StatSelect({
|
||||
value={option}
|
||||
disabled={value.length >= max && !value.includes(option)}
|
||||
>
|
||||
<StatIcons values={[option]} />
|
||||
<StatIcons values={[option]} elementIconUrls={elementIconUrls} />
|
||||
{option}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -432,28 +438,53 @@ function StatSelect({
|
||||
);
|
||||
}
|
||||
|
||||
function StatIcons({ values }: { values: readonly string[] }) {
|
||||
const iconNames = values.flatMap((value) => {
|
||||
function StatIcons({
|
||||
values,
|
||||
elementIconUrls = {},
|
||||
}: {
|
||||
values: readonly string[];
|
||||
elementIconUrls?: Record<string, string>;
|
||||
}) {
|
||||
const icons = values.flatMap((value) => {
|
||||
const element = recommendedStatElement(value);
|
||||
if (element && elementIconUrls[element]) {
|
||||
return [{ name: element, src: elementIconUrls[element], element }];
|
||||
}
|
||||
const targetIcon = targetStatIconName(value);
|
||||
if (targetIcon) return [targetIcon];
|
||||
if (targetIcon) {
|
||||
return [{ name: targetIcon, src: `/icon/stats/${targetIcon}.webp` }];
|
||||
}
|
||||
const recommendedIcon = recommendedStatIconName(value);
|
||||
return recommendedIcon ? [recommendedIcon] : [];
|
||||
return recommendedIcon
|
||||
? [{
|
||||
name: recommendedIcon,
|
||||
src: `/icon/stats/${recommendedIcon}.${recommendedIcon === "elemental-bonus" ? "svg" : "webp"}`,
|
||||
}]
|
||||
: [];
|
||||
});
|
||||
if (!iconNames.length) return null;
|
||||
if (!icons.length) return null;
|
||||
return (
|
||||
<span className="flex shrink-0 items-center gap-0.5" aria-hidden="true">
|
||||
{iconNames.map((iconName) => (
|
||||
<StatIcon key={iconName} iconName={iconName} />
|
||||
{icons.map((icon) => (
|
||||
<StatIcon key={icon.name} {...icon} />
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function StatIcon({ iconName }: { iconName: string }) {
|
||||
function StatIcon({
|
||||
name,
|
||||
src,
|
||||
element,
|
||||
}: {
|
||||
name: string;
|
||||
src: string;
|
||||
element?: string;
|
||||
}) {
|
||||
const [hasError, setHasError] = useState(false);
|
||||
if (hasError) {
|
||||
const PlaceholderIcon =
|
||||
iconName === "healing-bonus" ? HeartPulseIcon : SparklesIcon;
|
||||
name === "healing-bonus" ? HeartPulseIcon : SparklesIcon;
|
||||
return (
|
||||
<PlaceholderIcon
|
||||
className="size-5 text-muted-foreground"
|
||||
@@ -463,11 +494,12 @@ function StatIcon({ iconName }: { iconName: string }) {
|
||||
}
|
||||
return (
|
||||
<Image
|
||||
src={`/icon/stats/${iconName}.webp`}
|
||||
src={src}
|
||||
alt=""
|
||||
width={20}
|
||||
height={20}
|
||||
className="size-5 object-contain brightness-0 invert"
|
||||
className={cn("size-5 object-contain", !element && "brightness-0 invert")}
|
||||
style={element ? { filter: elementIconFilter(element) } : undefined}
|
||||
onError={() => setHasError(true)}
|
||||
/>
|
||||
);
|
||||
@@ -1130,8 +1162,16 @@ function ArtifactEditor({
|
||||
artifacts,
|
||||
initial,
|
||||
catalogTalents,
|
||||
characters,
|
||||
}: EditorBase) {
|
||||
const profile = initial.artifactProfile;
|
||||
const elementIconUrls = Object.fromEntries(
|
||||
characters.flatMap((character) =>
|
||||
character.element && character.elementIconUrl
|
||||
? [[character.element, character.elementIconUrl] as const]
|
||||
: [],
|
||||
),
|
||||
);
|
||||
const artifactSensors = useSensors(
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
@@ -1577,6 +1617,7 @@ function ArtifactEditor({
|
||||
value={sands}
|
||||
max={3}
|
||||
onChange={setSands}
|
||||
elementIconUrls={elementIconUrls}
|
||||
/>
|
||||
<StatSelect
|
||||
label="Goblet / ถ้วย"
|
||||
@@ -1584,6 +1625,7 @@ function ArtifactEditor({
|
||||
value={goblet}
|
||||
max={3}
|
||||
onChange={setGoblet}
|
||||
elementIconUrls={elementIconUrls}
|
||||
/>
|
||||
<StatSelect
|
||||
label="Circlet / หมวก"
|
||||
@@ -1591,6 +1633,7 @@ function ArtifactEditor({
|
||||
value={circlet}
|
||||
max={3}
|
||||
onChange={setCirclet}
|
||||
elementIconUrls={elementIconUrls}
|
||||
/>
|
||||
</div>
|
||||
<StatSelect
|
||||
@@ -1599,6 +1642,7 @@ function ArtifactEditor({
|
||||
value={substats}
|
||||
max={substatOptions.length}
|
||||
onChange={setSubstats}
|
||||
elementIconUrls={elementIconUrls}
|
||||
/>
|
||||
<FieldSet>
|
||||
<FieldLegend>Target Stats</FieldLegend>
|
||||
|
||||
Reference in New Issue
Block a user