Merge branch 'main' of github.com:pmret/website into main

This commit is contained in:
Alex Bates 2021-03-04 12:32:26 +00:00
commit ca87ffe3f8
No known key found for this signature in database
GPG Key ID: 7531C5E1D6B1CA9A
11 changed files with 257 additions and 70 deletions

75
src/Contributors.jsx Normal file
View File

@ -0,0 +1,75 @@
import React, { useState } from "react"
import { createPortal } from "react-dom"
import clsx from "clsx"
import cdownURL from "./cdown.svg"
function CDown() {
return <img src={cdownURL} style={{ width: "1em", height: "1em", verticalAlign: "-5px" }}/>
}
const contributors = [
{
name: "Ethan",
avatar: "https://avatars2.githubusercontent.com/u/2985314?s=400",
url: "https://github.com/ethteck",
description: <div>
</div>,
},
{
name: "stuckpixel",
avatar: "https://avatars3.githubusercontent.com/u/3634616?s=400",
url: "https://github.com/pixel-stuck",
description: <div>
</div>,
},
{
name: "alex",
avatar: "https://avatars3.githubusercontent.com/u/9429556?s=400",
url: "https://github.com/nanaian",
description: <div>
</div>,
},
]
export default function Contributors({ captionPortal }) {
const [selected, setSelected] = useState(0)
const { name, description, url } = contributors[selected]
return <div style={{ display: "flex", flexDirection: "row", width: "100%", alignItems: "center", justifyContent: "center" }}>
<div style={{ padding: ".5em", display: "flex", flexDirection: "column", alignItems: "center" }}>
<div className="avatars">
{contributors.map((contributor, i) => {
let t = (Math.PI * 2) * ((i - selected) / contributors.length) + Math.PI/2
let x = 150 * Math.cos(t) + 200
let y = 140 * Math.sin(t) + 200
return <img
key={contributor.name}
className={clsx("avatar", { inactive: i !== selected })}
onClick={() => setSelected(i)}
src={contributor.avatar}
alt={contributor.name}
style={{ top: y + "px", left: x + "px", zIndex: y }}
/>
})}
</div>
<button className="teal" style={{ width: "80%", cursor: "pointer" }} onClick={() => window.open(url)}>
{name}
</button>
</div>
<div>
<div className="shadow-box" style={{ width: "27em", height: "20em" }}>
<div className="shadow-box-inner" style={{ backgroundImage: `url(http://placekitten.com/800/${600 + Math.floor(Math.random() * 100)})`, backgroundSize: "cover" }}>
</div>
</div>
</div>
{captionPortal.current && createPortal(<div>
{description}
</div>, captionPortal.current)}
</div>
}

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from "react" import React, { useState, useEffect } from "react"
import { createPortal } from "react-dom" import { createPortal } from "react-dom"
import { Area, XAxis, YAxis, AreaChart, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts" import { Area, XAxis, YAxis, AreaChart, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts"
import { scalePow } from "d3-scale" import { scalePow } from "d3-scale"
@ -19,11 +19,16 @@ const csvVersions = {
}, },
} }
async function fetchData() { const colors = {
const csv = await fetch("https://papermar.io/reports/progress.csv") yellow: { stroke: "#e3ac34", fill: "#edc97e" },
green: { stroke: "#40e334", fill: "#91eb7f" },
}
async function fetchData(version) {
const csv = await fetch(`https://papermar.io/reports/progress_${version}.csv`)
.then(response => response.text()) .then(response => response.text())
return csv const rows = csv
.split("\n") .split("\n")
.filter(row => row.length) .filter(row => row.length)
.map(row => { .map(row => {
@ -35,29 +40,29 @@ async function fetchData() {
obj[key] = transform(data.shift()) obj[key] = transform(data.shift())
} }
obj.percentBytes = Math.round((obj.matchingBytes / obj.totalBytes) * 100)
return obj return obj
}) })
const latest = rows[rows.length - 1]
for (const row of rows) {
row.percentBytes = (row.matchingBytes / latest.totalBytes) * 100
}
return rows
} }
let cachedData = null export default function ProgressPane({ captionPortal, nonce, color, version }) {
const [data, setData] = useState([])
export default function ProgressPane({ captionPortal, nonce }) {
const [data, setData] = useState(cachedData)
useEffect(() => { useEffect(() => {
fetchData() fetchData(version)
.then(data => { .then(data => {
cachedData = data setData(data)
setData(cachedData)
}) })
}, []) }, [version])
// TODO: cute spin animation when data loads
return <div style={{ display: "flex", flexDirection: "column", width: "100%" }}> return <div style={{ display: "flex", flexDirection: "column", width: "100%" }}>
{data && <DataView data={data} nonce={nonce} captionPortal={captionPortal}/>} {data && <DataView data={data} nonce={nonce} captionPortal={captionPortal} color={color}/>}
{!data && "Loading..."} {!data && "Loading..."}
</div> </div>
} }
@ -73,9 +78,10 @@ const monthDates = []
} }
} }
function DataView({ data, captionPortal, nonce }) { function DataView({ data, captionPortal, nonce, color }) {
const latest = data[data.length - 1] const latest = data[data.length - 1]
const oldest = data[0] const oldest = data[0]
const { stroke, fill } = colors[color]
const [selectedEntry, setSelectedEntry] = useState(latest) const [selectedEntry, setSelectedEntry] = useState(latest)
@ -89,18 +95,9 @@ function DataView({ data, captionPortal, nonce }) {
return <span/> return <span/>
} }
const maxPercent = Math.ceil(latest.percentBytes / 25) * 25 const maxPercent = latest ? Math.ceil(latest.percentBytes / 25) * 25 : 25
return <> return <>
{/*<table width="250" className="outline-invert">
<tbody>
<tr>
<td>Matched</td>
<td className="thin align-right">{Math.round((latest.matchingBytes / latest.totalBytes) * 10000) / 100}%</td>
</tr>
</tbody>
</table>*/}
<div className="shadow-box flex-grow"> <div className="shadow-box flex-grow">
<div className="shadow-box-inner" style={{ paddingRight: ".7em", paddingTop: ".7em", "--text-outline": "transparent", background: "#e2e1d8" }}> <div className="shadow-box-inner" style={{ paddingRight: ".7em", paddingTop: ".7em", "--text-outline": "transparent", background: "#e2e1d8" }}>
<div className="progress-chart"> <div className="progress-chart">
@ -115,8 +112,8 @@ function DataView({ data, captionPortal, nonce }) {
type="linear" type="linear"
dataKey="percentBytes" dataKey="percentBytes"
unit="%" unit="%"
stroke="#e3ac34" strokeWidth={2} stroke={stroke} strokeWidth={2}
fill="#edc97e" fill={fill}
dot={true} dot={true}
isAnimationActive={false} isAnimationActive={false}
/> />
@ -127,7 +124,7 @@ function DataView({ data, captionPortal, nonce }) {
</div> </div>
</div> </div>
<button className="shadow-box-title yellow"> <button className={"shadow-box-title " + color}>
{selectedEntry ? formatTimestamp(selectedEntry.timestamp, { {selectedEntry ? formatTimestamp(selectedEntry.timestamp, {
dateStyle: "long", dateStyle: "long",
timeStyle: "short", timeStyle: "short",
@ -162,7 +159,7 @@ function EntryInfo({ entry, isLatest }) {
/*const [commitMessage, setCommitMessage] = useState(null) /*const [commitMessage, setCommitMessage] = useState(null)
useEffect(async () => { useEffect(async () => {
fetch(`https://api.github.com/repos/ethteck/papermario/commits/${entry.commit}`) fetch(`https://api.github.com/repos/pmret/papermario/commits/${entry.commit}`)
.then(resp => resp.json()) .then(resp => resp.json())
.then(resp => { .then(resp => {
setCommitMessage(resp.commit.message.split("\n")[0]) setCommitMessage(resp.commit.message.split("\n")[0])
@ -170,7 +167,7 @@ function EntryInfo({ entry, isLatest }) {
}, [entry.commit])*/ }, [entry.commit])*/
return <div> return <div>
<a href={`https://github.com/ethteck/papermario/commit/${entry.commit}`}> <a href={`https://github.com/pmret/papermario/commit/${entry.commit}`}>
{entry.commit.substr(0, 8)} {entry.commit.substr(0, 8)}
</a> </a>
{isLatest && " (latest)"} {isLatest && " (latest)"}
@ -179,8 +176,8 @@ function EntryInfo({ entry, isLatest }) {
<tr> <tr>
<td width="200">Matched</td> <td width="200">Matched</td>
<td className="thin align-right"> <td className="thin align-right">
{Math.round((entry.matchingBytes / entry.totalBytes) * 10000) / 100}% bytes {Math.round(entry.percentBytes * 100) / 100}% bytes
({entry.matchingFuncs}/{entry.totalFuncs} split functions) ({entry.matchingFuncs}/{entry.totalFuncs} functions)
</td> </td>
</tr> </tr>
</tbody> </tbody>

BIN
src/bg/green-checker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

BIN
src/bg/mrn-clouds.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

BIN
src/bg/mrn-toad-town.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
src/bg/teal-checker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

19
src/cdown.svg Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="62.695" height="62.695" version="1.1" viewBox="0 0 16.588 16.588" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" x1="72.973" x2="60.528" y1="266.26" y2="253.34" gradientUnits="userSpaceOnUse">
<stop stop-color="#a98723" offset="0"/>
<stop stop-color="#e7ce31" offset=".2617"/>
<stop stop-color="#e7ce31" offset=".66415"/>
<stop stop-color="#f8efd5" offset="1"/>
</linearGradient>
</defs>
<g transform="translate(-58.657 -251.24)">
<g>
<circle cx="66.951" cy="259.53" r="7.5003" fill="url(#a)" stroke="#623826" stroke-linecap="square" stroke-width="1.5875" style="paint-order:normal"/>
<path d="m63.138 257.4h7.7581l0.02305 2.018-3.9191 4.0028-3.862-3.959z" fill="none" stroke="#986c2b" stroke-width="2.0361"/>
<path d="m63.138 257.4h7.7581l0.02305 2.018-3.9191 4.0028-3.862-3.959z" fill="none" stroke="#fff" stroke-width="1.3616"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -7,7 +7,7 @@ html {
--dark: #313131; --dark: #313131;
--light: #d6d6ce; --light: #d6d6ce;
font-size: 28px; font-size: 32px;
font-family: "Paper Mario Dialog Redesigned", sans-serif; font-family: "Paper Mario Dialog Redesigned", sans-serif;
font-weight: 700; font-weight: 700;
line-height: 1.2; line-height: 1.2;
@ -15,11 +15,35 @@ html {
color: var(--light); color: var(--light);
--text-outline: var(--dark); --text-outline: var(--dark);
background: #090942; /* TODO: dynamic star bg */ background-color: #002c02;
background-image: url(bg/mrn-toad-town.png);
background-size: cover;
background-repeat: no-repeat;
overflow: hidden; overflow: hidden;
} }
body {
height: 100vh;
margin: 0;
padding: 0;
background-image: url(bg/mrn-clouds.png);
background-repeat: repeat-x;
background-size: contain;
animation: clouds linear 30s infinite normal;
}
@keyframes clouds {
from {
background-position: 0 0;
}
to {
background-position: 100vw 0;
}
}
* { * {
box-sizing: border-box; box-sizing: border-box;
@ -27,6 +51,10 @@ html {
text-shadow: var(--text-outline) 3px 0px 0px, var(--text-outline) 2.83487px 0.981584px 0px, var(--text-outline) 2.35766px 1.85511px 0px, var(--text-outline) 1.62091px 2.52441px 0px, var(--text-outline) 0.705713px 2.91581px 0px, var(--text-outline) -0.287171px 2.98622px 0px, var(--text-outline) -1.24844px 2.72789px 0px, var(--text-outline) -2.07227px 2.16926px 0px, var(--text-outline) -2.66798px 1.37182px 0px, var(--text-outline) -2.96998px 0.42336px 0px, var(--text-outline) -2.94502px -0.571704px 0px, var(--text-outline) -2.59586px -1.50383px 0px, var(--text-outline) -1.96093px -2.27041px 0px, var(--text-outline) -1.11013px -2.78704px 0px, var(--text-outline) -0.137119px -2.99686px 0px, var(--text-outline) 0.850987px -2.87677px 0px, var(--text-outline) 1.74541px -2.43999px 0px, var(--text-outline) 2.44769px -1.73459px 0px, var(--text-outline) 2.88051px -0.838247px 0px; text-shadow: var(--text-outline) 3px 0px 0px, var(--text-outline) 2.83487px 0.981584px 0px, var(--text-outline) 2.35766px 1.85511px 0px, var(--text-outline) 1.62091px 2.52441px 0px, var(--text-outline) 0.705713px 2.91581px 0px, var(--text-outline) -0.287171px 2.98622px 0px, var(--text-outline) -1.24844px 2.72789px 0px, var(--text-outline) -2.07227px 2.16926px 0px, var(--text-outline) -2.66798px 1.37182px 0px, var(--text-outline) -2.96998px 0.42336px 0px, var(--text-outline) -2.94502px -0.571704px 0px, var(--text-outline) -2.59586px -1.50383px 0px, var(--text-outline) -1.96093px -2.27041px 0px, var(--text-outline) -1.11013px -2.78704px 0px, var(--text-outline) -0.137119px -2.99686px 0px, var(--text-outline) 0.850987px -2.87677px 0px, var(--text-outline) 1.74541px -2.43999px 0px, var(--text-outline) 2.44769px -1.73459px 0px, var(--text-outline) 2.88051px -0.838247px 0px;
} }
p {
margin: 0;
}
a:any-link { a:any-link {
color: #3796ff; color: #3796ff;
--text-outline: #d3e5f9; --text-outline: #d3e5f9;
@ -38,7 +66,6 @@ button {
border-radius: 100em; border-radius: 100em;
padding: 0 .4em .2em .4em; padding: 0 .4em .2em .4em;
cursor: pointer;
filter: brightness(1.0); filter: brightness(1.0);
will-change: filter, box-shadow; will-change: filter, box-shadow;
@ -50,6 +77,9 @@ button {
border-right: 2px solid; border-right: 2px solid;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
user-select: none;
text-align: center;
} }
button.red { button.red {
@ -84,6 +114,22 @@ button.github {
border-right-color: #171515; border-right-color: #171515;
} }
button.teal {
border-top-color: #22eec9;
border-left-color: #22eec9;
background: #14a98e;
border-bottom-color: #076d5a;
border-right-color: #076d5a;
}
button.green {
border-top-color: #68ff51;
border-left-color: #68ff51;
background: #47b836;
border-bottom-color: #2a791e;
border-right-color: #2a791e;
}
button.inactive { button.inactive {
filter: brightness(0.6); filter: brightness(0.6);
} }
@ -95,6 +141,7 @@ button:hover {
.tab { .tab {
margin-right: .25em; margin-right: .25em;
cursor: pointer;
} }
#container { #container {
@ -108,11 +155,10 @@ button:hover {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
width: 1400px; width: 80%;
max-width: calc(100vw - 10vh); max-width: 95%;
height: 80%;
height: 1200px; max-height: 950%;
max-height: calc(100vh - 10vw);
} }
nav { nav {
@ -137,6 +183,7 @@ main {
border-radius: 1rem; border-radius: 1rem;
padding: 1em; padding: 1em;
/*padding-bottom: 2.5em;*/
overflow-y: auto; overflow-y: auto;
@ -148,6 +195,10 @@ main {
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.3); box-shadow: 0 2px 16px rgba(0, 0, 0, 0.3);
} }
main > :last-child {
padding-bottom: 1.5em;
}
main.red { main.red {
background-color: #e2b6b3; background-color: #e2b6b3;
background-image: url(bg/red-checker.png); background-image: url(bg/red-checker.png);
@ -158,6 +209,16 @@ main.yellow {
background-image: url(bg/yellow-checker.png); background-image: url(bg/yellow-checker.png);
} }
main.teal {
background-color: #cad9d6;
background-image: url(bg/teal-checker.png);
}
main.green {
background-color: #68ff51;
background-image: url(bg/green-checker.png);
}
main > * { main > * {
image-rendering: initial; image-rendering: initial;
} }
@ -231,14 +292,18 @@ button.shadow-box-title {
background-repeat: repeat; background-repeat: repeat;
background-size: 48px; background-size: 48px;
background-position: center; background-position: center;
-ms-interpolation-mode: nearest-neighbor;
image-rendering: optimizespeed;
image-rendering: crisp-edges;
image-rendering: pixelated; image-rendering: pixelated;
border-radius: 1rem; border-radius: 1rem;
width: 85%; width: 85%;
height: 3rem; height: 4rem;
padding: 8px 16px; padding: .5em 1em;
margin-top: -14px; margin-top: -2em;
z-index: 1; z-index: 1;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
@ -247,7 +312,7 @@ button.shadow-box-title {
.progress-chart { .progress-chart {
flex: 1; flex: 1;
font-size: 12px; font-size: 18px;
overflow: hidden; overflow: hidden;
user-select: none; user-select: none;
@ -269,3 +334,34 @@ button.shadow-box-title {
.flex-spacer { width: 1em; height: 1em } .flex-spacer { width: 1em; height: 1em }
.align-right { text-align: right } .align-right { text-align: right }
.avatars {
position: relative;
width: 400px;
height: 400px;
margin-bottom: 2em;
}
.avatar {
border-radius: 100em;
position: absolute;
will-change: filter, top, left;
transition: 200ms filter, 200ms top, 200ms left;
cursor: pointer;
width: 5em;
height: 5em;
transform: translate(-50%, -50%);
}
.avatar.inactive {
filter: brightness(0.7) saturate(0.7);
}
.avatar:hover {
filter: brightness(1.0);
}

View File

@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"/> <meta charset="utf-8"/>
<title>Paper Mario Reverse Engineering</title> <title>Paper Mario Decompilation</title>
<link rel="stylesheet" href="index.css"/> <link rel="stylesheet" href="index.css"/>
<script async defer src="main.jsx"></script> <script async defer src="main.jsx"></script>

View File

@ -1,14 +0,0 @@
import moment from "moment"
import("./progress.js").then(async ({ fetchData, functionsChart }) => {
const data = await fetchData()
functionsChart(data, document.getElementById("progress-chart"))
const first = data[0]
const latest = data[data.length - 1]
document.getElementById("matched-rom-percent").innerText = Math.round((latest.matchingBytes / latest.totalBytes) * 10000) / 100 + "% matched" // TODO: include data
document.getElementById("functions-ratio").innerText = latest.matchingFuncs + "/" + latest.totalFuncs
document.getElementById("play-time").innerText = moment(latest.timestamp).from(first.timestamp, true)
}).catch(console.error)

View File

@ -3,6 +3,7 @@ import ReactDOM from "react-dom"
import clsx from "clsx" import clsx from "clsx"
import ProgressPane from "./ProgressPane" import ProgressPane from "./ProgressPane"
import Contributors from "./Contributors"
const tabs = [ const tabs = [
{ {
@ -11,16 +12,28 @@ const tabs = [
color: "red", color: "red",
pane: () => <div> pane: () => <div>
<p className="outline-invert"> <p className="outline-invert">
Welcome to the Paper Mario Reverse-Engineering website! Welcome to the Paper Mario decompilation site!
</p> </p>
</div>, </div>,
}, },
{ {
slug: "/progress", slug: "/progress-us",
name: "Progress", name: "Progress (US)",
color: "yellow", color: "yellow",
pane: (props) => <ProgressPane {...props}/> pane: (props) => <ProgressPane version="us" color="yellow" {...props}/>
}, },
{
slug: "/progress-jp",
name: "Progress (JP)",
color: "green",
pane: (props) => <ProgressPane version="jp" color="green" {...props}/>
},
/*{
slug: "/contributors",
name: "Contributors",
color: "teal",
pane: (props) => <Contributors {...props}/>
},*/
] ]
let routedTabIndex = tabs.findIndex(tab => tab.slug === document.location.pathname) let routedTabIndex = tabs.findIndex(tab => tab.slug === document.location.pathname)
@ -33,7 +46,7 @@ function App() {
const [flip, setFlip] = useState(false) const [flip, setFlip] = useState(false)
const pane = useRef() const pane = useRef()
function switchToTab(index) { function switchToTab(index, pushState) {
if (index === paneIndex || index === tabIndex) return if (index === paneIndex || index === tabIndex) return
console.info("switching to tab", index) console.info("switching to tab", index)
@ -41,6 +54,7 @@ function App() {
setRotation(rotation - 180) setRotation(rotation - 180)
setTabIndex(index) setTabIndex(index)
if (pushState)
history.pushState(null, tabs[index].name, tabs[index].slug) history.pushState(null, tabs[index].name, tabs[index].slug)
setTimeout(() => { setTimeout(() => {
@ -51,7 +65,7 @@ function App() {
useEffect(() => { useEffect(() => {
function listener() { function listener() {
switchToTab(tabs.findIndex(tab => tab.slug === document.location.pathname)) switchToTab(tabs.findIndex(tab => tab.slug === window.location.pathname), false)
} }
window.addEventListener("popstate", listener) window.addEventListener("popstate", listener)
@ -67,7 +81,7 @@ function App() {
key={tab.name} key={tab.name}
className={clsx("tab", tab.color, { "inactive": index !== tabIndex })} className={clsx("tab", tab.color, { "inactive": index !== tabIndex })}
onClick={() => { onClick={() => {
switchToTab(index) switchToTab(index, true)
}} }}
> >
{tab.name} {tab.name}