Versioning (#1)

* progress caption wording

* initial party impl

* rm old index.js

* many things

* scrolling clouds in background
This commit is contained in:
alex 2021-02-22 09:57:38 +00:00 committed by GitHub
parent 63b528c2e7
commit 66142b2f6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 246 additions and 55 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 { Area, XAxis, YAxis, AreaChart, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts"
import { scalePow } from "d3-scale"
@ -19,8 +19,13 @@ const csvVersions = {
},
}
async function fetchData() {
const csv = await fetch("https://papermar.io/reports/progress.csv")
const colors = {
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())
const rows = csv
@ -46,23 +51,18 @@ async function fetchData() {
return rows
}
let cachedData = null
export default function ProgressPane({ captionPortal, nonce }) {
const [data, setData] = useState(cachedData)
export default function ProgressPane({ captionPortal, nonce, color, version }) {
const [data, setData] = useState([])
useEffect(() => {
fetchData()
fetchData(version)
.then(data => {
cachedData = data
setData(cachedData)
setData(data)
})
}, [])
// TODO: cute spin animation when data loads
}, [version])
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..."}
</div>
}
@ -78,9 +78,10 @@ const monthDates = []
}
}
function DataView({ data, captionPortal, nonce }) {
function DataView({ data, captionPortal, nonce, color }) {
const latest = data[data.length - 1]
const oldest = data[0]
const { stroke, fill } = colors[color]
const [selectedEntry, setSelectedEntry] = useState(latest)
@ -94,7 +95,7 @@ function DataView({ data, captionPortal, nonce }) {
return <span/>
}
const maxPercent = Math.ceil(latest.percentBytes / 25) * 25
const maxPercent = latest ? Math.ceil(latest.percentBytes / 25) * 25 : 25
return <>
<div className="shadow-box flex-grow">
@ -111,8 +112,8 @@ function DataView({ data, captionPortal, nonce }) {
type="linear"
dataKey="percentBytes"
unit="%"
stroke="#e3ac34" strokeWidth={2}
fill="#edc97e"
stroke={stroke} strokeWidth={2}
fill={fill}
dot={true}
isAnimationActive={false}
/>
@ -123,7 +124,7 @@ function DataView({ data, captionPortal, nonce }) {
</div>
</div>
<button className="shadow-box-title yellow">
<button className={"shadow-box-title " + color}>
{selectedEntry ? formatTimestamp(selectedEntry.timestamp, {
dateStyle: "long",
timeStyle: "short",
@ -175,7 +176,7 @@ function EntryInfo({ entry, isLatest }) {
<tr>
<td width="200">Matched</td>
<td className="thin align-right">
{Math.round((entry.percentBytes) * 100) / 100}%
{Math.round(entry.percentBytes * 100) / 100}% bytes
({entry.matchingFuncs}/{entry.totalFuncs} functions)
</td>
</tr>

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;
--light: #d6d6ce;
font-size: 28px;
font-size: 32px;
font-family: "Paper Mario Dialog Redesigned", sans-serif;
font-weight: 700;
line-height: 1.2;
@ -15,11 +15,35 @@ html {
color: var(--light);
--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;
}
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;
@ -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;
}
p {
margin: 0;
}
a:any-link {
color: #3796ff;
--text-outline: #d3e5f9;
@ -38,7 +66,6 @@ button {
border-radius: 100em;
padding: 0 .4em .2em .4em;
cursor: pointer;
filter: brightness(1.0);
will-change: filter, box-shadow;
@ -50,6 +77,9 @@ button {
border-right: 2px solid;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
user-select: none;
text-align: center;
}
button.red {
@ -84,6 +114,22 @@ button.github {
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 {
filter: brightness(0.6);
}
@ -95,6 +141,7 @@ button:hover {
.tab {
margin-right: .25em;
cursor: pointer;
}
#container {
@ -108,11 +155,10 @@ button:hover {
flex-direction: column;
align-items: center;
width: 1400px;
max-width: calc(100vw - 10vh);
height: 1200px;
max-height: calc(100vh - 10vw);
width: 1600px;
max-width: 95%;
height: 1100px;
max-height: 950%;
}
nav {
@ -137,6 +183,7 @@ main {
border-radius: 1rem;
padding: 1em;
/*padding-bottom: 2.5em;*/
overflow-y: auto;
@ -148,6 +195,10 @@ main {
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.3);
}
main > :last-child {
padding-bottom: 1.5em;
}
main.red {
background-color: #e2b6b3;
background-image: url(bg/red-checker.png);
@ -158,6 +209,16 @@ main.yellow {
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 > * {
image-rendering: initial;
}
@ -231,14 +292,18 @@ button.shadow-box-title {
background-repeat: repeat;
background-size: 48px;
background-position: center;
-ms-interpolation-mode: nearest-neighbor;
image-rendering: optimizespeed;
image-rendering: crisp-edges;
image-rendering: pixelated;
border-radius: 1rem;
width: 85%;
height: 3rem;
height: 4rem;
padding: 8px 16px;
margin-top: -14px;
padding: .5em 1em;
margin-top: -2em;
z-index: 1;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
@ -247,7 +312,7 @@ button.shadow-box-title {
.progress-chart {
flex: 1;
font-size: 12px;
font-size: 18px;
overflow: hidden;
user-select: none;
@ -269,3 +334,34 @@ button.shadow-box-title {
.flex-spacer { width: 1em; height: 1em }
.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">
<head>
<meta charset="utf-8"/>
<title>Paper Mario Reverse Engineering</title>
<title>Paper Mario Decompilation</title>
<link rel="stylesheet" href="index.css"/>
<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 ProgressPane from "./ProgressPane"
import Contributors from "./Contributors"
const tabs = [
{
@ -11,16 +12,28 @@ const tabs = [
color: "red",
pane: () => <div>
<p className="outline-invert">
Welcome to the Paper Mario Reverse-Engineering website!
Welcome to the Paper Mario decompilation site!
</p>
</div>,
},
{
slug: "/progress",
name: "Progress",
slug: "/progress-us",
name: "Progress (US)",
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)
@ -33,7 +46,7 @@ function App() {
const [flip, setFlip] = useState(false)
const pane = useRef()
function switchToTab(index) {
function switchToTab(index, pushState) {
if (index === paneIndex || index === tabIndex) return
console.info("switching to tab", index)
@ -41,7 +54,8 @@ function App() {
setRotation(rotation - 180)
setTabIndex(index)
history.pushState(null, tabs[index].name, tabs[index].slug)
if (pushState)
history.pushState(null, tabs[index].name, tabs[index].slug)
setTimeout(() => {
setPaneIndex(index)
@ -51,7 +65,7 @@ function App() {
useEffect(() => {
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)
@ -67,7 +81,7 @@ function App() {
key={tab.name}
className={clsx("tab", tab.color, { "inactive": index !== tabIndex })}
onClick={() => {
switchToTab(index)
switchToTab(index, true)
}}
>
{tab.name}