Skip to content

Commit

Permalink
Rebuilds snowflake using Next.js.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathan Fuchs committed Oct 15, 2017
1 parent d8b9dd6 commit a7e97cd
Show file tree
Hide file tree
Showing 28 changed files with 5,732 additions and 1,296 deletions.
12 changes: 12 additions & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[ignore]
.*/.next/.*
.*/node_modules/.*
.*/out/.*

[include]

[libs]
types/

[options]

4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.next
node_modules
out
yarn-error.log
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"flow.useNPMPackagedFlow": true,
"flow.runOnAllFiles": true,
"javascript.validate.enable": false,
"typescript.disableAutomaticTypeAcquisition": true
}
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# leveling
# Snowflake

`python -m SimpleHTTPServer`
## Environment

![the lannisters send their regards](http://i.imgur.com/RvYHIDq.png)
Get yarn if you don’t have it already:

`npm install -g yarn`

Install dependencies:

`yarn`

## Running the dev server

`yarn dev`

## Building

`yarn export`

This will put a static version of the site in `out/`.
41 changes: 41 additions & 0 deletions components/KeyboardListener.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// @flow

import React from 'react'

type Props = {
increaseFocusedMilestoneFn: () => void,
selectNextTrackFn: () => void,
decreaseFocusedMilestoneFn: () => void,
selectPrevTrackFn: () => void
}

class KeyboardListener extends React.Component<Props> {
componentDidMount() {
window.addEventListener('keydown', (e) => this.handleKeyDown(e)) // TK unlisten
}

handleKeyDown(e: KeyboardEvent) {
switch(e.code) {
case 'ArrowUp':
this.props.increaseFocusedMilestoneFn()
break
case 'ArrowRight':
this.props.selectNextTrackFn()
break
case 'ArrowDown':
this.props.decreaseFocusedMilestoneFn()
break
case 'ArrowLeft':
this.props.selectPrevTrackFn()
break
}
e.preventDefault()
}

render() {
return null
}

}

export default KeyboardListener
96 changes: 96 additions & 0 deletions components/LevelThermometer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// @flow

import * as d3 from 'd3'
import { pointsToLevels, categoryPointsFromMilestoneMap, categoryColorScale, categoryIds } from '../constants'
import React from 'react'
import type { MilestoneMap } from '../constants'

const margins = {
top: 30,
right: 20,
bottom: 30,
left: 0
}
const height = 100
const width = 500

type Props = {
milestoneByTrack: MilestoneMap,
}

class LevelThermometer extends React.Component<Props> {
pointScale: any
topAxisFn: any
bottomAxisFn: any
topAxis: *
bottomAxis: *

constructor(props: *) {
super(props)

this.pointScale = d3.scaleLinear()
.domain([0, 135]) // TK magic number
.rangeRound([0, width - margins.left - margins.right]);

this.topAxisFn = d3.axisTop()
.scale(this.pointScale)
.tickValues(Object.keys(pointsToLevels))
.tickFormat(points => pointsToLevels[points])

this.bottomAxisFn = d3.axisBottom()
.scale(this.pointScale)
.tickValues(Object.keys(pointsToLevels))
}

componentDidMount() {
d3.select(this.topAxis).call(this.topAxisFn)
.selectAll('text')
.style('text-anchor', 'start')
d3.select(this.bottomAxis).call(this.bottomAxisFn)
.selectAll('text')
.style('text-anchor', 'start')
}

render() {
let categoryPoints = categoryPointsFromMilestoneMap(this.props.milestoneByTrack)
let cumulativePoints = 0
return (
<figure>
<style jsx>{`
figure {
margin: 0;
}
svg {
width: ${width}px;
height: ${height}px;
}
`}</style>
<svg>
<g transform={`translate(${margins.left},${margins.top})`}>
{categoryPoints.map((categoryPoint) => {
const x = this.pointScale(cumulativePoints)
const width = this.pointScale(cumulativePoints + categoryPoint.points) - x
cumulativePoints += categoryPoint.points
return <rect
key={categoryPoint.categoryId}
x={x}
y={0}
width={width}
height={height - margins.top - margins.bottom}
style={{fill: categoryColorScale(categoryPoint.categoryId)}}
rx={3}
ry={3}
/>
})}
<g ref={ref => this.topAxis = ref} className="top-axis"
transform={`translate(0, -4)`}/>
<g ref={ref => this.bottomAxis = ref} className="bottom-axis"
transform={`translate(0,${height - margins.top - margins.bottom + 3})`}/>
</g>
</svg>
</figure>
)
}
}

export default LevelThermometer
89 changes: 89 additions & 0 deletions components/NightingaleChart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// @flow

import React from 'react'
import * as d3 from 'd3'
import { trackIds, milestones, tracks, categoryColorScale } from '../constants'
import type { TrackId, Milestone, MilestoneMap } from '../constants'

const width = 400

type Props = {
milestoneByTrack: MilestoneMap,
handleTrackMilestoneChangeFn: (TrackId, Milestone) => void
}

class NightingaleChart extends React.Component<Props> {
colorScale: any
radiusScale: any
arcFn: any

constructor(props: *) {
super(props)

this.colorScale = d3.scaleSequential(d3.interpolateWarm)
.domain([0, 5])

this.radiusScale = d3.scaleBand()
.domain(milestones)
.range([.1 * width, .45 * width])
.paddingInner(0.1)

this.arcFn = d3.arc()
.innerRadius(milestone => this.radiusScale(milestone))
.outerRadius(milestone => this.radiusScale(milestone) + this.radiusScale.bandwidth())
.startAngle(0)
.endAngle(2 * Math.PI / trackIds.length)
.padAngle(Math.PI / 200)
.padRadius(.45 * width)
.cornerRadius(2)
}

render() {
return (
<figure>
<style jsx>{`
figure {
margin: 0;
}
svg {
width: ${width}px;
height: ${width}px;
}
path.track-milestone {
fill: #eee;
cursor: pointer;
}
path.track-milestone.is-met {
fill: #F9F;
}
path.track-milestone:hover {
stroke: #000;
stroke-width: 4px;
stroke-linejoin: round;
}
`}</style>
<svg>
<g transform={`translate(${width/2},${width/2}) rotate(-45)`}>
{trackIds.map((trackId, i) => (
<g key={trackId} transform={`rotate(${i * 360 / trackIds.length})`}>
{milestones.map((milestone) => {
const isMet = this.props.milestoneByTrack[trackId] >= milestone || milestone == 0
return (
<path
key={milestone}
className={'track-milestone ' + (isMet ? 'is-met' : '')}
onClick={() => this.props.handleTrackMilestoneChangeFn(trackId, milestone)}
d={this.arcFn(milestone)}
style={{fill: isMet ? categoryColorScale(tracks[trackId].category) : undefined}} />
)
})}
</g>
))}
</g>
</svg>
</figure>
)
}
}

export default NightingaleChart
89 changes: 89 additions & 0 deletions components/PointSummaries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// @flow

import { pointsToLevels, milestoneToPoints, trackIds } from '../constants'
import type { MilestoneMap } from '../constants'
import React from 'react'

type Props = {
milestoneByTrack: MilestoneMap
}

class PointSummaries extends React.Component<Props> {
render() {
const totalPoints = trackIds.map(trackId => milestoneToPoints(this.props.milestoneByTrack[trackId])).reduce((sum, addend) => (sum + addend), 0)

let currentLevel, nextLevel

let pointsForCurrentLevel = totalPoints
while (!(currentLevel = pointsToLevels[pointsForCurrentLevel])) {
pointsForCurrentLevel--
}

let pointsToNextLevel = 1
while (!(nextLevel = pointsToLevels[totalPoints + pointsToNextLevel])) {
pointsToNextLevel++
if (pointsToNextLevel > 135) {
pointsToNextLevel = 'N/A'
break
}
}

const blocks = [
{
label: 'Total points',
value: totalPoints
},
{
label: 'Current level',
value: currentLevel
},
{
label: 'Points to next level',
value: pointsToNextLevel
}
]

return (
<table>
<style jsx>{`
table {
border-spacing: 3px;
margin-bottom: 20px;
}
.point-summary-label {
font-size: 12px;
text-align: center;
font-weight: normal;
}
.point-summary-value {
width: 120px;
background: #eee;
font-size: 24px;
font-weight: bold;
line-height: 40px;
border-radius: 2px;
text-align: center;
}
`}</style>
<tbody>
<tr>
{blocks.map(({label}, i) => (
<th key={i} className="point-summary-label">
{label}
</th>
))}
</tr>
<tr>
{blocks.map(({value}, i) => (
<td key={i} className="point-summary-value">
{value}
</td>
))}
</tr>
</tbody>
</table>
)
}
}

export default PointSummaries
Loading

0 comments on commit a7e97cd

Please sign in to comment.