feat : able to rotate img for mat card
CI / Verify (push) Successful in 1m46s
CI / Build immutable images and deploy (push) Successful in 2m3s

This commit is contained in:
2026-09-19 20:20:02 +07:00 Unverified
parent 26517ea5fd
commit 7c10b2c995
12 changed files with 2962 additions and 41 deletions
+23 -6
View File
@@ -31,24 +31,32 @@ describe("image export controls", () => {
expect(html).not.toContain("เงื่อนไข 1");
});
it("always offers both export modes even without notes", () => {
it("shows one export action when both note modes are available", () => {
const html = renderToStaticMarkup(<GuideImageExport fileName="guide.png">No notes</GuideImageExport>);
expect(html).toContain("ส่งออกภาพ");
expect(html).toContain("ส่งออกภาพโดยไม่รวมโน้ต");
expect(html).toContain("ไทย");
expect(html).toContain("English");
expect(html).toContain('data-language-flag="th"');
expect(html).toContain('data-language-flag="en"');
expect(html.match(/<button\b/gu)).toHaveLength(4);
expect(html.match(/<button\b/gu)).toHaveLength(3);
});
it("shows only one export action when no-note export is disabled", () => {
const html = renderToStaticMarkup(<GuideImageExport fileName="guide.png" allowWithoutNotes={false}>Content</GuideImageExport>);
expect(html).toContain("ส่งออกภาพ");
expect(html).not.toContain("ส่งออกภาพโดยไม่รวมโน้ต");
expect(html.match(/<button\b/gu)).toHaveLength(3);
});
it("can show note modes inline inside an existing dialog", () => {
const html = renderToStaticMarkup(
<GuideImageExport fileName="guide.png" modeSelection="inline">
Content
</GuideImageExport>,
);
expect(html).toContain("ส่งออกภาพโดยไม่รวมโน้ต");
expect(html.match(/<button\b/gu)).toHaveLength(4);
});
it("hides the persistent language chooser when language is prompted on export", () => {
const html = renderToStaticMarkup(
<GuideImageExport
@@ -65,9 +73,9 @@ describe("image export controls", () => {
expect(html.match(/<button\b/gu)).toHaveLength(1);
});
it("disables both modes when an export selection is empty", () => {
it("disables export when an export selection is empty", () => {
const html = renderToStaticMarkup(<GuideImageExport fileName="guide.png" disabled>No sections</GuideImageExport>);
expect(html.match(/\sdisabled=""/gu)).toHaveLength(2);
expect(html.match(/\sdisabled=""/gu)).toHaveLength(1);
});
it.each(["English note", "โน้ตภาษาไทย"])("marks constellation notes for omission regardless of language: %s", (note) => {
@@ -102,6 +110,7 @@ describe("material poster text", () => {
artworkOffsetX: 0,
artworkOffsetY: 0,
artworkScale: 100,
artworkRotation: 0,
topColors: ["#000000", "#000000"] as [string, string],
bottomColors: ["#000000", "#000000"] as [string, string],
};
@@ -130,4 +139,12 @@ describe("material poster text", () => {
expect(html).toContain('sizes="3200px"');
expect(html).not.toContain(`src="${characterArtUrl}"`);
});
it("rotates the character artwork and bleeding layers together", () => {
const html = renderToStaticMarkup(
<MaterialPoster {...props} characterArtUrl="/raiden.png" artworkRotation={45} />,
);
expect(html).toContain("rotate(45deg)");
});
});
+1
View File
@@ -168,6 +168,7 @@ export async function updateGuideBasics(guideId: string, input: unknown, details
...(data.posterArtOffsetX !== undefined ? { posterArtOffsetX: Math.round(data.posterArtOffsetX) } : {}),
...(data.posterArtOffsetY !== undefined ? { posterArtOffsetY: Math.round(data.posterArtOffsetY) } : {}),
...(data.posterArtScale !== undefined ? { posterArtScale: Math.round(data.posterArtScale) } : {}),
...(data.posterArtRotation !== undefined ? { posterArtRotation: Math.round(data.posterArtRotation) } : {}),
...(data.posterTopColors !== undefined ? { posterTopColors: data.posterTopColors } : {}),
...(data.posterBottomColors !== undefined ? { posterBottomColors: data.posterBottomColors } : {}),
}).where(eq(guides.id, guideId));
+1
View File
@@ -39,6 +39,7 @@ export const updateGuideSchema = z.strictObject({
posterArtOffsetX: z.number().finite().min(-1000).max(1000).optional(),
posterArtOffsetY: z.number().finite().min(-1000).max(1000).optional(),
posterArtScale: z.number().finite().min(50).max(500).optional(),
posterArtRotation: z.number().finite().min(-180).max(180).optional(),
posterTopColors: z.tuple([z.string().regex(/^#[0-9a-fA-F]{6}$/u), z.string().regex(/^#[0-9a-fA-F]{6}$/u)]).optional(),
posterBottomColors: z.tuple([z.string().regex(/^#[0-9a-fA-F]{6}$/u), z.string().regex(/^#[0-9a-fA-F]{6}$/u)]).optional(),
posterBleeding: z.array(z.strictObject({