commit
dc355b3bbd
4 changed files with 231 additions and 1 deletions
31
src/App.js
31
src/App.js
|
|
@ -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 />
|
||||
|
|
|
|||
84
src/components/Spinner.css
Normal file
84
src/components/Spinner.css
Normal 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
14
src/components/Spinner.js
Normal 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
103
src/util/fetch-content.js
Normal 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)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue