Improved perf on all browser
This commit is contained in:
parent
06d82e1133
commit
663bf4f08d
|
@ -187,7 +187,9 @@
|
|||
"story_x": "Story {x}",
|
||||
"player_name_tooltip": "Certain in-game texts use the player's name as part of the dialogue/narration. If you want to see your name in the transcript found on this website, feel free to enter your player's name here. If left empty, '(player)' will be used instead.",
|
||||
"download_scans": "Download scans",
|
||||
"search_placeholder": "Search..."
|
||||
"search_placeholder": "Search...",
|
||||
"performance_mode": "Performance mode",
|
||||
"performance_mode_tooltip": "This option improves performances by reducing certain visual effects (blurry backgrounds, shadows...). This mode is enabled by default on Linux, iOS, and Safari. This mode cannot be disabled on iOS or Safari as the experience would be too poor."
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -376,7 +378,9 @@
|
|||
"story_x": "Histoire {x}",
|
||||
"player_name_tooltip": "Certains textes dans les jeux utilisent le nom du joueur dans les dialogue/la narration. Si vous voulez voir votre nom dans les transcriptions se trouvant sur ce site web, n'hésitez pas à entrer votre nom de joueur ici. S'il n'est pas renseigné, '(player)' sera utilisé à la place.",
|
||||
"download_scans": "Télécharger les scans",
|
||||
"search_placeholder": "Rechercher ..."
|
||||
"search_placeholder": "Rechercher ...",
|
||||
"performance_mode": "Mode performance",
|
||||
"performance_mode_tooltip": "Cette option permet d'améliorer les performances en réduisant certains effets visuels (flous, ombres...). Ce mode est activé par défaut sur Linux, iOS et Safari. Ce mode ne peut pas être désactivé sur iOS ou Safari car l'expérience serait trop mauvaise."
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -565,7 +569,9 @@
|
|||
"story_x": null,
|
||||
"player_name_tooltip": null,
|
||||
"download_scans": null,
|
||||
"search_placeholder": null
|
||||
"search_placeholder": null,
|
||||
"performance_mode": null,
|
||||
"performance_mode_tooltip": null
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -754,7 +760,9 @@
|
|||
"story_x": null,
|
||||
"player_name_tooltip": null,
|
||||
"download_scans": null,
|
||||
"search_placeholder": null
|
||||
"search_placeholder": null,
|
||||
"performance_mode": null,
|
||||
"performance_mode_tooltip": null
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -943,7 +951,9 @@
|
|||
"story_x": null,
|
||||
"player_name_tooltip": null,
|
||||
"download_scans": null,
|
||||
"search_placeholder": null
|
||||
"search_placeholder": null,
|
||||
"performance_mode": null,
|
||||
"performance_mode_tooltip": null
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Head from "next/head";
|
||||
import { useSwipeable } from "react-swipeable";
|
||||
import { MaterialSymbol } from "material-symbols";
|
||||
import { atom } from "jotai";
|
||||
import { layout } from "../../design.config";
|
||||
import { Ico } from "./Ico";
|
||||
import { MainPanel } from "./Panels/MainPanel";
|
||||
|
@ -18,6 +19,7 @@ import { useFormat } from "hooks/useFormat";
|
|||
*/
|
||||
|
||||
const SENSIBILITY_SWIPE = 1.1;
|
||||
const isIOSAtom = atom((get) => get(atoms.userAgent.os) === "iOS");
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -48,11 +50,12 @@ export const AppLayout = ({
|
|||
const [isSubPanelOpened, setSubPanelOpened] = useAtomPair(atoms.layout.subPanelOpened);
|
||||
const [isMainPanelOpened, setMainPanelOpened] = useAtomPair(atoms.layout.mainPanelOpened);
|
||||
const isMenuGesturesEnabled = useAtomGetter(atoms.layout.menuGesturesEnabled);
|
||||
|
||||
const { format } = useFormat();
|
||||
|
||||
const isPerfModeEnabled = useAtomGetter(atoms.settings.isPerfModeEnabled);
|
||||
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
|
||||
const isScreenAtLeastXs = useAtomGetter(atoms.containerQueries.isScreenAtLeastXs);
|
||||
const isIOS = useAtomGetter(isIOSAtom);
|
||||
|
||||
const { format } = useFormat();
|
||||
|
||||
const handlers = useSwipeable({
|
||||
onSwipedLeft: (SwipeEventData) => {
|
||||
|
@ -121,7 +124,8 @@ export const AppLayout = ({
|
|||
<div
|
||||
id={Ids.ContentPanel}
|
||||
className={cJoin(
|
||||
"bg-light texture-paper-dots [grid-area:content]",
|
||||
"bg-light [grid-area:content]",
|
||||
cIf(!isIOS, "texture-paper-dots"),
|
||||
cIf(contentPanelScroolbar, "overflow-y-scroll")
|
||||
)}>
|
||||
{isDefined(contentPanel) ? (
|
||||
|
@ -140,7 +144,7 @@ export const AppLayout = ({
|
|||
[grid-area:content]`,
|
||||
cIf(
|
||||
(isMainPanelOpened || isSubPanelOpened) && is1ColumnLayout,
|
||||
"backdrop-blur",
|
||||
cIf(!isPerfModeEnabled, "backdrop-blur"),
|
||||
"pointer-events-none touch-none"
|
||||
)
|
||||
)}>
|
||||
|
@ -164,7 +168,8 @@ export const AppLayout = ({
|
|||
<div
|
||||
className={cJoin(
|
||||
`z-40 grid grid-cols-[5rem_1fr_5rem] place-items-center border-t
|
||||
border-dotted border-black bg-light texture-paper-dots [grid-area:navbar]`,
|
||||
border-dotted border-black bg-light [grid-area:navbar]`,
|
||||
cIf(!isIOS, "texture-paper-dots"),
|
||||
cIf(!is1ColumnLayout, "hidden")
|
||||
)}>
|
||||
<Ico
|
||||
|
@ -202,7 +207,8 @@ export const AppLayout = ({
|
|||
id={Ids.SubPanel}
|
||||
className={cJoin(
|
||||
`z-40 overflow-y-scroll border-r border-dark/50 bg-light
|
||||
transition-transform duration-300 scrollbar-none texture-paper-dots`,
|
||||
transition-transform duration-300 scrollbar-none`,
|
||||
cIf(!isIOS, "texture-paper-dots"),
|
||||
cIf(
|
||||
is1ColumnLayout,
|
||||
"justify-self-end border-r-0 [grid-area:content]",
|
||||
|
@ -219,7 +225,8 @@ export const AppLayout = ({
|
|||
<div
|
||||
className={cJoin(
|
||||
`z-40 overflow-y-scroll border-r border-dark/50 bg-light
|
||||
transition-transform duration-300 scrollbar-none texture-paper-dots`,
|
||||
transition-transform duration-300 scrollbar-none`,
|
||||
cIf(!isIOS, "texture-paper-dots"),
|
||||
cIf(is1ColumnLayout, "justify-self-start [grid-area:content]", "[grid-area:main]"),
|
||||
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"),
|
||||
cIf(!isMainPanelOpened && is1ColumnLayout, "-translate-x-full")
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
|
|||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomSetter } from "helpers/atoms";
|
||||
import { useAtomGetter, useAtomSetter } from "helpers/atoms";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
|
||||
/*
|
||||
|
@ -16,7 +16,6 @@ interface Props {
|
|||
isVisible: boolean;
|
||||
children: React.ReactNode;
|
||||
fillViewport?: boolean;
|
||||
hideBackground?: boolean;
|
||||
padding?: boolean;
|
||||
withCloseButton?: boolean;
|
||||
}
|
||||
|
@ -29,13 +28,13 @@ export const Popup = ({
|
|||
isVisible,
|
||||
children,
|
||||
fillViewport,
|
||||
hideBackground = false,
|
||||
padding = true,
|
||||
withCloseButton = true,
|
||||
}: Props): JSX.Element => {
|
||||
const setMenuGesturesEnabled = useAtomSetter(atoms.layout.menuGesturesEnabled);
|
||||
const [isHidden, setHidden] = useState(!isVisible);
|
||||
const [isActuallyVisible, setActuallyVisible] = useState(isVisible && !isHidden);
|
||||
const isPerfModeEnabled = useAtomGetter(atoms.settings.isPerfModeEnabled);
|
||||
|
||||
useHotkeys("escape", () => onCloseRequest?.(), { enabled: isVisible }, [onCloseRequest]);
|
||||
|
||||
|
@ -68,7 +67,8 @@ export const Popup = ({
|
|||
<div
|
||||
className={cJoin(
|
||||
"fixed inset-0 z-50 grid place-content-center transition-filter duration-500",
|
||||
cIf(isActuallyVisible, "backdrop-blur", "pointer-events-none touch-none")
|
||||
cIf(!isActuallyVisible, "pointer-events-none touch-none"),
|
||||
cIf(isActuallyVisible && !isPerfModeEnabled, "backdrop-blur")
|
||||
)}>
|
||||
<div
|
||||
className={cJoin(
|
||||
|
@ -80,15 +80,15 @@ export const Popup = ({
|
|||
|
||||
<div
|
||||
className={cJoin(
|
||||
"grid place-items-center gap-4 transition-transform",
|
||||
`grid place-items-center gap-4 rounded-lg bg-light shadow-2xl transition-transform
|
||||
shadow-shade`,
|
||||
cIf(padding, "p-10"),
|
||||
cIf(isActuallyVisible, "scale-100", "scale-0"),
|
||||
cIf(
|
||||
fillViewport,
|
||||
"absolute inset-10 content-start overflow-scroll",
|
||||
"relative max-h-[80vh] overflow-y-auto"
|
||||
),
|
||||
cIf(!hideBackground, "rounded-lg bg-light shadow-2xl shadow-shade")
|
||||
)
|
||||
)}>
|
||||
{withCloseButton && (
|
||||
<div className="absolute right-6 top-6">
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { MouseEventHandler, useState } from "react";
|
||||
import { Link } from "components/Inputs/Link";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { atoms } from "contexts/atoms";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -27,20 +29,24 @@ export const UpPressable = ({
|
|||
onClick,
|
||||
}: Props): JSX.Element => {
|
||||
const [isFocused, setFocused] = useState(false);
|
||||
const isPerfModeEnabled = useAtomGetter(atoms.settings.isPerfModeEnabled);
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
onFocusChanged={setFocused}
|
||||
onClick={onClick}
|
||||
className={cJoin(
|
||||
`drop-shadow-lg transition-all duration-300 shadow-shade`,
|
||||
"transition-all duration-300 !shadow-shade",
|
||||
cIf(isPerfModeEnabled, "shadow-lg", "drop-shadow-lg"),
|
||||
cIf(!noBackground, "overflow-hidden rounded-md bg-highlight"),
|
||||
cIf(
|
||||
disabled,
|
||||
"cursor-not-allowed opacity-50 grayscale",
|
||||
cJoin(
|
||||
"cursor-pointer hover:scale-102 hover:drop-shadow-xl",
|
||||
cIf(isFocused, "hover:scale-105 hover:drop-shadow-2xl hover:duration-100")
|
||||
"cursor-pointer hover:scale-102",
|
||||
cIf(isPerfModeEnabled, "hover:shadow-xl", "hover:drop-shadow-xl"),
|
||||
cIf(isFocused, "hover:scale-105 hover:duration-100")
|
||||
)
|
||||
),
|
||||
className
|
||||
|
|
|
@ -20,12 +20,13 @@ interface Props {
|
|||
icon?: MaterialSymbol;
|
||||
text?: string | null | undefined;
|
||||
alwaysNewTab?: boolean;
|
||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||
onMouseUp?: MouseEventHandler<HTMLDivElement>;
|
||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||
onMouseUp?: MouseEventHandler<HTMLButtonElement>;
|
||||
draggable?: boolean;
|
||||
badgeNumber?: number;
|
||||
disabled?: boolean;
|
||||
size?: "normal" | "small";
|
||||
type?: "button" | "reset" | "submit";
|
||||
}
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
@ -43,31 +44,31 @@ export const Button = ({
|
|||
alwaysNewTab = false,
|
||||
badgeNumber,
|
||||
disabled,
|
||||
type,
|
||||
size = "normal",
|
||||
}: Props): JSX.Element => (
|
||||
<Link href={href} alwaysNewTab={alwaysNewTab} disabled={disabled}>
|
||||
<div className="relative">
|
||||
<div
|
||||
<button
|
||||
type={type}
|
||||
draggable={draggable}
|
||||
id={id}
|
||||
onClick={(event) => !disabled && onClick?.(event)}
|
||||
disabled={disabled}
|
||||
onClick={(event) => onClick?.(event)}
|
||||
onMouseUp={onMouseUp}
|
||||
onFocus={(event) => event.target.blur()}
|
||||
className={cJoin(
|
||||
`group grid cursor-pointer select-none grid-flow-col place-content-center
|
||||
`group grid w-full grid-flow-col place-content-center
|
||||
place-items-center gap-2 rounded-full border border-dark
|
||||
leading-none text-dark transition-all`,
|
||||
leading-none text-dark transition-all disabled:cursor-not-allowed
|
||||
disabled:opacity-50 disabled:grayscale`,
|
||||
cIf(size === "small", "px-3 py-1 text-xs", "px-4 py-3"),
|
||||
cIf(active, "!border-black bg-black !text-light drop-shadow-lg shadow-black"),
|
||||
cIf(active, "!border-black bg-black !text-light shadow-lg shadow-black"),
|
||||
cIf(
|
||||
disabled,
|
||||
"cursor-not-allowed opacity-50 grayscale",
|
||||
cIf(
|
||||
!active,
|
||||
`shadow-shade hover:bg-dark hover:text-light hover:drop-shadow-lg
|
||||
active:hover:!border-black active:hover:bg-black active:hover:!text-light
|
||||
active:hover:drop-shadow-lg active:hover:shadow-black`
|
||||
)
|
||||
!disabled && !active,
|
||||
`shadow-shade hover:bg-dark hover:text-light hover:shadow-lg hover:shadow-shade
|
||||
active:hover:!border-black active:hover:bg-black active:hover:!text-light
|
||||
active:hover:shadow-lg active:hover:shadow-black`
|
||||
),
|
||||
className
|
||||
)}>
|
||||
|
@ -91,7 +92,7 @@ export const Button = ({
|
|||
/>
|
||||
)}
|
||||
{isDefinedAndNotEmpty(text) && <p className="-translate-y-[0.05em] text-center">{text}</p>}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
|
|
|
@ -27,6 +27,7 @@ export const TextInput = forwardRef<HTMLInputElement, Props>(
|
|||
className="w-full"
|
||||
type="text"
|
||||
name={name}
|
||||
autoCapitalize="off"
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
placeholder={placeholder ?? undefined}
|
||||
|
|
|
@ -39,6 +39,7 @@ export const LightBox = ({
|
|||
onPressNext,
|
||||
}: Props): JSX.Element => {
|
||||
const [currentZoom, setCurrentZoom] = useState(1);
|
||||
const isPerfModeEnabled = useAtomGetter(atoms.settings.isPerfModeEnabled);
|
||||
const { isFullscreen, toggleFullscreen, exitFullscreen, requestFullscreen } = useFullscreen(
|
||||
Ids.LightBox
|
||||
);
|
||||
|
@ -62,7 +63,7 @@ export const LightBox = ({
|
|||
id={Ids.LightBox}
|
||||
className={cJoin(
|
||||
"fixed inset-0 z-50 grid place-content-center transition-filter duration-500",
|
||||
cIf(isVisible, "backdrop-blur", "pointer-events-none touch-none")
|
||||
cIf(isVisible, cIf(!isPerfModeEnabled, "backdrop-blur"), "pointer-events-none touch-none")
|
||||
)}>
|
||||
<div
|
||||
className={cJoin(
|
||||
|
@ -90,8 +91,10 @@ export const LightBox = ({
|
|||
}}>
|
||||
{isDefined(src) && (
|
||||
<Img
|
||||
className={`h-[calc(100vh-4rem)] w-full object-contain drop-shadow-2xl
|
||||
shadow-shade`}
|
||||
className={cJoin(
|
||||
`h-[calc(100vh-4rem)] w-full object-contain`,
|
||||
cIf(!isPerfModeEnabled, "drop-shadow-2xl shadow-shade")
|
||||
)}
|
||||
src={src}
|
||||
quality={ImageQuality.Large}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import { Popup } from "components/Containers/Popup";
|
||||
import { Ico } from "components/Ico";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { sendAnalytics } from "helpers/analytics";
|
||||
import { useAtomGetter, useAtomPair } from "helpers/atoms";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
* ───────────────────────────────────────╯ COMPONENT ╰───────────────────────────────────────────
|
||||
*/
|
||||
|
||||
export const DebugPopup = (): JSX.Element => {
|
||||
const [isDebugMenuOpened, setDebugMenuOpened] = useAtomPair(atoms.layout.debugMenuOpened);
|
||||
|
||||
const os = useAtomGetter(atoms.userAgent.os);
|
||||
const browser = useAtomGetter(atoms.userAgent.browser);
|
||||
const engine = useAtomGetter(atoms.userAgent.engine);
|
||||
const deviceType = useAtomGetter(atoms.userAgent.deviceType);
|
||||
|
||||
const isPerfModeEnabled = useAtomGetter(atoms.settings.isPerfModeEnabled);
|
||||
const isPerfModeToggleable = useAtomGetter(atoms.settings.isPerfModeToggleable);
|
||||
const perfMode = useAtomGetter(atoms.settings.perfMode);
|
||||
|
||||
return (
|
||||
<Popup
|
||||
isVisible={isDebugMenuOpened}
|
||||
onCloseRequest={() => {
|
||||
setDebugMenuOpened(false);
|
||||
sendAnalytics("Debug", "Close debug menu");
|
||||
}}>
|
||||
<h2 className="inline-flex place-items-center gap-2 text-2xl">
|
||||
<Ico icon="bug_report" isFilled />
|
||||
Debug Menu
|
||||
</h2>
|
||||
|
||||
<h3>User Agent</h3>
|
||||
<div>
|
||||
<p>OS: {os}</p>
|
||||
<p>Device type: {deviceType ?? "undefined"}</p>
|
||||
<p>Browser: {browser}</p>
|
||||
<p>Engine: {engine}</p>
|
||||
</div>
|
||||
|
||||
<h3>Settings</h3>
|
||||
<div>
|
||||
<p>Raw perf mode: {perfMode}</p>
|
||||
<p>Perf mode: {isPerfModeEnabled ? "true" : "false"}</p>
|
||||
<p>Perf mode toggleable: {isPerfModeToggleable ? "true" : "false"}</p>
|
||||
</div>
|
||||
</Popup>
|
||||
);
|
||||
};
|
|
@ -23,9 +23,12 @@ export const MainPanel = (): JSX.Element => {
|
|||
const { format } = useFormat();
|
||||
const [isMainPanelReduced, setMainPanelReduced] = useAtomPair(atoms.layout.mainPanelReduced);
|
||||
const setMainPanelOpened = useAtomSetter(atoms.layout.mainPanelOpened);
|
||||
const [isSettingsOpened, setSettingsOpened] = useAtomPair(atoms.layout.settingsOpened);
|
||||
const [isSearchOpened, setSearchOpened] = useAtomPair(atoms.layout.searchOpened);
|
||||
const [isDebugMenuOpened, setDebugMenuOpened] = useAtomPair(atoms.layout.debugMenuOpened);
|
||||
const isDebugMenuAvailable = useAtomGetter(atoms.layout.debugMenuAvailable);
|
||||
|
||||
const closeMainPanel = useCallback(() => setMainPanelOpened(false), [setMainPanelOpened]);
|
||||
const setSettingsOpened = useAtomSetter(atoms.layout.settingsOpened);
|
||||
const setSearchOpened = useAtomSetter(atoms.layout.searchOpened);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -82,6 +85,7 @@ export const MainPanel = (): JSX.Element => {
|
|||
content={<h3 className="text-2xl">{format("open_settings")}</h3>}
|
||||
placement={isMainPanelReduced ? "right" : "top"}>
|
||||
<Button
|
||||
active={isSettingsOpened}
|
||||
onClick={() => {
|
||||
closeMainPanel();
|
||||
setSettingsOpened(true);
|
||||
|
@ -94,6 +98,7 @@ export const MainPanel = (): JSX.Element => {
|
|||
content={<h3 className="text-2xl">{format("open_search")}</h3>}
|
||||
placement={isMainPanelReduced ? "right" : "top"}>
|
||||
<Button
|
||||
active={isSearchOpened}
|
||||
onClick={() => {
|
||||
closeMainPanel();
|
||||
setSearchOpened(true);
|
||||
|
@ -102,6 +107,21 @@ export const MainPanel = (): JSX.Element => {
|
|||
icon="search"
|
||||
/>
|
||||
</ToolTip>
|
||||
{isDebugMenuAvailable && (
|
||||
<ToolTip
|
||||
content={<h3 className="text-2xl">Debug menu</h3>}
|
||||
placement={isMainPanelReduced ? "right" : "top"}>
|
||||
<Button
|
||||
active={isDebugMenuOpened}
|
||||
onClick={() => {
|
||||
closeMainPanel();
|
||||
setDebugMenuOpened(true);
|
||||
sendAnalytics("Debug", "Open debug menu");
|
||||
}}
|
||||
icon="bug_report"
|
||||
/>
|
||||
</ToolTip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,11 +11,12 @@ import { cJoin, cIf } from "helpers/className";
|
|||
import { prettyLanguage } from "helpers/formatters";
|
||||
import { filterHasAttributes, isDefined } from "helpers/asserts";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter, useAtomPair } from "helpers/atoms";
|
||||
import { ThemeMode } from "contexts/settings";
|
||||
import { useAtomGetter, useAtomPair, useAtomSetter } from "helpers/atoms";
|
||||
import { PerfMode, ThemeMode } from "contexts/settings";
|
||||
import { Ico } from "components/Ico";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -32,6 +33,9 @@ export const SettingsPopup = (): JSX.Element => {
|
|||
const [fontSize, setFontSize] = useAtomPair(atoms.settings.fontSize);
|
||||
const [playerName, setPlayerName] = useAtomPair(atoms.settings.playerName);
|
||||
const [themeMode, setThemeMode] = useAtomPair(atoms.settings.themeMode);
|
||||
const setPerfMode = useAtomSetter(atoms.settings.perfMode);
|
||||
const perfModeEnabled = useAtomGetter(atoms.settings.isPerfModeEnabled);
|
||||
const isPerfModeToggleable = useAtomGetter(atoms.settings.isPerfModeToggleable);
|
||||
|
||||
const languages = useAtomGetter(atoms.localData.languages);
|
||||
const { format } = useFormat();
|
||||
|
@ -237,6 +241,20 @@ export const SettingsPopup = (): JSX.Element => {
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid place-items-center">
|
||||
<div className="flex place-content-center place-items-center gap-1">
|
||||
<h3 className="text-xl">{format("performance_mode")}</h3>
|
||||
<ToolTip content={format("performance_mode_tooltip")} placement="top">
|
||||
<Ico icon="info" />
|
||||
</ToolTip>
|
||||
</div>
|
||||
<Switch
|
||||
value={perfModeEnabled}
|
||||
onClick={() => setPerfMode(perfModeEnabled ? PerfMode.Off : PerfMode.On)}
|
||||
disabled={!isPerfModeToggleable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
|
|
|
@ -77,6 +77,7 @@ export const PreviewCard = ({
|
|||
disabled = false,
|
||||
onClick,
|
||||
}: Props): JSX.Element => {
|
||||
const isPerfModeEnabled = useAtomGetter(atoms.settings.isPerfModeEnabled);
|
||||
const currency = useAtomGetter(atoms.settings.currency);
|
||||
const currencies = useAtomGetter(atoms.localData.currencies);
|
||||
const isHoverable = useDeviceSupportsHover();
|
||||
|
@ -117,7 +118,7 @@ export const PreviewCard = ({
|
|||
|
||||
return (
|
||||
<UpPressable
|
||||
className={cJoin("grid items-end text-left", className)}
|
||||
className={cJoin("relative grid items-end text-left", className)}
|
||||
href={href}
|
||||
onClick={onClick}
|
||||
noBackground
|
||||
|
@ -177,11 +178,11 @@ export const PreviewCard = ({
|
|||
"z-20 grid gap-2 p-4 transition-opacity linearbg-obi",
|
||||
cIf(
|
||||
!keepInfoVisible && isHoverable,
|
||||
`-inset-x-0.5 bottom-2 opacity-0 shadow-shade
|
||||
`-inset-x-0.5 bottom-2 opacity-0 !shadow-shade
|
||||
[border-radius:10%_10%_10%_10%_/_1%_1%_3%_3%]
|
||||
group-hover:opacity-100 hoverable:absolute hoverable:drop-shadow-lg
|
||||
group-hover:opacity-100 hoverable:absolute hoverable:shadow-lg
|
||||
notHoverable:rounded-b-md notHoverable:opacity-100`,
|
||||
"[border-radius:0%_0%_10%_10%_/_0%_0%_3%_3%]"
|
||||
cIf(!isPerfModeEnabled, "[border-radius:0%_0%_10%_10%_/_0%_0%_3%_3%]")
|
||||
)
|
||||
)}>
|
||||
{metadata?.position === "Top" && metadataJSX}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { atom } from "jotai";
|
||||
import { atomWithStorage } from "jotai/utils";
|
||||
import { containerQueries } from "contexts/containerQueries";
|
||||
import { userAgent } from "contexts/userAgent";
|
||||
import { atomPairing } from "helpers/atoms";
|
||||
import { settings } from "contexts/settings";
|
||||
import { UploadImageFragment } from "graphql/generated";
|
||||
|
@ -40,6 +41,8 @@ const searchOpened = atomPairing(atom(false));
|
|||
const settingsOpened = atomPairing(atom(false));
|
||||
const subPanelOpened = atomPairing(atom(false));
|
||||
const mainPanelOpened = atomPairing(atom(false));
|
||||
const debugMenuOpened = atomPairing(atom(false));
|
||||
const debugMenuAvailable = atom((get) => get(settings.playerName[0]) === "debug");
|
||||
const menuGesturesEnabled = atomPairing(atomWithStorage("isMenuGesturesEnabled", false));
|
||||
const terminalMode = atom((get) => get(settings.playerName[0]) === "root");
|
||||
|
||||
|
@ -51,6 +54,8 @@ const layout = {
|
|||
mainPanelOpened,
|
||||
menuGesturesEnabled,
|
||||
terminalMode,
|
||||
debugMenuAvailable,
|
||||
debugMenuOpened,
|
||||
};
|
||||
|
||||
export const atoms = {
|
||||
|
@ -59,6 +64,7 @@ export const atoms = {
|
|||
localData,
|
||||
lightBox,
|
||||
containerQueries,
|
||||
userAgent,
|
||||
};
|
||||
|
||||
// Do not import outside of the "contexts" folder
|
||||
|
|
|
@ -6,6 +6,7 @@ import { atomPairing, useAtomGetter, useAtomPair } from "helpers/atoms";
|
|||
import { getDefaultPreferredLanguages } from "helpers/locales";
|
||||
import { isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
import { usePrefersDarkMode } from "hooks/useMediaQuery";
|
||||
import { userAgent } from "contexts/userAgent";
|
||||
|
||||
export enum ThemeMode {
|
||||
Dark = "dark",
|
||||
|
@ -13,13 +14,43 @@ export enum ThemeMode {
|
|||
Light = "light",
|
||||
}
|
||||
|
||||
export enum PerfMode {
|
||||
On = "on",
|
||||
Auto = "auto",
|
||||
Off = "off",
|
||||
}
|
||||
|
||||
const preferredLanguagesAtom = atomPairing(atomWithStorage<string[]>("preferredLanguages", []));
|
||||
const themeModeAtom = atomPairing(atomWithStorage<ThemeMode>("themeMode", ThemeMode.Auto));
|
||||
const themeModeAtom = atomPairing(atomWithStorage("themeMode", ThemeMode.Auto));
|
||||
const darkModeAtom = atomPairing(atom(false));
|
||||
const fontSizeAtom = atomPairing(atomWithStorage("fontSize", 1));
|
||||
const dyslexicAtom = atomPairing(atomWithStorage("isDyslexic", false));
|
||||
const currencyAtom = atomPairing(atomWithStorage("currency", "USD"));
|
||||
const playerNameAtom = atomPairing(atomWithStorage("playerName", ""));
|
||||
const perfModeAtom = atomPairing(atomWithStorage("perfMode", PerfMode.Auto));
|
||||
|
||||
const isPerfModeEnabledAtom = atom((get) => {
|
||||
const os = get(userAgent.os);
|
||||
const engine = get(userAgent.engine);
|
||||
const perfMode = get(perfModeAtom[0]);
|
||||
|
||||
if (os === "iOS") return true;
|
||||
if (engine === "WebKit") return true;
|
||||
if (perfMode === "auto") {
|
||||
if (engine === "Blink") return false;
|
||||
if (os === "Linux") return true;
|
||||
if (os === "Android") return true;
|
||||
}
|
||||
return perfMode === PerfMode.On;
|
||||
});
|
||||
|
||||
const isPerfModeToggleableAtom = atom((get) => {
|
||||
const engine = get(userAgent.engine);
|
||||
const os = get(userAgent.os);
|
||||
if (os === "iOS") return false;
|
||||
if (engine === "WebKit") return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
export const settings = {
|
||||
preferredLanguages: preferredLanguagesAtom,
|
||||
|
@ -29,6 +60,9 @@ export const settings = {
|
|||
dyslexic: dyslexicAtom,
|
||||
currency: currencyAtom,
|
||||
playerName: playerNameAtom,
|
||||
perfMode: perfModeAtom,
|
||||
isPerfModeEnabled: isPerfModeEnabledAtom,
|
||||
isPerfModeToggleable: isPerfModeToggleableAtom,
|
||||
};
|
||||
|
||||
export const useSettings = (): void => {
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { atom } from "jotai";
|
||||
import { useIsClient } from "usehooks-ts";
|
||||
import { useEffect } from "react";
|
||||
import { UAParser } from "ua-parser-js";
|
||||
import { atomPairing, useAtomSetter } from "helpers/atoms";
|
||||
import { getLogger } from "helpers/logger";
|
||||
|
||||
const logger = getLogger("📱 [User Agent]");
|
||||
|
||||
const osAtom = atomPairing(atom<string | undefined>(undefined));
|
||||
const browserAtom = atomPairing(atom<string | undefined>(undefined));
|
||||
const engineAtom = atomPairing(atom<string | undefined>(undefined));
|
||||
const deviceTypeAtom = atomPairing(atom<string | undefined>(undefined));
|
||||
|
||||
export const userAgent = {
|
||||
os: osAtom[0],
|
||||
browser: browserAtom[0],
|
||||
engine: engineAtom[0],
|
||||
deviceType: deviceTypeAtom[0],
|
||||
};
|
||||
|
||||
export const useUserAgent = (): void => {
|
||||
const setOs = useAtomSetter(osAtom);
|
||||
const setBrowser = useAtomSetter(browserAtom);
|
||||
const setEngine = useAtomSetter(engineAtom);
|
||||
const setDeviceType = useAtomSetter(deviceTypeAtom);
|
||||
|
||||
const isClient = useIsClient();
|
||||
|
||||
useEffect(() => {
|
||||
const parser = new UAParser();
|
||||
|
||||
const os = parser.getOS().name;
|
||||
const browser = parser.getBrowser().name;
|
||||
const engine = parser.getEngine().name;
|
||||
const deviceType = parser.getDevice().type;
|
||||
|
||||
setOs(os);
|
||||
setBrowser(browser);
|
||||
setEngine(engine);
|
||||
setDeviceType(deviceType);
|
||||
|
||||
logger.log({ os, browser, engine, deviceType });
|
||||
}, [isClient, setBrowser, setDeviceType, setEngine, setOs]);
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
import { useEffect } from "react";
|
||||
import { isDefined } from "helpers/asserts";
|
||||
import { useIsWebkit } from "hooks/useIsWebkit";
|
||||
|
||||
export const useWebkitFixes = (): void => {
|
||||
const isWebkit = useIsWebkit();
|
||||
|
||||
useEffect(() => {
|
||||
const next = document.getElementById("__next");
|
||||
if (isDefined(next)) {
|
||||
if (isWebkit) {
|
||||
next.classList.add("webkit-fixes");
|
||||
} else {
|
||||
next.classList.remove("webkit-fixes");
|
||||
}
|
||||
}
|
||||
}, [isWebkit]);
|
||||
};
|
|
@ -186,4 +186,6 @@ export interface ICUParams {
|
|||
player_name_tooltip: never;
|
||||
download_scans: never;
|
||||
search_placeholder: never;
|
||||
performance_mode: never;
|
||||
performance_mode_tooltip: never;
|
||||
}
|
||||
|
|
|
@ -193,6 +193,8 @@ query localDataGetWebsiteInterfaces {
|
|||
player_name_tooltip
|
||||
download_scans
|
||||
search_placeholder
|
||||
performance_mode
|
||||
performance_mode_tooltip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import { useMemo } from "react";
|
||||
import UAParser from "ua-parser-js";
|
||||
import { useIsClient } from "usehooks-ts";
|
||||
|
||||
export const useIsWebkit = (): boolean => {
|
||||
const isClient = useIsClient();
|
||||
return useMemo<boolean>(() => {
|
||||
if (isClient) {
|
||||
const parser = new UAParser();
|
||||
return parser.getBrowser().name === "Safari" || parser.getOS().name === "iOS";
|
||||
}
|
||||
return false;
|
||||
}, [isClient]);
|
||||
};
|
|
@ -20,22 +20,24 @@ import { LightBoxProvider } from "contexts/LightBoxProvider";
|
|||
import { SettingsPopup } from "components/Panels/SettingsPopup";
|
||||
import { useSettings } from "contexts/settings";
|
||||
import { useContainerQueries } from "contexts/containerQueries";
|
||||
import { useWebkitFixes } from "contexts/webkitFixes";
|
||||
import { SearchPopup } from "components/Panels/SearchPopup";
|
||||
import { useScrollIntoView } from "hooks/useScrollIntoView";
|
||||
import { useUserAgent } from "contexts/userAgent";
|
||||
import { DebugPopup } from "components/Panels/DebugPopup";
|
||||
|
||||
const AccordsLibraryApp = (props: AppProps): JSX.Element => {
|
||||
useLocalData();
|
||||
useSettings();
|
||||
useContainerQueries();
|
||||
useWebkitFixes();
|
||||
useScrollIntoView();
|
||||
useUserAgent();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchPopup />
|
||||
<SettingsPopup />
|
||||
<LightBoxProvider />
|
||||
<DebugPopup />
|
||||
<Script
|
||||
data-website-id={process.env.NEXT_PUBLIC_UMAMI_ID}
|
||||
src={`${process.env.NEXT_PUBLIC_UMAMI_URL}/script.js`}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { sendAnalytics } from "helpers/analytics";
|
|||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -146,10 +147,10 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<input
|
||||
<Button
|
||||
type="submit"
|
||||
value={format("send")}
|
||||
className="w-min !px-6"
|
||||
text={format("send")}
|
||||
disabled={formState !== "stale"}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -73,6 +73,7 @@ interface Props extends AppLayoutRequired {
|
|||
|
||||
const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||
const currency = useAtomGetter(atoms.settings.currency);
|
||||
const isPerfModeEnabled = useAtomGetter(atoms.settings.isPerfModeEnabled);
|
||||
const { format, formatLibraryItemType } = useFormat();
|
||||
const currencies = useAtomGetter(atoms.localData.currencies);
|
||||
|
||||
|
@ -178,7 +179,8 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
<div className="grid place-items-center gap-12">
|
||||
<div
|
||||
className={cJoin(
|
||||
"relative h-[50vh] w-full cursor-pointer drop-shadow-xl shadow-shade",
|
||||
"relative h-[50vh] w-full cursor-pointer",
|
||||
cIf(!isPerfModeEnabled, "drop-shadow-xl shadow-shade"),
|
||||
cIf(isContentPanelAtLeast3xl, "mb-16", "h-[60vh]")
|
||||
)}>
|
||||
{item.thumbnail?.data?.attributes ? (
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useHotkeys } from "react-hotkeys-hook";
|
|||
import Slider from "rc-slider";
|
||||
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
|
||||
import { z } from "zod";
|
||||
import { atom } from "jotai";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import {
|
||||
Enum_Componentmetadatabooks_Page_Order as PageOrder,
|
||||
|
@ -37,7 +38,6 @@ import { useFullscreen } from "hooks/useFullscreen";
|
|||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { FilterSettings, useReaderSettings } from "hooks/useReaderSettings";
|
||||
import { useIsWebkit } from "hooks/useIsWebkit";
|
||||
import { useTypedRouter } from "hooks/useTypedRouter";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
@ -71,6 +71,8 @@ const queryParamSchema = z.object({
|
|||
page: z.coerce.number().optional(),
|
||||
});
|
||||
|
||||
const isWebKitAtom = atom((get) => get(atoms.userAgent.engine) === "WebKit");
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
* ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
|
||||
|
@ -120,7 +122,7 @@ const LibrarySlug = ({
|
|||
is1ColumnLayout ? "single" : "double"
|
||||
);
|
||||
const router = useTypedRouter(queryParamSchema);
|
||||
const isWebkit = useIsWebkit();
|
||||
const isWebKit = useAtomGetter(isWebKitAtom);
|
||||
|
||||
const { isFullscreen, toggleFullscreen, requestFullscreen } = useFullscreen(Ids.ContentPanel);
|
||||
|
||||
|
@ -299,7 +301,7 @@ const LibrarySlug = ({
|
|||
<Switch value={isSidePagesEnabled} onClick={toggleIsSidePagesEnabled} />
|
||||
</WithLabel>
|
||||
|
||||
{!isWebkit && (
|
||||
{!isWebKit && (
|
||||
<WithLabel label={format("shadow")}>
|
||||
<Switch value={filterSettings.dropShadow} onClick={toggleDropShadow} />
|
||||
</WithLabel>
|
||||
|
@ -394,7 +396,7 @@ const LibrarySlug = ({
|
|||
display: "grid",
|
||||
placeContent: "center",
|
||||
filter:
|
||||
!filterSettings.dropShadow || isWebkit
|
||||
!filterSettings.dropShadow || isWebKit
|
||||
? undefined
|
||||
: isDarkMode
|
||||
? CUSTOM_DARK_DROPSHADOW
|
||||
|
|
|
@ -54,13 +54,6 @@ textarea {
|
|||
@apply rounded-2xl p-6 text-left;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
@apply grid cursor-pointer place-content-center place-items-center rounded-full border
|
||||
border-dark px-4 pb-[0.5rem] pt-[0.4rem] text-dark outline-none transition-all shadow-shade
|
||||
hover:bg-dark hover:text-light hover:drop-shadow-lg active:border-black active:bg-black
|
||||
active:text-light active:drop-shadow-lg active:shadow-black;
|
||||
}
|
||||
|
||||
input:enabled,
|
||||
textarea:enabled {
|
||||
@apply hover:bg-mid hover:outline-transparent;
|
||||
|
|
|
@ -250,21 +250,6 @@ export default {
|
|||
});
|
||||
}),
|
||||
|
||||
/* Webkit fixes */
|
||||
plugin(({ addUtilities }) => {
|
||||
addUtilities({
|
||||
".webkit-fixes": {
|
||||
"*": {
|
||||
"--tw-drop-shadow": "unset !important",
|
||||
"--tw-shadow": "unset !important",
|
||||
},
|
||||
".texture-paper-dots": {
|
||||
backgroundImage: "unset !important",
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
/* Add support for break-wrods CSS attribute */
|
||||
plugin(({ addUtilities }) => {
|
||||
addUtilities({
|
||||
|
|
Loading…
Reference in New Issue