diff --git a/app/api/guides/[guideId]/artifacts/image/route.tsx b/app/api/guides/[guideId]/artifacts/image/route.tsx index 3144fa5..bea4e4e 100644 --- a/app/api/guides/[guideId]/artifacts/image/route.tsx +++ b/app/api/guides/[guideId]/artifacts/image/route.tsx @@ -1,4 +1,6 @@ /* eslint-disable @next/next/no-img-element */ +import { join } from "node:path"; + import { ImageResponse } from "next/og"; import sharp from "sharp"; @@ -53,6 +55,14 @@ async function pngDataUrl(url: string): Promise { } } +async function localPngDataUrl(fileName: string): Promise { + const png = await sharp(join(process.cwd(), "public", "icon", fileName)) + .resize(96, 96, { fit: "contain" }) + .png() + .toBuffer(); + return `data:image/png;base64,${png.toString("base64")}`; +} + export async function GET( _request: Request, context: RouteContext<"/api/guides/[guideId]/artifacts/image">, @@ -61,7 +71,7 @@ export async function GET( const guide = await getPublicArtifactExport(guideId); if (!guide) return new Response("Guide not found", { status: 404 }); - const [fonts, groupAssets] = await Promise.all([ + const [fonts, groupAssets, statIconAssets] = await Promise.all([ anuphanFonts, Promise.all( guide.groups.map((group) => @@ -73,6 +83,11 @@ export async function GET( ), ), ), + Promise.all([ + localPngDataUrl("Icon_Sands_of_Eon.webp"), + localPngDataUrl("Icon_Goblet_of_Eonothem.webp"), + localPngDataUrl("Icon_Circlet_of_Logos.webp"), + ]), ]); const profile = guide.profile; @@ -81,7 +96,7 @@ export async function GET( ? 60 + profile.targets.length * 30 + (profile.talents.length ? 58 : 0) + (profile.note ? Math.max(48, Math.ceil(profile.note.length / 58) * 24) : 0) : 0; const rightHeight = profile - ? 194 + (profile.substats.length ? 76 : 0) + (detailHeight > 60 ? detailHeight + 16 : 0) + 32 + ? 242 + (profile.substats.length ? 76 : 0) + (detailHeight > 60 ? detailHeight + 16 : 0) + 32 : 0; const height = Math.max(520, 190 + Math.max(leftHeight, rightHeight)); const talents = normalizeTalentPriorities(profile?.talents ?? []); @@ -162,11 +177,12 @@ export async function GET(
{[ - { label: "Sands", values: profile.sands }, - { label: "Goblet", values: profile.goblet }, - { label: "Circlet", values: profile.circlet }, + { label: "Sands", values: profile.sands, icon: statIconAssets[0] }, + { label: "Goblet", values: profile.goblet, icon: statIconAssets[1] }, + { label: "Circlet", values: profile.circlet, icon: statIconAssets[2] }, ].map((stat) => ( -
+
+
{stat.label}
diff --git a/components/admin/guide-section-editor.tsx b/components/admin/guide-section-editor.tsx index 61760cb..9fac8fa 100644 --- a/components/admin/guide-section-editor.tsx +++ b/components/admin/guide-section-editor.tsx @@ -29,6 +29,7 @@ import { } from "@/app/admin/actions"; import { AutosaveStatus } from "@/components/admin/autosave-status"; import { MediaUploader } from "@/components/admin/media-uploader"; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { @@ -348,7 +349,7 @@ function ConstellationEditor({ guide, initial, catalogConstellations }: EditorBa Constellationsชื่อ รายละเอียด และไอคอนมาจาก Lunaris อัตโนมัติ กรอกเฉพาะเปอร์เซ็นต์เทียบกับ C0 = 100% - {rows.map((row, index) => {row.iconUrl ? : null}C{row.level} · {row.name}{row.description}Cumulative % setRows(rows.map((item, itemIndex) => itemIndex === index ? { ...item, cumulativePercent: event.target.value } : item))} /> setRows(rows.map((item, itemIndex) => itemIndex === index ? { ...item, isGreat } : item))} aria-label={`Mark C${row.level} percentage as great`} className="data-[state=on]:text-warning">Great %)} + {rows.map((row, index) => {row.iconUrl ? : null}C{row.level} · {row.name}{row.description}Cumulative % setRows(rows.map((item, itemIndex) => itemIndex === index ? { ...item, cumulativePercent: event.target.value } : item))} /> setRows(rows.map((item, itemIndex) => itemIndex === index ? { ...item, isGreat } : item))} aria-label={`Mark C${row.level} percentage as great`} className={cn(SELECTED_TOGGLE_CLASS, "data-[state=on]:text-warning")}>Great %)} @@ -363,8 +364,34 @@ function TeamEditor({ guide, characters, weapons, artifacts, initial }: EditorBa return ( Team Calcใส่ความเสียหายของตัวละคร 4 ตัว ระบบจะแสดงสัดส่วนและ Total DPS และบันทึกอัตโนมัติ - - {teams.map((team, teamIndex) => { const total = team.members.reduce((sum, member) => sum + (Number(member.damage) || 0), 0); const dps = total / (Number(team.rotationSeconds) || 1); return Team {teamIndex + 1}Damage {total.toLocaleString()} • DPS {dps.toFixed(0)} setTeams(moveItem(teams, from, to))} remove={() => setTeams(teams.filter((_, index) => index !== teamIndex))} />ชื่อ Team setTeams(teams.map((item, index) => index === teamIndex ? { ...item, title: event.target.value } : item))} />
{team.members.map((member, memberIndex) => { const character = characters.find((item) => item.key === member.characterKey); const compatibleWeapons = weapons.filter((weapon) => supportsWeaponType(character?.weaponType, weapon.weaponType)); return ตัวละคร {memberIndex + 1}{total ? `${((Number(member.damage) || 0) / total * 100).toFixed(2)}% ของ Team` : "0% ของ Team"}{character ? {character.name.slice(0, 2).toUpperCase()} : null}ตัวละคร setTeams(teams.map((item, index) => index === teamIndex ? { ...item, members: item.members.map((candidate, inner) => inner === memberIndex ? { ...candidate, characterKey, weaponKey: null, artifactKey: null, constellation: 0, refinement: 1 } : candidate) } : item))} />Damage setTeams(teams.map((item, index) => index === teamIndex ? { ...item, members: item.members.map((candidate, inner) => inner === memberIndex ? { ...candidate, damage: event.target.value } : candidate) } : item))} />Weapon setTeams(teams.map((item, index) => index === teamIndex ? { ...item, members: item.members.map((candidate, inner) => inner === memberIndex ? { ...candidate, weaponKey: weaponKey || null } : candidate) } : item))} />Refinement { const refinement = selected[0]; if (refinement) setTeams(teams.map((item, index) => index === teamIndex ? { ...item, members: item.members.map((candidate, inner) => inner === memberIndex ? { ...candidate, refinement: Number(refinement.slice(1)) } : candidate) } : item)); }}>{[1, 2, 3, 4, 5].map((refinement) => R{refinement})}Artifact set setTeams(teams.map((item, index) => index === teamIndex ? { ...item, members: item.members.map((candidate, inner) => inner === memberIndex ? { ...candidate, artifactKey: artifactKey || null } : candidate) } : item))} />Constellation { const constellation = selected[0]; if (constellation) setTeams(teams.map((item, index) => index === teamIndex ? { ...item, members: item.members.map((candidate, inner) => inner === memberIndex ? { ...candidate, constellation: Number(constellation.slice(1)) } : candidate) } : item)); }}>{[0, 1, 2, 3, 4, 5, 6].map((constellation) => C{constellation})}; })}
Rotation (seconds) setTeams(teams.map((item, index) => index === teamIndex ? { ...item, rotationSeconds: event.target.value } : item))} />Combo