feat : update
CI / Verify (push) Successful in 1m28s
CI / Build immutable images and deploy (push) Successful in 2m5s

This commit is contained in:
2026-09-05 18:15:06 +07:00 Unverified
parent e66b5a3a0d
commit 5aa0ae80c1
42 changed files with 4613 additions and 67 deletions
+54 -14
View File
@@ -127,10 +127,12 @@ import {
targetStatDefaults,
targetStatIconName,
targetStatOptions,
statPriorityColorClass,
} from "@/lib/guides/stats";
import { elementIconFilter } from "@/lib/catalog/element";
import { supportsWeaponType } from "@/lib/catalog/lunaris";
import { rarityGradientClass } from "@/lib/catalog/rarity";
import { isCustomTravelerKey } from "@/lib/catalog/travelers";
import {
applyPresetFields,
applyTeamFields,
@@ -170,6 +172,7 @@ import { cn } from "@/lib/utils";
type CatalogItem = {
key: string;
name: string;
aliases?: string[];
imageUrl: string;
rarity?: number | null;
weaponType?: string | null;
@@ -200,7 +203,7 @@ type Guide = {
type WeaponValue = {
id: string;
weaponKey: string;
refinement: number;
refinement: string;
overallPercent: string | null;
personalPercent: string | null;
note: string | null;
@@ -393,6 +396,12 @@ function CatalogPicker({
onValueChange={(next) => onChange(next?.key ?? "")}
itemToStringLabel={(item) => item.name}
itemToStringValue={(item) => item.key}
filter={(item, query) =>
[item.name, ...(item.aliases ?? [])]
.join(" ")
.toLocaleLowerCase("th")
.includes(query.trim().toLocaleLowerCase("th"))
}
autoHighlight
>
<ComboboxInput
@@ -437,6 +446,7 @@ function StatSelect({
max,
onChange,
elementIconUrls,
priorityColors = false,
}: {
label: string;
options: readonly string[];
@@ -444,6 +454,7 @@ function StatSelect({
max: number;
onChange: (value: string[]) => void;
elementIconUrls: Record<string, string>;
priorityColors?: boolean;
}) {
return (
<Field>
@@ -457,7 +468,15 @@ function StatSelect({
>
<SelectTrigger className="w-full">
<StatIcons values={value} elementIconUrls={elementIconUrls} />
<SelectValue placeholder="Select recommended stats…" />
<SelectValue placeholder="Select recommended stats…">
{priorityColors && value.length
? () => value.map((selected, index) => (
<span key={selected} className={statPriorityColorClass(index)}>
{selected}{index < value.length - 1 ? "," : ""}
</span>
))
: undefined}
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectGroup>
@@ -466,6 +485,9 @@ function StatSelect({
key={option}
value={option}
disabled={value.length >= max && !value.includes(option)}
className={priorityColors && value.includes(option)
? statPriorityColorClass(value.indexOf(option))
: undefined}
>
<StatIcons values={[option]} elementIconUrls={elementIconUrls} />
{option}
@@ -1102,16 +1124,14 @@ function WeaponEditor({ guide, weapons, characters, initial }: EditorBase) {
item.clientId === row.clientId
? {
...item,
refinement: Number(
next.slice(1),
),
refinement: next.slice(1),
}
: item,
),
);
}}
>
{[1, 2, 3, 4, 5].map((refinement) => (
{["1", "2", "3", "4", "5", "1-5"].map((refinement) => (
<ToggleGroupItem
className={SELECTED_TOGGLE_CLASS}
key={refinement}
@@ -1246,7 +1266,7 @@ function WeaponEditor({ guide, weapons, characters, initial }: EditorBase) {
{
clientId: createClientId("weapon"),
weaponKey: "",
refinement: 1,
refinement: "1",
overallPercent: "",
personalPercent: "",
note: "",
@@ -1766,6 +1786,7 @@ function ArtifactEditor({
max={substatOptions.length}
onChange={setSubstats}
elementIconUrls={elementIconUrls}
priorityColors
/>
<FieldSet>
<FieldLegend>Target Stats</FieldLegend>
@@ -2257,9 +2278,9 @@ function TeamEditor({
const [searchQuery, setSearchQuery] = useState("");
const [searchTarget, setSearchTarget] = useState<TeamSearchTarget>("all");
const filteredTeams = useMemo(() => {
const characterNames = new Map(characters.map((item) => [item.key, item.name]));
const weaponNames = new Map(weapons.map((item) => [item.key, item.name]));
const artifactNames = new Map(artifacts.map((item) => [item.key, item.name]));
const characterNames = new Map(characters.map((item) => [item.key, [item.name, ...(item.aliases ?? [])]]));
const weaponNames = new Map(weapons.map((item) => [item.key, [item.name, ...(item.aliases ?? [])]]));
const artifactNames = new Map(artifacts.map((item) => [item.key, [item.name, ...(item.aliases ?? [])]]));
return teams
.map((team, teamIndex) => ({ team, teamIndex }))
@@ -2267,18 +2288,18 @@ function TeamEditor({
team: [team.title],
character: team.members.flatMap((member) => {
const name = characterNames.get(member.characterKey);
return name ? [name] : [];
return name ?? [];
}),
weapon: team.members.flatMap((member) => {
const name = member.weaponKey
? weaponNames.get(member.weaponKey)
: undefined;
return name ? [name] : [];
return name ?? [];
}),
artifact: team.members.flatMap((member) =>
[member.artifactKey, member.secondArtifactKey].flatMap((key) => {
const name = key ? artifactNames.get(key) : undefined;
return name ? [name] : [];
return name ?? [];
}),
),
}, searchQuery, searchTarget));
@@ -2796,6 +2817,9 @@ function TeamEditor({
weapon.weaponType,
),
);
const selectedWeapon = compatibleWeapons.find(
(weapon) => weapon.key === member.weaponKey,
);
return (
<SortableTeamMember
key={`${team.clientId}-member-${memberIndex}`}
@@ -2814,7 +2838,11 @@ function TeamEditor({
</CardDescription>
<CardAction className="flex items-center gap-1">
{character ? (
<Avatar>
<Avatar className={rarityGradientClass(
isCustomTravelerKey(character.key)
? 5
: character.rarity,
)}>
<AvatarImage
src={character.imageUrl}
alt={character.name}
@@ -2826,6 +2854,18 @@ function TeamEditor({
</AvatarFallback>
</Avatar>
) : null}
{selectedWeapon ? (
<Avatar className={rarityGradientClass(selectedWeapon.rarity)}>
<AvatarImage
src={selectedWeapon.imageUrl}
alt={selectedWeapon.name}
className="object-contain"
/>
<AvatarFallback>
{selectedWeapon.name.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
) : null}
{memberDragHandle}
</CardAction>
</CardHeader>