mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-25 15:56:19 -05:00
Add support for badge hue rotate
This commit is contained in:
parent
3378320f06
commit
94d5e8c137
41
app/components/Badge.tsx
Normal file
41
app/components/Badge.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import type { Badge as BadgeDBType } from "~/db/types";
|
||||
import { badgeUrl } from "~/utils/urls";
|
||||
import { Image } from "./Image";
|
||||
|
||||
export function Badge({
|
||||
badge,
|
||||
onClick,
|
||||
isAnimated,
|
||||
size,
|
||||
}: {
|
||||
badge: Pick<BadgeDBType, "displayName" | "code" | "hue">;
|
||||
onClick?: () => void;
|
||||
isAnimated: boolean;
|
||||
size: number;
|
||||
}) {
|
||||
const commonProps = {
|
||||
title: badge.displayName,
|
||||
onClick,
|
||||
width: size,
|
||||
height: size,
|
||||
style: badge.hue ? { filter: `hue-rotate(${badge.hue}deg)` } : undefined,
|
||||
};
|
||||
|
||||
if (isAnimated) {
|
||||
return (
|
||||
<img
|
||||
src={badgeUrl({ code: badge.code, extension: "gif" })}
|
||||
alt={badge.displayName}
|
||||
{...commonProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Image
|
||||
path={badgeUrl({ code: badge.code })}
|
||||
alt={badge.displayName}
|
||||
{...commonProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ export function Image({
|
|||
className,
|
||||
width,
|
||||
height,
|
||||
style,
|
||||
}: {
|
||||
path: string;
|
||||
alt: string;
|
||||
|
|
@ -12,6 +13,7 @@ export function Image({
|
|||
className?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
style?: React.CSSProperties;
|
||||
}) {
|
||||
return (
|
||||
<picture title={title}>
|
||||
|
|
@ -21,6 +23,7 @@ export function Image({
|
|||
className={className}
|
||||
width={width}
|
||||
height={height}
|
||||
style={style}
|
||||
/>
|
||||
<img
|
||||
alt={alt}
|
||||
|
|
@ -28,6 +31,7 @@ export function Image({
|
|||
className={className}
|
||||
width={width}
|
||||
height={height}
|
||||
style={style}
|
||||
/>
|
||||
</picture>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,12 @@ import { sql } from "../sql";
|
|||
import type { Badge, User } from "../types";
|
||||
|
||||
const countsByUserIdStm = sql.prepare(`
|
||||
select "Badge"."code", "Badge"."displayName", "Badge"."id", count("BadgeOwner"."badgeId") as count
|
||||
select
|
||||
"Badge"."code",
|
||||
"Badge"."displayName",
|
||||
"Badge"."id",
|
||||
"Badge"."hue",
|
||||
count("BadgeOwner"."badgeId") as count
|
||||
from "BadgeOwner"
|
||||
join "Badge" on "Badge"."id" = "BadgeOwner"."badgeId"
|
||||
where "BadgeOwner"."userId" = $userId
|
||||
|
|
@ -10,7 +15,7 @@ const countsByUserIdStm = sql.prepare(`
|
|||
`);
|
||||
|
||||
export type CountsByUserId = Array<
|
||||
Pick<Badge, "code" | "displayName" | "id"> & {
|
||||
Pick<Badge, "code" | "displayName" | "id" | "hue"> & {
|
||||
count: number;
|
||||
}
|
||||
>;
|
||||
|
|
@ -20,11 +25,15 @@ export function countsByUserId(userId: User["id"]) {
|
|||
}
|
||||
|
||||
const allStm = sql.prepare(`
|
||||
select "Badge"."id", "Badge"."code", "Badge"."displayName"
|
||||
select
|
||||
"Badge"."id",
|
||||
"Badge"."code",
|
||||
"Badge"."displayName",
|
||||
"Badge"."hue"
|
||||
from "Badge"
|
||||
`);
|
||||
|
||||
export type All = Array<Pick<Badge, "id" | "displayName" | "code">>;
|
||||
export type All = Array<Pick<Badge, "id" | "displayName" | "code" | "hue">>;
|
||||
|
||||
export function all() {
|
||||
return allStm.all() as All;
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ export interface Badge {
|
|||
id: number;
|
||||
code: string;
|
||||
displayName: string;
|
||||
hue?: number;
|
||||
}
|
||||
|
||||
export interface BadgeOwner {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import type { LinksFunction, LoaderFunction } from "@remix-run/node";
|
||||
import { NavLink, Outlet, useLoaderData } from "@remix-run/react";
|
||||
import { Image } from "~/components/Image";
|
||||
import { Badge } from "~/components/Badge";
|
||||
import { Main } from "~/components/Main";
|
||||
import { db } from "~/db";
|
||||
import type { All } from "~/db/models/badges.server";
|
||||
import styles from "~/styles/badges.css";
|
||||
import { jsonCached } from "~/utils/remix";
|
||||
import { badgeUrl } from "~/utils/urls";
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
return [{ rel: "stylesheet", href: styles }];
|
||||
|
|
@ -35,13 +34,7 @@ export default function BadgesPageLayout() {
|
|||
key={badge.id}
|
||||
to={String(badge.id)}
|
||||
>
|
||||
<Image
|
||||
path={badgeUrl({ code: badge.code })}
|
||||
title={badge.displayName}
|
||||
alt={badge.displayName}
|
||||
width={64}
|
||||
height={64}
|
||||
/>
|
||||
<Badge badge={badge} size={64} isAnimated={false} />
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import type { LoaderFunction } from "@remix-run/node";
|
||||
import { useLoaderData, useMatches, useParams } from "@remix-run/react";
|
||||
import clsx from "clsx";
|
||||
import { Badge } from "~/components/Badge";
|
||||
import { Redirect } from "~/components/Redirect";
|
||||
import { db } from "~/db";
|
||||
import type { OwnersByBadge } from "~/db/models/badges.server";
|
||||
import type { Badge } from "~/db/types";
|
||||
import type { Badge as BadgeDBType } from "~/db/types";
|
||||
import { jsonCached } from "~/utils/remix";
|
||||
import { discordFullName } from "~/utils/strings";
|
||||
import { BADGES_PAGE, badgeUrl } from "~/utils/urls";
|
||||
import { BADGES_PAGE } from "~/utils/urls";
|
||||
import type { BadgesLoaderData } from "../badges";
|
||||
|
||||
export interface BadgeDetailsLoaderData {
|
||||
|
|
@ -37,13 +38,7 @@ export default function BadgeDetailsPage() {
|
|||
|
||||
return (
|
||||
<div className="stack md items-center">
|
||||
<img
|
||||
src={badgeUrl({ code: badge.code, extension: "gif" })}
|
||||
alt={badge.displayName}
|
||||
title={badge.displayName}
|
||||
width="200"
|
||||
height="200"
|
||||
/>
|
||||
<Badge badge={badge} isAnimated size={200} />
|
||||
<div className="badges__explanation">{badgeExplanationText(badge)}</div>
|
||||
<div className="badges__owners-container">
|
||||
<ul className="badges__owners">
|
||||
|
|
@ -66,7 +61,7 @@ export default function BadgeDetailsPage() {
|
|||
}
|
||||
|
||||
export function badgeExplanationText(
|
||||
badge: Pick<Badge, "displayName" | "code"> & { count?: number }
|
||||
badge: Pick<BadgeDBType, "displayName" | "code"> & { count?: number }
|
||||
) {
|
||||
const countString =
|
||||
badge.count && badge.count > 1 ? ` (x${badge.count})` : "";
|
||||
|
|
|
|||
|
|
@ -56,8 +56,6 @@ export const loader: LoaderFunction = async ({ request }) => {
|
|||
export default function PlusVotingResultsPage() {
|
||||
const data = useLoaderData<PlusVotingResultsLoaderData>();
|
||||
|
||||
console.log({ data });
|
||||
|
||||
const { month, year } = lastCompletedVoting(new Date());
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { TwitchIcon } from "~/components/icons/Twitch";
|
|||
import { TwitterIcon } from "~/components/icons/Twitter";
|
||||
import { YouTubeIcon } from "~/components/icons/YouTube";
|
||||
import { badgeExplanationText } from "../badges/$id";
|
||||
import { badgeUrl } from "~/utils/urls";
|
||||
import { Badge } from "~/components/Badge";
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
return [{ rel: "stylesheet", href: styles }];
|
||||
|
|
@ -135,25 +135,16 @@ function BadgeContainer(props: { badges: UserPageLoaderData["badges"] }) {
|
|||
"justify-center": smallBadges.length === 0,
|
||||
})}
|
||||
>
|
||||
<img
|
||||
key={bigBadge.code}
|
||||
src={badgeUrl({ code: bigBadge.code, extension: "gif" })}
|
||||
alt={bigBadge.displayName}
|
||||
title={bigBadge.displayName}
|
||||
width="125"
|
||||
height="125"
|
||||
/>
|
||||
<Badge badge={bigBadge} size={125} isAnimated />
|
||||
{smallBadges.length > 0 ? (
|
||||
<div className="u__small-badges">
|
||||
{smallBadges.map((badge) => (
|
||||
<div key={badge.id} className="u__small-badge-container">
|
||||
<img
|
||||
src={badgeUrl({ code: badge.code, extension: "gif" })}
|
||||
alt={badge.displayName}
|
||||
title={badge.displayName}
|
||||
<Badge
|
||||
badge={badge}
|
||||
onClick={() => setBadgeFirst(badge)}
|
||||
width="48"
|
||||
height="48"
|
||||
size={48}
|
||||
isAnimated
|
||||
/>
|
||||
{badge.count > 1 ? (
|
||||
<div className="u__small-badge-count">×{badge.count}</div>
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-block: var(--s-2);
|
||||
padding-inline: var(--s-3);
|
||||
background-color: var(--bg-badge);
|
||||
border-radius: var(--rounded);
|
||||
gap: var(--s-6);
|
||||
padding-block: var(--s-2);
|
||||
padding-inline: var(--s-3);
|
||||
}
|
||||
|
||||
.badges__small-badges {
|
||||
|
|
@ -27,8 +27,8 @@
|
|||
}
|
||||
|
||||
.badges__owners-container {
|
||||
overflow-y: auto;
|
||||
height: 8rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.badges__owners {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
.button-text-paragraph > button {
|
||||
font-size: var(--fonts-md);
|
||||
font-weight: var(--semi-bold);
|
||||
font-size: var(--fonts-sm);
|
||||
font-weight: var(--semi-bold);
|
||||
margin-block-end: 0.125rem;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
.faq__summary {
|
||||
background-color: var(--bg-lighter);
|
||||
padding: var(--s-3);
|
||||
background-color: var(--bg-lighter);
|
||||
border-radius: var(--rounded);
|
||||
font-size: var(--fonts-lg);
|
||||
font-weight: var(--bold);
|
||||
}
|
||||
|
||||
.faq__details > p {
|
||||
padding-block: var(--s-3);
|
||||
margin-inline: var(--s-4);
|
||||
padding-block: var(--s-3);
|
||||
}
|
||||
|
|
|
|||
8
migrations/003-badges-hue.js
Normal file
8
migrations/003-badges-hue.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
module.exports.up = function (db) {
|
||||
db.prepare(`alter table "Badge" add "hue" integer`).run();
|
||||
db.prepare(`update "Badge" set "hue" = -72 where "code" = 'ebtv'`).run();
|
||||
};
|
||||
|
||||
module.exports.down = function (db) {
|
||||
db.prepare(`alter table "Badge" drop column "hue"`).run();
|
||||
};
|
||||
|
|
@ -21,8 +21,8 @@
|
|||
"test:unit": "uvu -r tsm -r tsconfig-paths/register -i cypress",
|
||||
"cy:open": "cypress open",
|
||||
"cy:run": "cypress run",
|
||||
"checks": "npm run typecheck && npm run test:unit && npm run lint:styles && npm run lint:ts && npm run prettier:check",
|
||||
"cf": "npm run typecheck && npm run test:unit && npm run lint:styles -- --fix && npm run lint:ts -- --fix && npm run prettier:write"
|
||||
"checks": "npm run test:unit && npm run lint:styles && npm run lint:ts && npm run prettier:check && npm run typecheck",
|
||||
"cf": "npm run test:unit && npm run lint:styles -- --fix && npm run lint:ts -- --fix && npm run prettier:write && npm run typecheck"
|
||||
},
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^7.3.0",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user