mirror of
https://github.com/kremalicious/portfolio.git
synced 2024-12-22 17:23:22 +01:00
Merge pull request #10 from kremalicious/feature/gatsby-v2
Update to Gatsby v2
This commit is contained in:
commit
eba94c3877
@ -1,2 +1,5 @@
|
||||
exclude_patterns:
|
||||
- src/lib/vcf/
|
||||
version: "2"
|
||||
checks:
|
||||
method-lines:
|
||||
config:
|
||||
threshold: 50 # Gatsby's StaticQuery makes render functions pretty long
|
||||
|
@ -18,9 +18,6 @@
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"globals": {
|
||||
"graphql": true
|
||||
},
|
||||
"rules": {
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "never"],
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@ yarn-debug.log*
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
plugins/gatsby-plugin-matomo
|
||||
src/components/svg
|
||||
|
@ -1,5 +1,4 @@
|
||||
node_modules/
|
||||
.cache/
|
||||
src/lib/vcf
|
||||
static/
|
||||
public/
|
||||
|
@ -7,10 +7,6 @@ cache:
|
||||
- public
|
||||
|
||||
install:
|
||||
- pip install --user awscli
|
||||
- export PATH=$PATH:$HOME/.local/bin
|
||||
- npm i -g gatsby-cli
|
||||
- export PATH="$PATH:/usr/local/bin/gatsby"
|
||||
- npm i
|
||||
|
||||
script:
|
||||
@ -18,9 +14,9 @@ script:
|
||||
- npm run build
|
||||
|
||||
after_success:
|
||||
- pip install --user awscli
|
||||
- export PATH=$PATH:$HOME/.local/bin
|
||||
- npm run deploy
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
slack:
|
||||
secure: v/sqm2fgXTi7RJIjVsaJz1i3bV/WNFyTkLLto2p/fXjoWHVWJBdodANTTH+9lgkbft6JdBcU4WDLDkLP8VKlR9JffFc1DvbxGmF3VTfP2kJcnCbo+koLHBtzR1oRwDB1aulp6+II0ROPS9OyOv+Xx45bj4jOt9y/r0Qcp2V5fKvSUEwEecxaTmvBEn3fvZ9COSgL8ufq9hCCT/6r3Bxl0Pp28Fj23pLwuM6tCKjriM4w5WY0xY2Sv4unz6u7LNec5DZH3QZPH4/pdRVBmC3rxt3quw8IMt6NNXG3ODUPRqS7GB44Q+k8L+15Dn5Cx22o92wULpXKTDvHmLLj2lej50V0cyoj7U4SbDkVOohUG23fn10/AzvZogYA2ugf3YXKJCBbyyloJV6jFnZAAvKRSD8B5PFnuhf+uLEAfX9Of1FLNO2c7XIQa86ht5xrpsRFCzNCFhtyHNc8pXwSv7EUv7K2bccCGKv0a7DxYquP0OB3OSkI6tvRc3mvYpe5k2WKNHFRByud14ywGxMn0FYBwtsHbW3QJ/z/WaFYS2hJXjtpjHQW1wNDy+67UTiZFi1anovUHoZvkrIKtRqY1eyXPJB31TqPHWSX0er0IfdhfE3o9mMuoSVUip6S29eY7hhVk5S3IsK1YlPAFW8pa9OPiUBt8z+gbf8WN23qi61Bn7g=
|
||||
|
@ -19,8 +19,6 @@ RUN apk update && \
|
||||
libpng-dev \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
RUN npm install --global gatsby-cli --no-optional
|
||||
|
||||
RUN mkdir -p /portfolio
|
||||
WORKDIR /portfolio
|
||||
VOLUME /portfolio
|
||||
|
34
README.md
34
README.md
@ -15,19 +15,19 @@
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Features](#features)
|
||||
* [One data file to rule all pages](#one-data-file-to-rule-all-pages)
|
||||
* [Theme switcher](#theme-switcher)
|
||||
* [SEO component](#seo-component)
|
||||
* [Client-side vCard creation](#client-side-vcard-creation)
|
||||
* [Matomo (formerly Piwik) analytics tracking](#matomo-formerly-piwik-analytics-tracking)
|
||||
* [Project images](#project-images)
|
||||
* [Importing SVG assets](#importing-svg-assets)
|
||||
* [Development](#development)
|
||||
* [Linting](#linting)
|
||||
* [Add a new project](#add-a-new-project)
|
||||
* [Deployment](#deployment)
|
||||
* [Licenses](#licenses)
|
||||
- [Features](#features)
|
||||
- [One data file to rule all pages](#one-data-file-to-rule-all-pages)
|
||||
- [Theme switcher](#theme-switcher)
|
||||
- [SEO component](#seo-component)
|
||||
- [Client-side vCard creation](#client-side-vcard-creation)
|
||||
- [Matomo (formerly Piwik) analytics tracking](#matomo-formerly-piwik-analytics-tracking)
|
||||
- [Project images](#project-images)
|
||||
- [Importing SVG assets](#importing-svg-assets)
|
||||
- [Development](#development)
|
||||
- [Linting](#linting)
|
||||
- [Add a new project](#add-a-new-project)
|
||||
- [Deployment](#deployment)
|
||||
- [Licenses](#licenses)
|
||||
|
||||
---
|
||||
|
||||
@ -79,10 +79,10 @@ All project images use one single component defined in [`src/components/atoms/Pr
|
||||
|
||||
### Importing SVG assets
|
||||
|
||||
Makes use of `gatsby-plugin-svgr` so SVG assets can be imported like so:
|
||||
All SVG assets under `src/images/` will be converted to React components before every build. Makes use of `SVGR` so SVG assets can be imported like so:
|
||||
|
||||
```js
|
||||
import { ReactComponent as Logo } from '../images/logo.svg'
|
||||
import Logo from './components/svg/Logo'
|
||||
```
|
||||
|
||||
## Development
|
||||
@ -133,8 +133,8 @@ portfolio-SLUG-03.png
|
||||
|
||||
Automatic deployments are triggered upon successful tests & builds on Travis:
|
||||
|
||||
* push to `master` initiates a live deployment
|
||||
* any Pull Request, and subsequent pushes to it, initiates a beta deployment
|
||||
- push to `master` initiates a live deployment
|
||||
- any Pull Request, and subsequent pushes to it, initiates a beta deployment
|
||||
|
||||
The deploy command simply calls the [`scripts/deploy.sh`](scripts/deploy.sh) script, syncing the contents of the `public/` folder to S3:
|
||||
|
||||
|
10
gatsby-browser.js
Normal file
10
gatsby-browser.js
Normal file
@ -0,0 +1,10 @@
|
||||
exports.onInitialClientRender = () => {
|
||||
require('./src/styles/base.scss')
|
||||
}
|
||||
|
||||
exports.onClientEntry = () => {
|
||||
// IntersectionObserver polyfill for gatsby-image (Safari, IE)
|
||||
if (typeof window.IntersectionObserver === 'undefined') {
|
||||
require('intersection-observer')
|
||||
}
|
||||
}
|
@ -2,20 +2,19 @@ const path = require('path')
|
||||
const fs = require('fs')
|
||||
const yaml = require('js-yaml')
|
||||
const meta = yaml.load(fs.readFileSync('./data/meta.yml', 'utf8'))
|
||||
const { url, matomoUrl, matomoSite } = meta
|
||||
const { url, matomoSite, matomoUrl } = meta
|
||||
|
||||
module.exports = {
|
||||
siteMetadata: {
|
||||
siteUrl: `${url}`
|
||||
},
|
||||
plugins: [
|
||||
'gatsby-plugin-react-next',
|
||||
'gatsby-plugin-react-helmet',
|
||||
'gatsby-transformer-yaml',
|
||||
'gatsby-transformer-sharp',
|
||||
'gatsby-plugin-sharp',
|
||||
'gatsby-plugin-sitemap',
|
||||
'gatsby-plugin-offline',
|
||||
// 'gatsby-plugin-offline',
|
||||
{
|
||||
resolve: 'gatsby-transformer-json',
|
||||
options: {
|
||||
@ -59,14 +58,6 @@ module.exports = {
|
||||
localScript: '/piwik.js'
|
||||
}
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-plugin-svgr',
|
||||
options: {
|
||||
icon: false,
|
||||
viewBox: true
|
||||
// see https://github.com/smooth-code/svgr for a list of all options
|
||||
}
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-plugin-favicon',
|
||||
options: {
|
||||
|
@ -3,17 +3,28 @@ const path = require('path')
|
||||
// Intersection Observer polyfill
|
||||
// requires `npm install intersection-observer`
|
||||
// https://github.com/gatsbyjs/gatsby/issues/2288#issuecomment-334467821
|
||||
exports.modifyWebpackConfig = ({ config, stage }) => {
|
||||
if (stage === 'build-html') {
|
||||
config.loader('null', {
|
||||
test: /intersection-observer/,
|
||||
loader: 'null-loader'
|
||||
})
|
||||
}
|
||||
}
|
||||
// exports.onCreateWebpackConfig = ({ actions, loaders, stage }) => {
|
||||
// const { setWebpackConfig } = actions
|
||||
|
||||
exports.createPages = ({ boundActionCreators, graphql }) => {
|
||||
const { createPage } = boundActionCreators
|
||||
// if (stage === 'build-html') {
|
||||
// const nullRule = {
|
||||
// test: /intersection-observer/,
|
||||
// use: [loaders.null()]
|
||||
// }
|
||||
|
||||
// setWebpackConfig({
|
||||
// module: {
|
||||
// rules: [nullRule]
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
//
|
||||
// Create project pages from projects.yml
|
||||
//
|
||||
exports.createPages = async ({ actions, graphql }) => {
|
||||
const { createPage } = actions
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const template = path.resolve('src/templates/Project.jsx')
|
||||
@ -26,44 +37,6 @@ exports.createPages = ({ boundActionCreators, graphql }) => {
|
||||
node {
|
||||
slug
|
||||
}
|
||||
previous {
|
||||
title
|
||||
slug
|
||||
img {
|
||||
id
|
||||
childImageSharp {
|
||||
sizes(maxWidth: 500, quality: 80) {
|
||||
aspectRatio
|
||||
src
|
||||
srcSet
|
||||
srcWebp
|
||||
srcSetWebp
|
||||
sizes
|
||||
originalImg
|
||||
originalName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
next {
|
||||
title
|
||||
slug
|
||||
img {
|
||||
id
|
||||
childImageSharp {
|
||||
sizes(maxWidth: 500, quality: 80) {
|
||||
aspectRatio
|
||||
src
|
||||
srcSet
|
||||
srcWebp
|
||||
srcSetWebp
|
||||
sizes
|
||||
originalImg
|
||||
originalName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -72,21 +45,17 @@ exports.createPages = ({ boundActionCreators, graphql }) => {
|
||||
reject(result.errors)
|
||||
}
|
||||
|
||||
result.data.allProjectsYaml.edges.forEach(
|
||||
({ node, previous, next }) => {
|
||||
const slug = node.slug
|
||||
result.data.allProjectsYaml.edges.forEach(({ node }) => {
|
||||
const slug = node.slug
|
||||
|
||||
createPage({
|
||||
path: slug,
|
||||
component: template,
|
||||
context: {
|
||||
slug,
|
||||
previous,
|
||||
next
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
createPage({
|
||||
path: slug,
|
||||
component: template,
|
||||
context: {
|
||||
slug
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
resolve()
|
||||
})
|
||||
|
66
package.json
66
package.json
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "portfolio",
|
||||
"name": "@kremalicious/portfolio",
|
||||
"version": "0.1.0",
|
||||
"homepage": "https://matthiaskretschmann.com",
|
||||
"repository": "github:kremalicious/portfolio",
|
||||
@ -9,57 +9,59 @@
|
||||
"scripts": {
|
||||
"lint:js": "eslint ./gatsby-*.{js,jsx} && eslint ./src/**/*.{js,jsx}",
|
||||
"lint:css": "stylelint ./src/**/*.{css,scss}",
|
||||
"lint": "npm run lint:js && npm run lint:css",
|
||||
"build": "gatsby build",
|
||||
"start": "docker-compose up",
|
||||
"lint": "npm run svg && npm run lint:js && npm run lint:css",
|
||||
"build": "npm run svg && ./node_modules/gatsby/dist/bin/gatsby.js build",
|
||||
"start": "npm run svg && ./node_modules/gatsby/dist/bin/gatsby.js develop",
|
||||
"format": "prettier --write 'src/**/*.{js,jsx}'",
|
||||
"format:css": "prettier-stylelint --write --quiet 'src/**/*.{css,scss}'",
|
||||
"test": "npm run lint",
|
||||
"deploy": "./scripts/deploy.sh",
|
||||
"new": "node ./scripts/new-project.js"
|
||||
"new": "node ./scripts/new.js",
|
||||
"svg": "./scripts/svg.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"camel-case": "^3.0.0",
|
||||
"file-saver": "^1.3.8",
|
||||
"gatsby": "^1.9.270",
|
||||
"gatsby-image": "^1.0.52",
|
||||
"gatsby-link": "^1.6.44",
|
||||
"gatsby-plugin-favicon": "^2.1.1",
|
||||
"gatsby-plugin-matomo": "^0.4.0",
|
||||
"gatsby-plugin-offline": "^1.0.18",
|
||||
"gatsby-plugin-react-helmet": "^2.0.11",
|
||||
"gatsby-plugin-react-next": "^1.0.11",
|
||||
"gatsby-plugin-sass": "^1.0.26",
|
||||
"gatsby-plugin-sharp": "^1.6.47",
|
||||
"gatsby-plugin-sitemap": "^1.2.25",
|
||||
"gatsby-plugin-svgr": "^1.0.1",
|
||||
"gatsby-source-filesystem": "^1.5.38",
|
||||
"gatsby-transformer-sharp": "^1.6.26",
|
||||
"gatsby-transformer-yaml": "^1.5.17",
|
||||
"giphy-js-sdk-core": "^1.0.3",
|
||||
"gatsby": "^2.0.0-beta.35",
|
||||
"gatsby-image": "^2.0.0-beta.6",
|
||||
"gatsby-plugin-favicon": "github:TuckerWhitehouse/gatsby-plugin-favicon#gatsby-v2",
|
||||
"gatsby-plugin-matomo": "^0.4.1",
|
||||
"gatsby-plugin-offline": "^2.0.0-beta.3",
|
||||
"gatsby-plugin-react-helmet": "next",
|
||||
"gatsby-plugin-sass": "next",
|
||||
"gatsby-plugin-sharp": "^2.0.0-beta.5",
|
||||
"gatsby-plugin-sitemap": "next",
|
||||
"gatsby-source-filesystem": "^2.0.1-beta.4",
|
||||
"gatsby-transformer-json": "next",
|
||||
"gatsby-transformer-sharp": "next",
|
||||
"gatsby-transformer-yaml": "next",
|
||||
"giphy-js-sdk-core": "^1.0.5",
|
||||
"graphql": "^0.13.2",
|
||||
"intersection-observer": "^0.5.0",
|
||||
"js-yaml": "^3.12.0",
|
||||
"node-sass": "^4.9.2",
|
||||
"react": "^16.4.1",
|
||||
"react-dom": "^16.4.1",
|
||||
"react-helmet": "^5.2.0",
|
||||
"react-markdown": "^3.3.2",
|
||||
"react-transition-group": "^2.3.1",
|
||||
"react-markdown": "^3.3.4",
|
||||
"react-transition-group": "^2.4.0",
|
||||
"vcf": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^8.2.3",
|
||||
"eslint": "^5.0.0",
|
||||
"@svgr/cli": "^2.1.1",
|
||||
"babel-eslint": "^8.2.6",
|
||||
"eslint": "^5.1.0",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"eslint-plugin-graphql": "^2.1.1",
|
||||
"eslint-plugin-prettier": "^2.6.0",
|
||||
"eslint-plugin-react": "^7.9.1",
|
||||
"gatsby-transformer-json": "^1.0.19",
|
||||
"eslint-plugin-prettier": "^2.6.2",
|
||||
"eslint-plugin-react": "^7.10.0",
|
||||
"ora": "^2.1.0",
|
||||
"prepend": "^1.0.2",
|
||||
"prettier": "^1.13.4",
|
||||
"prettier": "^1.13.7",
|
||||
"prettier-stylelint": "^0.4.2",
|
||||
"slugify": "^1.3.0",
|
||||
"stylelint": "^9.2.1",
|
||||
"stylelint-config-css-modules": "^1.2.0",
|
||||
"stylelint": "^9.3.0",
|
||||
"stylelint-config-css-modules": "^1.3.0",
|
||||
"stylelint-config-standard": "^18.2.0"
|
||||
},
|
||||
"browserslist": [
|
||||
|
@ -1,10 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
export PATH="$PATH:/usr/local/bin/gatsby"
|
||||
|
||||
# echo "Running npm install..."
|
||||
# npm install
|
||||
|
||||
# rm -rf ./public
|
||||
gatsby develop --host 0.0.0.0
|
||||
npm start
|
||||
|
@ -4,7 +4,7 @@ const prepend = require('prepend')
|
||||
const slugify = require('slugify')
|
||||
const ora = require('ora')
|
||||
|
||||
const templatePath = path.join(__dirname, 'new-project.yml')
|
||||
const templatePath = path.join(__dirname, 'new.yml')
|
||||
const template = fs.readFileSync(templatePath).toString()
|
||||
|
||||
const spinner = ora('Adding new project').start()
|
12
scripts/svg.sh
Executable file
12
scripts/svg.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
SRC='./src/images'
|
||||
OUT='./src/components/svg'
|
||||
|
||||
printf "Creating SVG components...\\n\\n"
|
||||
|
||||
# Usage: svgr [-d out-dir] [src-dir]
|
||||
./node_modules/@svgr/cli/bin/svgr --icon -d $OUT $SRC
|
||||
|
||||
printf "\\n🎉 Successfully created SVG components\\n\\n"
|
80
src/components/Layout.jsx
Normal file
80
src/components/Layout.jsx
Normal file
@ -0,0 +1,80 @@
|
||||
import React, { Fragment } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { StaticQuery, graphql } from 'gatsby'
|
||||
import Head from './atoms/Head'
|
||||
import Header from './organisms/Header'
|
||||
import Footer from './organisms/Footer'
|
||||
import styles from './Layout.module.scss'
|
||||
|
||||
const Layout = ({ children, location }) => {
|
||||
const isHomepage = location.pathname === '/'
|
||||
|
||||
return (
|
||||
<StaticQuery
|
||||
query={graphql`
|
||||
query {
|
||||
# the data/meta.yml file
|
||||
dataYaml {
|
||||
title
|
||||
tagline
|
||||
description
|
||||
url
|
||||
email
|
||||
avatar {
|
||||
childImageSharp {
|
||||
original: resize {
|
||||
src
|
||||
}
|
||||
small: resize(width: 256) {
|
||||
src
|
||||
}
|
||||
}
|
||||
}
|
||||
img {
|
||||
childImageSharp {
|
||||
resize(width: 980) {
|
||||
src
|
||||
}
|
||||
}
|
||||
}
|
||||
social {
|
||||
Email
|
||||
Blog
|
||||
Twitter
|
||||
GitHub
|
||||
Dribbble
|
||||
}
|
||||
availability {
|
||||
status
|
||||
}
|
||||
gpg
|
||||
addressbook
|
||||
}
|
||||
}
|
||||
`}
|
||||
render={data => {
|
||||
const meta = data.dataYaml
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Head meta={meta} />
|
||||
<Header meta={meta} isHomepage={isHomepage} />
|
||||
|
||||
<main className={styles.screen} location={location}>
|
||||
{children}
|
||||
</main>
|
||||
|
||||
<Footer meta={meta} />
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
Layout.propTypes = {
|
||||
children: PropTypes.any.isRequired,
|
||||
location: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default Layout
|
6
src/components/Layout.module.scss
Normal file
6
src/components/Layout.module.scss
Normal file
@ -0,0 +1,6 @@
|
||||
@import 'variables';
|
||||
|
||||
.screen {
|
||||
flex: 1;
|
||||
padding: $spacer;
|
||||
}
|
15
src/components/atoms/Button.jsx
Normal file
15
src/components/atoms/Button.jsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styles from './Button.module.scss'
|
||||
|
||||
const Button = props => (
|
||||
<a className={styles.button} {...props}>
|
||||
{props.children}
|
||||
</a>
|
||||
)
|
||||
|
||||
Button.propTypes = {
|
||||
children: PropTypes.node
|
||||
}
|
||||
|
||||
export default Button
|
48
src/components/atoms/Button.module.scss
Normal file
48
src/components/atoms/Button.module.scss
Normal file
@ -0,0 +1,48 @@
|
||||
@import 'variables';
|
||||
|
||||
.button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
color: $brand-cyan;
|
||||
text-align: center;
|
||||
border-radius: .25rem;
|
||||
padding: $spacer / 4 $spacer / 2;
|
||||
transition-property: all;
|
||||
background: rgba(#fff, .15);
|
||||
border: .05rem solid rgba($brand-cyan, .75);
|
||||
font-size: $font-size-small;
|
||||
text-transform: uppercase;
|
||||
|
||||
svg {
|
||||
fill: $brand-grey-light;
|
||||
margin-right: $spacer / 3;
|
||||
transition: .2s ease-out;
|
||||
margin-bottom: -.1rem;
|
||||
width: $font-size-small;
|
||||
height: $font-size-small;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: lighten($brand-cyan, 10%);
|
||||
border-color: rgba(lighten($brand-cyan, 10%), .75);
|
||||
transform: translate3d(0, -.1rem, 0);
|
||||
box-shadow: 0 6px 10px rgba($brand-main, .1),
|
||||
0 10px 25px rgba($brand-main, .05);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transition: none;
|
||||
background: rgba(#fff, .15);
|
||||
}
|
||||
|
||||
:global(.dark) & {
|
||||
background: darken($body-background-color--dark, 1%);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
box-shadow: 0 6px 10px rgba(darken($brand-main, 20%), .1),
|
||||
0 10px 25px rgba(darken($brand-main, 20%), .2);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import SEO from './SEO'
|
||||
import Typekit from './Typekit'
|
||||
|
||||
const Head = ({ meta }) => {
|
||||
const { title, tagline, typekitID } = meta
|
||||
const { title, tagline } = meta
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
@ -17,7 +17,7 @@ const Head = ({ meta }) => {
|
||||
<meta name="theme-color" content="#e7eef4" />
|
||||
</Helmet>
|
||||
|
||||
{typekitID && <Typekit id={typekitID} />}
|
||||
<Typekit />
|
||||
|
||||
<SEO meta={meta} />
|
||||
</Fragment>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ReactComponent as Logo } from '../../images/logo.svg'
|
||||
import Logo from '../svg/Logo'
|
||||
import styles from './LogoUnit.module.scss'
|
||||
|
||||
const LogoUnit = ({ meta, minimal }) => {
|
||||
|
@ -25,11 +25,13 @@
|
||||
margin-right: $spacer / 4;
|
||||
color: $brand-main;
|
||||
line-height: $line-height;
|
||||
transition: color .2s ease-out;
|
||||
}
|
||||
|
||||
.logounit__description {
|
||||
font-size: $font-size-h4;
|
||||
color: $brand-grey;
|
||||
transition: color .2s ease-out;
|
||||
|
||||
:global(.dark) & {
|
||||
color: $brand-grey-light;
|
||||
|
@ -1,28 +1,28 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { graphql } from 'gatsby'
|
||||
import Img from 'gatsby-image'
|
||||
import 'intersection-observer'
|
||||
import './ProjectImage.scss'
|
||||
import styles from './ProjectImage.module.scss'
|
||||
|
||||
const ProjectImage = ({ sizes, alt }) => (
|
||||
const ProjectImage = props => (
|
||||
<Img
|
||||
className="project__image"
|
||||
outerWrapperClassName="project__image-wrap"
|
||||
className={styles.project__image}
|
||||
outerWrapperClassName={styles.project__imagewrap}
|
||||
backgroundColor="#6b7f88"
|
||||
sizes={sizes}
|
||||
alt={alt}
|
||||
fluid={props.fluid}
|
||||
alt={props.alt}
|
||||
/>
|
||||
)
|
||||
|
||||
ProjectImage.propTypes = {
|
||||
sizes: PropTypes.object.isRequired,
|
||||
fluid: PropTypes.object.isRequired,
|
||||
alt: PropTypes.string
|
||||
}
|
||||
|
||||
export const projectImage = graphql`
|
||||
fragment ProjectImageSizes on ImageSharp {
|
||||
sizes(maxWidth: 1200, quality: 85) {
|
||||
...GatsbyImageSharpSizes_noBase64
|
||||
fragment ProjectImageFluid on ImageSharp {
|
||||
fluid(maxWidth: 1200, quality: 85) {
|
||||
...GatsbyImageSharpFluid_withWebp
|
||||
}
|
||||
}
|
||||
`
|
||||
|
@ -1,6 +1,6 @@
|
||||
@import 'variables';
|
||||
|
||||
.project__image-wrap {
|
||||
.project__imagewrap {
|
||||
max-height: 100vh;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
@ -14,7 +14,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dark & {
|
||||
:global(.dark) & {
|
||||
box-shadow: 0 3px 5px rgba(darken($brand-main, 20%), .15),
|
||||
0 5px 16px rgba(darken($brand-main, 20%), .15);
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { StaticQuery, graphql } from 'gatsby'
|
||||
import Helmet from 'react-helmet'
|
||||
|
||||
const TypekitScript = props => (
|
||||
const TypekitScript = typekitID => (
|
||||
<script>
|
||||
{`
|
||||
(function(d) {
|
||||
var config = {
|
||||
kitId: '${props.id}',
|
||||
kitId: '${typekitID}',
|
||||
scriptTimeout: 3000,
|
||||
async: true
|
||||
},
|
||||
@ -17,13 +18,30 @@ const TypekitScript = props => (
|
||||
</script>
|
||||
)
|
||||
|
||||
const Typekit = props => (
|
||||
<Helmet>
|
||||
<link rel="dns-prefetch" href="https://use.typekit.net/" />
|
||||
<link rel="dns-prefetch" href="https://p.typekit.net/" />
|
||||
const Typekit = () => (
|
||||
<StaticQuery
|
||||
query={graphql`
|
||||
query {
|
||||
dataYaml {
|
||||
typekitID
|
||||
}
|
||||
}
|
||||
`}
|
||||
render={data => {
|
||||
const { typekitID } = data.dataYaml
|
||||
|
||||
{TypekitScript(props)}
|
||||
</Helmet>
|
||||
return (
|
||||
typekitID && (
|
||||
<Helmet>
|
||||
<link rel="dns-prefetch" href="https://use.typekit.net/" />
|
||||
<link rel="dns-prefetch" href="https://p.typekit.net/" />
|
||||
|
||||
{TypekitScript(typekitID)}
|
||||
</Helmet>
|
||||
)
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
export default Typekit
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import FileSaver from 'file-saver'
|
||||
import vCard from '../../lib/vcf/vcard'
|
||||
import vCard from 'vcf'
|
||||
|
||||
class Vcard extends PureComponent {
|
||||
constructor(props) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Fragment, PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { StaticQuery, graphql } from 'gatsby'
|
||||
import { MoveIn } from '../atoms/Animations'
|
||||
import styles from './Availability.module.scss'
|
||||
|
||||
@ -9,35 +10,51 @@ class Availability extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { availability } = this.props.meta
|
||||
const { status, available, unavailable } = availability
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{!this.props.hide && (
|
||||
<MoveIn>
|
||||
<aside
|
||||
className={
|
||||
<StaticQuery
|
||||
query={graphql`
|
||||
query {
|
||||
dataYaml {
|
||||
availability {
|
||||
status
|
||||
? `${styles.availability} ${styles.available}`
|
||||
: `${styles.availability} ${styles.unavailable}`
|
||||
available
|
||||
unavailable
|
||||
}
|
||||
>
|
||||
<p
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: status ? available : unavailable
|
||||
}}
|
||||
/>
|
||||
</aside>
|
||||
</MoveIn>
|
||||
)}
|
||||
</Fragment>
|
||||
}
|
||||
}
|
||||
`}
|
||||
render={data => {
|
||||
const { availability } = data.dataYaml
|
||||
const { status, available, unavailable } = availability
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{!this.props.hide && (
|
||||
<MoveIn>
|
||||
<aside
|
||||
className={
|
||||
status
|
||||
? `${styles.availability} ${styles.available}`
|
||||
: `${styles.availability} ${styles.unavailable}`
|
||||
}
|
||||
>
|
||||
<p
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: status ? available : unavailable
|
||||
}}
|
||||
/>
|
||||
</aside>
|
||||
</MoveIn>
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Availability.propTypes = {
|
||||
meta: PropTypes.object,
|
||||
hide: PropTypes.bool
|
||||
}
|
||||
|
||||
|
@ -2,11 +2,11 @@ import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { FadeIn } from '../atoms/Animations'
|
||||
|
||||
import { ReactComponent as Email } from '../../images/email.svg'
|
||||
import { ReactComponent as Blog } from '../../images/blog.svg'
|
||||
import { ReactComponent as Twitter } from '../../images/twitter.svg'
|
||||
import { ReactComponent as GitHub } from '../../images/github.svg'
|
||||
import { ReactComponent as Dribbble } from '../../images/dribbble.svg'
|
||||
import Email from '../svg/Email'
|
||||
import Blog from '../svg/Blog'
|
||||
import Twitter from '../svg/Twitter'
|
||||
import GitHub from '../svg/Github'
|
||||
import Dribbble from '../svg/Dribbble'
|
||||
|
||||
import icons from '../atoms/Icons.module.scss'
|
||||
import styles from './Networks.module.scss'
|
||||
|
@ -1,12 +1,13 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { ReactComponent as Link } from '../../images/link.svg'
|
||||
import { ReactComponent as Download } from '../../images/download.svg'
|
||||
import { ReactComponent as Info } from '../../images/info.svg'
|
||||
import { ReactComponent as Styleguide } from '../../images/styleguide.svg'
|
||||
import { ReactComponent as GitHub } from '../../images/github.svg'
|
||||
import { ReactComponent as Dribbble } from '../../images/dribbble.svg'
|
||||
import Button from '../atoms/Button'
|
||||
import Link from '../svg/Link'
|
||||
import Download from '../svg/Download'
|
||||
import Info from '../svg/Info'
|
||||
import Styleguide from '../svg/Styleguide'
|
||||
import GitHub from '../svg/Github'
|
||||
import Dribbble from '../svg/Dribbble'
|
||||
|
||||
import icons from '../atoms/Icons.module.scss'
|
||||
import styles from './ProjectLinks.module.scss'
|
||||
@ -42,10 +43,10 @@ const ProjectLinks = ({ links }) => (
|
||||
|
||||
return (
|
||||
<li key={title}>
|
||||
<a href={url}>
|
||||
<Button href={url}>
|
||||
<LinkIcon title={title} className={icons.icon} />
|
||||
{title}
|
||||
</a>
|
||||
</Button>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
|
@ -16,49 +16,6 @@
|
||||
margin-left: $spacer / 2;
|
||||
margin-bottom: $spacer / 2;
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-right: $spacer / 3;
|
||||
transition: .2s ease-out;
|
||||
margin-bottom: -.1rem;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
color: $brand-cyan;
|
||||
text-align: center;
|
||||
border-radius: .25rem;
|
||||
padding: $spacer / 4 $spacer / 2;
|
||||
transition-property: all;
|
||||
background: rgba(#fff, .15);
|
||||
|
||||
svg {
|
||||
fill: $brand-grey-light;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
transform: translate3d(0, -.1rem, 0);
|
||||
box-shadow: 0 6px 10px rgba($brand-main, .1),
|
||||
0 10px 25px rgba($brand-main, .1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transition: none;
|
||||
background: rgba(#fff, .15);
|
||||
}
|
||||
|
||||
:global(.dark) & {
|
||||
background: darken($body-background-color--dark, 1%);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
box-shadow: 0 6px 10px rgba(darken($brand-main, 20%), .1),
|
||||
0 10px 25px rgba(darken($brand-main, 20%), .1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
|
@ -1,14 +1,15 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Link from 'gatsby-link'
|
||||
import { Link, graphql, StaticQuery } from 'gatsby'
|
||||
import Img from 'gatsby-image'
|
||||
import FullWidth from '../atoms/FullWidth'
|
||||
import styles from './ProjectNav.module.scss'
|
||||
|
||||
const ProjectLink = ({ node }) => (
|
||||
<Link className={styles.link} to={node.slug}>
|
||||
<Img
|
||||
className={styles.image}
|
||||
sizes={node.img.childImageSharp.sizes}
|
||||
fluid={node.img.childImageSharp.fluid}
|
||||
alt={node.title}
|
||||
/>
|
||||
<h1 className={styles.title}>{node.title}</h1>
|
||||
@ -19,52 +20,86 @@ class ProjectNav extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
scrolledToCurrent: false
|
||||
}
|
||||
|
||||
this.scrollToCurrent = this.scrollToCurrent.bind(this)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.scrollToCurrent()
|
||||
this.setState({ scrolledToCurrent: true })
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.scrollToCurrent()
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.setState({ scrolledToCurrent: false })
|
||||
}
|
||||
|
||||
scrollToCurrent = () => {
|
||||
const current = this.currentItem
|
||||
const currentLeft = current.getBoundingClientRect().left
|
||||
const currentWidth = current.clientWidth
|
||||
const finalPosition = currentLeft - window.innerWidth / 2 + currentWidth / 2
|
||||
|
||||
this.scrollContainer.scrollLeft = finalPosition
|
||||
this.scrollContainer.scrollLeft += finalPosition
|
||||
}
|
||||
|
||||
render() {
|
||||
const { projects, project } = this.props
|
||||
const { slug } = this.props
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={styles.projectNav}
|
||||
ref={node => {
|
||||
this.scrollContainer = node
|
||||
}}
|
||||
>
|
||||
{projects.map(({ node }) => {
|
||||
const current = node.slug === project.slug
|
||||
<StaticQuery
|
||||
query={graphql`
|
||||
query {
|
||||
allProjectsYaml {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
slug
|
||||
img {
|
||||
childImageSharp {
|
||||
fluid(maxWidth: 500, quality: 85) {
|
||||
...GatsbyImageSharpFluid_noBase64
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`}
|
||||
render={data => {
|
||||
const projects = data.allProjectsYaml.edges
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.item}
|
||||
key={node.slug}
|
||||
ref={node => {
|
||||
if (current) this.currentItem = node
|
||||
}}
|
||||
>
|
||||
<ProjectLink node={node} />
|
||||
</div>
|
||||
<FullWidth>
|
||||
<nav
|
||||
className={styles.projectNav}
|
||||
ref={node => (this.scrollContainer = node)}
|
||||
>
|
||||
{projects.map(({ node }) => {
|
||||
const current = node.slug === slug
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.item}
|
||||
key={node.slug}
|
||||
ref={node => current && (this.currentItem = node)}
|
||||
>
|
||||
<ProjectLink node={node} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
</FullWidth>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -74,8 +109,7 @@ ProjectLink.propTypes = {
|
||||
}
|
||||
|
||||
ProjectNav.propTypes = {
|
||||
projects: PropTypes.array,
|
||||
project: PropTypes.object
|
||||
slug: PropTypes.string
|
||||
}
|
||||
|
||||
export default ProjectNav
|
||||
|
@ -1,7 +1,7 @@
|
||||
@import 'variables';
|
||||
|
||||
.projectNav {
|
||||
composes: fullWidth from '../atoms/FullWidth.module.scss';
|
||||
// composes: fullWidth from '../atoms/FullWidth.module.scss';
|
||||
white-space: nowrap;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
|
@ -16,11 +16,12 @@
|
||||
text-align: center;
|
||||
background: rgba(#fff, .15);
|
||||
border-radius: .25rem;
|
||||
border: .05rem solid transparent;
|
||||
color: $brand-grey-light;
|
||||
margin-left: $spacer / 2;
|
||||
margin-bottom: $spacer / 2;
|
||||
flex: 0 0 calc(50% - #{$spacer / 2});
|
||||
font-size: $font-size-base;
|
||||
font-size: $font-size-small;
|
||||
|
||||
:global(.dark) & {
|
||||
background: darken($body-background-color--dark, 1%);
|
||||
|
@ -1,68 +1,79 @@
|
||||
import React, { PureComponent, Fragment } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Helmet from 'react-helmet'
|
||||
import { FadeIn } from '../atoms/Animations'
|
||||
import { ReactComponent as Day } from '../../images/day.svg'
|
||||
import { ReactComponent as Night } from '../../images/night.svg'
|
||||
import Day from '../svg/Day'
|
||||
import Night from '../svg/Night'
|
||||
import styles from './ThemeSwitch.module.scss'
|
||||
|
||||
const ThemeToggle = props => {
|
||||
return (
|
||||
<span
|
||||
id="toggle"
|
||||
className={styles.checkboxContainer}
|
||||
aria-live="assertive"
|
||||
>
|
||||
<Day className={props.dark ? null : 'active'} />
|
||||
<span className={styles.checkboxFake} />
|
||||
<Night className={props.dark ? 'active' : null} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
const ThemeToggle = props => (
|
||||
<span id="toggle" className={styles.checkboxContainer} aria-live="assertive">
|
||||
<Day className={props.dark ? null : 'active'} />
|
||||
<span className={styles.checkboxFake} />
|
||||
<Night className={props.dark ? 'active' : null} />
|
||||
</span>
|
||||
)
|
||||
|
||||
class ThemeSwitch extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = { dark: false }
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const now = new Date().getHours()
|
||||
|
||||
if (now >= 19 || now <= 7) {
|
||||
this.setState({ dark: true })
|
||||
}
|
||||
this.state = { dark: null }
|
||||
}
|
||||
|
||||
isDark = () => this.state.dark === true
|
||||
|
||||
handleChange = () => {
|
||||
this.setState({ dark: !this.isDark() })
|
||||
darkLocalStorageMode = darkLocalStorage => {
|
||||
if (darkLocalStorage === 'true') {
|
||||
this.setState({ dark: true })
|
||||
} else {
|
||||
this.setState({ dark: false })
|
||||
}
|
||||
}
|
||||
|
||||
darkMode = now => {
|
||||
if (!this.isDark() && (now >= 19 || now <= 7)) {
|
||||
this.setState({ dark: true })
|
||||
} else {
|
||||
this.setState({ dark: null })
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const now = new Date().getHours()
|
||||
const darkLocalStorage = localStorage.getItem('dark')
|
||||
|
||||
if (darkLocalStorage) {
|
||||
this.darkLocalStorageMode(darkLocalStorage)
|
||||
} else {
|
||||
this.darkMode(now)
|
||||
}
|
||||
}
|
||||
|
||||
handleChange = event => {
|
||||
this.setState({ dark: event.target.checked })
|
||||
localStorage.setItem('dark', event.target.checked)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<Helmet>
|
||||
<body className={this.state.dark ? 'dark' : null} />
|
||||
<body className={this.isDark() ? 'dark' : null} />
|
||||
</Helmet>
|
||||
<FadeIn>
|
||||
<aside className={styles.themeSwitch}>
|
||||
<label className={styles.checkbox}>
|
||||
<span className={styles.label}>Toggle Night Mode</span>
|
||||
<input
|
||||
onChange={this.handleChange}
|
||||
type="checkbox"
|
||||
name="toggle"
|
||||
value="toggle"
|
||||
aria-describedby="toggle"
|
||||
checked={this.state.dark}
|
||||
/>
|
||||
<ThemeToggle dark={this.state.dark} />
|
||||
</label>
|
||||
</aside>
|
||||
</FadeIn>
|
||||
<aside className={styles.themeSwitch}>
|
||||
<label className={styles.checkbox}>
|
||||
<span className={styles.label}>Toggle Night Mode</span>
|
||||
<input
|
||||
onChange={this.handleChange}
|
||||
type="checkbox"
|
||||
name="toggle"
|
||||
value="toggle"
|
||||
aria-describedby="toggle"
|
||||
checked={this.isDark()}
|
||||
/>
|
||||
<ThemeToggle dark={this.isDark()} />
|
||||
</label>
|
||||
</aside>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { StaticQuery, graphql } from 'gatsby'
|
||||
import Vcard from '../atoms/Vcard'
|
||||
import LogoUnit from '../atoms/LogoUnit'
|
||||
import Networks from '../molecules/Networks'
|
||||
@ -14,24 +15,43 @@ class Footer extends PureComponent {
|
||||
|
||||
render() {
|
||||
const meta = this.props.meta
|
||||
const pkg = this.props.pkg
|
||||
|
||||
return (
|
||||
<footer className={styles.footer}>
|
||||
<LogoUnit meta={meta} minimal />
|
||||
<Networks meta={meta} minimal />
|
||||
<StaticQuery
|
||||
query={graphql`
|
||||
query {
|
||||
# the package.json file
|
||||
portfolioJson {
|
||||
name
|
||||
homepage
|
||||
repository
|
||||
bugs
|
||||
}
|
||||
}
|
||||
`}
|
||||
render={data => {
|
||||
const pkg = data.portfolioJson
|
||||
|
||||
<p className={styles.footer__actions}>
|
||||
<Vcard meta={meta} />
|
||||
<a href={meta.gpg}>PGP/GPG key</a>
|
||||
<a href={pkg.bugs}>Found a bug?</a>
|
||||
</p>
|
||||
<p className={styles.footer__copyright}>
|
||||
<small>
|
||||
© {this.state.year} {meta.title} — All Rights Reserved
|
||||
</small>
|
||||
</p>
|
||||
</footer>
|
||||
return (
|
||||
<footer className={styles.footer}>
|
||||
<LogoUnit meta={meta} minimal />
|
||||
<Networks meta={meta} minimal />
|
||||
|
||||
<p className={styles.footer__actions}>
|
||||
<Vcard meta={meta} />
|
||||
<a href={meta.gpg}>PGP/GPG key</a>
|
||||
<a href={pkg.bugs}>Found a bug?</a>
|
||||
</p>
|
||||
<p className={styles.footer__copyright}>
|
||||
<small>
|
||||
© {this.state.year} {meta.title} — All Rights
|
||||
Reserved
|
||||
</small>
|
||||
</p>
|
||||
</footer>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -38,5 +38,5 @@
|
||||
}
|
||||
|
||||
.footer__copyright {
|
||||
opacity: .6;
|
||||
opacity: .7;
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import Link from 'gatsby-link'
|
||||
import { Link } from 'gatsby'
|
||||
import PropTypes from 'prop-types'
|
||||
import { FadeIn } from '../atoms/Animations'
|
||||
import Networks from '../molecules/Networks'
|
||||
import Availability from '../molecules/Availability'
|
||||
import ThemeSwitch from '../molecules/ThemeSwitch'
|
||||
@ -11,53 +10,34 @@ import styles from './Header.module.scss'
|
||||
class Header extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = { classes: 'header' }
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.toggleClasses()
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.toggleClasses()
|
||||
}
|
||||
|
||||
toggleClasses = () => {
|
||||
if (this.props.isHomepage) {
|
||||
this.setState({ classes: styles.header })
|
||||
} else {
|
||||
this.setState({ classes: `${styles.header} ${styles.minimal}` })
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const meta = this.props.meta
|
||||
const isHomepage = this.props.isHomepage
|
||||
const { isHomepage, meta } = this.props
|
||||
|
||||
return (
|
||||
<header className={this.state.classes}>
|
||||
<header
|
||||
className={
|
||||
isHomepage ? `${styles.header}` : `${styles.header} ${styles.minimal}`
|
||||
}
|
||||
>
|
||||
<ThemeSwitch />
|
||||
<FadeIn>
|
||||
<Link className={styles.header__link} to={'/'}>
|
||||
<LogoUnit meta={meta} minimal={!isHomepage} />
|
||||
</Link>
|
||||
</FadeIn>
|
||||
|
||||
<Link className={styles.header__link} to={'/'}>
|
||||
<LogoUnit meta={meta} minimal={!isHomepage} />
|
||||
</Link>
|
||||
|
||||
<Networks meta={meta} hide={!isHomepage} />
|
||||
|
||||
<Availability
|
||||
meta={meta}
|
||||
hide={!isHomepage && !meta.availability.status}
|
||||
/>
|
||||
<Availability hide={!isHomepage && !meta.availability.status} />
|
||||
</header>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
meta: PropTypes.object,
|
||||
isHomepage: PropTypes.bool
|
||||
isHomepage: PropTypes.bool,
|
||||
meta: PropTypes.object
|
||||
}
|
||||
|
||||
export default Header
|
||||
|
@ -1,118 +0,0 @@
|
||||
import React, { Component, Fragment } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import withRouter from 'react-router-dom/withRouter'
|
||||
import TransitionGroup from 'react-transition-group/TransitionGroup'
|
||||
import Head from '../components/atoms/Head'
|
||||
import Header from '../components/organisms/Header'
|
||||
import Footer from '../components/organisms/Footer'
|
||||
import { FadeIn } from '../components/atoms/Animations'
|
||||
import './index.scss'
|
||||
|
||||
class TransitionHandler extends Component {
|
||||
shouldComponentUpdate() {
|
||||
return this.props.location.pathname === window.location.pathname
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children } = this.props
|
||||
return <div className="transition-container">{children}</div>
|
||||
}
|
||||
}
|
||||
|
||||
const Main = ({ children }) => <main className="screen">{children}</main>
|
||||
|
||||
const TemplateWrapper = ({ data, location, children }) => {
|
||||
const meta = data.dataYaml
|
||||
const pkg = data.portfolioJson
|
||||
const isHomepage = location.pathname === '/'
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Head meta={meta} />
|
||||
<Header meta={meta} isHomepage={isHomepage} />
|
||||
|
||||
<TransitionGroup component={Main} appear={true}>
|
||||
<FadeIn
|
||||
key={location.pathname}
|
||||
timeout={{ enter: 200, exit: 150, appear: 200 }}
|
||||
>
|
||||
<TransitionHandler location={location}>
|
||||
{children()}
|
||||
</TransitionHandler>
|
||||
</FadeIn>
|
||||
</TransitionGroup>
|
||||
|
||||
<Footer meta={meta} pkg={pkg} />
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
TransitionHandler.propTypes = {
|
||||
children: PropTypes.any,
|
||||
location: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
Main.propTypes = {
|
||||
children: PropTypes.any
|
||||
}
|
||||
|
||||
TemplateWrapper.propTypes = {
|
||||
children: PropTypes.func,
|
||||
data: PropTypes.object.isRequired,
|
||||
location: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default withRouter(TemplateWrapper)
|
||||
|
||||
export const query = graphql`
|
||||
query metaQuery {
|
||||
# the data/meta.yml file
|
||||
dataYaml {
|
||||
title
|
||||
tagline
|
||||
description
|
||||
url
|
||||
email
|
||||
avatar {
|
||||
childImageSharp {
|
||||
original: resize {
|
||||
src
|
||||
}
|
||||
small: resize(width: 256) {
|
||||
src
|
||||
}
|
||||
}
|
||||
}
|
||||
img {
|
||||
childImageSharp {
|
||||
resize(width: 980) {
|
||||
src
|
||||
}
|
||||
}
|
||||
}
|
||||
social {
|
||||
Email
|
||||
Blog
|
||||
Twitter
|
||||
GitHub
|
||||
Dribbble
|
||||
}
|
||||
availability {
|
||||
status
|
||||
available
|
||||
unavailable
|
||||
}
|
||||
gpg
|
||||
addressbook
|
||||
typekitID
|
||||
}
|
||||
|
||||
# the package.json file
|
||||
portfolioJson {
|
||||
name
|
||||
homepage
|
||||
repository
|
||||
bugs
|
||||
}
|
||||
}
|
||||
`
|
@ -1,71 +0,0 @@
|
||||
var camelCase = require('camel-case') // that's the only change
|
||||
var Property = require('./property')
|
||||
|
||||
function set(object, key, value) {
|
||||
if (Array.isArray(object[key])) {
|
||||
object[key].push(value)
|
||||
} else if (object[key] != null) {
|
||||
object[key] = [object[key], value]
|
||||
} else {
|
||||
object[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
function createParams(params, param) {
|
||||
var parts = param.split('=')
|
||||
var k = camelCase(parts[0])
|
||||
var value = parts[1]
|
||||
|
||||
if (value == null || value === '') {
|
||||
value = parts[0]
|
||||
k = 'type'
|
||||
}
|
||||
|
||||
if (k === 'type') {
|
||||
value
|
||||
.toLowerCase()
|
||||
.split(',')
|
||||
.forEach(function(value) {
|
||||
set(params, k, value)
|
||||
})
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
set(params, k, value)
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
function parseLines(lines) {
|
||||
var data = {}
|
||||
|
||||
// NOTE: Line format:
|
||||
// PROPERTY[;PARAMETER[=VALUE]]:Attribute[;Attribute]
|
||||
var line = null
|
||||
var pattern = /^([^;:]+)((?:;(?:[^;:]+))*)(?:\:(.+))?$/i // eslint-disable-line no-useless-escape
|
||||
var len = lines.length - 1
|
||||
|
||||
for (var i = 1; i < len; i++) {
|
||||
line = lines[i]
|
||||
|
||||
var match = pattern.exec(line)
|
||||
if (!match) continue
|
||||
|
||||
var name = match[1].split('.')
|
||||
var property = name.pop()
|
||||
var group = name.pop()
|
||||
var value = match[3]
|
||||
var params = match[2] ? match[2].replace(/^;|;$/g, '').split(';') : []
|
||||
|
||||
var propParams = params.reduce(createParams, group ? { group: group } : {})
|
||||
var propName = camelCase(property)
|
||||
var propVal = new Property(propName, value, propParams)
|
||||
|
||||
set(data, propName, propVal)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
module.exports = parseLines
|
@ -1,146 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
/**
|
||||
* vCard Property
|
||||
* @constructor
|
||||
* @memberOf vCard
|
||||
* @param {String} field
|
||||
* @param {String} value
|
||||
* @param {Object} params
|
||||
* @return {Property}
|
||||
*/
|
||||
function Property(field, value, params) {
|
||||
if (!(this instanceof Property)) return new Property(value)
|
||||
|
||||
if (params != null) Object.assign(this, params)
|
||||
|
||||
this._field = field
|
||||
this._data = value
|
||||
|
||||
Object.defineProperty(this, '_field', { enumerable: false })
|
||||
Object.defineProperty(this, '_data', { enumerable: false })
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a vCard.Property from jCard data
|
||||
* @param {Array} data
|
||||
* @return {Property}
|
||||
*/
|
||||
Property.fromJSON = function(data) {
|
||||
var field = data[0]
|
||||
var params = data[1]
|
||||
|
||||
if (!/text/i.test(data[2])) params.value = data[2]
|
||||
|
||||
var value = Array.isArray(data[3]) ? data[3].join(';') : data[3]
|
||||
|
||||
return new Property(field, value, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a string into capitalized dash-case
|
||||
* @internal used by `Property#toString()`
|
||||
* @param {String} value
|
||||
* @return {String}
|
||||
* @ignore
|
||||
*/
|
||||
function capitalDashCase(value) {
|
||||
return value.replace(/([A-Z])/g, '-$1').toUpperCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Property prototype
|
||||
* @type {Object}
|
||||
*/
|
||||
Property.prototype = {
|
||||
constructor: Property,
|
||||
|
||||
/**
|
||||
* Check whether the property is of a given type
|
||||
* @param {String} type
|
||||
* @return {Boolean}
|
||||
*/
|
||||
is: function(type) {
|
||||
type = (type + '').toLowerCase()
|
||||
return Array.isArray(this.type)
|
||||
? this.type.indexOf(type)
|
||||
: this.type === type
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether the property is empty
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isEmpty: function() {
|
||||
return this._data == null && Object.keys(this).length === 0
|
||||
},
|
||||
|
||||
/**
|
||||
* Clone the property
|
||||
* @return {Property}
|
||||
*/
|
||||
clone: function() {
|
||||
return new Property(this._field, this._data, this)
|
||||
},
|
||||
|
||||
/**
|
||||
* Format the property as vcf with given version
|
||||
* @param {String} version
|
||||
* @return {String}
|
||||
*/
|
||||
toString: function(version) {
|
||||
var propName =
|
||||
(this.group ? this.group + '.' : '') + capitalDashCase(this._field)
|
||||
var keys = Object.keys(this)
|
||||
var params = []
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (keys[i] === 'group') continue
|
||||
params.push(capitalDashCase(keys[i]) + '=' + this[keys[i]])
|
||||
}
|
||||
|
||||
return (
|
||||
propName +
|
||||
(params.length ? ';' + params.join(';') : params) +
|
||||
':' +
|
||||
(Array.isArray(this._data) ? this._data.join(';') : this._data)
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the property's value
|
||||
* @return {String}
|
||||
*/
|
||||
valueOf: function() {
|
||||
return this._data
|
||||
},
|
||||
|
||||
/**
|
||||
* Format the property as jCard data
|
||||
* @return {Array}
|
||||
*/
|
||||
toJSON: function() {
|
||||
var params = Object.assign({}, this)
|
||||
|
||||
if (params.value === 'text') {
|
||||
params.value = void 0
|
||||
delete params.value
|
||||
}
|
||||
|
||||
var data = [this._field, params, this.value || 'text']
|
||||
|
||||
switch (this._field) {
|
||||
default:
|
||||
data.push(this._data)
|
||||
break
|
||||
case 'adr':
|
||||
case 'n':
|
||||
data.push(this._data.split(';'))
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
// Exports
|
||||
module.exports = Property
|
@ -1,322 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
/**
|
||||
* vCard
|
||||
* @constructor
|
||||
* @return {vCard}
|
||||
*/
|
||||
function vCard() {
|
||||
if (!(this instanceof vCard)) return new vCard()
|
||||
|
||||
/** @type {String} Version number */
|
||||
this.version = vCard.versions[vCard.versions.length - 1]
|
||||
/** @type {Object} Card data */
|
||||
this.data = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* vCard MIME type
|
||||
* @type {String}
|
||||
*/
|
||||
vCard.mimeType = 'text/vcard'
|
||||
|
||||
/**
|
||||
* vCard file extension
|
||||
* @type {String}
|
||||
*/
|
||||
vCard.extension = '.vcf'
|
||||
|
||||
/**
|
||||
* vCard versions
|
||||
* @type {Array}
|
||||
*/
|
||||
vCard.versions = ['2.1', '3.0', '4.0']
|
||||
|
||||
/**
|
||||
* Folds a long line according to the RFC 5322.
|
||||
* @see http://tools.ietf.org/html/rfc5322#section-2.1.1
|
||||
* @param {String} input
|
||||
* @param {Number} maxLength
|
||||
* @param {Boolean} hardWrap
|
||||
* @return {String}
|
||||
*/
|
||||
vCard.foldLine = require('foldline')
|
||||
|
||||
/**
|
||||
* Normalizes input (cast to string, line folding, whitespace)
|
||||
* @param {String} input
|
||||
* @return {String}
|
||||
*/
|
||||
vCard.normalize = function(input) {
|
||||
return (
|
||||
(input + '')
|
||||
// Trim whitespace
|
||||
.replace(/^[\s\r\n]+|[\s\r\n]+$/g, '')
|
||||
// Trim blank lines
|
||||
.replace(/(\r?\n)\s*(\r?\n)|$/g, '$1')
|
||||
// Unfold folded lines
|
||||
.replace(/\r?\n[\x20\x09]+/g, '') // eslint-disable-line no-control-regex
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a given version is supported
|
||||
* @param {String} version
|
||||
* @return {Boolean}
|
||||
*/
|
||||
vCard.isSupported = function(version) {
|
||||
return /^\d\.\d$/.test(version) && vCard.versions.indexOf(version) !== -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string or buffer into a vCard object
|
||||
* @param {String|Buffer} value
|
||||
* @return {Array<vCard>}
|
||||
*/
|
||||
vCard.parse = function(value) {
|
||||
var objects = (value + '').split(/(?=BEGIN\:VCARD)/gi) // eslint-disable-line no-useless-escape
|
||||
var cards = []
|
||||
|
||||
for (var i = 0; i < objects.length; i++) {
|
||||
cards.push(new vCard().parse(objects[i]))
|
||||
}
|
||||
|
||||
return cards
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an array of vcf formatted lines
|
||||
* @internal used by `vCard#parse()`
|
||||
* @type {Function}
|
||||
*/
|
||||
vCard.parseLines = require('./parse-lines')
|
||||
|
||||
/**
|
||||
* Constructs a vCard from jCard data
|
||||
* @param {Array} jcard
|
||||
* @return {vCard}
|
||||
*/
|
||||
vCard.fromJSON = function(jcard) {
|
||||
jcard = typeof jcard === 'string' ? JSON.parse(jcard) : jcard
|
||||
|
||||
if (jcard == null || !Array.isArray(jcard)) return new vCard()
|
||||
|
||||
if (!/vcard/i.test(jcard[0])) throw new Error('Object not in jCard format')
|
||||
|
||||
var card = new vCard()
|
||||
|
||||
jcard[1].forEach(function(prop) {
|
||||
card.addProperty(vCard.Property.fromJSON(prop))
|
||||
})
|
||||
|
||||
return card
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a card object according to the given version
|
||||
* @param {vCard} card
|
||||
* @param {String} version
|
||||
* @return {String}
|
||||
*/
|
||||
vCard.format = function(card, version) {
|
||||
version = version || card.version || vCard.versions[vCard.versions.length - 1]
|
||||
|
||||
if (!vCard.isSupported(version))
|
||||
throw new Error('Unsupported vCard version "' + version + '"')
|
||||
|
||||
var vcf = []
|
||||
|
||||
vcf.push('BEGIN:VCARD')
|
||||
vcf.push('VERSION:' + version)
|
||||
|
||||
var props = Object.keys(card.data)
|
||||
var prop = ''
|
||||
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
if (props[i] === 'version') continue
|
||||
prop = card.data[props[i]]
|
||||
if (Array.isArray(prop)) {
|
||||
for (var k = 0; k < prop.length; k++) {
|
||||
if (prop[k].isEmpty()) continue
|
||||
vcf.push(vCard.foldLine(prop[k].toString(version), 75))
|
||||
}
|
||||
} else if (!prop.isEmpty()) {
|
||||
vcf.push(vCard.foldLine(prop.toString(version), 75))
|
||||
}
|
||||
}
|
||||
|
||||
vcf.push('END:VCARD')
|
||||
|
||||
return vcf.join('\n')
|
||||
}
|
||||
|
||||
// vCard Property constructor
|
||||
vCard.Property = require('./property')
|
||||
|
||||
/**
|
||||
* vCard prototype
|
||||
* @type {Object}
|
||||
*/
|
||||
vCard.prototype = {
|
||||
constructor: vCard,
|
||||
|
||||
/**
|
||||
* Get a vCard property
|
||||
* @param {String} key
|
||||
* @return {Object|Array}
|
||||
*/
|
||||
get: function(key) {
|
||||
if (this.data[key] == null) {
|
||||
return this.data[key]
|
||||
}
|
||||
|
||||
if (Array.isArray(this.data[key])) {
|
||||
return this.data[key].map(function(prop) {
|
||||
return prop.clone()
|
||||
})
|
||||
} else {
|
||||
return this.data[key].clone()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a vCard property
|
||||
* @param {String} key
|
||||
* @param {String} value
|
||||
* @param {Object} params
|
||||
*/
|
||||
set: function(key, value, params) {
|
||||
return this.setProperty(new vCard.Property(key, value, params))
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a vCard property
|
||||
* @param {String} key
|
||||
* @param {String} value
|
||||
* @param {Object} params
|
||||
*/
|
||||
add: function(key, value, params) {
|
||||
var prop = new vCard.Property(key, value, params)
|
||||
this.addProperty(prop)
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a vCard property from an already
|
||||
* constructed vCard.Property
|
||||
* @param {vCard.Property} prop
|
||||
*/
|
||||
setProperty: function(prop) {
|
||||
this.data[prop._field] = prop
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a vCard property from an already
|
||||
* constructed vCard.Property
|
||||
* @param {vCard.Property} prop
|
||||
*/
|
||||
addProperty: function(prop) {
|
||||
var key = prop._field
|
||||
|
||||
if (Array.isArray(this.data[key])) {
|
||||
this.data[key].push(prop)
|
||||
} else if (this.data[key] != null) {
|
||||
this.data[key] = [this.data[key], prop]
|
||||
} else {
|
||||
this.data[key] = prop
|
||||
}
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse a vcf formatted vCard
|
||||
* @param {String} value
|
||||
* @return {vCard}
|
||||
*/
|
||||
parse: function(value) {
|
||||
// Normalize & split
|
||||
var lines = vCard.normalize(value).split(/\r?\n/g)
|
||||
|
||||
// Keep begin and end markers
|
||||
// for eventual error messages
|
||||
var begin = lines[0]
|
||||
var version = lines[1]
|
||||
var end = lines[lines.length - 1]
|
||||
|
||||
if (!/BEGIN:VCARD/i.test(begin))
|
||||
throw new SyntaxError(
|
||||
'Invalid vCard: Expected "BEGIN:VCARD" but found "' + begin + '"'
|
||||
)
|
||||
|
||||
if (!/END:VCARD/i.test(end))
|
||||
throw new SyntaxError(
|
||||
'Invalid vCard: Expected "END:VCARD" but found "' + end + '"'
|
||||
)
|
||||
|
||||
// TODO: For version 2.1, the VERSION can be anywhere between BEGIN & END
|
||||
if (!/VERSION:\d\.\d/i.test(version))
|
||||
throw new SyntaxError(
|
||||
'Invalid vCard: Expected "VERSION:\\d.\\d" but found "' + version + '"'
|
||||
)
|
||||
|
||||
this.version = version.substring(8, 11)
|
||||
|
||||
if (!vCard.isSupported(this.version))
|
||||
throw new Error('Unsupported version "' + this.version + '"')
|
||||
|
||||
this.data = vCard.parseLines(lines)
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Format the vCard as vcf with given version
|
||||
* @param {String} version
|
||||
* @param {String} charset
|
||||
* @return {String}
|
||||
*/
|
||||
toString: function(version, charset) {
|
||||
version = version || this.version
|
||||
return vCard.format(this, version)
|
||||
},
|
||||
|
||||
/**
|
||||
* Format the card as jCard
|
||||
* @param {String} version='4.0'
|
||||
* @return {Array} jCard
|
||||
*/
|
||||
toJCard: function(version) {
|
||||
version = version || '4.0'
|
||||
|
||||
var keys = Object.keys(this.data)
|
||||
var data = [['version', {}, 'text', version]]
|
||||
var prop = null
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (keys[i] === 'version') continue
|
||||
prop = this.data[keys[i]]
|
||||
if (Array.isArray(prop)) {
|
||||
for (var k = 0; k < prop.length; k++) {
|
||||
data.push(prop[k].toJSON())
|
||||
}
|
||||
} else {
|
||||
data.push(prop.toJSON())
|
||||
}
|
||||
}
|
||||
|
||||
return ['vcard', data]
|
||||
},
|
||||
|
||||
/**
|
||||
* Format the card as jCard
|
||||
* @return {Array} jCard
|
||||
*/
|
||||
toJSON: function() {
|
||||
return this.toJCard(this.version)
|
||||
}
|
||||
}
|
||||
|
||||
// Exports
|
||||
module.exports = vCard
|
@ -1,6 +1,8 @@
|
||||
import React, { Component } from 'react'
|
||||
import Link from 'gatsby-link'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Link } from 'gatsby'
|
||||
import giphyAPI from 'giphy-js-sdk-core'
|
||||
import Layout from '../components/Layout'
|
||||
import Content from '../components/atoms/Content'
|
||||
import './404.scss'
|
||||
|
||||
@ -40,24 +42,30 @@ class NotFound extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Content className="content content--404">
|
||||
<h1>Shenanigans, page not found.</h1>
|
||||
<p>
|
||||
You might want to check the url, or{' '}
|
||||
<Link to={'/'}>go back to the homepage</Link>. Or just check out some
|
||||
fail gifs, entirely your choice.
|
||||
</p>
|
||||
<Layout location={this.props.location}>
|
||||
<Content className="content content--404">
|
||||
<h1>Shenanigans, page not found.</h1>
|
||||
<p>
|
||||
You might want to check the url, or{' '}
|
||||
<Link to={'/'}>go back to the homepage</Link>. Or just check out
|
||||
some fail gifs, entirely your choice.
|
||||
</p>
|
||||
|
||||
<video className="gif" src={this.state.gif} autoPlay loop />
|
||||
<video className="gif" src={this.state.gif} autoPlay loop />
|
||||
|
||||
<div>
|
||||
<a className="gif__action" href="#" onClick={this.handleClick}>
|
||||
Show me another cat fail gif
|
||||
</a>
|
||||
</div>
|
||||
</Content>
|
||||
<div>
|
||||
<a className="gif__action" href="#" onClick={this.handleClick}>
|
||||
Show me another cat fail gif
|
||||
</a>
|
||||
</div>
|
||||
</Content>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
NotFound.propTypes = {
|
||||
location: PropTypes.object
|
||||
}
|
||||
|
||||
export default NotFound
|
||||
|
@ -1,39 +1,43 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Link from 'gatsby-link'
|
||||
import { Link, graphql } from 'gatsby'
|
||||
import Layout from '../components/Layout'
|
||||
import ProjectImage from '../components/atoms/ProjectImage'
|
||||
import FullWidth from '../components/atoms/FullWidth'
|
||||
import styles from './index.module.scss'
|
||||
|
||||
const Home = ({ data }) => {
|
||||
const Home = ({ data, location }) => {
|
||||
const projects = data.allProjectsYaml.edges
|
||||
|
||||
return (
|
||||
<FullWidth className="projects">
|
||||
{projects.map(({ node }) => {
|
||||
const { slug, title, img } = node
|
||||
<Layout location={location}>
|
||||
<FullWidth className="projects">
|
||||
{projects.map(({ node }) => {
|
||||
const { slug, title, img } = node
|
||||
|
||||
return (
|
||||
<article className={styles.project} key={slug}>
|
||||
<Link to={slug}>
|
||||
<h1 className={styles.title}>{title}</h1>
|
||||
<ProjectImage sizes={img.childImageSharp.sizes} alt={title} />
|
||||
</Link>
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
</FullWidth>
|
||||
return (
|
||||
<article className={styles.project} key={slug}>
|
||||
<Link to={slug}>
|
||||
<h1 className={styles.title}>{title}</h1>
|
||||
<ProjectImage fluid={img.childImageSharp.fluid} alt={title} />
|
||||
</Link>
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
</FullWidth>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
Home.propTypes = {
|
||||
data: PropTypes.object
|
||||
data: PropTypes.object,
|
||||
location: PropTypes.object
|
||||
}
|
||||
|
||||
export default Home
|
||||
|
||||
export const IndexQuery = graphql`
|
||||
query IndexQuery {
|
||||
query {
|
||||
allProjectsYaml {
|
||||
edges {
|
||||
node {
|
||||
@ -41,7 +45,7 @@ export const IndexQuery = graphql`
|
||||
slug
|
||||
img {
|
||||
childImageSharp {
|
||||
...ProjectImageSizes
|
||||
...ProjectImageFluid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,20 +10,14 @@ html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: $font-size-root;
|
||||
|
||||
&.wf-loading,
|
||||
&.wf-inactive {
|
||||
font-size: $font-size-root - 2px;
|
||||
}
|
||||
background: $body-background-color;
|
||||
}
|
||||
|
||||
body {
|
||||
background: $body-background-color;
|
||||
font-family: $font-family-base;
|
||||
font-weight: $font-weight-base;
|
||||
font-size: $font-size-base;
|
||||
@ -33,6 +27,14 @@ body {
|
||||
font-feature-settings: 'liga', 'kern';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
min-height: 100vh;
|
||||
background: $body-background-color;
|
||||
transition: background .6s $easing;
|
||||
|
||||
&.dark {
|
||||
background-color: $body-background-color--dark;
|
||||
color: $text-color--dark;
|
||||
}
|
||||
}
|
||||
|
||||
p,
|
||||
@ -115,25 +117,4 @@ svg {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
background: $body-background-color;
|
||||
transition: background .6s $easing;
|
||||
|
||||
.dark & {
|
||||
background: $body-background-color--dark;
|
||||
color: $text-color--dark;
|
||||
}
|
||||
}
|
||||
|
||||
.screen {
|
||||
flex: 1;
|
||||
padding: $spacer;
|
||||
}
|
||||
|
||||
.transition-group {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.transition-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import React, { Component, Fragment } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Helmet from 'react-helmet'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import { graphql } from 'gatsby'
|
||||
import Layout from '../components/Layout'
|
||||
import Content from '../components/atoms/Content'
|
||||
import FullWidth from '../components/atoms/FullWidth'
|
||||
import ProjectImage from '../components/atoms/ProjectImage'
|
||||
@ -9,67 +11,55 @@ import ProjectTechstack from '../components/molecules/ProjectTechstack'
|
||||
import ProjectLinks from '../components/molecules/ProjectLinks'
|
||||
import ProjectNav from '../components/molecules/ProjectNav'
|
||||
import SEO from '../components/atoms/SEO'
|
||||
import './Project.scss'
|
||||
|
||||
const ProjectMeta = props => {
|
||||
const { links, techstack } = props
|
||||
import styles from './Project.module.scss'
|
||||
|
||||
return (
|
||||
<footer className="project__meta">
|
||||
{!!links && <ProjectLinks links={links} />}
|
||||
{!!techstack && <ProjectTechstack techstack={techstack} />}
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
const ProjectMeta = ({ links, techstack }) => (
|
||||
<footer className={styles.project__meta}>
|
||||
{!!links && <ProjectLinks links={links} />}
|
||||
{!!techstack && <ProjectTechstack techstack={techstack} />}
|
||||
</footer>
|
||||
)
|
||||
|
||||
const ProjectImages = props => (
|
||||
const ProjectImages = ({ projectImages, title }) => (
|
||||
<FullWidth>
|
||||
{props.projectImages.map(({ node }) => (
|
||||
<ProjectImage key={node.id} sizes={node.sizes} alt={props.title} />
|
||||
{projectImages.map(({ node }) => (
|
||||
<div className={styles.spacer} key={node.id}>
|
||||
<ProjectImage fluid={node.fluid} alt={title} />
|
||||
</div>
|
||||
))}
|
||||
</FullWidth>
|
||||
)
|
||||
|
||||
class Project extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
const Project = ({ data, location }) => {
|
||||
const meta = data.dataYaml
|
||||
const project = data.projectsYaml
|
||||
const projectImages = data.projectImages.edges
|
||||
const { title, links, techstack } = project
|
||||
const description = data.projectsYaml.description
|
||||
const descriptionWithLineBreaks = description.split('\n').join('\n\n')
|
||||
|
||||
const description = this.props.data.projectsYaml.description
|
||||
return (
|
||||
<Layout location={location}>
|
||||
<Helmet title={title} />
|
||||
|
||||
this.state = {
|
||||
descriptionWithLineBreaks: description.split('\n').join('\n\n')
|
||||
}
|
||||
}
|
||||
<SEO project={project} meta={meta} />
|
||||
|
||||
render() {
|
||||
const meta = this.props.data.dataYaml
|
||||
const projects = this.props.data.allProjectsYaml.edges
|
||||
const project = this.props.data.projectsYaml
|
||||
const projectImages = this.props.data.projectImages.edges
|
||||
const { title, links, techstack } = project
|
||||
<article className={styles.project}>
|
||||
<Content>
|
||||
<h1 className={styles.project__title}>{title}</h1>
|
||||
<ReactMarkdown
|
||||
source={descriptionWithLineBreaks}
|
||||
className={styles.project__description}
|
||||
/>
|
||||
<ProjectImages projectImages={projectImages} title={title} />
|
||||
<ProjectMeta links={links} techstack={techstack} />
|
||||
</Content>
|
||||
</article>
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Helmet title={title} />
|
||||
|
||||
<SEO project={project} meta={meta} />
|
||||
|
||||
<article className="project">
|
||||
<Content>
|
||||
<h1 className="project__title">{title}</h1>
|
||||
<ReactMarkdown
|
||||
source={this.state.descriptionWithLineBreaks}
|
||||
className="project__description"
|
||||
/>
|
||||
<ProjectImages projectImages={projectImages} title={title} />
|
||||
<ProjectMeta links={links} techstack={techstack} />
|
||||
</Content>
|
||||
</article>
|
||||
|
||||
<ProjectNav projects={projects} project={project} />
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
<ProjectNav slug={project.slug} />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
ProjectMeta.propTypes = {
|
||||
@ -84,13 +74,13 @@ ProjectImages.propTypes = {
|
||||
|
||||
Project.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
pathContext: PropTypes.object.isRequired
|
||||
location: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default Project
|
||||
|
||||
export const projectAndProjectsQuery = graphql`
|
||||
query ProjectBySlug($slug: String!) {
|
||||
query($slug: String!) {
|
||||
projectsYaml(slug: { eq: $slug }) {
|
||||
title
|
||||
slug
|
||||
@ -122,11 +112,6 @@ export const projectAndProjectsQuery = graphql`
|
||||
GitHub
|
||||
Dribbble
|
||||
}
|
||||
availability {
|
||||
status
|
||||
available
|
||||
unavailable
|
||||
}
|
||||
img {
|
||||
childImageSharp {
|
||||
resize(width: 980) {
|
||||
@ -137,29 +122,13 @@ export const projectAndProjectsQuery = graphql`
|
||||
}
|
||||
|
||||
projectImages: allImageSharp(
|
||||
filter: { id: { regex: $slug } }
|
||||
sort: { fields: [id], order: ASC }
|
||||
filter: { fluid: { originalName: { regex: $slug } } }
|
||||
sort: { fields: [fluid___originalName], order: ASC }
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...ProjectImageSizes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allProjectsYaml {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
slug
|
||||
img {
|
||||
childImageSharp {
|
||||
sizes(maxWidth: 500, quality: 85) {
|
||||
...GatsbyImageSharpSizes_noBase64
|
||||
}
|
||||
}
|
||||
}
|
||||
...ProjectImageFluid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
@import 'variables';
|
||||
@import '../components/atoms/ProjectImage.module';
|
||||
|
||||
.project {
|
||||
.project__image-wrap {
|
||||
margin-bottom: $spacer * 3;
|
||||
// .project__imagewrap {
|
||||
.spacer {
|
||||
//composes: project__imagewrap from '../components/atoms/ProjectImage.module';
|
||||
margin-bottom: $spacer * 3;
|
||||
|
||||
@media (min-width: 30rem) {
|
||||
margin-bottom: $spacer * 6;
|
||||
}
|
||||
@media (min-width: 30rem) {
|
||||
margin-bottom: $spacer * 6;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user