Changed material icons to symbols and added wikipage search
This commit is contained in:
parent
46535e7973
commit
ce102ac23b
@ -1,4 +1,4 @@
|
||||
{
|
||||
"upgrade": false,
|
||||
"reject": ["@types/react", "react-hotkeys-hook"]
|
||||
"reject": ["react-hotkeys-hook"]
|
||||
}
|
||||
|
@ -34,7 +34,7 @@
|
||||
- Support for arbitrary React Components and Component Props!
|
||||
- Autogenerated multi-level table of content and anchor links for the different headers
|
||||
- Styling: [Tailwind CSS](https://tailwindcss.com/)
|
||||
- Support for [Material Icons](https://fonts.google.com/icons)
|
||||
- Support for [Material Symbols](https://fonts.google.com/icons)
|
||||
- Support for creating any arbitrary theming mode by swapping CSS variables
|
||||
- Support for Container Queries (media queries at the element level)
|
||||
- The website has a three-column layout, which turns into one-column + 2 toggleable side-menus if the screen is too narrow.
|
||||
|
2007
package-lock.json
generated
2007
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
35
package.json
35
package.json
@ -17,8 +17,6 @@
|
||||
"prettier": "prettier --end-of-line auto --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/material-icons": "^4.5.4",
|
||||
"@fontsource/material-icons-outlined": "^4.5.4",
|
||||
"@fontsource/opendyslexic": "^4.5.4",
|
||||
"@fontsource/share-tech-mono": "^4.5.9",
|
||||
"@fontsource/vollkorn": "^4.5.12",
|
||||
@ -27,11 +25,12 @@
|
||||
"autoprefixer": "^10.4.13",
|
||||
"cuid": "^2.1.8",
|
||||
"isomorphic-dompurify": "^0.24.0",
|
||||
"jotai": "^1.11.2",
|
||||
"jotai": "^1.12.1",
|
||||
"markdown-to-jsx": "^7.1.8",
|
||||
"marked": "^4.2.5",
|
||||
"material-symbols": "^0.4.2",
|
||||
"meilisearch": "^0.30.0",
|
||||
"next": "^13.0.6",
|
||||
"next": "^13.1.1",
|
||||
"nodemailer": "^6.8.0",
|
||||
"rc-slider": "^10.1.0",
|
||||
"react": "18.2.0",
|
||||
@ -49,32 +48,32 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@digitak/esrun": "^3.2.15",
|
||||
"@graphql-codegen/cli": "^2.16.1",
|
||||
"@graphql-codegen/typescript": "2.8.5",
|
||||
"@graphql-codegen/cli": "^2.16.3",
|
||||
"@graphql-codegen/typescript": "2.8.7",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.5.8",
|
||||
"@graphql-codegen/typescript-operations": "^2.5.10",
|
||||
"@graphql-codegen/typescript-operations": "^2.5.12",
|
||||
"@types/marked": "^4.0.8",
|
||||
"@types/node": "18.11.14",
|
||||
"@types/nodemailer": "^6.4.6",
|
||||
"@types/react": "^18.0.22",
|
||||
"@types/react-dom": "^18.0.9",
|
||||
"@types/node": "18.11.18",
|
||||
"@types/nodemailer": "^6.4.7",
|
||||
"@types/react": "^18.0.26",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@types/string-natural-compare": "^3.0.2",
|
||||
"@types/throttle-debounce": "^5.0.0",
|
||||
"@types/turndown": "^5.0.1",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
||||
"@typescript-eslint/parser": "^5.46.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"eslint": "^8.29.0",
|
||||
"eslint-config-next": "13.0.6",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-config-next": "13.1.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-request": "^5.0.0",
|
||||
"next-sitemap": "^3.1.32",
|
||||
"graphql-request": "^5.1.0",
|
||||
"next-sitemap": "^3.1.44",
|
||||
"prettier": "^2.8.1",
|
||||
"prettier-plugin-tailwindcss": "^0.2.1",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"ts-unused-exports": "^8.0.5",
|
||||
"ts-unused-exports": "^9.0.0",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"overrides": {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Ico, Icon } from "./Ico";
|
||||
import { Ico } from "./Ico";
|
||||
import { ToolTip } from "./ToolTip";
|
||||
import { cJoin } from "helpers/className";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
@ -22,8 +22,8 @@ export const AnchorShare = ({ id, className }: Props): JSX.Element => {
|
||||
<ToolTip content={langui.copy_anchor_link} trigger="mouseenter" className="text-sm">
|
||||
<ToolTip content={langui.anchor_link_copied} trigger="click" className="text-sm">
|
||||
<Ico
|
||||
icon={Icon.Link}
|
||||
className={cJoin("transition-color cursor-pointer hover:text-dark", className)}
|
||||
icon="link"
|
||||
className={cJoin("cursor-pointer transition-colors hover:text-dark", className)}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
`${process.env.NEXT_PUBLIC_URL_SELF + window.location.pathname}#${id}`
|
||||
|
@ -1,7 +1,8 @@
|
||||
import Head from "next/head";
|
||||
import { useSwipeable } from "react-swipeable";
|
||||
import { MaterialSymbol } from "material-symbols";
|
||||
import { layout } from "../../design.config";
|
||||
import { Ico, Icon } from "./Ico";
|
||||
import { Ico } from "./Ico";
|
||||
import { MainPanel } from "./Panels/MainPanel";
|
||||
import { isDefined, isUndefined } from "helpers/asserts";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
@ -28,7 +29,7 @@ export interface AppLayoutRequired {
|
||||
|
||||
interface Props extends AppLayoutRequired {
|
||||
subPanel?: React.ReactNode;
|
||||
subPanelIcon?: Icon;
|
||||
subPanelIcon?: MaterialSymbol;
|
||||
contentPanel?: React.ReactNode;
|
||||
contentPanelScroolbar?: boolean;
|
||||
}
|
||||
@ -39,7 +40,7 @@ export const AppLayout = ({
|
||||
subPanel,
|
||||
contentPanel,
|
||||
openGraph,
|
||||
subPanelIcon = Icon.Tune,
|
||||
subPanelIcon = "tune",
|
||||
contentPanelScroolbar = true,
|
||||
}: Props): JSX.Element => {
|
||||
const isMainPanelReduced = useAtomGetter(atoms.layout.mainPanelReduced);
|
||||
@ -148,10 +149,7 @@ export const AppLayout = ({
|
||||
{isDefined(contentPanel) ? (
|
||||
contentPanel
|
||||
) : (
|
||||
<ContentPlaceholder
|
||||
message={langui.select_option_sidebar ?? ""}
|
||||
icon={Icon.ChevronLeft}
|
||||
/>
|
||||
<ContentPlaceholder message={langui.select_option_sidebar ?? ""} icon={"chevron_left"} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -195,7 +193,7 @@ export const AppLayout = ({
|
||||
cIf(!is1ColumnLayout, "hidden")
|
||||
)}>
|
||||
<Ico
|
||||
icon={isMainPanelOpened ? Icon.Close : Icon.Menu}
|
||||
icon={isMainPanelOpened ? "close" : "menu"}
|
||||
className="cursor-pointer !text-2xl"
|
||||
onClick={() => {
|
||||
setMainPanelOpened((current) => !current);
|
||||
@ -213,7 +211,7 @@ export const AppLayout = ({
|
||||
</p>
|
||||
{isDefined(subPanel) && !turnSubIntoContent && (
|
||||
<Ico
|
||||
icon={isSubPanelOpened ? Icon.Close : subPanelIcon}
|
||||
icon={isSubPanelOpened ? "close" : subPanelIcon}
|
||||
className="cursor-pointer !text-2xl"
|
||||
onClick={() => {
|
||||
setSubPanelOpened((current) => !current);
|
||||
@ -230,7 +228,7 @@ export const AppLayout = ({
|
||||
|
||||
interface ContentPlaceholderProps {
|
||||
message: string;
|
||||
icon?: Icon;
|
||||
icon?: MaterialSymbol;
|
||||
}
|
||||
|
||||
const ContentPlaceholder = ({ message, icon }: ContentPlaceholderProps): JSX.Element => (
|
||||
|
@ -4,7 +4,7 @@ import { TranslatedChroniclePreview } from "./ChroniclePreview";
|
||||
import { GetChroniclesChaptersQuery } from "graphql/generated";
|
||||
import { filterHasAttributes } from "helpers/asserts";
|
||||
import { prettyInlineTitle, prettySlug, sJoin } from "helpers/formatters";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { Ico } from "components/Ico";
|
||||
import { compareDate } from "helpers/date";
|
||||
import { TranslatedProps } from "types/TranslatedProps";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
@ -33,7 +33,7 @@ const ChroniclesList = ({ chronicles, currentSlug, title }: Props): JSX.Element
|
||||
<div>
|
||||
<div className="grid place-content-center">
|
||||
<div className="grid cursor-pointer grid-cols-[1em_1fr] gap-4" onClick={toggleOpen}>
|
||||
<Ico className="!text-xl" icon={isOpen ? Icon.ArrowDropUp : Icon.ArrowDropDown} />
|
||||
<Ico className="!text-xl" icon={isOpen ? "arrow_drop_up" : "arrow_drop_down"} />
|
||||
<p className="mb-4 font-headers text-xl">{title}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { Ico } from "components/Ico";
|
||||
import { PageSelector } from "components/Inputs/PageSelector";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { isUndefined } from "helpers/asserts";
|
||||
@ -54,9 +54,9 @@ const DefaultRenderWhenEmpty = () => {
|
||||
<div
|
||||
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
||||
border-dark p-8 text-dark opacity-40">
|
||||
{is3ColumnsLayout && <Ico icon={Icon.ChevronLeft} className="!text-[300%]" />}
|
||||
{is3ColumnsLayout && <Ico icon="chevron_left" className="!text-[300%]" />}
|
||||
<p className="max-w-xs text-2xl">{langui.no_results_message}</p>
|
||||
{!is3ColumnsLayout && <Ico icon={Icon.ChevronRight} className="!text-[300%]" />}
|
||||
{!is3ColumnsLayout && <Ico icon="chevron_right" className="!text-[300%]" />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -4,7 +4,6 @@ import { cIf, cJoin } from "helpers/className";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomSetter } from "helpers/atoms";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { Icon } from "components/Ico";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
@ -68,7 +67,7 @@ export const Popup = ({
|
||||
)}>
|
||||
{withCloseButton && (
|
||||
<div className="absolute right-6 top-6">
|
||||
<Button icon={Icon.Close} onClick={onCloseRequest} />
|
||||
<Button icon="close" onClick={onCloseRequest} />
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
||||
import { MouseEventHandler, useCallback } from "react";
|
||||
import { MaterialSymbol } from "material-symbols";
|
||||
import { Link } from "./Link";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { Ico } from "components/Ico";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
import { TranslatedProps } from "types/TranslatedProps";
|
||||
@ -16,7 +17,7 @@ interface Props {
|
||||
className?: string;
|
||||
href?: string;
|
||||
active?: boolean;
|
||||
icon?: Icon;
|
||||
icon?: MaterialSymbol;
|
||||
text?: string | null | undefined;
|
||||
alwaysNewTab?: boolean;
|
||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||
@ -34,7 +35,7 @@ export const Button = ({
|
||||
id,
|
||||
onClick,
|
||||
onMouseUp,
|
||||
active,
|
||||
active = false,
|
||||
className,
|
||||
icon,
|
||||
text,
|
||||
@ -81,7 +82,13 @@ export const Button = ({
|
||||
</div>
|
||||
)}
|
||||
{isDefinedAndNotEmpty(icon) && (
|
||||
<Ico className="[font-size:150%] [line-height:0.66]" icon={icon} />
|
||||
<Ico
|
||||
className="[font-size:150%] [line-height:0.66]"
|
||||
icon={icon}
|
||||
isFilled={active}
|
||||
opticalSize={size === "normal" ? 24 : 20}
|
||||
weight={size === "normal" ? 500 : 800}
|
||||
/>
|
||||
)}
|
||||
{isDefinedAndNotEmpty(text) && <p className="-translate-y-[0.05em] text-center">{text}</p>}
|
||||
</div>
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Fragment } from "react";
|
||||
import { ToolTip } from "../ToolTip";
|
||||
import { Button } from "./Button";
|
||||
import { Icon } from "components/Ico";
|
||||
import { cJoin } from "helpers/className";
|
||||
import { prettyLanguage } from "helpers/formatters";
|
||||
import { iterateMap } from "helpers/others";
|
||||
@ -54,7 +53,7 @@ export const LanguageSwitcher = ({
|
||||
}>
|
||||
<Button
|
||||
badgeNumber={showBadge && locales.size > 1 ? locales.size : undefined}
|
||||
icon={Icon.Translate}
|
||||
icon="translate"
|
||||
size={size}
|
||||
/>
|
||||
</ToolTip>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Fragment, useCallback } from "react";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { Ico } from "components/Ico";
|
||||
import { arrayMove } from "helpers/others";
|
||||
import { isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
|
||||
@ -71,7 +71,7 @@ export const OrderableList = ({ onChange, items, insertLabels }: Props): JSX.Ele
|
||||
<div className="grid grid-rows-[.8em_.8em] place-items-center">
|
||||
{index > 0 && (
|
||||
<Ico
|
||||
icon={Icon.ArrowDropUp}
|
||||
icon="arrow_drop_up"
|
||||
className="row-start-1 cursor-pointer"
|
||||
onClick={() => {
|
||||
updateOrder(index, index - 1);
|
||||
@ -80,7 +80,7 @@ export const OrderableList = ({ onChange, items, insertLabels }: Props): JSX.Ele
|
||||
)}
|
||||
{index < items.length - 1 && (
|
||||
<Ico
|
||||
icon={Icon.ArrowDropDown}
|
||||
icon="arrow_drop_down"
|
||||
className="row-start-2 cursor-pointer"
|
||||
onClick={() => {
|
||||
updateOrder(index, index + 1);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { ButtonGroup } from "./ButtonGroup";
|
||||
import { Icon } from "components/Ico";
|
||||
import { cJoin } from "helpers/className";
|
||||
|
||||
/*
|
||||
@ -23,23 +22,23 @@ export const PageSelector = ({ page, className, pagesCount, onChange }: Props):
|
||||
{
|
||||
onClick: () => onChange(1),
|
||||
disabled: page === 1,
|
||||
icon: Icon.FirstPage,
|
||||
icon: "first_page",
|
||||
},
|
||||
{
|
||||
onClick: () => page > 1 && onChange(page - 1),
|
||||
disabled: page === 1,
|
||||
icon: Icon.NavigateBefore,
|
||||
icon: "navigate_before",
|
||||
},
|
||||
{ text: `${page} / ${pagesCount}` },
|
||||
{
|
||||
onClick: () => page < pagesCount && onChange(page + 1),
|
||||
disabled: page === pagesCount,
|
||||
icon: Icon.NavigateNext,
|
||||
icon: "navigate_next",
|
||||
},
|
||||
{
|
||||
onClick: () => onChange(pagesCount),
|
||||
disabled: page === pagesCount,
|
||||
icon: Icon.LastPage,
|
||||
icon: "last_page",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Fragment, useCallback, useRef } from "react";
|
||||
import { useBoolean, useOnClickOutside } from "usehooks-ts";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { Ico } from "components/Ico";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
|
||||
/*
|
||||
@ -72,12 +72,12 @@ export const Select = ({
|
||||
</p>
|
||||
{value >= 0 && allowEmpty && (
|
||||
<Ico
|
||||
icon={Icon.Close}
|
||||
icon="close"
|
||||
className="!text-xs"
|
||||
onClick={() => !disabled && onSelectionChanged(-1)}
|
||||
/>
|
||||
)}
|
||||
<Ico onClick={tryToggling} icon={isOpened ? Icon.ArrowDropUp : Icon.ArrowDropDown} />
|
||||
<Ico onClick={tryToggling} icon={isOpened ? "arrow_drop_up" : "arrow_drop_down"} />
|
||||
</div>
|
||||
<div className={cJoin("left-0 right-0 rounded-b-[1em]", cIf(isOpened, "absolute", "hidden"))}>
|
||||
{options.map((option, index) => (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { Ico } from "components/Ico";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
|
||||
@ -42,7 +42,7 @@ export const TextInput = ({
|
||||
<div className="absolute right-4 top-0 bottom-0 grid place-items-center">
|
||||
<Ico
|
||||
className={cJoin("!text-xs", cIf(disabled, "opacity-30 grayscale", "cursor-pointer"))}
|
||||
icon={Icon.Close}
|
||||
icon="close"
|
||||
onClick={() => !disabled && onChange("")}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Icon } from "components/Ico";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { LibraryItemUserStatus } from "types/types";
|
||||
@ -31,7 +30,7 @@ export const PreviewCardCTAs = ({ id, expand = false }: Props): JSX.Element => {
|
||||
)}>
|
||||
<ToolTip content={langui.want_it} disabled={expand}>
|
||||
<Button
|
||||
icon={Icon.Favorite}
|
||||
icon="favorite"
|
||||
text={expand ? langui.want_it : undefined}
|
||||
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Want}
|
||||
onClick={(event) => {
|
||||
@ -49,7 +48,7 @@ export const PreviewCardCTAs = ({ id, expand = false }: Props): JSX.Element => {
|
||||
</ToolTip>
|
||||
<ToolTip content={langui.have_it} disabled={expand}>
|
||||
<Button
|
||||
icon={Icon.BackHand}
|
||||
icon="back_hand"
|
||||
text={expand ? langui.have_it : undefined}
|
||||
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Have}
|
||||
onClick={(event) => {
|
||||
|
@ -3,7 +3,6 @@ import { useState } from "react";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import { Img } from "./Img";
|
||||
import { Button } from "./Inputs/Button";
|
||||
import { Icon } from "components/Ico";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { useFullscreen } from "hooks/useFullscreen";
|
||||
import { Ids } from "types/ids";
|
||||
@ -142,24 +141,17 @@ const ControlButtons = ({
|
||||
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
|
||||
|
||||
const PreviousButton = () => (
|
||||
<Button
|
||||
icon={Icon.NavigateBefore}
|
||||
onClick={onPressPrevious}
|
||||
disabled={!isPreviousImageAvailable}
|
||||
/>
|
||||
<Button icon="navigate_before" onClick={onPressPrevious} disabled={!isPreviousImageAvailable} />
|
||||
);
|
||||
const NextButton = () => (
|
||||
<Button icon={Icon.NavigateNext} onClick={onPressNext} disabled={!isNextImageAvailable} />
|
||||
<Button icon="navigate_next" onClick={onPressNext} disabled={!isNextImageAvailable} />
|
||||
);
|
||||
|
||||
const FullscreenButton = () => (
|
||||
<Button
|
||||
icon={isFullscreen ? Icon.FullscreenExit : Icon.Fullscreen}
|
||||
onClick={toggleFullscreen}
|
||||
/>
|
||||
<Button icon={isFullscreen ? "fullscreen_exit" : "fullscreen"} onClick={toggleFullscreen} />
|
||||
);
|
||||
|
||||
const CloseButton = () => <Button onClick={onCloseRequest} icon={Icon.Close} />;
|
||||
const CloseButton = () => <Button onClick={onCloseRequest} icon="close" />;
|
||||
|
||||
return is1ColumnLayout ? (
|
||||
<>
|
||||
|
@ -10,7 +10,7 @@ import { getAssetURL, ImageQuality } from "helpers/img";
|
||||
import { isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/asserts";
|
||||
import { AnchorShare } from "components/AnchorShare";
|
||||
import { useIntersectionList } from "hooks/useIntersectionList";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { Ico } from "components/Ico";
|
||||
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
@ -268,9 +268,9 @@ const Header = ({ level, title, slug }: HeaderProps): JSX.Element => {
|
||||
<div className="ml-10 flex place-items-center gap-4">
|
||||
{title === "* * *" ? (
|
||||
<div className="mt-8 mb-12 space-x-3 text-dark">
|
||||
<Ico icon={Icon.Emergency} />
|
||||
<Ico icon={Icon.Emergency} />
|
||||
<Ico icon={Icon.Emergency} />
|
||||
<Ico icon="emergency" />
|
||||
<Ico icon="emergency" />
|
||||
<Ico icon="emergency" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="font-headers">{title}</div>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { MouseEventHandler, useCallback } from "react";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { MaterialSymbol } from "material-symbols";
|
||||
import { Ico } from "components/Ico";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
@ -15,7 +16,7 @@ import { DownPressable } from "components/Containers/DownPressable";
|
||||
|
||||
interface Props {
|
||||
url: string;
|
||||
icon?: Icon;
|
||||
icon?: MaterialSymbol;
|
||||
title: string | null | undefined;
|
||||
subtitle?: string | null | undefined;
|
||||
border?: boolean;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { MaterialSymbol } from "material-symbols";
|
||||
import { Ico } from "components/Ico";
|
||||
import { isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
|
||||
/*
|
||||
@ -7,7 +8,7 @@ import { isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
icon?: Icon;
|
||||
icon?: MaterialSymbol;
|
||||
title: string | null | undefined;
|
||||
description?: string | null | undefined;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useCallback } from "react";
|
||||
import { Icon } from "components/Ico";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { TranslatedProps } from "types/TranslatedProps";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
@ -32,7 +31,7 @@ export const ReturnButton = ({ href, title, displayOnlyOn, className }: Props):
|
||||
(!is3ColumnsLayout && displayOnlyOn === "1ColumnLayout") ||
|
||||
isUndefined(displayOnlyOn)) && (
|
||||
<div className={className}>
|
||||
<Button href={href} text={`${langui.return_to} ${title}`} icon={Icon.NavigateBefore} />
|
||||
<Button href={href} text={`${langui.return_to} ${title}`} icon="navigate_before" />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
@ -2,7 +2,6 @@ import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { Icon } from "components/Ico";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
import { Link } from "components/Inputs/Link";
|
||||
@ -47,7 +46,7 @@ export const MainPanel = (): JSX.Element => {
|
||||
setMainPanelReduced((current) => !current);
|
||||
}}
|
||||
className="z-50 bg-light !px-2"
|
||||
icon={isMainPanelReduced ? Icon.ChevronRight : Icon.ChevronLeft}
|
||||
icon={isMainPanelReduced ? "chevron_right" : "chevron_left"}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -74,28 +73,24 @@ export const MainPanel = (): JSX.Element => {
|
||||
)}>
|
||||
<ToolTip
|
||||
content={<h3 className="text-2xl">{langui.open_settings}</h3>}
|
||||
placement="right"
|
||||
className="text-left"
|
||||
disabled={!isMainPanelReduced}>
|
||||
placement={isMainPanelReduced ? "right" : "top"}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setSettingsOpened(true);
|
||||
sendAnalytics("Settings", "Open settings");
|
||||
}}
|
||||
icon={Icon.Settings}
|
||||
icon="discover_tune"
|
||||
/>
|
||||
</ToolTip>
|
||||
<ToolTip
|
||||
content={<h3 className="text-2xl">{langui.open_search}</h3>}
|
||||
placement="right"
|
||||
className="text-left"
|
||||
disabled={!isMainPanelReduced}>
|
||||
placement={isMainPanelReduced ? "right" : "top"}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setSearchOpened(true);
|
||||
sendAnalytics("Search", "Open search");
|
||||
}}
|
||||
icon={Icon.Search}
|
||||
icon="search"
|
||||
/>
|
||||
</ToolTip>
|
||||
</div>
|
||||
@ -106,7 +101,7 @@ export const MainPanel = (): JSX.Element => {
|
||||
|
||||
<NavOption
|
||||
url="/library"
|
||||
icon={Icon.LibraryBooks}
|
||||
icon="auto_stories"
|
||||
title={langui.library}
|
||||
subtitle={langui.library_short_description}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
@ -114,7 +109,7 @@ export const MainPanel = (): JSX.Element => {
|
||||
|
||||
<NavOption
|
||||
url="/contents"
|
||||
icon={Icon.Workspaces}
|
||||
icon="workspaces"
|
||||
title={langui.contents}
|
||||
subtitle={langui.contents_short_description}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
@ -122,7 +117,7 @@ export const MainPanel = (): JSX.Element => {
|
||||
|
||||
<NavOption
|
||||
url="/wiki"
|
||||
icon={Icon.TravelExplore}
|
||||
icon="travel_explore"
|
||||
title={langui.wiki}
|
||||
subtitle={langui.wiki_short_description}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
@ -130,7 +125,7 @@ export const MainPanel = (): JSX.Element => {
|
||||
|
||||
<NavOption
|
||||
url="/chronicles"
|
||||
icon={Icon.WatchLater}
|
||||
icon="schedule"
|
||||
title={langui.chronicles}
|
||||
subtitle={langui.chronicles_short_description}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
@ -140,7 +135,7 @@ export const MainPanel = (): JSX.Element => {
|
||||
|
||||
<NavOption
|
||||
url="/news"
|
||||
icon={Icon.Feed}
|
||||
icon="newspaper"
|
||||
title={langui.news}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
/>
|
||||
@ -148,7 +143,7 @@ export const MainPanel = (): JSX.Element => {
|
||||
{/*
|
||||
<NavOption
|
||||
url="/merch"
|
||||
icon={Icon.Store}
|
||||
icon="store"
|
||||
title={langui.merch}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
/>
|
||||
@ -156,21 +151,21 @@ export const MainPanel = (): JSX.Element => {
|
||||
|
||||
<NavOption
|
||||
url="https://gallery.accords-library.com/posts/"
|
||||
icon={Icon.Collections}
|
||||
icon="perm_media"
|
||||
title={langui.gallery}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
/>
|
||||
|
||||
<NavOption
|
||||
url="/archives"
|
||||
icon={Icon.Inventory2}
|
||||
icon="save"
|
||||
title={langui.archives}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
/>
|
||||
|
||||
<NavOption
|
||||
url="/about-us"
|
||||
icon={Icon.Info}
|
||||
icon="info"
|
||||
title={langui.about_us}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
/>
|
||||
|
@ -13,10 +13,12 @@ import {
|
||||
MeiliLibraryItem,
|
||||
MeiliPost,
|
||||
MeiliVideo,
|
||||
MeiliWikiPage,
|
||||
} from "shared/meilisearch-graphql-typings/meiliTypes";
|
||||
import { getVideoThumbnailURL } from "helpers/videos";
|
||||
import { UpPressable } from "components/Containers/UpPressable";
|
||||
import { prettyItemSubType, prettySlug } from "helpers/formatters";
|
||||
import { Ico } from "components/Ico";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
@ -38,6 +40,7 @@ export const SearchPopup = (): JSX.Element => {
|
||||
const [contents, setContents] = useState<CustomSearchResponse<MeiliContent>>();
|
||||
const [videos, setVideos] = useState<CustomSearchResponse<MeiliVideo>>();
|
||||
const [posts, setPosts] = useState<CustomSearchResponse<MeiliPost>>();
|
||||
const [wikiPages, setWikiPages] = useState<CustomSearchResponse<MeiliWikiPage>>();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLibraryItems = async () => {
|
||||
@ -109,12 +112,28 @@ export const SearchPopup = (): JSX.Element => {
|
||||
setPosts(searchResult);
|
||||
};
|
||||
|
||||
const fetchWikiPages = async () => {
|
||||
const searchResult = await meiliSearch(MeiliIndices.WIKI_PAGE, query, {
|
||||
limit: SEARCH_LIMIT,
|
||||
attributesToHighlight: [
|
||||
"translations.title",
|
||||
"translations.aliases",
|
||||
"translations.summary",
|
||||
"translations.displayable_description",
|
||||
],
|
||||
attributesToCrop: ["translations.displayable_description"],
|
||||
});
|
||||
setWikiPages(searchResult);
|
||||
};
|
||||
|
||||
if (query === "") {
|
||||
setWikiPages(undefined);
|
||||
setLibraryItems(undefined);
|
||||
setContents(undefined);
|
||||
setVideos(undefined);
|
||||
setPosts(undefined);
|
||||
} else {
|
||||
fetchWikiPages();
|
||||
fetchLibraryItems();
|
||||
fetchContents();
|
||||
fetchVideos();
|
||||
@ -130,10 +149,13 @@ export const SearchPopup = (): JSX.Element => {
|
||||
sendAnalytics("Search", "Close search");
|
||||
}}
|
||||
fillViewport>
|
||||
<h2 className="text-2xl">{langui.search}</h2>
|
||||
<h2 className="inline-flex place-items-center gap-2 text-2xl">
|
||||
<Ico icon="search" isFilled />
|
||||
{langui.search}
|
||||
</h2>
|
||||
<TextInput onChange={setQuery} value={query} placeholder={langui.search_title} />
|
||||
|
||||
<div className="flex flex-wrap gap-12 gap-x-16">
|
||||
<div className="flex w-full flex-wrap gap-12 gap-x-16">
|
||||
{isDefined(libraryItems) && (
|
||||
<SearchResultSection
|
||||
title={langui.library}
|
||||
@ -184,8 +206,8 @@ export const SearchPopup = (): JSX.Element => {
|
||||
<div className="flex flex-wrap items-start gap-x-6 gap-y-8">
|
||||
{contents.hits.map((item) => (
|
||||
<PreviewCard
|
||||
className="w-56"
|
||||
key={item.id}
|
||||
className="w-56"
|
||||
href={`/contents/${item.slug}`}
|
||||
pre_title={item._formatted.translations?.[0]?.pre_title}
|
||||
title={item._formatted.translations?.[0]?.title}
|
||||
@ -213,6 +235,56 @@ export const SearchPopup = (): JSX.Element => {
|
||||
</SearchResultSection>
|
||||
)}
|
||||
|
||||
{isDefined(wikiPages) && (
|
||||
<SearchResultSection
|
||||
title={langui.wiki}
|
||||
href={"/wiki"}
|
||||
totalHits={wikiPages.estimatedTotalHits}>
|
||||
<div className="flex flex-wrap items-start gap-x-6 gap-y-8">
|
||||
{wikiPages.hits.map((item) => (
|
||||
<TranslatedPreviewCard
|
||||
key={item.id}
|
||||
className="w-56"
|
||||
href={`/wiki/${item.slug}`}
|
||||
translations={filterHasAttributes(item._formatted.translations, [
|
||||
"language.data.attributes.code",
|
||||
] as const).map(
|
||||
({
|
||||
aliases,
|
||||
summary,
|
||||
displayable_description,
|
||||
language,
|
||||
...otherAttributes
|
||||
}) => ({
|
||||
...otherAttributes,
|
||||
subtitle:
|
||||
aliases && aliases.length > 0
|
||||
? aliases.map((alias) => alias?.alias).join("・")
|
||||
: undefined,
|
||||
description: containsHighlight(displayable_description)
|
||||
? displayable_description
|
||||
: summary,
|
||||
language: language.data.attributes.code,
|
||||
})
|
||||
)}
|
||||
fallback={{ title: prettySlug(item.slug) }}
|
||||
thumbnail={item.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio={"4/3"}
|
||||
thumbnailRounded
|
||||
thumbnailForceAspectRatio
|
||||
keepInfoVisible
|
||||
topChips={filterHasAttributes(item.tags?.data, ["attributes"] as const).map(
|
||||
(tag) => tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
|
||||
)}
|
||||
bottomChips={filterHasAttributes(item.categories?.data, [
|
||||
"attributes",
|
||||
] as const).map((category) => category.attributes.short)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</SearchResultSection>
|
||||
)}
|
||||
|
||||
{isDefined(posts) && (
|
||||
<SearchResultSection
|
||||
title={langui.news}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Icon } from "components/Ico";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { ButtonGroup } from "components/Inputs/ButtonGroup";
|
||||
import { OrderableList } from "components/Inputs/OrderableList";
|
||||
@ -14,6 +13,7 @@ import { filterHasAttributes, isDefined } from "helpers/asserts";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter, useAtomPair } from "helpers/atoms";
|
||||
import { ThemeMode } from "contexts/settings";
|
||||
import { Ico } from "components/Ico";
|
||||
|
||||
export const SettingsPopup = (): JSX.Element => {
|
||||
const [preferredLanguages, setPreferredLanguages] = useAtomPair(
|
||||
@ -50,7 +50,10 @@ export const SettingsPopup = (): JSX.Element => {
|
||||
setSettingsOpened(false);
|
||||
sendAnalytics("Settings", "Close settings");
|
||||
}}>
|
||||
<h2 className="text-2xl">{langui.settings}</h2>
|
||||
<h2 className="inline-flex place-items-center gap-2 text-2xl">
|
||||
<Ico icon="discover_tune" isFilled />
|
||||
{langui.settings}
|
||||
</h2>
|
||||
|
||||
<div
|
||||
className={cJoin(
|
||||
@ -154,7 +157,7 @@ export const SettingsPopup = (): JSX.Element => {
|
||||
})}%)`
|
||||
);
|
||||
},
|
||||
icon: Icon.TextDecrease,
|
||||
icon: "text_decrease",
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
@ -175,7 +178,7 @@ export const SettingsPopup = (): JSX.Element => {
|
||||
})}%)`
|
||||
);
|
||||
},
|
||||
icon: Icon.TextIncrease,
|
||||
icon: "text_increase",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
@ -2,7 +2,7 @@ import { useCallback } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { Markdown } from "./Markdown/Markdown";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { Ico } from "components/Ico";
|
||||
import { Img } from "components/Img";
|
||||
import { UpPressable } from "components/Containers/UpPressable";
|
||||
import { DatePickerFragment, PricePickerFragment, UploadImageFragment } from "graphql/generated";
|
||||
@ -84,25 +84,25 @@ export const PreviewCard = ({
|
||||
<div className="flex w-full flex-row flex-wrap gap-x-3">
|
||||
{metadata.releaseDate && (
|
||||
<p className="text-sm">
|
||||
<Ico icon={Icon.Event} className="mr-1 translate-y-[.15em] !text-base" />
|
||||
<Ico icon="event" className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{prettyDate(metadata.releaseDate, router.locale)}
|
||||
</p>
|
||||
)}
|
||||
{metadata.price && (
|
||||
<p className="justify-self-end text-sm">
|
||||
<Ico icon={Icon.ShoppingCart} className="mr-1 translate-y-[.15em] !text-base" />
|
||||
<Ico icon="shopping_cart" className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{prettyPrice(metadata.price, currencies, currency)}
|
||||
</p>
|
||||
)}
|
||||
{metadata.views && (
|
||||
<p className="text-sm">
|
||||
<Ico icon={Icon.Visibility} className="mr-1 translate-y-[.15em] !text-base" />
|
||||
<Ico icon="visibility" className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{prettyShortenNumber(metadata.views)}
|
||||
</p>
|
||||
)}
|
||||
{metadata.author && (
|
||||
<p className="text-sm">
|
||||
<Ico icon={Icon.Person} className="mr-1 translate-y-[.15em] !text-base" />
|
||||
<Ico icon="person" className="mr-1 translate-y-[.15em] !text-base" />
|
||||
<Markdown text={metadata.author} className="inline-block" />
|
||||
</p>
|
||||
)}
|
||||
@ -142,7 +142,7 @@ export const PreviewCard = ({
|
||||
className="absolute inset-0 grid place-content-center bg-shade bg-opacity-0
|
||||
text-light transition-colors group-hover:bg-opacity-50">
|
||||
<Ico
|
||||
icon={Icon.PlayCircleOutline}
|
||||
icon="play_circle"
|
||||
className="!text-6xl text-black opacity-0 drop-shadow-lg transition-opacity
|
||||
shadow-shade group-hover:opacity-100"
|
||||
/>
|
||||
|
@ -3,7 +3,7 @@ import { useHotkeys } from "react-hotkeys-hook";
|
||||
import naturalCompare from "string-natural-compare";
|
||||
import { Chip } from "./Chip";
|
||||
import { PageSelector } from "./Inputs/PageSelector";
|
||||
import { Ico, Icon } from "./Ico";
|
||||
import { Ico } from "./Ico";
|
||||
import { cJoin } from "helpers/className";
|
||||
import { isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
||||
@ -229,9 +229,9 @@ const DefaultRenderWhenEmpty = () => {
|
||||
<div
|
||||
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
||||
border-dark p-8 text-dark opacity-40">
|
||||
{is3ColumnsLayout && <Ico icon={Icon.ChevronLeft} className="!text-[300%]" />}
|
||||
{is3ColumnsLayout && <Ico icon="chevron_left" className="!text-[300%]" />}
|
||||
<p className="max-w-xs text-2xl">{langui.no_results_message}</p>
|
||||
{!is3ColumnsLayout && <Ico icon={Icon.ChevronRight} className="!text-[300%]" />}
|
||||
{!is3ColumnsLayout && <Ico icon="chevron_right" className="!text-[300%]" />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import "@fontsource/material-icons";
|
||||
import "@fontsource/material-icons-outlined";
|
||||
import "material-symbols/rounded.css";
|
||||
import "@fontsource/opendyslexic/400.css";
|
||||
import "@fontsource/share-tech-mono/400.css";
|
||||
import "@fontsource/opendyslexic/700.css";
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { GetStaticProps } from "next";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { Icon } from "components/Ico";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Containers/SubPanel";
|
||||
@ -24,7 +23,7 @@ const AboutUs = (props: Props): JSX.Element => {
|
||||
subPanel={
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.Info}
|
||||
icon="info"
|
||||
title={langui.about_us}
|
||||
description={langui.about_us_description}
|
||||
/>
|
||||
|
@ -3,7 +3,6 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Containers/SubPanel";
|
||||
import { Icon } from "components/Ico";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
@ -21,11 +20,7 @@ const Archives = (props: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.Inventory}
|
||||
title={langui.archives}
|
||||
description={langui.archives_description}
|
||||
/>
|
||||
<PanelHeader icon="save" title={langui.archives} description={langui.archives_description} />
|
||||
<HorizontalLine />
|
||||
<NavOption title={"Videos"} url="/archives/videos/" border />
|
||||
</SubPanel>
|
||||
|
@ -3,7 +3,6 @@ import { useEffect, useMemo, useState } from "react";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { z } from "zod";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { Icon } from "components/Ico";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { TextInput } from "components/Inputs/TextInput";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
@ -167,7 +166,7 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
|
||||
/>
|
||||
|
||||
<PanelHeader
|
||||
icon={Icon.Movie}
|
||||
icon="movie"
|
||||
title={channel.title}
|
||||
description={`${channel.subscribers.toLocaleString()} ${langui.subscribers?.toLowerCase()}`}
|
||||
/>
|
||||
@ -223,7 +222,7 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
|
||||
<Button
|
||||
className="mt-8"
|
||||
text={langui.reset_all_filters}
|
||||
icon={Icon.Replay}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setOnlyShowGone(DEFAULT_FILTERS_STATE.onlyShowGone);
|
||||
setPage(DEFAULT_FILTERS_STATE.page);
|
||||
|
@ -3,7 +3,6 @@ import { useEffect, useMemo, useState } from "react";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { z } from "zod";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { Icon } from "components/Ico";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { TextInput } from "components/Inputs/TextInput";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
@ -160,11 +159,7 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => {
|
||||
className="mb-10"
|
||||
/>
|
||||
|
||||
<PanelHeader
|
||||
icon={Icon.Movie}
|
||||
title={langui.videos}
|
||||
description={langui.archives_description}
|
||||
/>
|
||||
<PanelHeader icon="movie" title={langui.videos} description={langui.archives_description} />
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
@ -217,7 +212,7 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => {
|
||||
<Button
|
||||
className="mt-8"
|
||||
text={langui.reset_all_filters}
|
||||
icon={Icon.Replay}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setPage(1);
|
||||
setOnlyShowGone(DEFAULT_FILTERS_STATE.onlyShowGone);
|
||||
|
@ -2,7 +2,7 @@ import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { Ico } from "components/Ico";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { InsetBox } from "components/Containers/InsetBox";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
@ -79,18 +79,18 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
||||
<h1 className="text-2xl">{video.title}</h1>
|
||||
<div className="flex w-full flex-row flex-wrap gap-x-6">
|
||||
<p>
|
||||
<Ico icon={Icon.Event} className="mr-1 translate-y-[.15em] !text-base" />
|
||||
<Ico icon="event" className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{prettyDate(video.published_date, router.locale)}
|
||||
</p>
|
||||
<p>
|
||||
<Ico icon={Icon.Visibility} className="mr-1 translate-y-[.15em] !text-base" />
|
||||
<Ico icon="visibility" className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{isContentPanelAtLeast4xl
|
||||
? video.views.toLocaleString()
|
||||
: prettyShortenNumber(video.views)}
|
||||
</p>
|
||||
{video.channel?.data?.attributes && (
|
||||
<p>
|
||||
<Ico icon={Icon.ThumbUp} className="mr-1 translate-y-[.15em] !text-base" />
|
||||
<Ico icon="thumb_up" className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{isContentPanelAtLeast4xl
|
||||
? video.likes.toLocaleString()
|
||||
: prettyShortenNumber(video.likes)}
|
||||
|
@ -13,7 +13,6 @@ import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { GetChroniclesChaptersQuery } from "graphql/generated";
|
||||
import { prettyInlineTitle, prettySlug } from "helpers/formatters";
|
||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||
import { Icon } from "components/Ico";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
||||
import { getDescription } from "helpers/description";
|
||||
@ -21,6 +20,8 @@ import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
||||
import { Ids } from "types/ids";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
@ -34,6 +35,8 @@ interface Props extends AppLayoutRequired {
|
||||
|
||||
const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
useScrollTopOnChange(Ids.ContentPanel, [chronicle.slug]);
|
||||
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||
items: chronicle.translations,
|
||||
languageExtractor: useCallback(
|
||||
@ -143,7 +146,7 @@ const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element =
|
||||
<AppLayout
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
subPanelIcon={Icon.FormatListNumbered}
|
||||
subPanelIcon="format_list_numbered"
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
|
@ -2,7 +2,6 @@ import { GetStaticProps } from "next";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Containers/SubPanel";
|
||||
import { Icon } from "components/Ico";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { GetChroniclesChaptersQuery } from "graphql/generated";
|
||||
import { filterHasAttributes } from "helpers/asserts";
|
||||
@ -28,7 +27,7 @@ const Chronicles = ({ chapters, ...otherProps }: Props): JSX.Element => {
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.WatchLater}
|
||||
icon="schedule"
|
||||
title={langui.chronicles}
|
||||
description={langui.chronicles_description}
|
||||
/>
|
||||
|
@ -12,7 +12,6 @@ import { TextInput } from "components/Inputs/TextInput";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
||||
import { Icon } from "components/Ico";
|
||||
import {
|
||||
filterDefined,
|
||||
filterHasAttributes,
|
||||
@ -132,14 +131,14 @@ const Contents = (props: Props): JSX.Element => {
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.Workspaces}
|
||||
icon="workspaces"
|
||||
title={langui.contents}
|
||||
description={langui.contents_description}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
<Button href="/contents" text={langui.switch_to_folder_view} icon={Icon.Folder} />
|
||||
<Button href="/contents" text={langui.switch_to_folder_view} icon="folder" />
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
@ -189,7 +188,7 @@ const Contents = (props: Props): JSX.Element => {
|
||||
<Button
|
||||
className="mt-8"
|
||||
text={langui.reset_all_filters}
|
||||
icon={Icon.Replay}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setPage(1);
|
||||
setQuery(DEFAULT_FILTERS_STATE.query);
|
||||
@ -243,12 +242,7 @@ const Contents = (props: Props): JSX.Element => {
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
subPanelIcon={Icon.Search}
|
||||
{...props}
|
||||
/>
|
||||
<AppLayout subPanel={subPanel} contentPanel={contentPanel} subPanelIcon="search" {...props} />
|
||||
);
|
||||
};
|
||||
export default Contents;
|
||||
|
@ -9,7 +9,7 @@ import { GetContentsFolderQuery } from "graphql/generated";
|
||||
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { SmartList } from "components/SmartList";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { Ico } from "components/Ico";
|
||||
import { Button, TranslatedButton } from "components/Inputs/Button";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Containers/SubPanel";
|
||||
@ -39,14 +39,14 @@ const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Elemen
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.Workspaces}
|
||||
icon="workspaces"
|
||||
title={langui.contents}
|
||||
description={langui.contents_description}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
<Button href="/contents/all" text={langui.switch_to_grid_view} icon={Icon.Apps} />
|
||||
<Button href="/contents/all" text={langui.switch_to_grid_view} icon="apps" />
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
@ -56,7 +56,7 @@ const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Elemen
|
||||
{folder.parent_folder?.data?.attributes && (
|
||||
<>
|
||||
{folder.parent_folder.data.attributes.slug === "root" ? (
|
||||
<Button href="/contents" icon={Icon.Home} />
|
||||
<Button href="/contents" icon="home" />
|
||||
) : (
|
||||
<TranslatedButton
|
||||
href={`/contents/folder/${folder.parent_folder.data.attributes.slug}`}
|
||||
@ -71,12 +71,12 @@ const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Elemen
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Ico icon={Icon.ChevronRight} />
|
||||
<Ico icon="chevron_right" />
|
||||
</>
|
||||
)}
|
||||
|
||||
{folder.slug === "root" ? (
|
||||
<Button href="/contents" icon={Icon.Home} active />
|
||||
<Button href="/contents" icon="home" active />
|
||||
) : (
|
||||
<TranslatedButton
|
||||
translations={filterHasAttributes(folder.titles, [
|
||||
|
@ -7,7 +7,6 @@ import { Markdawn, TableOfContents } from "components/Markdown/Markdawn";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel";
|
||||
import { Popup } from "components/Containers/Popup";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { Icon } from "components/Ico";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
|
||||
@ -208,15 +207,15 @@ const Editor = (props: Props): JSX.Element => {
|
||||
<Button onClick={() => preline("###### ")} text={"H6"} />
|
||||
</div>
|
||||
}>
|
||||
<Button icon={Icon.Title} />
|
||||
<Button icon="title" />
|
||||
</ToolTip>
|
||||
|
||||
<ToolTip placement="bottom" content={<h3 className="text-lg">Toggle Bold</h3>}>
|
||||
<Button onClick={() => toggleWrap("**")} icon={Icon.FormatBold} />
|
||||
<Button onClick={() => toggleWrap("**")} icon="format_bold" />
|
||||
</ToolTip>
|
||||
|
||||
<ToolTip placement="bottom" content={<h3 className="text-lg">Toggle Italic</h3>}>
|
||||
<Button onClick={() => toggleWrap("_")} icon={Icon.FormatItalic} />
|
||||
<Button onClick={() => toggleWrap("_")} icon="format_italic" />
|
||||
</ToolTip>
|
||||
|
||||
<ToolTip
|
||||
@ -230,7 +229,7 @@ const Editor = (props: Props): JSX.Element => {
|
||||
</p>
|
||||
</>
|
||||
}>
|
||||
<Button onClick={() => toggleWrap("`")} icon={Icon.Code} />
|
||||
<Button onClick={() => toggleWrap("`")} icon="code" />
|
||||
</ToolTip>
|
||||
|
||||
<ToolTip
|
||||
@ -246,7 +245,7 @@ const Editor = (props: Props): JSX.Element => {
|
||||
insert("[^x]");
|
||||
appendDoc("\n\n[^x]: This is a footnote.");
|
||||
}}
|
||||
icon={Icon.Superscript}
|
||||
icon="superscript"
|
||||
/>
|
||||
</ToolTip>
|
||||
|
||||
@ -267,7 +266,7 @@ const Editor = (props: Props): JSX.Element => {
|
||||
<h3 className="text-lg">Transcript container</h3>
|
||||
</>
|
||||
}>
|
||||
<Button onClick={() => wrap("Transcript", {}, true)} icon={Icon.AddBox} />
|
||||
<Button onClick={() => wrap("Transcript", {}, true)} icon="add_box" />
|
||||
</ToolTip>
|
||||
<ToolTip
|
||||
placement="right"
|
||||
@ -282,20 +281,20 @@ const Editor = (props: Props): JSX.Element => {
|
||||
}>
|
||||
<Button
|
||||
onClick={() => wrap("Line", { name: "speaker" })}
|
||||
icon={Icon.RecordVoiceOver}
|
||||
icon="record_voice_over"
|
||||
/>
|
||||
</ToolTip>
|
||||
</div>
|
||||
</>
|
||||
}>
|
||||
<Button icon={Icon.RecordVoiceOver} />
|
||||
<Button icon="record_voice_over" />
|
||||
</ToolTip>
|
||||
|
||||
<ToolTip placement="bottom" content={<h3 className="text-lg">Inset box</h3>}>
|
||||
<Button onClick={() => wrap("InsetBox", {}, true)} icon={Icon.CheckBoxOutlineBlank} />
|
||||
<Button onClick={() => wrap("InsetBox", {}, true)} icon="check_box_outline_blank" />
|
||||
</ToolTip>
|
||||
<ToolTip placement="bottom" content={<h3 className="text-lg">Scene break</h3>}>
|
||||
<Button onClick={() => insert("\n* * *\n")} icon={Icon.MoreHoriz} />
|
||||
<Button onClick={() => insert("\n* * *\n")} icon="more_horiz" />
|
||||
</ToolTip>
|
||||
<ToolTip
|
||||
content={
|
||||
@ -311,7 +310,7 @@ const Editor = (props: Props): JSX.Element => {
|
||||
}>
|
||||
<Button
|
||||
onClick={() => insert("[Link name](https://domain.com)")}
|
||||
icon={Icon.Link}
|
||||
icon="link"
|
||||
text={"External"}
|
||||
/>
|
||||
</ToolTip>
|
||||
@ -326,7 +325,7 @@ const Editor = (props: Props): JSX.Element => {
|
||||
</p>
|
||||
</>
|
||||
}>
|
||||
<Button onClick={() => wrap("IntraLink", {})} icon={Icon.Link} text={"Internal"} />
|
||||
<Button onClick={() => wrap("IntraLink", {})} icon="link" text={"Internal"} />
|
||||
</ToolTip>
|
||||
<ToolTip
|
||||
placement="right"
|
||||
@ -341,19 +340,19 @@ const Editor = (props: Props): JSX.Element => {
|
||||
}>
|
||||
<Button
|
||||
onClick={() => wrap("IntraLink", { target: "target" })}
|
||||
icon={Icon.Link}
|
||||
icon="link"
|
||||
text="Internal (w/ target)"
|
||||
/>
|
||||
</ToolTip>
|
||||
</div>
|
||||
}>
|
||||
<Button icon={Icon.Link} />
|
||||
<Button icon="link" />
|
||||
</ToolTip>
|
||||
|
||||
<ToolTip
|
||||
placement="bottom"
|
||||
content={<h3 className="text-lg">Player’s name placeholder</h3>}>
|
||||
<Button onClick={() => insert("@player")} icon={Icon.Person} />
|
||||
<Button onClick={() => insert("@player")} icon="person" />
|
||||
</ToolTip>
|
||||
|
||||
<ToolTip placement="bottom" content={<h3 className="text-lg">Open HTML Converter</h3>}>
|
||||
@ -361,7 +360,7 @@ const Editor = (props: Props): JSX.Element => {
|
||||
onClick={() => {
|
||||
setConverterOpened(true);
|
||||
}}
|
||||
icon={Icon.Html}
|
||||
icon="html"
|
||||
/>
|
||||
</ToolTip>
|
||||
</div>
|
||||
|
@ -7,7 +7,6 @@ import { getLangui } from "graphql/fetchLocalData";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { Icon } from "components/Ico";
|
||||
import { cJoin } from "helpers/className";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
@ -172,24 +171,24 @@ const DesignSystem = (props: Props): JSX.Element => {
|
||||
<p>Icon + Text</p>
|
||||
|
||||
<p className="self-center justify-self-start">Normal</p>
|
||||
<Button icon={Icon.Check} />
|
||||
<Button icon="check" />
|
||||
<Button text="Label" />
|
||||
<Button icon={Icon.NavigateBefore} text="Label" />
|
||||
<Button icon="navigate_before" text="Label" />
|
||||
|
||||
<p className="self-center justify-self-start">Active</p>
|
||||
<Button icon={Icon.Camera} active />
|
||||
<Button icon="camera" active />
|
||||
<Button text="Label" active />
|
||||
<Button icon={Icon.NavigateBefore} text="Label" active />
|
||||
<Button icon="navigate_before" text="Label" active />
|
||||
|
||||
<p className="self-center justify-self-start">Disabled</p>
|
||||
<Button icon={Icon.Air} disabled />
|
||||
<Button icon="air" disabled />
|
||||
<Button text="Label" disabled />
|
||||
<Button icon={Icon.NavigateBefore} text="Label" disabled />
|
||||
<Button icon="navigate_before" text="Label" disabled />
|
||||
|
||||
<p className="self-center justify-self-start">Badge</p>
|
||||
<Button icon={Icon.Snooze} badgeNumber={5} />
|
||||
<Button icon="snooze" badgeNumber={5} />
|
||||
<Button text="Label" badgeNumber={12} />
|
||||
<Button icon={Icon.NavigateBefore} text="Label" badgeNumber={201} />
|
||||
<Button icon="navigate_before" text="Label" badgeNumber={201} />
|
||||
</div>
|
||||
|
||||
<HorizontalLine />
|
||||
@ -197,43 +196,39 @@ const DesignSystem = (props: Props): JSX.Element => {
|
||||
|
||||
<div className="grid grid-cols-[repeat(4,auto)] place-content-center gap-4">
|
||||
<p className="self-center justify-self-start">Normal</p>
|
||||
<Button icon={Icon.Check} size={"small"} />
|
||||
<Button icon="check" size={"small"} />
|
||||
<Button text="Label" size={"small"} />
|
||||
<Button icon={Icon.NavigateBefore} text="Label" size={"small"} />
|
||||
<Button icon="navigate_before" text="Label" size={"small"} />
|
||||
|
||||
<p className="self-center justify-self-start">Active</p>
|
||||
<Button icon={Icon.Camera} active size={"small"} />
|
||||
<Button icon="camera" active size={"small"} />
|
||||
<Button text="Label" active size={"small"} />
|
||||
<Button icon={Icon.NavigateBefore} text="Label" active size={"small"} />
|
||||
<Button icon="navigate_before" text="Label" active size={"small"} />
|
||||
|
||||
<p className="self-center justify-self-start">Disabled</p>
|
||||
<Button icon={Icon.Air} disabled size={"small"} />
|
||||
<Button icon="air" disabled size={"small"} />
|
||||
<Button text="Label" disabled size={"small"} />
|
||||
<Button icon={Icon.NavigateBefore} text="Label" disabled size={"small"} />
|
||||
<Button icon="navigate_before" text="Label" disabled size={"small"} />
|
||||
|
||||
<p className="self-center justify-self-start">Badge</p>
|
||||
<Button icon={Icon.Snooze} badgeNumber={5} size={"small"} />
|
||||
<Button icon="snooze" badgeNumber={5} size={"small"} />
|
||||
<Button text="Label" badgeNumber={12} size={"small"} />
|
||||
<Button icon={Icon.NavigateBefore} text="Label" badgeNumber={201} size={"small"} />
|
||||
<Button icon="navigate_before" text="Label" badgeNumber={201} size={"small"} />
|
||||
</div>
|
||||
|
||||
<HorizontalLine />
|
||||
<h3 className="text-xl">Groups</h3>
|
||||
<div className="grid place-items-center gap-4">
|
||||
<ButtonGroup buttonsProps={[{ icon: Icon.CallEnd }, { icon: Icon.ZoomInMap }]} />
|
||||
<ButtonGroup buttonsProps={[{ icon: "call_end" }, { icon: "zoom_in_map" }]} />
|
||||
<ButtonGroup
|
||||
buttonsProps={[
|
||||
{ icon: Icon.CarCrash },
|
||||
{ icon: Icon.TimeToLeave },
|
||||
{ icon: Icon.LeakAdd },
|
||||
]}
|
||||
buttonsProps={[{ icon: "car_crash" }, { icon: "timelapse" }, { icon: "leak_add" }]}
|
||||
/>
|
||||
<ButtonGroup
|
||||
buttonsProps={[
|
||||
{ icon: Icon.CarCrash },
|
||||
{ icon: Icon.TimeToLeave, text: "Label", active: true },
|
||||
{ icon: "car_crash" },
|
||||
{ icon: "timelapse", text: "Label", active: true },
|
||||
{ text: "Another Label" },
|
||||
{ icon: Icon.Cable },
|
||||
{ icon: "cable" },
|
||||
]}
|
||||
/>
|
||||
<ButtonGroup
|
||||
@ -244,7 +239,7 @@ const DesignSystem = (props: Props): JSX.Element => {
|
||||
onClick: () => setButtonGroupState(0),
|
||||
},
|
||||
{
|
||||
icon: Icon.AdUnits,
|
||||
icon: "ad_units",
|
||||
text: "Label",
|
||||
active: buttonGroupState === 1,
|
||||
onClick: () => setButtonGroupState(1),
|
||||
@ -255,7 +250,7 @@ const DesignSystem = (props: Props): JSX.Element => {
|
||||
onClick: () => setButtonGroupState(2),
|
||||
},
|
||||
{
|
||||
icon: Icon.Security,
|
||||
icon: "security",
|
||||
active: buttonGroupState === 3,
|
||||
onClick: () => setButtonGroupState(3),
|
||||
},
|
||||
@ -462,52 +457,41 @@ const DesignSystem = (props: Props): JSX.Element => {
|
||||
|
||||
<p>Normal</p>
|
||||
<NavOption title="Title" url="#" />
|
||||
<NavOption icon={Icon.Home} title="Title" url="#" />
|
||||
<NavOption icon="home" title="Title" url="#" />
|
||||
<NavOption title="Title" subtitle="This is a subtitle" url="#" />
|
||||
<NavOption title="Title" subtitle="This is a subtitle" url="#" icon="calendar_month" />
|
||||
<NavOption
|
||||
title="Title"
|
||||
subtitle="This is a subtitle"
|
||||
url="#"
|
||||
icon={Icon.CalendarMonth}
|
||||
/>
|
||||
<NavOption
|
||||
title="Title"
|
||||
subtitle="This is a subtitle"
|
||||
url="#"
|
||||
icon={Icon.AccountBalance}
|
||||
icon="account_balance"
|
||||
reduced
|
||||
/>
|
||||
|
||||
<p>Border</p>
|
||||
<NavOption title="Title" url="#" border />
|
||||
<NavOption icon={Icon.TravelExplore} title="Title" url="#" border />
|
||||
<NavOption icon="travel_explore" title="Title" url="#" border />
|
||||
<NavOption title="Title" subtitle="This is a subtitle" url="#" border />
|
||||
<NavOption title="Title" subtitle="This is a subtitle" url="#" icon={Icon.Help} border />
|
||||
<NavOption title="Title" subtitle="This is a subtitle" url="#" icon="help" border />
|
||||
<NavOption
|
||||
title="Title"
|
||||
subtitle="This is a subtitle"
|
||||
url="#"
|
||||
icon={Icon.TableRestaurant}
|
||||
icon="table_restaurant"
|
||||
border
|
||||
reduced
|
||||
/>
|
||||
|
||||
<p>Active</p>
|
||||
<NavOption title="Title" url="#" active />
|
||||
<NavOption icon={Icon.Hail} title="Title" url="#" active />
|
||||
<NavOption icon="hail" title="Title" url="#" active />
|
||||
<NavOption title="Title" subtitle="This is a subtitle" url="#" active />
|
||||
<NavOption title="Title" subtitle="This is a subtitle" url="#" icon="grading" active />
|
||||
<NavOption
|
||||
title="Title"
|
||||
subtitle="This is a subtitle"
|
||||
url="#"
|
||||
icon={Icon.Grading}
|
||||
active
|
||||
/>
|
||||
<NavOption
|
||||
title="Title"
|
||||
subtitle="This is a subtitle"
|
||||
url="#"
|
||||
icon={Icon.Timer}
|
||||
icon="timer"
|
||||
active
|
||||
reduced
|
||||
/>
|
||||
@ -517,13 +501,13 @@ const DesignSystem = (props: Props): JSX.Element => {
|
||||
<br />+ Border
|
||||
</p>
|
||||
<NavOption title="Title" url="#" active />
|
||||
<NavOption icon={Icon.Upcoming} title="Title" url="#" active border />
|
||||
<NavOption icon="upcoming" title="Title" url="#" active border />
|
||||
<NavOption title="Title" subtitle="This is a subtitle" url="#" active border />
|
||||
<NavOption
|
||||
title="Title"
|
||||
subtitle="This is a subtitle"
|
||||
url="#"
|
||||
icon={Icon.Gamepad}
|
||||
icon="gamepad"
|
||||
active
|
||||
border
|
||||
/>
|
||||
@ -531,7 +515,7 @@ const DesignSystem = (props: Props): JSX.Element => {
|
||||
title="Title"
|
||||
subtitle="This is a subtitle"
|
||||
url="#"
|
||||
icon={Icon.Scale}
|
||||
icon="scale"
|
||||
active
|
||||
border
|
||||
reduced
|
||||
@ -539,20 +523,20 @@ const DesignSystem = (props: Props): JSX.Element => {
|
||||
|
||||
<p>Disabled</p>
|
||||
<NavOption title="Title" url="#" disabled />
|
||||
<NavOption icon={Icon.Lan} title="Title" url="#" disabled />
|
||||
<NavOption icon="lan" title="Title" url="#" disabled />
|
||||
<NavOption title="Title" subtitle="This is a subtitle" url="#" disabled />
|
||||
<NavOption
|
||||
title="Title"
|
||||
subtitle="This is a subtitle"
|
||||
url="#"
|
||||
icon={Icon.AlignHorizontalRight}
|
||||
icon="align_horizontal_right"
|
||||
disabled
|
||||
/>
|
||||
<NavOption
|
||||
title="Title"
|
||||
subtitle="This is a subtitle"
|
||||
url="#"
|
||||
icon={Icon.YoutubeSearchedFor}
|
||||
icon="youtube_searched_for"
|
||||
reduced
|
||||
disabled
|
||||
/>
|
||||
@ -562,13 +546,13 @@ const DesignSystem = (props: Props): JSX.Element => {
|
||||
<br />+ Border
|
||||
</p>
|
||||
<NavOption title="Title" url="#" border disabled />
|
||||
<NavOption icon={Icon.Sanitizer} title="Title" url="#" border disabled />
|
||||
<NavOption icon="sanitizer" title="Title" url="#" border disabled />
|
||||
<NavOption title="Title" subtitle="This is a subtitle" url="#" border disabled />
|
||||
<NavOption
|
||||
title="Title"
|
||||
subtitle="This is a subtitle"
|
||||
url="#"
|
||||
icon={Icon.Pages}
|
||||
icon="pages"
|
||||
border
|
||||
disabled
|
||||
/>
|
||||
@ -576,7 +560,7 @@ const DesignSystem = (props: Props): JSX.Element => {
|
||||
title="Title"
|
||||
subtitle="This is a subtitle"
|
||||
url="#"
|
||||
icon={Icon.Synagogue}
|
||||
icon="synagogue"
|
||||
border
|
||||
reduced
|
||||
disabled
|
||||
@ -587,13 +571,13 @@ const DesignSystem = (props: Props): JSX.Element => {
|
||||
<br />+ Active
|
||||
</p>
|
||||
<NavOption title="Title" url="#" active disabled />
|
||||
<NavOption icon={Icon.Stairs} title="Title" url="#" active disabled />
|
||||
<NavOption icon="stairs" title="Title" url="#" active disabled />
|
||||
<NavOption title="Title" subtitle="This is a subtitle" url="#" active disabled />
|
||||
<NavOption
|
||||
title="Title"
|
||||
subtitle="This is a subtitle"
|
||||
url="#"
|
||||
icon={Icon.Park}
|
||||
icon="park"
|
||||
active
|
||||
disabled
|
||||
/>
|
||||
@ -601,7 +585,7 @@ const DesignSystem = (props: Props): JSX.Element => {
|
||||
title="Title"
|
||||
subtitle="This is a subtitle"
|
||||
url="#"
|
||||
icon={Icon.Password}
|
||||
icon="password"
|
||||
active
|
||||
reduced
|
||||
disabled
|
||||
|
@ -42,7 +42,7 @@ import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
||||
import { isUntangibleGroupItem } from "helpers/libraryItem";
|
||||
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { Ico } from "components/Ico";
|
||||
import { cJoin, cIf } from "helpers/className";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
@ -740,7 +740,7 @@ const ContentLine = ({
|
||||
className={`grid-flow-col place-content-start place-items-center gap-2 ${
|
||||
isOpened ? "grid" : "hidden"
|
||||
}`}>
|
||||
<Ico icon={Icon.SubdirectoryArrowRight} className="text-dark" />
|
||||
<Ico icon={"subdirectory_arrow_right"} className="text-dark" />
|
||||
|
||||
{hasScanSet || isDefined(content) ? (
|
||||
<>
|
||||
|
@ -2,8 +2,8 @@ import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { Fragment, useCallback, useEffect, useState } from "react";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import Slider from "rc-slider";
|
||||
import { useRouter } from "next/router";
|
||||
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
|
||||
import { z } from "zod";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import {
|
||||
Enum_Componentmetadatabooks_Page_Order as PageOrder,
|
||||
@ -22,7 +22,6 @@ import { cIf, cJoin } from "helpers/className";
|
||||
import { clamp, isInteger } from "helpers/numbers";
|
||||
import { SubPanel } from "components/Containers/SubPanel";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { Icon } from "components/Ico";
|
||||
import { Ids } from "types/ids";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
@ -40,6 +39,15 @@ import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { FilterSettings, useReaderSettings } from "hooks/useReaderSettings";
|
||||
import { useIsWebkit } from "hooks/useIsWebkit";
|
||||
import { useTypedRouter } from "hooks/useTypedRouter";
|
||||
|
||||
type BookType = "book" | "manga";
|
||||
type DisplayMode = "double" | "single";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
* ────────────────────────────────────────╯ CONSTANTS ╰──────────────────────────────────────────
|
||||
*/
|
||||
|
||||
const CUSTOM_DARK_DROPSHADOW = `
|
||||
drop-shadow(0 0 0.5em rgb(var(--theme-color-shade) / 30%))
|
||||
@ -56,8 +64,10 @@ const CUSTOM_LIGHT_DROPSHADOW = `
|
||||
const SIDEPAGES_PAGE_COUNT_ON_TEXTURE = 200;
|
||||
const SIDEPAGES_PAGE_WIDTH = 0.02;
|
||||
|
||||
type BookType = "book" | "manga";
|
||||
type DisplayMode = "double" | "single";
|
||||
const queryParamSchema = z.object({
|
||||
query: z.coerce.string().optional(),
|
||||
page: z.coerce.number().positive().optional(),
|
||||
});
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
@ -107,7 +117,7 @@ const LibrarySlug = ({
|
||||
const [displayMode, setDisplayMode] = useState<DisplayMode>(
|
||||
is1ColumnLayout ? "single" : "double"
|
||||
);
|
||||
const router = useRouter();
|
||||
const router = useTypedRouter(queryParamSchema);
|
||||
const isWebkit = useIsWebkit();
|
||||
|
||||
const { isFullscreen, toggleFullscreen, requestFullscreen } = useFullscreen(Ids.ContentPanel);
|
||||
@ -119,12 +129,7 @@ const LibrarySlug = ({
|
||||
|
||||
const changeCurrentPageIndex = useCallback(
|
||||
(callbackFn: (current: number) => number) => {
|
||||
setCurrentPageIndex((current) => {
|
||||
let result = callbackFn(current);
|
||||
result = clamp(result, 0, pages.length - 1);
|
||||
window.history.replaceState({}, "", `?page=${result - 1}`);
|
||||
return result;
|
||||
});
|
||||
setCurrentPageIndex((current) => clamp(callbackFn(current), 0, pages.length - 1));
|
||||
},
|
||||
[pages.length]
|
||||
);
|
||||
@ -132,13 +137,19 @@ const LibrarySlug = ({
|
||||
useEffect(() => setDisplayMode(is1ColumnLayout ? "single" : "double"), [is1ColumnLayout]);
|
||||
|
||||
useEffect(() => {
|
||||
const indexQueryString = router.asPath.indexOf("?page=");
|
||||
if (indexQueryString > 0) {
|
||||
const page = parseInt(router.asPath.slice(indexQueryString + "?page=".length), 10);
|
||||
changeCurrentPageIndex(() => page + 1);
|
||||
if (router.isReady)
|
||||
router.updateQuery({
|
||||
page: currentPageIndex - 1,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPageIndex, router.isReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady) {
|
||||
if (isDefined(router.query.page)) setCurrentPageIndex(router.query.page + 1);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.asPath]);
|
||||
}, [router.isReady]);
|
||||
|
||||
const changeDisplayMode = useCallback(
|
||||
(newDisplayMode: DisplayMode) => {
|
||||
@ -311,13 +322,13 @@ const LibrarySlug = ({
|
||||
<ButtonGroup
|
||||
buttonsProps={[
|
||||
{
|
||||
icon: Icon.Description,
|
||||
icon: "description",
|
||||
tooltip: langui.single_page_view,
|
||||
active: displayMode === "single",
|
||||
onClick: () => changeDisplayMode("single"),
|
||||
},
|
||||
{
|
||||
icon: Icon.AutoStories,
|
||||
icon: "auto_stories",
|
||||
tooltip: langui.double_page_view,
|
||||
active: displayMode === "double",
|
||||
onClick: () => changeDisplayMode("double"),
|
||||
@ -347,7 +358,7 @@ const LibrarySlug = ({
|
||||
<Button
|
||||
className="mt-8"
|
||||
text={langui.reset_all_options}
|
||||
icon={Icon.Replay}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
resetReaderSettings();
|
||||
setDisplayMode(is1ColumnLayout ? "single" : "double");
|
||||
@ -364,7 +375,6 @@ const LibrarySlug = ({
|
||||
onZoom={(zoom) => setCurrentZoom(zoom.state.scale)}
|
||||
panning={{ disabled: currentZoom <= 1, velocityDisabled: false }}
|
||||
doubleClick={{ disabled: true, mode: "reset" }}
|
||||
zoomAnimation={{ size: 0.1 }}
|
||||
velocityAnimation={{ animationTime: 0, equalToMove: true }}>
|
||||
<TransformComponent
|
||||
wrapperStyle={{ overflow: "visible", placeSelf: "center" }}
|
||||
@ -497,12 +507,12 @@ const LibrarySlug = ({
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
icon={isGalleryMode ? Icon.ExpandMore : Icon.ExpandLess}
|
||||
icon={isGalleryMode ? "expand_more" : "expand_less"}
|
||||
onClick={() => setIsGalleryMode((current) => !current)}
|
||||
size="small"
|
||||
/>
|
||||
<Button
|
||||
icon={isFullscreen ? Icon.FullscreenExit : Icon.Fullscreen}
|
||||
icon={isFullscreen ? "fullscreen_exit" : "fullscreen"}
|
||||
onClick={toggleFullscreen}
|
||||
size="small"
|
||||
/>
|
||||
|
@ -9,7 +9,6 @@ import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel";
|
||||
import { SubPanel } from "components/Containers/SubPanel";
|
||||
import { LibraryItemUserStatus } from "types/types";
|
||||
import { Icon } from "components/Ico";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
import { TextInput } from "components/Inputs/TextInput";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
@ -234,7 +233,7 @@ const Library = (props: Props): JSX.Element => {
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.LibraryBooks}
|
||||
icon="auto_stories"
|
||||
title={langui.library}
|
||||
description={langui.library_description}
|
||||
/>
|
||||
@ -322,7 +321,7 @@ const Library = (props: Props): JSX.Element => {
|
||||
buttonsProps={[
|
||||
{
|
||||
tooltip: langui.only_display_items_i_want,
|
||||
icon: Icon.Favorite,
|
||||
icon: "favorite",
|
||||
onClick: () => {
|
||||
setPage(1);
|
||||
setFilterUserStatus(LibraryItemUserStatus.Want);
|
||||
@ -332,7 +331,7 @@ const Library = (props: Props): JSX.Element => {
|
||||
},
|
||||
{
|
||||
tooltip: langui.only_display_items_i_have,
|
||||
icon: Icon.BackHand,
|
||||
icon: "back_hand",
|
||||
onClick: () => {
|
||||
setPage(1);
|
||||
setFilterUserStatus(LibraryItemUserStatus.Have);
|
||||
@ -342,7 +341,7 @@ const Library = (props: Props): JSX.Element => {
|
||||
},
|
||||
{
|
||||
tooltip: langui.only_display_unmarked_items,
|
||||
icon: Icon.RadioButtonUnchecked,
|
||||
icon: "nearby_off",
|
||||
onClick: () => {
|
||||
setPage(1);
|
||||
setFilterUserStatus(LibraryItemUserStatus.None);
|
||||
@ -366,7 +365,7 @@ const Library = (props: Props): JSX.Element => {
|
||||
<Button
|
||||
className="mt-8"
|
||||
text={langui.reset_all_filters}
|
||||
icon={Icon.Replay}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setQuery(DEFAULT_FILTERS_STATE.query);
|
||||
setShowSubitems(DEFAULT_FILTERS_STATE.showSubitems);
|
||||
@ -426,12 +425,7 @@ const Library = (props: Props): JSX.Element => {
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
subPanelIcon={Icon.Search}
|
||||
{...props}
|
||||
/>
|
||||
<AppLayout subPanel={subPanel} contentPanel={contentPanel} subPanelIcon="search" {...props} />
|
||||
);
|
||||
};
|
||||
export default Library;
|
||||
|
@ -2,7 +2,6 @@ import { GetStaticProps } from "next";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Containers/SubPanel";
|
||||
import { Icon } from "components/Ico";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { atoms } from "contexts/atoms";
|
||||
@ -20,11 +19,7 @@ const Merch = (props: Props): JSX.Element => {
|
||||
<AppLayout
|
||||
subPanel={
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.Store}
|
||||
title={langui.merch}
|
||||
description={langui.merch_description}
|
||||
/>
|
||||
<PanelHeader icon="store" title={langui.merch} description={langui.merch_description} />
|
||||
</SubPanel>
|
||||
}
|
||||
{...props}
|
||||
|
@ -7,7 +7,6 @@ import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel";
|
||||
import { SubPanel } from "components/Containers/SubPanel";
|
||||
import { Icon } from "components/Ico";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
import { TextInput } from "components/Inputs/TextInput";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
@ -103,7 +102,7 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader icon={Icon.Feed} title={langui.news} description={langui.news_description} />
|
||||
<PanelHeader icon="newspaper" title={langui.news} description={langui.news_description} />
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
@ -136,7 +135,7 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
|
||||
<Button
|
||||
className="mt-8"
|
||||
text={langui.reset_all_filters}
|
||||
icon={Icon.Replay}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setQuery(DEFAULT_FILTERS_STATE.query);
|
||||
setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
@ -195,7 +194,7 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
|
||||
<AppLayout
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
subPanelIcon={Icon.Search}
|
||||
subPanelIcon="search"
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
|
@ -146,7 +146,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
||||
"/contents/",
|
||||
definition.source.data.attributes.content.data.attributes.slug
|
||||
)
|
||||
: cJoin(
|
||||
: sJoin(
|
||||
"/library/",
|
||||
definition.source?.data?.attributes?.ranged_content?.data?.attributes
|
||||
?.library_item?.data?.attributes?.slug
|
||||
@ -165,6 +165,10 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{isDefined(selectedTranslation.body) && (
|
||||
<div className="whitespace-pre-line">{selectedTranslation.body.body}</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
@ -19,7 +19,7 @@ import { getOpenGraph } from "helpers/openGraph";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { Ico } from "components/Ico";
|
||||
import { AnchorShare } from "components/AnchorShare";
|
||||
import { datePickerToDate } from "helpers/date";
|
||||
import { TranslatedProps } from "types/TranslatedProps";
|
||||
@ -333,7 +333,7 @@ export const ChronologyEvent = ({ event, id }: ChronologyEventProps): JSX.Elemen
|
||||
`(${event.source.data.attributes?.name})`
|
||||
) : (
|
||||
<div className="flex items-center gap-1">
|
||||
<Ico icon={Icon.Warning} className="!text-sm" />
|
||||
<Ico icon="warning" className="!text-sm" />
|
||||
{langui.no_source_warning}
|
||||
</div>
|
||||
)}
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { GetStaticProps } from "next";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { z } from "zod";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Containers/SubPanel";
|
||||
import { Icon } from "components/Ico";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { GetWikiPageQuery, GetWikiPagesPreviewsQuery } from "graphql/generated";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
@ -15,23 +13,18 @@ import { Switch } from "components/Inputs/Switch";
|
||||
import { TextInput } from "components/Inputs/TextInput";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
||||
import {
|
||||
filterDefined,
|
||||
filterHasAttributes,
|
||||
isDefinedAndNotEmpty,
|
||||
SelectiveNonNullable,
|
||||
} from "helpers/asserts";
|
||||
import { SmartList } from "components/SmartList";
|
||||
import { Select } from "components/Inputs/Select";
|
||||
import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { TranslatedPreviewCard } from "components/PreviewCard";
|
||||
import { cIf } from "helpers/className";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { sendAnalytics } from "helpers/analytics";
|
||||
import { Terminal } from "components/Cli/Terminal";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useTypedRouter } from "hooks/useTypedRouter";
|
||||
import { MeiliIndices, MeiliWikiPage } from "shared/meilisearch-graphql-typings/meiliTypes";
|
||||
import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/search";
|
||||
import { Paginator } from "components/Containers/Paginator";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
@ -39,31 +32,28 @@ import { useAtomGetter } from "helpers/atoms";
|
||||
*/
|
||||
|
||||
const DEFAULT_FILTERS_STATE = {
|
||||
searchName: "",
|
||||
query: "",
|
||||
keepInfoVisible: true,
|
||||
groupingMethod: -1,
|
||||
page: 1,
|
||||
};
|
||||
|
||||
const queryParamSchema = z.object({
|
||||
query: z.coerce.string().optional(),
|
||||
page: z.coerce.number().positive().optional(),
|
||||
});
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
* ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
|
||||
*/
|
||||
|
||||
interface Props extends AppLayoutRequired {
|
||||
pages: NonNullable<GetWikiPagesPreviewsQuery["wikiPages"]>["data"];
|
||||
}
|
||||
interface Props extends AppLayoutRequired {}
|
||||
|
||||
const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => {
|
||||
const Wiki = (props: Props): JSX.Element => {
|
||||
const hoverable = useDeviceSupportsHover();
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl);
|
||||
const isTerminalMode = useAtomGetter(atoms.layout.terminalMode);
|
||||
|
||||
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
|
||||
|
||||
const [groupingMethod, setGroupingMethod] = useState<number>(
|
||||
DEFAULT_FILTERS_STATE.groupingMethod
|
||||
);
|
||||
const router = useTypedRouter(queryParamSchema);
|
||||
const [query, setQuery] = useState(router.query.query ?? DEFAULT_FILTERS_STATE.query);
|
||||
|
||||
const {
|
||||
value: keepInfoVisible,
|
||||
@ -71,10 +61,48 @@ const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => {
|
||||
setValue: setKeepInfoVisible,
|
||||
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
|
||||
const [page, setPage] = useState<number>(router.query.page ?? DEFAULT_FILTERS_STATE.page);
|
||||
const [wikiPages, setWikiPages] = useState<CustomSearchResponse<MeiliWikiPage>>();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchWikiPages = async () => {
|
||||
const searchResult = await meiliSearch(MeiliIndices.WIKI_PAGE, query, {
|
||||
hitsPerPage: 25,
|
||||
page,
|
||||
attributesToHighlight: [
|
||||
"translations.title",
|
||||
"translations.aliases",
|
||||
"translations.summary",
|
||||
"translations.displayable_description",
|
||||
],
|
||||
attributesToCrop: ["translations.displayable_description"],
|
||||
});
|
||||
setWikiPages(searchResult);
|
||||
};
|
||||
fetchWikiPages();
|
||||
}, [query, page]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady)
|
||||
router.updateQuery({
|
||||
page,
|
||||
query,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [page, query, router.isReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady) {
|
||||
if (isDefined(router.query.page)) setPage(router.query.page);
|
||||
if (isDefined(router.query.query)) setQuery(router.query.query);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.isReady]);
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.TravelExplore}
|
||||
icon="travel_explore"
|
||||
title={langui.wiki}
|
||||
description={langui.wiki_description}
|
||||
/>
|
||||
@ -84,9 +112,10 @@ const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => {
|
||||
<TextInput
|
||||
className="mb-6 w-full"
|
||||
placeholder={langui.search_title ?? "Search..."}
|
||||
value={searchName}
|
||||
value={query}
|
||||
onChange={(name) => {
|
||||
setSearchName(name);
|
||||
setPage(1);
|
||||
setQuery(name);
|
||||
if (isDefinedAndNotEmpty(name)) {
|
||||
sendAnalytics("Wiki", "Change search term");
|
||||
} else {
|
||||
@ -95,19 +124,6 @@ const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => {
|
||||
}}
|
||||
/>
|
||||
|
||||
<WithLabel label={langui.group_by}>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={[langui.category ?? "Category"]}
|
||||
value={groupingMethod}
|
||||
onChange={(value) => {
|
||||
setGroupingMethod(value);
|
||||
sendAnalytics("Wiki", `Change grouping method (${["none", "category"][value + 1]})`);
|
||||
}}
|
||||
allowEmpty
|
||||
/>
|
||||
</WithLabel>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<Switch
|
||||
@ -123,10 +139,10 @@ const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => {
|
||||
<Button
|
||||
className="mt-8"
|
||||
text={langui.reset_all_filters}
|
||||
icon={Icon.Replay}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setSearchName(DEFAULT_FILTERS_STATE.searchName);
|
||||
setGroupingMethod(DEFAULT_FILTERS_STATE.groupingMethod);
|
||||
setPage(1);
|
||||
setQuery(DEFAULT_FILTERS_STATE.query);
|
||||
setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
sendAnalytics("Wiki", "Reset all filters");
|
||||
}}
|
||||
@ -140,104 +156,52 @@ const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => {
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
const groupingFunction = useCallback(
|
||||
(
|
||||
item: SelectiveNonNullable<
|
||||
NonNullable<GetWikiPageQuery["wikiPages"]>["data"][number],
|
||||
"attributes" | "id"
|
||||
>
|
||||
): string[] => {
|
||||
switch (groupingMethod) {
|
||||
case 0: {
|
||||
const categories = filterHasAttributes(item.attributes.categories?.data, [
|
||||
"attributes",
|
||||
] as const);
|
||||
if (categories.length > 0) {
|
||||
return categories.map((category) => category.attributes.name);
|
||||
}
|
||||
return [langui.no_category ?? "No category"];
|
||||
}
|
||||
default: {
|
||||
return [""];
|
||||
}
|
||||
}
|
||||
},
|
||||
[groupingMethod, langui]
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
<SmartList
|
||||
items={filterHasAttributes(pages, ["id", "attributes"] as const)}
|
||||
getItemId={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<Paginator page={page} onPageChange={setPage} totalNumberOfPages={wikiPages?.totalPages}>
|
||||
<div
|
||||
className="grid grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-start
|
||||
gap-x-6 gap-y-8">
|
||||
{wikiPages?.hits.map((item) => (
|
||||
<TranslatedPreviewCard
|
||||
href={`/wiki/${item.attributes.slug}`}
|
||||
translations={filterHasAttributes(item.attributes.translations, [
|
||||
key={item.id}
|
||||
href={`/wiki/${item.slug}`}
|
||||
translations={filterHasAttributes(item._formatted.translations, [
|
||||
"language.data.attributes.code",
|
||||
] as const).map((translation) => ({
|
||||
title: translation.title,
|
||||
] as const).map(
|
||||
({ aliases, summary, displayable_description, language, ...otherAttributes }) => ({
|
||||
...otherAttributes,
|
||||
subtitle:
|
||||
translation.aliases && translation.aliases.length > 0
|
||||
? translation.aliases.map((alias) => alias?.alias).join("・")
|
||||
aliases && aliases.length > 0
|
||||
? aliases.map((alias) => alias?.alias).join("・")
|
||||
: undefined,
|
||||
description: translation.summary,
|
||||
language: translation.language.data.attributes.code,
|
||||
}))}
|
||||
fallback={{ title: prettySlug(item.attributes.slug) }}
|
||||
thumbnail={item.attributes.thumbnail?.data?.attributes}
|
||||
description: containsHighlight(displayable_description)
|
||||
? displayable_description
|
||||
: summary,
|
||||
language: language.data.attributes.code,
|
||||
})
|
||||
)}
|
||||
fallback={{ title: prettySlug(item.slug) }}
|
||||
thumbnail={item.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio={"4/3"}
|
||||
thumbnailRounded
|
||||
thumbnailForceAspectRatio
|
||||
keepInfoVisible={keepInfoVisible}
|
||||
topChips={filterHasAttributes(item.attributes.tags?.data, ["attributes"] as const).map(
|
||||
keepInfoVisible
|
||||
topChips={filterHasAttributes(item.tags?.data, ["attributes"] as const).map(
|
||||
(tag) => tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
|
||||
)}
|
||||
bottomChips={filterHasAttributes(item.attributes.categories?.data, [
|
||||
"attributes",
|
||||
] as const).map((category) => category.attributes.short)}
|
||||
/>
|
||||
bottomChips={filterHasAttributes(item.categories?.data, ["attributes"] as const).map(
|
||||
(category) => category.attributes.short
|
||||
)}
|
||||
className={cIf(
|
||||
isContentPanelAtLeast4xl,
|
||||
"grid-cols-[repeat(auto-fill,minmax(15rem,1fr))] gap-x-6 gap-y-8",
|
||||
"grid-cols-2 gap-x-3 gap-y-5"
|
||||
)}
|
||||
searchingTerm={searchName}
|
||||
searchingBy={(item) =>
|
||||
filterDefined(item.attributes.translations)
|
||||
.map(
|
||||
(translation) =>
|
||||
`${translation.title} ${filterDefined(translation.aliases)
|
||||
.map((alias) => alias.alias)
|
||||
.join(" ")}`
|
||||
)
|
||||
.join(" ")
|
||||
}
|
||||
groupingFunction={groupingFunction}
|
||||
paginationItemPerPage={25}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Paginator>
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
if (isTerminalMode) {
|
||||
return (
|
||||
<Terminal
|
||||
parentPath="/"
|
||||
childrenPaths={filterHasAttributes(pages, ["attributes"] as const).map(
|
||||
(page) => page.attributes.slug
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
subPanelIcon={Icon.Search}
|
||||
{...otherProps}
|
||||
/>
|
||||
<AppLayout subPanel={subPanel} contentPanel={contentPanel} subPanelIcon="search" {...props} />
|
||||
);
|
||||
};
|
||||
export default Wiki;
|
||||
@ -247,31 +211,12 @@ export default Wiki;
|
||||
* ───────────────────────────────────╯ NEXT DATA FETCHING ╰──────────────────────────────────────
|
||||
*/
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
export const getStaticProps: GetStaticProps = (context) => {
|
||||
const langui = getLangui(context.locale);
|
||||
const pages = await sdk.getWikiPagesPreviews({
|
||||
language_code: context.locale ?? "en",
|
||||
});
|
||||
if (!pages.wikiPages?.data) return { notFound: true };
|
||||
|
||||
const props: Props = {
|
||||
pages: sortPages(pages.wikiPages.data),
|
||||
openGraph: getOpenGraph(langui, langui.wiki ?? "Wiki"),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* ╭───────────────────╮
|
||||
* ─────────────────────────────────────╯ PRIVATE METHODS ╰───────────────────────────────────────
|
||||
*/
|
||||
|
||||
const sortPages = (pages: Props["pages"]): Props["pages"] =>
|
||||
pages.sort((a, b) => {
|
||||
const slugA = a.attributes?.slug ?? "";
|
||||
const slugB = b.attributes?.slug ?? "";
|
||||
return slugA.localeCompare(slugB);
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,17 +4,24 @@ import {
|
||||
GetLibraryItemQuery,
|
||||
GetPostQuery,
|
||||
GetVideoQuery,
|
||||
GetWikiPageQuery,
|
||||
LibraryItemAttributesFragment,
|
||||
PostAttributesFragment,
|
||||
VideoAttributesFragment,
|
||||
WikiPageAttributesFragment,
|
||||
} from "./generated";
|
||||
|
||||
export interface MeiliLibraryItem extends Omit<LibraryItemAttributesFragment, "descriptions"> {
|
||||
id: string;
|
||||
descriptions: string[];
|
||||
sortable_name: string;
|
||||
sortable_price: number | undefined;
|
||||
sortable_date: number | undefined;
|
||||
untangible_group_item: boolean;
|
||||
}
|
||||
|
||||
export interface MeiliContent extends Omit<ContentAttributesFragment, "translations"> {
|
||||
export interface MeiliContent
|
||||
extends Omit<ContentAttributesFragment, "translations" | "updatedAt"> {
|
||||
id: string;
|
||||
translations?: Array<{
|
||||
__typename?: "ComponentTranslationsTitle";
|
||||
@ -30,6 +37,7 @@ export interface MeiliContent extends Omit<ContentAttributesFragment, "translati
|
||||
} | null;
|
||||
} | null;
|
||||
} | null> | null;
|
||||
sortable_updated_date: number;
|
||||
}
|
||||
|
||||
export interface MeiliVideo extends VideoAttributesFragment {
|
||||
@ -43,11 +51,22 @@ export interface MeiliPost extends PostAttributesFragment {
|
||||
sortable_date: number;
|
||||
}
|
||||
|
||||
export interface MeiliWikiPage extends Omit<WikiPageAttributesFragment, "translations"> {
|
||||
id: string;
|
||||
translations: (Omit<
|
||||
NonNullable<NonNullable<WikiPageAttributesFragment["translations"]>[number]>,
|
||||
"body"
|
||||
> & {
|
||||
displayable_description?: string | null;
|
||||
})[];
|
||||
}
|
||||
|
||||
export enum MeiliIndices {
|
||||
LIBRARY_ITEM = "library-item",
|
||||
CONTENT = "content",
|
||||
VIDEOS = "video",
|
||||
POST = "post",
|
||||
WIKI_PAGE = "wiki-page",
|
||||
}
|
||||
|
||||
export type MeiliDocumentsType =
|
||||
@ -70,4 +89,9 @@ export type MeiliDocumentsType =
|
||||
index: MeiliIndices.POST;
|
||||
documents: MeiliPost;
|
||||
strapi: GetPostQuery["post"];
|
||||
}
|
||||
| {
|
||||
index: MeiliIndices.WIKI_PAGE;
|
||||
documents: MeiliWikiPage;
|
||||
strapi: GetWikiPageQuery["wikiPage"];
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user