mirror of
https://github.com/kremalicious/portfolio.git
synced 2024-12-23 01:29:41 +01:00
commit
90bb4c65e4
@ -42,10 +42,11 @@ The whole [portfolio](https://matthiaskretschmann.com) is a React-based Single P
|
|||||||
|
|
||||||
### 💍 One data file to rule all pages
|
### 💍 One data file to rule all pages
|
||||||
|
|
||||||
All content is powered by one YAML file where all the portfolio's projects are defined. The project description itself is transformed from Markdown written inside the YAML file.
|
All content is powered by one YAML file where all the portfolio's projects are defined. The project description itself is transformed from Markdown written inside the YAML file int HTML on build time.
|
||||||
|
|
||||||
Gatsby automatically creates pages from each item in that file utilizing the [`Project.jsx`](src/templates/Project.jsx) template.
|
Gatsby automatically creates pages from each item in that file utilizing the [`Project.jsx`](src/templates/Project.jsx) template.
|
||||||
|
|
||||||
|
- [`gatsby-node.js`](gatsby-node.js)
|
||||||
- [`data/projects.yml`](data/projects.yml)
|
- [`data/projects.yml`](data/projects.yml)
|
||||||
- [`src/templates/Project.jsx`](src/templates/Project.jsx)
|
- [`src/templates/Project.jsx`](src/templates/Project.jsx)
|
||||||
|
|
||||||
|
@ -1,4 +1,36 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const remark = require('remark')
|
||||||
|
const markdown = require('remark-parse')
|
||||||
|
const html = require('remark-html')
|
||||||
|
|
||||||
|
exports.onCreateNode = ({ node, actions }) => {
|
||||||
|
const { createNodeField } = actions
|
||||||
|
|
||||||
|
// Projects YAML nodes
|
||||||
|
if (node.internal.type === 'ProjectsYaml') {
|
||||||
|
// Add transformed Markdown descriptions
|
||||||
|
const description = node.description
|
||||||
|
const descriptionWithLineBreaks = description.split('\n').join('\n\n')
|
||||||
|
|
||||||
|
let descriptionHtml
|
||||||
|
|
||||||
|
remark()
|
||||||
|
.use(markdown, { gfm: true, commonmark: true, pedantic: true })
|
||||||
|
.use(html)
|
||||||
|
.process(descriptionWithLineBreaks, (err, file) => {
|
||||||
|
if (err) throw Error('Could not transform project description')
|
||||||
|
|
||||||
|
descriptionHtml = file.contents
|
||||||
|
return descriptionHtml
|
||||||
|
})
|
||||||
|
|
||||||
|
createNodeField({
|
||||||
|
node,
|
||||||
|
name: 'descriptionHtml',
|
||||||
|
value: descriptionHtml
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Create project pages from projects.yml
|
// Create project pages from projects.yml
|
||||||
|
30
package.json
30
package.json
@ -21,15 +21,15 @@
|
|||||||
"new": "babel-node ./scripts/new.js"
|
"new": "babel-node ./scripts/new.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"file-saver": "^2.0.0-rc.4",
|
"file-saver": "^2.0.0",
|
||||||
"gatsby": "^2.0.50",
|
"gatsby": "^2.0.55",
|
||||||
"gatsby-image": "^2.0.20",
|
"gatsby-image": "^2.0.20",
|
||||||
"gatsby-plugin-favicon": "^3.1.4",
|
"gatsby-plugin-favicon": "^3.1.4",
|
||||||
"gatsby-plugin-matomo": "^0.5.0",
|
"gatsby-plugin-matomo": "^0.5.1",
|
||||||
"gatsby-plugin-offline": "^2.0.15",
|
"gatsby-plugin-offline": "^2.0.17",
|
||||||
"gatsby-plugin-react-helmet": "^3.0.2",
|
"gatsby-plugin-react-helmet": "^3.0.2",
|
||||||
"gatsby-plugin-sass": "^2.0.4",
|
"gatsby-plugin-sass": "^2.0.4",
|
||||||
"gatsby-plugin-sharp": "^2.0.12",
|
"gatsby-plugin-sharp": "^2.0.13",
|
||||||
"gatsby-plugin-sitemap": "^2.0.2",
|
"gatsby-plugin-sitemap": "^2.0.2",
|
||||||
"gatsby-plugin-svgr": "^2.0.1",
|
"gatsby-plugin-svgr": "^2.0.1",
|
||||||
"gatsby-source-filesystem": "^2.0.8",
|
"gatsby-source-filesystem": "^2.0.8",
|
||||||
@ -44,8 +44,10 @@
|
|||||||
"react": "^16.6.3",
|
"react": "^16.6.3",
|
||||||
"react-dom": "^16.6.3",
|
"react-dom": "^16.6.3",
|
||||||
"react-helmet": "^5.2.0",
|
"react-helmet": "^5.2.0",
|
||||||
"react-markdown": "^4.0.3",
|
|
||||||
"react-pose": "^4.0.2",
|
"react-pose": "^4.0.2",
|
||||||
|
"remark": "^10.0.1",
|
||||||
|
"remark-html": "^9.0.0",
|
||||||
|
"remark-parse": "^6.0.3",
|
||||||
"suncalc": "^1.8.0",
|
"suncalc": "^1.8.0",
|
||||||
"vcf": "^2.0.1"
|
"vcf": "^2.0.1"
|
||||||
},
|
},
|
||||||
@ -53,23 +55,23 @@
|
|||||||
"@babel/core": "^7.1.6",
|
"@babel/core": "^7.1.6",
|
||||||
"@babel/node": "^7.0.0",
|
"@babel/node": "^7.0.0",
|
||||||
"@babel/preset-env": "^7.1.6",
|
"@babel/preset-env": "^7.1.6",
|
||||||
"@svgr/webpack": "^4.0.3",
|
"@svgr/webpack": "^4.1.0",
|
||||||
"ava": "^0.25.0",
|
"ava": "^0.25.0",
|
||||||
"babel-eslint": "^10.0.0",
|
"babel-eslint": "^10.0.1",
|
||||||
"chrome-launcher": "^0.10.5",
|
"chrome-launcher": "^0.10.5",
|
||||||
"eslint": "^5.8.0",
|
"eslint": "^5.9.0",
|
||||||
"eslint-config-prettier": "^3.1.0",
|
"eslint-config-prettier": "^3.3.0",
|
||||||
"eslint-loader": "^2.1.0",
|
"eslint-loader": "^2.1.1",
|
||||||
"eslint-plugin-graphql": "^3.0.1",
|
"eslint-plugin-graphql": "^3.0.1",
|
||||||
"eslint-plugin-prettier": "^3.0.0",
|
"eslint-plugin-prettier": "^3.0.0",
|
||||||
"eslint-plugin-react": "^7.11.1",
|
"eslint-plugin-react": "^7.11.1",
|
||||||
"lighthouse": "^3.2.1",
|
"lighthouse": "^4.0.0-alpha.2-3.2.1",
|
||||||
"ora": "^3.0.0",
|
"ora": "^3.0.0",
|
||||||
"prepend": "^1.0.2",
|
"prepend": "^1.0.2",
|
||||||
"prettier": "^1.15.1",
|
"prettier": "^1.15.1",
|
||||||
"prettier-stylelint": "^0.4.2",
|
"prettier-stylelint": "^0.4.2",
|
||||||
"slugify": "^1.3.2",
|
"slugify": "^1.3.3",
|
||||||
"stylelint": "^9.7.1",
|
"stylelint": "^9.8.0",
|
||||||
"stylelint-config-css-modules": "^1.3.0",
|
"stylelint-config-css-modules": "^1.3.0",
|
||||||
"stylelint-config-standard": "^18.2.0",
|
"stylelint-config-standard": "^18.2.0",
|
||||||
"why-did-you-update": "^1.0.6"
|
"why-did-you-update": "^1.0.6"
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { PureComponent, Fragment } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import posed, { PoseGroup } from 'react-pose'
|
import posed, { PoseGroup } from 'react-pose'
|
||||||
import { fadeIn } from './atoms/Transitions'
|
import { fadeIn } from './atoms/Transitions'
|
||||||
import Head from './molecules/Head'
|
import Typekit from './atoms/Typekit'
|
||||||
import Header from './organisms/Header'
|
import Header from './organisms/Header'
|
||||||
import Footer from './organisms/Footer'
|
import Footer from './organisms/Footer'
|
||||||
import styles from './Layout.module.scss'
|
import styles from './Layout.module.scss'
|
||||||
@ -27,8 +27,9 @@ export default class Layout extends PureComponent {
|
|||||||
const RoutesContainer = posed.div(fadeIn)
|
const RoutesContainer = posed.div(fadeIn)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<>
|
||||||
<Head />
|
<Typekit />
|
||||||
|
|
||||||
<PoseGroup animateOnMount={true}>
|
<PoseGroup animateOnMount={true}>
|
||||||
<RoutesContainer
|
<RoutesContainer
|
||||||
key={location.pathname}
|
key={location.pathname}
|
||||||
@ -39,8 +40,9 @@ export default class Layout extends PureComponent {
|
|||||||
<main className={styles.screen}>{children}</main>
|
<main className={styles.screen}>{children}</main>
|
||||||
</RoutesContainer>
|
</RoutesContainer>
|
||||||
</PoseGroup>
|
</PoseGroup>
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
</Fragment>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,8 @@ const SEO = ({ project }) => (
|
|||||||
render={data => {
|
render={data => {
|
||||||
const meta = data.dataYaml
|
const meta = data.dataYaml
|
||||||
|
|
||||||
const title = project.title ? project.title : meta.title
|
const title = project.title || meta.title
|
||||||
|
const tagline = meta.tagline
|
||||||
const description = project.description
|
const description = project.description
|
||||||
? truncate.apply(project.description, [320, true])
|
? truncate.apply(project.description, [320, true])
|
||||||
: truncate.apply(meta.description, [320, true])
|
: truncate.apply(meta.description, [320, true])
|
||||||
@ -59,9 +60,14 @@ const SEO = ({ project }) => (
|
|||||||
const url = project.slug ? `${meta.url}${project.slug}` : meta.url
|
const url = project.slug ? `${meta.url}${project.slug}` : meta.url
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Helmet>
|
<Helmet
|
||||||
|
defaultTitle={`${title.toLowerCase()} { ${tagline.toLowerCase()} }`}
|
||||||
|
titleTemplate={`%s // ${title.toLowerCase()} { ${tagline.toLowerCase()} }`}
|
||||||
|
>
|
||||||
<html lang="en" />
|
<html lang="en" />
|
||||||
|
|
||||||
|
<title>{title}</title>
|
||||||
|
|
||||||
{/* General tags */}
|
{/* General tags */}
|
||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
<meta name="image" content={`${meta.url}${image}`} />
|
<meta name="image" content={`${meta.url}${image}`} />
|
||||||
|
@ -34,7 +34,6 @@ const Typekit = () => (
|
|||||||
return (
|
return (
|
||||||
typekitID && (
|
typekitID && (
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<link rel="dns-prefetch" href="https://use.typekit.net/" />
|
|
||||||
<link rel="dns-prefetch" href="https://p.typekit.net/" />
|
<link rel="dns-prefetch" href="https://p.typekit.net/" />
|
||||||
|
|
||||||
{TypekitScript(typekitID)}
|
{TypekitScript(typekitID)}
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
import React, { Fragment } from 'react'
|
|
||||||
import Helmet from 'react-helmet'
|
|
||||||
import { StaticQuery, graphql } from 'gatsby'
|
|
||||||
import SEO from '../atoms/SEO'
|
|
||||||
import Typekit from '../atoms/Typekit'
|
|
||||||
|
|
||||||
const query = graphql`
|
|
||||||
query {
|
|
||||||
dataYaml {
|
|
||||||
title
|
|
||||||
tagline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const Head = () => (
|
|
||||||
<StaticQuery
|
|
||||||
query={query}
|
|
||||||
render={data => {
|
|
||||||
const { title, tagline } = data.dataYaml
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<Helmet
|
|
||||||
defaultTitle={`${title.toLowerCase()} { ${tagline.toLowerCase()} }`}
|
|
||||||
titleTemplate={`%s // ${title.toLowerCase()} { ${tagline.toLowerCase()} }`}
|
|
||||||
>
|
|
||||||
<meta
|
|
||||||
name="apple-mobile-web-app-title"
|
|
||||||
content={title.toLowerCase()}
|
|
||||||
/>
|
|
||||||
</Helmet>
|
|
||||||
|
|
||||||
<Typekit />
|
|
||||||
|
|
||||||
<SEO />
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default Head
|
|
@ -51,7 +51,6 @@
|
|||||||
transform-origin: top center;
|
transform-origin: top center;
|
||||||
transform-box: border-box;
|
transform-box: border-box;
|
||||||
|
|
||||||
// stylelint-disable no-descending-specificity
|
|
||||||
.logounit__title,
|
.logounit__title,
|
||||||
.logounit__description {
|
.logounit__description {
|
||||||
color: $text-color-light;
|
color: $text-color-light;
|
||||||
@ -60,7 +59,6 @@
|
|||||||
color: $text-color-light--dark;
|
color: $text-color-light--dark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// stylelint-enable no-descending-specificity
|
|
||||||
|
|
||||||
.logounit__logo {
|
.logounit__logo {
|
||||||
margin-bottom: $spacer / 3;
|
margin-bottom: $spacer / 3;
|
||||||
|
@ -27,20 +27,22 @@ const query = graphql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const NetworkIcon = props => {
|
class NetworkIcon extends PureComponent {
|
||||||
switch (props.title) {
|
render() {
|
||||||
case 'Email':
|
switch (this.props.title) {
|
||||||
return <Email {...props} />
|
case 'Email':
|
||||||
case 'Blog':
|
return <Email {...this.props} />
|
||||||
return <Blog {...props} />
|
case 'Blog':
|
||||||
case 'Twitter':
|
return <Blog {...this.props} />
|
||||||
return <Twitter {...props} />
|
case 'Twitter':
|
||||||
case 'GitHub':
|
return <Twitter {...this.props} />
|
||||||
return <GitHub {...props} />
|
case 'GitHub':
|
||||||
case 'Dribbble':
|
return <GitHub {...this.props} />
|
||||||
return <Dribbble {...props} />
|
case 'Dribbble':
|
||||||
default:
|
return <Dribbble {...this.props} />
|
||||||
return null
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import Button from '../atoms/Button'
|
import Button from '../atoms/Button'
|
||||||
@ -50,31 +50,33 @@ const LinkIcon = ({ title, type, ...props }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProjectLinks = ({ links }) => (
|
export default class ProjectLinks extends PureComponent {
|
||||||
<div className={styles.projectLinks}>
|
static propTypes = {
|
||||||
<h3 className={styles.title}>
|
links: PropTypes.array
|
||||||
Links <span>Learn more on the interwebz.</span>
|
}
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
render() {
|
||||||
{links.map(link => {
|
return (
|
||||||
const { title, url, type } = link
|
<div className={styles.projectLinks}>
|
||||||
|
<h3 className={styles.title}>
|
||||||
|
Links <span>Learn more on the interwebz.</span>
|
||||||
|
</h3>
|
||||||
|
|
||||||
return (
|
<ul>
|
||||||
<li key={title}>
|
{this.props.links.map(link => {
|
||||||
<Button href={url}>
|
const { title, url, type } = link
|
||||||
<LinkIcon title={title} type={type} className={icons.icon} />
|
|
||||||
{title}
|
|
||||||
</Button>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
ProjectLinks.propTypes = {
|
return (
|
||||||
links: PropTypes.array
|
<li key={title}>
|
||||||
|
<Button href={url}>
|
||||||
|
<LinkIcon title={title} type={type} className={icons.icon} />
|
||||||
|
{title}
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ProjectLinks
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Link, graphql, StaticQuery } from 'gatsby'
|
import { Link, graphql, StaticQuery } from 'gatsby'
|
||||||
import Img from 'gatsby-image'
|
import Img from 'gatsby-image'
|
||||||
@ -24,18 +24,24 @@ const query = graphql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const ProjectLink = ({ node }) => (
|
class ProjectLink extends PureComponent {
|
||||||
<Link className={styles.link} to={node.slug}>
|
render() {
|
||||||
<Img
|
const { node } = this.props
|
||||||
className={styles.image}
|
|
||||||
fluid={node.img.childImageSharp.fluid}
|
|
||||||
alt={node.title}
|
|
||||||
/>
|
|
||||||
<h1 className={styles.title}>{node.title}</h1>
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default class ProjectNav extends Component {
|
return (
|
||||||
|
<Link className={styles.link} to={node.slug}>
|
||||||
|
<Img
|
||||||
|
className={styles.image}
|
||||||
|
fluid={node.img.childImageSharp.fluid}
|
||||||
|
alt={node.title}
|
||||||
|
/>
|
||||||
|
<h1 className={styles.title}>{node.title}</h1>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ProjectNav extends PureComponent {
|
||||||
state = {
|
state = {
|
||||||
scrolledToCurrent: false
|
scrolledToCurrent: false
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import { Link, StaticQuery, graphql } from 'gatsby'
|
||||||
import { StaticQuery, graphql } from 'gatsby'
|
|
||||||
import Vcard from '../atoms/Vcard'
|
import Vcard from '../atoms/Vcard'
|
||||||
import LogoUnit from '../molecules/LogoUnit'
|
import LogoUnit from '../molecules/LogoUnit'
|
||||||
import Networks from '../molecules/Networks'
|
import Networks from '../molecules/Networks'
|
||||||
@ -23,33 +22,30 @@ const query = graphql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const FooterMarkup = ({ meta, pkg, year }) => (
|
|
||||||
<footer className={styles.footer}>
|
|
||||||
<LogoUnit minimal />
|
|
||||||
<Networks minimal />
|
|
||||||
|
|
||||||
<p className={styles.footer__actions}>
|
|
||||||
<Vcard />
|
|
||||||
<a href={meta.gpg}>PGP/GPG key</a>
|
|
||||||
<a href={pkg.bugs}>Found a bug?</a>
|
|
||||||
</p>
|
|
||||||
<p className={styles.footer__copyright}>
|
|
||||||
<small>
|
|
||||||
© {year} {meta.title} — All Rights Reserved
|
|
||||||
</small>
|
|
||||||
</p>
|
|
||||||
</footer>
|
|
||||||
)
|
|
||||||
|
|
||||||
FooterMarkup.propTypes = {
|
|
||||||
meta: PropTypes.object,
|
|
||||||
pkg: PropTypes.object,
|
|
||||||
year: PropTypes.number
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Footer extends PureComponent {
|
export default class Footer extends PureComponent {
|
||||||
state = { year: new Date().getFullYear() }
|
state = { year: new Date().getFullYear() }
|
||||||
|
|
||||||
|
FooterMarkup = ({ meta, pkg, year }) => (
|
||||||
|
<footer className={styles.footer}>
|
||||||
|
<Link to={'/'}>
|
||||||
|
<LogoUnit minimal />
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Networks minimal />
|
||||||
|
|
||||||
|
<p className={styles.footer__actions}>
|
||||||
|
<Vcard />
|
||||||
|
<a href={meta.gpg}>PGP/GPG key</a>
|
||||||
|
<a href={pkg.bugs}>Found a bug?</a>
|
||||||
|
</p>
|
||||||
|
<p className={styles.footer__copyright}>
|
||||||
|
<small>
|
||||||
|
© {year} {meta.title} — All Rights Reserved
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<StaticQuery
|
<StaticQuery
|
||||||
@ -58,7 +54,9 @@ export default class Footer extends PureComponent {
|
|||||||
const pkg = data.portfolioJson
|
const pkg = data.portfolioJson
|
||||||
const meta = data.dataYaml
|
const meta = data.dataYaml
|
||||||
|
|
||||||
return <FooterMarkup year={this.state.year} pkg={pkg} meta={meta} />
|
return (
|
||||||
|
<this.FooterMarkup year={this.state.year} pkg={pkg} meta={meta} />
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -18,6 +18,10 @@ const query = graphql`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export default class Header extends PureComponent {
|
export default class Header extends PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
minimal: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
state = { isMinimal: this.props.minimal }
|
state = { isMinimal: this.props.minimal }
|
||||||
|
|
||||||
checkMinimal = () => {
|
checkMinimal = () => {
|
||||||
@ -61,7 +65,3 @@ export default class Header extends PureComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Header.propTypes = {
|
|
||||||
minimal: PropTypes.bool
|
|
||||||
}
|
|
||||||
|
@ -2,6 +2,7 @@ import React, { Component } from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Link } from 'gatsby'
|
import { Link } from 'gatsby'
|
||||||
import giphyAPI from 'giphy-js-sdk-core'
|
import giphyAPI from 'giphy-js-sdk-core'
|
||||||
|
import SEO from '../components/atoms/SEO'
|
||||||
import Button from '../components/atoms/Button'
|
import Button from '../components/atoms/Button'
|
||||||
import styles from './404.module.scss'
|
import styles from './404.module.scss'
|
||||||
|
|
||||||
@ -38,22 +39,26 @@ export default class NotFound extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<article className={styles.content}>
|
<>
|
||||||
<h1>Shenanigans, page not found.</h1>
|
<SEO />
|
||||||
<p>
|
|
||||||
You might want to check the url, or{' '}
|
|
||||||
<Link to={'/'}>go back to the homepage</Link>. Or just check out some{' '}
|
|
||||||
{tag} gifs, entirely your choice.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<video className="gif" src={this.state.gif} autoPlay loop />
|
<article className={styles.content}>
|
||||||
|
<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 {tag} gifs, entirely your choice.
|
||||||
|
</p>
|
||||||
|
|
||||||
<div>
|
<video className="gif" src={this.state.gif} autoPlay loop />
|
||||||
<Button
|
|
||||||
onClick={this.handleClick}
|
<div>
|
||||||
>{`Get another '${tag}' gif`}</Button>
|
<Button
|
||||||
</div>
|
onClick={this.handleClick}
|
||||||
</article>
|
>{`Get another '${tag}' gif`}</Button>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,62 +1,66 @@
|
|||||||
import React from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Link, graphql } from 'gatsby'
|
import { Link, graphql } from 'gatsby'
|
||||||
|
import SEO from '../components/atoms/SEO'
|
||||||
import ProjectImage from '../components/molecules/ProjectImage'
|
import ProjectImage from '../components/molecules/ProjectImage'
|
||||||
import { ReactComponent as Images } from '../images/images.svg'
|
import { ReactComponent as Images } from '../images/images.svg'
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
|
|
||||||
const getImageCount = (images, slug) => {
|
function getImageCount(images, slug) {
|
||||||
let array = []
|
let array = []
|
||||||
let slugWithoutSlashes = slug.replace(/\//g, '')
|
let slugWithoutSlashes = slug.replace(/\//g, '')
|
||||||
|
|
||||||
images.map(({ node }) => {
|
images.map(
|
||||||
if (node.name.includes(slugWithoutSlashes)) {
|
({ node }) => node.name.includes(slugWithoutSlashes) && array.push(node)
|
||||||
array.push(node)
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return array.length
|
return array.length
|
||||||
}
|
}
|
||||||
|
|
||||||
const Home = ({ data }) => {
|
export default class Home extends PureComponent {
|
||||||
const projects = data.allProjectsYaml.edges
|
static propTypes = {
|
||||||
const images = data.projectImageFiles.edges
|
data: PropTypes.object,
|
||||||
|
location: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
render() {
|
||||||
<div className={styles.projects}>
|
const { data } = this.props
|
||||||
{projects.map(({ node }) => {
|
const projects = data.allProjectsYaml.edges
|
||||||
const { slug, title, img } = node
|
const images = data.projectImageFiles.edges
|
||||||
const imageCount = getImageCount(images, slug)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className={styles.project} key={slug}>
|
<>
|
||||||
<Link to={slug}>
|
<SEO />
|
||||||
<h1 className={styles.title}>{title}</h1>
|
|
||||||
<ProjectImage fluid={img.childImageSharp.fluid} alt={title} />
|
|
||||||
|
|
||||||
{imageCount > 1 && (
|
<div className={styles.projects}>
|
||||||
<small
|
{projects.map(({ node }) => {
|
||||||
className={styles.imageCount}
|
const { slug, title, img } = node
|
||||||
title={`${imageCount} project images`}
|
const imageCount = getImageCount(images, slug)
|
||||||
>
|
|
||||||
<Images /> {imageCount}
|
return (
|
||||||
</small>
|
<article className={styles.project} key={slug}>
|
||||||
)}
|
<Link to={slug}>
|
||||||
</Link>
|
<h1 className={styles.title}>{title}</h1>
|
||||||
</article>
|
<ProjectImage fluid={img.childImageSharp.fluid} alt={title} />
|
||||||
)
|
|
||||||
})}
|
{imageCount > 1 && (
|
||||||
</div>
|
<small
|
||||||
)
|
className={styles.imageCount}
|
||||||
|
title={`${imageCount} project images`}
|
||||||
|
>
|
||||||
|
<Images /> {imageCount}
|
||||||
|
</small>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Home.propTypes = {
|
|
||||||
data: PropTypes.object,
|
|
||||||
location: PropTypes.object
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Home
|
|
||||||
|
|
||||||
export const IndexQuery = graphql`
|
export const IndexQuery = graphql`
|
||||||
query {
|
query {
|
||||||
allProjectsYaml {
|
allProjectsYaml {
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import React, { Fragment } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import Helmet from 'react-helmet'
|
|
||||||
import ReactMarkdown from 'react-markdown'
|
|
||||||
import { graphql } from 'gatsby'
|
import { graphql } from 'gatsby'
|
||||||
import FullWidth from '../components/atoms/FullWidth'
|
import FullWidth from '../components/atoms/FullWidth'
|
||||||
import ProjectImage from '../components/molecules/ProjectImage'
|
import ProjectImage from '../components/molecules/ProjectImage'
|
||||||
@ -11,12 +9,23 @@ import ProjectNav from '../components/molecules/ProjectNav'
|
|||||||
import SEO from '../components/atoms/SEO'
|
import SEO from '../components/atoms/SEO'
|
||||||
import styles from './Project.module.scss'
|
import styles from './Project.module.scss'
|
||||||
|
|
||||||
const ProjectMeta = ({ links, techstack }) => (
|
class ProjectMeta extends PureComponent {
|
||||||
<footer className={styles.meta}>
|
static propTypes = {
|
||||||
{!!links && <ProjectLinks links={links} />}
|
links: PropTypes.array,
|
||||||
{!!techstack && <ProjectTechstack techstack={techstack} />}
|
techstack: PropTypes.array
|
||||||
</footer>
|
}
|
||||||
)
|
|
||||||
|
render() {
|
||||||
|
const { links, techstack } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer className={styles.meta}>
|
||||||
|
{!!links && <ProjectLinks links={links} />}
|
||||||
|
{!!techstack && <ProjectTechstack techstack={techstack} />}
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ProjectImages = ({ projectImages, title }) => (
|
const ProjectImages = ({ projectImages, title }) => (
|
||||||
<FullWidth>
|
<FullWidth>
|
||||||
@ -28,52 +37,44 @@ const ProjectImages = ({ projectImages, title }) => (
|
|||||||
</FullWidth>
|
</FullWidth>
|
||||||
)
|
)
|
||||||
|
|
||||||
const Project = ({ data }) => {
|
|
||||||
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')
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<Helmet title={title} />
|
|
||||||
|
|
||||||
<SEO project={project} />
|
|
||||||
|
|
||||||
<article>
|
|
||||||
<header>
|
|
||||||
<h1 className={styles.title}>{title}</h1>
|
|
||||||
</header>
|
|
||||||
<ReactMarkdown
|
|
||||||
source={descriptionWithLineBreaks}
|
|
||||||
className={styles.description}
|
|
||||||
/>
|
|
||||||
<ProjectImages projectImages={projectImages} title={title} />
|
|
||||||
<ProjectMeta links={links} techstack={techstack} />
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<ProjectNav slug={project.slug} />
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
ProjectMeta.propTypes = {
|
|
||||||
links: PropTypes.array,
|
|
||||||
techstack: PropTypes.array
|
|
||||||
}
|
|
||||||
|
|
||||||
ProjectImages.propTypes = {
|
ProjectImages.propTypes = {
|
||||||
projectImages: PropTypes.array,
|
projectImages: PropTypes.array,
|
||||||
title: PropTypes.string
|
title: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
Project.propTypes = {
|
export default class Project extends PureComponent {
|
||||||
data: PropTypes.object.isRequired,
|
static propTypes = {
|
||||||
location: PropTypes.object.isRequired
|
data: PropTypes.object.isRequired,
|
||||||
}
|
location: PropTypes.object.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
export default Project
|
render() {
|
||||||
|
const project = this.props.data.projectsYaml
|
||||||
|
const projectImages = this.props.data.projectImages.edges
|
||||||
|
const descriptionHtml = this.props.data.projectsYaml.fields.descriptionHtml
|
||||||
|
const { title, links, techstack } = project
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SEO project={project} />
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1 className={styles.title}>{title}</h1>
|
||||||
|
</header>
|
||||||
|
<div
|
||||||
|
className={styles.description}
|
||||||
|
dangerouslySetInnerHTML={{ __html: descriptionHtml }}
|
||||||
|
/>
|
||||||
|
<ProjectImages projectImages={projectImages} title={title} />
|
||||||
|
<ProjectMeta links={links} techstack={techstack} />
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<ProjectNav slug={project.slug} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const projectAndProjectsQuery = graphql`
|
export const projectAndProjectsQuery = graphql`
|
||||||
query($slug: String!) {
|
query($slug: String!) {
|
||||||
@ -81,6 +82,9 @@ export const projectAndProjectsQuery = graphql`
|
|||||||
title
|
title
|
||||||
slug
|
slug
|
||||||
description
|
description
|
||||||
|
fields {
|
||||||
|
descriptionHtml
|
||||||
|
}
|
||||||
links {
|
links {
|
||||||
title
|
title
|
||||||
url
|
url
|
||||||
|
Loading…
Reference in New Issue
Block a user