mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
176 lines
5.2 KiB
TypeScript
176 lines
5.2 KiB
TypeScript
import clsx from "clsx";
|
|
import * as React from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import type { MetaFunction, ShouldRevalidateFunction } from "react-router";
|
|
import { useLoaderData, useSearchParams } from "react-router";
|
|
import { AddNewButton } from "~/components/AddNewButton";
|
|
import { SendouButton } from "~/components/elements/Button";
|
|
import { SendouSwitch } from "~/components/elements/Switch";
|
|
import {
|
|
SendouTab,
|
|
SendouTabList,
|
|
SendouTabPanel,
|
|
SendouTabs,
|
|
} from "~/components/elements/Tabs";
|
|
import { CrossIcon } from "~/components/icons/Cross";
|
|
import { Label } from "~/components/Label";
|
|
import { Main } from "~/components/Main";
|
|
import type { SendouRouteHandle } from "~/utils/remix.server";
|
|
import { artPage, navIconUrl, newArtPage } from "~/utils/urls";
|
|
import { metaTags, type SerializeFrom } from "../../../utils/remix";
|
|
import { FILTERED_TAG_KEY_SEARCH_PARAM_KEY } from "../art-constants";
|
|
import { ArtGrid } from "../components/ArtGrid";
|
|
import { TagSelect } from "../components/TagSelect";
|
|
import { loader } from "../loaders/art.server";
|
|
export { loader };
|
|
|
|
const OPEN_COMMISIONS_KEY = "open";
|
|
const TAB_KEY = "tab";
|
|
const TABS = {
|
|
RECENTLY_UPLOADED: "recently-uploaded",
|
|
SHOWCASE: "showcase",
|
|
} as const;
|
|
|
|
export const shouldRevalidate: ShouldRevalidateFunction = (args) => {
|
|
const currentFilteredTag = args.currentUrl.searchParams.get(
|
|
FILTERED_TAG_KEY_SEARCH_PARAM_KEY,
|
|
);
|
|
const nextFilteredTag = args.nextUrl.searchParams.get(
|
|
FILTERED_TAG_KEY_SEARCH_PARAM_KEY,
|
|
);
|
|
|
|
if (currentFilteredTag === nextFilteredTag) return false;
|
|
|
|
return args.defaultShouldRevalidate;
|
|
};
|
|
|
|
export const handle: SendouRouteHandle = {
|
|
i18n: ["art"],
|
|
breadcrumb: () => ({
|
|
imgPath: navIconUrl("art"),
|
|
href: artPage(),
|
|
type: "IMAGE",
|
|
}),
|
|
};
|
|
|
|
export const meta: MetaFunction = (args) => {
|
|
const data = args.data as SerializeFrom<typeof loader> | null;
|
|
|
|
if (!data) return [];
|
|
|
|
return metaTags({
|
|
title: "Art",
|
|
ogTitle: "Splatoon art showcase",
|
|
description:
|
|
"Splatoon art filterable by various tags. Find artist to commission for your own custom art. Includes various styles such as traditional, digital, 3D and SFM.",
|
|
location: args.location,
|
|
});
|
|
};
|
|
|
|
export default function ArtPage() {
|
|
const { t } = useTranslation(["art", "common"]);
|
|
const data = useLoaderData<typeof loader>();
|
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
const switchId = React.useId();
|
|
|
|
const selectedTab = searchParams.get(TAB_KEY) ?? TABS.RECENTLY_UPLOADED;
|
|
const filteredTag = searchParams.get(FILTERED_TAG_KEY_SEARCH_PARAM_KEY);
|
|
const showOpenCommissions = searchParams.get(OPEN_COMMISIONS_KEY) === "true";
|
|
|
|
const showcaseArts = !showOpenCommissions
|
|
? data.showcaseArts
|
|
: data.showcaseArts.filter((art) => art.author?.commissionsOpen);
|
|
|
|
const recentlyUploadedArts = !showOpenCommissions
|
|
? data.recentlyUploadedArts
|
|
: data.recentlyUploadedArts.filter((art) => art.author?.commissionsOpen);
|
|
|
|
return (
|
|
<Main className="stack lg">
|
|
<div className="stack horizontal md justify-between items-center flex-wrap">
|
|
<div className="stack horizontal sm text-sm font-semi-bold">
|
|
<SendouSwitch
|
|
isSelected={showOpenCommissions}
|
|
onChange={() =>
|
|
setSearchParams((prev) => {
|
|
prev.set(OPEN_COMMISIONS_KEY, String(!showOpenCommissions));
|
|
return prev;
|
|
})
|
|
}
|
|
id={switchId}
|
|
/>
|
|
<Label htmlFor={switchId} className="m-auto-0">
|
|
{t("art:openCommissionsOnly")}
|
|
</Label>
|
|
</div>
|
|
<div className="stack horizontal sm items-center">
|
|
<div
|
|
className={clsx({
|
|
invisible: selectedTab !== TABS.SHOWCASE,
|
|
})}
|
|
>
|
|
<TagSelect
|
|
key={filteredTag}
|
|
tags={data.allTags}
|
|
onSelectionChange={(tagName) => {
|
|
setSearchParams((prev) => {
|
|
prev.set(
|
|
FILTERED_TAG_KEY_SEARCH_PARAM_KEY,
|
|
tagName as string,
|
|
);
|
|
return prev;
|
|
});
|
|
}}
|
|
/>
|
|
</div>
|
|
<AddNewButton navIcon="art" to={newArtPage()} />
|
|
</div>
|
|
</div>
|
|
{filteredTag ? (
|
|
<div className="text-xs text-lighter stack md horizontal items-center">
|
|
{t("art:filteringByTag", { tag: filteredTag })}
|
|
<SendouButton
|
|
size="small"
|
|
variant="minimal-destructive"
|
|
icon={<CrossIcon />}
|
|
onPress={() => {
|
|
setSearchParams((prev) => {
|
|
prev.delete(FILTERED_TAG_KEY_SEARCH_PARAM_KEY);
|
|
return prev;
|
|
});
|
|
}}
|
|
data-testid="clear-filter-button"
|
|
>
|
|
{t("common:actions.clear")}
|
|
</SendouButton>
|
|
</div>
|
|
) : null}
|
|
<SendouTabs
|
|
selectedKey={selectedTab}
|
|
onSelectionChange={(key) => {
|
|
setSearchParams((prev) => {
|
|
prev.set(TAB_KEY, key as string);
|
|
if (key === TABS.RECENTLY_UPLOADED) {
|
|
prev.delete(FILTERED_TAG_KEY_SEARCH_PARAM_KEY);
|
|
}
|
|
return prev;
|
|
});
|
|
}}
|
|
>
|
|
<SendouTabList>
|
|
<SendouTab id={TABS.RECENTLY_UPLOADED}>
|
|
{t("art:tabs.recentlyUploaded")}
|
|
</SendouTab>
|
|
<SendouTab id={TABS.SHOWCASE}>{t("art:tabs.showcase")}</SendouTab>
|
|
</SendouTabList>
|
|
<SendouTabPanel id={TABS.RECENTLY_UPLOADED}>
|
|
<ArtGrid arts={recentlyUploadedArts} showUploadDate />
|
|
</SendouTabPanel>
|
|
<SendouTabPanel id={TABS.SHOWCASE}>
|
|
<ArtGrid arts={showcaseArts} />
|
|
</SendouTabPanel>
|
|
</SendouTabs>
|
|
</Main>
|
|
);
|
|
}
|