Moved assert helpers into their own folder + activated some tsconfig strict options

This commit is contained in:
DrMint 2022-12-09 23:03:09 +01:00
parent e9950602c4
commit 0ddd46643b
65 changed files with 314 additions and 302 deletions

View File

@ -3,7 +3,7 @@ import { useSwipeable } from "react-swipeable";
import { layout } from "../../design.config"; import { layout } from "../../design.config";
import { Ico, Icon } from "./Ico"; import { Ico, Icon } from "./Ico";
import { MainPanel } from "./Panels/MainPanel"; import { MainPanel } from "./Panels/MainPanel";
import { isDefined, isUndefined } from "helpers/others"; import { isDefined, isUndefined } from "helpers/asserts";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { OpenGraph, TITLE_PREFIX, TITLE_SEPARATOR } from "helpers/openGraph"; import { OpenGraph, TITLE_PREFIX, TITLE_SEPARATOR } from "helpers/openGraph";
import { Ids } from "types/ids"; import { Ids } from "types/ids";
@ -239,7 +239,7 @@ const ContentPlaceholder = ({ message, icon }: ContentPlaceholderProps): JSX.Ele
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
border-dark p-8 text-dark opacity-40"> border-dark p-8 text-dark opacity-40">
{isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />} {isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />}
<p className={cJoin("w-64 text-2xl", cIf(!isDefined(icon), "text-center"))}>{message}</p> <p className={cJoin("w-64 text-2xl", cIf(isUndefined(icon), "text-center"))}>{message}</p>
</div> </div>
</div> </div>
); );

View File

@ -3,7 +3,7 @@ import { DatePickerFragment } from "graphql/generated";
import { TranslatedProps } from "types/TranslatedProps"; import { TranslatedProps } from "types/TranslatedProps";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { DownPressable } from "components/Containers/DownPressable"; import { DownPressable } from "components/Containers/DownPressable";
import { isDefined } from "helpers/others"; import { isDefined } from "helpers/asserts";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
/* /*

View File

@ -2,7 +2,7 @@ import { useCallback } from "react";
import { useBoolean } from "usehooks-ts"; import { useBoolean } from "usehooks-ts";
import { TranslatedChroniclePreview } from "./ChroniclePreview"; import { TranslatedChroniclePreview } from "./ChroniclePreview";
import { GetChroniclesChaptersQuery } from "graphql/generated"; import { GetChroniclesChaptersQuery } from "graphql/generated";
import { filterHasAttributes } from "helpers/others"; import { filterHasAttributes } from "helpers/asserts";
import { prettyInlineTitle, prettySlug, sJoin } from "helpers/formatters"; import { prettyInlineTitle, prettySlug, sJoin } from "helpers/formatters";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { compareDate } from "helpers/date"; import { compareDate } from "helpers/date";

View File

@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { cJoin, cIf } from "helpers/className"; import { cJoin, cIf } from "helpers/className";
import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; import { isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
import { atoms } from "contexts/atoms"; import { atoms } from "contexts/atoms";
import { useAtomSetter, useAtomPair } from "helpers/atoms"; import { useAtomSetter, useAtomPair } from "helpers/atoms";

View File

@ -2,7 +2,7 @@ import { MouseEventHandler, useCallback } from "react";
import { Link } from "./Link"; import { Link } from "./Link";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; import { isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
import { TranslatedProps } from "types/TranslatedProps"; import { TranslatedProps } from "types/TranslatedProps";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";

View File

@ -2,7 +2,7 @@ import { Button } from "./Button";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { cJoin } from "helpers/className"; import { cJoin } from "helpers/className";
import { ConditionalWrapper, Wrapper } from "helpers/component"; import { ConditionalWrapper, Wrapper } from "helpers/component";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/asserts";
/* /*
* *

View File

@ -1,7 +1,7 @@
import React, { MouseEventHandler } from "react"; import React, { MouseEventHandler } from "react";
import NextLink from "next/link"; import NextLink from "next/link";
import { ConditionalWrapper, Wrapper } from "helpers/component"; import { ConditionalWrapper, Wrapper } from "helpers/component";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/asserts";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
interface Props { interface Props {

View File

@ -1,6 +1,7 @@
import { Fragment, useCallback } from "react"; import { Fragment, useCallback } from "react";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { arrayMove, isDefinedAndNotEmpty } from "helpers/others"; import { arrayMove } from "helpers/others";
import { isDefinedAndNotEmpty } from "helpers/asserts";
/* /*
* *
@ -16,6 +17,14 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
interface InsertedLabelProps {
label?: string;
}
const InsertedLabel = ({ label }: InsertedLabelProps) => (
<>{isDefinedAndNotEmpty(label) && <p>{label}</p>}</>
);
export const OrderableList = ({ onChange, items, insertLabels }: Props): JSX.Element => { export const OrderableList = ({ onChange, items, insertLabels }: Props): JSX.Element => {
const updateOrder = useCallback( const updateOrder = useCallback(
(sourceIndex: number, targetIndex: number) => { (sourceIndex: number, targetIndex: number) => {
@ -29,9 +38,8 @@ export const OrderableList = ({ onChange, items, insertLabels }: Props): JSX.Ele
<div className="grid gap-2"> <div className="grid gap-2">
{items.map((item, index) => ( {items.map((item, index) => (
<Fragment key={index}> <Fragment key={index}>
{insertLabels && isDefinedAndNotEmpty(insertLabels[index]?.name) && ( <InsertedLabel label={insertLabels?.[index]?.name} />
<p>{insertLabels[index].name}</p>
)}
<div <div
onDragStart={(event) => { onDragStart={(event) => {
const source = event.target as HTMLElement; const source = event.target as HTMLElement;

View File

@ -1,6 +1,6 @@
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/asserts";
/* /*
* *

View File

@ -1,5 +1,5 @@
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/asserts";
/* /*
* *

View File

@ -9,7 +9,7 @@ import { useFullscreen } from "hooks/useFullscreen";
import { Ids } from "types/ids"; import { Ids } from "types/ids";
import { UploadImageFragment } from "graphql/generated"; import { UploadImageFragment } from "graphql/generated";
import { ImageQuality } from "helpers/img"; import { ImageQuality } from "helpers/img";
import { isDefined } from "helpers/others"; import { isDefined } from "helpers/asserts";
import { useAtomGetter } from "helpers/atoms"; import { useAtomGetter } from "helpers/atoms";
import { atoms } from "contexts/atoms"; import { atoms } from "contexts/atoms";

View File

@ -7,7 +7,7 @@ import { InsetBox } from "components/Containers/InsetBox";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { slugify } from "helpers/formatters"; 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/asserts";
import { AnchorShare } from "components/AnchorShare"; import { AnchorShare } from "components/AnchorShare";
import { useIntersectionList } from "hooks/useIntersectionList"; import { useIntersectionList } from "hooks/useIntersectionList";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
@ -459,7 +459,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
h5 = -1; h5 = -1;
scenebreak = 0; scenebreak = 0;
} else if (h2 >= 0 && line.startsWith('<Header level="3"')) { } else if (h2 >= 0 && line.startsWith('<Header level="3"')) {
toc.children[h2].children.push({ toc.children[h2]?.children.push({
title: getTitle(line), title: getTitle(line),
slug: getSlug(line), slug: getSlug(line),
children: [], children: [],
@ -469,7 +469,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
h5 = -1; h5 = -1;
scenebreak = 0; scenebreak = 0;
} else if (h3 >= 0 && line.startsWith('<Header level="4"')) { } else if (h3 >= 0 && line.startsWith('<Header level="4"')) {
toc.children[h2].children[h3].children.push({ toc.children[h2]?.children[h3]?.children.push({
title: getTitle(line), title: getTitle(line),
slug: getSlug(line), slug: getSlug(line),
children: [], children: [],
@ -478,7 +478,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
h5 = -1; h5 = -1;
scenebreak = 0; scenebreak = 0;
} else if (h4 >= 0 && line.startsWith('<Header level="5"')) { } else if (h4 >= 0 && line.startsWith('<Header level="5"')) {
toc.children[h2].children[h3].children[h4].children.push({ toc.children[h2]?.children[h3]?.children[h4]?.children.push({
title: getTitle(line), title: getTitle(line),
slug: getSlug(line), slug: getSlug(line),
children: [], children: [],
@ -486,7 +486,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
h5++; h5++;
scenebreak = 0; scenebreak = 0;
} else if (h5 >= 0 && line.startsWith('<Header level="6"')) { } else if (h5 >= 0 && line.startsWith('<Header level="6"')) {
toc.children[h2].children[h3].children[h4].children[h5].children.push({ toc.children[h2]?.children[h3]?.children[h4]?.children[h5]?.children.push({
title: getTitle(line), title: getTitle(line),
slug: getSlug(line), slug: getSlug(line),
children: [], children: [],
@ -502,13 +502,13 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
}; };
if (h5 >= 0) { if (h5 >= 0) {
toc.children[h2].children[h3].children[h4].children[h5].children.push(child); toc.children[h2]?.children[h3]?.children[h4]?.children[h5]?.children.push(child);
} else if (h4 >= 0) { } else if (h4 >= 0) {
toc.children[h2].children[h3].children[h4].children.push(child); toc.children[h2]?.children[h3]?.children[h4]?.children.push(child);
} else if (h3 >= 0) { } else if (h3 >= 0) {
toc.children[h2].children[h3].children.push(child); toc.children[h2]?.children[h3]?.children.push(child);
} else if (h2 >= 0) { } else if (h2 >= 0) {
toc.children[h2].children.push(child); toc.children[h2]?.children.push(child);
} else { } else {
toc.children.push(child); toc.children.push(child);
} }

View File

@ -3,7 +3,7 @@ import { MouseEventHandler, useCallback } from "react";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/asserts";
import { TranslatedProps } from "types/TranslatedProps"; import { TranslatedProps } from "types/TranslatedProps";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { DownPressable } from "components/Containers/DownPressable"; import { DownPressable } from "components/Containers/DownPressable";

View File

@ -1,5 +1,5 @@
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/asserts";
/* /*
* *

View File

@ -3,7 +3,7 @@ import { Icon } from "components/Ico";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
import { TranslatedProps } from "types/TranslatedProps"; import { TranslatedProps } from "types/TranslatedProps";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { isDefined } from "helpers/others"; import { isUndefined } from "helpers/asserts";
import { atoms } from "contexts/atoms"; import { atoms } from "contexts/atoms";
import { useAtomGetter } from "helpers/atoms"; import { useAtomGetter } from "helpers/atoms";
@ -30,7 +30,7 @@ export const ReturnButton = ({ href, title, displayOnlyOn, className }: Props):
<> <>
{((is3ColumnsLayout && displayOnlyOn === "3ColumnsLayout") || {((is3ColumnsLayout && displayOnlyOn === "3ColumnsLayout") ||
(!is3ColumnsLayout && displayOnlyOn === "1ColumnLayout") || (!is3ColumnsLayout && displayOnlyOn === "1ColumnLayout") ||
!isDefined(displayOnlyOn)) && ( isUndefined(displayOnlyOn)) && (
<div className={className}> <div className={className}>
<Button href={href} text={`${langui.return_to} ${title}`} icon={Icon.NavigateBefore} /> <Button href={href} text={`${langui.return_to} ${title}`} icon={Icon.NavigateBefore} />
</div> </div>

View File

@ -4,7 +4,7 @@ import { NavOption } from "components/PanelComponents/NavOption";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/asserts";
import { Link } from "components/Inputs/Link"; import { Link } from "components/Inputs/Link";
import { sendAnalytics } from "helpers/analytics"; import { sendAnalytics } from "helpers/analytics";
import { ColoredSvg } from "components/ColoredSvg"; import { ColoredSvg } from "components/ColoredSvg";

View File

@ -10,7 +10,7 @@ import { Popup } from "components/Containers/Popup";
import { sendAnalytics } from "helpers/analytics"; import { sendAnalytics } from "helpers/analytics";
import { cJoin, cIf } from "helpers/className"; import { cJoin, cIf } from "helpers/className";
import { prettyLanguage } from "helpers/formatters"; import { prettyLanguage } from "helpers/formatters";
import { filterHasAttributes, isDefined } from "helpers/others"; import { filterHasAttributes, isDefined } from "helpers/asserts";
import { atoms } from "contexts/atoms"; import { atoms } from "contexts/atoms";
import { useAtomGetter, useAtomPair } from "helpers/atoms"; import { useAtomGetter, useAtomPair } from "helpers/atoms";
import { ThemeMode } from "contexts/settings"; import { ThemeMode } from "contexts/settings";
@ -39,15 +39,10 @@ export const SettingsPopup = (): JSX.Element => {
); );
const [currencySelect, setCurrencySelect] = useState<number>(-1); const [currencySelect, setCurrencySelect] = useState<number>(-1);
useEffect(() => { useEffect(() => {
if (isDefined(currency)) setCurrencySelect(currencyOptions.indexOf(currency)); if (isDefined(currency)) setCurrencySelect(currencyOptions.indexOf(currency));
}, [currency, currencyOptions]); }, [currency, currencyOptions]);
useEffect(() => {
if (currencySelect >= 0) setCurrency(currencyOptions[currencySelect]);
}, [currencyOptions, currencySelect, setCurrency]);
return ( return (
<Popup <Popup
isVisible={isSettingsOpened} isVisible={isSettingsOpened}
@ -134,8 +129,11 @@ export const SettingsPopup = (): JSX.Element => {
options={currencyOptions} options={currencyOptions}
value={currencySelect} value={currencySelect}
onChange={(newCurrency) => { onChange={(newCurrency) => {
setCurrencySelect(newCurrency); const newCurrencyName = currencyOptions[newCurrency];
sendAnalytics("Settings", `Change currency (${currencyOptions[newCurrency]})}`); if (isDefined(newCurrencyName)) {
setCurrency(newCurrencyName);
sendAnalytics("Settings", `Change currency (${currencyOptions[newCurrency]})}`);
}
}} }}
className="w-28" className="w-28"
/> />

View File

@ -11,7 +11,8 @@ import { ThumbnailHeader } from "./ThumbnailHeader";
import { ToolTip } from "./ToolTip"; import { ToolTip } from "./ToolTip";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { PostWithTranslations } from "types/types"; import { PostWithTranslations } from "types/types";
import { filterHasAttributes, getStatusDescription } from "helpers/others"; import { getStatusDescription } from "helpers/others";
import { filterHasAttributes } from "helpers/asserts";
import { prettySlug } from "helpers/formatters"; import { prettySlug } from "helpers/formatters";
import { atoms } from "contexts/atoms"; import { atoms } from "contexts/atoms";
import { useAtomGetter } from "helpers/atoms"; import { useAtomGetter } from "helpers/atoms";

View File

@ -7,7 +7,7 @@ import { ImageQuality } from "helpers/img";
import { TranslatedProps } from "types/TranslatedProps"; import { TranslatedProps } from "types/TranslatedProps";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { isDefined } from "helpers/others"; import { isDefined } from "helpers/asserts";
/* /*
* *

View File

@ -5,7 +5,7 @@ import { ToolTip } from "./ToolTip";
import { Chip } from "components/Chip"; import { Chip } from "components/Chip";
import { RecorderChipFragment } from "graphql/generated"; import { RecorderChipFragment } from "graphql/generated";
import { ImageQuality } from "helpers/img"; import { ImageQuality } from "helpers/img";
import { filterHasAttributes } from "helpers/others"; import { filterHasAttributes } from "helpers/asserts";
import { atoms } from "contexts/atoms"; import { atoms } from "contexts/atoms";
import { useAtomGetter } from "helpers/atoms"; import { useAtomGetter } from "helpers/atoms";

View File

@ -5,7 +5,7 @@ import { Chip } from "./Chip";
import { PageSelector } from "./Inputs/PageSelector"; import { PageSelector } from "./Inputs/PageSelector";
import { Ico, Icon } from "./Ico"; import { Ico, Icon } from "./Ico";
import { cJoin } from "helpers/className"; import { cJoin } from "helpers/className";
import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; import { isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange"; import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { Ids } from "types/ids"; import { Ids } from "types/ids";
import { atoms } from "contexts/atoms"; import { atoms } from "contexts/atoms";
@ -95,17 +95,17 @@ export const SmartList = <T,>({
const memo: Group<T>[] = []; const memo: Group<T>[] = [];
sortedItem.forEach((item) => { sortedItem.forEach((item) => {
groupingFunction(item).forEach((category) => { groupingFunction(item).forEach((groupName) => {
const index = memo.findIndex((group) => group.name === category); const group = memo.find((elem) => elem.name === groupName);
if (index === -1) { if (isDefined(group)) {
group.items.push(item);
group.totalCount += groupCountingFunction(item);
} else {
memo.push({ memo.push({
name: category, name: groupName,
items: [item], items: [item],
totalCount: groupCountingFunction(item), totalCount: groupCountingFunction(item),
}); });
} else {
memo[index].items.push(item);
memo[index].totalCount += groupCountingFunction(item);
} }
}); });
}); });
@ -170,7 +170,7 @@ export const SmartList = <T,>({
)} )}
<div className="mb-8"> <div className="mb-8">
{pages[page]?.length > 0 ? ( {(pages[page]?.length ?? 0) > 0 ? (
pages[page]?.map( pages[page]?.map(
(group) => (group) =>
group.items.length > 0 && ( group.items.length > 0 && (

View File

@ -5,7 +5,7 @@ import { Markdawn } from "components/Markdown/Markdawn";
import { GetContentTextQuery, UploadImageFragment } from "graphql/generated"; import { GetContentTextQuery, UploadImageFragment } from "graphql/generated";
import { prettyInlineTitle, prettySlug, slugify } from "helpers/formatters"; import { prettyInlineTitle, prettySlug, slugify } from "helpers/formatters";
import { ImageQuality } from "helpers/img"; import { ImageQuality } from "helpers/img";
import { filterHasAttributes } from "helpers/others"; import { filterHasAttributes } from "helpers/asserts";
import { useAtomGetter } from "helpers/atoms"; import { useAtomGetter } from "helpers/atoms";
import { atoms } from "contexts/atoms"; import { atoms } from "contexts/atoms";

View File

@ -3,7 +3,7 @@ import { useEffectOnce } from "usehooks-ts";
import { atom } from "jotai"; import { atom } from "jotai";
import { UploadImageFragment } from "graphql/generated"; import { UploadImageFragment } from "graphql/generated";
import { LightBox } from "components/LightBox"; import { LightBox } from "components/LightBox";
import { filterDefined } from "helpers/others"; import { filterDefined } from "helpers/asserts";
import { atomPairing, useAtomSetter } from "helpers/atoms"; import { atomPairing, useAtomSetter } from "helpers/atoms";
const lightBoxAtom = atomPairing( const lightBoxAtom = atomPairing(

View File

@ -4,7 +4,7 @@ import { atom } from "jotai";
import { atomWithStorage } from "jotai/utils"; import { atomWithStorage } from "jotai/utils";
import { atomPairing, useAtomGetter, useAtomPair } from "helpers/atoms"; import { atomPairing, useAtomGetter, useAtomPair } from "helpers/atoms";
import { getDefaultPreferredLanguages } from "helpers/locales"; import { getDefaultPreferredLanguages } from "helpers/locales";
import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; import { isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
import { usePrefersDarkMode } from "hooks/useMediaQuery"; import { usePrefersDarkMode } from "hooks/useMediaQuery";
export enum ThemeMode { export enum ThemeMode {

View File

@ -1,5 +1,5 @@
import { useLayoutEffect } from "react"; import { useLayoutEffect } from "react";
import { isDefined } from "helpers/others"; import { isDefined } from "helpers/asserts";
import { useIsWebkit } from "hooks/useIsWebkit"; import { useIsWebkit } from "hooks/useIsWebkit";
export const useWebkitFixes = (): void => { export const useWebkitFixes = (): void => {

View File

@ -5,7 +5,7 @@ import { PostWithTranslations } from "types/types";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { prettyDate, prettySlug } from "helpers/formatters"; import { prettyDate, prettySlug } from "helpers/formatters";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales"; import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
import { filterHasAttributes, isDefined } from "helpers/others"; import { filterHasAttributes, isDefined } from "helpers/asserts";
import { getDescription } from "helpers/description"; import { getDescription } from "helpers/description";
import { AppLayoutRequired } from "components/AppLayout"; import { AppLayoutRequired } from "components/AppLayout";
@ -25,7 +25,7 @@ export const getPostStaticProps =
if ( if (
post.posts?.data && post.posts?.data &&
post.posts.data.length > 0 && post.posts.data.length > 0 &&
post.posts.data[0].attributes?.translations && post.posts.data[0]?.attributes?.translations &&
isDefined(context.locale) && isDefined(context.locale) &&
isDefined(context.locales) isDefined(context.locales)
) { ) {

79
src/helpers/asserts.ts Normal file
View File

@ -0,0 +1,79 @@
type JoinDot<K extends string, P extends string> = `${K}${"" extends K ? "" : "."}${P}`;
type PathDot<T, Acc extends string = ""> = T extends object
? {
[K in keyof T]: K extends string ? JoinDot<Acc, K> | PathDot<T[K], JoinDot<Acc, K>> : never;
}[keyof T]
: Acc;
type PathHead<T extends unknown[]> = T extends [infer head]
? head
: // eslint-disable-next-line @typescript-eslint/no-unused-vars
T extends [infer head, ...infer rest]
? head
: "";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type PathRest<T extends unknown[]> = T extends [infer head, ...infer rest]
? rest extends []
? never
: rest
: never;
type PathLast<T extends unknown[]> = T["length"] extends 1 ? true : false;
type Recursive<T, Path extends unknown[]> = PathHead<Path> extends keyof T
? Omit<T, PathHead<Path>> & {
[P in PathHead<Path>]-?: PathLast<Path> extends true
? NonNullable<T[P]>
: Recursive<NonNullable<T[P]>, PathRest<Path>>;
}
: T;
type Split<Str, Acc extends string[] = []> = Str extends `${infer Head}.${infer Rest}`
? Split<Rest, [...Acc, Head]>
: Str extends `${infer Last}`
? [...Acc, Last]
: never;
export type SelectiveNonNullable<T, P extends PathDot<T>> = Recursive<NonNullable<T>, Split<P>>;
export const isDefined = <T>(t: T): t is NonNullable<T> => t !== null && t !== undefined;
export const isUndefined = <T>(t: T | null | undefined): t is null | undefined => !isDefined(t);
export const isDefinedAndNotEmpty = (string: string | null | undefined): string is string =>
isDefined(string) && string.length > 0;
export const filterDefined = <T>(t: T[] | null | undefined): NonNullable<T>[] =>
isUndefined(t) ? [] : (t.filter((item) => isDefined(item)) as NonNullable<T>[]);
export const filterHasAttributes = <T, P extends PathDot<T>>(
t: T[] | null | undefined,
paths: readonly P[]
): SelectiveNonNullable<T, typeof paths[number]>[] =>
isDefined(t)
? (t.filter((item) => hasAttributes(item, paths)) as unknown as SelectiveNonNullable<
T,
typeof paths[number]
>[])
: [];
const hasAttributes = <T>(item: T, paths: readonly PathDot<T>[]): boolean =>
isDefined(item) && paths.every((path) => hasAttribute(item, path));
const hasAttribute = <T>(item: T, path: string): boolean => {
if (isDefined(item)) {
const [head, ...rest] = path.split(".");
if (isDefined(head) && Object.keys(item).includes(head)) {
const attribute = head as keyof T;
if (isDefined(item[attribute])) {
if (rest.length > 0) {
return hasAttribute(item[attribute], rest.join("."));
}
return true;
}
}
}
return false;
};

View File

@ -1,4 +1,4 @@
import { isDefined } from "./others"; import { isDefined } from "./asserts";
export interface Wrapper { export interface Wrapper {
children: React.ReactNode; children: React.ReactNode;

View File

@ -1,4 +1,4 @@
import { isUndefined } from "./others"; import { isUndefined } from "./asserts";
import { DatePickerFragment } from "graphql/generated"; import { DatePickerFragment } from "graphql/generated";
export const compareDate = ( export const compareDate = (

View File

@ -1,4 +1,4 @@
import { filterDefined, isDefined, isDefinedAndNotEmpty } from "./others"; import { filterDefined, isDefined, isDefinedAndNotEmpty } from "./asserts";
export const getDescription = ( export const getDescription = (
description: string | null | undefined, description: string | null | undefined,

View File

@ -1,5 +1,5 @@
import { convertPrice } from "./numbers"; import { convertPrice } from "./numbers";
import { isDefinedAndNotEmpty, isUndefined } from "./others"; import { isDefinedAndNotEmpty, isUndefined } from "./asserts";
import { datePickerToDate } from "./date"; import { datePickerToDate } from "./date";
import { Currencies, Languages, Langui } from "./localData"; import { Currencies, Languages, Langui } from "./localData";
import { DatePickerFragment, PricePickerFragment } from "graphql/generated"; import { DatePickerFragment, PricePickerFragment } from "graphql/generated";
@ -180,7 +180,7 @@ export const prettyItemSubType = (
case "ComponentMetadataGame": case "ComponentMetadataGame":
return metadata.platforms?.data && return metadata.platforms?.data &&
metadata.platforms.data.length > 0 && metadata.platforms.data.length > 0 &&
metadata.platforms.data[0].attributes metadata.platforms.data[0]?.attributes
? metadata.platforms.data[0].attributes.short ? metadata.platforms.data[0].attributes.short
: ""; : "";
case "ComponentMetadataGroup": { case "ComponentMetadataGroup": {

View File

@ -27,13 +27,11 @@ const imageQualityProperties: Record<ImageQuality, ImageProperties> = {
}; };
export const getAssetFilename = (path: string): string => { export const getAssetFilename = (path: string): string => {
let result = path.split("/"); // /uploads/329_7f41d09a98.webp -> 329_7f41d09a98.webp
result = result[result.length - 1].split("."); let result = path.substring(path.lastIndexOf("/") + 1);
result = result // 329_7f41d09a98.webp -> 329
.splice(0, result.length - 1) result = result.substring(0, result.indexOf("_"));
.join(".") return result;
.split("_");
return result[0];
}; };
export const getAssetURL = (url: string, quality: ImageQuality): string => { export const getAssetURL = (url: string, quality: ImageQuality): string => {

View File

@ -1,4 +1,4 @@
import { isDefined } from "./others"; import { isDefined } from "./asserts";
export const isUntangibleGroupItem = ( export const isUntangibleGroupItem = (
metadata: metadata:

View File

@ -1,4 +1,4 @@
import { isDefined } from "./others"; import { isDefined } from "./asserts";
export const getDefaultPreferredLanguages = (routerLocal: string, locales: string[]): string[] => { export const getDefaultPreferredLanguages = (routerLocal: string, locales: string[]): string[] => {
let defaultPreferredLanguages: string[] = []; let defaultPreferredLanguages: string[] = [];

View File

@ -1,5 +1,5 @@
import { OgImage, getImgSizesByQuality, ImageQuality, getAssetURL } from "./img"; import { OgImage, getImgSizesByQuality, ImageQuality, getAssetURL } from "./img";
import { isDefinedAndNotEmpty } from "./others"; import { isDefinedAndNotEmpty } from "./asserts";
import { Langui } from "./localData"; import { Langui } from "./localData";
import { UploadImageFragment } from "graphql/generated"; import { UploadImageFragment } from "graphql/generated";

View File

@ -1,5 +1,5 @@
import { PathDot, SelectiveNonNullable } from "../types/SelectiveNonNullable";
import { Langui } from "./localData"; import { Langui } from "./localData";
import { isDefined } from "./asserts";
import { import {
Enum_Componentsetstextset_Status, Enum_Componentsetstextset_Status,
GetLibraryItemQuery, GetLibraryItemQuery,
@ -45,47 +45,6 @@ export const getStatusDescription = (status: string, langui: Langui): string | n
} }
}; };
export const isDefined = <T>(t: T): t is NonNullable<T> => t !== null && t !== undefined;
export const isUndefined = <T>(t: T | null | undefined): t is null | undefined =>
t === null || t === undefined;
export const isDefinedAndNotEmpty = (string: string | null | undefined): string is string =>
isDefined(string) && string.length > 0;
export const filterDefined = <T>(t: T[] | null | undefined): NonNullable<T>[] =>
isUndefined(t) ? [] : (t.filter((item) => isDefined(item)) as NonNullable<T>[]);
export const filterHasAttributes = <T, P extends PathDot<T>>(
t: T[] | null | undefined,
paths: readonly P[]
): SelectiveNonNullable<T, typeof paths[number]>[] =>
isDefined(t)
? (t.filter((item) => hasAttributes(item, paths)) as unknown as SelectiveNonNullable<
T,
typeof paths[number]
>[])
: [];
const hasAttributes = <T>(item: T, paths: readonly PathDot<T>[]): boolean =>
isDefined(item) && paths.every((path) => hasAttribute(item, path));
const hasAttribute = <T>(item: T, path: string): boolean => {
if (isDefined(item)) {
const [head, ...rest] = path.split(".");
if (Object.keys(item).includes(head)) {
const attribute = head as keyof T;
if (isDefined(item[attribute])) {
if (rest.length > 0) {
return hasAttribute(item[attribute], rest.join("."));
}
return true;
}
}
}
return false;
};
export const iterateMap = <K, V, U>( export const iterateMap = <K, V, U>(
map: Map<K, V>, map: Map<K, V>,
callbackfn: (key: K, value: V, index: number) => U, callbackfn: (key: K, value: V, index: number) => U,
@ -99,7 +58,10 @@ export const iterateMap = <K, V, U>(
}; };
export const arrayMove = <T>(arr: T[], sourceIndex: number, targetIndex: number): T[] => { export const arrayMove = <T>(arr: T[], sourceIndex: number, targetIndex: number): T[] => {
arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]); const sourceItem = arr.splice(sourceIndex, 1)[0];
if (isDefined(sourceItem)) {
arr.splice(targetIndex, 0, sourceItem);
}
return arr; return arr;
}; };

View File

@ -1,4 +1,4 @@
import { isDefinedAndNotEmpty } from "./others"; import { isDefinedAndNotEmpty } from "./asserts";
export const prettyTerminalUnderlinedTitle = (string: string | null | undefined): string => export const prettyTerminalUnderlinedTitle = (string: string | null | undefined): string =>
isDefinedAndNotEmpty(string) isDefinedAndNotEmpty(string)

View File

@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { useIsClient } from "usehooks-ts"; import { useIsClient } from "usehooks-ts";
import { isDefined } from "helpers/others"; import { isDefined } from "helpers/asserts";
export const useFullscreen = ( export const useFullscreen = (
id: string id: string

View File

@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from "react";
import { throttle } from "throttle-debounce"; import { throttle } from "throttle-debounce";
import { useIsClient } from "usehooks-ts"; import { useIsClient } from "usehooks-ts";
import { useOnScroll } from "./useOnScroll"; import { useOnScroll } from "./useOnScroll";
import { isDefined } from "helpers/others"; import { isDefined, isUndefined } from "helpers/asserts";
import { Ids } from "types/ids"; import { Ids } from "types/ids";
export const useIntersectionList = (ids: string[]): number => { export const useIntersectionList = (ids: string[]): number => {
@ -16,21 +16,21 @@ export const useIntersectionList = (ids: string[]): number => {
(scroll: number) => { (scroll: number) => {
console.log("useIntersectionList"); console.log("useIntersectionList");
if (!isDefined(contentPanel)) { if (isUndefined(contentPanel)) {
setCurrentIntersection(-1); setCurrentIntersection(-1);
return; return;
} }
for (let idIndex = 0; idIndex < ids.length; idIndex++) { for (const [index, id] of [...ids].reverse().entries()) {
const elem = document.getElementById(ids[ids.length - 1 - idIndex]); const elem = document.getElementById(id);
const halfScreenOffset = window.screen.height / 2; const halfScreenOffset = window.screen.height / 2;
if (isDefined(elem) && scroll > elem.offsetTop - halfScreenOffset) { if (isDefined(elem) && scroll > elem.offsetTop - halfScreenOffset) {
setCurrentIntersection(ids.length - 1 - idIndex); const unreversedIndex = ids.length - 1 - index;
setCurrentIntersection(unreversedIndex);
return; return;
} }
} }
setCurrentIntersection(-1);
}, },
[ids, contentPanel] [ids, contentPanel]
); );

View File

@ -1,6 +1,6 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useIsClient } from "usehooks-ts"; import { useIsClient } from "usehooks-ts";
import { isDefined } from "helpers/others"; import { isDefined } from "helpers/asserts";
export const useOnResize = ( export const useOnResize = (
id: string, id: string,
@ -11,9 +11,12 @@ export const useOnResize = (
useEffect(() => { useEffect(() => {
console.log("[useOnResize]", id); console.log("[useOnResize]", id);
const elem = isClient ? document.querySelector(`#${id}`) : null; const elem = isClient ? document.querySelector(`#${id}`) : null;
const ro = new ResizeObserver((resizeObserverEntry) => const ro = new ResizeObserver((resizeObserverEntry) => {
onResize(resizeObserverEntry[0].contentRect.width, resizeObserverEntry[0].contentRect.height) const entry = resizeObserverEntry[0];
); if (isDefined(entry)) {
onResize(entry.contentRect.width, entry.contentRect.height);
}
});
if (isDefined(elem)) { if (isDefined(elem)) {
ro.observe(elem); ro.observe(elem);
} }

View File

@ -1,7 +1,7 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useCounter } from "usehooks-ts"; import { useCounter } from "usehooks-ts";
import { isDefined } from "helpers/others"; import { isDefined } from "helpers/asserts";
const NUM_RETRIES = 10; const NUM_RETRIES = 10;

View File

@ -1,7 +1,7 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher"; import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher";
import { filterDefined, isDefined } from "helpers/others"; import { filterDefined, isDefined } from "helpers/asserts";
import { getPreferredLanguage } from "helpers/locales"; import { getPreferredLanguage } from "helpers/locales";
import { atoms } from "contexts/atoms"; import { atoms } from "contexts/atoms";
import { useAtomGetter } from "helpers/atoms"; import { useAtomGetter } from "helpers/atoms";

View File

@ -1,6 +1,7 @@
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { i18n } from "../../../next.config"; import { i18n } from "../../../next.config";
import { cartesianProduct, filterHasAttributes, isDefined } from "helpers/others"; import { cartesianProduct } from "helpers/others";
import { filterHasAttributes, isDefined } from "helpers/asserts";
import { fetchLocalData } from "graphql/fetchLocalData"; import { fetchLocalData } from "graphql/fetchLocalData";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
@ -183,7 +184,7 @@ const Revalidate = async (
language_code: "en", language_code: "en",
slug: body.entry.slug, slug: body.entry.slug,
}); });
filterHasAttributes(libraryItem.libraryItems?.data[0].attributes?.subitem_of?.data, [ filterHasAttributes(libraryItem.libraryItems?.data[0]?.attributes?.subitem_of?.data, [
"attributes.slug", "attributes.slug",
] as const).forEach((parentItem) => paths.push(`/library/${parentItem.attributes.slug}`)); ] as const).forEach((parentItem) => paths.push(`/library/${parentItem.attributes.slug}`));
} }
@ -202,12 +203,12 @@ const Revalidate = async (
slug: body.entry.slug, slug: body.entry.slug,
}); });
const folderSlug = content.contents?.data[0].attributes?.folder?.data?.attributes?.slug; const folderSlug = content.contents?.data[0]?.attributes?.folder?.data?.attributes?.slug;
if (folderSlug) { if (folderSlug) {
paths.push(`/contents/folder/${folderSlug}`); paths.push(`/contents/folder/${folderSlug}`);
} }
filterHasAttributes(content.contents?.data[0].attributes?.ranged_contents?.data, [ filterHasAttributes(content.contents?.data[0]?.attributes?.ranged_contents?.data, [
"attributes.library_item.data.attributes.slug", "attributes.library_item.data.attributes.slug",
] as const).forEach((ranged_content) => { ] as const).forEach((ranged_content) => {
const parentSlug = ranged_content.attributes.library_item.data.attributes.slug; const parentSlug = ranged_content.attributes.library_item.data.attributes.slug;
@ -260,16 +261,16 @@ const Revalidate = async (
slug: body.entry.slug, slug: body.entry.slug,
}); });
const parentSlug = const parentSlug =
folder.contentsFolders?.data[0].attributes?.parent_folder?.data?.attributes?.slug; folder.contentsFolders?.data[0]?.attributes?.parent_folder?.data?.attributes?.slug;
if (parentSlug) { if (parentSlug) {
paths.push(`/contents/folder/${parentSlug}`); paths.push(`/contents/folder/${parentSlug}`);
} }
filterHasAttributes(folder.contentsFolders?.data[0].attributes?.subfolders?.data, [ filterHasAttributes(folder.contentsFolders?.data[0]?.attributes?.subfolders?.data, [
"attributes.slug", "attributes.slug",
] as const).forEach((subfolder) => ] as const).forEach((subfolder) =>
paths.push(`/contents/folder/${subfolder.attributes.slug}`) paths.push(`/contents/folder/${subfolder.attributes.slug}`)
); );
filterHasAttributes(folder.contentsFolders?.data[0].attributes?.contents?.data, [ filterHasAttributes(folder.contentsFolders?.data[0]?.attributes?.contents?.data, [
"attributes.slug", "attributes.slug",
] as const).forEach((content) => paths.push(`/contents/${content.attributes.slug}`)); ] as const).forEach((content) => paths.push(`/contents/${content.attributes.slug}`));
} }
@ -308,7 +309,7 @@ const Revalidate = async (
paths.push(`/archives/videos`); paths.push(`/archives/videos`);
paths.push(`/archives/videos/v/${body.entry.uid}`); paths.push(`/archives/videos/v/${body.entry.uid}`);
const video = await sdk.getVideo({ uid: body.entry.uid }); const video = await sdk.getVideo({ uid: body.entry.uid });
const channelUid = video.videos?.data[0].attributes?.channel?.data?.attributes?.uid; const channelUid = video.videos?.data[0]?.attributes?.channel?.data?.attributes?.uid;
if (isDefined(channelUid)) { if (isDefined(channelUid)) {
paths.push(`/archives/videos/c/${channelUid}`); paths.push(`/archives/videos/c/${channelUid}`);
} }

View File

@ -14,7 +14,7 @@ import { getVideoThumbnailURL } from "helpers/videos";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { useDeviceSupportsHover } from "hooks/useMediaQuery";
import { WithLabel } from "components/Inputs/WithLabel"; import { WithLabel } from "components/Inputs/WithLabel";
import { filterHasAttributes, isDefined } from "helpers/others"; import { filterHasAttributes, isDefined } from "helpers/asserts";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { compareDate } from "helpers/date"; import { compareDate } from "helpers/date";
import { HorizontalLine } from "components/HorizontalLine"; import { HorizontalLine } from "components/HorizontalLine";
@ -136,7 +136,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
const channel = await sdk.getVideoChannel({ const channel = await sdk.getVideoChannel({
channel: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "", channel: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "",
}); });
if (!channel.videoChannels?.data[0].attributes) return { notFound: true }; if (!channel.videoChannels?.data[0]?.attributes) return { notFound: true };
channel.videoChannels.data[0].attributes.videos?.data channel.videoChannels.data[0].attributes.videos?.data
.sort((a, b) => compareDate(a.attributes?.published_date, b.attributes?.published_date)) .sort((a, b) => compareDate(a.attributes?.published_date, b.attributes?.published_date))

View File

@ -14,7 +14,7 @@ import { SubPanel } from "components/Containers/SubPanel";
import { PreviewCard } from "components/PreviewCard"; import { PreviewCard } from "components/PreviewCard";
import { GetVideosPreviewQuery } from "graphql/generated"; import { GetVideosPreviewQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { filterHasAttributes } from "helpers/others"; import { filterHasAttributes } from "helpers/asserts";
import { getVideoThumbnailURL } from "helpers/videos"; import { getVideoThumbnailURL } from "helpers/videos";
import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { useDeviceSupportsHover } from "hooks/useMediaQuery";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";

View File

@ -12,7 +12,7 @@ import { SubPanel } from "components/Containers/SubPanel";
import { GetVideoQuery } from "graphql/generated"; import { GetVideoQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { prettyDate, prettyShortenNumber } from "helpers/formatters"; import { prettyDate, prettyShortenNumber } from "helpers/formatters";
import { filterHasAttributes, isDefined } from "helpers/others"; import { filterHasAttributes, isDefined } from "helpers/asserts";
import { getVideoFile } from "helpers/videos"; import { getVideoFile } from "helpers/videos";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { getLangui } from "graphql/fetchLocalData"; import { getLangui } from "graphql/fetchLocalData";

View File

@ -1,7 +1,7 @@
import { GetStaticProps, GetStaticPaths, GetStaticPathsResult } from "next"; import { GetStaticProps, GetStaticPaths, GetStaticPathsResult } from "next";
import { useCallback, useMemo } from "react"; import { useCallback } from "react";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { isDefined, filterHasAttributes } from "helpers/others"; import { isDefined, filterHasAttributes } from "helpers/asserts";
import { ChronicleWithTranslations } from "types/types"; import { ChronicleWithTranslations } from "types/types";
import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";

View File

@ -5,7 +5,7 @@ import { SubPanel } from "components/Containers/SubPanel";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { GetChroniclesChaptersQuery } from "graphql/generated"; import { GetChroniclesChaptersQuery } from "graphql/generated";
import { filterHasAttributes } from "helpers/others"; import { filterHasAttributes } from "helpers/asserts";
import { prettySlug } from "helpers/formatters"; import { prettySlug } from "helpers/formatters";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList"; import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList";

View File

@ -21,7 +21,8 @@ import {
prettySlug, prettySlug,
} from "helpers/formatters"; } from "helpers/formatters";
import { isUntangibleGroupItem } from "helpers/libraryItem"; import { isUntangibleGroupItem } from "helpers/libraryItem";
import { filterHasAttributes, getStatusDescription, isDefinedAndNotEmpty } from "helpers/others"; import { getStatusDescription } from "helpers/others";
import { filterHasAttributes, isDefinedAndNotEmpty } from "helpers/asserts";
import { ContentWithTranslations } from "types/types"; import { ContentWithTranslations } from "types/types";
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange"; import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
@ -466,7 +467,7 @@ type FolderContents = NonNullable<
const getPreviousContent = (contents: FolderContents, currentSlug: string) => { const getPreviousContent = (contents: FolderContents, currentSlug: string) => {
for (let index = 0; index < contents.length; index++) { for (let index = 0; index < contents.length; index++) {
const content = contents[index]; const content = contents[index];
if (content.attributes?.slug === currentSlug && index > 0) { if (content?.attributes?.slug === currentSlug && index > 0) {
return contents[index - 1]; return contents[index - 1];
} }
} }
@ -478,7 +479,7 @@ const getPreviousContent = (contents: FolderContents, currentSlug: string) => {
const getNextContent = (contents: FolderContents, currentSlug: string) => { const getNextContent = (contents: FolderContents, currentSlug: string) => {
for (let index = 0; index < contents.length; index++) { for (let index = 0; index < contents.length; index++) {
const content = contents[index]; const content = contents[index];
if (content.attributes?.slug === currentSlug && index < contents.length - 1) { if (content?.attributes?.slug === currentSlug && index < contents.length - 1) {
return contents[index + 1]; return contents[index + 1];
} }
} }

View File

@ -15,10 +15,14 @@ import { WithLabel } from "components/Inputs/WithLabel";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { useDeviceSupportsHover } from "hooks/useMediaQuery";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { filterDefined, filterHasAttributes, isDefinedAndNotEmpty } from "helpers/others"; import {
filterDefined,
filterHasAttributes,
isDefinedAndNotEmpty,
SelectiveNonNullable,
} from "helpers/asserts";
import { GetContentsQuery } from "graphql/generated"; import { GetContentsQuery } from "graphql/generated";
import { SmartList } from "components/SmartList"; import { SmartList } from "components/SmartList";
import { SelectiveNonNullable } from "types/SelectiveNonNullable";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { HorizontalLine } from "components/HorizontalLine"; import { HorizontalLine } from "components/HorizontalLine";
import { TranslatedPreviewCard } from "components/PreviewCard"; import { TranslatedPreviewCard } from "components/PreviewCard";

View File

@ -4,7 +4,7 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel"; import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { filterHasAttributes } from "helpers/others"; import { filterHasAttributes } from "helpers/asserts";
import { GetContentsFolderQuery } from "graphql/generated"; import { GetContentsFolderQuery } from "graphql/generated";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales"; import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
import { prettySlug } from "helpers/formatters"; import { prettySlug } from "helpers/formatters";

View File

@ -6,7 +6,7 @@ import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/Cont
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { DevGetContentsQuery } from "graphql/generated"; import { DevGetContentsQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { filterDefined, filterHasAttributes } from "helpers/others"; import { filterDefined, filterHasAttributes } from "helpers/asserts";
import { Report, Severity } from "types/Report"; import { Report, Severity } from "types/Report";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { getLangui } from "graphql/fetchLocalData"; import { getLangui } from "graphql/fetchLocalData";
@ -61,7 +61,8 @@ const CheckupContents = ({ contents, ...otherProps }: Props): JSX.Element => {
? "bg-[#fff344] !opacity-100" ? "bg-[#fff344] !opacity-100"
: "" : ""
} }
text={Severity[line.severity]} // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
text={Severity[line.severity] ?? "Unknown"}
/> />
<ToolTip content={line.recommandation} placement="left"> <ToolTip content={line.recommandation} placement="left">
<p>{line.description}</p> <p>{line.description}</p>

View File

@ -47,7 +47,7 @@ const CheckupLibraryItems = ({ libraryItems, ...otherProps }: Props): JSX.Elemen
<div <div
key={index} key={index}
className="mb-2 grid className="mb-2 grid
grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center justify-items-start gap-2"> grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center justify-items-start gap-2">
<Button href={line.frontendUrl} className="w-4 text-xs" text="F" alwaysNewTab /> <Button href={line.frontendUrl} className="w-4 text-xs" text="F" alwaysNewTab />
<Button href={line.backendUrl} className="w-4 text-xs" text="B" alwaysNewTab /> <Button href={line.backendUrl} className="w-4 text-xs" text="B" alwaysNewTab />
<p>{line.subitems.join(" -> ")}</p> <p>{line.subitems.join(" -> ")}</p>
@ -63,7 +63,8 @@ const CheckupLibraryItems = ({ libraryItems, ...otherProps }: Props): JSX.Elemen
? "bg-[#fff344] !opacity-100" ? "bg-[#fff344] !opacity-100"
: "" : ""
} }
text={Severity[line.severity]} // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
text={Severity[line.severity] ?? "Unknown"}
/> />
<ToolTip content={line.recommandation} placement="left"> <ToolTip content={line.recommandation} placement="left">
<p>{line.description}</p> <p>{line.description}</p>

View File

@ -431,12 +431,7 @@ const DesignSystem = (props: Props): JSX.Element => {
value={sliderState} value={sliderState}
max={100} max={100}
onChange={(event) => { onChange={(event) => {
let value = 0; const value = (Array.isArray(event) ? event[0] : event) ?? 0;
if (Array.isArray(event)) {
value = event[0];
} else {
value = event;
}
setSliderState(() => value); setSliderState(() => value);
}} }}
/> />

View File

@ -32,7 +32,7 @@ const replaceSelection = (
const swapChar = (char: string, swaps: string[]): string => { const swapChar = (char: string, swaps: string[]): string => {
for (let index = 0; index < swaps.length; index++) { for (let index = 0; index < swaps.length; index++) {
if (char === swaps[index]) { if (char === swaps[index]) {
return swaps[(index + 1) % swaps.length]; return swaps[(index + 1) % swaps.length] ?? char;
} }
} }
return char; return char;

View File

@ -31,13 +31,13 @@ import {
} from "helpers/formatters"; } from "helpers/formatters";
import { ImageQuality } from "helpers/img"; import { ImageQuality } from "helpers/img";
import { convertMmToInch } from "helpers/numbers"; import { convertMmToInch } from "helpers/numbers";
import { sortRangedContent } from "helpers/others";
import { import {
filterDefined, filterDefined,
filterHasAttributes, filterHasAttributes,
isDefined, isDefined,
isDefinedAndNotEmpty, isDefinedAndNotEmpty,
sortRangedContent, } from "helpers/asserts";
} from "helpers/others";
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange"; import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { isUntangibleGroupItem } from "helpers/libraryItem"; import { isUntangibleGroupItem } from "helpers/libraryItem";
import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { useDeviceSupportsHover } from "hooks/useMediaQuery";

View File

@ -11,13 +11,8 @@ import {
UploadImageFragment, UploadImageFragment,
} from "graphql/generated"; } from "graphql/generated";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { import { getStatusDescription, sortRangedContent } from "helpers/others";
filterHasAttributes, import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
getStatusDescription,
isDefined,
isDefinedAndNotEmpty,
sortRangedContent,
} from "helpers/others";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { getLangui } from "graphql/fetchLocalData"; import { getLangui } from "graphql/fetchLocalData";
import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel"; import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel";
@ -305,12 +300,7 @@ const LibrarySlug = ({
max={10} max={10}
value={filterSettings.teint * 10} value={filterSettings.teint * 10}
onChange={(event) => { onChange={(event) => {
let value = 0; const value = (Array.isArray(event) ? event[0] : event) ?? 0;
if (Array.isArray(event)) {
value = event[0];
} else {
value = event;
}
setTeint(value / 10); setTeint(value / 10);
}} }}
/> />
@ -390,7 +380,7 @@ const LibrarySlug = ({
? CUSTOM_DARK_DROPSHADOW ? CUSTOM_DARK_DROPSHADOW
: CUSTOM_LIGHT_DROPSHADOW, : CUSTOM_LIGHT_DROPSHADOW,
}}> }}>
{effectiveDisplayMode === "single" ? ( {effectiveDisplayMode === "single" && isDefined(firstPage) ? (
<div <div
className={cJoin( className={cJoin(
"relative grid grid-flow-col", "relative grid grid-flow-col",
@ -412,70 +402,73 @@ const LibrarySlug = ({
/> />
</div> </div>
) : ( ) : (
<> isDefined(firstPage) &&
<div isDefined(secondPage) && (
className={cJoin( <>
"relative grid grid-flow-col", <div
cIf(currentZoom <= 1, "cursor-pointer", "cursor-move") className={cJoin(
)} "relative grid grid-flow-col",
onClick={() => currentZoom <= 1 && handlePageNavigation("left")} cIf(currentZoom <= 1, "cursor-pointer", "cursor-move")
style={{ )}
clipPath: leftSideClipPath, onClick={() => currentZoom <= 1 && handlePageNavigation("left")}
}}> style={{
{isSidePagesEnabled && ( clipPath: leftSideClipPath,
<div }}>
style={{ {isSidePagesEnabled && (
width: leftSidePagesWidth, <div
backgroundImage: `url(/reader/sidepages-${bookType}.webp)`, style={{
backgroundSize: `${ width: leftSidePagesWidth,
(SIDEPAGES_PAGE_COUNT_ON_TEXTURE / leftSidePagesCount) * 100 backgroundImage: `url(/reader/sidepages-${bookType}.webp)`,
}% 100%`, backgroundSize: `${
}} (SIDEPAGES_PAGE_COUNT_ON_TEXTURE / leftSidePagesCount) * 100
/> }% 100%`,
)} }}
/>
<Img
style={{ maxHeight: pageHeight, width: "auto" }}
src={pageOrder === PageOrder.LeftToRight ? firstPage : secondPage}
quality={pageQuality}
/>
<PageFilters page="left" bookType={bookType} options={filterSettings} />
</div>
<div
className={cJoin(
"relative grid grid-flow-col",
cIf(currentZoom <= 1, "cursor-pointer", "cursor-move")
)}
onClick={() => currentZoom <= 1 && handlePageNavigation("right")}
style={{
clipPath: rightSideClipPath,
}}>
<Img
style={{ maxHeight: pageHeight, width: "auto" }}
className={cIf(
is1ColumnLayout,
`max-h-[calc(100vh-5rem)]`,
"max-h-[calc(100vh-4rem)]"
)} )}
src={pageOrder === PageOrder.LeftToRight ? secondPage : firstPage}
quality={pageQuality}
/>
{isSidePagesEnabled && (
<div
style={{
width: rightSidePagesWidth,
backgroundImage: `url(/reader/sidepages-${bookType}.webp)`,
backgroundPositionX: "right",
backgroundSize: `${
(SIDEPAGES_PAGE_COUNT_ON_TEXTURE / rightSidePagesCount) * 100
}% 100%`,
}}
/>
)}
<PageFilters page="right" bookType={bookType} options={filterSettings} /> <Img
</div> style={{ maxHeight: pageHeight, width: "auto" }}
</> src={pageOrder === PageOrder.LeftToRight ? firstPage : secondPage}
quality={pageQuality}
/>
<PageFilters page="left" bookType={bookType} options={filterSettings} />
</div>
<div
className={cJoin(
"relative grid grid-flow-col",
cIf(currentZoom <= 1, "cursor-pointer", "cursor-move")
)}
onClick={() => currentZoom <= 1 && handlePageNavigation("right")}
style={{
clipPath: rightSideClipPath,
}}>
<Img
style={{ maxHeight: pageHeight, width: "auto" }}
className={cIf(
is1ColumnLayout,
`max-h-[calc(100vh-5rem)]`,
"max-h-[calc(100vh-4rem)]"
)}
src={pageOrder === PageOrder.LeftToRight ? secondPage : firstPage}
quality={pageQuality}
/>
{isSidePagesEnabled && (
<div
style={{
width: rightSidePagesWidth,
backgroundImage: `url(/reader/sidepages-${bookType}.webp)`,
backgroundPositionX: "right",
backgroundSize: `${
(SIDEPAGES_PAGE_COUNT_ON_TEXTURE / rightSidePagesCount) * 100
}% 100%`,
}}
/>
)}
<PageFilters page="right" bookType={bookType} options={filterSettings} />
</div>
</>
)
)} )}
</TransformComponent> </TransformComponent>
</TransformWrapper> </TransformWrapper>
@ -498,12 +491,7 @@ const LibrarySlug = ({
max={pages.length - 1} max={pages.length - 1}
value={currentPageIndex - 1} value={currentPageIndex - 1}
onChange={(event) => { onChange={(event) => {
let value = 0; const value = (Array.isArray(event) ? event[0] : event) ?? 0;
if (Array.isArray(event)) {
value = event[0];
} else {
value = event;
}
changeCurrentPageIndex(() => value); changeCurrentPageIndex(() => value);
}} }}
/> />
@ -657,7 +645,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
bookType, bookType,
pageWidth, pageWidth,
itemSlug: item.libraryItems.data[0].attributes.slug, itemSlug: item.libraryItems.data[0].attributes.slug,
pageRatio: `${pages[0].width ?? 21} / ${pages[0].height ?? 29.7}`, pageRatio: `${pages[0]?.width ?? 21} / ${pages[0]?.height ?? 29.7}`,
openGraph: getOpenGraph( openGraph: getOpenGraph(
langui, langui,
item.libraryItems.data[0].attributes.title, item.libraryItems.data[0].attributes.title,

View File

@ -21,10 +21,15 @@ import { isUntangibleGroupItem } from "helpers/libraryItem";
import { PreviewCard } from "components/PreviewCard"; import { PreviewCard } from "components/PreviewCard";
import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { useDeviceSupportsHover } from "hooks/useMediaQuery";
import { ButtonGroup } from "components/Inputs/ButtonGroup"; import { ButtonGroup } from "components/Inputs/ButtonGroup";
import { filterHasAttributes, isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/others"; import {
filterHasAttributes,
isDefined,
isDefinedAndNotEmpty,
isUndefined,
SelectiveNonNullable,
} from "helpers/asserts";
import { convertPrice } from "helpers/numbers"; import { convertPrice } from "helpers/numbers";
import { SmartList } from "components/SmartList"; import { SmartList } from "components/SmartList";
import { SelectiveNonNullable } from "types/SelectiveNonNullable";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { compareDate } from "helpers/date"; import { compareDate } from "helpers/date";
import { HorizontalLine } from "components/HorizontalLine"; import { HorizontalLine } from "components/HorizontalLine";
@ -142,11 +147,14 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
return naturalCompare(titleA, titleB); return naturalCompare(titleA, titleB);
} }
case 1: { case 1: {
const commonCurrency = currencies[0];
if (isUndefined(commonCurrency)) return 0;
const priceA = a.attributes.price const priceA = a.attributes.price
? convertPrice(a.attributes.price, currencies[0]) ? convertPrice(a.attributes.price, commonCurrency)
: Infinity; : Infinity;
const priceB = b.attributes.price const priceB = b.attributes.price
? convertPrice(b.attributes.price, currencies[0]) ? convertPrice(b.attributes.price, commonCurrency)
: Infinity; : Infinity;
return priceA - priceB; return priceA - priceB;
} }

View File

@ -3,7 +3,7 @@ import { NextRouter, useRouter } from "next/router";
import { PostPage } from "components/PostPage"; import { PostPage } from "components/PostPage";
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps"; import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/others"; import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
import { Terminal } from "components/Cli/Terminal"; import { Terminal } from "components/Cli/Terminal";
import { PostWithTranslations } from "types/types"; import { PostWithTranslations } from "types/types";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales"; import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";

View File

@ -14,7 +14,7 @@ import { WithLabel } from "components/Inputs/WithLabel";
import { TextInput } from "components/Inputs/TextInput"; import { TextInput } from "components/Inputs/TextInput";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { useDeviceSupportsHover } from "hooks/useMediaQuery";
import { filterHasAttributes, isDefinedAndNotEmpty } from "helpers/others"; import { filterHasAttributes, isDefinedAndNotEmpty } from "helpers/asserts";
import { SmartList } from "components/SmartList"; import { SmartList } from "components/SmartList";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { compareDate } from "helpers/date"; import { compareDate } from "helpers/date";

View File

@ -10,7 +10,7 @@ import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/Cont
import { SubPanel } from "components/Containers/SubPanel"; import { SubPanel } from "components/Containers/SubPanel";
import DefinitionCard from "components/Wiki/DefinitionCard"; import DefinitionCard from "components/Wiki/DefinitionCard";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/others"; import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
import { WikiPageWithTranslations } from "types/types"; import { WikiPageWithTranslations } from "types/types";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { prettySlug, sJoin } from "helpers/formatters"; import { prettySlug, sJoin } from "helpers/formatters";

View File

@ -13,12 +13,8 @@ import {
} from "graphql/generated"; } from "graphql/generated";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { prettySlug } from "helpers/formatters"; import { prettySlug } from "helpers/formatters";
import { import { getStatusDescription } from "helpers/others";
filterHasAttributes, import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
getStatusDescription,
isDefined,
isDefinedAndNotEmpty,
} from "helpers/others";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
@ -156,7 +152,7 @@ const ChronologyEra = ({ id, title, description, chronologyItems }: ChronologyEr
let currentYear = -Infinity; let currentYear = -Infinity;
filterHasAttributes(chronologyItems, ["attributes"] as const).forEach((item) => { filterHasAttributes(chronologyItems, ["attributes"] as const).forEach((item) => {
if (currentYear === item.attributes.year) { if (currentYear === item.attributes.year) {
memo[memo.length - 1].push(item); memo[memo.length - 1]?.push(item);
} else { } else {
currentYear = item.attributes.year; currentYear = item.attributes.year;
memo.push([item]); memo.push([item]);
@ -214,7 +210,7 @@ interface ChronologyYearProps {
const ChronologyYear = ({ items }: ChronologyYearProps) => ( const ChronologyYear = ({ items }: ChronologyYearProps) => (
<div <div
className="rounded-2xl target:my-4 target:bg-mid target:py-4" className="rounded-2xl target:my-4 target:bg-mid target:py-4"
id={generateAnchor(items[0].attributes?.year)}> id={generateAnchor(items[0]?.attributes?.year)}>
{filterHasAttributes(items, ["attributes.events"] as const).map((item, index) => ( {filterHasAttributes(items, ["attributes.events"] as const).map((item, index) => (
<ChronologyDate <ChronologyDate
key={index} key={index}

View File

@ -15,10 +15,14 @@ import { Switch } from "components/Inputs/Switch";
import { TextInput } from "components/Inputs/TextInput"; import { TextInput } from "components/Inputs/TextInput";
import { WithLabel } from "components/Inputs/WithLabel"; import { WithLabel } from "components/Inputs/WithLabel";
import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { useDeviceSupportsHover } from "hooks/useMediaQuery";
import { filterDefined, filterHasAttributes, isDefinedAndNotEmpty } from "helpers/others"; import {
filterDefined,
filterHasAttributes,
isDefinedAndNotEmpty,
SelectiveNonNullable,
} from "helpers/asserts";
import { SmartList } from "components/SmartList"; import { SmartList } from "components/SmartList";
import { Select } from "components/Inputs/Select"; import { Select } from "components/Inputs/Select";
import { SelectiveNonNullable } from "types/SelectiveNonNullable";
import { prettySlug } from "helpers/formatters"; import { prettySlug } from "helpers/formatters";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { TranslatedPreviewCard } from "components/PreviewCard"; import { TranslatedPreviewCard } from "components/PreviewCard";

View File

@ -1,39 +0,0 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
type JoinDot<K extends string, P extends string> = `${K}${"" extends K ? "" : "."}${P}`;
export type PathDot<T, Acc extends string = ""> = T extends object
? {
[K in keyof T]: K extends string ? JoinDot<Acc, K> | PathDot<T[K], JoinDot<Acc, K>> : never;
}[keyof T]
: Acc;
type PathHead<T extends unknown[]> = T extends [infer head]
? head
: T extends [infer head, ...infer rest]
? head
: "";
type PathRest<T extends unknown[]> = T extends [infer head, ...infer rest]
? rest extends []
? never
: rest
: never;
type PathLast<T extends unknown[]> = T["length"] extends 1 ? true : false;
type Recursive<T, Path extends unknown[]> = PathHead<Path> extends keyof T
? Omit<T, PathHead<Path>> & {
[P in PathHead<Path>]-?: PathLast<Path> extends true
? NonNullable<T[P]>
: Recursive<NonNullable<T[P]>, PathRest<Path>>;
}
: T;
type Split<Str, Acc extends string[] = []> = Str extends `${infer Head}.${infer Rest}`
? Split<Rest, [...Acc, Head]>
: Str extends `${infer Last}`
? [...Acc, Last]
: never;
export type SelectiveNonNullable<T, P extends PathDot<T>> = Recursive<NonNullable<T>, Split<P>>;

View File

@ -1,11 +1,15 @@
{ {
"compilerOptions": { "compilerOptions": {
// Type Checking
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"strict": true,
"target": "ES6", "target": "ES6",
"lib": ["dom", "dom.iterable", "esnext"], "lib": ["dom", "dom.iterable", "esnext"],
"importHelpers": true, "importHelpers": true,
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noEmit": true, "noEmit": true,
"esModuleInterop": true, "esModuleInterop": true,
@ -16,7 +20,6 @@
"jsx": "preserve", "jsx": "preserve",
"incremental": true, "incremental": true,
"baseUrl": "src" "baseUrl": "src"
// "noUncheckedIndexedAccess": true
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"] "exclude": ["node_modules"]