Chronology v2
This commit is contained in:
parent
a04f1b50c3
commit
ac38f1dae0
|
@ -0,0 +1,37 @@
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { Ico, Icon } from "./Ico";
|
||||||
|
import { ToolTip } from "./ToolTip";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ╭─────────────╮
|
||||||
|
* ───────────────────────────────────────╯ COMPONENT ╰───────────────────────────────────────────
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
|
export const AnchorShare = ({ id }: Props): JSX.Element => (
|
||||||
|
<ToolTip
|
||||||
|
content={"Copy anchor link"}
|
||||||
|
trigger="mouseenter"
|
||||||
|
className="text-sm"
|
||||||
|
>
|
||||||
|
{/* TODO: Langui Copied! */}
|
||||||
|
<ToolTip content={"Copied! 👍"} trigger="click" className="text-sm">
|
||||||
|
<Ico
|
||||||
|
icon={Icon.Link}
|
||||||
|
className="transition-color cursor-pointer hover:text-dark"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(
|
||||||
|
`${
|
||||||
|
process.env.NEXT_PUBLIC_URL_SELF + window.location.pathname
|
||||||
|
}#${id}`
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ToolTip>
|
||||||
|
</ToolTip>
|
||||||
|
);
|
|
@ -47,7 +47,7 @@ export const ChroniclesList = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="grid gap-4 overflow-hidden transition-[max-height]"
|
className="grid gap-4 overflow-hidden transition-[max-height] duration-500"
|
||||||
style={{ maxHeight: isOpen ? `${8 * chronicles.length}rem` : 0 }}
|
style={{ maxHeight: isOpen ? `${8 * chronicles.length}rem` : 0 }}
|
||||||
>
|
>
|
||||||
{filterHasAttributes(chronicles, [
|
{filterHasAttributes(chronicles, [
|
||||||
|
|
|
@ -19,6 +19,7 @@ interface Props {
|
||||||
localesIndex: number | undefined;
|
localesIndex: number | undefined;
|
||||||
onLanguageChanged: (index: number) => void;
|
onLanguageChanged: (index: number) => void;
|
||||||
size?: Parameters<typeof Button>[0]["size"];
|
size?: Parameters<typeof Button>[0]["size"];
|
||||||
|
showBadge?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
@ -30,6 +31,7 @@ export const LanguageSwitcher = ({
|
||||||
languages,
|
languages,
|
||||||
size,
|
size,
|
||||||
onLanguageChanged,
|
onLanguageChanged,
|
||||||
|
showBadge = true,
|
||||||
}: Props): JSX.Element => (
|
}: Props): JSX.Element => (
|
||||||
<ToolTip
|
<ToolTip
|
||||||
content={
|
content={
|
||||||
|
@ -47,7 +49,7 @@ export const LanguageSwitcher = ({
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
badgeNumber={locales.size > 1 ? locales.size : undefined}
|
badgeNumber={showBadge && locales.size > 1 ? locales.size : undefined}
|
||||||
icon={Icon.Translate}
|
icon={Icon.Translate}
|
||||||
size={size}
|
size={size}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -3,10 +3,8 @@ import { useRouter } from "next/router";
|
||||||
import React, { Fragment, useMemo } from "react";
|
import React, { Fragment, useMemo } from "react";
|
||||||
import ReactDOMServer from "react-dom/server";
|
import ReactDOMServer from "react-dom/server";
|
||||||
import { HorizontalLine } from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
import { Ico, Icon } from "components/Ico";
|
|
||||||
import { Img } from "components/Img";
|
import { Img } from "components/Img";
|
||||||
import { InsetBox } from "components/InsetBox";
|
import { InsetBox } from "components/InsetBox";
|
||||||
import { ToolTip } from "components/ToolTip";
|
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { cJoin } from "helpers/className";
|
import { cJoin } from "helpers/className";
|
||||||
|
@ -14,6 +12,7 @@ import { slugify } from "helpers/formatters";
|
||||||
import { getAssetURL, ImageQuality } from "helpers/img";
|
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||||
import { isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/others";
|
import { isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/others";
|
||||||
import { useLightBox } from "hooks/useLightBox";
|
import { useLightBox } from "hooks/useLightBox";
|
||||||
|
import { AnchorShare } from "components/AnchorShare";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭─────────────╮
|
* ╭─────────────╮
|
||||||
|
@ -86,7 +85,7 @@ export const Markdawn = ({
|
||||||
}) => (
|
}) => (
|
||||||
<h1 id={compProps.id} style={compProps.style}>
|
<h1 id={compProps.id} style={compProps.style}>
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
<HeaderToolTip id={compProps.id} />
|
<AnchorShare id={compProps.id} />
|
||||||
</h1>
|
</h1>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -99,7 +98,7 @@ export const Markdawn = ({
|
||||||
}) => (
|
}) => (
|
||||||
<h2 id={compProps.id} style={compProps.style}>
|
<h2 id={compProps.id} style={compProps.style}>
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
<HeaderToolTip id={compProps.id} />
|
<AnchorShare id={compProps.id} />
|
||||||
</h2>
|
</h2>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -112,7 +111,7 @@ export const Markdawn = ({
|
||||||
}) => (
|
}) => (
|
||||||
<h3 id={compProps.id} style={compProps.style}>
|
<h3 id={compProps.id} style={compProps.style}>
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
<HeaderToolTip id={compProps.id} />
|
<AnchorShare id={compProps.id} />
|
||||||
</h3>
|
</h3>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -125,7 +124,7 @@ export const Markdawn = ({
|
||||||
}) => (
|
}) => (
|
||||||
<h4 id={compProps.id} style={compProps.style}>
|
<h4 id={compProps.id} style={compProps.style}>
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
<HeaderToolTip id={compProps.id} />
|
<AnchorShare id={compProps.id} />
|
||||||
</h4>
|
</h4>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -138,7 +137,7 @@ export const Markdawn = ({
|
||||||
}) => (
|
}) => (
|
||||||
<h5 id={compProps.id} style={compProps.style}>
|
<h5 id={compProps.id} style={compProps.style}>
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
<HeaderToolTip id={compProps.id} />
|
<AnchorShare id={compProps.id} />
|
||||||
</h5>
|
</h5>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -151,7 +150,7 @@ export const Markdawn = ({
|
||||||
}) => (
|
}) => (
|
||||||
<h6 id={compProps.id} style={compProps.style}>
|
<h6 id={compProps.id} style={compProps.style}>
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
<HeaderToolTip id={compProps.id} />
|
<AnchorShare id={compProps.id} />
|
||||||
</h6>
|
</h6>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -383,33 +382,6 @@ const TocLevel = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
* ╭───────────────────╮
|
|
||||||
* ─────────────────────────────────────╯ PRIVATE METHODS ╰───────────────────────────────────────
|
|
||||||
*/
|
|
||||||
|
|
||||||
const HeaderToolTip = (props: { id: string }): JSX.Element => (
|
|
||||||
<ToolTip
|
|
||||||
content={"Copy anchor link"}
|
|
||||||
trigger="mouseenter"
|
|
||||||
className="text-sm"
|
|
||||||
>
|
|
||||||
<ToolTip content={"Copied! 👍"} trigger="click" className="text-sm">
|
|
||||||
<Ico
|
|
||||||
icon={Icon.Link}
|
|
||||||
className="transition-color cursor-pointer hover:text-dark"
|
|
||||||
onClick={() => {
|
|
||||||
navigator.clipboard.writeText(
|
|
||||||
`${process.env.NEXT_PUBLIC_URL_SELF + window.location.pathname}#${
|
|
||||||
props.id
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ToolTip>
|
|
||||||
</ToolTip>
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭──────────────────────╮
|
* ╭──────────────────────╮
|
||||||
* ───────────────────────────────────╯ PRIVATE COMPONENTS ╰──────────────────────────────────────
|
* ───────────────────────────────────╯ PRIVATE COMPONENTS ╰──────────────────────────────────────
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { ChroniclePreview } from "./Chronicles/ChroniclePreview";
|
||||||
import { ChroniclesList } from "./Chronicles/ChroniclesList";
|
import { ChroniclesList } from "./Chronicles/ChroniclesList";
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
|
|
||||||
type TranslatedProps<P, K extends keyof P> = Omit<P, K> & {
|
export type TranslatedProps<P, K extends keyof P> = Omit<P, K> & {
|
||||||
translations: (Pick<P, K> & { language: string })[];
|
translations: (Pick<P, K> & { language: string })[];
|
||||||
fallback: Pick<P, K>;
|
fallback: Pick<P, K>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,182 +0,0 @@
|
||||||
import { Fragment } from "react";
|
|
||||||
import { Chip } from "components/Chip";
|
|
||||||
import { Ico, Icon } from "components/Ico";
|
|
||||||
import { ToolTip } from "components/ToolTip";
|
|
||||||
import {
|
|
||||||
Enum_Componenttranslationschronologyitem_Status,
|
|
||||||
GetChronologyItemsQuery,
|
|
||||||
} from "graphql/generated";
|
|
||||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
|
||||||
import {
|
|
||||||
filterDefined,
|
|
||||||
filterHasAttributes,
|
|
||||||
getStatusDescription,
|
|
||||||
isDefined,
|
|
||||||
} from "helpers/others";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ╭─────────────╮
|
|
||||||
* ───────────────────────────────────────╯ COMPONENT ╰───────────────────────────────────────────
|
|
||||||
*/
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
item: NonNullable<GetChronologyItemsQuery["chronologyItems"]>["data"][number];
|
|
||||||
displayYear: boolean;
|
|
||||||
langui: AppStaticProps["langui"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
|
||||||
|
|
||||||
export const ChronologyItemComponent = ({
|
|
||||||
langui,
|
|
||||||
item,
|
|
||||||
displayYear,
|
|
||||||
}: Props): JSX.Element => (
|
|
||||||
<>
|
|
||||||
{isDefined(item.attributes) && (
|
|
||||||
<div
|
|
||||||
className="grid grid-cols-[4em] grid-rows-[auto_1fr] place-content-start
|
|
||||||
rounded-2xl py-4 px-8 target:my-4 target:bg-mid target:py-8"
|
|
||||||
id={generateAnchor(
|
|
||||||
item.attributes.year,
|
|
||||||
item.attributes.month,
|
|
||||||
item.attributes.day
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{displayYear && (
|
|
||||||
<p className="mt-[-.2em] text-lg font-bold">
|
|
||||||
{generateYear(item.attributes.displayed_date, item.attributes.year)}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<p className="col-start-1 text-sm text-dark">
|
|
||||||
{generateDate(item.attributes.month, item.attributes.day)}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="col-start-2 row-span-2 row-start-1 grid gap-4">
|
|
||||||
{item.attributes.events &&
|
|
||||||
filterHasAttributes(item.attributes.events, [
|
|
||||||
"id",
|
|
||||||
"translations",
|
|
||||||
] as const).map((event) => (
|
|
||||||
<Fragment key={event.id}>
|
|
||||||
<div className="m-0">
|
|
||||||
{filterDefined(event.translations).map(
|
|
||||||
(translation, translationIndex) => (
|
|
||||||
<Fragment key={translationIndex}>
|
|
||||||
<Fragment>
|
|
||||||
<div
|
|
||||||
className="grid
|
|
||||||
grid-flow-col place-content-start place-items-start gap-2"
|
|
||||||
>
|
|
||||||
{translation.status !==
|
|
||||||
Enum_Componenttranslationschronologyitem_Status.Done && (
|
|
||||||
<ToolTip
|
|
||||||
content={getStatusDescription(
|
|
||||||
translation.status,
|
|
||||||
langui
|
|
||||||
)}
|
|
||||||
maxWidth={"20rem"}
|
|
||||||
>
|
|
||||||
<Chip text={translation.status} />
|
|
||||||
</ToolTip>
|
|
||||||
)}
|
|
||||||
{translation.title ? (
|
|
||||||
<h3>{translation.title}</h3>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{translation.description && (
|
|
||||||
<p
|
|
||||||
className={
|
|
||||||
event.translations.length > 1
|
|
||||||
? `mt-2 whitespace-pre-line before:ml-[-1em] before:inline-block
|
|
||||||
before:w-4 before:text-dark before:content-['-']`
|
|
||||||
: "whitespace-pre-line"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{translation.description}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{translation.note ? (
|
|
||||||
<em>{`${langui.notes}: ${translation.note}`}</em>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
<p className="mt-1 grid grid-flow-col gap-1 place-self-start text-xs text-dark">
|
|
||||||
{event.source?.data ? (
|
|
||||||
`(${event.source.data.attributes?.name})`
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<Ico icon={Icon.Warning} className="!text-sm" />
|
|
||||||
{langui.no_source_warning}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ╭───────────────────╮
|
|
||||||
* ─────────────────────────────────────╯ PRIVATE METHODS ╰───────────────────────────────────────
|
|
||||||
*/
|
|
||||||
|
|
||||||
const generateAnchor = (
|
|
||||||
year: number | undefined,
|
|
||||||
month: number | null | undefined,
|
|
||||||
day: number | null | undefined
|
|
||||||
): string => {
|
|
||||||
let result = "";
|
|
||||||
if (year) result += year;
|
|
||||||
if (month) result += `- ${month.toString().padStart(2, "0")}`;
|
|
||||||
if (day) result += `- ${day.toString().padStart(2, "0")}`;
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateYear = (
|
|
||||||
displayed_date: string | null | undefined,
|
|
||||||
year: number | undefined
|
|
||||||
): string => displayed_date ?? year?.toString() ?? "";
|
|
||||||
|
|
||||||
const generateDate = (
|
|
||||||
month: number | null | undefined,
|
|
||||||
day: number | null | undefined
|
|
||||||
): string => {
|
|
||||||
const lut = [
|
|
||||||
"Jan",
|
|
||||||
"Feb",
|
|
||||||
"Mar",
|
|
||||||
"Apr",
|
|
||||||
"May",
|
|
||||||
"Jun",
|
|
||||||
"Jul",
|
|
||||||
"Aug",
|
|
||||||
"Sep",
|
|
||||||
"Oct",
|
|
||||||
"Nov",
|
|
||||||
"Dec",
|
|
||||||
];
|
|
||||||
|
|
||||||
let result = "";
|
|
||||||
if (month && month >= 1 && month <= 12) {
|
|
||||||
result += lut[month - 1];
|
|
||||||
if (day) {
|
|
||||||
result += ` ${day}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { ChronologyItemComponent } from "components/Wiki/Chronology/ChronologyItemComponent";
|
|
||||||
import { GetChronologyItemsQuery } from "graphql/generated";
|
|
||||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ╭─────────────╮
|
|
||||||
* ───────────────────────────────────────╯ COMPONENT ╰───────────────────────────────────────────
|
|
||||||
*/
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
year: number;
|
|
||||||
items: NonNullable<
|
|
||||||
GetChronologyItemsQuery["chronologyItems"]
|
|
||||||
>["data"][number][];
|
|
||||||
langui: AppStaticProps["langui"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
|
||||||
|
|
||||||
export const ChronologyYearComponent = ({
|
|
||||||
langui,
|
|
||||||
year,
|
|
||||||
items,
|
|
||||||
}: Props): JSX.Element => (
|
|
||||||
<div
|
|
||||||
className="rounded-2xl target:my-4 target:bg-mid target:py-4"
|
|
||||||
id={items.length > 1 ? year.toString() : undefined}
|
|
||||||
>
|
|
||||||
{items.map((item, index) => (
|
|
||||||
<ChronologyItemComponent
|
|
||||||
key={index}
|
|
||||||
item={item}
|
|
||||||
displayYear={index === 0}
|
|
||||||
langui={langui}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
|
@ -20,6 +20,13 @@ query getChronologyItems {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
translations(pagination: { limit: -1 }) {
|
translations(pagination: { limit: -1 }) {
|
||||||
|
language {
|
||||||
|
data {
|
||||||
|
attributes {
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
title
|
title
|
||||||
description
|
description
|
||||||
note
|
note
|
||||||
|
|
|
@ -7,6 +7,13 @@ query getEras {
|
||||||
starting_year
|
starting_year
|
||||||
ending_year
|
ending_year
|
||||||
title(pagination: { limit: -1 }) {
|
title(pagination: { limit: -1 }) {
|
||||||
|
language {
|
||||||
|
data {
|
||||||
|
attributes {
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
title
|
title
|
||||||
description
|
description
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,12 @@ export const compareDate = (
|
||||||
if (isUndefined(a) || isUndefined(b)) {
|
if (isUndefined(a) || isUndefined(b)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const dateA = (a.year ?? 99999) * 365 + (a.month ?? 12) * 31 + (a.day ?? 31);
|
const dateA =
|
||||||
const dateB = (b.year ?? 99999) * 365 + (b.month ?? 12) * 31 + (b.day ?? 31);
|
(a.year ?? Infinity) * 365 + (a.month ?? 12) * 31 + (a.day ?? 31);
|
||||||
|
const dateB =
|
||||||
|
(b.year ?? Infinity) * 365 + (b.month ?? 12) * 31 + (b.day ?? 31);
|
||||||
return dateA - dateB;
|
return dateA - dateB;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const datePickerToDate = (date: DatePickerFragment): Date =>
|
||||||
|
new Date(date.year ?? 0, date.month ? date.month - 1 : 0, date.day ?? 1);
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
import { AppStaticProps } from "../graphql/getAppStaticProps";
|
import { AppStaticProps } from "../graphql/getAppStaticProps";
|
||||||
import { convertPrice } from "./numbers";
|
import { convertPrice } from "./numbers";
|
||||||
import { isDefinedAndNotEmpty, isUndefined } from "./others";
|
import { isDefinedAndNotEmpty, isUndefined } from "./others";
|
||||||
|
import { datePickerToDate } from "./date";
|
||||||
import { DatePickerFragment, PricePickerFragment } from "graphql/generated";
|
import { DatePickerFragment, PricePickerFragment } from "graphql/generated";
|
||||||
|
|
||||||
export const prettyDate = (
|
export const prettyDate = (
|
||||||
datePicker: DatePickerFragment,
|
datePicker: DatePickerFragment,
|
||||||
locale = "en",
|
locale = "en",
|
||||||
dateStyle: Intl.DateTimeFormatOptions["dateStyle"] = "medium"
|
dateStyle: Intl.DateTimeFormatOptions["dateStyle"] = "medium"
|
||||||
): string =>
|
): string => datePickerToDate(datePicker).toLocaleString(locale, { dateStyle });
|
||||||
new Date(
|
|
||||||
datePicker.year ?? 0,
|
|
||||||
datePicker.month ?? 0,
|
|
||||||
datePicker.day ?? 1
|
|
||||||
).toLocaleString(locale, { dateStyle });
|
|
||||||
|
|
||||||
export const prettyPrice = (
|
export const prettyPrice = (
|
||||||
pricePicker: PricePickerFragment,
|
pricePicker: PricePickerFragment,
|
||||||
|
|
|
@ -25,11 +25,11 @@ export interface OpenGraph {
|
||||||
|
|
||||||
export const getOpenGraph = (
|
export const getOpenGraph = (
|
||||||
langui: AppStaticProps["langui"],
|
langui: AppStaticProps["langui"],
|
||||||
title: string,
|
title?: string | null | undefined,
|
||||||
description?: string | null | undefined,
|
description?: string | null | undefined,
|
||||||
thumbnail?: UploadImageFragment | null | undefined
|
thumbnail?: UploadImageFragment | null | undefined
|
||||||
): OpenGraph => ({
|
): OpenGraph => ({
|
||||||
title: `${TITLE_PREFIX}${isDefinedAndNotEmpty(title) && ` - ${title}`}`,
|
title: `${TITLE_PREFIX}${isDefinedAndNotEmpty(title) ? ` - ${title}` : ""}`,
|
||||||
description: isDefinedAndNotEmpty(description)
|
description: isDefinedAndNotEmpty(description)
|
||||||
? description
|
? description
|
||||||
: langui.default_description ?? "",
|
: langui.default_description ?? "",
|
||||||
|
|
|
@ -2,6 +2,8 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import getConfig from "next/config";
|
import getConfig from "next/config";
|
||||||
|
|
||||||
type RequestProps =
|
type RequestProps =
|
||||||
|
| HookChronicle
|
||||||
|
| HookChronicleChapter
|
||||||
| HookChronology
|
| HookChronology
|
||||||
| HookContent
|
| HookContent
|
||||||
| HookContentGroup
|
| HookContentGroup
|
||||||
|
@ -83,6 +85,22 @@ type HookWiki = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type HookChronicle = {
|
||||||
|
event: "entry.create" | "entry.delete" | "entry.update";
|
||||||
|
model: "chronicle";
|
||||||
|
entry: {
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type HookChronicleChapter = {
|
||||||
|
event: "entry.create" | "entry.delete" | "entry.update";
|
||||||
|
model: "chronicles-chapter";
|
||||||
|
entry: {
|
||||||
|
chronicles: { slug: string }[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
type ResponseMailProps = {
|
type ResponseMailProps = {
|
||||||
message: string;
|
message: string;
|
||||||
revalidated: boolean;
|
revalidated: boolean;
|
||||||
|
@ -208,6 +226,30 @@ const Revalidate = (
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "chronicle": {
|
||||||
|
paths.push(`/chronicles`);
|
||||||
|
paths.push(`/chronicles/${body.entry.slug}`);
|
||||||
|
serverRuntimeConfig.locales?.map((locale: string) => {
|
||||||
|
paths.push(`/${locale}/chronicles`);
|
||||||
|
paths.push(`/${locale}/chronicles/${body.entry.slug}`);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "chronicles-chapter": {
|
||||||
|
paths.push(`/chronicles`);
|
||||||
|
serverRuntimeConfig.locales?.map((locale: string) => {
|
||||||
|
paths.push(`/${locale}/chronicles`);
|
||||||
|
});
|
||||||
|
body.entry.chronicles.map((chronicle) => {
|
||||||
|
paths.push(`/chronicles/${chronicle.slug}`);
|
||||||
|
serverRuntimeConfig.locales?.map((locale: string) => {
|
||||||
|
paths.push(`/${locale}/chronicles/${chronicle.slug}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "custom": {
|
case "custom": {
|
||||||
paths.push(`${body.url}`);
|
paths.push(`${body.url}`);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -3,15 +3,18 @@ import {
|
||||||
getPostStaticProps,
|
getPostStaticProps,
|
||||||
PostStaticProps,
|
PostStaticProps,
|
||||||
} from "graphql/getPostStaticProps";
|
} from "graphql/getPostStaticProps";
|
||||||
|
import { getOpenGraph } from "helpers/openGraph";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭────────╮
|
* ╭────────╮
|
||||||
* ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
|
* ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const Home = (props: PostStaticProps): JSX.Element => (
|
const Home = ({ langui, ...otherProps }: PostStaticProps): JSX.Element => (
|
||||||
<PostPage
|
<PostPage
|
||||||
{...props}
|
{...otherProps}
|
||||||
|
openGraph={getOpenGraph(langui)}
|
||||||
|
langui={langui}
|
||||||
prependBody={
|
prependBody={
|
||||||
<div className="grid w-full place-content-center place-items-center gap-5 text-center">
|
<div className="grid w-full place-content-center place-items-center gap-5 text-center">
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -165,10 +165,10 @@ const Library = ({
|
||||||
case 1: {
|
case 1: {
|
||||||
const priceA = a.attributes.price
|
const priceA = a.attributes.price
|
||||||
? convertPrice(a.attributes.price, currencies[0])
|
? convertPrice(a.attributes.price, currencies[0])
|
||||||
: 99999;
|
: Infinity;
|
||||||
const priceB = b.attributes.price
|
const priceB = b.attributes.price
|
||||||
? convertPrice(b.attributes.price, currencies[0])
|
? convertPrice(b.attributes.price, currencies[0])
|
||||||
: 99999;
|
: Infinity;
|
||||||
return priceA - priceB;
|
return priceA - priceB;
|
||||||
}
|
}
|
||||||
case 2: {
|
case 2: {
|
||||||
|
|
|
@ -1,21 +1,36 @@
|
||||||
import { GetStaticProps } from "next";
|
import { GetStaticProps } from "next";
|
||||||
import { Fragment, useMemo } from "react";
|
import { Fragment, useCallback, useMemo } from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||||
import { InsetBox } from "components/InsetBox";
|
import { InsetBox } from "components/InsetBox";
|
||||||
import { NavOption } from "components/PanelComponents/NavOption";
|
|
||||||
import {
|
import {
|
||||||
ReturnButton,
|
ReturnButton,
|
||||||
ReturnButtonType,
|
ReturnButtonType,
|
||||||
} from "components/PanelComponents/ReturnButton";
|
} from "components/PanelComponents/ReturnButton";
|
||||||
import { ContentPanel } from "components/Panels/ContentPanel";
|
import { ContentPanel } from "components/Panels/ContentPanel";
|
||||||
import { SubPanel } from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import { ChronologyYearComponent } from "components/Wiki/Chronology/ChronologyYearComponent";
|
import {
|
||||||
import { GetChronologyItemsQuery, GetErasQuery } from "graphql/generated";
|
Enum_Componenttranslationschronologyitem_Status,
|
||||||
|
GetChronologyItemsQuery,
|
||||||
|
GetErasQuery,
|
||||||
|
} 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 { prettySlug } from "helpers/formatters";
|
import { prettySlug } from "helpers/formatters";
|
||||||
import { filterHasAttributes, isDefined } from "helpers/others";
|
import {
|
||||||
|
filterHasAttributes,
|
||||||
|
getStatusDescription,
|
||||||
|
isDefined,
|
||||||
|
isDefinedAndNotEmpty,
|
||||||
|
} from "helpers/others";
|
||||||
import { getOpenGraph } from "helpers/openGraph";
|
import { getOpenGraph } from "helpers/openGraph";
|
||||||
|
import { TranslatedNavOption, TranslatedProps } from "components/Translated";
|
||||||
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
|
import { ToolTip } from "components/ToolTip";
|
||||||
|
import { Chip } from "components/Chip";
|
||||||
|
import { Ico, Icon } from "components/Ico";
|
||||||
|
import { AnchorShare } from "components/AnchorShare";
|
||||||
|
import { datePickerToDate } from "helpers/date";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭────────╮
|
* ╭────────╮
|
||||||
|
@ -33,37 +48,9 @@ const Chronology = ({
|
||||||
chronologyItems,
|
chronologyItems,
|
||||||
chronologyEras,
|
chronologyEras,
|
||||||
langui,
|
langui,
|
||||||
|
languages,
|
||||||
...otherProps
|
...otherProps
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
// Group by year the Chronology items
|
|
||||||
const chronologyItemYearGroups = useMemo(() => {
|
|
||||||
const memo: Props["chronologyItems"][number][][][] = [];
|
|
||||||
chronologyEras.map(() => {
|
|
||||||
memo.push([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
let currentChronologyEraIndex = 0;
|
|
||||||
chronologyItems.map((item) => {
|
|
||||||
if (item.attributes) {
|
|
||||||
if (
|
|
||||||
item.attributes.year >
|
|
||||||
(chronologyEras[currentChronologyEraIndex].attributes?.ending_year ??
|
|
||||||
999999)
|
|
||||||
) {
|
|
||||||
currentChronologyEraIndex++;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
Object.hasOwn(memo[currentChronologyEraIndex], item.attributes.year)
|
|
||||||
) {
|
|
||||||
memo[currentChronologyEraIndex][item.attributes.year].push(item);
|
|
||||||
} else {
|
|
||||||
memo[currentChronologyEraIndex][item.attributes.year] = [item];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return memo;
|
|
||||||
}, [chronologyEras, chronologyItems]);
|
|
||||||
|
|
||||||
const subPanel = useMemo(
|
const subPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
|
@ -78,16 +65,19 @@ const Chronology = ({
|
||||||
{filterHasAttributes(chronologyEras, ["attributes", "id"] as const).map(
|
{filterHasAttributes(chronologyEras, ["attributes", "id"] as const).map(
|
||||||
(era) => (
|
(era) => (
|
||||||
<Fragment key={era.id}>
|
<Fragment key={era.id}>
|
||||||
<NavOption
|
<TranslatedNavOption
|
||||||
|
translations={filterHasAttributes(era.attributes.title, [
|
||||||
|
"language.data.attributes.code",
|
||||||
|
] as const).map((translation) => ({
|
||||||
|
language: translation.language.data.attributes.code,
|
||||||
|
title: translation.title,
|
||||||
|
subtitle: `${era.attributes.starting_year} → ${era.attributes.ending_year}`,
|
||||||
|
}))}
|
||||||
|
fallback={{
|
||||||
|
title: prettySlug(era.attributes.slug),
|
||||||
|
subtitle: `${era.attributes.starting_year} → ${era.attributes.ending_year}`,
|
||||||
|
}}
|
||||||
url={`#${era.attributes.slug}`}
|
url={`#${era.attributes.slug}`}
|
||||||
title={
|
|
||||||
era.attributes.title &&
|
|
||||||
era.attributes.title.length > 0 &&
|
|
||||||
era.attributes.title[0]
|
|
||||||
? era.attributes.title[0].title
|
|
||||||
: prettySlug(era.attributes.slug)
|
|
||||||
}
|
|
||||||
subtitle={`${era.attributes.starting_year} → ${era.attributes.ending_year}`}
|
|
||||||
border
|
border
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
@ -109,39 +99,34 @@ const Chronology = ({
|
||||||
className="mb-10"
|
className="mb-10"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{chronologyItemYearGroups.map((era, eraIndex) => (
|
{filterHasAttributes(chronologyEras, ["attributes"] as const).map(
|
||||||
<Fragment key={eraIndex}>
|
(era) => (
|
||||||
<InsetBox
|
<TranslatedChronologyEra
|
||||||
id={chronologyEras[eraIndex].attributes?.slug}
|
key={era.attributes.slug}
|
||||||
className="my-8 grid gap-4 text-center"
|
id={era.attributes.slug}
|
||||||
>
|
translations={filterHasAttributes(era.attributes.title, [
|
||||||
<h2 className="text-2xl">
|
"language.data.attributes.code",
|
||||||
{chronologyEras[eraIndex].attributes?.title?.[0]
|
] as const).map((translation) => ({
|
||||||
? chronologyEras[eraIndex].attributes?.title?.[0]?.title
|
language: translation.language.data.attributes.code,
|
||||||
: prettySlug(chronologyEras[eraIndex].attributes?.slug)}
|
title: translation.title,
|
||||||
</h2>
|
description: translation.description,
|
||||||
<p className="whitespace-pre-line ">
|
}))}
|
||||||
{chronologyEras[eraIndex].attributes?.title?.[0]
|
fallback={{ title: prettySlug(era.attributes.slug) }}
|
||||||
? chronologyEras[eraIndex].attributes?.title?.[0]?.description
|
chronologyItems={filterHasAttributes(chronologyItems, [
|
||||||
: ""}
|
"attributes",
|
||||||
</p>
|
] as const).filter(
|
||||||
</InsetBox>
|
(item) =>
|
||||||
{era.map((items, index) => (
|
item.attributes.year >= era.attributes.starting_year &&
|
||||||
<Fragment key={index}>
|
item.attributes.year < era.attributes.ending_year
|
||||||
{items[0].attributes && isDefined(items[0].attributes.year) && (
|
)}
|
||||||
<ChronologyYearComponent
|
langui={langui}
|
||||||
year={items[0].attributes.year}
|
languages={languages}
|
||||||
items={items}
|
/>
|
||||||
langui={langui}
|
)
|
||||||
/>
|
)}
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</ContentPanel>
|
</ContentPanel>
|
||||||
),
|
),
|
||||||
[chronologyEras, chronologyItemYearGroups, langui]
|
[chronologyEras, chronologyItems, languages, langui]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -149,6 +134,7 @@ const Chronology = ({
|
||||||
contentPanel={contentPanel}
|
contentPanel={contentPanel}
|
||||||
subPanel={subPanel}
|
subPanel={subPanel}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
|
languages={languages}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -180,3 +166,317 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
props: props,
|
props: props,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ╭──────────────────────╮
|
||||||
|
* ───────────────────────────────────╯ PRIVATE COMPONENTS ╰──────────────────────────────────────
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface ChronologyEraProps {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description?: string | null | undefined;
|
||||||
|
chronologyItems: Props["chronologyItems"];
|
||||||
|
langui: AppStaticProps["langui"];
|
||||||
|
languages: AppStaticProps["languages"];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChronologyEra = ({
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
chronologyItems,
|
||||||
|
langui,
|
||||||
|
languages,
|
||||||
|
}: ChronologyEraProps) => {
|
||||||
|
const yearGroups = useMemo(() => {
|
||||||
|
const memo: Props["chronologyItems"][] = [];
|
||||||
|
let currentYear = -Infinity;
|
||||||
|
filterHasAttributes(chronologyItems, ["attributes"] as const).forEach(
|
||||||
|
(item) => {
|
||||||
|
if (currentYear === item.attributes.year) {
|
||||||
|
memo[memo.length - 1].push(item);
|
||||||
|
} else {
|
||||||
|
currentYear = item.attributes.year;
|
||||||
|
memo.push([item]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return memo;
|
||||||
|
}, [chronologyItems]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id={id}>
|
||||||
|
<InsetBox className="my-8 grid gap-4 text-center">
|
||||||
|
<h2 className="flex place-content-center gap-3 text-2xl">
|
||||||
|
{title}
|
||||||
|
<AnchorShare id={id} />
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{isDefinedAndNotEmpty(description) && (
|
||||||
|
<p className="whitespace-pre-line ">{description}</p>
|
||||||
|
)}
|
||||||
|
</InsetBox>
|
||||||
|
<div>
|
||||||
|
{yearGroups.map((item, index) => (
|
||||||
|
<ChronologyYear
|
||||||
|
key={index}
|
||||||
|
items={item}
|
||||||
|
langui={langui}
|
||||||
|
languages={languages}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
|
const TranslatedChronologyEra = ({
|
||||||
|
translations,
|
||||||
|
fallback,
|
||||||
|
...otherProps
|
||||||
|
}: TranslatedProps<
|
||||||
|
Parameters<typeof ChronologyEra>[0],
|
||||||
|
"description" | "title"
|
||||||
|
>): JSX.Element => {
|
||||||
|
const [selectedTranslation] = useSmartLanguage({
|
||||||
|
items: translations,
|
||||||
|
languageExtractor: (item: { language: string }): string => item.language,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChronologyEra
|
||||||
|
title={selectedTranslation?.title ?? fallback.title}
|
||||||
|
description={selectedTranslation?.description ?? fallback.description}
|
||||||
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
|
interface ChronologyYearProps {
|
||||||
|
items: NonNullable<Props["chronologyItems"]>;
|
||||||
|
langui: AppStaticProps["langui"];
|
||||||
|
languages: AppStaticProps["languages"];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChronologyYear = ({ items, langui, languages }: ChronologyYearProps) => (
|
||||||
|
<div
|
||||||
|
className="rounded-2xl target:my-4 target:bg-mid target:py-4"
|
||||||
|
id={generateAnchor(items[0].attributes?.year)}
|
||||||
|
>
|
||||||
|
{filterHasAttributes(items, ["attributes.events"] as const).map(
|
||||||
|
(item, index) => (
|
||||||
|
<ChronologyDate
|
||||||
|
key={index}
|
||||||
|
langui={langui}
|
||||||
|
languages={languages}
|
||||||
|
date={{
|
||||||
|
year: item.attributes.year,
|
||||||
|
month: item.attributes.month,
|
||||||
|
day: item.attributes.day,
|
||||||
|
displayYear: index === 0,
|
||||||
|
overwriteYear: item.attributes.displayed_date,
|
||||||
|
}}
|
||||||
|
events={item.attributes.events}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
|
interface ChronologyDateProps {
|
||||||
|
date: {
|
||||||
|
year: number;
|
||||||
|
month: number | null | undefined;
|
||||||
|
day: number | null | undefined;
|
||||||
|
displayYear: boolean;
|
||||||
|
overwriteYear?: string | null | undefined;
|
||||||
|
};
|
||||||
|
events: NonNullable<
|
||||||
|
NonNullable<
|
||||||
|
NonNullable<Props["chronologyItems"]>[number]["attributes"]
|
||||||
|
>["events"]
|
||||||
|
>;
|
||||||
|
langui: AppStaticProps["langui"];
|
||||||
|
languages: AppStaticProps["languages"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChronologyDate = ({
|
||||||
|
date,
|
||||||
|
events,
|
||||||
|
langui,
|
||||||
|
languages,
|
||||||
|
}: ChronologyDateProps): JSX.Element => {
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="grid grid-cols-[4em] grid-rows-[auto_1fr]
|
||||||
|
gap-x-8 rounded-2xl py-4 px-8 target:my-4 target:bg-mid target:py-8"
|
||||||
|
id={generateAnchor(date.year, date.month, date.day)}
|
||||||
|
>
|
||||||
|
{date.displayYear && (
|
||||||
|
<p className="mt-5 text-right text-lg font-bold">
|
||||||
|
{isDefinedAndNotEmpty(date.overwriteYear)
|
||||||
|
? date.overwriteYear
|
||||||
|
: date.year}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p className="col-start-1 text-right text-sm text-dark">
|
||||||
|
{isDefined(date.month)
|
||||||
|
? isDefined(date.day)
|
||||||
|
? datePickerToDate({
|
||||||
|
year: date.year,
|
||||||
|
month: date.month,
|
||||||
|
day: date.day,
|
||||||
|
}).toLocaleDateString(router.locale, {
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
})
|
||||||
|
: datePickerToDate({
|
||||||
|
year: date.year,
|
||||||
|
month: date.month,
|
||||||
|
day: date.day,
|
||||||
|
}).toLocaleDateString(router.locale, {
|
||||||
|
month: "short",
|
||||||
|
})
|
||||||
|
: ""}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="col-start-2 row-span-2 row-start-1 grid gap-4">
|
||||||
|
{filterHasAttributes(events, ["id", "translations"] as const).map(
|
||||||
|
(event) => (
|
||||||
|
<ChronologyEvent
|
||||||
|
id={generateAnchor(date.year, date.month, date.day)}
|
||||||
|
key={event.id}
|
||||||
|
event={event}
|
||||||
|
langui={langui}
|
||||||
|
languages={languages}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
|
interface ChronologyEventProps {
|
||||||
|
event: NonNullable<
|
||||||
|
NonNullable<
|
||||||
|
NonNullable<
|
||||||
|
NonNullable<Props["chronologyItems"]>[number]["attributes"]
|
||||||
|
>["events"]
|
||||||
|
>[number]
|
||||||
|
>;
|
||||||
|
langui: AppStaticProps["langui"];
|
||||||
|
languages: AppStaticProps["languages"];
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChronologyEvent = ({
|
||||||
|
event,
|
||||||
|
langui,
|
||||||
|
languages,
|
||||||
|
id,
|
||||||
|
}: ChronologyEventProps): JSX.Element => {
|
||||||
|
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
||||||
|
useSmartLanguage({
|
||||||
|
items: event.translations ?? [],
|
||||||
|
languageExtractor: useCallback(
|
||||||
|
(
|
||||||
|
item: NonNullable<
|
||||||
|
ChronologyEventProps["event"]["translations"]
|
||||||
|
>[number]
|
||||||
|
) => item?.language?.data?.attributes?.code,
|
||||||
|
[]
|
||||||
|
),
|
||||||
|
languages: languages,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{selectedTranslation && (
|
||||||
|
<>
|
||||||
|
<div className="mr-2 flex place-items-center gap-x-2">
|
||||||
|
<LanguageSwitcher
|
||||||
|
{...languageSwitcherProps}
|
||||||
|
size="small"
|
||||||
|
showBadge={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{selectedTranslation.status !==
|
||||||
|
Enum_Componenttranslationschronologyitem_Status.Done && (
|
||||||
|
<ToolTip
|
||||||
|
content={getStatusDescription(
|
||||||
|
selectedTranslation.status,
|
||||||
|
langui
|
||||||
|
)}
|
||||||
|
maxWidth={"20rem"}
|
||||||
|
>
|
||||||
|
<Chip text={selectedTranslation.status} />
|
||||||
|
</ToolTip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p className="mt-[0.2rem] grid grid-flow-col gap-1 place-self-start text-xs text-dark">
|
||||||
|
{event.source?.data ? (
|
||||||
|
`(${event.source.data.attributes?.name})`
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Ico icon={Icon.Warning} className="!text-sm" />
|
||||||
|
{langui.no_source_warning}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<span className="flex-shrink">
|
||||||
|
<AnchorShare id={id} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedTranslation.title && (
|
||||||
|
<div className="mt-1 flex place-content-start place-items-start gap-2">
|
||||||
|
<h3 className="font-headers font-bold">
|
||||||
|
{selectedTranslation.title}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedTranslation.description && (
|
||||||
|
<p className="whitespace-pre-line">
|
||||||
|
{selectedTranslation.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedTranslation.note && (
|
||||||
|
<em>{`${langui.notes}: ${selectedTranslation.note}`}</em>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ╭───────────────────╮
|
||||||
|
* ─────────────────────────────────────╯ PRIVATE METHODS ╰───────────────────────────────────────
|
||||||
|
*/
|
||||||
|
|
||||||
|
const generateAnchor = (
|
||||||
|
year: number | undefined,
|
||||||
|
month?: number | null | undefined,
|
||||||
|
day?: number | null | undefined
|
||||||
|
): string => {
|
||||||
|
let result = "";
|
||||||
|
if (isDefined(year)) result += year;
|
||||||
|
if (isDefined(month)) result += `-${month.toString().padStart(2, "0")}`;
|
||||||
|
if (isDefined(day)) result += `-${day.toString().padStart(2, "0")}`;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue