Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docs] First page #156

Merged
merged 2 commits into from
Aug 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
382 changes: 380 additions & 2 deletions docs/pages/_app.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,387 @@
import MyApp from '@material-ui/monorepo/docs/pages/_app';
/* eslint-disable import/first */

import { LicenseInfo } from '@material-ui/x-grid';

// Remove the license warning from demonstration purposes
LicenseInfo.setLicenseKey(
'0f94d8b65161817ca5d7f7af8ac2f042T1JERVI6TVVJLVN0b3J5Ym9vayxFWFBJUlk9MTY1NDg1ODc1MzU1MCxLRVlWRVJTSU9OPTE=',
);

export default MyApp;
import 'docs/src/modules/components/bootstrap';
// --- Post bootstrap -----
import React from 'react';
import find from 'lodash/find';
import { Provider as ReduxProvider, useDispatch, useSelector } from 'react-redux';
import { loadCSS } from 'fg-loadcss/src/loadCSS';
import NextHead from 'next/head';
import PropTypes from 'prop-types';
import acceptLanguage from 'accept-language';
import { create } from 'jss';
import rtl from 'jss-rtl';
import { useRouter } from 'next/router';
import { StylesProvider, jssPreset } from '@material-ui/styles';
import pages from 'docsx/src/pages';
import initRedux from 'docs/src/modules/redux/initRedux';
import PageContext from 'docs/src/modules/components/PageContext';
import GoogleAnalytics from 'docs/src/modules/components/GoogleAnalytics';
import loadScript from 'docs/src/modules/utils/loadScript';
import { ThemeProvider } from 'docs/src/modules/components/ThemeContext';
import { pathnameToLanguage, getCookie } from 'docs/src/modules/utils/helpers';
import { ACTION_TYPES, CODE_VARIANTS } from 'docs/src/modules/constants';

// Configure JSS
const jss = create({
plugins: [...jssPreset().plugins, rtl()],
insertionPoint: process.browser ? document.querySelector('#insertion-point-jss') : null,
});

function useFirstRender() {
const firstRenderRef = React.useRef(true);
React.useEffect(() => {
firstRenderRef.current = false;
}, []);

return firstRenderRef.current;
}

acceptLanguage.languages(['en', 'zh', 'pt', 'ru']);

function LanguageNegotiation() {
const dispatch = useDispatch();
const router = useRouter();
const userLanguage = useSelector((state) => state.options.userLanguage);

React.useEffect(() => {
const { userLanguage: userLanguageUrl, canonical } = pathnameToLanguage(router.asPath);
const preferedLanguage =
getCookie('userLanguage') !== 'noDefault' && userLanguage === 'en'
? acceptLanguage.get(navigator.language)
: userLanguage;

if (preferedLanguage !== userLanguage) {
window.location = preferedLanguage === 'en' ? canonical : `/${preferedLanguage}${canonical}`;
} else if (userLanguageUrl !== userLanguage) {
dispatch({ type: ACTION_TYPES.OPTIONS_CHANGE, payload: { userLanguage: userLanguageUrl } });
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps

return null;
}

/**
* Priority: on first render: navigated value, persisted value; otherwise initial value, 'JS'
*
* @returns {string} - The persisted variant if the initial value is undefined
*/
function usePersistCodeVariant() {
const dispatch = useDispatch();
const { codeVariant: initialCodeVariant = CODE_VARIANTS.JS } = useSelector(
(state) => state.options,
);

const isFirstRender = useFirstRender();

const navigatedCodeVariant = React.useMemo(() => {
const navigatedCodeVariantMatch =
typeof window !== 'undefined' ? window.location.hash.match(/\.(js|tsx)$/) : null;

if (navigatedCodeVariantMatch === null) {
return undefined;
}

return navigatedCodeVariantMatch[1] === 'tsx' ? CODE_VARIANTS.TS : CODE_VARIANTS.JS;
}, []);

const persistedCodeVariant = React.useMemo(() => {
if (typeof window === 'undefined') {
return undefined;
}
return getCookie('codeVariant');
}, []);

/**
* we initialize from navigation or cookies. on subsequent renders the store is the
* truth
*/
const codeVariant =
isFirstRender === true
? navigatedCodeVariant || persistedCodeVariant || initialCodeVariant
: initialCodeVariant;

React.useEffect(() => {
if (codeVariant !== initialCodeVariant) {
dispatch({ type: ACTION_TYPES.OPTIONS_CHANGE, payload: { codeVariant } });
}
});

React.useEffect(() => {
document.cookie = `codeVariant=${codeVariant};path=/;max-age=31536000`;
}, [codeVariant]);

return codeVariant;
}

/**
* basically just a `useAnalytics` hook.
* However, it needs the redux store which is created
* in the same component this "hook" is used.
*/
function Analytics() {
React.useEffect(() => {
loadScript('https://www.google-analytics.com/analytics.js', document.querySelector('head'));
}, []);

const options = useSelector((state) => state.options);

const codeVariant = usePersistCodeVariant();
React.useEffect(() => {
window.ga('set', 'dimension1', codeVariant);
}, [codeVariant]);

React.useEffect(() => {
window.ga('set', 'dimension2', options.userLanguage);
}, [options.userLanguage]);

React.useEffect(() => {
/**
* @type {null | MediaQueryList}
*/
let matchMedia = null;

/**
* Based on https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#Monitoring_screen_resolution_or_zoom_level_changes
* Adjusted to track 3 or more different ratios
*/
function trackDevicePixelRation() {
window.ga('set', 'dimension3', Math.round(window.devicePixelRatio * 10) / 10);

matchMedia = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
// Need to setup again.
// Otherwise we track only changes from the initial ratio to another.
// It would not track 3 or more different monitors/zoom stages
matchMedia.addListener(trackDevicePixelRation);
}

trackDevicePixelRation();

return () => {
matchMedia = null;
};
}, []);

return null;
}

// Inspired by
// https://developers.google.com/web/tools/workbox/guides/advanced-recipes#offer_a_page_reload_for_users
function forcePageReload(registration) {
// console.log('already controlled?', Boolean(navigator.serviceWorker.controller));

if (!navigator.serviceWorker.controller) {
// The window client isn't currently controlled so it's a new service
// worker that will activate immediately.
return;
}

// console.log('registration waiting?', Boolean(registration.waiting));
if (registration.waiting) {
// SW is waiting to activate. Can occur if multiple clients open and
// one of the clients is refreshed.
registration.waiting.postMessage('skipWaiting');
return;
}

function listenInstalledStateChange() {
registration.installing.addEventListener('statechange', (event) => {
// console.log('statechange', event.target.state);
if (event.target.state === 'installed' && registration.waiting) {
// A new service worker is available, inform the user
registration.waiting.postMessage('skipWaiting');
} else if (event.target.state === 'activated') {
// Force the control of the page by the activated service worker.
window.location.reload();
}
});
}

if (registration.installing) {
listenInstalledStateChange();
return;
}

// We are currently controlled so a new SW may be found...
// Add a listener in case a new SW is found,
registration.addEventListener('updatefound', listenInstalledStateChange);
}

async function registerServiceWorker() {
if (
'serviceWorker' in navigator &&
process.env.NODE_ENV === 'production' &&
window.location.host.indexOf('material-ui.com') <= 0
) {
// register() automatically attempts to refresh the sw.js.
const registration = await navigator.serviceWorker.register('/sw.js');
// Force the page reload for users.
forcePageReload(registration);
}
}

let dependenciesLoaded = false;

function loadDependencies() {
if (dependenciesLoaded) {
return;
}

dependenciesLoaded = true;

loadCSS(
'https://fonts.googleapis.com/icon?family=Material+Icons',
document.querySelector('#material-icon-font'),
);
}

if (process.browser && process.env.NODE_ENV === 'production') {
// eslint-disable-next-line no-console
console.log(
`%c

███╗ ███╗ █████╗ ████████╗███████╗██████╗ ██╗ █████╗ ██╗ ██╗ ██╗██╗
████╗ ████║██╔══██╗╚══██╔══╝██╔════╝██╔══██╗██║██╔══██╗██║ ██║ ██║██║
██╔████╔██║███████║ ██║ █████╗ ██████╔╝██║███████║██║█████╗██║ ██║██║
██║╚██╔╝██║██╔══██║ ██║ ██╔══╝ ██╔══██╗██║██╔══██║██║╚════╝██║ ██║██║
██║ ╚═╝ ██║██║ ██║ ██║ ███████╗██║ ██║██║██║ ██║███████╗ ╚██████╔╝██║
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝

Tip: you can access the documentation \`theme\` object directly in the console.
`,
'font-family:monospace;color:#1976d2;font-size:12px;',
);
}

function findActivePage(currentPages, pathname) {
const activePage = find(currentPages, (page) => {
if (page.children) {
if (pathname.indexOf(`${page.pathname}/`) === 0) {
// Check if one of the children matches (for /components)
return findActivePage(page.children, pathname);
}
}

// Should be an exact match if no children
return pathname === page.pathname;
});

if (!activePage) {
return null;
}

// We need to drill down
if (activePage.pathname !== pathname) {
return findActivePage(activePage.children, pathname);
}

return activePage;
}

function AppWrapper(props) {
const { children, pageProps } = props;

const router = useRouter();
const [redux] = React.useState(() =>
initRedux({ options: { userLanguage: pageProps.userLanguage } }),
);

React.useEffect(() => {
loadDependencies();
registerServiceWorker();

// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);

const activePage = findActivePage(pages, router.pathname);

let fonts = ['https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap'];
if (router.pathname.match(/onepirate/)) {
fonts = [
'https://fonts.googleapis.com/css?family=Roboto+Condensed:700|Work+Sans:300,400&display=swap',
];
}

return (
<React.Fragment>
<NextHead>
{fonts.map((font) => (
<link rel="stylesheet" href={font} key={font} />
))}
</NextHead>
<ReduxProvider store={redux}>
<PageContext.Provider value={{ activePage, pages, versions: pageProps.versions }}>
<StylesProvider jss={jss}>
<ThemeProvider>{children}</ThemeProvider>
</StylesProvider>
</PageContext.Provider>
<LanguageNegotiation />
<Analytics />
</ReduxProvider>
<GoogleAnalytics key={router.route} />
</React.Fragment>
);
}

AppWrapper.propTypes = {
children: PropTypes.node.isRequired,
pageProps: PropTypes.object.isRequired,
};

export default function MyApp(props) {
const { Component, pageProps } = props;

return (
<AppWrapper pageProps={pageProps}>
<Component {...pageProps} />
</AppWrapper>
);
}

MyApp.propTypes = {
Component: PropTypes.elementType.isRequired,
pageProps: PropTypes.object.isRequired,
};

MyApp.getInitialProps = async ({ ctx, Component }) => {
let pageProps = {};

if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}

return {
pageProps: {
userLanguage: ctx.query.userLanguage || 'en',
...pageProps,
},
};
};

// Track fraction of actual events to prevent exceeding event quota.
// Filter sessions instead of individual events so that we can track multiple metrics per device.
const disableWebVitalsReporting = Math.random() > 0.0001;
export function reportWebVitals({ id, name, label, value }) {
if (disableWebVitalsReporting) {
return;
}

window.ga('send', 'event', {
eventCategory: label === 'web-vital' ? 'Web Vitals' : 'Next.js custom metric',
eventAction: name,
eventValue: Math.round(name === 'CLS' ? value * 1000 : value), // values must be integers
eventLabel: id, // id unique to current page load
nonInteraction: true, // avoids affecting bounce rate.
});
}
Loading