Prettify everything
This commit is contained in:
@ -1,6 +1,8 @@
|
||||
import React from "react"
|
||||
import contentHTML from "./About.md"
|
||||
import React from "react";
|
||||
import contentHTML from "./About.md";
|
||||
|
||||
const About = () => <div className="wrapper" dangerouslySetInnerHTML={{ __html: contentHTML }} />
|
||||
const About = () => (
|
||||
<div className="wrapper" dangerouslySetInnerHTML={{ __html: contentHTML }} />
|
||||
);
|
||||
|
||||
export default About
|
||||
export default About;
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import React from "react"
|
||||
import { HashRouter } from "react-router-dom"
|
||||
import Layout from "./Layout"
|
||||
import React from "react";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
import Layout from "./Layout";
|
||||
|
||||
const App = () => (
|
||||
<HashRouter>
|
||||
<Layout />
|
||||
</HashRouter>
|
||||
)
|
||||
);
|
||||
|
||||
export default App
|
||||
export default App;
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import React from "react"
|
||||
import contentHTML from "./Home.md"
|
||||
import React from "react";
|
||||
import contentHTML from "./Home.md";
|
||||
|
||||
const Home = () => <div className="wrapper" dangerouslySetInnerHTML={{ __html: contentHTML }} />
|
||||
const Home = () => (
|
||||
<div className="wrapper" dangerouslySetInnerHTML={{ __html: contentHTML }} />
|
||||
);
|
||||
|
||||
export default Home
|
||||
export default Home;
|
||||
|
||||
@ -1,78 +1,81 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { Motion, spring } from "react-motion"
|
||||
import { Switch, Route, Link, withRouter } from "react-router-dom"
|
||||
import WindowSize from "./WindowSize"
|
||||
import About from "./About"
|
||||
import Stats from "./Stats"
|
||||
import Home from "./Home"
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Motion, spring } from "react-motion";
|
||||
import { Switch, Route, Link, withRouter } from "react-router-dom";
|
||||
import WindowSize from "./WindowSize";
|
||||
import About from "./About";
|
||||
import Stats from "./Stats";
|
||||
import Home from "./Home";
|
||||
|
||||
class Layout extends React.Component {
|
||||
static propTypes = {
|
||||
location: PropTypes.object,
|
||||
children: PropTypes.node
|
||||
}
|
||||
};
|
||||
|
||||
state = {
|
||||
underlineLeft: 0,
|
||||
underlineWidth: 0,
|
||||
useSpring: false,
|
||||
stats: null
|
||||
}
|
||||
};
|
||||
|
||||
adjustUnderline = (useSpring = false) => {
|
||||
let itemIndex
|
||||
let itemIndex;
|
||||
switch (this.props.location.pathname) {
|
||||
case "/stats":
|
||||
itemIndex = 1
|
||||
break
|
||||
itemIndex = 1;
|
||||
break;
|
||||
case "/about":
|
||||
itemIndex = 2
|
||||
break
|
||||
itemIndex = 2;
|
||||
break;
|
||||
case "/":
|
||||
default:
|
||||
itemIndex = 0
|
||||
itemIndex = 0;
|
||||
}
|
||||
|
||||
const itemNodes = this.listNode.querySelectorAll("li")
|
||||
const currentNode = itemNodes[itemIndex]
|
||||
const itemNodes = this.listNode.querySelectorAll("li");
|
||||
const currentNode = itemNodes[itemIndex];
|
||||
|
||||
this.setState({
|
||||
underlineLeft: currentNode.offsetLeft,
|
||||
underlineWidth: currentNode.offsetWidth,
|
||||
useSpring
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.adjustUnderline()
|
||||
this.adjustUnderline();
|
||||
|
||||
fetch("/_stats?period=last-month")
|
||||
.then(res => res.json())
|
||||
.then(stats => this.setState({ stats }))
|
||||
.then(stats => this.setState({ stats }));
|
||||
|
||||
if (window.localStorage) {
|
||||
const savedStats = window.localStorage.savedStats
|
||||
const savedStats = window.localStorage.savedStats;
|
||||
|
||||
if (savedStats) this.setState({ stats: JSON.parse(savedStats) })
|
||||
if (savedStats) this.setState({ stats: JSON.parse(savedStats) });
|
||||
|
||||
window.onbeforeunload = () => {
|
||||
localStorage.savedStats = JSON.stringify(this.state.stats)
|
||||
}
|
||||
localStorage.savedStats = JSON.stringify(this.state.stats);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.location.pathname !== this.props.location.pathname) this.adjustUnderline(true)
|
||||
if (prevProps.location.pathname !== this.props.location.pathname)
|
||||
this.adjustUnderline(true);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { underlineLeft, underlineWidth, useSpring } = this.state
|
||||
const { underlineLeft, underlineWidth, useSpring } = this.state;
|
||||
|
||||
const style = {
|
||||
left: useSpring ? spring(underlineLeft, { stiffness: 220 }) : underlineLeft,
|
||||
left: useSpring
|
||||
? spring(underlineLeft, { stiffness: 220 })
|
||||
: underlineLeft,
|
||||
width: useSpring ? spring(underlineWidth) : underlineWidth
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -81,7 +84,10 @@ class Layout extends React.Component {
|
||||
<header>
|
||||
<h1 className="layout-title">unpkg</h1>
|
||||
<nav className="layout-nav">
|
||||
<ol className="layout-nav-list" ref={node => (this.listNode = node)}>
|
||||
<ol
|
||||
className="layout-nav-list"
|
||||
ref={node => (this.listNode = node)}
|
||||
>
|
||||
<li>
|
||||
<Link to="/">Home</Link>
|
||||
</li>
|
||||
@ -111,13 +117,16 @@ class Layout extends React.Component {
|
||||
</div>
|
||||
|
||||
<Switch>
|
||||
<Route path="/stats" render={() => <Stats data={this.state.stats} />} />
|
||||
<Route
|
||||
path="/stats"
|
||||
render={() => <Stats data={this.state.stats} />}
|
||||
/>
|
||||
<Route path="/about" component={About} />
|
||||
<Route path="/" component={Home} />
|
||||
</Switch>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(Layout)
|
||||
export default withRouter(Layout);
|
||||
|
||||
131
client/Stats.js
131
client/Stats.js
@ -1,51 +1,55 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import formatBytes from "pretty-bytes"
|
||||
import formatDate from "date-fns/format"
|
||||
import parseDate from "date-fns/parse"
|
||||
import formatNumber from "./utils/formatNumber"
|
||||
import formatPercent from "./utils/formatPercent"
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import formatBytes from "pretty-bytes";
|
||||
import formatDate from "date-fns/format";
|
||||
import parseDate from "date-fns/parse";
|
||||
import formatNumber from "./utils/formatNumber";
|
||||
import formatPercent from "./utils/formatPercent";
|
||||
|
||||
import { continents, countries } from "countries-list"
|
||||
import { continents, countries } from "countries-list";
|
||||
|
||||
const getCountriesByContinent = continent =>
|
||||
Object.keys(countries).filter(country => countries[country].continent === continent)
|
||||
Object.keys(countries).filter(
|
||||
country => countries[country].continent === continent
|
||||
);
|
||||
|
||||
const sumKeyValues = (hash, keys) => keys.reduce((n, key) => n + (hash[key] || 0), 0)
|
||||
const sumKeyValues = (hash, keys) =>
|
||||
keys.reduce((n, key) => n + (hash[key] || 0), 0);
|
||||
|
||||
const sumValues = hash => Object.keys(hash).reduce((memo, key) => memo + hash[key], 0)
|
||||
const sumValues = hash =>
|
||||
Object.keys(hash).reduce((memo, key) => memo + hash[key], 0);
|
||||
|
||||
class Stats extends React.Component {
|
||||
static propTypes = {
|
||||
data: PropTypes.object
|
||||
}
|
||||
};
|
||||
|
||||
state = {
|
||||
minPackageRequests: 1000000,
|
||||
minCountryRequests: 1000000
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { data } = this.props
|
||||
const { data } = this.props;
|
||||
|
||||
if (data == null) return null
|
||||
if (data == null) return null;
|
||||
|
||||
const totals = data.totals
|
||||
const totals = data.totals;
|
||||
|
||||
// Summary data
|
||||
const since = parseDate(totals.since)
|
||||
const until = parseDate(totals.until)
|
||||
const since = parseDate(totals.since);
|
||||
const until = parseDate(totals.until);
|
||||
|
||||
// Packages
|
||||
const packageRows = []
|
||||
const packageRows = [];
|
||||
|
||||
Object.keys(totals.requests.package)
|
||||
.sort((a, b) => {
|
||||
return totals.requests.package[b] - totals.requests.package[a]
|
||||
return totals.requests.package[b] - totals.requests.package[a];
|
||||
})
|
||||
.forEach(packageName => {
|
||||
const requests = totals.requests.package[packageName]
|
||||
const bandwidth = totals.bandwidth.package[packageName]
|
||||
const requests = totals.requests.package[packageName];
|
||||
const bandwidth = totals.bandwidth.package[packageName];
|
||||
|
||||
if (requests >= this.state.minPackageRequests) {
|
||||
packageRows.push(
|
||||
@ -59,44 +63,51 @@ class Stats extends React.Component {
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{formatNumber(requests)} ({formatPercent(requests / totals.requests.all)}%)
|
||||
{formatNumber(requests)} ({formatPercent(
|
||||
requests / totals.requests.all
|
||||
)}%)
|
||||
</td>
|
||||
{bandwidth ? (
|
||||
<td>
|
||||
{formatBytes(bandwidth)} ({formatPercent(bandwidth / totals.bandwidth.all)}%)
|
||||
{formatBytes(bandwidth)} ({formatPercent(
|
||||
bandwidth / totals.bandwidth.all
|
||||
)}%)
|
||||
</td>
|
||||
) : (
|
||||
<td>-</td>
|
||||
)}
|
||||
</tr>
|
||||
)
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Regions
|
||||
const regionRows = []
|
||||
const regionRows = [];
|
||||
|
||||
const continentsData = Object.keys(continents).reduce((memo, continent) => {
|
||||
const localCountries = getCountriesByContinent(continent)
|
||||
const localCountries = getCountriesByContinent(continent);
|
||||
|
||||
memo[continent] = {
|
||||
countries: localCountries,
|
||||
requests: sumKeyValues(totals.requests.country, localCountries),
|
||||
bandwidth: sumKeyValues(totals.bandwidth.country, localCountries)
|
||||
}
|
||||
};
|
||||
|
||||
return memo
|
||||
}, {})
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
const topContinents = Object.keys(continentsData).sort((a, b) => {
|
||||
return continentsData[b].requests - continentsData[a].requests
|
||||
})
|
||||
return continentsData[b].requests - continentsData[a].requests;
|
||||
});
|
||||
|
||||
topContinents.forEach(continent => {
|
||||
const continentName = continents[continent]
|
||||
const continentData = continentsData[continent]
|
||||
const continentName = continents[continent];
|
||||
const continentData = continentsData[continent];
|
||||
|
||||
if (continentData.requests > this.state.minCountryRequests && continentData.bandwidth !== 0) {
|
||||
if (
|
||||
continentData.requests > this.state.minCountryRequests &&
|
||||
continentData.bandwidth !== 0
|
||||
) {
|
||||
regionRows.push(
|
||||
<tr key={continent} className="continent-row">
|
||||
<td>{continentName}</td>
|
||||
@ -111,15 +122,15 @@ class Stats extends React.Component {
|
||||
)}%)
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
);
|
||||
|
||||
const topCountries = continentData.countries.sort((a, b) => {
|
||||
return totals.requests.country[b] - totals.requests.country[a]
|
||||
})
|
||||
return totals.requests.country[b] - totals.requests.country[a];
|
||||
});
|
||||
|
||||
topCountries.forEach(country => {
|
||||
const countryRequests = totals.requests.country[country]
|
||||
const countryBandwidth = totals.bandwidth.country[country]
|
||||
const countryRequests = totals.requests.country[country];
|
||||
const countryBandwidth = totals.bandwidth.country[country];
|
||||
|
||||
if (countryRequests > this.state.minCountryRequests) {
|
||||
regionRows.push(
|
||||
@ -136,19 +147,19 @@ class Stats extends React.Component {
|
||||
)}%)
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Protocols
|
||||
const protocolRows = Object.keys(totals.requests.protocol)
|
||||
.sort((a, b) => {
|
||||
return totals.requests.protocol[b] - totals.requests.protocol[a]
|
||||
return totals.requests.protocol[b] - totals.requests.protocol[a];
|
||||
})
|
||||
.map(protocol => {
|
||||
const requests = totals.requests.protocol[protocol]
|
||||
const requests = totals.requests.protocol[protocol];
|
||||
|
||||
return (
|
||||
<tr key={protocol}>
|
||||
@ -159,19 +170,22 @@ class Stats extends React.Component {
|
||||
)}%)
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="wrapper">
|
||||
<p>
|
||||
From <strong>{formatDate(since, "MMM D")}</strong> to{" "}
|
||||
<strong>{formatDate(until, "MMM D")}</strong> unpkg served{" "}
|
||||
<strong>{formatNumber(totals.requests.all)}</strong> requests and a total of{" "}
|
||||
<strong>{formatBytes(totals.bandwidth.all)}</strong> of data to{" "}
|
||||
<strong>{formatNumber(totals.uniques.all)}</strong> unique visitors,{" "}
|
||||
<strong>{formatPercent(totals.requests.cached / totals.requests.all, 0)}%</strong> of
|
||||
which were served from the cache.
|
||||
<strong>{formatNumber(totals.requests.all)}</strong> requests and a
|
||||
total of <strong>{formatBytes(totals.bandwidth.all)}</strong> of data
|
||||
to <strong>{formatNumber(totals.uniques.all)}</strong> unique
|
||||
visitors,{" "}
|
||||
<strong>
|
||||
{formatPercent(totals.requests.cached / totals.requests.all, 0)}%
|
||||
</strong>{" "}
|
||||
of which were served from the cache.
|
||||
</p>
|
||||
|
||||
<h3>Packages</h3>
|
||||
@ -241,7 +255,12 @@ class Stats extends React.Component {
|
||||
requests.
|
||||
</p>
|
||||
|
||||
<table cellSpacing="0" cellPadding="0" style={{ width: "100%" }} className="regions-table">
|
||||
<table
|
||||
cellSpacing="0"
|
||||
cellPadding="0"
|
||||
style={{ width: "100%" }}
|
||||
className="regions-table"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Region</th>
|
||||
@ -270,8 +289,8 @@ class Stats extends React.Component {
|
||||
<tbody>{protocolRows}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Stats
|
||||
export default Stats;
|
||||
|
||||
@ -1,34 +1,34 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import addEvent from "./utils/addEvent"
|
||||
import removeEvent from "./utils/removeEvent"
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import addEvent from "./utils/addEvent";
|
||||
import removeEvent from "./utils/removeEvent";
|
||||
|
||||
const ResizeEvent = "resize"
|
||||
const ResizeEvent = "resize";
|
||||
|
||||
class WindowSize extends React.Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func
|
||||
}
|
||||
};
|
||||
|
||||
handleWindowResize = () => {
|
||||
if (this.props.onChange)
|
||||
this.props.onChange({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
addEvent(window, ResizeEvent, this.handleWindowResize)
|
||||
addEvent(window, ResizeEvent, this.handleWindowResize);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
removeEvent(window, ResizeEvent, this.handleWindowResize)
|
||||
removeEvent(window, ResizeEvent, this.handleWindowResize);
|
||||
}
|
||||
|
||||
render() {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default WindowSize
|
||||
export default WindowSize;
|
||||
|
||||
@ -1,12 +1,7 @@
|
||||
body {
|
||||
font-size: 16px;
|
||||
font-family: -apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
|
||||
Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
padding: 5px 20px;
|
||||
}
|
||||
@ -50,7 +45,8 @@ th {
|
||||
text-align: left;
|
||||
background-color: #eee;
|
||||
}
|
||||
th, td {
|
||||
th,
|
||||
td {
|
||||
padding: 5px;
|
||||
}
|
||||
th {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from "react"
|
||||
import ReactDOM from "react-dom"
|
||||
import App from "./App"
|
||||
import "./main.css"
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import App from "./App";
|
||||
import "./main.css";
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById("app"))
|
||||
ReactDOM.render(<App />, document.getElementById("app"));
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
const addEvent = (node, type, handler) => {
|
||||
if (node.addEventListener) {
|
||||
node.addEventListener(type, handler, false)
|
||||
node.addEventListener(type, handler, false);
|
||||
} else if (node.attachEvent) {
|
||||
node.attachEvent("on" + type, handler)
|
||||
node.attachEvent("on" + type, handler);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default addEvent
|
||||
export default addEvent;
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
const formatNumber = n => {
|
||||
const digits = String(n).split("")
|
||||
const groups = []
|
||||
const digits = String(n).split("");
|
||||
const groups = [];
|
||||
|
||||
while (digits.length) groups.unshift(digits.splice(-3).join(""))
|
||||
while (digits.length) groups.unshift(digits.splice(-3).join(""));
|
||||
|
||||
return groups.join(",")
|
||||
}
|
||||
return groups.join(",");
|
||||
};
|
||||
|
||||
export default formatNumber
|
||||
export default formatNumber;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
const formatPercent = (n, fixed = 1) => String((n.toPrecision(2) * 100).toFixed(fixed))
|
||||
const formatPercent = (n, fixed = 1) =>
|
||||
String((n.toPrecision(2) * 100).toFixed(fixed));
|
||||
|
||||
export default formatPercent
|
||||
export default formatPercent;
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
const parseNumber = s => parseInt(s.replace(/,/g, ""), 10) || 0
|
||||
const parseNumber = s => parseInt(s.replace(/,/g, ""), 10) || 0;
|
||||
|
||||
export default parseNumber
|
||||
export default parseNumber;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
const removeEvent = (node, type, handler) => {
|
||||
if (node.removeEventListener) {
|
||||
node.removeEventListener(type, handler, false)
|
||||
node.removeEventListener(type, handler, false);
|
||||
} else if (node.detachEvent) {
|
||||
node.detachEvent("on" + type, handler)
|
||||
node.detachEvent("on" + type, handler);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default removeEvent
|
||||
export default removeEvent;
|
||||
|
||||
Reference in New Issue
Block a user