Preview Mode init

This commit is contained in:
Eric Jinks 2018-02-16 10:51:43 +10:00
parent 5f6bfab2da
commit 60defaa432
4 changed files with 231 additions and 1 deletions

View file

@ -1,6 +1,7 @@
import React, { Component } from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import Helmet from 'react-helmet'
import _merge from 'lodash/merge'
import ScrollToTop from './components/ScrollToTop'
import Meta from './components/Meta'
@ -13,11 +14,38 @@ import Footer from './components/Footer'
import GithubCorner from './components/GithubCorner'
import ServiceWorkerNotifications from './components/ServiceWorkerNotifications'
import AOS from './components/AOS'
import Spinner from './components/Spinner'
import { fetchContent } from './util/fetch-content'
import data from './data.json'
class App extends Component {
state = {
data
data,
loading: false
}
componentDidMount = () => {
this.fetchPreviewContent()
}
fetchPreviewContent = () => {
if (
!window.netlifyIdentity ||
!window.netlifyIdentity.currentUser() ||
process.env.NODE_ENV === 'development'
) {
return false
}
this.setState({ loading: true })
fetchContent()
.then(newData => {
this.setState(prevState => {
const data = _merge(prevState.data, newData)
return { data, loading: false }
})
})
.catch(() => this.setState({ loading: false }))
}
getDocument = (collection, name) =>
@ -39,6 +67,7 @@ class App extends Component {
return (
<Router>
<div className='React-Wrap'>
{this.state.loading && <Spinner />}
<AOS options={{ duration: 250 }} />
<ScrollToTop />
<ServiceWorkerNotifications reloadOnUpdate />

View file

@ -0,0 +1,84 @@
.Spinner {
position: fixed;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.7);
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
}
.semipolar-spinner,
.semipolar-spinner * {
box-sizing: border-box;
}
.semipolar-spinner {
--size: 150px;
--duration: 750ms;
--color: var(--primary);
height: var(--size);
width: var(--size);
position: relative;
}
.semipolar-spinner .ring {
border-radius: 50%;
position: absolute;
border: calc(var(--size) * 0.05) solid transparent;
border-top-color: var(--color);
border-left-color: var(--color);
animation: semipolar-spinner-animation 2s infinite;
}
.semipolar-spinner .ring:nth-child(1) {
height: calc(var(--size) - var(--size) * 0.2 * 0);
width: calc(var(--size) - var(--size) * 0.2 * 0);
top: calc(var(--size) * 0.1 * 0);
left: calc(var(--size) * 0.1 * 0);
animation-delay: calc(var(--duration) * 0.1 * 4);
z-index: 5;
}
.semipolar-spinner .ring:nth-child(2) {
height: calc(var(--size) - var(--size) * 0.2 * 1);
width: calc(var(--size) - var(--size) * 0.2 * 1);
top: calc(var(--size) * 0.1 * 1);
left: calc(var(--size) * 0.1 * 1);
animation-delay: calc(var(--duration) * 0.1 * 3);
z-index: 4;
}
.semipolar-spinner .ring:nth-child(3) {
height: calc(var(--size) - var(--size) * 0.2 * 2);
width: calc(var(--size) - var(--size) * 0.2 * 2);
top: calc(var(--size) * 0.1 * 2);
left: calc(var(--size) * 0.1 * 2);
animation-delay: calc(var(--duration) * 0.1 * 2);
z-index: 3;
}
.semipolar-spinner .ring:nth-child(4) {
height: calc(var(--size) - var(--size) * 0.2 * 3);
width: calc(var(--size) - var(--size) * 0.2 * 3);
top: calc(var(--size) * 0.1 * 3);
left: calc(var(--size) * 0.1 * 3);
animation-delay: calc(var(--duration) * 0.1 * 1);
z-index: 2;
}
.semipolar-spinner .ring:nth-child(5) {
height: calc(var(--size) - var(--size) * 0.2 * 4);
width: calc(var(--size) - var(--size) * 0.2 * 4);
top: calc(var(--size) * 0.1 * 4);
left: calc(var(--size) * 0.1 * 4);
animation-delay: calc(var(--duration) * 0.1 * 0);
z-index: 1;
}
@keyframes semipolar-spinner-animation {
50% {
transform: rotate(360deg) scale(0.7);
}
}

14
src/components/Spinner.js Normal file
View file

@ -0,0 +1,14 @@
import React from 'react'
import './Spinner.css'
export default () => (
<div className='Spinner'>
<div className='semipolar-spinner'>
<div className='ring' />
<div className='ring' />
<div className='ring' />
<div className='ring' />
<div className='ring' />
</div>
</div>
)

103
src/util/fetch-content.js Normal file
View file

@ -0,0 +1,103 @@
const matter = require('gray-matter')
const yaml = require('js-yaml')
const b64DecodeUnicode = str =>
// https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings#30106551
decodeURIComponent(
Array.prototype.map
.call(atob(str), function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
})
.join('')
)
const getFileExtension = str => {
const matches = str.match(/.*\.(.{2,4})$/i)
return matches && matches[1]
}
const parseMarkdown = data => {
console.log('Parsing md')
data = matter(data)
data = { ...data, ...data.data }
delete data.data
return data
}
const parseYaml = data => {
console.log('Parsing yaml')
return yaml.safeLoad(data) || {}
}
export const fetchContent = async (rateLimit = 100) => {
if (!window.localStorage || !window.netlifyIdentity) {
return Promise.resolve(null)
}
const currentUser = window.netlifyIdentity.currentUser()
if (!currentUser) return Promise.resolve(null)
const token = await currentUser.jwt()
const wait = time =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(), time)
})
const fetchGithub = (path = 'content') => {
const siteUrl = window.localStorage.netlifySiteURL || ''
const endpoint = `${siteUrl}/.netlify/git/github/contents/${path}`
console.log(`Fetching ${path}`)
return wait(rateLimit)
.then(() =>
fetch(endpoint, {
headers: {
Authorization: `Bearer ${token}`
}
})
)
.then(res => res.json())
.then(res => {
if (res.code === 401) throw new Error(res.msg)
return res
})
.catch(console.error)
}
let data = {}
return fetchGithub()
.then(items => {
if (!items) throw new Error('No items found')
const dirs = items.filter(item => item.type === 'dir')
return dirs
})
.then(dirs => {
const dirsToFetch = dirs.map(dir => {
const dirKey = dir.path.split('/').pop()
data[dirKey] = []
return fetchGithub(dir.path)
.then(files => {
const filesToFetch = files
.filter(file => file.type === 'file')
.map(file => wait(rateLimit).then(() => fetchGithub(file.path)))
return Promise.all(filesToFetch)
})
.then(files => {
files.forEach(file => {
const fileType = getFileExtension(file.name)
const fileContents = b64DecodeUnicode(file.content)
const json =
fileType === 'md'
? parseMarkdown(fileContents)
: parseYaml(fileContents)
if (json) data[dirKey].push(json)
})
return data
})
})
return Promise.all(dirsToFetch)
})
.then(() => data)
.catch(console.error)
}