Compare commits

...

4 Commits

22 changed files with 512 additions and 318 deletions

View File

@ -17,7 +17,8 @@
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
"react-helmet": "^5.2.1", "react-helmet": "^5.2.1",
"styled-components": "^5.2.1" "styled-components": "^5.2.1",
"zustand": "^3.3.1"
}, },
"scripts": { "scripts": {
"build": "gatsby build", "build": "gatsby build",

50
src/components/Alert.js Normal file
View File

@ -0,0 +1,50 @@
import React from 'react';
import styled from 'styled-components';
import useStore from '../store';
import Emoji from './Emoji';
const bgs = {
green: {
light: 'beige',
dark: 'darkolivegreen'
},
blue: {
light: 'aliceblue',
dark: 'steelblue',
},
orange: {
light: 'linen',
dark: 'sienna'
},
red: {
light: 'lightpink',
dark: 'mediumvioletred'
},
white: {
light: 'floralwhite',
dark: 'darkslategrey'
},
}
const StyledAlert = styled.div`
margin: 30px 0px;
padding: 10px;
border-radius: 3px;
background-color: ${p => bgs[p.colour][p.theme]};
color: ${p => p.theme === 'dark' ? 'white' : null};
h3{
margin-top: 0px;
}
`;
const Alert = ({ emoji, title, colour, children }) => {
const theme = useStore(s => s.theme);
return (
<StyledAlert colour={colour} theme={theme}>
<h3>{emoji && <Emoji e={emoji} />} {title}</h3>
{children}
</StyledAlert>
);
}
export default Alert;

View File

@ -2,16 +2,20 @@ import React from 'react'
import { Link } from 'gatsby' import { Link } from 'gatsby'
import moment from 'moment'; import moment from 'moment';
import Emoji from './Emoji'; import Emoji from './Emoji';
import Alert from './Alert';
import Tag from './Tag';
const BlogPostHeader = ({ post }) => ( const BlogPostHeader = ({ post }) => (
<div style={{marginBottom: '1.45rem', background: 'floralwhite', padding: 3, borderRadius: 3}}> <Alert
<h3><Emoji e='📝' /> <Link to={post.fields.slug}>{post.frontmatter.title}</Link> <small>{moment(post.frontmatter.date).format('D MMMM YYYY')}</small></h3> colour='white'
title={<span><Emoji e='📝' /> <Link to={post.fields.slug}>{post.frontmatter.title}</Link> <small>{moment(post.frontmatter.date).format('D MMMM YYYY')}</small></span>}
>
<div>
<p><em>{post.excerpt}</em></p> <p><em>{post.excerpt}</em></p>
{post.frontmatter.tags && post.frontmatter.tags.map(tag => {post.frontmatter.tags && post.frontmatter.tags.map(tag => <Tag key={tag} tag={tag} />)}
<Link style={{marginRight: 10}} to={`/tags/${tag}`}><Emoji e="🏷️" /> #{tag}</Link>
)}
</div> </div>
</Alert>
) )
export default BlogPostHeader; export default BlogPostHeader;

View File

@ -3,9 +3,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons' import { faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons'
const ExternalLink = (props) => { const ExternalLink = (props) => {
const isExternal = props.href.indexOf('http') === 0 && props.href.indexOf('wilw.dev') === -1;
return ( return (
<a {...props} href={props.href} target="_blank" rel="noopener noreferrer">{props.children} {isExternal && <FontAwesomeIcon icon={faExternalLinkAlt} />}</a> <a {...props} href={props.href} target="_blank" rel="noopener noreferrer">{props.children} <FontAwesomeIcon icon={faExternalLinkAlt} /></a>
); );
} }

View File

@ -2,8 +2,10 @@ import React from 'react'
import { Link } from 'gatsby' import { Link } from 'gatsby'
import styled from 'styled-components'; import styled from 'styled-components';
import Emoji from './Emoji'; import Emoji from './Emoji';
import useStore from '../store';
import { themes } from '../components/Layout/Layout'
import Will from '../images/will.jpg'; import Will from '../images/will.jpeg';
const StyledHeader = styled.header` const StyledHeader = styled.header`
text-align: left; text-align: left;
@ -53,8 +55,23 @@ const StyledNavBar = styled.div`
font-size: 20px; font-size: 20px;
} }
`; `;
const StyledThemeSelector = styled.select`
padding: 4px;
background: white;
border: 1px solid gray;
border-radius: 5px;
cursor: pointer;
`;
const Header = () => ( const Header = () => {
const store = useStore();
const switchTheme = themeName => {
store.setTheme(themeName);
localStorage.setItem('theme', themeName);
};
return (
<StyledHeader> <StyledHeader>
<StyledHeaderArea> <StyledHeaderArea>
<Link to='/'><StyledAvatar /></Link> <Link to='/'><StyledAvatar /></Link>
@ -71,8 +88,14 @@ const Header = () => (
<Link to='/notes' activeClassName='active-link'>notes</Link> <Link to='/notes' activeClassName='active-link'>notes</Link>
<Link to='/projects' activeClassName='active-link'>projects</Link> <Link to='/projects' activeClassName='active-link'>projects</Link>
<Link to='/research' activeClassName='active-link'>research</Link> <Link to='/research' activeClassName='active-link'>research</Link>
<StyledThemeSelector value={store.theme} onChange={e => switchTheme(e.target.value)}>
{Object.keys(themes).map(themeName =>
<option key={themeName} value={themeName}>{themes[themeName].name}</option>
)}
</StyledThemeSelector>
</StyledNavBar> </StyledNavBar>
</StyledHeader> </StyledHeader>
) );
}
export default Header export default Header

View File

@ -12,19 +12,6 @@ html {
overflow-y: scroll; overflow-y: scroll;
} }
a {
color: #01BAEF;
background-color: transparent;
-webkit-text-decoration-skip: objects;
}
a:active,
a:hover {
outline-width: 0;
}
a.active-link{
color:rgb(50,50,50);
}
body { body {
margin: 0; margin: 0;
font-family: 'Recursive', monospace; font-family: 'Recursive', monospace;
@ -84,6 +71,14 @@ h4 {
line-height: 1.3; line-height: 1.3;
} }
a {
background-color: transparent;
-webkit-text-decoration-skip: objects;
}
a:active, a:hover {
outline-width: 0;
}
p { p {
margin: 0px 0px 15px 0px; margin: 0px 0px 15px 0px;
} }

View File

@ -1,11 +1,47 @@
import React from 'react' import React, { useEffect } from 'react'
import Helmet from 'react-helmet' import Helmet from 'react-helmet'
import styled from 'styled-components' import styled, { createGlobalStyle } from 'styled-components'
import Header from '../Header' import Header from '../Header'
import Footer from '../Footer'; import Footer from '../Footer';
import useStore from '../../store';
import Will from '../../images/will.png'; import Will from '../../images/will.png';
import './Layout.css' import './Layout.css';
export const themes = {
light: {
name: '☀️ Light Theme',
background: 'white',
text: 'black',
links: '#01BAEF',
activeLinks: 'rgb(50,50,50)',
},
dark: {
name: '🌒 Dark Theme',
background: '#0f0e17',
text: '#a7a9be',
headers: '#fffffe',
links: '#ff8906',
activeLinks: 'rgb(200,200,200)',
}
}
const GlobalStyle = createGlobalStyle`
body{
background-color: ${({ theme }) => theme.background};
color: ${({ theme }) => theme.text};
transition: background-color 0.25s, color 0.25s;
}
h1,h2,h3,h4 {
color: ${({ theme }) => theme.headers};
}
a {
color: ${({ theme }) => theme.links};
&.active-link {
color: ${({ theme }) => theme.activeLinks};
}
}
`;
const StyledMain = styled.div` const StyledMain = styled.div`
margin: 0px auto; margin: 0px auto;
@ -13,8 +49,23 @@ const StyledMain = styled.div`
padding: 0px 1.1rem 1.45rem; padding: 0px 1.1rem 1.45rem;
`; `;
const TemplateWrapper = ({ children }) => ( const TemplateWrapper = ({ children }) => {
const theme = useStore(s => s.theme);
const setTheme = useStore(s => s.setTheme);
useEffect(() => {
const rememberedTheme = localStorage.getItem('theme');
if (rememberedTheme && themes[rememberedTheme]) {
setTheme(rememberedTheme);
} else {
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
if (isDarkMode) setTheme('dark');
}
}, [setTheme]);
return (
<div> <div>
<GlobalStyle theme={themes[theme]} />
<Helmet titleTemplate="%s | Will Webberley" defaultTitle="Will Webberley"> <Helmet titleTemplate="%s | Will Webberley" defaultTitle="Will Webberley">
<link rel="icon" type="image/png" href={Will} /> <link rel="icon" type="image/png" href={Will} />
</Helmet> </Helmet>
@ -22,6 +73,7 @@ const TemplateWrapper = ({ children }) => (
<StyledMain>{children}</StyledMain> <StyledMain>{children}</StyledMain>
<Footer /> <Footer />
</div> </div>
) );
}
export default TemplateWrapper export default TemplateWrapper

View File

@ -2,16 +2,15 @@ import React from 'react'
import { Link } from 'gatsby' import { Link } from 'gatsby'
import moment from 'moment'; import moment from 'moment';
import Emoji from './Emoji'; import Emoji from './Emoji';
import Alert from './Alert';
import Tag from './Tag';
const NoteHeader = ({ post }) => ( const NoteHeader = ({ post }) => (
<Alert colour='blue'
<div style={{marginBottom: '1.45rem', background: 'aliceblue', padding: 3, borderRadius: 3}}> title={<span><Emoji e='📔' /> <Link to={post.fields.slug}>{post.frontmatter.title}</Link> <small>(last updated {moment(post.frontmatter.date).format('D MMMM YYYY')})</small></span>}>
<h3><Emoji e='📔' /> <Link to={post.fields.slug}>{post.frontmatter.title}</Link> <small>(last updated {moment(post.frontmatter.date).format('D MMMM YYYY')})</small></h3>
<p>{post.frontmatter.description}</p> <p>{post.frontmatter.description}</p>
{post.frontmatter.tags && post.frontmatter.tags.map(tag => {post.frontmatter.tags && post.frontmatter.tags.map(tag => <Tag key={tag} tag={tag} />)}
<Link style={{marginRight: 10}} to={`/tags/${tag}`}><Emoji e="🏷️" /> #{tag}</Link> </Alert>
)} );
</div>
)
export default NoteHeader; export default NoteHeader;

15
src/components/Tag.js Normal file
View File

@ -0,0 +1,15 @@
import React from 'react';
import { Link } from 'gatsby';
import styled from 'styled-components';
import Emoji from '../components/Emoji';
const StlyedTag = styled(Link)`
margin-left: 10px;
`;
const Tag = ({ tag }) => (
<StlyedTag key={tag} to={`/tags/${tag}`}><Emoji e="🏷️" /> #{tag}</StlyedTag>
);
export default Tag;

BIN
src/images/will.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -4,8 +4,8 @@ import Layout from '../components/Layout/Layout.js';
const ErrorPage = () => ( const ErrorPage = () => (
<Layout> <Layout>
<h2 style={{fontFamily:'Courier, Monospace'}}>~/errors/404</h2> <h2>Not found</h2>
<p>The requested file was not found.</p> <p>The requested file could not be found.</p>
</Layout> </Layout>
) )

View File

@ -1,12 +1,18 @@
import React from "react"; import React from "react";
import { graphql, Link } from 'gatsby'; import { graphql, Link } from 'gatsby';
import Helmet from 'react-helmet' import Helmet from 'react-helmet';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faRss } from '@fortawesome/free-solid-svg-icons' import { faRss } from '@fortawesome/free-solid-svg-icons';
import styled from 'styled-components';
import Layout from '../../components/Layout/Layout.js'; import Layout from '../../components/Layout/Layout.js';
import BlogPostHeader from '../../components/BlogPostHeader'; import BlogPostHeader from '../../components/BlogPostHeader';
const StyledHeader = styled.h2`
display: flex;
justify-content: space-between;
`;
export default class Blog extends React.Component { export default class Blog extends React.Component {
render() { render() {
@ -15,12 +21,15 @@ export default class Blog extends React.Component {
<Helmet title='Blog'> <Helmet title='Blog'>
<meta name="description" content="Blog post entries and articles" /> <meta name="description" content="Blog post entries and articles" />
</Helmet> </Helmet>
<h2>Blog posts <StyledHeader>
<small style={{float: 'right'}}><FontAwesomeIcon icon={faRss} /> <Link to='/feeds'>RSS feeds</Link></small> <span>Blog posts</span>
</h2> <small>
<FontAwesomeIcon icon={faRss} /> <Link to='/feeds'>RSS feeds</Link>
</small>
</StyledHeader>
{this.props.data.allMarkdownRemark.edges.map(({ node }) => { {this.props.data.allMarkdownRemark.edges.map(({ node }) => {
if (node.fields.slug.includes('/blog/')) { if (node.fields.slug.includes('/blog/')) {
return <BlogPostHeader post={node} />; return <BlogPostHeader key={node.fields.slug} post={node} />;
} else return null; } else return null;
})} })}

View File

@ -7,6 +7,7 @@ import styled from 'styled-components';
import Layout from '../components/Layout/Layout.js'; import Layout from '../components/Layout/Layout.js';
import ExternalLink from '../components/ExternalLink'; import ExternalLink from '../components/ExternalLink';
import Emoji from '../components/Emoji'; import Emoji from '../components/Emoji';
import Alert from '../components/Alert';
const StyledButton = styled(ExternalLink)` const StyledButton = styled(ExternalLink)`
display: inline-block; display: inline-block;
@ -64,25 +65,20 @@ const FeedsPage = () => {
<p>RSS lets you subscribe to receive new articles (blog posts, notes, etc.) as they are published.</p> <p>RSS lets you subscribe to receive new articles (blog posts, notes, etc.) as they are published.</p>
<p>My site has a number of different RSS feeds you can subscribe to, so you can choose the one(s) you want and avoid the content you're not interested in.</p> <p>My site has a number of different RSS feeds you can subscribe to, so you can choose the one(s) you want and avoid the content you're not interested in.</p>
<div style={{background: 'linen', padding: 10, borderRadius: 3, margin: '20px 0px'}}> <Alert colour='orange' title='Do you need an RSS reader?'>
<h3>Do you need an RSS reader?</h3>
<p>There are lots of good ones. Just <ExternalLink href="https://duckduckgo.com/?q=rss+reader">search</ExternalLink> for one for your device. <p>There are lots of good ones. Just <ExternalLink href="https://duckduckgo.com/?q=rss+reader">search</ExternalLink> for one for your device.
I use <ExternalLink href="https://www.reederapp.com/">Reeder 5</ExternalLink> on my <ExternalLink href="https://itunes.apple.com/app/id1529448980">Mac</ExternalLink> and <ExternalLink href="https://apps.apple.com/app/id1529445840">iPhone</ExternalLink>.</p> I use <ExternalLink href="https://www.reederapp.com/">Reeder 5</ExternalLink> on my <ExternalLink href="https://itunes.apple.com/app/id1529448980">Mac</ExternalLink> and <ExternalLink href="https://apps.apple.com/app/id1529445840">iPhone</ExternalLink>.</p>
</div> </Alert>
<h3><Emoji e='🌍' /> Recommended: enter <code>wilw.dev <StyledCopyButton onClick={copyFeedsUrl}><FontAwesomeIcon icon={faCopy} /></StyledCopyButton></code> into your RSS reader &amp; it should list the available feeds</h3> <h3><Emoji e='🌍' /> Recommended: enter <code>wilw.dev <StyledCopyButton onClick={copyFeedsUrl}><FontAwesomeIcon icon={faCopy} /></StyledCopyButton></code> into your RSS reader &amp; it should list the available feeds</h3>
<p>Alternatively you can choose a feed from the options below.</p> <p>Alternatively you can choose a feed from the options below.</p>
<div style={{marginTop: 20}}>
{feeds.map(feed => {feeds.map(feed =>
<div style={{background: 'whitesmoke', padding: 10, borderRadius: 3, margin: '20px 0px'}}> <Alert key={feed.url} colour='green' title={<span><FontAwesomeIcon icon={faRssSquare} /> {feed.name}</span>}>
<h3><FontAwesomeIcon icon={faRssSquare} /> {feed.name}</h3>
<p>{feed.description}</p> <p>{feed.description}</p>
<StyledButton href={feed.url}>Subscribe</StyledButton> <StyledButton href={feed.url}>Subscribe</StyledButton>
</div> </Alert>
)} )}
</div>
</Layout> </Layout>
); );
} }

View File

@ -6,6 +6,7 @@ import { faRss } from '@fortawesome/free-solid-svg-icons'
import Layout from '../components/Layout/Layout.js'; import Layout from '../components/Layout/Layout.js';
import Emoji from '../components/Emoji'; import Emoji from '../components/Emoji';
import Alert from '../components/Alert';
import ArticleHeader from '../components/ArticleHeader'; import ArticleHeader from '../components/ArticleHeader';
import ExternalLink from '../components/ExternalLink'; import ExternalLink from '../components/ExternalLink';
@ -17,11 +18,12 @@ const IndexPage = (props) => (
<p><Emoji n="Live long" e='🖖' /> Hello and welcome. I'm a tech lead/enthusiast based in Wales, UK. My main interests are indie and open-source projects, web and mobile technologies, serverless, IoT and automation.</p> <p><Emoji n="Live long" e='🖖' /> Hello and welcome. I'm a tech lead/enthusiast based in Wales, UK. My main interests are indie and open-source projects, web and mobile technologies, serverless, IoT and automation.</p>
<div style={{background: 'beige', padding: '2px', borderRadius: 3, margin: '40px 0px'}}> <Alert colour='green'
<h3><Emoji e='💯' /> 100 Days to Offload</h3> title={<span><Emoji e='💯' /> 100 Days to Offload</span>}
>
<p>I'm taking part in the <ExternalLink href="https://100daystooffload.com/">100 Days to Offload challenge</ExternalLink> in 2021.</p> <p>I'm taking part in the <ExternalLink href="https://100daystooffload.com/">100 Days to Offload challenge</ExternalLink> in 2021.</p>
<p><Link to='/blog/2021/01/29/100-days-to-offload'>Read more about it</Link> and <Link to='/tags/100daystooffload'>see what I've written so far</Link> for this challenge.</p> <p><Link to='/blog/2021/01/29/100-days-to-offload'>Read more about it</Link> and <Link to='/tags/100daystooffload'>see what I've written so far</Link> for this challenge.</p>
</div> </Alert>
<h3>Quick background</h3> <h3>Quick background</h3>
<p><Emoji e='💡' /> Since 2016 I have been Chief Technology Officer at enterprise SaaS company <ExternalLink href="https://simplydo.co.uk">Simply Do Ideas</ExternalLink>. Before this I was a software engineer at <ExternalLink href="https://www.chaserhq.com">Chaser</ExternalLink>.</p> <p><Emoji e='💡' /> Since 2016 I have been Chief Technology Officer at enterprise SaaS company <ExternalLink href="https://simplydo.co.uk">Simply Do Ideas</ExternalLink>. Before this I was a software engineer at <ExternalLink href="https://www.chaserhq.com">Chaser</ExternalLink>.</p>
@ -46,7 +48,7 @@ const IndexPage = (props) => (
<h3>Recent posts</h3> <h3>Recent posts</h3>
{props.data.recentPosts.edges {props.data.recentPosts.edges
.map(({ node }) => node.fields.slug.indexOf('blog') > -1 ? <ArticleHeader post={node} /> : null)} .map(({ node }) => node.fields.slug.indexOf('blog') > -1 ? <ArticleHeader key={node.fields.slug} post={node} /> : null)}
<p><Link to='/blog'>See more</Link></p> <p><Link to='/blog'>See more</Link></p>
</Layout> </Layout>
) )

View File

@ -17,7 +17,7 @@ export default class Notes extends React.Component {
<p>These are notes that I try and keep up-to-date. View <Link to='/blog'>my blog</Link> to see a post stream.</p> <p>These are notes that I try and keep up-to-date. View <Link to='/blog'>my blog</Link> to see a post stream.</p>
{this.props.data.notesQuery.edges.map(({ node }) => { {this.props.data.notesQuery.edges.map(({ node }) => {
if (node.fields.slug.includes('/notes/')) { if (node.fields.slug.includes('/notes/')) {
return <NoteHeader post={node} />; return <NoteHeader key={node.fields.slug} post={node} />;
} else return null; } else return null;
})} })}

View File

@ -1,15 +1,15 @@
import React from 'react' import React from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faLaptop } from '@fortawesome/free-solid-svg-icons' import { faLaptop } from '@fortawesome/free-solid-svg-icons';
import { faLinux, faApple, faAndroid, faGithub } from '@fortawesome/free-brands-svg-icons'; import { faLinux, faApple, faAndroid, faGithub } from '@fortawesome/free-brands-svg-icons';
import Helmet from 'react-helmet' import Helmet from 'react-helmet';
import Layout from '../components/Layout/Layout.js'; import styled from 'styled-components';
import Layout from '../components/Layout/Layout.js';
import treadlIcon from '../images/treadl.png'; import treadlIcon from '../images/treadl.png';
import dottyIcon from '../images/dotty.png'; import dottyIcon from '../images/dotty.png';
import ssotoolsIcon from '../images/ssotools.png'; import ssotoolsIcon from '../images/ssotools.png';
const ProjectsPage = () => {
const projects = [ const projects = [
{ {
name: 'Treadl', name: 'Treadl',
@ -79,28 +79,51 @@ const ProjectsPage = () => {
} }
]; ];
return ( const StyledProjects = styled.div`
display: grid;
grid-column-gap: 20px;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
`;
const StyledProject = styled.div`
display: flex;
flex-direction: row;
margin-bottom: 30px;
border-bottom: 1px solid rgba(150,150,150,0.2);
`;
const StyledProjectImage = styled.div`
padding-right: 30px;
padding-top: 30px;
img{
width: 100px;
}
`;
const StyledPlatform = styled.a`
text-decoration: none;
margin-right: 15px;
`;
const ProjectsPage = () => (
<Layout> <Layout>
<Helmet title='Projects'> <Helmet title='Projects'>
<meta name="description" content="Projects I'm working on now and in the past" /> <meta name="description" content="Projects I'm working on now and in the past" />
</Helmet> </Helmet>
<h2>Projects</h2> <h2>Projects</h2>
<div style={{display:'grid', gridColumnGap: 20, gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))'}}> <StyledProjects>
<div> <div>
<p>Some of the things I am currently working on. Please <a href='https://twitter.com/willwebberley' target='_blank' rel='noopener noreferrer'>get in touch</a> if you are interested in finding out more about these (or if you'd like to help out or get involved!).</p> <p>Some of the things I am currently working on. Please <a href='https://twitter.com/willwebberley' target='_blank' rel='noopener noreferrer'>get in touch</a> if you are interested in finding out more about these (or if you'd like to help out or get involved!).</p>
{projects.map((p, i) => {projects.map((p, i) =>
<div key={i} style={{display: 'flex', flexDirection: 'row', marginBottom: 30, borderBottom: '1px solid rgb(250,250,250)'}}> <StyledProject key={i}>
<div style={{paddingRight: 30, paddingTop: 30}}> <StyledProjectImage>
<img alt={p.name} src={p.logo} style={{width: 100}} /> <img alt={p.name} src={p.logo} />
</div> </StyledProjectImage>
<div> <div>
<h3><a href={p.url} target='_blank' rel='noopener noreferrer'>{p.name}</a></h3> <h3><a href={p.url} target='_blank' rel='noopener noreferrer'>{p.name}</a></h3>
<p>{p.description}</p> <p>{p.description}</p>
<p>{p.availableFor && p.availableFor.map((a, j) => <p>{p.availableFor && p.availableFor.map((a, j) =>
<a key={j} href={a.url || p.url} target="_blank" rel="noopener noreferrer" style={{textDecoration: 'none', marginRight: 15}}>{a.icon && <FontAwesomeIcon icon={a.icon} />} {a.name}</a> <StyledPlatform key={j} href={a.url || p.url} target="_blank" rel="noopener noreferrer">{a.icon && <FontAwesomeIcon icon={a.icon} />} {a.name}</StyledPlatform>
)}</p> )}</p>
</div> </div>
</div> </StyledProject>
)} )}
</div> </div>
<div> <div>
@ -113,9 +136,8 @@ const ProjectsPage = () => {
</div> </div>
)} )}
</div> </div>
</div> </StyledProjects>
</Layout> </Layout>
) );
}
export default ProjectsPage export default ProjectsPage

View File

@ -1,9 +1,9 @@
import React from 'react' import React from 'react';
import Helmet from 'react-helmet' import Helmet from 'react-helmet';
import styled from 'styled-components';
import Layout from '../components/Layout/Layout.js'; import Layout from '../components/Layout/Layout.js';
const ProjectsPage = () => {
const outputs = [ const outputs = [
{ {
name: 'Retweeting: A Study of Message-Forwarding in Twitter', name: 'Retweeting: A Study of Message-Forwarding in Twitter',
@ -104,15 +104,37 @@ const ProjectsPage = () => {
} }
]; ];
return ( const StyledResearch = styled.div`
display: grid;
grid-column-gap: 20px;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
`;
const StyledPublication = styled.div`
display: flex;
flex-direction: row;
margin-bottom: 10px;
border-bottom: 1px solid rgba(150,150,150,0.2);
`;
const PublicationTitle = styled.h4`
margin-top: 0px;
`;
const PublicationAuthors = styled.p`
margin-bottom: 0px;
font-size: 15px;
`;
const PublicationAttribution = styled.p`
font-size: 13px;
`;
const ProjectsPage = () => (
<Layout> <Layout>
<Helmet title='Research'> <Helmet title='Research'>
<meta name="description" content="Research work papers and outputs" /> <meta name="description" content="Research work papers and outputs" />
</Helmet> </Helmet>
<h2>Research</h2> <h2>Research</h2>
<div style={{display:'grid', gridColumnGap: 20, gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))'}}> <StyledResearch>
<div> <div>
<h3 style={{marginTop: 0}}>Projects</h3> <h3>Projects</h3>
<p>I have been involved in a number of research projects.</p> <p>I have been involved in a number of research projects.</p>
<h4>NIS/DAIS-ITA project</h4> <h4>NIS/DAIS-ITA project</h4>
@ -123,20 +145,19 @@ const ProjectsPage = () => {
</div> </div>
<div> <div>
<h3 style={{marginTop: 0}}>Publications</h3> <h3>Publications</h3>
{outputs.reverse().map((o, i) => {outputs.reverse().map((o, i) =>
<div key={i} style={{display: 'flex', flexDirection: 'row', marginBottom: 10, borderBottom: '1px solid rgb(250,250,250)'}}> <StyledPublication key={i}>
<div> <div>
<h4 style={{marginTop:0}}><a href={o.url} target='_blank' rel='noopener noreferrer'>{o.name}</a></h4> <PublicationTitle><a href={o.url} target='_blank' rel='noopener noreferrer'>{o.name}</a></PublicationTitle>
<p style={{marginBottom:0, fontSize:15}}>{o.authors}</p> <PublicationAuthors>{o.authors}</PublicationAuthors>
<p style={{fontSize: 13}}>{o.journal}, {o.date}</p> <PublicationAttribution>{o.journal}, {o.date}</PublicationAttribution>
</div>
</div> </div>
</StyledPublication>
)} )}
</div> </div>
</div> </StyledResearch>
</Layout> </Layout>
) );
}
export default ProjectsPage export default ProjectsPage

View File

@ -29,7 +29,8 @@ const ThisPage = () => {
<p>This is a static site built using <ExternalLink href="https://www.gatsbyjs.com">GatsbyJS</ExternalLink> - an open-source generator that leverages React to build performant websites.</p> <p>This is a static site built using <ExternalLink href="https://www.gatsbyjs.com">GatsbyJS</ExternalLink> - an open-source generator that leverages React to build performant websites.</p>
<p>I deploy the site using <ExternalLink href="https://vercel.com">Vercel</ExternalLink>, which uses its network of CDN nodes to serve the static site assets.</p> <p>I deploy the site using <ExternalLink href="https://vercel.com">Vercel</ExternalLink>, which uses its network of CDN nodes to serve the static site assets.</p>
<p>I use the <ExternalLink href="https://www.recursive.design/">Recursive</ExternalLink> font.</p> <p>I use the <ExternalLink href="https://www.recursive.design/">Recursive</ExternalLink> font.</p>
<p>The content and layout/theming is hand-made by me. You can check out <ExternalLink href="https://git.wilw.dev/wilw/wilw.dev">the source code</ExternalLink> if you're interested.</p> <p><ExternalLink href="https://www.happyhues.co">Happy Hues</ExternalLink> provides the inspiration for some of the colour themes.</p>
<p>The content and layout is hand-made by me. You can check out <ExternalLink href="https://git.wilw.dev/wilw/wilw.dev">the source code</ExternalLink> if you're interested.</p>
<h2><Emoji e='' /> Other remarks</h2> <h2><Emoji e='' /> Other remarks</h2>
<p>The content on this site represents my own thoughts and opinions, and does not necessarily reflect the opinions of the people and companies I work with and for.</p> <p>The content on this site represents my own thoughts and opinions, and does not necessarily reflect the opinions of the people and companies I work with and for.</p>

6
src/store.js Normal file
View File

@ -0,0 +1,6 @@
import create from 'zustand'
export default create(set => ({
theme: 'light',
setTheme: theme => set({ theme }),
}));

View File

@ -8,7 +8,8 @@ import { config } from "@fortawesome/fontawesome-svg-core"
import "@fortawesome/fontawesome-svg-core/styles.css" import "@fortawesome/fontawesome-svg-core/styles.css"
import Layout from '../components/Layout/Layout.js'; import Layout from '../components/Layout/Layout.js';
import Emoji from '../components/Emoji'; import Tag from '../components/Tag';
import Alert from '../components/Alert';
import ExternalLink from '../components/ExternalLink'; import ExternalLink from '../components/ExternalLink';
config.autoAddCss = false; config.autoAddCss = false;
@ -26,35 +27,30 @@ export default class BlogPost extends React.Component {
<h1>{post.frontmatter.title}</h1> <h1>{post.frontmatter.title}</h1>
<h4>{moment(post.frontmatter.date).format('D MMMM YYYY')} <i><small>({moment(post.frontmatter.date).fromNow()})</small></i> <h4>{moment(post.frontmatter.date).format('D MMMM YYYY')} <i><small>({moment(post.frontmatter.date).fromNow()})</small></i>
{post.frontmatter.tags && post.frontmatter.tags.map(tag => {post.frontmatter.tags && post.frontmatter.tags.map(tag => <Tag tag={tag} key={tag} />)}
<Link style={{marginLeft: 10}} to={`/tags/${tag}`}><Emoji e="🏷️" /> #{tag}</Link>
)}
</h4> </h4>
{post.frontmatter.tags && post.frontmatter.tags.indexOf('100daystooffload') > -1 && {post.frontmatter.tags && post.frontmatter.tags.indexOf('100daystooffload') > -1 &&
<div style={{background: 'beige', padding: 4, borderRadius: 3, margin: '20px 0px'}}> <Alert colour='green' emoji='💯' title='100 Days to Offload'>
<h3><Emoji e='💯' /> 100 Days to Offload</h3>
<p><b>This article is one of a series of posts in the <ExternalLink href="https://100daystooffload.com">100 Days to Offload challenge</ExternalLink></b>. <p><b>This article is one of a series of posts in the <ExternalLink href="https://100daystooffload.com">100 Days to Offload challenge</ExternalLink></b>.
The challenge focuses on writing <strong>frequency</strong> rather than <strong>quality</strong>, and so posts may not always be fully planned out. They are simply a way to offload thoughts.</p> The challenge focuses on writing <strong>frequency</strong> rather than <strong>quality</strong>, and so posts may not always be fully planned out. They are simply a way to offload thoughts.</p>
<p><Link to='/tags/100daystooffload'>View other articles in this series</Link></p> <p><Link to='/tags/100daystooffload'>View other articles in this series</Link></p>
</div> </Alert>
} }
{post.frontmatter.tags && post.frontmatter.tags.indexOf('book') > -1 && {post.frontmatter.tags && post.frontmatter.tags.indexOf('book') > -1 &&
<div style={{background: 'linen', padding: 4, borderRadius: 3, margin: '20px 0px'}}> <Alert colour='orange' emoji='📚' title='This article is about a book'>
<h3><Emoji e='📚' /> This article is about a book</h3>
<p>A quick warning: I always try to avoid giving away spoilers but be careful if you're worried about finding out too much.</p> <p>A quick warning: I always try to avoid giving away spoilers but be careful if you're worried about finding out too much.</p>
</div> </Alert>
} }
{moment(post.frontmatter.date).isBefore(moment().subtract(12, 'months')) && {moment(post.frontmatter.date).isBefore(moment().subtract(12, 'months')) &&
<div style={{background: 'lightpink', padding: 4, borderRadius: 3, margin: '20px 0px'}}> <Alert colour='pink' emoji='🕰️' title='This is an old post'>
<h3><Emoji e='🕰️' /> This is an old post</h3>
<p>Please note that this article was posted quite a while ago and may now be out-of-date or inaccurate.</p> <p>Please note that this article was posted quite a while ago and may now be out-of-date or inaccurate.</p>
</div> </Alert>
} }
<article className='post' style={{marginTop: 10}}> <article>
<div dangerouslySetInnerHTML={{ __html: post.html }} /> <div dangerouslySetInnerHTML={{ __html: post.html }} />
</article> </article>
@ -66,17 +62,15 @@ export default class BlogPost extends React.Component {
} }
{post.frontmatter.tags && post.frontmatter.tags.indexOf('book') > -1 && {post.frontmatter.tags && post.frontmatter.tags.indexOf('book') > -1 &&
<div style={{background: 'linen', padding: 4, borderRadius: 3, margin: '20px 0px'}}> <Alert colour='orange' emoji='📚' title='I try and read a wide variety of books'>
<h3><Emoji e='📚' /> I try and read a wide variety of books</h3>
<p>If you're interested in seeing what else I read you can <a href="https://www.goodreads.com/user/show/22390023-will" target="_blank" rel="noopener noreferrer">check out my Goodreads profile</a>.</p> <p>If you're interested in seeing what else I read you can <a href="https://www.goodreads.com/user/show/22390023-will" target="_blank" rel="noopener noreferrer">check out my Goodreads profile</a>.</p>
</div> </Alert>
} }
<div style={{background: 'honeydew', padding: 4, borderRadius: 3, marginTop: 10}}> <Alert colour='blue' emoji='📲' title='Enjoyed this article? Subscribe to updates!'>
<h3><Emoji e='📲' /> Enjoyed this article? Subscribe to updates!</h3>
<p>If you would like to read more posts like this, then you can subscribe via RSS.</p> <p>If you would like to read more posts like this, then you can subscribe via RSS.</p>
<p><FontAwesomeIcon icon={faRss} /> <Link to='/feeds'>Subscribe to an RSS feed</Link></p> <p><FontAwesomeIcon icon={faRss} /> <Link to='/feeds'>Subscribe to an RSS feed</Link></p>
</div> </Alert>
</Layout> </Layout>
); );
} }

View File

@ -7,6 +7,8 @@ import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'
import Layout from '../components/Layout/Layout.js'; import Layout from '../components/Layout/Layout.js';
import Emoji from '../components/Emoji'; import Emoji from '../components/Emoji';
import Alert from '../components/Alert';
import Tag from '../components/Tag';
export default class Note extends React.Component { export default class Note extends React.Component {
@ -21,19 +23,17 @@ export default class Note extends React.Component {
<h1>{note.frontmatter.title}</h1> <h1>{note.frontmatter.title}</h1>
<h4>Last updated {moment(note.frontmatter.date).format('D MMMM YYYY')} <i><small>({moment(note.frontmatter.date).fromNow()})</small></i> <h4>Last updated {moment(note.frontmatter.date).format('D MMMM YYYY')} <i><small>({moment(note.frontmatter.date).fromNow()})</small></i>
{note.frontmatter.tags && note.frontmatter.tags.map(tag => {note.frontmatter.tags && note.frontmatter.tags.map(tag => <Tag key={tag} tag={tag} />)}
<Link style={{marginLeft: 10}} to={`/tags/${tag}`}><Emoji e="🏷️" /> #{tag}</Link>
)}
</h4> </h4>
{moment(note.frontmatter.date).isBefore(moment().subtract(12, 'months')) && {moment(note.frontmatter.date).isBefore(moment().subtract(12, 'months')) &&
<div style={{background: 'lightpink', padding: 4, borderRadius: 3, margin: '20px 0px'}}> <Alert colour='pink' emoji='🕰️' title='This is an old note'>
<h3><Emoji e='🕰️' /> This is an old note</h3> <h3><Emoji e='🕰️' /> This is an old note</h3>
<p>Please note that this article was last updated quite a while ago and may now be out-of-date or inaccurate.</p> <p>Please note that this article was last updated quite a while ago and may now be out-of-date or inaccurate.</p>
</div> </Alert>
} }
<article className='post' style={{marginTop: 10}}> <article>
<div dangerouslySetInnerHTML={{ __html: note.html }} /> <div dangerouslySetInnerHTML={{ __html: note.html }} />
</article> </article>
</Layout> </Layout>

View File

@ -12378,6 +12378,11 @@ yurnalist@^2.1.0:
read "^1.0.7" read "^1.0.7"
strip-ansi "^5.2.0" strip-ansi "^5.2.0"
zustand@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.3.1.tgz#de5c4b51112b84e0f819d8b3f336fbfbc087d758"
integrity sha512-o0rgrBsi29nCkPHdhtkAHisCIlmRUoXOV+1AmDMeCgkGG0i5edFSpGU0KiZYBvFmBYycnck4Z07JsLYDjSET9g==
zwitch@^1.0.0: zwitch@^1.0.0:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"