Compare commits

..

No commits in common. "fdc412bf15cc33fb339b1040bbf2c138dec2346b" and "0d2074f1d43d8a183b5ed73825c48b7adbdd334f" have entirely different histories.

22 changed files with 318 additions and 512 deletions

View File

@ -17,8 +17,7 @@
"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",

View File

@ -1,50 +0,0 @@
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,20 +2,16 @@ 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 }) => (
<Alert <div style={{marginBottom: '1.45rem', background: 'floralwhite', padding: 3, borderRadius: 3}}>
colour='white' <h3><Emoji e='📝' /> <Link to={post.fields.slug}>{post.frontmatter.title}</Link> <small>{moment(post.frontmatter.date).format('D MMMM YYYY')}</small></h3>
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 => <Tag key={tag} tag={tag} />)} {post.frontmatter.tags && post.frontmatter.tags.map(tag =>
<Link style={{marginRight: 10}} to={`/tags/${tag}`}><Emoji e="🏷️" /> #{tag}</Link>
)}
</div> </div>
</Alert>
) )
export default BlogPostHeader; export default BlogPostHeader;

View File

@ -3,8 +3,9 @@ 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} <FontAwesomeIcon icon={faExternalLinkAlt} /></a> <a {...props} href={props.href} target="_blank" rel="noopener noreferrer">{props.children} {isExternal && <FontAwesomeIcon icon={faExternalLinkAlt} />}</a>
); );
} }

View File

@ -2,10 +2,8 @@ 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.jpeg'; import Will from '../images/will.jpg';
const StyledHeader = styled.header` const StyledHeader = styled.header`
text-align: left; text-align: left;
@ -55,23 +53,8 @@ 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>
@ -88,14 +71,8 @@ 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,6 +12,19 @@ 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;
@ -71,14 +84,6 @@ 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,47 +1,11 @@
import React, { useEffect } from 'react' import React from 'react'
import Helmet from 'react-helmet' import Helmet from 'react-helmet'
import styled, { createGlobalStyle } from 'styled-components' import styled 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;
@ -49,23 +13,8 @@ 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>
@ -73,7 +22,6 @@ const TemplateWrapper = ({ children }) => {
<StyledMain>{children}</StyledMain> <StyledMain>{children}</StyledMain>
<Footer /> <Footer />
</div> </div>
); )
}
export default TemplateWrapper export default TemplateWrapper

View File

@ -2,15 +2,16 @@ 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'
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>}> <div style={{marginBottom: '1.45rem', background: 'aliceblue', padding: 3, borderRadius: 3}}>
<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 => <Tag key={tag} tag={tag} />)} {post.frontmatter.tags && post.frontmatter.tags.map(tag =>
</Alert> <Link style={{marginRight: 10}} to={`/tags/${tag}`}><Emoji e="🏷️" /> #{tag}</Link>
); )}
</div>
)
export default NoteHeader; export default NoteHeader;

View File

@ -1,15 +0,0 @@
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;

Binary file not shown.

Before

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>Not found</h2> <h2 style={{fontFamily:'Courier, Monospace'}}>~/errors/404</h2>
<p>The requested file could not be found.</p> <p>The requested file was not found.</p>
</Layout> </Layout>
) )

View File

@ -1,18 +1,12 @@
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() {
@ -21,15 +15,12 @@ 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>
<StyledHeader> <h2>Blog posts
<span>Blog posts</span> <small style={{float: 'right'}}><FontAwesomeIcon icon={faRss} /> <Link to='/feeds'>RSS feeds</Link></small>
<small> </h2>
<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 key={node.fields.slug} post={node} />; return <BlogPostHeader post={node} />;
} else return null; } else return null;
})} })}

View File

@ -7,7 +7,6 @@ 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;
@ -65,20 +64,25 @@ 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>
<Alert colour='orange' title='Do you need an RSS reader?'> <div style={{background: 'linen', padding: 10, borderRadius: 3, margin: '20px 0px'}}>
<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>
</Alert> </div>
<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 =>
<Alert key={feed.url} colour='green' title={<span><FontAwesomeIcon icon={faRssSquare} /> {feed.name}</span>}> <div style={{background: 'whitesmoke', padding: 10, borderRadius: 3, margin: '20px 0px'}}>
<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>
</Alert> </div>
)} )}
</div>
</Layout> </Layout>
); );
} }

View File

@ -6,7 +6,6 @@ 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';
@ -18,12 +17,11 @@ 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>
<Alert colour='green' <div style={{background: 'beige', padding: '2px', borderRadius: 3, margin: '40px 0px'}}>
title={<span><Emoji e='💯' /> 100 Days to Offload</span>} <h3><Emoji e='💯' /> 100 Days to Offload</h3>
>
<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>
</Alert> </div>
<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>
@ -48,7 +46,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 key={node.fields.slug} post={node} /> : null)} .map(({ node }) => node.fields.slug.indexOf('blog') > -1 ? <ArticleHeader 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 key={node.fields.slug} post={node} />; return <NoteHeader post={node} />;
} else return null; } else return null;
})} })}

View File

@ -1,16 +1,16 @@
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 styled from 'styled-components';
import Layout from '../components/Layout/Layout.js'; 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 projects = [ const ProjectsPage = () => {
const projects = [
{ {
name: 'Treadl', name: 'Treadl',
logo: treadlIcon, logo: treadlIcon,
@ -41,9 +41,9 @@ const projects = [
{ name: 'Web', icon: faLaptop }, { name: 'Android', icon: faAndroid, url: 'https://play.google.com/store/apps/details?id=app.trilo' }, { name: 'iOS', icon: faApple, url: 'https://itunes.apple.com/gb/app/trilo/id1460738681' } { name: 'Web', icon: faLaptop }, { name: 'Android', icon: faAndroid, url: 'https://play.google.com/store/apps/details?id=app.trilo' }, { name: 'iOS', icon: faApple, url: 'https://itunes.apple.com/gb/app/trilo/id1460738681' }
] ]
},*/ },*/
]; ];
const otherProjects = [ const otherProjects = [
{ {
name: 'Gower Tides', name: 'Gower Tides',
description: 'An Android app for displaying daily tidal patterns, along with weather and surf conditions, for the sea around the Gower Peninsula in South Wales. The app was aimed at surfers and other sea-users, and was available for several years on Google Play, but is now discontinued.', description: 'An Android app for displaying daily tidal patterns, along with weather and surf conditions, for the sea around the Gower Peninsula in South Wales. The app was aimed at surfers and other sea-users, and was available for several years on Google Play, but is now discontinued.',
@ -77,53 +77,30 @@ const otherProjects = [
description: 'A project that supported simultaneous audio output (e.g. for music) across devices on a network. The project hooked into PulseAudio and made uss of RTP to broadcast sound to low-powered devices (such as a Raspberry Pi). Whilst I used to use this fairly frequently, the project is no longer being maintained.', description: 'A project that supported simultaneous audio output (e.g. for music) across devices on a network. The project hooked into PulseAudio and made uss of RTP to broadcast sound to low-powered devices (such as a Raspberry Pi). Whilst I used to use this fairly frequently, the project is no longer being maintained.',
source: 'https://github.com/willwebberley/CasaStream' source: 'https://github.com/willwebberley/CasaStream'
} }
]; ];
const StyledProjects = styled.div` return (
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>
<StyledProjects> <div style={{display:'grid', gridColumnGap: 20, gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))'}}>
<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) =>
<StyledProject key={i}> <div key={i} style={{display: 'flex', flexDirection: 'row', marginBottom: 30, borderBottom: '1px solid rgb(250,250,250)'}}>
<StyledProjectImage> <div style={{paddingRight: 30, paddingTop: 30}}>
<img alt={p.name} src={p.logo} /> <img alt={p.name} src={p.logo} style={{width: 100}} />
</StyledProjectImage> </div>
<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) =>
<StyledPlatform key={j} href={a.url || p.url} target="_blank" rel="noopener noreferrer">{a.icon && <FontAwesomeIcon icon={a.icon} />} {a.name}</StyledPlatform> <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>
)}</p> )}</p>
</div> </div>
</StyledProject> </div>
)} )}
</div> </div>
<div> <div>
@ -136,8 +113,9 @@ const ProjectsPage = () => (
</div> </div>
)} )}
</div> </div>
</StyledProjects> </div>
</Layout> </Layout>
); )
}
export default ProjectsPage export default ProjectsPage

View File

@ -1,10 +1,10 @@
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 outputs = [ const ProjectsPage = () => {
const outputs = [
{ {
name: 'Retweeting: A Study of Message-Forwarding in Twitter', name: 'Retweeting: A Study of Message-Forwarding in Twitter',
date: '2011', date: '2011',
@ -102,39 +102,17 @@ const outputs = [
journal: 'IEEE Transactions on Computational Social Systems', journal: 'IEEE Transactions on Computational Social Systems',
url: 'https://ieeexplore.ieee.org/iel7/6570650/6780646/08232468.pdf' url: 'https://ieeexplore.ieee.org/iel7/6570650/6780646/08232468.pdf'
} }
]; ];
const StyledResearch = styled.div` return (
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>
<StyledResearch> <div style={{display:'grid', gridColumnGap: 20, gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))'}}>
<div> <div>
<h3>Projects</h3> <h3 style={{marginTop: 0}}>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>
@ -145,19 +123,20 @@ const ProjectsPage = () => (
</div> </div>
<div> <div>
<h3>Publications</h3> <h3 style={{marginTop: 0}}>Publications</h3>
{outputs.reverse().map((o, i) => {outputs.reverse().map((o, i) =>
<StyledPublication key={i}> <div key={i} style={{display: 'flex', flexDirection: 'row', marginBottom: 10, borderBottom: '1px solid rgb(250,250,250)'}}>
<div> <div>
<PublicationTitle><a href={o.url} target='_blank' rel='noopener noreferrer'>{o.name}</a></PublicationTitle> <h4 style={{marginTop:0}}><a href={o.url} target='_blank' rel='noopener noreferrer'>{o.name}</a></h4>
<PublicationAuthors>{o.authors}</PublicationAuthors> <p style={{marginBottom:0, fontSize:15}}>{o.authors}</p>
<PublicationAttribution>{o.journal}, {o.date}</PublicationAttribution> <p style={{fontSize: 13}}>{o.journal}, {o.date}</p>
</div>
</div> </div>
</StyledPublication>
)} )}
</div> </div>
</StyledResearch> </div>
</Layout> </Layout>
); )
}
export default ProjectsPage export default ProjectsPage

View File

@ -29,8 +29,7 @@ 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><ExternalLink href="https://www.happyhues.co">Happy Hues</ExternalLink> provides the inspiration for some of the colour themes.</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>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>

View File

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

View File

@ -8,8 +8,7 @@ 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 Tag from '../components/Tag'; import Emoji from '../components/Emoji';
import Alert from '../components/Alert';
import ExternalLink from '../components/ExternalLink'; import ExternalLink from '../components/ExternalLink';
config.autoAddCss = false; config.autoAddCss = false;
@ -27,30 +26,35 @@ 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 => <Tag tag={tag} key={tag} />)} {post.frontmatter.tags && post.frontmatter.tags.map(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 &&
<Alert colour='green' emoji='💯' title='100 Days to Offload'> <div style={{background: 'beige', padding: 4, borderRadius: 3, margin: '20px 0px'}}>
<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>
</Alert> </div>
} }
{post.frontmatter.tags && post.frontmatter.tags.indexOf('book') > -1 && {post.frontmatter.tags && post.frontmatter.tags.indexOf('book') > -1 &&
<Alert colour='orange' emoji='📚' title='This article is about a book'> <div style={{background: 'linen', padding: 4, borderRadius: 3, margin: '20px 0px'}}>
<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>
</Alert> </div>
} }
{moment(post.frontmatter.date).isBefore(moment().subtract(12, 'months')) && {moment(post.frontmatter.date).isBefore(moment().subtract(12, 'months')) &&
<Alert colour='pink' emoji='🕰️' title='This is an old post'> <div style={{background: 'lightpink', padding: 4, borderRadius: 3, margin: '20px 0px'}}>
<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>
</Alert> </div>
} }
<article> <article className='post' style={{marginTop: 10}}>
<div dangerouslySetInnerHTML={{ __html: post.html }} /> <div dangerouslySetInnerHTML={{ __html: post.html }} />
</article> </article>
@ -62,15 +66,17 @@ 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 &&
<Alert colour='orange' emoji='📚' title='I try and read a wide variety of books'> <div style={{background: 'linen', padding: 4, borderRadius: 3, margin: '20px 0px'}}>
<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>
</Alert> </div>
} }
<Alert colour='blue' emoji='📲' title='Enjoyed this article? Subscribe to updates!'> <div style={{background: 'honeydew', padding: 4, borderRadius: 3, marginTop: 10}}>
<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>
</Alert> </div>
</Layout> </Layout>
); );
} }

View File

@ -7,8 +7,6 @@ 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 {
@ -23,17 +21,19 @@ 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 => <Tag key={tag} tag={tag} />)} {note.frontmatter.tags && note.frontmatter.tags.map(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')) &&
<Alert colour='pink' emoji='🕰️' title='This is an old note'> <div style={{background: 'lightpink', padding: 4, borderRadius: 3, margin: '20px 0px'}}>
<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>
</Alert> </div>
} }
<article> <article className='post' style={{marginTop: 10}}>
<div dangerouslySetInnerHTML={{ __html: note.html }} /> <div dangerouslySetInnerHTML={{ __html: note.html }} />
</article> </article>
</Layout> </Layout>

View File

@ -12378,11 +12378,6 @@ 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"