2021-01-05 15:25:09 +00:00
/ * *
* Copyright ( C ) 2019 - 2021 Carl Kittelberger < icedream @ icedream.pw >
*
* 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 < http : / / www.gnu.org / licenses / > .
* /
2020-08-22 20:25:57 +00:00
import * as React from 'react' ;
import {
config as fontawesomeSvgCoreConfig ,
library ,
} from '@fortawesome/fontawesome-svg-core' ;
import {
faClock ,
2021-01-04 06:51:44 +00:00
faCopy ,
2021-01-03 19:24:25 +00:00
faDownload ,
2021-01-04 06:51:44 +00:00
faHourglass ,
faLanguage ,
faLightbulb ,
2023-01-08 10:07:44 +00:00
faRunning ,
2020-08-22 20:25:57 +00:00
faSearch ,
faShare ,
} from '@fortawesome/free-solid-svg-icons' ;
import { faLightbulb as farLightbulb } from '@fortawesome/free-regular-svg-icons' ;
2023-01-08 10:07:44 +00:00
import {
faFacebook as fabFacebook ,
faTwitch as fabTwitch ,
faTwitter as fabTwitter ,
faYoutube as fabYoutube ,
} from '@fortawesome/free-brands-svg-icons' ;
2020-08-22 20:25:57 +00:00
import '@fortawesome/fontawesome-svg-core/styles.css' ;
import Container from 'react-bootstrap/Container' ;
import Navbar from 'react-bootstrap/Navbar' ;
2023-01-09 01:35:54 +00:00
import SSRProvider from 'react-bootstrap/SSRProvider' ;
2020-08-22 20:25:57 +00:00
import { IntlProvider , FormattedMessage } from 'react-intl' ;
import Link from 'next/link' ;
import Router from 'next/router' ;
2023-01-09 01:35:54 +00:00
import App , { AppContext , AppProps } from 'next/app' ;
2020-08-22 20:25:57 +00:00
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' ;
2023-01-09 01:35:54 +00:00
import { ButtonGroup } from 'react-bootstrap' ;
2020-08-22 20:25:57 +00:00
import { shouldPolyfill } from '@formatjs/intl-numberformat/should-polyfill' ;
2023-01-09 01:35:54 +00:00
import '../styles/Home.module.css' ;
2020-08-22 20:25:57 +00:00
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 (
2023-01-08 10:07:44 +00:00
fabFacebook ,
2021-01-04 06:51:44 +00:00
fabTwitch ,
2023-01-08 10:07:44 +00:00
fabTwitter ,
fabYoutube ,
2020-08-22 20:25:57 +00:00
faClock ,
faCopy ,
2021-01-03 19:24:25 +00:00
faDownload ,
2021-01-04 06:51:44 +00:00
faHourglass ,
2020-08-22 20:25:57 +00:00
faLanguage ,
faLightbulb ,
farLightbulb ,
2023-01-08 10:07:44 +00:00
faRunning ,
2021-01-04 06:51:44 +00:00
faSearch ,
faShare ,
2020-08-22 20:25:57 +00:00
) ;
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 < string , any > {
locale : string ,
messages : any ,
enableDark : boolean ,
}
const polyfillRequired = shouldPolyfill ( ) ;
function GDQArchiveApp ( {
Component ,
pageProps : {
initialEnableDark ,
initialLocale ,
initialMessages ,
. . . pageProps
} ,
2023-01-09 01:35:54 +00:00
} : AppProps < GDQArchiveAppPageProps > ) {
2020-08-22 20:25:57 +00:00
/* 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 ,
2023-01-09 01:35:54 +00:00
fallbackData : {
2020-08-22 20:25:57 +00:00
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 ,
} ) ;
2021-01-03 17:52:45 +00:00
mutate ( { . . . userData , locale : value } ) ;
2020-08-22 20:25:57 +00:00
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 ) ;
}
/ * *
2023-01-09 01:35:54 +00:00
* @param { bool } value
* /
2020-08-22 20:25:57 +00:00
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 ,
} ) ;
2021-01-03 17:52:45 +00:00
mutate ( { . . . userData , enableDark : value } ) ;
2020-08-22 20:25:57 +00:00
triggerDarkToggleAnimation ( ) ;
setIsChangingDarkMode ( false ) ;
}
React . useEffect ( ( ) = > {
if ( enableDarkMode ) {
2023-01-08 10:07:44 +00:00
document . documentElement . setAttribute ( 'data-bs-color-scheme' , 'dark' ) ;
2020-08-22 20:25:57 +00:00
} else {
2023-01-08 10:07:44 +00:00
document . documentElement . removeAttribute ( 'data-bs-color-scheme' ) ;
2020-08-22 20:25:57 +00:00
}
document . documentElement . setAttribute ( 'lang' , currentLocale ) ;
} ) ;
return (
2023-01-09 01:35:54 +00:00
< SSRProvider >
< SWRConfig
value = { {
fetcher : fetchJson ,
onError ( err ) {
console . error ( err ) ;
} ,
} }
2020-08-22 20:25:57 +00:00
>
2023-01-09 01:35:54 +00:00
< IntlProvider
messages = { currentMessages }
locale = { currentLocale }
defaultLocale = { defaultLocale }
>
< Navbar bg = "dark" expand = "md" variant = "dark" >
< Container fluid >
< Link legacyBehavior passHref href = "/" >
< Navbar.Brand >
< Image
src = { typeof gdqLogo === 'string' ? gdqLogo : gdqLogo.src }
alt = "Games Done Quick"
height = { 30 }
className = { [
// HomeStyle.logo,
'd-inline-block' ,
// 'align-middle',
'align-top' ,
] . join ( ' ' ) }
/ >
{ ' ' }
< FormattedMessage
id = "Navbar.brandText"
defaultMessage = "Instant Archive"
/ >
< / Navbar.Brand >
< / Link >
{ / * < N a v b a r . T e x t >
2023-01-08 10:07:44 +00:00
< Breadcrumb >
< Breadcrumb.Item >
2023-01-09 01:35:54 +00:00
< Link legacyBehavior passHref href = "/" >
2023-01-08 10:07:44 +00:00
< FormattedMessage
id = "Breadcrumb.homeTitle"
defaultMessage = "GDQ Instant Archive"
description = "Root node text in breadcrumb"
/ >
< / Link >
< / Breadcrumb.Item >
< Breadcrumb.Item >
< FormattedMessage
id = "Breadcrumb.homeTitle"
defaultMessage = "GDQ Instant Archive"
description = "Root node text in breadcrumb"
/ >
< / Breadcrumb.Item >
< / Breadcrumb >
< / Navbar.Text > * / }
2023-01-09 01:35:54 +00:00
< Navbar.Toggle aria - controls = "basic-navbar-nav" / >
< Navbar.Collapse
id = "basic-navbar-nav"
className = "justify-content-end"
>
< ButtonGroup >
< LocaleSwitcher
locale = { currentLocale }
// eslint-disable-next-line react/jsx-no-bind
onChangeLocale = { onChangeLocale }
disabled = { isChangingLocale || isValidating }
showLoading = { isChangingLocale }
/ >
< DarkToggler
isDarkEnabled = { enableDarkMode }
// eslint-disable-next-line react/jsx-no-bind
onChangeDarkMode = { onChangeDarkMode }
disabled = { isChangingDarkMode || isValidating }
showLoading = { isChangingDarkMode }
/ >
< / ButtonGroup >
< / Navbar.Collapse >
< / Container >
< / Navbar >
< Head >
< meta name = "color-scheme" content = "light dark" / >
< meta name = "theme-color" content = "rgb(33, 37, 41)" / >
< meta name = "keywords" content = "gdq,games done quick,gaming,games,speedrun,speedrunning,vod,vods,video on demand,twitch,youtube,replay,archive,video,videos" / >
< link rel = "icon" type = "image/svg+xml" href = { typeof favicon === 'string' ? favicon : favicon.src } / >
< / Head >
< Container className = "mt-3" >
{ /* eslint-disable-next-line react/jsx-props-no-spreading */ }
< Component { ...pageProps } / >
2023-01-08 10:07:44 +00:00
< / Container >
2023-01-09 01:35:54 +00:00
< / IntlProvider >
< / SWRConfig >
< / SSRProvider >
2020-08-22 20:25:57 +00:00
) ;
}
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 ;