1
0
mirror of https://github.com/kremalicious/portfolio.git synced 2024-06-26 11:16:40 +02:00

queries refactoring

This commit is contained in:
Matthias Kretschmann 2018-08-26 21:28:06 +02:00
parent 409a239104
commit 6bbf35b32e
Signed by: m
GPG Key ID: 606EEEF3C479A91F
10 changed files with 330 additions and 280 deletions

View File

@ -1,71 +1,22 @@
import React, { Fragment } from 'react' import React, { Fragment } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { StaticQuery, graphql } from 'gatsby'
import Head from './molecules/Head' import Head from './molecules/Head'
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'
const Layout = ({ children, location }) => { const Layout = ({ children, location }) => {
const isHomepage = location.pathname === '/'
return ( return (
<StaticQuery <Fragment>
query={graphql` <Head />
query { <Header isHomepage={isHomepage} />
# 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
const isHomepage = location.pathname === '/'
return ( <main className={styles.screen}>{children}</main>
<Fragment>
<Head meta={meta} />
<Header meta={meta} isHomepage={isHomepage} />
<main className={styles.screen}>{children}</main> <Footer />
</Fragment>
<Footer meta={meta} />
</Fragment>
)
}}
/>
) )
} }

View File

@ -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 Logo from '../svg/Logo' import Logo from '../svg/Logo'
import styles from './LogoUnit.module.scss' import styles from './LogoUnit.module.scss'
@ -25,23 +26,39 @@ class LogoUnit extends PureComponent {
} }
render() { render() {
const { meta } = this.props
const { minimal } = this.state
return ( return (
<div className={minimal ? styles.minimal : styles.logounit}> <StaticQuery
<Logo className={styles.logounit__logo} /> query={graphql`
<h1 className={styles.logounit__title}>{meta.title.toLowerCase()}</h1> query {
<p className={styles.logounit__description}> dataYaml {
<span>{'{ '}</span> {meta.tagline.toLowerCase()} <span>{' }'}</span> title
</p> tagline
</div> }
}
`}
render={data => {
const meta = data.dataYaml
const { minimal } = this.state
return (
<div className={minimal ? styles.minimal : styles.logounit}>
<Logo className={styles.logounit__logo} />
<h1 className={styles.logounit__title}>
{meta.title.toLowerCase()}
</h1>
<p className={styles.logounit__description}>
<span>{'{ '}</span> {meta.tagline.toLowerCase()}{' '}
<span>{' }'}</span>
</p>
</div>
)
}}
/>
) )
} }
} }
LogoUnit.propTypes = { LogoUnit.propTypes = {
meta: PropTypes.object.isRequired,
minimal: PropTypes.bool minimal: PropTypes.bool
} }

View File

@ -4,13 +4,13 @@ import { graphql } from 'gatsby'
import Img from 'gatsby-image' import Img from 'gatsby-image'
import styles from './ProjectImage.module.scss' import styles from './ProjectImage.module.scss'
const ProjectImage = props => ( const ProjectImage = ({ fluid, alt }) => (
<Img <Img
className={styles.project__image} className={styles.project__image}
outerWrapperClassName={styles.project__imagewrap} outerWrapperClassName={styles.project__imagewrap}
backgroundColor="#6b7f88" backgroundColor="#6b7f88"
fluid={props.fluid} fluid={fluid}
alt={props.alt} alt={alt}
/> />
) )

View File

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import Helmet from 'react-helmet' import Helmet from 'react-helmet'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { StaticQuery, graphql } from 'gatsby'
function truncate(n, useWordBoundary) { function truncate(n, useWordBoundary) {
if (this.length <= n) { if (this.length <= n) {
@ -14,49 +15,80 @@ function truncate(n, useWordBoundary) {
) )
} }
const SEO = ({ project, meta }) => { const SEO = ({ project }) => (
const title = project.title ? project.title : meta.title <StaticQuery
const description = project.description query={graphql`
? truncate.apply(project.description, [320, true]) query {
: truncate.apply(meta.description, [320, true]) dataYaml {
const image = project.img title
? project.img.childImageSharp.twitterImage.src tagline
: meta.img.childImageSharp.resize.src description
const url = project.slug ? `${meta.url}${project.slug}` : meta.url url
email
img {
childImageSharp {
resize(width: 980) {
src
}
}
}
social {
Email
Blog
Twitter
GitHub
Dribbble
}
gpg
addressbook
}
}
`}
render={data => {
const meta = data.dataYaml
return ( const title = project.title ? project.title : meta.title
<Helmet> const description = project.description
<html lang="en" /> ? truncate.apply(project.description, [320, true])
: truncate.apply(meta.description, [320, true])
const image = project.img
? project.img.childImageSharp.twitterImage.src
: meta.img.childImageSharp.resize.src
const url = project.slug ? `${meta.url}${project.slug}` : meta.url
{/* General tags */} return (
<meta name="description" content={description} /> <Helmet>
<meta name="image" content={`${meta.url}${image}`} /> <html lang="en" />
<link rel="canonical" href={url} />
{/* OpenGraph tags */} {/* General tags */}
<meta property="og:url" content={url} /> <meta name="description" content={description} />
<meta property="og:title" content={title} /> <meta name="image" content={`${meta.url}${image}`} />
<meta property="og:description" content={description} /> <link rel="canonical" href={url} />
<meta property="og:image" content={`${meta.url}${image}`} />
{/* Twitter Card tags */} {/* OpenGraph tags */}
<meta name="twitter:card" content="summary_large_image" /> <meta property="og:url" content={url} />
<meta name="twitter:creator" content={meta.social.Twitter} /> <meta property="og:title" content={title} />
<meta name="twitter:title" content={title} /> <meta property="og:description" content={description} />
<meta name="twitter:description" content={description} /> <meta property="og:image" content={`${meta.url}${image}`} />
<meta name="twitter:image" content={`${meta.url}${image}`} />
</Helmet> {/* Twitter Card tags */}
) <meta name="twitter:card" content="summary_large_image" />
} <meta name="twitter:creator" content={meta.social.Twitter} />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={`${meta.url}${image}`} />
</Helmet>
)
}}
/>
)
SEO.propTypes = { SEO.propTypes = {
project: PropTypes.object, project: PropTypes.object
meta: PropTypes.object
} }
SEO.defaultProps = { SEO.defaultProps = {
project: {}, project: {}
meta: {}
} }
export default SEO export default SEO

View File

@ -1,101 +1,132 @@
import React, { PureComponent } from 'react' import React from 'react'
import PropTypes from 'prop-types' import { StaticQuery, graphql } from 'gatsby'
import FileSaver from 'file-saver' import FileSaver from 'file-saver'
import vCard from 'vcf' import vCard from 'vcf'
class Vcard extends PureComponent { const Vcard = () => (
constructor(props) { <StaticQuery
super(props) query={graphql`
query {
dataYaml {
title
tagline
description
url
email
avatar {
childImageSharp {
original: resize {
src
}
}
}
social {
Email
Blog
Twitter
GitHub
Dribbble
}
gpg
addressbook
}
}
`}
render={data => {
const meta = data.dataYaml
const constructVcard = () => {
const contact = new vCard()
const photoSrc = meta.avatar.childImageSharp.original.src
// first, convert the avatar to base64,
// then construct all vCard elements
toDataURL(
photoSrc,
dataUrl => {
// stripping this data out of base64 string is required
// for vcard to actually display the image for whatever reason
const dataUrlCleaned = dataUrl
.split('data:image/jpeg;base64,')
.join('')
contact.set('photo', dataUrlCleaned, {
encoding: 'b',
type: 'JPEG'
})
contact.set('fn', meta.title)
contact.set('title', meta.tagline)
contact.set('email', meta.email)
contact.set('url', meta.url, { type: 'Portfolio' })
contact.add('url', meta.social.Blog, { type: 'Blog' })
contact.set('nickname', 'kremalicious')
contact.add('x-socialprofile', meta.social.Twitter, {
type: 'twitter'
})
contact.add('x-socialprofile', meta.social.GitHub, {
type: 'GitHub'
})
const vcard = contact.toString('3.0')
downloadVcard(vcard)
},
'image/jpeg'
)
}
// Construct the download from a blob of the just constructed vCard,
// and save it to user's file system
const downloadVcard = vcard => {
const name = meta.addressbook.split('/').join('')
const blob = new Blob([vcard], { type: 'text/x-vcard' })
FileSaver.saveAs(blob, name)
}
const handleAddressbookClick = e => {
e.preventDefault()
constructVcard()
}
return (
<a
// href is kinda fake, only there for usability
// so user knows what to expect when hovering the link before clicking
href={meta.addressbook}
onClick={handleAddressbookClick}
>
Add to addressbook
</a>
)
}}
/>
)
// Helper function to create base64 string from avatar image
// without the need to read image file from file system
const toDataURL = (src, callback, outputFormat) => {
const img = new Image()
img.crossOrigin = 'Anonymous'
img.onload = function() {
// yeah, we're gonna create a fake canvas to render the image
// and then create a base64 string from the rendered result
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
let dataURL
canvas.height = this.naturalHeight
canvas.width = this.naturalWidth
ctx.drawImage(this, 0, 0)
dataURL = canvas.toDataURL(outputFormat)
callback(dataURL)
} }
// Helper function to create base64 string from avatar image img.src = src
// without the need to read image file from file system if (img.complete || img.complete === undefined) {
toDataURL(src, callback, outputFormat) { img.src =
const img = new Image() 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='
img.crossOrigin = 'Anonymous'
img.onload = function() {
// yeah, we're gonna create a fake canvas to render the image
// and then create a base64 string from the rendered result
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
let dataURL
canvas.height = this.naturalHeight
canvas.width = this.naturalWidth
ctx.drawImage(this, 0, 0)
dataURL = canvas.toDataURL(outputFormat)
callback(dataURL)
}
img.src = src img.src = src
if (img.complete || img.complete === undefined) {
img.src =
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='
img.src = src
}
} }
constructVcard() {
const meta = this.props.meta
const contact = new vCard()
const photoSrc = meta.avatar.childImageSharp.original.src
// first, convert the avatar to base64,
// then construct all vCard elements
this.toDataURL(
photoSrc,
dataUrl => {
// stripping this data out of base64 string is required
// for vcard to actually display the image for whatever reason
const dataUrlCleaned = dataUrl.split('data:image/jpeg;base64,').join('')
contact.set('photo', dataUrlCleaned, { encoding: 'b', type: 'JPEG' })
contact.set('fn', meta.title)
contact.set('title', meta.tagline)
contact.set('email', meta.email)
contact.set('url', meta.url, { type: 'Portfolio' })
contact.add('url', meta.social.Blog, { type: 'Blog' })
contact.set('nickname', 'kremalicious')
contact.add('x-socialprofile', meta.social.Twitter, { type: 'twitter' })
contact.add('x-socialprofile', meta.social.GitHub, { type: 'GitHub' })
const vcard = contact.toString('3.0')
this.downloadVcard(vcard)
},
'image/jpeg'
)
}
// Construct the download from a blob of the just constructed vCard,
// and save it to user's file system
downloadVcard(vcard) {
const name = this.props.meta.addressbook.split('/').join('')
const blob = new Blob([vcard], { type: 'text/x-vcard' })
FileSaver.saveAs(blob, name)
}
handleAddressbookClick = e => {
e.preventDefault()
this.constructVcard()
}
render() {
return (
<a
// href is kinda fake, only there for usability
// so user knows what to expect when hovering the link before clicking
href={this.props.meta.addressbook}
onClick={this.handleAddressbookClick}
>
Add to addressbook
</a>
)
}
}
Vcard.propTypes = {
meta: PropTypes.object
} }
export default Vcard export default Vcard

View File

@ -1,30 +1,41 @@
import React, { Fragment } from 'react' import React, { Fragment } from 'react'
import PropTypes from 'prop-types'
import Helmet from 'react-helmet' import Helmet from 'react-helmet'
import { StaticQuery, graphql } from 'gatsby'
import SEO from '../atoms/SEO' import SEO from '../atoms/SEO'
import Typekit from '../atoms/Typekit' import Typekit from '../atoms/Typekit'
const Head = ({ meta }) => { const Head = () => (
const { title, tagline } = meta <StaticQuery
query={graphql`
query {
dataYaml {
title
tagline
}
}
`}
render={data => {
const { title, tagline } = data.dataYaml
return ( return (
<Fragment> <Fragment>
<Helmet <Helmet
defaultTitle={`${title.toLowerCase()} { ${tagline.toLowerCase()} }`} defaultTitle={`${title.toLowerCase()} { ${tagline.toLowerCase()} }`}
titleTemplate={`%s // ${title.toLowerCase()} { ${tagline.toLowerCase()} }`} titleTemplate={`%s // ${title.toLowerCase()} { ${tagline.toLowerCase()} }`}
> >
<meta name="apple-mobile-web-app-title" content={title.toLowerCase()} /> <meta
</Helmet> name="apple-mobile-web-app-title"
content={title.toLowerCase()}
/>
</Helmet>
<Typekit /> <Typekit />
<SEO meta={meta} /> <SEO />
</Fragment> </Fragment>
) )
} }}
/>
Head.propTypes = { )
meta: PropTypes.object.isRequired
}
export default Head export default Head

View File

@ -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 { FadeIn } from '../atoms/Animations' import { FadeIn } from '../atoms/Animations'
import Email from '../svg/Email' import Email from '../svg/Email'
@ -55,28 +56,44 @@ class Network extends PureComponent {
render() { render() {
return ( return (
!this.props.hide && ( <StaticQuery
<FadeIn> query={graphql`
<aside className={this.state.classes}> query {
{Object.keys(this.props.meta.social).map((key, i) => ( dataYaml {
<a social {
className={styles.link} Email
href={this.props.meta.social[key]} Blog
key={i} Twitter
> GitHub
<NetworkIcon title={key} className={icons.icon} /> Dribbble
<span className={styles.title}>{key}</span> }
</a> }
))} }
</aside> `}
</FadeIn> render={data => {
) const meta = data.dataYaml
return (
!this.props.hide && (
<FadeIn>
<aside className={this.state.classes}>
{Object.keys(meta.social).map((key, i) => (
<a className={styles.link} href={meta.social[key]} key={i}>
<NetworkIcon title={key} className={icons.icon} />
<span className={styles.title}>{key}</span>
</a>
))}
</aside>
</FadeIn>
)
)
}}
/>
) )
} }
} }
Network.propTypes = { Network.propTypes = {
meta: PropTypes.object,
minimal: PropTypes.bool, minimal: PropTypes.bool,
hide: PropTypes.bool hide: PropTypes.bool
} }

View File

@ -1,5 +1,4 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { StaticQuery, graphql } from 'gatsby' 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'
@ -14,8 +13,6 @@ class Footer extends PureComponent {
} }
render() { render() {
const meta = this.props.meta
return ( return (
<StaticQuery <StaticQuery
query={graphql` query={graphql`
@ -27,18 +24,24 @@ class Footer extends PureComponent {
repository repository
bugs bugs
} }
dataYaml {
title
gpg
}
} }
`} `}
render={data => { render={data => {
const pkg = data.portfolioJson const pkg = data.portfolioJson
const meta = data.dataYaml
return ( return (
<footer className={styles.footer}> <footer className={styles.footer}>
<LogoUnit meta={meta} minimal /> <LogoUnit minimal />
<Networks meta={meta} minimal /> <Networks minimal />
<p className={styles.footer__actions}> <p className={styles.footer__actions}>
<Vcard meta={meta} /> <Vcard />
<a href={meta.gpg}>PGP/GPG key</a> <a href={meta.gpg}>PGP/GPG key</a>
<a href={pkg.bugs}>Found a bug?</a> <a href={pkg.bugs}>Found a bug?</a>
</p> </p>
@ -56,9 +59,4 @@ class Footer extends PureComponent {
} }
} }
Footer.propTypes = {
meta: PropTypes.object,
pkg: PropTypes.object
}
export default Footer export default Footer

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { Link } from 'gatsby'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Link, StaticQuery, graphql } from 'gatsby'
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'
@ -29,28 +29,44 @@ class Header extends PureComponent {
} }
render() { render() {
const { isHomepage, meta } = this.props const { isHomepage } = this.props
const { minimal } = this.state const { minimal } = this.state
return ( return (
<header className={minimal ? styles.minimal : styles.header}> <StaticQuery
<ThemeSwitch /> query={graphql`
query {
dataYaml {
availability {
status
}
}
}
`}
render={data => {
const meta = data.dataYaml
<Link className={styles.header__link} to={'/'}> return (
<LogoUnit meta={meta} minimal={!isHomepage} /> <header className={minimal ? styles.minimal : styles.header}>
</Link> <ThemeSwitch />
<Networks meta={meta} hide={!isHomepage} /> <Link className={styles.header__link} to={'/'}>
<LogoUnit minimal={!isHomepage} />
</Link>
<Availability hide={!isHomepage && !meta.availability.status} /> <Networks hide={!isHomepage} />
</header>
<Availability hide={!isHomepage && !meta.availability.status} />
</header>
)
}}
/>
) )
} }
} }
Header.propTypes = { Header.propTypes = {
isHomepage: PropTypes.bool, isHomepage: PropTypes.bool
meta: PropTypes.object
} }
export default Header export default Header

View File

@ -32,7 +32,6 @@ const ProjectImages = ({ projectImages, title }) => (
) )
const Project = ({ data, location }) => { const Project = ({ data, location }) => {
const meta = data.dataYaml
const project = data.projectsYaml const project = data.projectsYaml
const projectImages = data.projectImages.edges const projectImages = data.projectImages.edges
const { title, links, techstack } = project const { title, links, techstack } = project
@ -43,7 +42,7 @@ const Project = ({ data, location }) => {
<Layout location={location}> <Layout location={location}>
<Helmet title={title} /> <Helmet title={title} />
<SEO project={project} meta={meta} /> <SEO project={project} />
<article className={styles.project}> <article className={styles.project}>
<Content> <Content>
@ -99,28 +98,6 @@ export const projectAndProjectsQuery = graphql`
} }
} }
# the data/meta.yml file
dataYaml {
title
tagline
description
url
social {
Email
Blog
Twitter
GitHub
Dribbble
}
img {
childImageSharp {
resize(width: 980) {
src
}
}
}
}
projectImages: allImageSharp( projectImages: allImageSharp(
filter: { fluid: { originalName: { regex: $slug } } } filter: { fluid: { originalName: { regex: $slug } } }
sort: { fields: [fluid___originalName], order: ASC } sort: { fields: [fluid___originalName], order: ASC }