From 3464dc514478ebdd9f9bacc91bd8554d6ca3f269 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 24 Nov 2020 18:04:36 -0800 Subject: [PATCH] init --- .gitignore | 2 + README.md | 38 + debug.log | 20 + frontend/.gitignore | 34 + frontend/README.md | 41 + frontend/components/Inventory.tsx | 106 + frontend/components/Turtle.tsx | 140 + frontend/components/TurtleSwitcher.tsx | 15 + frontend/components/World.tsx | 242 ++ frontend/next-env.d.ts | 2 + frontend/package.json | 35 + frontend/pages/_app.tsx | 36 + frontend/pages/_document.tsx | 82 + frontend/pages/index.tsx | 159 + frontend/public/otherturtle.blend | Bin 0 -> 805608 bytes frontend/public/otherturtle.glb | Bin 0 -> 15848 bytes frontend/public/otheruv.png | Bin 0 -> 12488 bytes frontend/public/turtle.blend | Bin 0 -> 829424 bytes frontend/public/turtle.glb | Bin 0 -> 29180 bytes frontend/public/uv.png | Bin 0 -> 3235 bytes frontend/src/theme.ts | 25 + frontend/tsconfig.json | 23 + frontend/yarn.lock | 5305 ++++++++++++++++++++++++ package.json | 34 + patches/carlo+0.9.46.patch | 15 + src/index.ts | 60 + src/turtle.ts | 265 ++ src/world.ts | 44 + tsconfig.json | 40 + tslint.json | 3 + turtle/startup.lua | 562 +++ world.json | 1 + yarn-error.log | 1319 ++++++ yarn.lock | 2035 +++++++++ 34 files changed, 10683 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 debug.log create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/components/Inventory.tsx create mode 100644 frontend/components/Turtle.tsx create mode 100644 frontend/components/TurtleSwitcher.tsx create mode 100644 frontend/components/World.tsx create mode 100644 frontend/next-env.d.ts create mode 100644 frontend/package.json create mode 100644 frontend/pages/_app.tsx create mode 100644 frontend/pages/_document.tsx create mode 100644 frontend/pages/index.tsx create mode 100644 frontend/public/otherturtle.blend create mode 100644 frontend/public/otherturtle.glb create mode 100644 frontend/public/otheruv.png create mode 100755 frontend/public/turtle.blend create mode 100644 frontend/public/turtle.glb create mode 100644 frontend/public/uv.png create mode 100644 frontend/src/theme.ts create mode 100644 frontend/tsconfig.json create mode 100644 frontend/yarn.lock create mode 100644 package.json create mode 100644 patches/carlo+0.9.46.patch create mode 100644 src/index.ts create mode 100644 src/turtle.ts create mode 100644 src/world.ts create mode 100644 tsconfig.json create mode 100644 tslint.json create mode 100644 turtle/startup.lua create mode 100644 world.json create mode 100644 yarn-error.log create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..491fc35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +lib diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d645d6 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# TODO + +✓ Place blocks +✓ Break blocks +- Craft items +✓ Refuel +✓ Display current fuel level +✓ Show block name on mouse over +✓ Drop items +✓ Change inventory slot +- Pathfinding? +✓ Yoink items from chests +* Move items in inventory +- Equip items +- Interface with peripherals? +✓ Place signs + +# TURTLE MATERIALS +- 7 iron ingots +- 2 logs +- 7 smooth stone +- 1 redstone +- 1 glass pane + + +# GAMBIT MAIN PLAN + +1. Get ludwig to set up a single turtle. Ideally in town or a central area. +2. Preparation. Mine for coal and resources to self-replicate. +3. Advertisement. Start placing "Turtles are People" signs on the server. +4. Contact Michael. Approach with turtle and: + - ask to talk on discord / twitter? + - link him to a screenshot of the UI + - roleplay as the turtle: ask for fuel or something + - obfuscate code and add twitter / discord as comment +5. Michael gets Abe to whitelist me +6. ?????? +7. Profit \ No newline at end of file diff --git a/debug.log b/debug.log new file mode 100644 index 0000000..d7f41ef --- /dev/null +++ b/debug.log @@ -0,0 +1,20 @@ +[1120/183326.479:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1120/204905.802:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1120/222506.283:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1120/234127.576:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1121/012540.708:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1121/021010.707:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1121/142532.002:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1121/143553.245:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1121/155937.617:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1121/162455.543:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1121/163833.780:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1121/171217.788:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1121/174932.629:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1121/181207.172:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1121/193659.416:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1121/201515.989:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1122/172427.761:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1122/190312.500:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1122/191337.607:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1122/195440.703:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..1437c53 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,34 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..0a93bb3 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,41 @@ +# TypeScript Next.js example + +This is a really simple project that shows the usage of Next.js with TypeScript. + +## Deploy your own + +Deploy the example using [Vercel](https://vercel.com): + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/vercel/next.js/tree/canary/examples/with-typescript) + +## How to use it? + +Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: + +```bash +npx create-next-app --example with-typescript with-typescript-app +# or +yarn create next-app --example with-typescript with-typescript-app +``` + +Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). + +## Notes + +This example shows how to integrate the TypeScript type system into Next.js. Since TypeScript is supported out of the box with Next.js, all we have to do is to install TypeScript. + +``` +npm install --save-dev typescript +``` + +To enable TypeScript's features, we install the type declarations for React and Node. + +``` +npm install --save-dev @types/react @types/react-dom @types/node +``` + +When we run `next dev` the next time, Next.js will start looking for any `.ts` or `.tsx` files in our project and builds it. It even automatically creates a `tsconfig.json` file for our project with the recommended settings. + +Next.js has built-in TypeScript declarations, so we'll get autocompletion for Next.js' modules straight away. + +A `type-check` script is also added to `package.json`, which runs TypeScript's `tsc` CLI in `noEmit` mode to run type-checking separately. You can then include this, for example, in your `test` scripts. diff --git a/frontend/components/Inventory.tsx b/frontend/components/Inventory.tsx new file mode 100644 index 0000000..515dcee --- /dev/null +++ b/frontend/components/Inventory.tsx @@ -0,0 +1,106 @@ +import Grid from '@material-ui/core/Grid'; +import Paper from '@material-ui/core/Paper'; +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; +import Tooltip from '@material-ui/core/Tooltip'; +import makeStyles from '@material-ui/core/styles/makeStyles'; +import React, { useState } from 'react'; +import { Turtle } from '../pages'; +import Typography from '@material-ui/core/Typography'; +import { hashCode } from './World'; +import Color from 'color'; + +const useStyles = makeStyles(() => ({ + + inventory: { + position: 'absolute', + top: 100, + left: 0, + background: '#252525', + height: 200, + width: 200, + zIndex: 10, + borderRadius: 5, + overflow: 'hidden' + }, + inventoryItem: { + width: '25%', + height: '25%', + '& .MuiPaper-root': { + height: '100%', + width: '100%', + border: '2px solid transparent', + '&.selected': { + borderColor: 'white' + } + }, + cursor: 'pointer' + } +})); + +const initialState = { + mouseX: null, + mouseY: null, +}; + +interface InventoryProps { + turtle: Turtle; +} + +export default function Inventory({ turtle }: InventoryProps) { + const classes = useStyles(); + const [state, setState] = useState<{ + mouseX: null | number; + mouseY: null | number; + }>(initialState); + const handleClick = (event: React.MouseEvent) => { + event.preventDefault(); + setState({ + mouseX: event.clientX - 2, + mouseY: event.clientY - 4, + }); + }; + + const handleClose = () => { + setState(initialState); + }; + return ( + + + Copy + Print + Highlight + Email + + { + turtle.inventory.map((item, i) => ( + + turtle.selectSlot(i + 1)}> + {item && + + {item.count} + + } + + + )) + } + + ); +} \ No newline at end of file diff --git a/frontend/components/Turtle.tsx b/frontend/components/Turtle.tsx new file mode 100644 index 0000000..c34dee1 --- /dev/null +++ b/frontend/components/Turtle.tsx @@ -0,0 +1,140 @@ +import React, { useMemo, useState, useRef } from 'react'; +import { Turtle, World, BlockDirection } from '../pages'; +import Button from '@material-ui/core/Button'; +import ButtonGroup, { ButtonGroupProps } from '@material-ui/core/ButtonGroup'; +import ArrowDownward from '@material-ui/icons/ArrowDownward'; +import ArrowUpward from '@material-ui/icons/ArrowUpward'; +import CircularProgress, { CircularProgressProps } from '@material-ui/core/CircularProgress'; +import Box from '@material-ui/core/Box'; +import Typography from '@material-ui/core/Typography'; +import { MuiThemeProvider, createMuiTheme, makeStyles } from '@material-ui/core/styles'; +import Inventory from './Inventory'; +import Dialog from '@material-ui/core/Dialog'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import TextField from '@material-ui/core/TextField'; +import DialogActions from '@material-ui/core/DialogActions/DialogActions'; +import TurtleSwitcher from './TurtleSwitcher'; + +export interface TurtlePageProps { + turtle: Turtle; + enabled: boolean; +} + +const useStyles = makeStyles(theme => ({ + toolbar: { + display: 'flex', + justifyContent: 'start', + alignItems: 'center', + background: '#252525', + height: 100, + width: '100%', + }, + groups: { + '&>*': { + marginLeft: theme.spacing(1), + marginRight: theme.spacing(1), + } + } +})); + +function CircularProgressWithLabel(props: CircularProgressProps) { + return ( + + + + {`${Math.round(props.value!)}%`} + + + ); +} + + +export default function TurtlePage({ turtle, enabled }: TurtlePageProps) { + const [signText, setSignText] = useState(null); + const currentSignDirection = useRef(BlockDirection.FORWARD); + const classes = useStyles({ enabled }); + + const placeBlock = (dir: BlockDirection) => { + if (turtle.inventory[turtle.selectedSlot - 1]?.name === 'minecraft:sign') { + currentSignDirection.current = dir; + setSignText(''); + } else { + turtle.place(dir); + } + } + + return ( + <> + setSignText(null)}> + Sign Text + + setSignText(ev.target.value)} variant="outlined" /> + + + + + + +
+ +
+ + + + + + + + + + + + + +
+ + +
+ + ); +} + +interface TurtleButtonGroupProps { + turtle: Turtle; + func: 'place' | 'dig' | 'drop' | 'suck'; + color: string; +} + +function ColoredButtonGroup({ groupColor, ...props }: { groupColor: string } & ButtonGroupProps) { + const theme = useMemo(() => createMuiTheme({ + palette: { + primary: { + main: groupColor + } + }, + }), [groupColor]); + return ( + + + + ); + +} + +function TurtleButtonGroup({ turtle, func, color }: TurtleButtonGroupProps) { + return ( + + + + + + ); +} diff --git a/frontend/components/TurtleSwitcher.tsx b/frontend/components/TurtleSwitcher.tsx new file mode 100644 index 0000000..e3934d9 --- /dev/null +++ b/frontend/components/TurtleSwitcher.tsx @@ -0,0 +1,15 @@ +import { MenuItem, Select } from '@material-ui/core'; +import { useContext } from 'react'; +import { TurtleContext } from '../pages'; + + +export default function TurtleSwitcher() { + const [index, setIndex, turtles] = useContext(TurtleContext); + return ( + + ) +} \ No newline at end of file diff --git a/frontend/components/World.tsx b/frontend/components/World.tsx new file mode 100644 index 0000000..e38263a --- /dev/null +++ b/frontend/components/World.tsx @@ -0,0 +1,242 @@ +import { Canvas, MeshProps, extend, useFrame, useThree, ReactThreeFiber, useLoader } from 'react-three-fiber'; +import { useRef, useState, useMemo, useEffect, Suspense, HTMLProps, RefObject, SetStateAction, Dispatch, useContext } from 'react'; +import { Mesh, BoxBufferGeometry, Vector3, Quaternion, Euler, Raycaster, Vector2, Object3D } from 'three'; +import { OrbitControls } from 'three-orbitcontrols-ts'; +// import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; +import { Turtle, TurtleContext, World } from '../pages'; +import useEventListener from '@use-it/event-listener'; +import Color from 'color'; +import Tooltip from '@material-ui/core/Tooltip'; + +extend({ OrbitControls }); +declare global { + namespace JSX { + interface IntrinsicElements { + orbitControls: ReactThreeFiber.Object3DNode + } + } +} + +export const hashCode = function (s: string): number { + return s.split("").reduce(function (a, b) { a = ((a << 5) - a) + b.charCodeAt(0); return a & a }, 0); +} + +function Controls({ target }: { target: [number, number, number] }) { + // const controls = useInterpolate('target', target);\ + const controls = useRef(null); + const { camera, gl } = useThree(); + useFrame(() => controls.current.update()); + return ( + + ); +} + +function useInterpolate(property: 'position' | 'target', position: [number, number, number], rotation?: [number, number, number]) { + const ref = useRef(null); + useFrame(() => { + if (ref.current) { + const current = ref.current[property]; + const newPos = current.lerp(new Vector3(position[0], position[1], position[2]), 0.3); + ref.current[property].x = newPos.x; + ref.current[property].y = newPos.y; + ref.current[property].z = newPos.z; + if (rotation) { + const currentR = ref.current.quaternion; + const targetR = new Quaternion(); + targetR.setFromEuler(new Euler(rotation[0], rotation[1], rotation[2])); + const newRot = currentR.slerp(targetR, 0.3); + ref.current.rotation.setFromQuaternion(newRot); + } + } + }); + return ref; +} + +function Model({ url, position, rotation }: { url: string, position: [number, number, number], rotation: [number, number, number] }) { + const GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader').GLTFLoader; + const obj = useLoader(GLTFLoader, url) as any; + const ref = useInterpolate('position', position, rotation); + return ; +} + +function OtherTurtles({ turtles }: { turtles: Turtle[] }) { + const GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader').GLTFLoader; + const obj = useLoader(GLTFLoader, "/otherturtle.glb") as any; + const [geometries, setGeometries] = useState([]); + // useEffect(() => { + // setGeometries(old => { + // old.push(obj.scene.clone(true)); + // old.push(obj.scene.clone(true)); + // // while(old.length < turtles.length) { + // // old.push(obj.scene.clone(true)); + // // } + // return [ + // obj.scene.clone(true), + // obj.scene.clone(true) + // ]; + // }) + // }, [obj, turtles.length]); + // console.log(geometries, turtles.length); + return ( + <> + {turtles.map((turtle, i) => )} + + ); +} + +function OtherTurtle({ obj, turtle }: { obj: any, turtle: Turtle }) { + const geom = useMemo(() => obj.scene.clone(true), []); + return ; +} + +function TooltipRaycaster({ mouse, setHovered }: { mouse: RefObject<{ x: number, y: number }>, setHovered: Dispatch> }) { + const { camera, scene, size } = useThree(); + const ray = useRef(null); + + useFrame(() => { + if (!ray.current || !mouse.current) return; + let pos = new Vector2(); + pos.x = (mouse.current.x / size.width) * 2 - 1; + pos.y = - (mouse.current.y / size.height) * 2 + 1; + + ray.current.setFromCamera(pos, camera); + var intersects = ray.current.intersectObjects(scene.children); + let object: Object3D | null = null; + for (let i = 0; i < intersects.length; i++) { + object = intersects[i].object; + if (object.name) break; + } + if (object) { + setHovered(object.name); + } else { + setHovered(''); + } + }); + + return ; +} + +export default function WorldRenderer({ turtle, world, disableEvents, ...props }: { turtle: Turtle, world: World, disableEvents: boolean } & HTMLProps) { + + const [, , turtles] = useContext(TurtleContext); + const position = useRef({ x: 0, y: 0 }); + const popperRef = useRef(null); + const [hovered, setHovered] = useState(''); + + const disableEventsRef = useRef(disableEvents); + useEffect(() => { + disableEventsRef.current = disableEvents; + }, [disableEvents]); + + useEventListener('keyup', (ev: KeyboardEvent) => { + if (disableEventsRef.current) return; + if (ev.code === 'KeyW') { + turtle.forward(); + } else if (ev.code === 'KeyA') { + turtle.turnLeft(); + } else if (ev.code === 'KeyS') { + turtle.back(); + } else if (ev.code === 'KeyD') { + turtle.turnRight(); + } else if (ev.code === 'Space') { + turtle.up(); + } else if (ev.code === 'ShiftLeft') { + turtle.down(); + } + }); + return ( + ({ + top: position.current.y + 100, + left: position.current.x, + right: position.current.x, + bottom: position.current.y + 100, + width: 0, + height: 0, + }), + } + }} + onMouseMove={(ev) => { + // console.log(scene); + // // mouse.x = (ev.clientX / size.width) * 2 - 1; + // // mouse.y = - (ev.clientY / size.height) * 2 + 1; + // ray.setFromCamera(mouse, camera) + // var intersects = ray.intersectObjects(scene.children); + // if (intersects.length > 0) { + // console.log(intersects[0].object.name); + // } else { + // console.log("Nothing"); + // } + // console.log(ev.clientY); + position.current = { x: ev.clientX, y: ev.clientY - 100 }; + if (popperRef.current) + popperRef.current.update(); + }} + > +
+ + + + + + + + {Object.keys(world).map(k => { + let positions = k.split(',').map(p => parseInt(p)) as [number, number, number]; + return + })} + + t.id !== turtle.id)} /> + + +
+
+ ) +} + +function Box(props: MeshProps & { color: string, name: string }) { + if (props.name === 'computercraft:turtle_expanded') return null; + // This reference will give us direct access to the mesh + const mesh = useRef() + + // Set up state for the hovered and active state + + // Rotate mesh every frame, this is outside of React without overhead + // useFrame(() => { + // if (mesh.current) mesh.current.rotation.x = mesh.current.rotation.y += 0.01 + // if (lines.current) lines.current.rotation.x = lines.current.rotation.y += 0.01 + // }) + + const geom = useMemo(() => new BoxBufferGeometry(1, 1, 1), []); + + return ( + <> + + + + + + + + + + ) +} \ No newline at end of file diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts new file mode 100644 index 0000000..7b7aa2c --- /dev/null +++ b/frontend/next-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..f7c6e45 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,35 @@ +{ + "name": "with-typescript", + "version": "1.0.0", + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start", + "type-check": "tsc" + }, + "dependencies": { + "@emotion/react": "^11.1.1", + "@emotion/server": "^11.0.0", + "@emotion/styled": "^11.0.0", + "@material-ui/core": "^4.11.0", + "@material-ui/icons": "^4.9.1", + "@types/color": "^3.0.1", + "@use-it/event-listener": "^0.1.6", + "babel-preset-env": "^1.7.0", + "color": "^3.1.3", + "next": "latest", + "react": "^16.12.0", + "react-dom": "^16.12.0", + "react-three-fiber": "^5.3.1", + "three": "^0.122.0", + "three-obj-loader-es6-module": "^1.0.1", + "three-orbitcontrols-ts": "git+https://git@github.com/nicolaspanel/three-orbitcontrols-ts.git" + }, + "devDependencies": { + "@types/node": "^12.12.21", + "@types/react": "^16.9.16", + "@types/react-dom": "^16.9.4", + "typescript": "4.0" + }, + "license": "MIT" +} diff --git a/frontend/pages/_app.tsx b/frontend/pages/_app.tsx new file mode 100644 index 0000000..893fb2d --- /dev/null +++ b/frontend/pages/_app.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import Head from 'next/head'; +import { AppProps } from 'next/app'; +import { ThemeProvider } from '@material-ui/core/styles'; +import { CacheProvider } from '@emotion/react'; +import CssBaseline from '@material-ui/core/CssBaseline'; +import createCache from '@emotion/cache'; +import theme from '../src/theme'; + +export const cache = createCache({ key: 'css' }); + +export default function MyApp(props: AppProps) { + const { Component, pageProps } = props; + + React.useEffect(() => { + // Remove the server-side injected CSS. + const jssStyles = document.querySelector('#jss-server-side'); + if (jssStyles) { + jssStyles.parentElement!.removeChild(jssStyles); + } + }, []); + + return ( + + + My page + + + + {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} + + + + + ); +} \ No newline at end of file diff --git a/frontend/pages/_document.tsx b/frontend/pages/_document.tsx new file mode 100644 index 0000000..d53205c --- /dev/null +++ b/frontend/pages/_document.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import Document, { Html, Head, Main, NextScript } from 'next/document'; +import { ServerStyleSheets } from '@material-ui/core/styles'; +import createEmotionServer from '@emotion/server/create-instance'; +import theme from '../src/theme'; +import { cache } from './_app'; + +const { extractCritical } = createEmotionServer(cache); + +export default class MyDocument extends Document { + render() { + return ( + + + {/* PWA primary color */} + + + + +
+ + + + ); + } +} + +// `getInitialProps` belongs to `_document` (instead of `_app`), +// it's compatible with static-site generation (SSG). +MyDocument.getInitialProps = async (ctx) => { + // Resolution order + // + // On the server: + // 1. app.getInitialProps + // 2. page.getInitialProps + // 3. document.getInitialProps + // 4. app.render + // 5. page.render + // 6. document.render + // + // On the server with error: + // 1. document.getInitialProps + // 2. app.render + // 3. page.render + // 4. document.render + // + // On the client + // 1. app.getInitialProps + // 2. page.getInitialProps + // 3. app.render + // 4. page.render + + // Render app and page and get the context of the page with collected side effects. + const sheets = new ServerStyleSheets(); + const originalRenderPage = ctx.renderPage; + + ctx.renderPage = () => + originalRenderPage({ + enhanceApp: (App) => (props) => sheets.collect(), + }); + + const initialProps = await Document.getInitialProps(ctx); + const styles = extractCritical(initialProps.html); + + return { + ...initialProps, + // Styles fragment is rendered after the app and page rendering finish. + styles: [ + ...React.Children.toArray(initialProps.styles), + sheets.getStyleElement(), +