refact: clean up (#12)

refact: clean up
This commit is contained in:
Jean-Philippe Sirois
2019-07-27 18:05:48 -04:00
committed by GitHub
24 changed files with 253 additions and 317 deletions
+6 -9
View File
@@ -1,12 +1,9 @@
import React from "react" import React, { PropsWithChildren } from "react"
import * as SC from "./styles" import * as SC from "./styles"
interface ContainerProps { export function Container({
children: React.ReactNode children,
...restProps
}: PropsWithChildren<{}>): JSX.Element {
return <SC.ContainerWrapper {...restProps}>{children}</SC.ContainerWrapper>
} }
const Container = ({ children, ...restProps }: ContainerProps): JSX.Element => (
<SC.ContainerWrapper {...restProps}>{children}</SC.ContainerWrapper>
)
export default Container
+9 -17
View File
@@ -8,13 +8,11 @@
import React, { PropsWithChildren } from "react" import React, { PropsWithChildren } from "react"
import { useStaticQuery, graphql } from "gatsby" import { useStaticQuery, graphql } from "gatsby"
import Container from "../Container" import { DocsSidebar } from "../DocsSidebar"
import DocsSidebar from "../DocsSidebar" import { Footer } from "../Footer"
import { Main, MainContent } from "./styles" import * as SC from "./styles"
import Scrollbar from "react-perfect-scrollbar"
import "react-perfect-scrollbar/dist/css/styles.css"
const DocsLayout = ({ children }: PropsWithChildren<{}>) => { export function DocsLayout({ children }: PropsWithChildren<{}>) {
const data = useStaticQuery(graphql` const data = useStaticQuery(graphql`
query { query {
site { site {
@@ -27,20 +25,14 @@ const DocsLayout = ({ children }: PropsWithChildren<{}>) => {
return ( return (
<div> <div>
<Main> <SC.Main>
<DocsSidebar /> <DocsSidebar />
<MainContent> <SC.MainContent>
<h1>{data.site.siteMetadata.title}</h1> <h1>{data.site.siteMetadata.title}</h1>
{children} {children}
</MainContent> </SC.MainContent>
</Main> </SC.Main>
<footer> <Footer />
© {new Date().getFullYear()}, Built with
{` `}
<a href="https://www.gatsbyjs.org">Gatsby</a>
</footer>
</div> </div>
) )
} }
export default DocsLayout
-41
View File
@@ -1,41 +0,0 @@
// TODO: refactor this into styled components
html {
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/**
* If you already use line highlighting
*/
/* Adjust the position of the line numbers */
.gatsby-highlight pre[class*="language-"].line-numbers {
padding-left: 2.8em;
}
/**
* If you only want to use line numbering
*/
.gatsby-highlight {
background-color: #fdf6e3;
border-radius: 0.3em;
margin: 0.5em 0;
padding: 1em;
overflow: auto;
}
.gatsby-highlight pre[class*="language-"].line-numbers {
padding: 0;
padding-left: 2.8em;
overflow: initial;
}
+10 -92
View File
@@ -1,99 +1,25 @@
import * as R from "ramda"
import React, { useState } from "react" import React, { useState } from "react"
import Tree from "react-treeview" import Tree from "react-treeview"
import { useStaticQuery, graphql } from "gatsby" import { useStaticQuery, graphql } from "gatsby"
import useBuildTree from "./useBuildTree"
import * as SC from "./styles" import * as SC from "./styles"
interface IFile { export interface IFile {
title: string title: string
type: "file" type: "file"
path: string path: string
} }
interface IFolder { export interface IFolder {
title: string title: string
type: "folder" type: "folder"
path: string path: string
children: IFileOrFolder[] children: IFileOrFolder[]
} }
type IFileOrFolder = IFile | IFolder export type IFileOrFolder = IFile | IFolder
const traverse = ( export interface IFileQuery {
[head, ...tail]: string[],
basePath = "/docs"
): IFileOrFolder => {
const path = basePath + "/" + head
const isFile = !tail.length
if (isFile) {
// probably not more than one dot
const [name] = head.split(".")
return {
title: name,
type: "file",
path,
}
}
return {
title: head,
type: "folder",
path,
children: [traverse(tail, path)],
}
}
const generateFolder = ({
title,
path,
targets,
}: {
title: IFolder["title"]
path: IFile["path"]
targets: IFolder[]
}): IFolder => {
const children = join(R.chain(target => target.children, targets))
return {
title,
type: "folder",
path,
children,
}
}
const generateFile = ({
title,
path,
}: {
title: IFile["title"]
path: IFile["path"]
}): IFile => {
return {
title,
path,
type: "file",
}
}
const join = ([head, ...tail]: IFileOrFolder[]): IFileOrFolder[] => {
if (!head) return []
const [similarFs, remaining] = R.partition(
obj => obj.title === head.title && obj.type === head.type,
tail
)
const targets = [head, ...similarFs]
const { title, path } = head
const current =
head.type === "folder"
? generateFolder({ title, path, targets: targets as IFolder[] })
: generateFile({ title, path })
return [current, ...join(remaining)]
}
interface IFileQuery {
node: { node: {
relativePath: string relativePath: string
childMarkdownRemark: { childMarkdownRemark: {
@@ -105,7 +31,7 @@ interface IFileQuery {
} }
} }
interface IAllDocsQuery { export interface IAllDocsQuery {
allFile: { allFile: {
edges: IFileQuery[] edges: IFileQuery[]
} }
@@ -129,7 +55,7 @@ const ALL_DOCS = graphql`
} }
` `
const plantTree = (item: IFileOrFolder) => { function plantTree(item: IFileOrFolder) {
if (item.type === "file") { if (item.type === "file") {
return ( return (
<SC.PageLink to={item.path} activeClassName="active"> <SC.PageLink to={item.path} activeClassName="active">
@@ -158,21 +84,13 @@ function Folder({ item }: { item: IFolder }) {
) )
} }
const DocsSidebar = () => { export function DocsSidebar() {
const docs = useStaticQuery<IAllDocsQuery>(ALL_DOCS) const docs = useStaticQuery<IAllDocsQuery>(ALL_DOCS)
const tree = useBuildTree(docs)
const objects = docs.allFile.edges.map(({ node: file }: IFileQuery) =>
traverse(file.relativePath.split("/"))
)
const results = join(objects)
console.log(results)
return ( return (
<SC.DocsSidebarWrapper> <SC.DocsSidebarWrapper>
{results.map(node => plantTree(node))} {tree.map(node => plantTree(node))}
</SC.DocsSidebarWrapper> </SC.DocsSidebarWrapper>
) )
} }
export default DocsSidebar
@@ -0,0 +1,90 @@
import * as R from "ramda"
import {
IFileOrFolder,
IFile,
IFolder,
IAllDocsQuery,
IFileQuery,
} from "./index"
function traverse(
[head, ...tail]: string[],
basePath = "/docs"
): IFileOrFolder {
const path = basePath + "/" + head
const isFile = !tail.length
if (isFile) {
// probably not more than one dot
const [name] = head.split(".")
return {
title: name,
type: "file",
path,
}
}
return {
title: head,
type: "folder",
path,
children: [traverse(tail, path)],
}
}
function generateFolder({
title,
path,
targets,
}: {
title: IFolder["title"]
path: IFile["path"]
targets: IFolder[]
}): IFolder {
const children = join(R.chain(target => target.children, targets))
return {
title,
type: "folder",
path,
children,
}
}
function generateFile({
title,
path,
}: {
title: IFile["title"]
path: IFile["path"]
}): IFile {
return {
title,
type: "file",
path,
}
}
function join([head, ...tail]: IFileOrFolder[]): IFileOrFolder[] {
if (!head) return []
const [similarFs, remaining] = R.partition(
obj => obj.title === head.title && obj.type === head.type,
tail
)
const targets = [head, ...similarFs]
const { title, path } = head
const current =
head.type === "folder"
? generateFolder({ title, path, targets: targets as IFolder[] })
: generateFile({ title, path })
return [current, ...join(remaining)]
}
export default function useBuildTree(docs: IAllDocsQuery) {
const objects = docs.allFile.edges.map(({ node: file }: IFileQuery) =>
traverse(file.relativePath.split("/"))
)
return join(objects)
}
+5 -7
View File
@@ -1,20 +1,18 @@
import React from "react" import React, { PropsWithChildren } from "react"
import { SidebarTitle } from "./style" import * as SC from "./styles"
export interface DocsSidebarSectionProps { export interface DocsSidebarSectionProps {
readonly title: string readonly title: string
} }
const DocsSidebarSection = ({ export function DocsSidebarSection({
title, title,
children, children,
}: React.PropsWithChildren<DocsSidebarSectionProps>) => { }: PropsWithChildren<DocsSidebarSectionProps>) {
return ( return (
<div> <div>
<SidebarTitle>{title}</SidebarTitle> <SC.SidebarTitle>{title}</SC.SidebarTitle>
{children} {children}
</div> </div>
) )
} }
export default DocsSidebarSection
@@ -1,5 +1,5 @@
import styled from "styled-components"; import styled from "styled-components"
export const SidebarTitle = styled.h3` export const SidebarTitle = styled.h3`
text-transform: uppercase; text-transform: uppercase;
`; `
+15
View File
@@ -0,0 +1,15 @@
import React from "react"
import * as SC from "./styles"
import { Container } from "../Container"
export function Footer() {
return (
<SC.FooterWrapper>
<Container>
© {new Date().getFullYear()}, Built with
{` `}
<a href="https://www.gatsbyjs.org">Gatsby</a>
</Container>
</SC.FooterWrapper>
)
}
+4
View File
@@ -0,0 +1,4 @@
import { Link } from "gatsby"
import styled from "styled-components"
export const FooterWrapper = styled.footer``
+10 -18
View File
@@ -8,15 +8,15 @@
import React, { PropsWithChildren } from "react" import React, { PropsWithChildren } from "react"
import { useStaticQuery, graphql } from "gatsby" import { useStaticQuery, graphql } from "gatsby"
import Container from "../Container" import { Container } from "../Container"
import Navbar from "../Navbar" import { Footer } from "../Footer"
import Sidebar from "../Sidebar" import { Sidebar } from "../Sidebar"
import "./layout.scss" import "./layout.scss"
import { Main, MainContent } from "./styles" import * as SC from "./styles"
import Scrollbar from "react-perfect-scrollbar" import Scrollbar from "react-perfect-scrollbar"
import "react-perfect-scrollbar/dist/css/styles.css" import "react-perfect-scrollbar/dist/css/styles.css"
const Layout = ({ children }: PropsWithChildren<{}>) => { export function Layout({ children }: PropsWithChildren<{}>) {
const data = useStaticQuery(graphql` const data = useStaticQuery(graphql`
query { query {
site { site {
@@ -30,23 +30,15 @@ const Layout = ({ children }: PropsWithChildren<{}>) => {
return ( return (
<Scrollbar> <Scrollbar>
<Container> <Container>
<Main> <SC.Main>
<Sidebar /> <Sidebar />
<MainContent> <SC.MainContent>
<h1>{data.site.siteMetadata.title}</h1> <h1>{data.site.siteMetadata.title}</h1>
{children} {children}
</MainContent> </SC.MainContent>
</Main> </SC.Main>
</Container> </Container>
<footer> <Footer />
<Container>
© {new Date().getFullYear()}, Built with
{` `}
<a href="https://www.gatsbyjs.org">Gatsby</a>
</Container>
</footer>
</Scrollbar> </Scrollbar>
) )
} }
export default Layout
+1
View File
@@ -6,6 +6,7 @@ html {
-ms-text-size-adjust: 100%; -ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
} }
body { body {
margin: 0; margin: 0;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-43
View File
@@ -1,43 +0,0 @@
import { Link } from "gatsby";
import * as React from "react";
import icon from "./icon.png";
import { HeaderWrapper } from "./styles";
// @todo maybe find alternative type for data
const Navbar = ({ siteTitle }: any) => (
<HeaderWrapper>
<div
style={{
margin: `0 auto`,
maxWidth: 960,
padding: `1.45rem 1.0875rem`,
}}
>
<div style={{ display: "flex", flexDirection: "row", alignItems: "center" }}>
{/* <img src={icon} style={{ width: "70px" }} /> */}
<h1 style={{ margin: 0 }}>
<Link
to="/"
style={{
color: `white`,
textDecoration: `none`,
}}
>
{siteTitle}
</Link>
</h1>
</div>
</div>
</HeaderWrapper>
)
Navbar.propTypes = {
siteTitle: String
}
Navbar.defaultProps = {
siteTitle: ``,
}
export default Navbar;
-6
View File
@@ -1,6 +0,0 @@
import styled from "styled-components";
export const HeaderWrapper = styled.header`
background: #ba1a2e;
marginBottom: 1.45rem;
`;
+23 -9
View File
@@ -3,13 +3,20 @@ import Helmet from "react-helmet"
import { useStaticQuery, graphql } from "gatsby" import { useStaticQuery, graphql } from "gatsby"
interface SEOProps { interface SEOProps {
readonly description: string; readonly description?: string
readonly lang: string; readonly lang?: string
readonly meta: object[]; readonly meta?: any[]
readonly title: string; readonly keywords?: string[]
readonly title: string
} }
const SEO = ({ description = "", lang = "en", meta = [], title }: SEOProps) => { export function SEO({
description,
lang = "en",
meta = [],
keywords = [],
title,
}: SEOProps) {
const { site } = useStaticQuery( const { site } = useStaticQuery(
graphql` graphql`
query { query {
@@ -66,9 +73,16 @@ const SEO = ({ description = "", lang = "en", meta = [], title }: SEOProps) => {
name: `twitter:description`, name: `twitter:description`,
content: metaDescription, content: metaDescription,
}, },
]} ]
.concat(
keywords.length > 0
? {
name: `keywords`,
content: keywords.join(`, `),
}
: []
)
.concat(meta)}
/> />
) )
}; }
export default SEO
+3 -5
View File
@@ -1,9 +1,9 @@
import React from "react" import React, { PropsWithChildren } from "react"
import logo from "../../images/tph-logo.png" import logo from "../../images/tph-logo.png"
import * as SC from "./styles" import * as SC from "./styles"
import ArrowRight from "../../icons/arrow-right.svg" import ArrowRight from "../../icons/arrow-right.svg"
function MenuItem({ children, to }: { children: React.ReactNode; to: string }) { function MenuItem({ children, to }: PropsWithChildren<{ to: string }>) {
return ( return (
<SC.MenuItem to={to}> <SC.MenuItem to={to}>
{children} <ArrowRight /> {children} <ArrowRight />
@@ -11,7 +11,7 @@ function MenuItem({ children, to }: { children: React.ReactNode; to: string }) {
) )
} }
const Sidebar = () => { export function Sidebar() {
return ( return (
<SC.SidebarWrapper> <SC.SidebarWrapper>
<SC.Logo src={logo} /> <SC.Logo src={logo} />
@@ -27,5 +27,3 @@ const Sidebar = () => {
</SC.SidebarWrapper> </SC.SidebarWrapper>
) )
} }
export default Sidebar
+12 -12
View File
@@ -31,8 +31,8 @@ called `getMembers` that retrieves all the members in a discord server. When we
function we see the following result. function we see the following result.
```js ```js
const members = getMembers("The Programmers Hangout"); const members = getMembers("The Programmers Hangout")
console.log(members); // Promise {<pending>} console.log(members) // Promise {<pending>}
``` ```
Normally, we would have expected to see an array of all the members but it takes time to Normally, we would have expected to see an array of all the members but it takes time to
@@ -44,8 +44,8 @@ to access the actual members like so.
```js ```js
getMembers("The Programmers Hangout").then(members => { getMembers("The Programmers Hangout").then(members => {
console.log(members); // (32k) [{...}, {...}, {...}] console.log(members) // (32k) [{...}, {...}, {...}]
}); })
``` ```
This way we are able to make sure that we only try to `console.log` when the `getMembers` function has resolved and ready to be used. This way we are able to make sure that we only try to `console.log` when the `getMembers` function has resolved and ready to be used.
@@ -63,10 +63,10 @@ You may have tried doing something like this before.
```js ```js
// Incorrect code, don't copy! // Incorrect code, don't copy!
let results; let results
getWeather("Los Angeles").then(weather => { getWeather("Los Angeles").then(weather => {
results = weather; results = weather
}); })
console.log(results) // undefined console.log(results) // undefined
``` ```
@@ -81,8 +81,8 @@ In order to fix this problem we need to move the `console.log` inside the `.then
```js ```js
getWeather("Los Angeles").then(weather => { getWeather("Los Angeles").then(weather => {
console.log(weather); // Sunny, probably console.log(weather) // Sunny, probably
}); })
``` ```
## Real World Example ## Real World Example
@@ -94,12 +94,12 @@ You can try it in your browser if you want to test it out.
function getCharacters() { function getCharacters() {
return fetch(`https://rickandmortyapi.com/api/character`) return fetch(`https://rickandmortyapi.com/api/character`)
.then(response => response.json()) .then(response => response.json())
.then(response => response.results); .then(response => response.results)
} }
getCharacters().then(characters => { getCharacters().then(characters => {
console.log(characters); // (20) [{...}, {...}, {...}] console.log(characters) // (20) [{...}, {...}, {...}]
}); })
``` ```
Let's break down what's happening in this function Let's break down what's happening in this function
@@ -15,9 +15,9 @@ function doAsync(number) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
doDatabase().then(function(dbResult) { doDatabase().then(function(dbResult) {
otherDbFunction(dbResult).then(function(secondResult) { otherDbFunction(dbResult).then(function(secondResult) {
resolve(secondResult + 10); resolve(secondResult + 10)
}) })
}); })
}) })
} }
``` ```
@@ -29,9 +29,9 @@ already returns a promise, you can just return the original thing.
function doAsync(number) { function doAsync(number) {
return doDatabase().then(function(dbResult) { return doDatabase().then(function(dbResult) {
otherDbFunction(dbResult).then(function(secondResult) { otherDbFunction(dbResult).then(function(secondResult) {
return secondResult + 10; return secondResult + 10
}) })
}); })
} }
``` ```
@@ -40,11 +40,13 @@ is that they allow you do chain them sequentially.
```js ```js
function doAsync(number) { function doAsync(number) {
return doDatabase().then(function(dbResult) { return doDatabase()
return otherDbFunction(dbResult) .then(function(dbResult) {
}).then(function(secondResult) { return otherDbFunction(dbResult)
return secondResult + 10; })
}); .then(function(secondResult) {
return secondResult + 10
})
} }
``` ```
@@ -56,17 +58,18 @@ function doAsync(number) {
return doDatabase() return doDatabase()
.then(otherDbFunction) .then(otherDbFunction)
.then(function(secondResult) { .then(function(secondResult) {
return secondResult + 10; return secondResult + 10
}); })
} }
``` ```
And you don't need those returns if you just have ES6 arrow functions And you don't need those returns if you just have ES6 arrow functions
```js ```js
const doAsync = number => doDatabase() const doAsync = number =>
.then(otherDbFunction) doDatabase()
.then(secondResult => secondResult + 10); .then(otherDbFunction)
.then(secondResult => secondResult + 10)
``` ```
Wow, that last one looks a lot cleaner to me than the first. Keeping that in mind, maybe we could be making some of our other functions cleaner as well Wow, that last one looks a lot cleaner to me than the first. Keeping that in mind, maybe we could be making some of our other functions cleaner as well
@@ -13,7 +13,6 @@ There are multiple benefits to using a singleton class
- You're always going to pull the same instance of the class - You're always going to pull the same instance of the class
- It's only instantiated once - It's only instantiated once
- Use `$this` in a static-like context - Use `$this` in a static-like context
-
By using a singleton instead of a static class we expose a cleaner class to use and we can use regular instance properties instead of static properties. By using a singleton instead of a static class we expose a cleaner class to use and we can use regular instance properties instead of static properties.
+11 -9
View File
@@ -1,14 +1,16 @@
import React from "react" import React from "react"
import Layout from "../components/Layout" import { Layout } from "../components/Layout"
import SEO from "../components/SEO" import { SEO } from "../components/SEO"
const NotFoundPage = () => ( function NotFoundPage() {
<Layout> return (
<SEO title="404: Not found" /> <Layout>
<h1>NOT FOUND</h1> <SEO title="404: Not found" />
<p>You just hit a route that doesn&#39;t exist... the sadness.</p> <h1>NOT FOUND</h1>
</Layout> <p>You just hit a route that doesn&#39;t exist... the sadness.</p>
) </Layout>
)
}
export default NotFoundPage export default NotFoundPage
+10 -8
View File
@@ -1,13 +1,15 @@
import React from "react" import React from "react"
import DocsLayout from "../components/DocsLayout" import { DocsLayout } from "../components/DocsLayout"
import SEO from "../components/SEO" import { SEO } from "../components/SEO"
const DocsPage = () => ( function DocsPage() {
<DocsLayout> return (
<SEO title="Docs" /> <DocsLayout>
the docs <SEO title="Docs" />
</DocsLayout> the docs
) </DocsLayout>
)
}
export default DocsPage export default DocsPage
-1
View File
@@ -1 +0,0 @@
# Hello
+14 -13
View File
@@ -1,19 +1,20 @@
import React from "react" import React from "react"
import { Link } from "gatsby" import { Link } from "gatsby"
import Layout from "../components/Layout" import { Layout } from "../components/Layout"
import SEO from "../components/SEO" import { SEO } from "../components/SEO"
const IndexPage = () => ( function IndexPage() {
<Layout> return (
<SEO title="Home" /> <Layout>
<h1>Hi people</h1> <SEO title="Home" />
<p>Welcome to your new Gatsby site.</p> <h1>Hi people</h1>
<p>Now go build something great.</p> <p>Welcome to your new Gatsby site.</p>
<div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}> <p>Now go build something great.</p>
</div> <div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}></div>
<Link to="/page-2/">Go to page 2</Link> <Link to="/page-2/">Go to page 2</Link>
</Layout> </Layout>
) )
}
export default IndexPage export default IndexPage
+3 -3
View File
@@ -1,10 +1,10 @@
import React from "react" import React from "react"
import { graphql } from "gatsby" import { graphql } from "gatsby"
import SEO from "../components/SEO" import { SEO } from "../components/SEO"
import DocsLayout from "../components/DocsLayout" import { DocsLayout } from "../components/DocsLayout"
// @todo maybe find alternative type for data // @todo maybe find alternative type for data
const LanguagePost = ({ data }: any) => { function LanguagePost({ data }: any) {
const { html, frontmatter } = data.file.post const { html, frontmatter } = data.file.post
console.log(data) console.log(data)
return ( return (
+1
View File
@@ -1 +1,2 @@
declare module "*.svg" declare module "*.svg"
declare module "*.png"