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:
|
version: "2"
|
||||||
- src/lib/vcf/
|
checks:
|
||||||
|
method-lines:
|
||||||
|
config:
|
||||||
|
threshold: 50 # Gatsby's StaticQuery makes render functions pretty long
|
||||||
|
@ -18,9 +18,6 @@
|
|||||||
"node": true,
|
"node": true,
|
||||||
"es6": true
|
"es6": true
|
||||||
},
|
},
|
||||||
"globals": {
|
|
||||||
"graphql": true
|
|
||||||
},
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"quotes": ["error", "single"],
|
"quotes": ["error", "single"],
|
||||||
"semi": ["error", "never"],
|
"semi": ["error", "never"],
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@ yarn-debug.log*
|
|||||||
yarn.lock
|
yarn.lock
|
||||||
package-lock.json
|
package-lock.json
|
||||||
plugins/gatsby-plugin-matomo
|
plugins/gatsby-plugin-matomo
|
||||||
|
src/components/svg
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
.cache/
|
.cache/
|
||||||
src/lib/vcf
|
|
||||||
static/
|
static/
|
||||||
public/
|
public/
|
||||||
|
@ -7,10 +7,6 @@ cache:
|
|||||||
- public
|
- public
|
||||||
|
|
||||||
install:
|
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
|
- npm i
|
||||||
|
|
||||||
script:
|
script:
|
||||||
@ -18,9 +14,9 @@ script:
|
|||||||
- npm run build
|
- npm run build
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
|
- pip install --user awscli
|
||||||
|
- export PATH=$PATH:$HOME/.local/bin
|
||||||
- npm run deploy
|
- npm run deploy
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
email: false
|
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 \
|
libpng-dev \
|
||||||
&& rm -rf /var/cache/apk/*
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
RUN npm install --global gatsby-cli --no-optional
|
|
||||||
|
|
||||||
RUN mkdir -p /portfolio
|
RUN mkdir -p /portfolio
|
||||||
WORKDIR /portfolio
|
WORKDIR /portfolio
|
||||||
VOLUME /portfolio
|
VOLUME /portfolio
|
||||||
|
34
README.md
34
README.md
@ -15,19 +15,19 @@
|
|||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
* [Features](#features)
|
- [Features](#features)
|
||||||
* [One data file to rule all pages](#one-data-file-to-rule-all-pages)
|
- [One data file to rule all pages](#one-data-file-to-rule-all-pages)
|
||||||
* [Theme switcher](#theme-switcher)
|
- [Theme switcher](#theme-switcher)
|
||||||
* [SEO component](#seo-component)
|
- [SEO component](#seo-component)
|
||||||
* [Client-side vCard creation](#client-side-vcard-creation)
|
- [Client-side vCard creation](#client-side-vcard-creation)
|
||||||
* [Matomo (formerly Piwik) analytics tracking](#matomo-formerly-piwik-analytics-tracking)
|
- [Matomo (formerly Piwik) analytics tracking](#matomo-formerly-piwik-analytics-tracking)
|
||||||
* [Project images](#project-images)
|
- [Project images](#project-images)
|
||||||
* [Importing SVG assets](#importing-svg-assets)
|
- [Importing SVG assets](#importing-svg-assets)
|
||||||
* [Development](#development)
|
- [Development](#development)
|
||||||
* [Linting](#linting)
|
- [Linting](#linting)
|
||||||
* [Add a new project](#add-a-new-project)
|
- [Add a new project](#add-a-new-project)
|
||||||
* [Deployment](#deployment)
|
- [Deployment](#deployment)
|
||||||
* [Licenses](#licenses)
|
- [Licenses](#licenses)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -79,10 +79,10 @@ All project images use one single component defined in [`src/components/atoms/Pr
|
|||||||
|
|
||||||
### Importing SVG assets
|
### 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
|
```js
|
||||||
import { ReactComponent as Logo } from '../images/logo.svg'
|
import Logo from './components/svg/Logo'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
@ -133,8 +133,8 @@ portfolio-SLUG-03.png
|
|||||||
|
|
||||||
Automatic deployments are triggered upon successful tests & builds on Travis:
|
Automatic deployments are triggered upon successful tests & builds on Travis:
|
||||||
|
|
||||||
* push to `master` initiates a live deployment
|
- push to `master` initiates a live deployment
|
||||||
* any Pull Request, and subsequent pushes to it, initiates a beta 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:
|
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 fs = require('fs')
|
||||||
const yaml = require('js-yaml')
|
const yaml = require('js-yaml')
|
||||||
const meta = yaml.load(fs.readFileSync('./data/meta.yml', 'utf8'))
|
const meta = yaml.load(fs.readFileSync('./data/meta.yml', 'utf8'))
|
||||||
const { url, matomoUrl, matomoSite } = meta
|
const { url, matomoSite, matomoUrl } = meta
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
siteMetadata: {
|
siteMetadata: {
|
||||||
siteUrl: `${url}`
|
siteUrl: `${url}`
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
'gatsby-plugin-react-next',
|
|
||||||
'gatsby-plugin-react-helmet',
|
'gatsby-plugin-react-helmet',
|
||||||
'gatsby-transformer-yaml',
|
'gatsby-transformer-yaml',
|
||||||
'gatsby-transformer-sharp',
|
'gatsby-transformer-sharp',
|
||||||
'gatsby-plugin-sharp',
|
'gatsby-plugin-sharp',
|
||||||
'gatsby-plugin-sitemap',
|
'gatsby-plugin-sitemap',
|
||||||
'gatsby-plugin-offline',
|
// 'gatsby-plugin-offline',
|
||||||
{
|
{
|
||||||
resolve: 'gatsby-transformer-json',
|
resolve: 'gatsby-transformer-json',
|
||||||
options: {
|
options: {
|
||||||
@ -59,14 +58,6 @@ module.exports = {
|
|||||||
localScript: '/piwik.js'
|
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',
|
resolve: 'gatsby-plugin-favicon',
|
||||||
options: {
|
options: {
|
||||||
|
@ -3,17 +3,28 @@ const path = require('path')
|
|||||||
// Intersection Observer polyfill
|
// Intersection Observer polyfill
|
||||||
// requires `npm install intersection-observer`
|
// requires `npm install intersection-observer`
|
||||||
// https://github.com/gatsbyjs/gatsby/issues/2288#issuecomment-334467821
|
// https://github.com/gatsbyjs/gatsby/issues/2288#issuecomment-334467821
|
||||||
exports.modifyWebpackConfig = ({ config, stage }) => {
|
// exports.onCreateWebpackConfig = ({ actions, loaders, stage }) => {
|
||||||
if (stage === 'build-html') {
|
// const { setWebpackConfig } = actions
|
||||||
config.loader('null', {
|
|
||||||
test: /intersection-observer/,
|
|
||||||
loader: 'null-loader'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.createPages = ({ boundActionCreators, graphql }) => {
|
// if (stage === 'build-html') {
|
||||||
const { createPage } = boundActionCreators
|
// 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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const template = path.resolve('src/templates/Project.jsx')
|
const template = path.resolve('src/templates/Project.jsx')
|
||||||
@ -26,44 +37,6 @@ exports.createPages = ({ boundActionCreators, graphql }) => {
|
|||||||
node {
|
node {
|
||||||
slug
|
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)
|
reject(result.errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
result.data.allProjectsYaml.edges.forEach(
|
result.data.allProjectsYaml.edges.forEach(({ node }) => {
|
||||||
({ node, previous, next }) => {
|
const slug = node.slug
|
||||||
const slug = node.slug
|
|
||||||
|
|
||||||
createPage({
|
createPage({
|
||||||
path: slug,
|
path: slug,
|
||||||
component: template,
|
component: template,
|
||||||
context: {
|
context: {
|
||||||
slug,
|
slug
|
||||||
previous,
|
}
|
||||||
next
|
})
|
||||||
}
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
|
66
package.json
66
package.json
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "portfolio",
|
"name": "@kremalicious/portfolio",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"homepage": "https://matthiaskretschmann.com",
|
"homepage": "https://matthiaskretschmann.com",
|
||||||
"repository": "github:kremalicious/portfolio",
|
"repository": "github:kremalicious/portfolio",
|
||||||
@ -9,57 +9,59 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"lint:js": "eslint ./gatsby-*.{js,jsx} && eslint ./src/**/*.{js,jsx}",
|
"lint:js": "eslint ./gatsby-*.{js,jsx} && eslint ./src/**/*.{js,jsx}",
|
||||||
"lint:css": "stylelint ./src/**/*.{css,scss}",
|
"lint:css": "stylelint ./src/**/*.{css,scss}",
|
||||||
"lint": "npm run lint:js && npm run lint:css",
|
"lint": "npm run svg && npm run lint:js && npm run lint:css",
|
||||||
"build": "gatsby build",
|
"build": "npm run svg && ./node_modules/gatsby/dist/bin/gatsby.js build",
|
||||||
"start": "docker-compose up",
|
"start": "npm run svg && ./node_modules/gatsby/dist/bin/gatsby.js develop",
|
||||||
"format": "prettier --write 'src/**/*.{js,jsx}'",
|
"format": "prettier --write 'src/**/*.{js,jsx}'",
|
||||||
"format:css": "prettier-stylelint --write --quiet 'src/**/*.{css,scss}'",
|
"format:css": "prettier-stylelint --write --quiet 'src/**/*.{css,scss}'",
|
||||||
"test": "npm run lint",
|
"test": "npm run lint",
|
||||||
"deploy": "./scripts/deploy.sh",
|
"deploy": "./scripts/deploy.sh",
|
||||||
"new": "node ./scripts/new-project.js"
|
"new": "node ./scripts/new.js",
|
||||||
|
"svg": "./scripts/svg.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"camel-case": "^3.0.0",
|
|
||||||
"file-saver": "^1.3.8",
|
"file-saver": "^1.3.8",
|
||||||
"gatsby": "^1.9.270",
|
"gatsby": "^2.0.0-beta.35",
|
||||||
"gatsby-image": "^1.0.52",
|
"gatsby-image": "^2.0.0-beta.6",
|
||||||
"gatsby-link": "^1.6.44",
|
"gatsby-plugin-favicon": "github:TuckerWhitehouse/gatsby-plugin-favicon#gatsby-v2",
|
||||||
"gatsby-plugin-favicon": "^2.1.1",
|
"gatsby-plugin-matomo": "^0.4.1",
|
||||||
"gatsby-plugin-matomo": "^0.4.0",
|
"gatsby-plugin-offline": "^2.0.0-beta.3",
|
||||||
"gatsby-plugin-offline": "^1.0.18",
|
"gatsby-plugin-react-helmet": "next",
|
||||||
"gatsby-plugin-react-helmet": "^2.0.11",
|
"gatsby-plugin-sass": "next",
|
||||||
"gatsby-plugin-react-next": "^1.0.11",
|
"gatsby-plugin-sharp": "^2.0.0-beta.5",
|
||||||
"gatsby-plugin-sass": "^1.0.26",
|
"gatsby-plugin-sitemap": "next",
|
||||||
"gatsby-plugin-sharp": "^1.6.47",
|
"gatsby-source-filesystem": "^2.0.1-beta.4",
|
||||||
"gatsby-plugin-sitemap": "^1.2.25",
|
"gatsby-transformer-json": "next",
|
||||||
"gatsby-plugin-svgr": "^1.0.1",
|
"gatsby-transformer-sharp": "next",
|
||||||
"gatsby-source-filesystem": "^1.5.38",
|
"gatsby-transformer-yaml": "next",
|
||||||
"gatsby-transformer-sharp": "^1.6.26",
|
"giphy-js-sdk-core": "^1.0.5",
|
||||||
"gatsby-transformer-yaml": "^1.5.17",
|
"graphql": "^0.13.2",
|
||||||
"giphy-js-sdk-core": "^1.0.3",
|
|
||||||
"intersection-observer": "^0.5.0",
|
"intersection-observer": "^0.5.0",
|
||||||
"js-yaml": "^3.12.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-helmet": "^5.2.0",
|
||||||
"react-markdown": "^3.3.2",
|
"react-markdown": "^3.3.4",
|
||||||
"react-transition-group": "^2.3.1",
|
"react-transition-group": "^2.4.0",
|
||||||
"vcf": "^2.0.1"
|
"vcf": "^2.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-eslint": "^8.2.3",
|
"@svgr/cli": "^2.1.1",
|
||||||
"eslint": "^5.0.0",
|
"babel-eslint": "^8.2.6",
|
||||||
|
"eslint": "^5.1.0",
|
||||||
"eslint-config-prettier": "^2.9.0",
|
"eslint-config-prettier": "^2.9.0",
|
||||||
"eslint-loader": "^2.0.0",
|
"eslint-loader": "^2.0.0",
|
||||||
"eslint-plugin-graphql": "^2.1.1",
|
"eslint-plugin-graphql": "^2.1.1",
|
||||||
"eslint-plugin-prettier": "^2.6.0",
|
"eslint-plugin-prettier": "^2.6.2",
|
||||||
"eslint-plugin-react": "^7.9.1",
|
"eslint-plugin-react": "^7.10.0",
|
||||||
"gatsby-transformer-json": "^1.0.19",
|
|
||||||
"ora": "^2.1.0",
|
"ora": "^2.1.0",
|
||||||
"prepend": "^1.0.2",
|
"prepend": "^1.0.2",
|
||||||
"prettier": "^1.13.4",
|
"prettier": "^1.13.7",
|
||||||
"prettier-stylelint": "^0.4.2",
|
"prettier-stylelint": "^0.4.2",
|
||||||
"slugify": "^1.3.0",
|
"slugify": "^1.3.0",
|
||||||
"stylelint": "^9.2.1",
|
"stylelint": "^9.3.0",
|
||||||
"stylelint-config-css-modules": "^1.2.0",
|
"stylelint-config-css-modules": "^1.3.0",
|
||||||
"stylelint-config-standard": "^18.2.0"
|
"stylelint-config-standard": "^18.2.0"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
export PATH="$PATH:/usr/local/bin/gatsby"
|
|
||||||
|
|
||||||
# echo "Running npm install..."
|
# echo "Running npm install..."
|
||||||
# npm install
|
# npm install
|
||||||
|
|
||||||
# rm -rf ./public
|
# 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 slugify = require('slugify')
|
||||||
const ora = require('ora')
|
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 template = fs.readFileSync(templatePath).toString()
|
||||||
|
|
||||||
const spinner = ora('Adding new project').start()
|
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'
|
import Typekit from './Typekit'
|
||||||
|
|
||||||
const Head = ({ meta }) => {
|
const Head = ({ meta }) => {
|
||||||
const { title, tagline, typekitID } = meta
|
const { title, tagline } = meta
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
@ -17,7 +17,7 @@ const Head = ({ meta }) => {
|
|||||||
<meta name="theme-color" content="#e7eef4" />
|
<meta name="theme-color" content="#e7eef4" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
{typekitID && <Typekit id={typekitID} />}
|
<Typekit />
|
||||||
|
|
||||||
<SEO meta={meta} />
|
<SEO meta={meta} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { ReactComponent as Logo } from '../../images/logo.svg'
|
import Logo from '../svg/Logo'
|
||||||
import styles from './LogoUnit.module.scss'
|
import styles from './LogoUnit.module.scss'
|
||||||
|
|
||||||
const LogoUnit = ({ meta, minimal }) => {
|
const LogoUnit = ({ meta, minimal }) => {
|
||||||
|
@ -25,11 +25,13 @@
|
|||||||
margin-right: $spacer / 4;
|
margin-right: $spacer / 4;
|
||||||
color: $brand-main;
|
color: $brand-main;
|
||||||
line-height: $line-height;
|
line-height: $line-height;
|
||||||
|
transition: color .2s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logounit__description {
|
.logounit__description {
|
||||||
font-size: $font-size-h4;
|
font-size: $font-size-h4;
|
||||||
color: $brand-grey;
|
color: $brand-grey;
|
||||||
|
transition: color .2s ease-out;
|
||||||
|
|
||||||
:global(.dark) & {
|
:global(.dark) & {
|
||||||
color: $brand-grey-light;
|
color: $brand-grey-light;
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { graphql } from 'gatsby'
|
||||||
import Img from 'gatsby-image'
|
import Img from 'gatsby-image'
|
||||||
import 'intersection-observer'
|
import styles from './ProjectImage.module.scss'
|
||||||
import './ProjectImage.scss'
|
|
||||||
|
|
||||||
const ProjectImage = ({ sizes, alt }) => (
|
const ProjectImage = props => (
|
||||||
<Img
|
<Img
|
||||||
className="project__image"
|
className={styles.project__image}
|
||||||
outerWrapperClassName="project__image-wrap"
|
outerWrapperClassName={styles.project__imagewrap}
|
||||||
backgroundColor="#6b7f88"
|
backgroundColor="#6b7f88"
|
||||||
sizes={sizes}
|
fluid={props.fluid}
|
||||||
alt={alt}
|
alt={props.alt}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
ProjectImage.propTypes = {
|
ProjectImage.propTypes = {
|
||||||
sizes: PropTypes.object.isRequired,
|
fluid: PropTypes.object.isRequired,
|
||||||
alt: PropTypes.string
|
alt: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const projectImage = graphql`
|
export const projectImage = graphql`
|
||||||
fragment ProjectImageSizes on ImageSharp {
|
fragment ProjectImageFluid on ImageSharp {
|
||||||
sizes(maxWidth: 1200, quality: 85) {
|
fluid(maxWidth: 1200, quality: 85) {
|
||||||
...GatsbyImageSharpSizes_noBase64
|
...GatsbyImageSharpFluid_withWebp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
@import 'variables';
|
@import 'variables';
|
||||||
|
|
||||||
.project__image-wrap {
|
.project__imagewrap {
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
@ -14,7 +14,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark & {
|
:global(.dark) & {
|
||||||
box-shadow: 0 3px 5px rgba(darken($brand-main, 20%), .15),
|
box-shadow: 0 3px 5px rgba(darken($brand-main, 20%), .15),
|
||||||
0 5px 16px rgba(darken($brand-main, 20%), .15);
|
0 5px 16px rgba(darken($brand-main, 20%), .15);
|
||||||
}
|
}
|
@ -1,13 +1,14 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { StaticQuery, graphql } from 'gatsby'
|
||||||
import Helmet from 'react-helmet'
|
import Helmet from 'react-helmet'
|
||||||
|
|
||||||
const TypekitScript = props => (
|
const TypekitScript = typekitID => (
|
||||||
<script>
|
<script>
|
||||||
{`
|
{`
|
||||||
(function(d) {
|
(function(d) {
|
||||||
var config = {
|
var config = {
|
||||||
kitId: '${props.id}',
|
kitId: '${typekitID}',
|
||||||
scriptTimeout: 3000,
|
scriptTimeout: 3000,
|
||||||
async: true
|
async: true
|
||||||
},
|
},
|
||||||
@ -17,13 +18,30 @@ const TypekitScript = props => (
|
|||||||
</script>
|
</script>
|
||||||
)
|
)
|
||||||
|
|
||||||
const Typekit = props => (
|
const Typekit = () => (
|
||||||
<Helmet>
|
<StaticQuery
|
||||||
<link rel="dns-prefetch" href="https://use.typekit.net/" />
|
query={graphql`
|
||||||
<link rel="dns-prefetch" href="https://p.typekit.net/" />
|
query {
|
||||||
|
dataYaml {
|
||||||
|
typekitID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
render={data => {
|
||||||
|
const { typekitID } = data.dataYaml
|
||||||
|
|
||||||
{TypekitScript(props)}
|
return (
|
||||||
</Helmet>
|
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
|
export default Typekit
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import FileSaver from 'file-saver'
|
import FileSaver from 'file-saver'
|
||||||
import vCard from '../../lib/vcf/vcard'
|
import vCard from 'vcf'
|
||||||
|
|
||||||
class Vcard extends PureComponent {
|
class Vcard extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { Fragment, PureComponent } from 'react'
|
import React, { Fragment, PureComponent } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { StaticQuery, graphql } from 'gatsby'
|
||||||
import { MoveIn } from '../atoms/Animations'
|
import { MoveIn } from '../atoms/Animations'
|
||||||
import styles from './Availability.module.scss'
|
import styles from './Availability.module.scss'
|
||||||
|
|
||||||
@ -9,35 +10,51 @@ class Availability extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { availability } = this.props.meta
|
|
||||||
const { status, available, unavailable } = availability
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<StaticQuery
|
||||||
{!this.props.hide && (
|
query={graphql`
|
||||||
<MoveIn>
|
query {
|
||||||
<aside
|
dataYaml {
|
||||||
className={
|
availability {
|
||||||
status
|
status
|
||||||
? `${styles.availability} ${styles.available}`
|
available
|
||||||
: `${styles.availability} ${styles.unavailable}`
|
unavailable
|
||||||
}
|
}
|
||||||
>
|
}
|
||||||
<p
|
}
|
||||||
dangerouslySetInnerHTML={{
|
`}
|
||||||
__html: status ? available : unavailable
|
render={data => {
|
||||||
}}
|
const { availability } = data.dataYaml
|
||||||
/>
|
const { status, available, unavailable } = availability
|
||||||
</aside>
|
|
||||||
</MoveIn>
|
return (
|
||||||
)}
|
<Fragment>
|
||||||
</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 = {
|
Availability.propTypes = {
|
||||||
meta: PropTypes.object,
|
|
||||||
hide: PropTypes.bool
|
hide: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,11 +2,11 @@ import React, { PureComponent } from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { FadeIn } from '../atoms/Animations'
|
import { FadeIn } from '../atoms/Animations'
|
||||||
|
|
||||||
import { ReactComponent as Email } from '../../images/email.svg'
|
import Email from '../svg/Email'
|
||||||
import { ReactComponent as Blog } from '../../images/blog.svg'
|
import Blog from '../svg/Blog'
|
||||||
import { ReactComponent as Twitter } from '../../images/twitter.svg'
|
import Twitter from '../svg/Twitter'
|
||||||
import { ReactComponent as GitHub } from '../../images/github.svg'
|
import GitHub from '../svg/Github'
|
||||||
import { ReactComponent as Dribbble } from '../../images/dribbble.svg'
|
import Dribbble from '../svg/Dribbble'
|
||||||
|
|
||||||
import icons from '../atoms/Icons.module.scss'
|
import icons from '../atoms/Icons.module.scss'
|
||||||
import styles from './Networks.module.scss'
|
import styles from './Networks.module.scss'
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import { ReactComponent as Link } from '../../images/link.svg'
|
import Button from '../atoms/Button'
|
||||||
import { ReactComponent as Download } from '../../images/download.svg'
|
import Link from '../svg/Link'
|
||||||
import { ReactComponent as Info } from '../../images/info.svg'
|
import Download from '../svg/Download'
|
||||||
import { ReactComponent as Styleguide } from '../../images/styleguide.svg'
|
import Info from '../svg/Info'
|
||||||
import { ReactComponent as GitHub } from '../../images/github.svg'
|
import Styleguide from '../svg/Styleguide'
|
||||||
import { ReactComponent as Dribbble } from '../../images/dribbble.svg'
|
import GitHub from '../svg/Github'
|
||||||
|
import Dribbble from '../svg/Dribbble'
|
||||||
|
|
||||||
import icons from '../atoms/Icons.module.scss'
|
import icons from '../atoms/Icons.module.scss'
|
||||||
import styles from './ProjectLinks.module.scss'
|
import styles from './ProjectLinks.module.scss'
|
||||||
@ -42,10 +43,10 @@ const ProjectLinks = ({ links }) => (
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={title}>
|
<li key={title}>
|
||||||
<a href={url}>
|
<Button href={url}>
|
||||||
<LinkIcon title={title} className={icons.icon} />
|
<LinkIcon title={title} className={icons.icon} />
|
||||||
{title}
|
{title}
|
||||||
</a>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
@ -16,49 +16,6 @@
|
|||||||
margin-left: $spacer / 2;
|
margin-left: $spacer / 2;
|
||||||
margin-bottom: $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 {
|
.title {
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import Link from 'gatsby-link'
|
import { Link, graphql, StaticQuery } from 'gatsby'
|
||||||
import Img from 'gatsby-image'
|
import Img from 'gatsby-image'
|
||||||
|
import FullWidth from '../atoms/FullWidth'
|
||||||
import styles from './ProjectNav.module.scss'
|
import styles from './ProjectNav.module.scss'
|
||||||
|
|
||||||
const ProjectLink = ({ node }) => (
|
const ProjectLink = ({ node }) => (
|
||||||
<Link className={styles.link} to={node.slug}>
|
<Link className={styles.link} to={node.slug}>
|
||||||
<Img
|
<Img
|
||||||
className={styles.image}
|
className={styles.image}
|
||||||
sizes={node.img.childImageSharp.sizes}
|
fluid={node.img.childImageSharp.fluid}
|
||||||
alt={node.title}
|
alt={node.title}
|
||||||
/>
|
/>
|
||||||
<h1 className={styles.title}>{node.title}</h1>
|
<h1 className={styles.title}>{node.title}</h1>
|
||||||
@ -19,52 +20,86 @@ class ProjectNav extends Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
scrolledToCurrent: false
|
||||||
|
}
|
||||||
|
|
||||||
this.scrollToCurrent = this.scrollToCurrent.bind(this)
|
this.scrollToCurrent = this.scrollToCurrent.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.scrollToCurrent()
|
this.scrollToCurrent()
|
||||||
|
this.setState({ scrolledToCurrent: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
this.scrollToCurrent()
|
this.scrollToCurrent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.setState({ scrolledToCurrent: false })
|
||||||
|
}
|
||||||
|
|
||||||
scrollToCurrent = () => {
|
scrollToCurrent = () => {
|
||||||
const current = this.currentItem
|
const current = this.currentItem
|
||||||
const currentLeft = current.getBoundingClientRect().left
|
const currentLeft = current.getBoundingClientRect().left
|
||||||
const currentWidth = current.clientWidth
|
const currentWidth = current.clientWidth
|
||||||
const finalPosition = currentLeft - window.innerWidth / 2 + currentWidth / 2
|
const finalPosition = currentLeft - window.innerWidth / 2 + currentWidth / 2
|
||||||
|
|
||||||
this.scrollContainer.scrollLeft = finalPosition
|
this.scrollContainer.scrollLeft += finalPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { projects, project } = this.props
|
const { slug } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav
|
<StaticQuery
|
||||||
className={styles.projectNav}
|
query={graphql`
|
||||||
ref={node => {
|
query {
|
||||||
this.scrollContainer = node
|
allProjectsYaml {
|
||||||
}}
|
edges {
|
||||||
>
|
node {
|
||||||
{projects.map(({ node }) => {
|
title
|
||||||
const current = node.slug === project.slug
|
slug
|
||||||
|
img {
|
||||||
|
childImageSharp {
|
||||||
|
fluid(maxWidth: 500, quality: 85) {
|
||||||
|
...GatsbyImageSharpFluid_noBase64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
render={data => {
|
||||||
|
const projects = data.allProjectsYaml.edges
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<FullWidth>
|
||||||
className={styles.item}
|
<nav
|
||||||
key={node.slug}
|
className={styles.projectNav}
|
||||||
ref={node => {
|
ref={node => (this.scrollContainer = node)}
|
||||||
if (current) this.currentItem = node
|
>
|
||||||
}}
|
{projects.map(({ node }) => {
|
||||||
>
|
const current = node.slug === slug
|
||||||
<ProjectLink node={node} />
|
|
||||||
</div>
|
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 = {
|
ProjectNav.propTypes = {
|
||||||
projects: PropTypes.array,
|
slug: PropTypes.string
|
||||||
project: PropTypes.object
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ProjectNav
|
export default ProjectNav
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
@import 'variables';
|
@import 'variables';
|
||||||
|
|
||||||
.projectNav {
|
.projectNav {
|
||||||
composes: fullWidth from '../atoms/FullWidth.module.scss';
|
// composes: fullWidth from '../atoms/FullWidth.module.scss';
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
|
@ -16,11 +16,12 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
background: rgba(#fff, .15);
|
background: rgba(#fff, .15);
|
||||||
border-radius: .25rem;
|
border-radius: .25rem;
|
||||||
|
border: .05rem solid transparent;
|
||||||
color: $brand-grey-light;
|
color: $brand-grey-light;
|
||||||
margin-left: $spacer / 2;
|
margin-left: $spacer / 2;
|
||||||
margin-bottom: $spacer / 2;
|
margin-bottom: $spacer / 2;
|
||||||
flex: 0 0 calc(50% - #{$spacer / 2});
|
flex: 0 0 calc(50% - #{$spacer / 2});
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-small;
|
||||||
|
|
||||||
:global(.dark) & {
|
:global(.dark) & {
|
||||||
background: darken($body-background-color--dark, 1%);
|
background: darken($body-background-color--dark, 1%);
|
||||||
|
@ -1,68 +1,79 @@
|
|||||||
import React, { PureComponent, Fragment } from 'react'
|
import React, { PureComponent, Fragment } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import Helmet from 'react-helmet'
|
import Helmet from 'react-helmet'
|
||||||
import { FadeIn } from '../atoms/Animations'
|
import Day from '../svg/Day'
|
||||||
import { ReactComponent as Day } from '../../images/day.svg'
|
import Night from '../svg/Night'
|
||||||
import { ReactComponent as Night } from '../../images/night.svg'
|
|
||||||
import styles from './ThemeSwitch.module.scss'
|
import styles from './ThemeSwitch.module.scss'
|
||||||
|
|
||||||
const ThemeToggle = props => {
|
const ThemeToggle = props => (
|
||||||
return (
|
<span id="toggle" className={styles.checkboxContainer} aria-live="assertive">
|
||||||
<span
|
<Day className={props.dark ? null : 'active'} />
|
||||||
id="toggle"
|
<span className={styles.checkboxFake} />
|
||||||
className={styles.checkboxContainer}
|
<Night className={props.dark ? 'active' : null} />
|
||||||
aria-live="assertive"
|
</span>
|
||||||
>
|
)
|
||||||
<Day className={props.dark ? null : 'active'} />
|
|
||||||
<span className={styles.checkboxFake} />
|
|
||||||
<Night className={props.dark ? 'active' : null} />
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
class ThemeSwitch extends PureComponent {
|
class ThemeSwitch extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = { dark: false }
|
this.state = { dark: null }
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const now = new Date().getHours()
|
|
||||||
|
|
||||||
if (now >= 19 || now <= 7) {
|
|
||||||
this.setState({ dark: true })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isDark = () => this.state.dark === true
|
isDark = () => this.state.dark === true
|
||||||
|
|
||||||
handleChange = () => {
|
darkLocalStorageMode = darkLocalStorage => {
|
||||||
this.setState({ dark: !this.isDark() })
|
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() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<body className={this.state.dark ? 'dark' : null} />
|
<body className={this.isDark() ? 'dark' : null} />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<FadeIn>
|
<aside className={styles.themeSwitch}>
|
||||||
<aside className={styles.themeSwitch}>
|
<label className={styles.checkbox}>
|
||||||
<label className={styles.checkbox}>
|
<span className={styles.label}>Toggle Night Mode</span>
|
||||||
<span className={styles.label}>Toggle Night Mode</span>
|
<input
|
||||||
<input
|
onChange={this.handleChange}
|
||||||
onChange={this.handleChange}
|
type="checkbox"
|
||||||
type="checkbox"
|
name="toggle"
|
||||||
name="toggle"
|
value="toggle"
|
||||||
value="toggle"
|
aria-describedby="toggle"
|
||||||
aria-describedby="toggle"
|
checked={this.isDark()}
|
||||||
checked={this.state.dark}
|
/>
|
||||||
/>
|
<ThemeToggle dark={this.isDark()} />
|
||||||
<ThemeToggle dark={this.state.dark} />
|
</label>
|
||||||
</label>
|
</aside>
|
||||||
</aside>
|
|
||||||
</FadeIn>
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { StaticQuery, graphql } from 'gatsby'
|
||||||
import Vcard from '../atoms/Vcard'
|
import Vcard from '../atoms/Vcard'
|
||||||
import LogoUnit from '../atoms/LogoUnit'
|
import LogoUnit from '../atoms/LogoUnit'
|
||||||
import Networks from '../molecules/Networks'
|
import Networks from '../molecules/Networks'
|
||||||
@ -14,24 +15,43 @@ class Footer extends PureComponent {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const meta = this.props.meta
|
const meta = this.props.meta
|
||||||
const pkg = this.props.pkg
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className={styles.footer}>
|
<StaticQuery
|
||||||
<LogoUnit meta={meta} minimal />
|
query={graphql`
|
||||||
<Networks meta={meta} minimal />
|
query {
|
||||||
|
# the package.json file
|
||||||
|
portfolioJson {
|
||||||
|
name
|
||||||
|
homepage
|
||||||
|
repository
|
||||||
|
bugs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
render={data => {
|
||||||
|
const pkg = data.portfolioJson
|
||||||
|
|
||||||
<p className={styles.footer__actions}>
|
return (
|
||||||
<Vcard meta={meta} />
|
<footer className={styles.footer}>
|
||||||
<a href={meta.gpg}>PGP/GPG key</a>
|
<LogoUnit meta={meta} minimal />
|
||||||
<a href={pkg.bugs}>Found a bug?</a>
|
<Networks meta={meta} minimal />
|
||||||
</p>
|
|
||||||
<p className={styles.footer__copyright}>
|
<p className={styles.footer__actions}>
|
||||||
<small>
|
<Vcard meta={meta} />
|
||||||
© {this.state.year} {meta.title} — All Rights Reserved
|
<a href={meta.gpg}>PGP/GPG key</a>
|
||||||
</small>
|
<a href={pkg.bugs}>Found a bug?</a>
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
<p className={styles.footer__copyright}>
|
||||||
|
<small>
|
||||||
|
© {this.state.year} {meta.title} — All Rights
|
||||||
|
Reserved
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,5 +38,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer__copyright {
|
.footer__copyright {
|
||||||
opacity: .6;
|
opacity: .7;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import Link from 'gatsby-link'
|
import { Link } from 'gatsby'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { FadeIn } from '../atoms/Animations'
|
|
||||||
import Networks from '../molecules/Networks'
|
import Networks from '../molecules/Networks'
|
||||||
import Availability from '../molecules/Availability'
|
import Availability from '../molecules/Availability'
|
||||||
import ThemeSwitch from '../molecules/ThemeSwitch'
|
import ThemeSwitch from '../molecules/ThemeSwitch'
|
||||||
@ -11,53 +10,34 @@ import styles from './Header.module.scss'
|
|||||||
class Header extends PureComponent {
|
class Header extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(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() {
|
render() {
|
||||||
const meta = this.props.meta
|
const { isHomepage, meta } = this.props
|
||||||
const isHomepage = this.props.isHomepage
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className={this.state.classes}>
|
<header
|
||||||
|
className={
|
||||||
|
isHomepage ? `${styles.header}` : `${styles.header} ${styles.minimal}`
|
||||||
|
}
|
||||||
|
>
|
||||||
<ThemeSwitch />
|
<ThemeSwitch />
|
||||||
<FadeIn>
|
|
||||||
<Link className={styles.header__link} to={'/'}>
|
<Link className={styles.header__link} to={'/'}>
|
||||||
<LogoUnit meta={meta} minimal={!isHomepage} />
|
<LogoUnit meta={meta} minimal={!isHomepage} />
|
||||||
</Link>
|
</Link>
|
||||||
</FadeIn>
|
|
||||||
|
|
||||||
<Networks meta={meta} hide={!isHomepage} />
|
<Networks meta={meta} hide={!isHomepage} />
|
||||||
|
|
||||||
<Availability
|
<Availability hide={!isHomepage && !meta.availability.status} />
|
||||||
meta={meta}
|
|
||||||
hide={!isHomepage && !meta.availability.status}
|
|
||||||
/>
|
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Header.propTypes = {
|
Header.propTypes = {
|
||||||
meta: PropTypes.object,
|
isHomepage: PropTypes.bool,
|
||||||
isHomepage: PropTypes.bool
|
meta: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Header
|
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 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 giphyAPI from 'giphy-js-sdk-core'
|
||||||
|
import Layout from '../components/Layout'
|
||||||
import Content from '../components/atoms/Content'
|
import Content from '../components/atoms/Content'
|
||||||
import './404.scss'
|
import './404.scss'
|
||||||
|
|
||||||
@ -40,24 +42,30 @@ class NotFound extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Content className="content content--404">
|
<Layout location={this.props.location}>
|
||||||
<h1>Shenanigans, page not found.</h1>
|
<Content className="content content--404">
|
||||||
<p>
|
<h1>Shenanigans, page not found.</h1>
|
||||||
You might want to check the url, or{' '}
|
<p>
|
||||||
<Link to={'/'}>go back to the homepage</Link>. Or just check out some
|
You might want to check the url, or{' '}
|
||||||
fail gifs, entirely your choice.
|
<Link to={'/'}>go back to the homepage</Link>. Or just check out
|
||||||
</p>
|
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>
|
<div>
|
||||||
<a className="gif__action" href="#" onClick={this.handleClick}>
|
<a className="gif__action" href="#" onClick={this.handleClick}>
|
||||||
Show me another cat fail gif
|
Show me another cat fail gif
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</Content>
|
</Content>
|
||||||
|
</Layout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NotFound.propTypes = {
|
||||||
|
location: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
export default NotFound
|
export default NotFound
|
||||||
|
@ -1,39 +1,43 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
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 ProjectImage from '../components/atoms/ProjectImage'
|
||||||
import FullWidth from '../components/atoms/FullWidth'
|
import FullWidth from '../components/atoms/FullWidth'
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
|
|
||||||
const Home = ({ data }) => {
|
const Home = ({ data, location }) => {
|
||||||
const projects = data.allProjectsYaml.edges
|
const projects = data.allProjectsYaml.edges
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FullWidth className="projects">
|
<Layout location={location}>
|
||||||
{projects.map(({ node }) => {
|
<FullWidth className="projects">
|
||||||
const { slug, title, img } = node
|
{projects.map(({ node }) => {
|
||||||
|
const { slug, title, img } = node
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className={styles.project} key={slug}>
|
<article className={styles.project} key={slug}>
|
||||||
<Link to={slug}>
|
<Link to={slug}>
|
||||||
<h1 className={styles.title}>{title}</h1>
|
<h1 className={styles.title}>{title}</h1>
|
||||||
<ProjectImage sizes={img.childImageSharp.sizes} alt={title} />
|
<ProjectImage fluid={img.childImageSharp.fluid} alt={title} />
|
||||||
</Link>
|
</Link>
|
||||||
</article>
|
</article>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</FullWidth>
|
</FullWidth>
|
||||||
|
</Layout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Home.propTypes = {
|
Home.propTypes = {
|
||||||
data: PropTypes.object
|
data: PropTypes.object,
|
||||||
|
location: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Home
|
export default Home
|
||||||
|
|
||||||
export const IndexQuery = graphql`
|
export const IndexQuery = graphql`
|
||||||
query IndexQuery {
|
query {
|
||||||
allProjectsYaml {
|
allProjectsYaml {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
@ -41,7 +45,7 @@ export const IndexQuery = graphql`
|
|||||||
slug
|
slug
|
||||||
img {
|
img {
|
||||||
childImageSharp {
|
childImageSharp {
|
||||||
...ProjectImageSizes
|
...ProjectImageFluid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,20 +10,14 @@ html,
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
font-size: $font-size-root;
|
font-size: $font-size-root;
|
||||||
|
background: $body-background-color;
|
||||||
&.wf-loading,
|
|
||||||
&.wf-inactive {
|
|
||||||
font-size: $font-size-root - 2px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: $body-background-color;
|
|
||||||
font-family: $font-family-base;
|
font-family: $font-family-base;
|
||||||
font-weight: $font-weight-base;
|
font-weight: $font-weight-base;
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-base;
|
||||||
@ -33,6 +27,14 @@ body {
|
|||||||
font-feature-settings: 'liga', 'kern';
|
font-feature-settings: 'liga', 'kern';
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-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,
|
p,
|
||||||
@ -115,25 +117,4 @@ svg {
|
|||||||
display: flex;
|
display: flex;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
flex-direction: column;
|
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 PropTypes from 'prop-types'
|
||||||
import Helmet from 'react-helmet'
|
import Helmet from 'react-helmet'
|
||||||
import ReactMarkdown from 'react-markdown'
|
import ReactMarkdown from 'react-markdown'
|
||||||
|
import { graphql } from 'gatsby'
|
||||||
|
import Layout from '../components/Layout'
|
||||||
import Content from '../components/atoms/Content'
|
import Content from '../components/atoms/Content'
|
||||||
import FullWidth from '../components/atoms/FullWidth'
|
import FullWidth from '../components/atoms/FullWidth'
|
||||||
import ProjectImage from '../components/atoms/ProjectImage'
|
import ProjectImage from '../components/atoms/ProjectImage'
|
||||||
@ -9,67 +11,55 @@ import ProjectTechstack from '../components/molecules/ProjectTechstack'
|
|||||||
import ProjectLinks from '../components/molecules/ProjectLinks'
|
import ProjectLinks from '../components/molecules/ProjectLinks'
|
||||||
import ProjectNav from '../components/molecules/ProjectNav'
|
import ProjectNav from '../components/molecules/ProjectNav'
|
||||||
import SEO from '../components/atoms/SEO'
|
import SEO from '../components/atoms/SEO'
|
||||||
import './Project.scss'
|
|
||||||
|
|
||||||
const ProjectMeta = props => {
|
import styles from './Project.module.scss'
|
||||||
const { links, techstack } = props
|
|
||||||
|
|
||||||
return (
|
const ProjectMeta = ({ links, techstack }) => (
|
||||||
<footer className="project__meta">
|
<footer className={styles.project__meta}>
|
||||||
{!!links && <ProjectLinks links={links} />}
|
{!!links && <ProjectLinks links={links} />}
|
||||||
{!!techstack && <ProjectTechstack techstack={techstack} />}
|
{!!techstack && <ProjectTechstack techstack={techstack} />}
|
||||||
</footer>
|
</footer>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
const ProjectImages = props => (
|
const ProjectImages = ({ projectImages, title }) => (
|
||||||
<FullWidth>
|
<FullWidth>
|
||||||
{props.projectImages.map(({ node }) => (
|
{projectImages.map(({ node }) => (
|
||||||
<ProjectImage key={node.id} sizes={node.sizes} alt={props.title} />
|
<div className={styles.spacer} key={node.id}>
|
||||||
|
<ProjectImage fluid={node.fluid} alt={title} />
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</FullWidth>
|
</FullWidth>
|
||||||
)
|
)
|
||||||
|
|
||||||
class Project extends Component {
|
const Project = ({ data, location }) => {
|
||||||
constructor(props) {
|
const meta = data.dataYaml
|
||||||
super(props)
|
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 = {
|
<SEO project={project} meta={meta} />
|
||||||
descriptionWithLineBreaks: description.split('\n').join('\n\n')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
<article className={styles.project}>
|
||||||
const meta = this.props.data.dataYaml
|
<Content>
|
||||||
const projects = this.props.data.allProjectsYaml.edges
|
<h1 className={styles.project__title}>{title}</h1>
|
||||||
const project = this.props.data.projectsYaml
|
<ReactMarkdown
|
||||||
const projectImages = this.props.data.projectImages.edges
|
source={descriptionWithLineBreaks}
|
||||||
const { title, links, techstack } = project
|
className={styles.project__description}
|
||||||
|
/>
|
||||||
|
<ProjectImages projectImages={projectImages} title={title} />
|
||||||
|
<ProjectMeta links={links} techstack={techstack} />
|
||||||
|
</Content>
|
||||||
|
</article>
|
||||||
|
|
||||||
return (
|
<ProjectNav slug={project.slug} />
|
||||||
<Fragment>
|
</Layout>
|
||||||
<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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectMeta.propTypes = {
|
ProjectMeta.propTypes = {
|
||||||
@ -84,13 +74,13 @@ ProjectImages.propTypes = {
|
|||||||
|
|
||||||
Project.propTypes = {
|
Project.propTypes = {
|
||||||
data: PropTypes.object.isRequired,
|
data: PropTypes.object.isRequired,
|
||||||
pathContext: PropTypes.object.isRequired
|
location: PropTypes.object.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Project
|
export default Project
|
||||||
|
|
||||||
export const projectAndProjectsQuery = graphql`
|
export const projectAndProjectsQuery = graphql`
|
||||||
query ProjectBySlug($slug: String!) {
|
query($slug: String!) {
|
||||||
projectsYaml(slug: { eq: $slug }) {
|
projectsYaml(slug: { eq: $slug }) {
|
||||||
title
|
title
|
||||||
slug
|
slug
|
||||||
@ -122,11 +112,6 @@ export const projectAndProjectsQuery = graphql`
|
|||||||
GitHub
|
GitHub
|
||||||
Dribbble
|
Dribbble
|
||||||
}
|
}
|
||||||
availability {
|
|
||||||
status
|
|
||||||
available
|
|
||||||
unavailable
|
|
||||||
}
|
|
||||||
img {
|
img {
|
||||||
childImageSharp {
|
childImageSharp {
|
||||||
resize(width: 980) {
|
resize(width: 980) {
|
||||||
@ -137,29 +122,13 @@ export const projectAndProjectsQuery = graphql`
|
|||||||
}
|
}
|
||||||
|
|
||||||
projectImages: allImageSharp(
|
projectImages: allImageSharp(
|
||||||
filter: { id: { regex: $slug } }
|
filter: { fluid: { originalName: { regex: $slug } } }
|
||||||
sort: { fields: [id], order: ASC }
|
sort: { fields: [fluid___originalName], order: ASC }
|
||||||
) {
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
...ProjectImageSizes
|
...ProjectImageFluid
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allProjectsYaml {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
title
|
|
||||||
slug
|
|
||||||
img {
|
|
||||||
childImageSharp {
|
|
||||||
sizes(maxWidth: 500, quality: 85) {
|
|
||||||
...GatsbyImageSharpSizes_noBase64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
@import 'variables';
|
@import 'variables';
|
||||||
|
@import '../components/atoms/ProjectImage.module';
|
||||||
|
|
||||||
.project {
|
// .project__imagewrap {
|
||||||
.project__image-wrap {
|
.spacer {
|
||||||
margin-bottom: $spacer * 3;
|
//composes: project__imagewrap from '../components/atoms/ProjectImage.module';
|
||||||
|
margin-bottom: $spacer * 3;
|
||||||
|
|
||||||
@media (min-width: 30rem) {
|
@media (min-width: 30rem) {
|
||||||
margin-bottom: $spacer * 6;
|
margin-bottom: $spacer * 6;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user