Browse Source

Cleaned code

- switched from styled-components to less
- included all images in project and removed the github reference
- nicer loading button -> use circle notch now
pull/1/head
Arwed Mett 3 years ago
parent
commit
ab54675c82
Signed by: Pfeifenjoy GPG Key ID: 86943827297DA9FC
  1. 3
      .babelrc
  2. 2
      .flowconfig
  3. 20
      package.json
  4. 16
      src/action/action.js
  5. 1
      src/action/index.js
  6. 4
      src/action/message.js
  7. 36
      src/app.js
  8. 159
      src/components/contact.js
  9. 4
      src/components/header.js
  10. BIN
      src/components/images/github-chrome-fullname.png
  11. BIN
      src/components/images/java-compiler.gif
  12. BIN
      src/components/images/theseus.gif
  13. 36
      src/components/links.js
  14. 9
      src/components/projects.js
  15. 39
      src/components/references.js
  16. 17
      src/elements/body/body.js
  17. 3
      src/elements/body/index.js
  18. 54
      src/elements/button/index.js
  19. 9
      src/elements/contact.js
  20. 4
      src/elements/contact/index.js
  21. 10
      src/elements/contact/input.js
  22. 11
      src/elements/contact/textarea.js
  23. 62
      src/elements/content.js
  24. 5
      src/elements/header/header.js
  25. 23
      src/elements/header/image/index.js
  26. 16
      src/elements/header/index.js
  27. 8
      src/elements/header/links/container.js
  28. 10
      src/elements/header/links/icon.js
  29. 9
      src/elements/header/links/label.js
  30. 22
      src/elements/header/links/link.js
  31. 10
      src/elements/header/name.js
  32. 5
      src/elements/header/references/container.js
  33. 5
      src/elements/header/references/icon.js
  34. 6
      src/elements/header/references/index.js
  35. 5
      src/elements/header/references/label.js
  36. 12
      src/elements/header/references/reference.js
  37. 11
      src/elements/index.js
  38. 140
      src/elements/project.js
  39. 33
      src/elements/section.js
  40. 24
      src/elements/text/headings.js
  41. 9
      src/elements/text/index.js
  42. 8
      src/elements/text/text.js
  43. 5
      src/elements/title.js
  44. 8
      src/index.html
  45. 3
      src/index.js
  46. 53
      src/noscript.js
  47. 4
      src/reducer/index.js
  48. 7
      src/render/index.ejs
  49. 29
      src/render/index.js
  50. 35
      src/router.js
  51. 18
      src/runtime.js
  52. 14
      src/store/index.js
  53. 8
      src/store/middleware.js
  54. 7
      src/style/body.less
  55. 27
      src/style/button.less
  56. 3
      src/style/colors.less
  57. 29
      src/style/content/contact/form.less
  58. 6
      src/style/content/contact/index.less
  59. 32
      src/style/content/contact/input.less
  60. 10
      src/style/content/contact/send/container.less
  61. 6
      src/style/content/contact/send/icon.less
  62. 6
      src/style/content/contact/send/index.less
  63. 4
      src/style/content/contact/send/text.less
  64. 14
      src/style/content/contact/submit.less
  65. 5
      src/style/content/header/container.less
  66. 11
      src/style/content/header/image.less
  67. 6
      src/style/content/header/index.less
  68. 3
      src/style/content/header/name.less
  69. 4
      src/style/content/header/references/container.less
  70. 3
      src/style/content/header/references/icon.less
  71. 6
      src/style/content/header/references/index.less
  72. 6
      src/style/content/header/references/label.less
  73. 8
      src/style/content/index.less
  74. 20
      src/style/content/navigation/container.less
  75. 5
      src/style/content/navigation/index.less
  76. 13
      src/style/content/navigation/section-link.less
  77. 40
      src/style/content/no-script/close-button.less
  78. 4
      src/style/content/no-script/index.less
  79. 10
      src/style/content/no-script/warning.less
  80. 19
      src/style/content/project/background-image.less
  81. 7
      src/style/content/project/constants.less
  82. 8
      src/style/content/project/container.less
  83. 5
      src/style/content/project/content.less
  84. 4
      src/style/content/project/description.less
  85. 5
      src/style/content/project/index.less
  86. 13
      src/style/content/project/navigation-container.less
  87. 8
      src/style/content/project/navigation.less
  88. 37
      src/style/content/project/project.less
  89. 3
      src/style/content/project/title.less
  90. 12
      src/style/content/section.less
  91. 11
      src/style/headings.less
  92. 5
      src/style/index.js
  93. 5
      src/style/index.less
  94. 9
      src/style/link.less
  95. 3
      src/util/index.js
  96. 4
      src/util/map.js
  97. 4
      test/index.js
  98. 9
      test/util/index.js
  99. 13
      test/util/map.js
  100. 14
      webpack.config.js

3
.babelrc

@ -3,8 +3,5 @@
"@babel/preset-env",
"@babel/preset-flow",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties"
]
}

2
.flowconfig

@ -1,5 +1,6 @@
[ignore]
.*/build/.*
.*/node_modules/styled-components/.*
[include]
@ -12,6 +13,7 @@ flow-typed
module.use_strict=true
suppress_type=$FlowIgnore
module.name_mapper.extension='html' -> '<PROJECT_ROOT>/flow/stub/file-loader.js'
module.name_mapper.extension='less' -> '<PROJECT_ROOT>/flow/stub/file-loader.js'
module.name_mapper.extension='asc' -> '<PROJECT_ROOT>/flow/stub/file-loader.js'
module.name_mapper.extension='ejs' -> '<PROJECT_ROOT>/flow/stub/raw-loader.js'

20
package.json

@ -15,12 +15,12 @@
"scripts": {
"start": "webpack-dev-server --env development",
"test:flow": "flow --quiet",
"test:lint": "eslint src",
"test:lint": "eslint src test",
"test:mocha": "mocha --use_strict --require @babel/register --require babel-polyfill",
"test": "npm run test:lint && npm run test:flow && npm run test:mocha",
"babel-node": "npx babel-node src/local-instance.js --source-maps",
"build:webpack": "webpack --env production",
"build:flow": "flow-copy-source src build",
"build:webpack": "webpack --env production",
"build:flow": "flow-copy-source src build",
"build": "npm run build:webpack && npm run build:flow",
"clean": "rm -rf build"
},
@ -29,19 +29,19 @@
"devDependencies": {
"@babel/cli": "^7.1.2",
"@babel/core": "^7.1.2",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/polyfill": "^7.0.0",
"@babel/preset-env": "^7.1.0",
"@babel/preset-flow": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.0.0",
"@babel/node": "^7.2.2",
"babel-eslint": "^9.0.0",
"babel-loader": "^8.0.4",
"eslint": "^5.6.0",
"eslint-plugin-flowtype": "^2.46.1",
"eslint-plugin-react": "^7.7.0",
"eslint-plugin-flowtype": "^3.4.2",
"eslint-plugin-react": "^7.12.4",
"file-loader": "^2.0.0",
"flow-bin": "^0.69.0",
"flow-bin": "^0.93.0",
"flow-copy-source": "^2.0.2",
"flow-typed": "^2.4.0",
"mocha": "^5.1.0",
@ -52,14 +52,16 @@
"webpack-dev-server": "^3.1.14"
},
"dependencies": {
"@babel/node": "^7.2.2",
"@fortawesome/fontawesome-svg-core": "^1.2.6",
"@fortawesome/free-brands-svg-icons": "^5.4.1",
"@fortawesome/free-solid-svg-icons": "^5.4.1",
"@fortawesome/react-fontawesome": "^0.1.3",
"css-loader": "^2.1.0",
"ejs": "^2.6.1",
"express": "^4.16.4",
"intersection-observer": "^0.5.0",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"react": "^16.5.2",
"react-autosize-textarea": "^5.0.0",
"react-dom": "^16.5.2",
@ -71,6 +73,6 @@
"redux-logger": "^3.0.6",
"redux-promise-middleware": "^6.1.0",
"redux-thunk": "^2.3.0",
"styled-components": "^3.4.10"
"style-loader": "^0.23.1"
}
}

16
src/action/action.js

@ -0,0 +1,16 @@
//@flow
export type async_action_type = "SEND_MESSAGE"
export type static_action_type = "SEND_MESSAGE"
export type AsyncAction = {|
type: async_action_type,
payload: Promise<*>
|}
export type StaticAction<T> = {|
type: static_action_type,
payload: T
|}
export type Action<T> = AsyncAction | StaticAction<T>

1
src/action/index.js

@ -1,3 +1,4 @@
//@flow
export { send_message } from "./message"
export type * from "./action"

4
src/action/message.js

@ -1,12 +1,14 @@
//@flow
import type { AsyncAction } from "./action"
export type message_t = {
email: string;
name: string;
text: string;
};
export const send_message = (payload: message_t) => ({
export const send_message = (payload: message_t): AsyncAction => ({
type: "SEND_MESSAGE",
payload: fetch("api/message/create", {
method: "POST",

36
src/app.js

@ -1,31 +1,27 @@
//@flow
import React from "react"
import React, { Fragment } from "react"
import { Header, Projects, Contact } from "./components"
import { Body, content } from "./elements"
import { content } from "./elements"
import NoscriptWarning from "./noscript"
import { ThemeProvider } from "styled-components"
import { Dark } from "./themes"
import { Provider } from "react-redux"
import store from "./store"
const projects = {
title: "My Projects",
body: () => <Projects />
body: () => <Projects />,
hash: "projects"
}
const contact = {
title: "Contact",
body: () => <Contact />
body: () => <Contact />,
hash: "contact"
}
export default () => <Provider store={ store }>
<ThemeProvider theme={ Dark }>
<Body>
<NoscriptWarning />
<Header />
{ content([
projects,
contact
]) }
</Body>
</ThemeProvider>
</Provider>
export default () => //<ThemeProvider theme={ Dark }>
<Fragment>
<NoscriptWarning />
<Header />
{ content([
projects,
contact
]) }
</Fragment>
//</ThemeProvider>

159
src/components/contact.js

@ -1,121 +1,39 @@
//@flow
import React, { Component } from "react"
import React, { Component, Fragment } from "react"
import { Button } from "../elements"
import styled from "styled-components"
import textarea from "react-autosize-textarea"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons"
import { connect } from "react-redux"
import { send_message } from "../action"
import {
ContactInput as Input,
ContactTextarea as Textarea
} from "../elements"
/************************************************************/
/* Styling */
/************************************************************/
const BaseInput = ({ validation }: { validation: boolean }) => `
border: none;
border-bottom: #888 solid;
padding: 5px;
background-color: inherit;
outline: none;
transition: 0.3s ease;
color: white;
:focus {
border-bottom: white solid;
}
:invalid {
${ validation ? "border-bottom: red solid;" : "" }
}
`
const Input = styled.input`
${ BaseInput }
`
const Name = styled(Input)`
grid-area: name;
`
const Email = styled(Input)`
grid-area: email;
`
const Message = styled(textarea)`
${ BaseInput }
grid-area: text;
grid-column-end: span 2;
resize: vertical;
box-sizing: border-box;
`
const Send = styled(Button)`
grid-area: send;
justify-self: center;
background-color: #666;
border: dotted 1px white;
color: white;
padding-top: 5px;
padding-bottom: 5px;
padding-left: 15px;
padding-right: 15px;
cursor: pointer;
font-family: inherit;
font-size: 10pt;
`
const Form = styled.form`
display: grid;
width: 100%;
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr auto auto;
grid-template-areas:
"name"
"email"
"text"
"send";
grid-gap: 10px;
@media screen and (min-width: 500px) {
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr auto auto;
grid-template-areas:
"name email"
"text text"
"send send";
}
const Name = (props: *) => <Input className="name" { ...props } />
const Email = (props: *) => <Input className="email" { ...props } />
const Message = (props: *) => <Textarea className="message" { ...props } />
const Submit = (props: *) => <Button className="submit" { ...props } />
const Form = (props: *) => <form { ...props } />
margin-top: 10px;
margin-bottom: 10px;
max-width: 800px;
margin-left: auto;
margin-right: auto;
`
/************************************************************/
/* Successfully send */
/************************************************************/
const SendIcon = styled(FontAwesomeIcon)`
color: white;
min-width: 50px;
min-height: 50px;
margin: 10px;
`
const SendText = styled.div`
color: white;
font-size: 10pt;
`
const SendContainer = styled.div`
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
distplay: flex;
height: 250px;
`
const SendIcon = (props: *) => <FontAwesomeIcon className="icon" { ...props } />
const SendText = (props: *) => <div className="text" { ...props } />
const SendContainer = (props: *) => <div className="send" { ...props } />
const Success = () => <SendContainer>
<SendIcon icon={ faCheck } />
<SendText>Success</SendText>
@ -144,23 +62,17 @@ type ContactState = {
validation: boolean
}
/************************************************************/
/* Container */
/************************************************************/
const Container = styled.div`
min-height: 250px;
width: 100%;
padding: 0;
margin: 0;
`
export class Contact extends Component<ContactProps, ContactState> {
state = {
name: "",
email: "",
text: "",
validation: false
constructor(props: ContactProps) {
super(props)
this.state = {
name: "",
email: "",
text: "",
validation: false
}
}
handleInput(name: string) {
@ -197,6 +109,7 @@ export class Contact extends Component<ContactProps, ContactState> {
body = <Form
action="/contact"
method="post"
className="contact"
onSubmit={ this.handleSubmit.bind(this) }
>
<Name
@ -206,7 +119,7 @@ export class Contact extends Component<ContactProps, ContactState> {
required={ true }
value={ name }
onChange={ this.handleInput("name").bind(this) }
validation={ validation ? 1 : 0 }
validation={ validation }
disabled={ state === "PENDING" }
/>
<Email
@ -216,7 +129,7 @@ export class Contact extends Component<ContactProps, ContactState> {
required={ true }
value={ email }
onChange={ this.handleInput("email").bind(this) }
validation={ validation ? 1 : 0 }
validation={ validation }
disabled={ state === "PENDING" }
/>
<Message
@ -227,16 +140,16 @@ export class Contact extends Component<ContactProps, ContactState> {
required={ true }
value={ text }
onChange={ this.handleInput("text").bind(this) }
validation={ validation ? 1 : 0 }
validation={ validation }
disabled={ state === "PENDING" }
/>
<Send
<Submit
busy={ state === "PENDING" }
type="submit"
onClick={ this.activateValidation.bind(this) }
>
Send
</Send>
</Submit>
</Form>
break
case "FULFILLED":
@ -246,7 +159,7 @@ export class Contact extends Component<ContactProps, ContactState> {
body = <Fail />
}
return <Container>{ body }</Container>
return <Fragment>{ body }</Fragment>
}
}

4
src/components/header.js

@ -2,11 +2,11 @@
import React from "react"
import { Header, HeaderImage, HeaderName } from "../elements"
import Links from "./links"
import References from "./references"
export default () => <Header>
<HeaderImage />
<HeaderName>Arwed Mett</HeaderName>
<Links />
<References />
</Header>

BIN
src/components/images/github-chrome-fullname.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
src/components/images/java-compiler.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 KiB

BIN
src/components/images/theseus.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 KiB

36
src/components/links.js

@ -1,36 +0,0 @@
//@flow
import React from "react"
import { Container, Icon, Link } from "../elements/header/links"
import { library } from "@fortawesome/fontawesome-svg-core"
import { faKey } from "@fortawesome/free-solid-svg-icons"
import { faGithub, faLinkedin, faStackOverflow } from "@fortawesome/free-brands-svg-icons"
import pubKey from "./arwed-mett.pub.asc"
library.add(faGithub)
library.add(faLinkedin)
library.add(faStackOverflow)
library.add(faKey)
export default () => <Container>
<Link
icon={ <Icon icon={ [ "fab", "github" ] } /> }
label="Github"
href="https://github.com/Pfeifenjoy"
/>
<Link
icon={ <Icon icon={ [ "fab", "linkedin" ] } /> }
label="Linkedin"
href="https://www.linkedin.com/in/arwed-mett-4b5784123/"
/>
<Link
icon={ <Icon icon={ [ "fab", "stack-overflow" ] } /> }
label="Stack Overflow"
href="https://stackoverflow.com/users/4399651/arwed-mett?tab=profile"
/>
<Link
icon={ <Icon icon={ faKey } /> }
label="GPG - Public Key"
href={ pubKey }
/>
</Container>

9
src/components/projects.js

@ -2,13 +2,16 @@
import React from "react"
import { ProjectContainer, Project } from "../elements"
import theseus_image from "./images/theseus.gif"
import java_compiler_image from "./images/java-compiler.gif"
import github_chrome_fullname_image from "./images/github-chrome-fullname.png"
export default () => <ProjectContainer>
<Project
title="Theseus"
href="https://github.com/Pfeifenjoy/Theseus"
description="Adventure game written in the context of my software engineering lecture."
img="https://github.com/Pfeifenjoy/Theseus/raw/master/theseus.gif"
img={ theseus_image }
/>
<Project
title="Chat"
@ -24,12 +27,12 @@ export default () => <ProjectContainer>
title="github-chrome-fullname"
href="https://github.com/Pfeifenjoy/github-chrome-fullname"
description="Chrome extension to display full-name(s) instead of SAP D- / I-User in GitHub Enterprise."
img="https://github.com/cgrail/github-chrome-fullname/raw/master/chrome-store-screenshot.png"
img={ github_chrome_fullname_image }
/>
<Project
title="Small Java Compiler"
href="https://github.com/Pfeifenjoy/compilerbau-WS17-18"
description="A small Java compiler developed during my compiler construction lecture."
img="https://github.com/Pfeifenjoy/compilerbau-WS17-18/blob/master/figs/usage.gif?raw=true"
img={ java_compiler_image }
/>
</ProjectContainer>

39
src/components/references.js

@ -0,0 +1,39 @@
//@flow
import React from "react"
import {
ReferenceContainer as Container,
ReferenceIcon as Icon,
Reference
} from "../elements/header"
import {
Key,
Github,
Linkedin,
StackOverflow
} from "../icon"
import pubKey from "./arwed-mett.pub.asc"
export default () => <Container>
<Reference
icon={ Icon(<Github />) }
label="Github"
href="https://github.com/Pfeifenjoy"
/>
<Reference
icon={ Icon(<Linkedin />) }
label="Linkedin"
href="https://www.linkedin.com/in/arwed-mett-4b5784123/"
/>
<Reference
icon={ Icon(<StackOverflow />) }
label="Stack Overflow"
href="https://stackoverflow.com/users/4399651/arwed-mett?tab=profile"
/>
<Reference
icon={ Icon(<Key />) }
label="GPG - Public Key"
href={ pubKey }
/>
</Container>

17
src/elements/body/body.js

@ -1,17 +0,0 @@
//@flow
import styled from "styled-components"
export default styled.div.attrs({
backgroundcolor: props => props.theme.backgroundcolor || "#111111"
})`
background-color: ${ props => props.backgroundcolor };
width: 100%;
min-height: 100%;
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
font-family: monospace;
color: white;
`

3
src/elements/body/index.js

@ -1,3 +0,0 @@
//@flow
export { default as Body } from "./body"

54
src/elements/button/index.js

@ -1,59 +1,19 @@
//@flow
import React, { Component, Fragment } from "react"
import type { ElementProps, Node } from "react"
import { library } from "@fortawesome/fontawesome-svg-core"
import { faCircle } from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import styled, { keyframes } from "styled-components"
import type { Node } from "react"
import { CircleNotch } from "../../icon"
library.add(faCircle)
/************************************************************/
/* Loader */
/************************************************************/
const LoaderKeyframes = keyframes`
from {
transform: translateX(-50%) translateY(-50%) rotate(0deg);
}
to {
transform: translateX(-50%) translateY(-50%) rotate(360deg);
}
`
const LoaderStyle = styled(FontAwesomeIcon)`
position: absolute;
display: block;
height: 90%;
max-height: 200px;
top: 50%;
left: 50%;
animation: ${ LoaderKeyframes } 2s linear infinite;
`
const Loader = () => <LoaderStyle icon="circle" />
/************************************************************/
/* Button Style */
/************************************************************/
const Base = styled.button`
position: relative;
`
type props_t = {
type ButtonProps = {
busy: boolean,
children: ?Node
} | ElementProps<typeof Base>
}
/************************************************************/
/* Button logic */
/************************************************************/
export default class Button extends Component<props_t, { }> {
export default class Button extends Component<ButtonProps, { }> {
render() {
const { busy, children, ...props } = this.props
@ -62,10 +22,10 @@ export default class Button extends Component<props_t, { }> {
<span style={{ visibility: "hidden" }}>
{ children }
</span>
<Loader />
<CircleNotch className="loader" />
</Fragment>
return <Base { ...props }>{ busy ? Busy: children }</Base>
return <button { ...props }>{ busy ? Busy : children }</button>
}
}

9
src/elements/contact.js

@ -1,9 +0,0 @@
//@flow
import styled from "styled-components"
export const Container = styled.div`
width: 100%;
display: flex;
justify-content: center;
`

4
src/elements/contact/index.js

@ -0,0 +1,4 @@
//@flow
export { default as Input } from "./input"
export { default as Textarea } from "./textarea"

10
src/elements/contact/input.js

@ -0,0 +1,10 @@
//@flow
import React from "react"
export default
({ validation, className, ...props }: { validation: boolean, className: string }) =>
<input
className={ className + (validation ? " validate" : "") }
{ ...props }
/>

11
src/elements/contact/textarea.js

@ -0,0 +1,11 @@
//@flow
import React from "react"
import textarea from "react-autosize-textarea"
export default
({ validation, className, ...props }: { validation: boolean, className: string }) =>
<textarea
className={ className + (validation ? " validate" : "" )}
{ ...props }
/>

62
src/elements/content.js

@ -3,52 +3,21 @@
import React, { Component, Fragment } from "react"
import { Section } from "."
import type { SectionDescription } from "."
import styled from "styled-components"
import Observer from "react-intersection-observer"
const Link = styled.a`
text-decoration: inherit;
color: white;
min-height: 40px;
display: flex;
align-items: center;
padding: 0 20px;
const Link = (props: *) => <a className="section-link" { ...props } />
transition: 0.3s ease;
:hover {
background-color: #777;
}
`
const Header = styled.header.attrs({
backgroundcolor: props => props.theme.backgroundcolor || "#111111"
})`
position: sticky;
top: 0;
display: flex;
justify-content: left;
align-items: center;
transition: 0.5s ease;
width: ${ props => props.stuck ? "100%" : "60%" };
z-index: 5;
margin: 20px auto;
min-height: 40px;
border-bottom: solid white;
background-color: ${ props => props.backgroundcolor };
`
/**
* create a hash based on the section title
*/
const get_hash = (section: SectionDescription, key: number) =>
key + "-" + section.title.toLocaleLowerCase().split(" ").join("-")
const Navigation = ({ stuck, ...rest }: { stuck: boolean }) =>
<nav
className={ stuck ? "stuck" : "" }
{ ...rest }
/>
/**
* create a link to the section, according to the name and key.
*/
const create_link = (section: SectionDescription, key: number) =>
<Link href={ "#" + get_hash(section, key) } key={ key }>{ section.title }</Link>
<Link href={ "#" + section.hash } key={ key }>{ section.title }</Link>
/**
* create a link for every section.
@ -61,7 +30,7 @@ const generate_links = (sections: Array<SectionDescription>) => sections.map(cre
const create_section = (description: SectionDescription, key: number) =>
<Section
description={ description }
hash={ get_hash(description, key) }
hash={ description.hash }
key={ key }
/>
@ -71,11 +40,16 @@ const create_section = (description: SectionDescription, key: number) =>
const generate_sections = (descriptions: Array<SectionDescription>) =>
descriptions.map(create_section)
type TopProps = { sections: Array<SectionDescription> }
type TopState = { stuck: boolean }
class Top extends Component<{ sections: Array<SectionDescription> }, { stuck: boolean }> {
class Top extends Component<TopProps, TopState> {
state = {
stuck: false
constructor(props: TopProps) {
super(props)
this.state = {
stuck: false
}
}
sentinel(in_view: boolean) {
@ -88,9 +62,9 @@ class Top extends Component<{ sections: Array<SectionDescription> }, { stuck: bo
return <Fragment>
<Observer onChange={ this.sentinel.bind(this) }>
</Observer>
<Header stuck={ stuck }>
<Navigation stuck={ stuck }>
{ generate_links(sections) }
</Header>
</Navigation>
</Fragment>
}
}

5
src/elements/header/header.js

@ -0,0 +1,5 @@
//@flow
import React from "react"
export default (props: *) => <header className="header" { ...props } />

23
src/elements/header/image/index.js

@ -1,19 +1,12 @@
//@flow
import styled from "styled-components"
import React from "react"
import src from "./image.jpg"
export default styled.img.attrs({
src,
size: props => props.size || "200px",
bordercolor: props => props.theme.textcolor || "#FFFFFF",
backgroundcolor: props => props.theme.backgroundcolor || "#111111"
})`
border-radius: 100%;
border: solid 2px;
border-color: ${ props => props.bordercolor };
margin-top: 6em;
width: ${ props => props.size };
height: ${ props => props.size };
background-color: ${ props => props.backgroundcolor };
`
export default (props: *) =>
<img
className="image"
src={ src }
{ ...props }
/>

16
src/elements/header/index.js

@ -1,13 +1,11 @@
//@flow
import styled from "styled-components"
export default styled.div`
display: flex;
align-items: center;
flex-direction: column;
`
export { default as default } from "./header"
export { default as Name } from "./name"
export { default as Image } from "./image"
export {
Reference,
Container as ReferenceContainer,
Label as ReferenceLabel,
Icon as ReferenceIcon
} from "./references"

8
src/elements/header/links/container.js

@ -1,8 +0,0 @@
//@flow
import styled from "styled-components"
export default styled.div`
display: flex;
align-items: center;
`

10
src/elements/header/links/icon.js

@ -1,10 +0,0 @@
//@flow
import styled from "styled-components"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
export default styled(FontAwesomeIcon)`
color: ${ props => props.theme.textColor || "#FFFFFF" };
margin-right: 5px;
`

9
src/elements/header/links/label.js

@ -1,9 +0,0 @@
//@flow
import styled from "styled-components"
export default styled.span`
color: ${ props => props.theme.textColor || "#FFFFFF" };
font-family: ${ props => props.theme.fontFamily || "monospace" };
font-size: 8pt;
`

22
src/elements/header/links/link.js

@ -1,22 +0,0 @@
//@flow
import React from "react"
import type { Node } from "react"
import styled from "styled-components"
import Label from "./label"
const Wrapper = styled.a`
color: ${ props => props.theme.textColor };
display: flex;
align-items: center;
text-decoration: none;
margin: 5px;
`
export default (props: { icon?: Node, label: string, href: string }) =>
<Wrapper
href={ props.href }
target="_blank">
{ props.icon }
<Label>{ props.label }</Label>
</Wrapper>

10
src/elements/header/name.js

@ -1,10 +1,6 @@
//@flow
import styled from "styled-components"
import { Title } from "../text"
import React from "react"
import { Title } from ".."
const Name = styled(Title)`
margin: 20px;
`
export default Name
export default (props: *) => <Title className="name" { ...props } />

5
src/elements/header/references/container.js

@ -0,0 +1,5 @@
//@flow
import React from "react"
export default (props: *) => <div className="references" { ...props } />

5
src/elements/header/references/icon.js

@ -0,0 +1,5 @@
//@flow
import React, { type Node } from "react"
export default (icon: Node) => <span className="icon">{ icon }</span>

6
src/elements/header/links/index.js → src/elements/header/references/index.js

@ -1,6 +1,6 @@
//@flow
export { default as Container } from "./container"
export { default as Label } from "./label"
export { default as Icon } from "./icon"
export { default as Link } from "./link"
export { default as Container } from "./container"
export { default as Reference } from "./reference"

5
src/elements/header/references/label.js

@ -0,0 +1,5 @@
//@flow
import React from "react"
export default (props: *) => <span className="label" { ...props } />

12
src/elements/header/references/reference.js

@ -0,0 +1,12 @@
//@flow
import React, { type Node } from "react"
import { Label } from "."
export default (props: { icon?: Node, label: string, href: string }) =>
<a
href={ props.href }
>
{ props.icon }
<Label>{ props.label }</Label>
</a>

11
src/elements/index.js

@ -6,8 +6,6 @@ export {
Name as HeaderName
} from "./header"
export { Body } from "./body"
export { default as Section } from "./section"
export type { SectionDescription } from "./section"
@ -16,10 +14,13 @@ export {
ProjectContainer
} from "./project"
export {
H1, H2, Title, P
} from "./text"
export { default as Title } from "./title"
export { default as content } from "./content"
export { default as Button } from "./button"
export {
Input as ContactInput,
Textarea as ContactTextarea
} from "./contact"

140
src/elements/project.js

@ -2,116 +2,24 @@
import React, { Component } from "react"
import styled from "styled-components"
import { P } from "./text"
const Wrapper = (props: *) => <a className="project" { ...props } />
const height = 200
const width = 200
const Title = (props: *) => <h2 className="title" { ...props } />
const WrapperPadding = 20
const NavTextSize = 12
const NavTextPadding = 5
const Description = (props: *) => <p className="description" { ...props } />
const Wrapper = styled.a`
position: relative;
border: dashed 1px;
padding: ${ WrapperPadding }px;
const BackgroundImage = ({ background, ...props }: { background: boolean, src?: string }) =>
<img
className={ "background-image" + (background ? " background" : "") }
{ ...props }
/>
@media (min-width: 500px) {
width: ${ width }px;
min-height: ${ height }px;
}
@media (max-width: 500px) {
width: 100%;
height: 100%;
padding-bottom: calc(${ 2 * WrapperPadding }px + ${ NavTextSize }pt + ${ 2 * NavTextPadding }px);
}
border-color: ${ props => props.theme.textColor };
margin: 15px;
background-color: ${ props => props.theme.projectTileColor };
text-decoration: inherit;
color: ${ props => props.theme.textColor };
&:visited {
text-decoration: inherit;
color: ${ props => props.theme.projectVisitedColor };
border-color: ${ props => props.theme.projectVisitedColor };
}
`
Wrapper.defaultProps = {
theme: {
textColor: "#FFFFFF",
projectTileColor: "#222222",
projectVisitedColor: "#888888"
}
}
const Content = (props: *) => <div className="content" { ...props } />
const Title = styled.h2`
font-family: ${ props => props.theme.fontFamily };
font-size: 14pt;
`
const NavigationContainer = (props: *) =>
<div className="navigation-container" { ...props } />
Title.defaultProps = {
theme: {
fontFamily: "monospace"
}
}
const Description = styled(P)`
margin-top: 10px;
color: inherit;
`
const BackgroundImage = styled.img`
position: absolute;
top: ${ props => props.background ? "5px" : "0" };
left: ${ props => props.background ? "5px" : "0" };
width: calc(100% - ${ props => props.background ? "10px" : "0px" });
height: calc(100% - ${ props => props.background ? "10px" : "0px" });
z-index: 1;
filter: ${ props => props.background ? "blur(5px) brightness(30%)" : "blur(2px) brightness(50%)" };
transition: ${ props => props.background ? "0s" : "0.5s" };
display: ${ props => props.src ? "" : "none" };
`
const Content = styled.div`
position: relative;
width: 100%;
z-index: 2;
`
const NavigationContainer = styled.div`
position: absolute;
bottom: ${ WrapperPadding }px;
z-index: 2;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
left: 0;
margin-top: ${ WrapperPadding }px;
`
const Navigation = styled.span`
color: #FFFFFF;
font-size: ${ NavTextSize }pt;
background-color: #FF4136;
padding: ${ NavTextPadding }px;
border-radius: 5px;
font-family: ${ props => props.theme.fontFamily };
z-index: 2;
`
Navigation.defaultProps = {
theme: {
fontFamily: "monospace"
}
}
const Navigation = (props: *) => <span className="navigation" { ...props } />
type ProjectProps = {
title: string,
@ -125,13 +33,11 @@ type ProjectState = {
}
export default class Project extends Component<ProjectProps, ProjectState> {
static defaultProps = {
description: "",
href: ""
}
state = {
hover: false
constructor(props: ProjectProps) {
super(props)
this.state = {
hover: false
}
}
render() {
@ -145,7 +51,7 @@ export default class Project extends Component<ProjectProps, ProjectState> {
onMouseLeave={ () => this.setState({ hover: false }) }
>
<BackgroundImage src={ img } background={ !hover && !!img } />
<Content background={ hover && !!img }>
<Content>
<Title>{ title }</Title>
<Description>{ description }</Description>
</Content>
@ -156,11 +62,5 @@ export default class Project extends Component<ProjectProps, ProjectState> {
}
}
export const ProjectContainer = styled.div`
display: flex;
width: 100%;
flex-direction: row;
justify-content: center;
flex-wrap: wrap;
align-items: stretch;
`
export const ProjectContainer = (props: *) =>
<div className="projects" { ...props } />

33
src/elements/section.js

@ -2,29 +2,16 @@
import React from "react"
import type { Node } from "react"
import styled from "styled-components"
import { H1 } from "./text"
const padding = 20
const Style = styled.section`
min-width: 100px;
width: calc(100% - ${ 2 * padding }%);
padding-left: ${ padding }%;
padding-right: ${ padding }%;
margin-top: 30px;
margin-bottom: 30px;
`
const Title = H1
export type SectionDescription = {
export type SectionDescription = {|
title: string,
body: () => Node
}
body: () => Node,
hash: string
|}
export default (props: { description: SectionDescription, hash: string }) => <Style>
<Title id={ props.hash || "" }>{ props.description.title }</Title>
<br />
<div>{ props.description.body() }</div>
</Style>
export default (props: { description: SectionDescription, hash: string }) =>
<section id={ props.hash }>
<h1>{ props.description.title }</h1>
<br />
<div>{ props.description.body() }</div>
</section>

24
src/elements/text/headings.js

@ -1,24 +0,0 @@
//@flow
import styled from "styled-components"
const Base = (props: { theme: { textColor: string, fontFamily: string } }) => `
color: ${ props.theme.textColor || "#FFFFFF" };
font-family: ${ props.theme.fontFamily || "monospace" };
`
export const Title = styled.h1`
${ props => Base(props) };
font-size: 24pt;
`
export const H1 = styled.h1`
${ props => Base(props) };
font-size: 20pt;
`
export const H2 = styled.h2`
${ props => Base(props) };
font-size: 18pt;
`

9
src/elements/text/index.js

@ -1,9 +0,0 @@
//@flow
export {
H1, H2, Title
} from "./headings"
export {
P
} from "./text"

8
src/elements/text/text.js

@ -1,8 +0,0 @@
//@flow
import styled from "styled-components"
export const P = styled.p`
color: ${ props => props.theme.textColor };
font-family: ${ props => props.theme.fontFamily };
`

5
src/elements/title.js

@ -0,0 +1,5 @@
//@flow
import React from "react"
export default (props: *) => <span className="title" { ...props } />

8
src/index.html

@ -2,8 +2,10 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Homepage - Arwed Mett</title>
<title>Arwed Mett</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Personal homepage of Arwed Mett. It provides information about projects which I have worked on.">
<meta name="author" content="Arwed Mett">
<style>
/* http://meyerweb.com/eric/tools/css/reset/
@ -57,7 +59,7 @@
</style>
</head>
<body>
<div id="content"></div>
<script src="runtime.bundle.js"></script>
<main id="content"></main>
<script src="runtime.bundle.js" async></script>
</body>
</html>

3
src/index.js

@ -1,4 +1,5 @@
//@flow
export { default as App } from "./app"
export { default as default } from "./router"
export { default as createStore } from "./store"
export { default as renderToString } from "./render"

53
src/noscript.js

@ -1,59 +1,10 @@
//@flow
import React from "react"
import styled from "styled-components"
const NoscriptWarning = styled.div`
background-color: red;
display: block;
position: absolute;
width: 100%;
top: 0;
left: 0;
text-align: center;
min-height: 20px;
`
const NoscriptWarning = (props: *) => <div className="warning" { ...props } />
const CloseButton = styled.input`
:checked {
display: none;
}
:checked + div {
display: none;
}
z-index: 1;
right: 0;
top: 0;
position: absolute;
margin: 4px;
appearance: none;
opacity: 0.6;
width: 12px;
height: 12px;
cursor: pointer;
:hover {
opacity: 1;
}
:before, :after {
position: absolute;
content: ' ';
left: 5px;
height: 12px;
width: 2px;
background-color: #333;
}
:before {
transform: rotate(45deg);
}
:after {
transform: rotate(-45deg);
}
`
const CloseButton = (props: *) => <input className="close-button" { ...props } />
export default () => <noscript>
<CloseButton type="checkbox" />

4
src/reducer/index.js

@ -3,6 +3,4 @@
import { combineReducers } from "redux"
import message from "./message"
export default combineReducers({
message
})
export default combineReducers({ message })

7
src/index.ejs → src/render/index.ejs

@ -60,9 +60,12 @@
<%- style %>
</head>
<body>
<div id="content">
<main id="content">
<%- content %>
</div>
</main>
<script>
window.__PRELOADED_STATE__ = <%= state %>
</script>
<script src="runtime.bundle.js" async></script>
</body>
</html>

29
src/render/index.js

@ -0,0 +1,29 @@
//@flow
import React from "react"
import type { StaticAction } from "../action"
import createStore from "../store"
import { renderToString } from "react-dom/server"
import { Provider } from "react-redux"
import App from "../app"
import ejs from "ejs"
import index_file from "raw-loader!./index.ejs"
import style from "../style"
//static variables
export const template = ejs.compile(index_file)
export const title = "Arwed Mett"
export default (actions: Array<StaticAction<*>>) => {