From d782e698bc17c856d50ce9700087bf9b094cd8db Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sun, 20 Sep 2026 11:54:46 +0300 Subject: [PATCH] Art & tier list maker tweaks --- .../art/components/ArtGrid.module.css | 12 +++++ app/features/art/components/ArtGrid.tsx | 19 ++++++-- .../comp-analyzer/routes/comp-analyzer.tsx | 44 +++++++++++++------ .../tier-list-maker-utils.test.ts | 5 ++- .../tier-list-maker/tier-list-maker-utils.ts | 26 ++++++++--- locales/da/analyzer.json | 3 +- locales/da/art.json | 3 +- locales/de/analyzer.json | 3 +- locales/de/art.json | 3 +- locales/en/analyzer.json | 3 +- locales/en/art.json | 3 +- locales/es-ES/analyzer.json | 3 +- locales/es-ES/art.json | 3 +- locales/es-US/analyzer.json | 3 +- locales/es-US/art.json | 3 +- locales/fr-CA/analyzer.json | 3 +- locales/fr-CA/art.json | 3 +- locales/fr-EU/analyzer.json | 3 +- locales/fr-EU/art.json | 3 +- locales/he/analyzer.json | 3 +- locales/he/art.json | 3 +- locales/it/analyzer.json | 3 +- locales/it/art.json | 3 +- locales/ja/analyzer.json | 3 +- locales/ja/art.json | 3 +- locales/ko/analyzer.json | 3 +- locales/ko/art.json | 3 +- locales/nl/analyzer.json | 3 +- locales/nl/art.json | 3 +- locales/pl/analyzer.json | 3 +- locales/pl/art.json | 3 +- locales/pt-BR/analyzer.json | 3 +- locales/pt-BR/art.json | 3 +- locales/ru/analyzer.json | 3 +- locales/ru/art.json | 3 +- locales/zh/analyzer.json | 3 +- locales/zh/art.json | 3 +- 37 files changed, 146 insertions(+), 56 deletions(-) diff --git a/app/features/art/components/ArtGrid.module.css b/app/features/art/components/ArtGrid.module.css index 42321780f..0393bf380 100644 --- a/app/features/art/components/ArtGrid.module.css +++ b/app/features/art/components/ArtGrid.module.css @@ -1,3 +1,15 @@ +.thumbnailButton { + all: unset; + display: block; + cursor: pointer; + + /* thumbnails have square corners, a rounded ring would leave the art's corners outside it */ + &:focus-visible { + outline: var(--focus-ring); + outline-offset: 2px; + } +} + .thumbnail { cursor: pointer; transition: all 0.2s ease; diff --git a/app/features/art/components/ArtGrid.tsx b/app/features/art/components/ArtGrid.tsx index 401e2b6bf..6bfad46b9 100644 --- a/app/features/art/components/ArtGrid.tsx +++ b/app/features/art/components/ArtGrid.tsx @@ -211,20 +211,31 @@ function ImagePreview({ const { t } = useTranslation(["common", "art"]); const formatDistanceToNow = useFormatDistanceToNow(); - const img = ( - // biome-ignore lint/a11y/noStaticElementInteractions: Biome v2 migration + const image = ( preloadImage(art.url) : undefined} ref={imageRef} className={enablePreview ? styles.thumbnail : undefined} data-testid="art-image" /> ); + const img = onClick ? ( + + ) : ( + image + ); + if (!art.author && canEdit) { return (
diff --git a/app/features/comp-analyzer/routes/comp-analyzer.tsx b/app/features/comp-analyzer/routes/comp-analyzer.tsx index 2b08eda31..d4486cd2a 100644 --- a/app/features/comp-analyzer/routes/comp-analyzer.tsx +++ b/app/features/comp-analyzer/routes/comp-analyzer.tsx @@ -2,7 +2,11 @@ import { HardDriveDownload } from "lucide-react"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import type { MetaFunction, ShouldRevalidateFunction } from "react-router"; -import { SendouButton } from "~/components/elements/Button"; +import { + SendouButton, + type SendouButtonProps, +} from "~/components/elements/Button"; +import { SendouPopover } from "~/components/elements/Popover"; import { SendouSwitch } from "~/components/elements/Switch"; import { Main } from "~/components/Main"; import { Placeholder } from "~/components/Placeholder"; @@ -63,6 +67,7 @@ export default function CompAnalyzerShell() { } function CompAnalyzerPage() { + const { t } = useTranslation(["common", "analyzer"]); const [selectedWeaponIds, setSelectedWeaponIds] = useSelectedWeapons(); const [categorization, setCategorization] = useCategorization(); const [isGridCollapsed, setIsGridCollapsed] = useState( @@ -99,14 +104,18 @@ function CompAnalyzerPage() { onRemove={handleRemoveWeapon} onReorder={setSelectedWeaponIds} /> - {selectedWeaponIds.length >= MAX_WEAPONS ? ( -
+
+ {selectedWeaponIds.length >= MAX_WEAPONS ? ( -
- ) : null} + ) : ( + }> + {t("analyzer:comp.exportHint", { max: MAX_WEAPONS })} + + )} +
} - > - {t("common:imageExport.export")} - - } + trigger={} heading={t("common:imageExport.export")} filename="comp" settings={ @@ -212,3 +213,18 @@ function CompGraphicWithCombos({ /> ); } + +function ExportButton(props: SendouButtonProps) { + const { t } = useTranslation(["common"]); + + return ( + } + > + {t("common:imageExport.export")} + + ); +} diff --git a/app/features/tier-list-maker/tier-list-maker-utils.test.ts b/app/features/tier-list-maker/tier-list-maker-utils.test.ts index 0ffe7fa1d..74b4d0758 100644 --- a/app/features/tier-list-maker/tier-list-maker-utils.test.ts +++ b/app/features/tier-list-maker/tier-list-maker-utils.test.ts @@ -203,7 +203,10 @@ describe("isLightColor", () => { { hex: "#8b0000", expected: false, why: "dark red" }, { hex: "#4169e1", expected: false, why: "royal blue" }, { hex: "#000000", expected: false, why: "black" }, - { hex: "#fff", expected: false, why: "unsupported short form" }, + { hex: "#fff", expected: true, why: "short form white" }, + { hex: "#000", expected: false, why: "short form black" }, + { hex: "#ffd", expected: true, why: "short form light yellow" }, + { hex: "not-a-color", expected: false, why: "garbage" }, ])("$why -> $expected", ({ hex, expected }) => { expect(isLightColor(hex)).toBe(expected); }); diff --git a/app/features/tier-list-maker/tier-list-maker-utils.ts b/app/features/tier-list-maker/tier-list-maker-utils.ts index 887bdb55a..9d2d1f391 100644 --- a/app/features/tier-list-maker/tier-list-maker-utils.ts +++ b/app/features/tier-list-maker/tier-list-maker-utils.ts @@ -87,20 +87,36 @@ export function tierNameFontSize(name: string) { const LIGHT_COLOR_LUMINANCE_THRESHOLD = 0.5; -/** Whether dark text/icons read better on the given `#rrggbb` color than light ones. */ +/** Whether dark text/icons read better on the given `#rgb` or `#rrggbb` color than light ones. */ export function isLightColor(hex: string) { - const channels = hex.replace("#", "").match(/.{2}/g); - if (!channels || channels.length < 3) return false; + const channels = hexChannels(hex); + if (!channels) return false; - const [r, g, b] = channels.map((channel) => Number.parseInt(channel, 16)); + const [r, g, b] = channels; const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; return luminance > LIGHT_COLOR_LUMINANCE_THRESHOLD; } -/** Text color that stays readable on a tier's `#rrggbb` background color. */ +/** Text color that stays readable on a tier's `#rgb` or `#rrggbb` background color. */ export function tierTextColor(hex: string) { return isLightColor(hex) ? "var(--color-text-on-light)" : "var(--color-text-on-dark)"; } + +function hexChannels(hex: string) { + const digits = hex.replace("#", ""); + const channels = + digits.length === 3 + ? digits.split("").map((digit) => `${digit}${digit}`) + : digits.match(/.{2}/g); + + if (!channels || channels.length < 3) return null; + + const parsed = channels + .slice(0, 3) + .map((channel) => Number.parseInt(channel, 16)); + + return parsed.some(Number.isNaN) ? null : parsed; +} diff --git a/locales/da/analyzer.json b/locales/da/analyzer.json index ac183de84..9553b48c2 100644 --- a/locales/da/analyzer.json +++ b/locales/da/analyzer.json @@ -219,5 +219,6 @@ "comp.removeAll": "", "comp.addAll": "", "comp.singleWeaponCombos": "", - "comp.graphicSubtitle": "" + "comp.graphicSubtitle": "", + "comp.exportHint": "" } diff --git a/locales/da/art.json b/locales/da/art.json index b5349fc3d..d2bc3b231 100644 --- a/locales/da/art.json +++ b/locales/da/art.json @@ -23,5 +23,6 @@ "delete.title": "", "unlink.title": "", "noArtForTag": "", - "imageFailed": "" + "imageFailed": "", + "openImage": "" } diff --git a/locales/de/analyzer.json b/locales/de/analyzer.json index b62d5b6eb..b02045949 100644 --- a/locales/de/analyzer.json +++ b/locales/de/analyzer.json @@ -219,5 +219,6 @@ "comp.removeAll": "", "comp.addAll": "", "comp.singleWeaponCombos": "", - "comp.graphicSubtitle": "" + "comp.graphicSubtitle": "", + "comp.exportHint": "" } diff --git a/locales/de/art.json b/locales/de/art.json index 852565d8a..4d3d4b4d7 100644 --- a/locales/de/art.json +++ b/locales/de/art.json @@ -23,5 +23,6 @@ "delete.title": "", "unlink.title": "", "noArtForTag": "", - "imageFailed": "" + "imageFailed": "", + "openImage": "" } diff --git a/locales/en/analyzer.json b/locales/en/analyzer.json index 25cc78dd3..faf96e554 100644 --- a/locales/en/analyzer.json +++ b/locales/en/analyzer.json @@ -219,5 +219,6 @@ "comp.removeAll": "Remove all", "comp.addAll": "Add all", "comp.singleWeaponCombos": "Single weapon combos", - "comp.graphicSubtitle": "Composition Analyzer" + "comp.graphicSubtitle": "Composition Analyzer", + "comp.exportHint": "Pick {{max}} weapons to export an image" } diff --git a/locales/en/art.json b/locales/en/art.json index 655b8117d..6c931915a 100644 --- a/locales/en/art.json +++ b/locales/en/art.json @@ -23,5 +23,6 @@ "delete.title": "Are you sure you want to delete the art?", "unlink.title": "Are you sure you want to remove this art from your profile (only {{username}} can add it back)?", "noArtForTag": "No uploaded art found for #{{tag}}", - "imageFailed": "Image could not be loaded" + "imageFailed": "Image could not be loaded", + "openImage": "Open image" } diff --git a/locales/es-ES/analyzer.json b/locales/es-ES/analyzer.json index c59003008..10803581d 100644 --- a/locales/es-ES/analyzer.json +++ b/locales/es-ES/analyzer.json @@ -223,5 +223,6 @@ "comp.removeAll": "Eliminar todos", "comp.addAll": "Añadir todos", "comp.singleWeaponCombos": "Combos de un arma", - "comp.graphicSubtitle": "" + "comp.graphicSubtitle": "", + "comp.exportHint": "" } diff --git a/locales/es-ES/art.json b/locales/es-ES/art.json index a83b4598f..e335fbc27 100644 --- a/locales/es-ES/art.json +++ b/locales/es-ES/art.json @@ -24,5 +24,6 @@ "delete.title": "¿Seguro que quieres eliminar este arte?", "unlink.title": "¿Seguro que quieres desvincular este arte de tu perfil? (solo {{username}} puede volver a añadirlo)", "noArtForTag": "No se ha encontrado arte subido para #{{tag}}", - "imageFailed": "" + "imageFailed": "", + "openImage": "" } diff --git a/locales/es-US/analyzer.json b/locales/es-US/analyzer.json index 996ac0c53..36e3ca987 100644 --- a/locales/es-US/analyzer.json +++ b/locales/es-US/analyzer.json @@ -223,5 +223,6 @@ "comp.removeAll": "Eliminar todos", "comp.addAll": "Añadir todos", "comp.singleWeaponCombos": "Combos de un arma", - "comp.graphicSubtitle": "" + "comp.graphicSubtitle": "", + "comp.exportHint": "" } diff --git a/locales/es-US/art.json b/locales/es-US/art.json index f1756fdb5..56d215d0c 100644 --- a/locales/es-US/art.json +++ b/locales/es-US/art.json @@ -24,5 +24,6 @@ "delete.title": "¿Seguro que quieres eliminar este arte?", "unlink.title": "¿Seguro que quieres desvincular este arte de tu perfil? (solo {{username}} puede volver a añadirlo)", "noArtForTag": "No se ha encontrado arte subido para #{{tag}}", - "imageFailed": "" + "imageFailed": "", + "openImage": "" } diff --git a/locales/fr-CA/analyzer.json b/locales/fr-CA/analyzer.json index 12388804b..4268422ae 100644 --- a/locales/fr-CA/analyzer.json +++ b/locales/fr-CA/analyzer.json @@ -223,5 +223,6 @@ "comp.removeAll": "", "comp.addAll": "", "comp.singleWeaponCombos": "", - "comp.graphicSubtitle": "" + "comp.graphicSubtitle": "", + "comp.exportHint": "" } diff --git a/locales/fr-CA/art.json b/locales/fr-CA/art.json index 9f38265a1..543ad1b29 100644 --- a/locales/fr-CA/art.json +++ b/locales/fr-CA/art.json @@ -24,5 +24,6 @@ "delete.title": "", "unlink.title": "", "noArtForTag": "", - "imageFailed": "" + "imageFailed": "", + "openImage": "" } diff --git a/locales/fr-EU/analyzer.json b/locales/fr-EU/analyzer.json index 01c689472..bb5486f82 100644 --- a/locales/fr-EU/analyzer.json +++ b/locales/fr-EU/analyzer.json @@ -223,5 +223,6 @@ "comp.removeAll": "", "comp.addAll": "", "comp.singleWeaponCombos": "", - "comp.graphicSubtitle": "" + "comp.graphicSubtitle": "", + "comp.exportHint": "" } diff --git a/locales/fr-EU/art.json b/locales/fr-EU/art.json index 10328c217..8a73b5492 100644 --- a/locales/fr-EU/art.json +++ b/locales/fr-EU/art.json @@ -24,5 +24,6 @@ "delete.title": "Êtes vous sûre de vouloir supprimer cette œuvre?", "unlink.title": "Êtes vous sûre de vouloir supprimer cette œuvre de votre profil? (Seulement {{username}} pourra le remettre)", "noArtForTag": "", - "imageFailed": "" + "imageFailed": "", + "openImage": "" } diff --git a/locales/he/analyzer.json b/locales/he/analyzer.json index f5d919788..a6e86984f 100644 --- a/locales/he/analyzer.json +++ b/locales/he/analyzer.json @@ -223,5 +223,6 @@ "comp.removeAll": "הסר הכל", "comp.addAll": "הוסף הכל", "comp.singleWeaponCombos": "", - "comp.graphicSubtitle": "" + "comp.graphicSubtitle": "", + "comp.exportHint": "" } diff --git a/locales/he/art.json b/locales/he/art.json index 355a72d8e..294c25582 100644 --- a/locales/he/art.json +++ b/locales/he/art.json @@ -24,5 +24,6 @@ "delete.title": "האם אתה בטוח שאתה רוצה למחוק את הציור?", "unlink.title": "האם אתה בטוח שאתה רוצה להסיר את הציור הזה מהפרופיל שלך (רק {{username}} יוכל להוסיף אותו חזרה)?", "noArtForTag": "", - "imageFailed": "" + "imageFailed": "", + "openImage": "" } diff --git a/locales/it/analyzer.json b/locales/it/analyzer.json index 647ecedaf..4b8ec69e5 100644 --- a/locales/it/analyzer.json +++ b/locales/it/analyzer.json @@ -223,5 +223,6 @@ "comp.removeAll": "", "comp.addAll": "", "comp.singleWeaponCombos": "", - "comp.graphicSubtitle": "" + "comp.graphicSubtitle": "", + "comp.exportHint": "" } diff --git a/locales/it/art.json b/locales/it/art.json index bab969f02..13878e8f8 100644 --- a/locales/it/art.json +++ b/locales/it/art.json @@ -24,5 +24,6 @@ "delete.title": "", "unlink.title": "", "noArtForTag": "", - "imageFailed": "" + "imageFailed": "", + "openImage": "" } diff --git a/locales/ja/analyzer.json b/locales/ja/analyzer.json index a8ab48f47..ed6fd3491 100644 --- a/locales/ja/analyzer.json +++ b/locales/ja/analyzer.json @@ -211,5 +211,6 @@ "comp.removeAll": "", "comp.addAll": "", "comp.singleWeaponCombos": "", - "comp.graphicSubtitle": "" + "comp.graphicSubtitle": "", + "comp.exportHint": "" } diff --git a/locales/ja/art.json b/locales/ja/art.json index 707c457b4..d28910974 100644 --- a/locales/ja/art.json +++ b/locales/ja/art.json @@ -21,5 +21,6 @@ "delete.title": "本当にこの作品を削除しますか?", "unlink.title": "本当にこの作品をプロフィールから削除しますか?{{username}}しか再び追加することはできません。", "noArtForTag": "#{{tag}}の作品が見つかりませんでした。", - "imageFailed": "" + "imageFailed": "", + "openImage": "" } diff --git a/locales/ko/analyzer.json b/locales/ko/analyzer.json index 6ccc2aa71..5b2523043 100644 --- a/locales/ko/analyzer.json +++ b/locales/ko/analyzer.json @@ -211,5 +211,6 @@ "comp.removeAll": "", "comp.addAll": "", "comp.singleWeaponCombos": "", - "comp.graphicSubtitle": "" + "comp.graphicSubtitle": "", + "comp.exportHint": "" } diff --git a/locales/ko/art.json b/locales/ko/art.json index 654f7c885..fcaf0c8c3 100644 --- a/locales/ko/art.json +++ b/locales/ko/art.json @@ -21,5 +21,6 @@ "delete.title": "", "unlink.title": "", "noArtForTag": "", - "imageFailed": "" + "imageFailed": "", + "openImage": "" } diff --git a/locales/nl/analyzer.json b/locales/nl/analyzer.json index 559986a49..bbe72b670 100644 --- a/locales/nl/analyzer.json +++ b/locales/nl/analyzer.json @@ -219,5 +219,6 @@ "comp.removeAll": "", "comp.addAll": "", "comp.singleWeaponCombos": "", - "comp.graphicSubtitle": "" + "comp.graphicSubtitle": "", + "comp.exportHint": "" } diff --git a/locales/nl/art.json b/locales/nl/art.json index 852565d8a..4d3d4b4d7 100644 --- a/locales/nl/art.json +++ b/locales/nl/art.json @@ -23,5 +23,6 @@ "delete.title": "", "unlink.title": "", "noArtForTag": "", - "imageFailed": "" + "imageFailed": "", + "openImage": "" } diff --git a/locales/pl/analyzer.json b/locales/pl/analyzer.json index b9adfd20f..631a26eb4 100644 --- a/locales/pl/analyzer.json +++ b/locales/pl/analyzer.json @@ -227,5 +227,6 @@ "comp.removeAll": "", "comp.addAll": "", "comp.singleWeaponCombos": "", - "comp.graphicSubtitle": "" + "comp.graphicSubtitle": "", + "comp.exportHint": "" } diff --git a/locales/pl/art.json b/locales/pl/art.json index f801affdc..9929c2269 100644 --- a/locales/pl/art.json +++ b/locales/pl/art.json @@ -25,5 +25,6 @@ "delete.title": "", "unlink.title": "", "noArtForTag": "", - "imageFailed": "" + "imageFailed": "", + "openImage": "" } diff --git a/locales/pt-BR/analyzer.json b/locales/pt-BR/analyzer.json index fb72dce13..f5d7a26de 100644 --- a/locales/pt-BR/analyzer.json +++ b/locales/pt-BR/analyzer.json @@ -223,5 +223,6 @@ "comp.removeAll": "", "comp.addAll": "", "comp.singleWeaponCombos": "", - "comp.graphicSubtitle": "" + "comp.graphicSubtitle": "", + "comp.exportHint": "" } diff --git a/locales/pt-BR/art.json b/locales/pt-BR/art.json index 303786be2..4fd889008 100644 --- a/locales/pt-BR/art.json +++ b/locales/pt-BR/art.json @@ -24,5 +24,6 @@ "delete.title": "", "unlink.title": "", "noArtForTag": "", - "imageFailed": "" + "imageFailed": "", + "openImage": "" } diff --git a/locales/ru/analyzer.json b/locales/ru/analyzer.json index ebf3a53e4..b4e534cbf 100644 --- a/locales/ru/analyzer.json +++ b/locales/ru/analyzer.json @@ -227,5 +227,6 @@ "comp.removeAll": "", "comp.addAll": "", "comp.singleWeaponCombos": "", - "comp.graphicSubtitle": "" + "comp.graphicSubtitle": "", + "comp.exportHint": "" } diff --git a/locales/ru/art.json b/locales/ru/art.json index 08a0d70a6..a78245334 100644 --- a/locales/ru/art.json +++ b/locales/ru/art.json @@ -25,5 +25,6 @@ "delete.title": "Вы точно хотите удалить этот арт?", "unlink.title": "Вы точно хотите удалить этот арт с вашего профиля (только {{username}} может добавить его обратно)?", "noArtForTag": "", - "imageFailed": "" + "imageFailed": "", + "openImage": "" } diff --git a/locales/zh/analyzer.json b/locales/zh/analyzer.json index a29764961..91ef92042 100644 --- a/locales/zh/analyzer.json +++ b/locales/zh/analyzer.json @@ -214,5 +214,6 @@ "comp.removeAll": "移除所有", "comp.addAll": "添加所有", "comp.singleWeaponCombos": "", - "comp.graphicSubtitle": "" + "comp.graphicSubtitle": "", + "comp.exportHint": "" } diff --git a/locales/zh/art.json b/locales/zh/art.json index af36e7d61..fb1cf8c78 100644 --- a/locales/zh/art.json +++ b/locales/zh/art.json @@ -22,5 +22,6 @@ "delete.title": "您确定要删除这件作品吗?", "unlink.title": "您确定要将这件作品从个人资料中移除吗(只有 {{username}} 可以重新添加)?", "noArtForTag": "未能找到包含 #{{tag}} 的作品。", - "imageFailed": "" + "imageFailed": "", + "openImage": "" }