Added ability to mark library item as 'Want' or 'have'
This commit is contained in:
parent
8b6abd6379
commit
59283fa465
|
@ -0,0 +1,76 @@
|
||||||
|
import { Icon } from "components/Ico";
|
||||||
|
import { Button } from "components/Inputs/Button";
|
||||||
|
import { ToolTip } from "components/ToolTip";
|
||||||
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
import { LibraryItemUserStatus } from "helpers/types";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string | null | undefined;
|
||||||
|
displayCTAs: boolean;
|
||||||
|
expand?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PreviewCardCTAs(props: Props): JSX.Element {
|
||||||
|
const { id, displayCTAs, expand = false } = props;
|
||||||
|
const appLayout = useAppLayout();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{displayCTAs && id && (
|
||||||
|
<div
|
||||||
|
className={`flex flex-row place-content-center place-items-center ${
|
||||||
|
expand ? "gap-4" : "gap-2"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{/* TODO: Add to langui */}
|
||||||
|
<ToolTip content="I want it!">
|
||||||
|
<Button
|
||||||
|
icon={Icon.Favorite}
|
||||||
|
text={expand ? "I want it!" : undefined}
|
||||||
|
active={
|
||||||
|
appLayout.libraryItemUserStatus?.[id] ===
|
||||||
|
LibraryItemUserStatus.Want
|
||||||
|
}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
appLayout.setLibraryItemUserStatus((current) => {
|
||||||
|
const newLibraryItemUserStatus = current
|
||||||
|
? { ...current }
|
||||||
|
: {};
|
||||||
|
newLibraryItemUserStatus[id] =
|
||||||
|
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Want
|
||||||
|
? LibraryItemUserStatus.None
|
||||||
|
: LibraryItemUserStatus.Want;
|
||||||
|
return newLibraryItemUserStatus;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip content="I have it!">
|
||||||
|
<Button
|
||||||
|
icon={Icon.BackHand}
|
||||||
|
text={expand ? "I have it!" : undefined}
|
||||||
|
active={
|
||||||
|
appLayout.libraryItemUserStatus?.[id] ===
|
||||||
|
LibraryItemUserStatus.Have
|
||||||
|
}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
appLayout.setLibraryItemUserStatus((current) => {
|
||||||
|
const newLibraryItemUserStatus = current
|
||||||
|
? { ...current }
|
||||||
|
: {};
|
||||||
|
newLibraryItemUserStatus[id] =
|
||||||
|
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have
|
||||||
|
? LibraryItemUserStatus.None
|
||||||
|
: LibraryItemUserStatus.Have;
|
||||||
|
return newLibraryItemUserStatus;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ interface Props {
|
||||||
author?: string;
|
author?: string;
|
||||||
position: "Bottom" | "Top";
|
position: "Bottom" | "Top";
|
||||||
};
|
};
|
||||||
|
infoAppend?: React.ReactNode;
|
||||||
hoverlay?:
|
hoverlay?:
|
||||||
| {
|
| {
|
||||||
__typename: "Video";
|
__typename: "Video";
|
||||||
|
@ -61,6 +62,7 @@ export function PreviewCard(props: Immutable<Props>): JSX.Element {
|
||||||
thumbnailAspectRatio,
|
thumbnailAspectRatio,
|
||||||
metadata,
|
metadata,
|
||||||
hoverlay,
|
hoverlay,
|
||||||
|
infoAppend,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const appLayout = useAppLayout();
|
const appLayout = useAppLayout();
|
||||||
|
@ -251,6 +253,8 @@ export function PreviewCard(props: Immutable<Props>): JSX.Element {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{metadata?.position === "Bottom" && metadataJSX}
|
{metadata?.position === "Bottom" && metadataJSX}
|
||||||
|
|
||||||
|
{infoAppend}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -1,41 +1,57 @@
|
||||||
import { Immutable } from "helpers/types";
|
import { Immutable, LibraryItemUserStatus } from "helpers/types";
|
||||||
import { useDarkMode } from "hooks/useDarkMode";
|
import { useDarkMode } from "hooks/useDarkMode";
|
||||||
import { useStateWithLocalStorage } from "hooks/useStateWithLocalStorage";
|
import { useStateWithLocalStorage } from "hooks/useStateWithLocalStorage";
|
||||||
import React, { ReactNode, useContext, useState } from "react";
|
import React, { ReactNode, useContext, useState } from "react";
|
||||||
|
|
||||||
interface AppLayoutState {
|
export interface AppLayoutState {
|
||||||
subPanelOpen: boolean | undefined;
|
subPanelOpen: boolean | undefined;
|
||||||
|
setSubPanelOpen: React.Dispatch<
|
||||||
|
React.SetStateAction<AppLayoutState["subPanelOpen"]>
|
||||||
|
>;
|
||||||
configPanelOpen: boolean | undefined;
|
configPanelOpen: boolean | undefined;
|
||||||
|
setConfigPanelOpen: React.Dispatch<
|
||||||
|
React.SetStateAction<AppLayoutState["configPanelOpen"]>
|
||||||
|
>;
|
||||||
searchPanelOpen: boolean | undefined;
|
searchPanelOpen: boolean | undefined;
|
||||||
|
setSearchPanelOpen: React.Dispatch<
|
||||||
|
React.SetStateAction<AppLayoutState["searchPanelOpen"]>
|
||||||
|
>;
|
||||||
mainPanelReduced: boolean | undefined;
|
mainPanelReduced: boolean | undefined;
|
||||||
mainPanelOpen: boolean | undefined;
|
|
||||||
darkMode: boolean | undefined;
|
|
||||||
selectedThemeMode: boolean | undefined;
|
|
||||||
fontSize: number | undefined;
|
|
||||||
dyslexic: boolean | undefined;
|
|
||||||
currency: string | undefined;
|
|
||||||
playerName: string | undefined;
|
|
||||||
preferredLanguages: string[] | undefined;
|
|
||||||
menuGestures: boolean;
|
|
||||||
setSubPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
|
||||||
setConfigPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
|
||||||
setSearchPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
|
||||||
setMainPanelReduced: React.Dispatch<
|
setMainPanelReduced: React.Dispatch<
|
||||||
React.SetStateAction<boolean | undefined>
|
React.SetStateAction<AppLayoutState["mainPanelReduced"]>
|
||||||
>;
|
>;
|
||||||
setMainPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
mainPanelOpen: boolean | undefined;
|
||||||
setDarkMode: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
setMainPanelOpen: React.Dispatch<
|
||||||
|
React.SetStateAction<AppLayoutState["mainPanelOpen"]>
|
||||||
|
>;
|
||||||
|
darkMode: boolean | undefined;
|
||||||
|
setDarkMode: React.Dispatch<React.SetStateAction<AppLayoutState["darkMode"]>>;
|
||||||
|
selectedThemeMode: boolean | undefined;
|
||||||
setSelectedThemeMode: React.Dispatch<
|
setSelectedThemeMode: React.Dispatch<
|
||||||
React.SetStateAction<boolean | undefined>
|
React.SetStateAction<AppLayoutState["selectedThemeMode"]>
|
||||||
>;
|
>;
|
||||||
setFontSize: React.Dispatch<React.SetStateAction<number | undefined>>;
|
fontSize: number | undefined;
|
||||||
setDyslexic: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
setFontSize: React.Dispatch<React.SetStateAction<AppLayoutState["fontSize"]>>;
|
||||||
setCurrency: React.Dispatch<React.SetStateAction<string | undefined>>;
|
dyslexic: boolean | undefined;
|
||||||
setPlayerName: React.Dispatch<React.SetStateAction<string | undefined>>;
|
setDyslexic: React.Dispatch<React.SetStateAction<AppLayoutState["dyslexic"]>>;
|
||||||
|
currency: string | undefined;
|
||||||
|
setCurrency: React.Dispatch<React.SetStateAction<AppLayoutState["currency"]>>;
|
||||||
|
playerName: string | undefined;
|
||||||
|
setPlayerName: React.Dispatch<
|
||||||
|
React.SetStateAction<AppLayoutState["playerName"]>
|
||||||
|
>;
|
||||||
|
preferredLanguages: string[] | undefined;
|
||||||
setPreferredLanguages: React.Dispatch<
|
setPreferredLanguages: React.Dispatch<
|
||||||
React.SetStateAction<string[] | undefined>
|
React.SetStateAction<AppLayoutState["preferredLanguages"]>
|
||||||
|
>;
|
||||||
|
menuGestures: boolean;
|
||||||
|
setMenuGestures: React.Dispatch<
|
||||||
|
React.SetStateAction<AppLayoutState["menuGestures"]>
|
||||||
|
>;
|
||||||
|
libraryItemUserStatus: Record<string, LibraryItemUserStatus> | undefined;
|
||||||
|
setLibraryItemUserStatus: React.Dispatch<
|
||||||
|
React.SetStateAction<AppLayoutState["libraryItemUserStatus"]>
|
||||||
>;
|
>;
|
||||||
setMenuGestures: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||||
|
@ -53,6 +69,7 @@ const initialState: AppLayoutState = {
|
||||||
playerName: "",
|
playerName: "",
|
||||||
preferredLanguages: [],
|
preferredLanguages: [],
|
||||||
menuGestures: true,
|
menuGestures: true,
|
||||||
|
libraryItemUserStatus: {},
|
||||||
setSubPanelOpen: () => {},
|
setSubPanelOpen: () => {},
|
||||||
setMainPanelReduced: () => {},
|
setMainPanelReduced: () => {},
|
||||||
setMainPanelOpen: () => {},
|
setMainPanelOpen: () => {},
|
||||||
|
@ -66,6 +83,7 @@ const initialState: AppLayoutState = {
|
||||||
setPlayerName: () => {},
|
setPlayerName: () => {},
|
||||||
setPreferredLanguages: () => {},
|
setPreferredLanguages: () => {},
|
||||||
setMenuGestures: () => {},
|
setMenuGestures: () => {},
|
||||||
|
setLibraryItemUserStatus: () => {},
|
||||||
};
|
};
|
||||||
/* eslint-enable @typescript-eslint/no-empty-function */
|
/* eslint-enable @typescript-eslint/no-empty-function */
|
||||||
|
|
||||||
|
@ -82,53 +100,66 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AppContextProvider(props: Immutable<Props>): JSX.Element {
|
export function AppContextProvider(props: Immutable<Props>): JSX.Element {
|
||||||
const [subPanelOpen, setSubPanelOpen] = useStateWithLocalStorage<
|
const [subPanelOpen, setSubPanelOpen] = useStateWithLocalStorage(
|
||||||
boolean | undefined
|
"subPanelOpen",
|
||||||
>("subPanelOpen", initialState.subPanelOpen);
|
initialState.subPanelOpen
|
||||||
|
);
|
||||||
|
|
||||||
const [configPanelOpen, setConfigPanelOpen] = useStateWithLocalStorage<
|
const [configPanelOpen, setConfigPanelOpen] = useStateWithLocalStorage(
|
||||||
boolean | undefined
|
"configPanelOpen",
|
||||||
>("configPanelOpen", initialState.configPanelOpen);
|
initialState.configPanelOpen
|
||||||
|
);
|
||||||
|
|
||||||
const [mainPanelReduced, setMainPanelReduced] = useStateWithLocalStorage<
|
const [mainPanelReduced, setMainPanelReduced] = useStateWithLocalStorage(
|
||||||
boolean | undefined
|
"mainPanelReduced",
|
||||||
>("mainPanelReduced", initialState.mainPanelReduced);
|
initialState.mainPanelReduced
|
||||||
|
);
|
||||||
|
|
||||||
const [mainPanelOpen, setMainPanelOpen] = useStateWithLocalStorage<
|
const [mainPanelOpen, setMainPanelOpen] = useStateWithLocalStorage(
|
||||||
boolean | undefined
|
"mainPanelOpen",
|
||||||
>("mainPanelOpen", initialState.mainPanelOpen);
|
initialState.mainPanelOpen
|
||||||
|
);
|
||||||
|
|
||||||
const [darkMode, selectedThemeMode, setDarkMode, setSelectedThemeMode] =
|
const [darkMode, selectedThemeMode, setDarkMode, setSelectedThemeMode] =
|
||||||
useDarkMode("darkMode", initialState.darkMode);
|
useDarkMode("darkMode", initialState.darkMode);
|
||||||
|
|
||||||
const [fontSize, setFontSize] = useStateWithLocalStorage<number | undefined>(
|
const [fontSize, setFontSize] = useStateWithLocalStorage(
|
||||||
"fontSize",
|
"fontSize",
|
||||||
initialState.fontSize
|
initialState.fontSize
|
||||||
);
|
);
|
||||||
|
|
||||||
const [dyslexic, setDyslexic] = useStateWithLocalStorage<boolean | undefined>(
|
const [dyslexic, setDyslexic] = useStateWithLocalStorage(
|
||||||
"dyslexic",
|
"dyslexic",
|
||||||
initialState.dyslexic
|
initialState.dyslexic
|
||||||
);
|
);
|
||||||
|
|
||||||
const [currency, setCurrency] = useStateWithLocalStorage<string | undefined>(
|
const [currency, setCurrency] = useStateWithLocalStorage(
|
||||||
"currency",
|
"currency",
|
||||||
initialState.currency
|
initialState.currency
|
||||||
);
|
);
|
||||||
|
|
||||||
const [playerName, setPlayerName] = useStateWithLocalStorage<
|
const [playerName, setPlayerName] = useStateWithLocalStorage(
|
||||||
string | undefined
|
"playerName",
|
||||||
>("playerName", initialState.playerName);
|
initialState.playerName
|
||||||
|
);
|
||||||
|
|
||||||
const [preferredLanguages, setPreferredLanguages] = useStateWithLocalStorage<
|
const [preferredLanguages, setPreferredLanguages] = useStateWithLocalStorage(
|
||||||
string[] | undefined
|
"preferredLanguages",
|
||||||
>("preferredLanguages", initialState.preferredLanguages);
|
initialState.preferredLanguages
|
||||||
|
);
|
||||||
|
|
||||||
const [menuGestures, setMenuGestures] = useState(false);
|
const [menuGestures, setMenuGestures] = useState(false);
|
||||||
|
|
||||||
const [searchPanelOpen, setSearchPanelOpen] = useStateWithLocalStorage<
|
const [searchPanelOpen, setSearchPanelOpen] = useStateWithLocalStorage(
|
||||||
boolean | undefined
|
"searchPanelOpen",
|
||||||
>("searchPanelOpen", initialState.searchPanelOpen);
|
initialState.searchPanelOpen
|
||||||
|
);
|
||||||
|
|
||||||
|
const [libraryItemUserStatus, setLibraryItemUserStatus] =
|
||||||
|
useStateWithLocalStorage(
|
||||||
|
"libraryItemUserStatus",
|
||||||
|
initialState.libraryItemUserStatus
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContext.Provider
|
<AppContext.Provider
|
||||||
|
@ -146,6 +177,7 @@ export function AppContextProvider(props: Immutable<Props>): JSX.Element {
|
||||||
playerName,
|
playerName,
|
||||||
preferredLanguages,
|
preferredLanguages,
|
||||||
menuGestures,
|
menuGestures,
|
||||||
|
libraryItemUserStatus,
|
||||||
setSubPanelOpen,
|
setSubPanelOpen,
|
||||||
setConfigPanelOpen,
|
setConfigPanelOpen,
|
||||||
setSearchPanelOpen,
|
setSearchPanelOpen,
|
||||||
|
@ -159,6 +191,7 @@ export function AppContextProvider(props: Immutable<Props>): JSX.Element {
|
||||||
setPlayerName,
|
setPlayerName,
|
||||||
setPreferredLanguages,
|
setPreferredLanguages,
|
||||||
setMenuGestures,
|
setMenuGestures,
|
||||||
|
setLibraryItemUserStatus,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|
|
@ -0,0 +1,251 @@
|
||||||
|
import { AppLayoutState } from "contexts/AppLayoutContext";
|
||||||
|
import { GetLibraryItemsPreviewQuery } from "graphql/generated";
|
||||||
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { prettyinlineTitle, prettyDate } from "./formatters";
|
||||||
|
import { convertPrice } from "./numbers";
|
||||||
|
import { Immutable, LibraryItemUserStatus } from "./types";
|
||||||
|
type Items = NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"];
|
||||||
|
type GroupLibraryItems = Map<string, Immutable<Items>>;
|
||||||
|
|
||||||
|
export function getGroups(
|
||||||
|
langui: AppStaticProps["langui"],
|
||||||
|
groupByType: number,
|
||||||
|
items: Immutable<Items>
|
||||||
|
): GroupLibraryItems {
|
||||||
|
switch (groupByType) {
|
||||||
|
case 0: {
|
||||||
|
const typeGroup = new Map();
|
||||||
|
typeGroup.set("Drakengard 1", []);
|
||||||
|
typeGroup.set("Drakengard 1.3", []);
|
||||||
|
typeGroup.set("Drakengard 2", []);
|
||||||
|
typeGroup.set("Drakengard 3", []);
|
||||||
|
typeGroup.set("Drakengard 4", []);
|
||||||
|
typeGroup.set("NieR Gestalt", []);
|
||||||
|
typeGroup.set("NieR Replicant", []);
|
||||||
|
typeGroup.set("NieR Replicant ver.1.22474487139...", []);
|
||||||
|
typeGroup.set("NieR:Automata", []);
|
||||||
|
typeGroup.set("NieR Re[in]carnation", []);
|
||||||
|
typeGroup.set("SINoALICE", []);
|
||||||
|
typeGroup.set("Voice of Cards", []);
|
||||||
|
typeGroup.set("Final Fantasy XIV", []);
|
||||||
|
typeGroup.set("Thou Shalt Not Die", []);
|
||||||
|
typeGroup.set("Bakuken", []);
|
||||||
|
typeGroup.set("YoRHa", []);
|
||||||
|
typeGroup.set("YoRHa Boys", []);
|
||||||
|
typeGroup.set(langui.no_category, []);
|
||||||
|
|
||||||
|
items.map((item) => {
|
||||||
|
if (item.attributes?.categories?.data.length === 0) {
|
||||||
|
typeGroup.get(langui.no_category)?.push(item);
|
||||||
|
} else {
|
||||||
|
item.attributes?.categories?.data.map((category) => {
|
||||||
|
typeGroup.get(category.attributes?.name)?.push(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return typeGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1: {
|
||||||
|
const group = new Map();
|
||||||
|
group.set(langui.audio ?? "Audio", []);
|
||||||
|
group.set(langui.game ?? "Game", []);
|
||||||
|
group.set(langui.textual ?? "Textual", []);
|
||||||
|
group.set(langui.video ?? "Video", []);
|
||||||
|
group.set(langui.other ?? "Other", []);
|
||||||
|
group.set(langui.group ?? "Group", []);
|
||||||
|
group.set(langui.no_type ?? "No type", []);
|
||||||
|
items.map((item) => {
|
||||||
|
if (item.attributes?.metadata && item.attributes.metadata.length > 0) {
|
||||||
|
switch (item.attributes.metadata[0]?.__typename) {
|
||||||
|
case "ComponentMetadataAudio":
|
||||||
|
group.get(langui.audio ?? "Audio")?.push(item);
|
||||||
|
break;
|
||||||
|
case "ComponentMetadataGame":
|
||||||
|
group.get(langui.game ?? "Game")?.push(item);
|
||||||
|
break;
|
||||||
|
case "ComponentMetadataBooks":
|
||||||
|
group.get(langui.textual ?? "Textual")?.push(item);
|
||||||
|
break;
|
||||||
|
case "ComponentMetadataVideo":
|
||||||
|
group.get(langui.video ?? "Video")?.push(item);
|
||||||
|
break;
|
||||||
|
case "ComponentMetadataOther":
|
||||||
|
group.get(langui.other ?? "Other")?.push(item);
|
||||||
|
break;
|
||||||
|
case "ComponentMetadataGroup":
|
||||||
|
switch (
|
||||||
|
item.attributes.metadata[0]?.subitems_type?.data?.attributes
|
||||||
|
?.slug
|
||||||
|
) {
|
||||||
|
case "audio":
|
||||||
|
group.get(langui.audio ?? "Audio")?.push(item);
|
||||||
|
break;
|
||||||
|
case "video":
|
||||||
|
group.get(langui.video ?? "Video")?.push(item);
|
||||||
|
break;
|
||||||
|
case "game":
|
||||||
|
group.get(langui.game ?? "Game")?.push(item);
|
||||||
|
break;
|
||||||
|
case "textual":
|
||||||
|
group.get(langui.textual ?? "Textual")?.push(item);
|
||||||
|
break;
|
||||||
|
case "mixed":
|
||||||
|
group.get(langui.group ?? "Group")?.push(item);
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
throw new Error(
|
||||||
|
"An unexpected subtype of group-metadata was given"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
throw new Error("An unexpected type of metadata was given");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
group.get(langui.no_type ?? "No type")?.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2: {
|
||||||
|
const years: number[] = [];
|
||||||
|
items.map((item) => {
|
||||||
|
if (item.attributes?.release_date?.year) {
|
||||||
|
if (!years.includes(item.attributes.release_date.year))
|
||||||
|
years.push(item.attributes.release_date.year);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const group = new Map();
|
||||||
|
years.sort((a, b) => a - b);
|
||||||
|
years.map((year) => {
|
||||||
|
group.set(year.toString(), []);
|
||||||
|
});
|
||||||
|
group.set(langui.no_year ?? "No year", []);
|
||||||
|
items.map((item) => {
|
||||||
|
if (item.attributes?.release_date?.year) {
|
||||||
|
group.get(item.attributes.release_date.year.toString())?.push(item);
|
||||||
|
} else {
|
||||||
|
group.get(langui.no_year ?? "No year")?.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
const group = new Map();
|
||||||
|
group.set("", items);
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterItems(
|
||||||
|
appLayout: AppLayoutState,
|
||||||
|
items: Immutable<Items>,
|
||||||
|
searchName: string,
|
||||||
|
showSubitems: boolean,
|
||||||
|
showPrimaryItems: boolean,
|
||||||
|
showSecondaryItems: boolean,
|
||||||
|
filterUserStatus: LibraryItemUserStatus | undefined
|
||||||
|
): Immutable<Items> {
|
||||||
|
return [...items].filter((item) => {
|
||||||
|
if (!showSubitems && !item.attributes?.root_item) return false;
|
||||||
|
if (showSubitems && isUntangibleGroupItem(item.attributes?.metadata?.[0])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (item.attributes?.primary && !showPrimaryItems) return false;
|
||||||
|
if (!item.attributes?.primary && !showSecondaryItems) return false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
searchName.length > 1 &&
|
||||||
|
!prettyinlineTitle("", item.attributes?.title, item.attributes?.subtitle)
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(searchName.toLowerCase())
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
filterUserStatus !== undefined &&
|
||||||
|
item.id &&
|
||||||
|
appLayout.libraryItemUserStatus
|
||||||
|
) {
|
||||||
|
if (isUntangibleGroupItem(item.attributes?.metadata?.[0])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (filterUserStatus === LibraryItemUserStatus.None) {
|
||||||
|
if (appLayout.libraryItemUserStatus[item.id]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
filterUserStatus !== appLayout.libraryItemUserStatus[item.id]
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Properly type this shit
|
||||||
|
// Best attempt was Immutable<NonNullable<NonNullable<Items[number]["attributes"]>["metadata"]>[number]>
|
||||||
|
export function isUntangibleGroupItem(metadata: any) {
|
||||||
|
return (
|
||||||
|
metadata &&
|
||||||
|
metadata.__typename === "ComponentMetadataGroup" &&
|
||||||
|
(metadata.subtype?.data?.attributes?.slug === "variant-set" ||
|
||||||
|
metadata.subtype?.data?.attributes?.slug === "relation-set")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sortBy(
|
||||||
|
orderByType: number,
|
||||||
|
items: Immutable<Items>,
|
||||||
|
currencies: AppStaticProps["currencies"]
|
||||||
|
): Immutable<Items> {
|
||||||
|
switch (orderByType) {
|
||||||
|
case 0:
|
||||||
|
return [...items].sort((a, b) => {
|
||||||
|
const titleA = prettyinlineTitle(
|
||||||
|
"",
|
||||||
|
a.attributes?.title,
|
||||||
|
a.attributes?.subtitle
|
||||||
|
);
|
||||||
|
const titleB = prettyinlineTitle(
|
||||||
|
"",
|
||||||
|
b.attributes?.title,
|
||||||
|
b.attributes?.subtitle
|
||||||
|
);
|
||||||
|
return titleA.localeCompare(titleB);
|
||||||
|
});
|
||||||
|
case 1:
|
||||||
|
return [...items].sort((a, b) => {
|
||||||
|
const priceA = a.attributes?.price
|
||||||
|
? convertPrice(a.attributes.price, currencies[0])
|
||||||
|
: 99999;
|
||||||
|
const priceB = b.attributes?.price
|
||||||
|
? convertPrice(b.attributes.price, currencies[0])
|
||||||
|
: 99999;
|
||||||
|
return priceA - priceB;
|
||||||
|
});
|
||||||
|
case 2:
|
||||||
|
return [...items].sort((a, b) => {
|
||||||
|
const dateA = a.attributes?.release_date
|
||||||
|
? prettyDate(a.attributes.release_date)
|
||||||
|
: "9999";
|
||||||
|
const dateB = b.attributes?.release_date
|
||||||
|
? prettyDate(b.attributes.release_date)
|
||||||
|
: "9999";
|
||||||
|
return dateA.localeCompare(dateB);
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,3 +24,9 @@ export type Immutable<T> = {
|
||||||
? T[K]
|
? T[K]
|
||||||
: Immutable<T[K]>;
|
: Immutable<T[K]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum LibraryItemUserStatus {
|
||||||
|
None = 0,
|
||||||
|
Want = 1,
|
||||||
|
Have = 2,
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { Button } from "components/Inputs/Button";
|
||||||
import { Switch } from "components/Inputs/Switch";
|
import { Switch } from "components/Inputs/Switch";
|
||||||
import { InsetBox } from "components/InsetBox";
|
import { InsetBox } from "components/InsetBox";
|
||||||
import { ContentLine } from "components/Library/ContentLine";
|
import { ContentLine } from "components/Library/ContentLine";
|
||||||
|
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
|
||||||
import { NavOption } from "components/PanelComponents/NavOption";
|
import { NavOption } from "components/PanelComponents/NavOption";
|
||||||
import {
|
import {
|
||||||
ReturnButton,
|
ReturnButton,
|
||||||
|
@ -44,6 +45,7 @@ import {
|
||||||
GetStaticPropsContext,
|
GetStaticPropsContext,
|
||||||
} from "next";
|
} from "next";
|
||||||
import { Fragment, useState } from "react";
|
import { Fragment, useState } from "react";
|
||||||
|
import { isUntangibleGroupItem } from "helpers/libraryItem";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
item: NonNullable<
|
item: NonNullable<
|
||||||
|
@ -55,7 +57,7 @@ interface Props extends AppStaticProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LibrarySlug(props: Immutable<Props>): JSX.Element {
|
export default function LibrarySlug(props: Immutable<Props>): JSX.Element {
|
||||||
const { item, langui, currencies } = props;
|
const { item, itemId, langui, currencies } = props;
|
||||||
const appLayout = useAppLayout();
|
const appLayout = useAppLayout();
|
||||||
|
|
||||||
useScrollTopOnChange(AnchorIds.ContentPanel, [item]);
|
useScrollTopOnChange(AnchorIds.ContentPanel, [item]);
|
||||||
|
@ -169,6 +171,12 @@ export default function LibrarySlug(props: Immutable<Props>): JSX.Element {
|
||||||
<h1 className="text-3xl">{item?.title}</h1>
|
<h1 className="text-3xl">{item?.title}</h1>
|
||||||
{item?.subtitle && <h2 className="text-2xl">{item.subtitle}</h2>}
|
{item?.subtitle && <h2 className="text-2xl">{item.subtitle}</h2>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<PreviewCardCTAs
|
||||||
|
id={itemId}
|
||||||
|
displayCTAs={!isUntangibleGroupItem(item?.metadata?.[0])}
|
||||||
|
expand
|
||||||
|
/>
|
||||||
{item?.descriptions?.[0] && (
|
{item?.descriptions?.[0] && (
|
||||||
<p className="text-justify">{item.descriptions[0].description}</p>
|
<p className="text-justify">{item.descriptions[0].description}</p>
|
||||||
)}
|
)}
|
||||||
|
@ -402,7 +410,7 @@ export default function LibrarySlug(props: Immutable<Props>): JSX.Element {
|
||||||
>
|
>
|
||||||
{item.subitems.data.map((subitem) => (
|
{item.subitems.data.map((subitem) => (
|
||||||
<Fragment key={subitem.id}>
|
<Fragment key={subitem.id}>
|
||||||
{subitem.attributes && (
|
{subitem.attributes && subitem.id && (
|
||||||
<PreviewCard
|
<PreviewCard
|
||||||
href={`/library/${subitem.attributes.slug}`}
|
href={`/library/${subitem.attributes.slug}`}
|
||||||
title={subitem.attributes.title}
|
title={subitem.attributes.title}
|
||||||
|
@ -426,6 +434,16 @@ export default function LibrarySlug(props: Immutable<Props>): JSX.Element {
|
||||||
price: subitem.attributes.price,
|
price: subitem.attributes.price,
|
||||||
position: "Bottom",
|
position: "Bottom",
|
||||||
}}
|
}}
|
||||||
|
infoAppend={
|
||||||
|
<PreviewCardCTAs
|
||||||
|
id={subitem.id}
|
||||||
|
displayCTAs={
|
||||||
|
!isUntangibleGroupItem(
|
||||||
|
subitem.attributes.metadata?.[0]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
|
@ -8,30 +8,32 @@ import {
|
||||||
ContentPanelWidthSizes,
|
ContentPanelWidthSizes,
|
||||||
} from "components/Panels/ContentPanel";
|
} from "components/Panels/ContentPanel";
|
||||||
import { SubPanel } from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import { PreviewCard } from "components/PreviewCard";
|
|
||||||
import { GetLibraryItemsPreviewQuery } from "graphql/generated";
|
import { GetLibraryItemsPreviewQuery } from "graphql/generated";
|
||||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import {
|
import { prettyItemSubType } from "helpers/formatters";
|
||||||
prettyDate,
|
import { Immutable, LibraryItemUserStatus } from "helpers/types";
|
||||||
prettyinlineTitle,
|
|
||||||
prettyItemSubType,
|
|
||||||
} from "helpers/formatters";
|
|
||||||
import { convertPrice } from "helpers/numbers";
|
|
||||||
import { Immutable } from "helpers/types";
|
|
||||||
import { GetStaticPropsContext } from "next";
|
import { GetStaticPropsContext } from "next";
|
||||||
import { Fragment, useEffect, useState } from "react";
|
import { Fragment, useEffect, useState } from "react";
|
||||||
import { Icon } from "components/Ico";
|
import { Icon } from "components/Ico";
|
||||||
import { WithLabel } from "components/Inputs/WithLabel";
|
import { WithLabel } from "components/Inputs/WithLabel";
|
||||||
import { TextInput } from "components/Inputs/TextInput";
|
import { TextInput } from "components/Inputs/TextInput";
|
||||||
import { Button } from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
|
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
|
||||||
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
import { ToolTip } from "components/ToolTip";
|
||||||
|
import {
|
||||||
|
filterItems,
|
||||||
|
getGroups,
|
||||||
|
sortBy,
|
||||||
|
isUntangibleGroupItem,
|
||||||
|
} from "helpers/libraryItem";
|
||||||
|
import { PreviewCard } from "components/PreviewCard";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
items: NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"];
|
items: NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"];
|
||||||
}
|
}
|
||||||
|
|
||||||
type GroupLibraryItems = Map<string, Immutable<Props["items"]>>;
|
|
||||||
|
|
||||||
const defaultFiltersState = {
|
const defaultFiltersState = {
|
||||||
searchName: "",
|
searchName: "",
|
||||||
showSubitems: false,
|
showSubitems: false,
|
||||||
|
@ -40,10 +42,12 @@ const defaultFiltersState = {
|
||||||
sortingMethod: 0,
|
sortingMethod: 0,
|
||||||
groupingMethod: -1,
|
groupingMethod: -1,
|
||||||
keepInfoVisible: false,
|
keepInfoVisible: false,
|
||||||
|
filterUserStatus: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Library(props: Immutable<Props>): JSX.Element {
|
export default function Library(props: Immutable<Props>): JSX.Element {
|
||||||
const { langui, items: libraryItems, currencies } = props;
|
const { langui, items: libraryItems, currencies } = props;
|
||||||
|
const appLayout = useAppLayout();
|
||||||
|
|
||||||
const [searchName, setSearchName] = useState(defaultFiltersState.searchName);
|
const [searchName, setSearchName] = useState(defaultFiltersState.searchName);
|
||||||
const [showSubitems, setShowSubitems] = useState<boolean>(
|
const [showSubitems, setShowSubitems] = useState<boolean>(
|
||||||
|
@ -64,14 +68,19 @@ export default function Library(props: Immutable<Props>): JSX.Element {
|
||||||
const [keepInfoVisible, setKeepInfoVisible] = useState(
|
const [keepInfoVisible, setKeepInfoVisible] = useState(
|
||||||
defaultFiltersState.keepInfoVisible
|
defaultFiltersState.keepInfoVisible
|
||||||
);
|
);
|
||||||
|
const [filterUserStatus, setFilterUserStatus] = useState<
|
||||||
|
LibraryItemUserStatus | undefined
|
||||||
|
>(defaultFiltersState.filterUserStatus);
|
||||||
|
|
||||||
const [filteredItems, setFilteredItems] = useState(
|
const [filteredItems, setFilteredItems] = useState(
|
||||||
filterItems(
|
filterItems(
|
||||||
|
appLayout,
|
||||||
libraryItems,
|
libraryItems,
|
||||||
searchName,
|
searchName,
|
||||||
showSubitems,
|
showSubitems,
|
||||||
showPrimaryItems,
|
showPrimaryItems,
|
||||||
showSecondaryItems
|
showSecondaryItems,
|
||||||
|
filterUserStatus
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -86,11 +95,13 @@ export default function Library(props: Immutable<Props>): JSX.Element {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFilteredItems(
|
setFilteredItems(
|
||||||
filterItems(
|
filterItems(
|
||||||
|
appLayout,
|
||||||
libraryItems,
|
libraryItems,
|
||||||
searchName,
|
searchName,
|
||||||
showSubitems,
|
showSubitems,
|
||||||
showPrimaryItems,
|
showPrimaryItems,
|
||||||
showSecondaryItems
|
showSecondaryItems,
|
||||||
|
filterUserStatus
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
|
@ -99,6 +110,8 @@ export default function Library(props: Immutable<Props>): JSX.Element {
|
||||||
showPrimaryItems,
|
showPrimaryItems,
|
||||||
showSecondaryItems,
|
showSecondaryItems,
|
||||||
searchName,
|
searchName,
|
||||||
|
filterUserStatus,
|
||||||
|
appLayout,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -181,6 +194,42 @@ export default function Library(props: Immutable<Props>): JSX.Element {
|
||||||
input={<Switch state={keepInfoVisible} setState={setKeepInfoVisible} />}
|
input={<Switch state={keepInfoVisible} setState={setKeepInfoVisible} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className="mt-4 grid grid-flow-col">
|
||||||
|
{/* TODO: Add to Langui */}
|
||||||
|
<ToolTip content="Only display items marked as “I want”">
|
||||||
|
<Button
|
||||||
|
className="rounded-r-none"
|
||||||
|
icon={Icon.Favorite}
|
||||||
|
onClick={() => setFilterUserStatus(LibraryItemUserStatus.Want)}
|
||||||
|
active={filterUserStatus === LibraryItemUserStatus.Want}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip content="Only display items marked as “I have”">
|
||||||
|
<Button
|
||||||
|
className="rounded-none border-l-0"
|
||||||
|
icon={Icon.BackHand}
|
||||||
|
onClick={() => setFilterUserStatus(LibraryItemUserStatus.Have)}
|
||||||
|
active={filterUserStatus === LibraryItemUserStatus.Have}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip content="Only display unmarked items">
|
||||||
|
<Button
|
||||||
|
className="rounded-none border-l-0"
|
||||||
|
icon={Icon.RadioButtonUnchecked}
|
||||||
|
onClick={() => setFilterUserStatus(LibraryItemUserStatus.None)}
|
||||||
|
active={filterUserStatus === LibraryItemUserStatus.None}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
<ToolTip content="Display all items">
|
||||||
|
<Button
|
||||||
|
className="rounded-l-none border-l-0"
|
||||||
|
text={"All"}
|
||||||
|
onClick={() => setFilterUserStatus(undefined)}
|
||||||
|
active={filterUserStatus === undefined}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* TODO: Add to Langui */}
|
{/* TODO: Add to Langui */}
|
||||||
<Button
|
<Button
|
||||||
className="mt-8"
|
className="mt-8"
|
||||||
|
@ -194,6 +243,7 @@ export default function Library(props: Immutable<Props>): JSX.Element {
|
||||||
setSortingMethod(defaultFiltersState.sortingMethod);
|
setSortingMethod(defaultFiltersState.sortingMethod);
|
||||||
setGroupingMethod(defaultFiltersState.groupingMethod);
|
setGroupingMethod(defaultFiltersState.groupingMethod);
|
||||||
setKeepInfoVisible(defaultFiltersState.keepInfoVisible);
|
setKeepInfoVisible(defaultFiltersState.keepInfoVisible);
|
||||||
|
setFilterUserStatus(defaultFiltersState.filterUserStatus);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
|
@ -224,7 +274,7 @@ export default function Library(props: Immutable<Props>): JSX.Element {
|
||||||
>
|
>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<Fragment key={item.id}>
|
<Fragment key={item.id}>
|
||||||
{item.attributes && (
|
{item.id && item.attributes && (
|
||||||
<PreviewCard
|
<PreviewCard
|
||||||
href={`/library/${item.attributes.slug}`}
|
href={`/library/${item.attributes.slug}`}
|
||||||
title={item.attributes.title}
|
title={item.attributes.title}
|
||||||
|
@ -248,6 +298,16 @@ export default function Library(props: Immutable<Props>): JSX.Element {
|
||||||
price: item.attributes.price,
|
price: item.attributes.price,
|
||||||
position: "Bottom",
|
position: "Bottom",
|
||||||
}}
|
}}
|
||||||
|
infoAppend={
|
||||||
|
<PreviewCardCTAs
|
||||||
|
id={item.id}
|
||||||
|
displayCTAs={
|
||||||
|
!isUntangibleGroupItem(
|
||||||
|
item.attributes.metadata?.[0]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
@ -286,221 +346,3 @@ export async function getStaticProps(
|
||||||
props: props,
|
props: props,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGroups(
|
|
||||||
langui: AppStaticProps["langui"],
|
|
||||||
groupByType: number,
|
|
||||||
items: Immutable<Props["items"]>
|
|
||||||
): GroupLibraryItems {
|
|
||||||
switch (groupByType) {
|
|
||||||
case 0: {
|
|
||||||
const typeGroup = new Map();
|
|
||||||
typeGroup.set("Drakengard 1", []);
|
|
||||||
typeGroup.set("Drakengard 1.3", []);
|
|
||||||
typeGroup.set("Drakengard 2", []);
|
|
||||||
typeGroup.set("Drakengard 3", []);
|
|
||||||
typeGroup.set("Drakengard 4", []);
|
|
||||||
typeGroup.set("NieR Gestalt", []);
|
|
||||||
typeGroup.set("NieR Replicant", []);
|
|
||||||
typeGroup.set("NieR Replicant ver.1.22474487139...", []);
|
|
||||||
typeGroup.set("NieR:Automata", []);
|
|
||||||
typeGroup.set("NieR Re[in]carnation", []);
|
|
||||||
typeGroup.set("SINoALICE", []);
|
|
||||||
typeGroup.set("Voice of Cards", []);
|
|
||||||
typeGroup.set("Final Fantasy XIV", []);
|
|
||||||
typeGroup.set("Thou Shalt Not Die", []);
|
|
||||||
typeGroup.set("Bakuken", []);
|
|
||||||
typeGroup.set("YoRHa", []);
|
|
||||||
typeGroup.set("YoRHa Boys", []);
|
|
||||||
typeGroup.set(langui.no_category, []);
|
|
||||||
|
|
||||||
items.map((item) => {
|
|
||||||
if (item.attributes?.categories?.data.length === 0) {
|
|
||||||
typeGroup.get(langui.no_category)?.push(item);
|
|
||||||
} else {
|
|
||||||
item.attributes?.categories?.data.map((category) => {
|
|
||||||
typeGroup.get(category.attributes?.name)?.push(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return typeGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 1: {
|
|
||||||
const group = new Map();
|
|
||||||
group.set(langui.audio ?? "Audio", []);
|
|
||||||
group.set(langui.game ?? "Game", []);
|
|
||||||
group.set(langui.textual ?? "Textual", []);
|
|
||||||
group.set(langui.video ?? "Video", []);
|
|
||||||
group.set(langui.other ?? "Other", []);
|
|
||||||
group.set(langui.group ?? "Group", []);
|
|
||||||
group.set(langui.no_type ?? "No type", []);
|
|
||||||
items.map((item) => {
|
|
||||||
if (item.attributes?.metadata && item.attributes.metadata.length > 0) {
|
|
||||||
switch (item.attributes.metadata[0]?.__typename) {
|
|
||||||
case "ComponentMetadataAudio":
|
|
||||||
group.get(langui.audio ?? "Audio")?.push(item);
|
|
||||||
break;
|
|
||||||
case "ComponentMetadataGame":
|
|
||||||
group.get(langui.game ?? "Game")?.push(item);
|
|
||||||
break;
|
|
||||||
case "ComponentMetadataBooks":
|
|
||||||
group.get(langui.textual ?? "Textual")?.push(item);
|
|
||||||
break;
|
|
||||||
case "ComponentMetadataVideo":
|
|
||||||
group.get(langui.video ?? "Video")?.push(item);
|
|
||||||
break;
|
|
||||||
case "ComponentMetadataOther":
|
|
||||||
group.get(langui.other ?? "Other")?.push(item);
|
|
||||||
break;
|
|
||||||
case "ComponentMetadataGroup":
|
|
||||||
switch (
|
|
||||||
item.attributes.metadata[0]?.subitems_type?.data?.attributes
|
|
||||||
?.slug
|
|
||||||
) {
|
|
||||||
case "audio":
|
|
||||||
group.get(langui.audio ?? "Audio")?.push(item);
|
|
||||||
break;
|
|
||||||
case "video":
|
|
||||||
group.get(langui.video ?? "Video")?.push(item);
|
|
||||||
break;
|
|
||||||
case "game":
|
|
||||||
group.get(langui.game ?? "Game")?.push(item);
|
|
||||||
break;
|
|
||||||
case "textual":
|
|
||||||
group.get(langui.textual ?? "Textual")?.push(item);
|
|
||||||
break;
|
|
||||||
case "mixed":
|
|
||||||
group.get(langui.group ?? "Group")?.push(item);
|
|
||||||
break;
|
|
||||||
default: {
|
|
||||||
throw new Error(
|
|
||||||
"An unexpected subtype of group-metadata was given"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default: {
|
|
||||||
throw new Error("An unexpected type of metadata was given");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
group.get(langui.no_type ?? "No type")?.push(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 2: {
|
|
||||||
const years: number[] = [];
|
|
||||||
items.map((item) => {
|
|
||||||
if (item.attributes?.release_date?.year) {
|
|
||||||
if (!years.includes(item.attributes.release_date.year))
|
|
||||||
years.push(item.attributes.release_date.year);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const group = new Map();
|
|
||||||
years.sort((a, b) => a - b);
|
|
||||||
years.map((year) => {
|
|
||||||
group.set(year.toString(), []);
|
|
||||||
});
|
|
||||||
group.set(langui.no_year ?? "No year", []);
|
|
||||||
items.map((item) => {
|
|
||||||
if (item.attributes?.release_date?.year) {
|
|
||||||
group.get(item.attributes.release_date.year.toString())?.push(item);
|
|
||||||
} else {
|
|
||||||
group.get(langui.no_year ?? "No year")?.push(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
const group = new Map();
|
|
||||||
group.set("", items);
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterItems(
|
|
||||||
items: Immutable<Props["items"]>,
|
|
||||||
searchName: string,
|
|
||||||
showSubitems: boolean,
|
|
||||||
showPrimaryItems: boolean,
|
|
||||||
showSecondaryItems: boolean
|
|
||||||
): Immutable<Props["items"]> {
|
|
||||||
return [...items].filter((item) => {
|
|
||||||
if (!showSubitems && !item.attributes?.root_item) return false;
|
|
||||||
if (
|
|
||||||
showSubitems &&
|
|
||||||
item.attributes?.metadata?.[0]?.__typename === "ComponentMetadataGroup" &&
|
|
||||||
(item.attributes.metadata[0].subtype?.data?.attributes?.slug ===
|
|
||||||
"variant-set" ||
|
|
||||||
item.attributes.metadata[0].subtype?.data?.attributes?.slug ===
|
|
||||||
"relation-set")
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (item.attributes?.primary && !showPrimaryItems) return false;
|
|
||||||
if (!item.attributes?.primary && !showSecondaryItems) return false;
|
|
||||||
if (searchName.length > 1) {
|
|
||||||
if (
|
|
||||||
prettyinlineTitle("", item.attributes?.title, item.attributes?.subtitle)
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchName.toLowerCase())
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortBy(
|
|
||||||
orderByType: number,
|
|
||||||
items: Immutable<Props["items"]>,
|
|
||||||
currencies: AppStaticProps["currencies"]
|
|
||||||
): Immutable<Props["items"]> {
|
|
||||||
switch (orderByType) {
|
|
||||||
case 0:
|
|
||||||
return [...items].sort((a, b) => {
|
|
||||||
const titleA = prettyinlineTitle(
|
|
||||||
"",
|
|
||||||
a.attributes?.title,
|
|
||||||
a.attributes?.subtitle
|
|
||||||
);
|
|
||||||
const titleB = prettyinlineTitle(
|
|
||||||
"",
|
|
||||||
b.attributes?.title,
|
|
||||||
b.attributes?.subtitle
|
|
||||||
);
|
|
||||||
return titleA.localeCompare(titleB);
|
|
||||||
});
|
|
||||||
case 1:
|
|
||||||
return [...items].sort((a, b) => {
|
|
||||||
const priceA = a.attributes?.price
|
|
||||||
? convertPrice(a.attributes.price, currencies[0])
|
|
||||||
: 99999;
|
|
||||||
const priceB = b.attributes?.price
|
|
||||||
? convertPrice(b.attributes.price, currencies[0])
|
|
||||||
: 99999;
|
|
||||||
return priceA - priceB;
|
|
||||||
});
|
|
||||||
case 2:
|
|
||||||
return [...items].sort((a, b) => {
|
|
||||||
const dateA = a.attributes?.release_date
|
|
||||||
? prettyDate(a.attributes.release_date)
|
|
||||||
: "9999";
|
|
||||||
const dateB = b.attributes?.release_date
|
|
||||||
? prettyDate(b.attributes.release_date)
|
|
||||||
: "9999";
|
|
||||||
return dateA.localeCompare(dateB);
|
|
||||||
});
|
|
||||||
default:
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue