/** * Copyright (C) 2019-2021 Carl Kittelberger * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import * as React from 'react'; import { config as fontawesomeSvgCoreConfig, library, } from '@fortawesome/fontawesome-svg-core'; import { faClock, faCopy, faDownload, faHourglass, faLanguage, faLightbulb, faSearch, faShare, } from '@fortawesome/free-solid-svg-icons'; import { faLightbulb as farLightbulb } from '@fortawesome/free-regular-svg-icons'; import { faTwitch as fabTwitch } from '@fortawesome/free-brands-svg-icons'; import '@fortawesome/fontawesome-svg-core/styles.css'; import Container from 'react-bootstrap/Container'; import Navbar from 'react-bootstrap/Navbar'; import { IntlProvider, FormattedMessage } from 'react-intl'; import Link from 'next/link'; import Router from 'next/router'; import App, { AppContext } from 'next/app'; import useSWR, { SWRConfig } from 'swr'; import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; import '../styles/main.scss'; import Image from 'react-bootstrap/Image'; import Head from 'next/head'; import { ButtonGroup } from 'react-bootstrap'; import { shouldPolyfill } from '@formatjs/intl-numberformat/should-polyfill'; import HomeStyle from '../styles/Home.module.css'; import DarkToggler from '../components/DarkToggler'; import { fetchJson } from '../util/api'; import { defaultLocale, loadLocaleData, loadLocalePolyfill } from '../util/localization'; import gdqLogo from '../images/gdqlogo.png'; import favicon from '../images/favicon.svg'; import LocaleSwitcher from '../components/LocaleSwitcher'; import withSession from '../util/session'; fontawesomeSvgCoreConfig.autoAddCss = false; library.add( fabTwitch, faClock, faCopy, faDownload, faHourglass, faLanguage, faLightbulb, farLightbulb, faSearch, faShare, ); Router.events.on('routeChangeStart', (url) => { console.log(`Loading: ${url}`); NProgress.start(); }); Router.events.on('routeChangeComplete', () => NProgress.done()); Router.events.on('routeChangeError', () => NProgress.done()); export interface GDQArchiveAppPageProps extends Record { locale: string, messages: any, enableDark: boolean, } const polyfillRequired = shouldPolyfill(); function GDQArchiveApp({ Component, pageProps: { initialEnableDark, initialLocale, initialMessages, ...pageProps }, }: { Component: React.ElementType>, pageProps: GDQArchiveAppPageProps, }) { /* Component state */ const [currentMessages, setMessages] = React.useState(initialMessages); const [isChangingLocale, setIsChangingLocale] = React.useState(false); const [isChangingDarkMode, setIsChangingDarkMode] = React.useState(false); const [{ loadedPolyfill, isLoadingPolyfill, }, setPolyfillState] = React.useState({ loadedPolyfill: null, isLoadingPolyfill: false, }); /* Synchronize via SWR */ const { data: userData, error, isValidating, mutate, } = useSWR('/api/user', { fetcher: fetchJson, initialData: { locale: initialLocale, enableDark: initialEnableDark, }, onError(err) { console.log(err); }, }); if (error) { console.log(error); } const { enableDark: enableDarkMode, locale: currentLocale, } = userData; /* Polyfill for locale */ if (!isLoadingPolyfill && polyfillRequired && currentLocale !== loadedPolyfill) { console.log('load locale polyfill start'); loadLocalePolyfill(currentLocale) .then(() => { console.log('set polyfill state end'); setPolyfillState({ loadedPolyfill: currentLocale, isLoadingPolyfill: false, }); }); } async function onChangeLocale(value: string) { setIsChangingLocale(true); const formData = new URLSearchParams(); formData.append('locale', value); await fetchJson('/api/changePreferences', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: formData, }); mutate({ ...userData, locale: value }); setMessages(await loadLocaleData(value)); setIsChangingLocale(false); } let darkToggleAnimationTimer: NodeJS.Timeout = null; function triggerDarkToggleAnimation() { document.documentElement.setAttribute('data-toggled-dark', 'true'); if (darkToggleAnimationTimer !== null) { clearTimeout(darkToggleAnimationTimer); } darkToggleAnimationTimer = setTimeout(() => { document.documentElement.removeAttribute('data-toggled-dark'); }, 1200); } /** * @param {bool} value */ async function onChangeDarkMode(value: boolean) { setIsChangingDarkMode(true); const formData = new URLSearchParams(); formData.append('enableDark', value.toString()); await fetchJson('/api/changePreferences', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: formData, }); mutate({ ...userData, enableDark: value }); triggerDarkToggleAnimation(); setIsChangingDarkMode(false); } React.useEffect(() => { if (enableDarkMode) { document.documentElement.setAttribute('data-enable-dark', 'true'); } else { document.documentElement.removeAttribute('data-enable-dark'); } document.documentElement.setAttribute('lang', currentLocale); }); return ( Games Done Quick {' '} {/* eslint-disable-next-line react/jsx-props-no-spreading */} ); } const getInitialPropsFromReq = withSession(async (req) => { const initialLocale = req.session.get('locale') || defaultLocale; const initialMessages = await loadLocaleData(initialLocale); const initialEnableDark = req.session.get('enable-dark') || false; return { pageProps: { initialEnableDark, initialLocale, initialMessages, }, }; }); GDQArchiveApp.getInitialProps = async (appContext: AppContext) => { const result = ({ ...await App.getInitialProps(appContext), ...await getInitialPropsFromReq(appContext.ctx.req, appContext.ctx.res), }); // console.log('getInitialProps for app:', result); return result; }; export default GDQArchiveApp;