Changed all functional components to class components. added better comments, minor optimizations here and there, removed some unfinished posts from blogs
This commit is contained in:
parent
c850184bc7
commit
046dd05713
20 changed files with 987 additions and 927 deletions
|
@ -22,15 +22,8 @@
|
|||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["react", "@typescript-eslint"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-module-boundary-types": ["off"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"react/no-unknown-property": [
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
title: death of miracle
|
||||
author: developomp
|
||||
date: May 15, 2021
|
||||
---
|
||||
|
||||
Understanding things what we once called magic.
|
||||
Science.
|
|
@ -1,25 +0,0 @@
|
|||
---
|
||||
title: guide to arch linux
|
||||
author: developomp
|
||||
date: May 13, 2021
|
||||
---
|
||||
|
||||
# Guide to Arch Linux
|
||||
|
||||
## Why arch linux?
|
||||
|
||||
## Getting started
|
||||
|
||||
### Philosophy
|
||||
|
||||
### Arch wiki
|
||||
|
||||
### Command line
|
||||
|
||||
## Installation
|
||||
|
||||
## display manager
|
||||
|
||||
## window manager
|
||||
|
||||
# Conclusion
|
|
@ -1,6 +0,0 @@
|
|||
학교로 갔다
|
||||
집으로 왔다
|
||||
일을 한다
|
||||
죽었다
|
||||
나는 죽었다
|
||||
나는 죽었다
|
|
@ -1,12 +1,12 @@
|
|||
import React, { createContext } from "react"
|
||||
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"
|
||||
import { ThemeProvider, createGlobalStyle } from "styled-components"
|
||||
import { HelmetProvider } from "react-helmet-async"
|
||||
import storage from "local-storage-fallback"
|
||||
import { useState, useEffect } from "react"
|
||||
import Spinner from "./components/Spinner"
|
||||
import LanguageContext from "./LanguageContext"
|
||||
|
||||
import theming from "./theming"
|
||||
|
||||
import Spinner from "./components/Spinner"
|
||||
import Navbar from "./components/Navbar"
|
||||
import Footer from "./components/Footer"
|
||||
|
||||
|
@ -126,124 +126,130 @@ blockquote {
|
|||
}
|
||||
`
|
||||
|
||||
function App() {
|
||||
/**
|
||||
* Loading
|
||||
*/
|
||||
const [isLoading, setLoading] = useState(true)
|
||||
interface AppProps {}
|
||||
|
||||
interface AppState {
|
||||
isLoading: boolean
|
||||
currentTheme: string
|
||||
currentLanguage: string
|
||||
}
|
||||
|
||||
export const LanguageContext = createContext({
|
||||
language: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
toggleLanguage: () => {},
|
||||
})
|
||||
|
||||
export default class App extends React.Component<AppProps, AppState> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
currentTheme: storage.getItem("theme") || "dark", // get theme from storage and set to "dark" mode if not set already
|
||||
currentLanguage: storage.getItem("lang") || "en", // get language from storage and set to "en" if not set already
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// show loading screen until all fonts are loaded.
|
||||
// Experimental feature. Not fully supported on all browsers (IE, I'm looking at you).
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet
|
||||
|
||||
// show loading screen until all fonts are loaded.
|
||||
// Experimental feature. Not fully supported on all browsers (IE, I'm looking at you).
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet
|
||||
useEffect(() => {
|
||||
// checks if document.fonts.onloadingdone is supported on the browser
|
||||
if (typeof document.fonts.onloadingdone != undefined) {
|
||||
document.fonts.onloadingdone = () => {
|
||||
setLoading(false)
|
||||
this.setState({ isLoading: false })
|
||||
}
|
||||
} else {
|
||||
setLoading(false)
|
||||
this.setState({ isLoading: false })
|
||||
}
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* Theme
|
||||
*/
|
||||
const [currentTheme, _setTheme] = useState(
|
||||
storage.getItem("theme") || "dark" // get theme from storage and set to "dark" mode if not set already
|
||||
)
|
||||
|
||||
// save theme when it is changed
|
||||
useEffect(() => {
|
||||
storage.setItem("theme", currentTheme)
|
||||
}, [currentTheme])
|
||||
|
||||
/**
|
||||
* Language
|
||||
*/
|
||||
|
||||
const [currentLanguage, _setLanguage] = useState(
|
||||
storage.getItem("lang") || "en" // get language from storage and set to "en" if not set already
|
||||
)
|
||||
|
||||
// save language when it is changed
|
||||
useEffect(() => {
|
||||
storage.setItem("lang", currentLanguage)
|
||||
}, [currentLanguage])
|
||||
|
||||
const languageState = {
|
||||
language: currentLanguage,
|
||||
toggleLanguage: () => {
|
||||
// cycle through languages
|
||||
let setLanguageTo = "en"
|
||||
if (currentLanguage == "en") setLanguageTo = "kr"
|
||||
_setLanguage(setLanguageTo)
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<HelmetProvider>
|
||||
<ThemeProvider
|
||||
theme={{
|
||||
currentTheme: currentTheme,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
setTheme: (setThemeTo) => _setTheme(setThemeTo), // make setTheme function available in other components
|
||||
}}
|
||||
>
|
||||
<LanguageContext.Provider value={languageState}>
|
||||
<GlobalStyle />
|
||||
<Router>
|
||||
<Navbar />
|
||||
<div id="content">
|
||||
{isLoading ? (
|
||||
<Spinner
|
||||
size={200}
|
||||
color={
|
||||
currentTheme == "dark"
|
||||
? theming.dark.color1
|
||||
: theming.light.color1
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/"
|
||||
component={() => (
|
||||
<Home howMany={4} title="Home" />
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/archives"
|
||||
component={() => (
|
||||
<Home title="Archives" />
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/portfolio"
|
||||
component={Portfolio}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/404"
|
||||
component={NotFound}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/:path*"
|
||||
component={Page}
|
||||
/>
|
||||
</Switch>
|
||||
)}
|
||||
</div>
|
||||
<Footer />
|
||||
</Router>
|
||||
</LanguageContext.Provider>
|
||||
</ThemeProvider>
|
||||
</HelmetProvider>
|
||||
)
|
||||
}
|
||||
componentDidUpdate(_, prevState) {
|
||||
if (this.state.currentTheme !== prevState.currentTheme) {
|
||||
// save theme when it is changed
|
||||
storage.setItem("theme", this.state.currentTheme)
|
||||
}
|
||||
|
||||
export default App
|
||||
if (this.state.currentLanguage !== prevState.currentLanguage) {
|
||||
// save language when it is changed
|
||||
storage.setItem("lang", this.state.currentLanguage)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<HelmetProvider>
|
||||
<ThemeProvider
|
||||
theme={{
|
||||
currentTheme: this.state.currentTheme,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
setTheme: (setThemeTo) =>
|
||||
this.setState({ currentTheme: setThemeTo }), // make setTheme function available in other components
|
||||
}}
|
||||
>
|
||||
<LanguageContext.Provider
|
||||
value={{
|
||||
language: this.state.currentLanguage,
|
||||
toggleLanguage: () => {
|
||||
// cycle through languages
|
||||
let setLanguageTo = "en"
|
||||
if (this.state.currentLanguage == "en")
|
||||
setLanguageTo = "kr"
|
||||
this.setState({
|
||||
currentLanguage: setLanguageTo,
|
||||
})
|
||||
},
|
||||
}}
|
||||
>
|
||||
<GlobalStyle />
|
||||
<Router>
|
||||
<Navbar />
|
||||
<div id="content">
|
||||
{this.state.isLoading ? (
|
||||
<Spinner size={200} />
|
||||
) : (
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/"
|
||||
component={() => (
|
||||
<Home
|
||||
howMany={4}
|
||||
title="Home"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/archives"
|
||||
component={() => (
|
||||
<Home title="Archives" />
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/portfolio"
|
||||
component={Portfolio}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/404"
|
||||
component={NotFound}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/:path*"
|
||||
component={Page}
|
||||
/>
|
||||
</Switch>
|
||||
)}
|
||||
</div>
|
||||
<Footer />
|
||||
</Router>
|
||||
</LanguageContext.Provider>
|
||||
</ThemeProvider>
|
||||
</HelmetProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
/**
|
||||
* go to App.tsx and search for `languageState` to see the actual values
|
||||
*/
|
||||
|
||||
import { createContext } from "react"
|
||||
|
||||
export default createContext({
|
||||
language: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
toggleLanguage: () => {},
|
||||
})
|
|
@ -1,69 +1,75 @@
|
|||
import React from "react"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faGithub } from "@fortawesome/free-brands-svg-icons"
|
||||
import styled from "styled-components"
|
||||
import theming from "../theming"
|
||||
|
||||
const StyledFooter = styled.footer`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1px; /* footer goes outside the page by 1 px for some reason */
|
||||
padding: 50px 10px;
|
||||
background-color: white;
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "white",
|
||||
dark: "black",
|
||||
})};
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "black",
|
||||
dark: "white",
|
||||
})};
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.logo {
|
||||
color: gray;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledLink = styled.a`
|
||||
width: 30px;
|
||||
font-size: 2rem;
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "lightgrey",
|
||||
dark: "grey",
|
||||
})};
|
||||
|
||||
&:hover {
|
||||
export default class Footer extends React.Component {
|
||||
StyledFooter = styled.footer`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1px; /* footer goes outside the page by 1 px for some reason */
|
||||
padding: 50px 10px;
|
||||
background-color: white;
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "white",
|
||||
dark: "black",
|
||||
})};
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "black",
|
||||
dark: "white",
|
||||
})};
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.logo {
|
||||
color: gray;
|
||||
}
|
||||
`
|
||||
|
||||
StyledLink = styled.a`
|
||||
width: 30px;
|
||||
font-size: 2rem;
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "lightgrey",
|
||||
dark: "grey",
|
||||
})};
|
||||
|
||||
&:hover {
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "black",
|
||||
dark: "white",
|
||||
})};
|
||||
}
|
||||
`
|
||||
|
||||
StyledStrong = styled.strong`
|
||||
font-size: 1.1rem;
|
||||
`
|
||||
|
||||
render() {
|
||||
return (
|
||||
<this.StyledFooter>
|
||||
<div className="logo">
|
||||
Copyright © develo
|
||||
<this.StyledStrong>p</this.StyledStrong>omp
|
||||
</div>
|
||||
|
||||
<div className="icons">
|
||||
<this.StyledLink
|
||||
href="https://github.com/developomp/developomp-site"
|
||||
target="_blank"
|
||||
>
|
||||
<FontAwesomeIcon icon={faGithub} />
|
||||
</this.StyledLink>
|
||||
</div>
|
||||
</this.StyledFooter>
|
||||
)
|
||||
}
|
||||
`
|
||||
|
||||
function Footer() {
|
||||
return (
|
||||
<StyledFooter>
|
||||
<div className="logo">
|
||||
Copyright © develo<strong>p</strong>omp
|
||||
</div>
|
||||
|
||||
<div className="icons">
|
||||
<StyledLink
|
||||
href="https://github.com/developomp/developomp-site"
|
||||
target="_blank"
|
||||
>
|
||||
<FontAwesomeIcon icon={faGithub} />
|
||||
</StyledLink>
|
||||
</div>
|
||||
</StyledFooter>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
||||
|
|
|
@ -1,48 +1,55 @@
|
|||
import React from "react"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faLanguage } from "@fortawesome/free-solid-svg-icons"
|
||||
import styled from "styled-components"
|
||||
import ReactTooltip from "react-tooltip"
|
||||
|
||||
import LanguageContext from "../LanguageContext"
|
||||
import { LanguageContext } from "../App"
|
||||
import theming from "../theming"
|
||||
|
||||
const StyledThemeButton = styled.div<{ language: string }>`
|
||||
${theming.styles.navbarButtonStyle}
|
||||
${(props) =>
|
||||
props.language == "en"
|
||||
? ""
|
||||
: "transform: scaleX(-1);\
|
||||
interface StyledThemeButtonProps {
|
||||
language: string
|
||||
}
|
||||
|
||||
export default class LanguageToggleButton extends React.Component {
|
||||
StyledThemeButton = styled.div<StyledThemeButtonProps>`
|
||||
${theming.styles.navbarButtonStyle}
|
||||
${(props) =>
|
||||
props.language == "en"
|
||||
? ""
|
||||
: "transform: scaleX(-1);\
|
||||
-moz-transform: scaleX(-1);\
|
||||
-webkit-transform: scaleX(-1);\
|
||||
-ms-transform: scaleX(-1);"};
|
||||
`
|
||||
`
|
||||
|
||||
function LanguageToggleButton() {
|
||||
function languageName(language) {
|
||||
languageName = (language) => {
|
||||
let name = "English"
|
||||
if (language == "kr") name = "Korean"
|
||||
return name
|
||||
}
|
||||
|
||||
return (
|
||||
<LanguageContext.Consumer>
|
||||
{({ language, toggleLanguage }) => (
|
||||
<>
|
||||
<StyledThemeButton
|
||||
data-tip
|
||||
data-for="language"
|
||||
onClick={toggleLanguage}
|
||||
language={language}
|
||||
>
|
||||
<FontAwesomeIcon icon={faLanguage} />
|
||||
</StyledThemeButton>
|
||||
<ReactTooltip id="language" type="dark" effect="solid">
|
||||
<span>Using {languageName(language)} language</span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
)}
|
||||
</LanguageContext.Consumer>
|
||||
)
|
||||
render() {
|
||||
return (
|
||||
<LanguageContext.Consumer>
|
||||
{({ language, toggleLanguage }) => (
|
||||
<>
|
||||
<this.StyledThemeButton
|
||||
data-tip
|
||||
data-for="language"
|
||||
onClick={toggleLanguage}
|
||||
language={language}
|
||||
>
|
||||
<FontAwesomeIcon icon={faLanguage} />
|
||||
</this.StyledThemeButton>
|
||||
<ReactTooltip id="language" type="dark" effect="solid">
|
||||
<span>
|
||||
Using {this.languageName(language)} language
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
)}
|
||||
</LanguageContext.Consumer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default LanguageToggleButton
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import React from "react"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faGithub } from "@fortawesome/free-brands-svg-icons"
|
||||
import styled from "styled-components"
|
||||
|
@ -11,85 +12,84 @@ import Sidebar from "./Sidebar"
|
|||
import ThemeToggleButton from "./ThemeToggleButton"
|
||||
import LanguageToggleButton from "./LanguageToggleButton"
|
||||
|
||||
const StyledNav = styled.nav`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 2rem;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.backgroundColor0,
|
||||
dark: theming.dark.backgroundColor0,
|
||||
})};
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color0,
|
||||
dark: theming.dark.color0,
|
||||
})};
|
||||
box-shadow: 0 4px 10px rgb(0 0 0 / 5%);
|
||||
export default class Navbar extends React.Component {
|
||||
StyledNav = styled.nav`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 2rem;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.backgroundColor0,
|
||||
dark: theming.dark.backgroundColor0,
|
||||
})};
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color0,
|
||||
dark: theming.dark.color0,
|
||||
})};
|
||||
box-shadow: 0 4px 10px rgb(0 0 0 / 5%);
|
||||
|
||||
.right {
|
||||
margin-left: auto;
|
||||
.right {
|
||||
margin-left: auto;
|
||||
}
|
||||
`
|
||||
|
||||
StyledNavLinks = styled.div`
|
||||
@media only screen and (max-width: ${theming.size.screen_size1}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
StyledImg = styled.img`
|
||||
height: 2rem;
|
||||
margin: 1rem;
|
||||
`
|
||||
|
||||
StyledLink = styled(Link)`
|
||||
${theming.styles.navbarButtonStyle}
|
||||
`
|
||||
|
||||
StyledALink = styled.a`
|
||||
${theming.styles.navbarButtonStyle}
|
||||
`
|
||||
render() {
|
||||
return (
|
||||
<this.StyledNav>
|
||||
<Link to="/">
|
||||
<this.StyledImg
|
||||
src={process.env.PUBLIC_URL + "/icon/icon_circle.svg"}
|
||||
/>
|
||||
</Link>
|
||||
<this.StyledNavLinks>
|
||||
{NavbarData.map((item, index) => {
|
||||
return (
|
||||
<this.StyledLink key={index} to={item.path}>
|
||||
{item.title}
|
||||
</this.StyledLink>
|
||||
)
|
||||
})}
|
||||
</this.StyledNavLinks>
|
||||
|
||||
<ThemeToggleButton />
|
||||
<LanguageToggleButton />
|
||||
|
||||
<this.StyledALink
|
||||
data-tip
|
||||
data-for="github"
|
||||
href="https://github.com/developomp/developomp-site"
|
||||
target="_blank"
|
||||
>
|
||||
<FontAwesomeIcon icon={faGithub} />
|
||||
</this.StyledALink>
|
||||
<ReactTooltip id="github" type="dark" effect="solid">
|
||||
<span>View source code</span>
|
||||
</ReactTooltip>
|
||||
|
||||
<SearchBox />
|
||||
<Sidebar />
|
||||
</this.StyledNav>
|
||||
)
|
||||
}
|
||||
`
|
||||
|
||||
const StyledNavLinks = styled.div`
|
||||
@media only screen and (max-width: ${theming.size.screen_size1}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledImg = styled.img`
|
||||
height: 2rem;
|
||||
margin: 1rem;
|
||||
`
|
||||
|
||||
const StyledLink = styled(Link)`
|
||||
${theming.styles.navbarButtonStyle}
|
||||
`
|
||||
|
||||
const StyledALink = styled.a`
|
||||
${theming.styles.navbarButtonStyle}
|
||||
`
|
||||
|
||||
function Navbar() {
|
||||
return (
|
||||
<StyledNav>
|
||||
<Link to="/">
|
||||
<StyledImg
|
||||
src={process.env.PUBLIC_URL + "/icon/icon_circle.svg"}
|
||||
/>
|
||||
</Link>
|
||||
<StyledNavLinks>
|
||||
{NavbarData.map((item, index) => {
|
||||
return (
|
||||
<StyledLink key={index} to={item.path}>
|
||||
{item.title}
|
||||
</StyledLink>
|
||||
)
|
||||
})}
|
||||
</StyledNavLinks>
|
||||
|
||||
<ThemeToggleButton />
|
||||
<LanguageToggleButton />
|
||||
|
||||
<StyledALink
|
||||
data-tip
|
||||
data-for="github"
|
||||
href="https://github.com/developomp/developomp-site"
|
||||
target="_blank"
|
||||
>
|
||||
<FontAwesomeIcon icon={faGithub} />
|
||||
</StyledALink>
|
||||
<ReactTooltip id="github" type="dark" effect="solid">
|
||||
<span>View source code</span>
|
||||
</ReactTooltip>
|
||||
|
||||
<SearchBox />
|
||||
<Sidebar />
|
||||
</StyledNav>
|
||||
)
|
||||
}
|
||||
|
||||
export default Navbar
|
||||
|
|
|
@ -1,54 +1,60 @@
|
|||
import React from "react"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faSearch } from "@fortawesome/free-solid-svg-icons"
|
||||
import styled from "styled-components"
|
||||
import theming from "../theming"
|
||||
|
||||
const StyledSearchBoxContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "white",
|
||||
dark: "#202225",
|
||||
})};
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "black",
|
||||
dark: "#CFD0D0",
|
||||
})};
|
||||
|
||||
&:hover {
|
||||
export default class Navbar extends React.Component {
|
||||
StyledSearchBoxContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
margin: 0.5rem;
|
||||
align-items: center;
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "whitesmoke",
|
||||
dark: "#36393F",
|
||||
light: "white",
|
||||
dark: "#2F3136",
|
||||
})};
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "black",
|
||||
dark: "#CFD0D0",
|
||||
})};
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "whitesmoke",
|
||||
dark: "#36393F",
|
||||
})};
|
||||
}
|
||||
`
|
||||
|
||||
StyledSearchBox = styled.input`
|
||||
width: 80%;
|
||||
border: none;
|
||||
border-right: 1rem;
|
||||
outline: none;
|
||||
padding: 10px 10px;
|
||||
text-decoration: none;
|
||||
background-color: inherit;
|
||||
color: inherit;
|
||||
`
|
||||
|
||||
StyledSearchButton = styled(FontAwesomeIcon)`
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
render() {
|
||||
return (
|
||||
<this.StyledSearchBoxContainer>
|
||||
<this.StyledSearchBox
|
||||
type="text"
|
||||
name="search"
|
||||
placeholder="Search"
|
||||
/>
|
||||
<this.StyledSearchButton icon={faSearch} />
|
||||
</this.StyledSearchBoxContainer>
|
||||
)
|
||||
}
|
||||
`
|
||||
|
||||
const StyledSearchBox = styled.input`
|
||||
width: 80%;
|
||||
border: none;
|
||||
border-right: 1rem;
|
||||
outline: none;
|
||||
padding: 10px 10px;
|
||||
text-decoration: none;
|
||||
background-color: inherit;
|
||||
color: inherit;
|
||||
`
|
||||
|
||||
const StyledSearchButton = styled(FontAwesomeIcon)`
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
function Navbar() {
|
||||
return (
|
||||
<StyledSearchBoxContainer>
|
||||
<StyledSearchBox type="text" name="search" placeholder="Search" />
|
||||
<StyledSearchButton icon={faSearch} />
|
||||
</StyledSearchBoxContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Navbar
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useState } from "react"
|
||||
import React from "react"
|
||||
import styled, { css } from "styled-components"
|
||||
import NavbarData from "../data/NavbarData"
|
||||
import NavbarData, { Item } from "../data/NavbarData"
|
||||
import ReactTooltip from "react-tooltip"
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
|
@ -9,117 +9,130 @@ import { faEllipsisV, faTimes } from "@fortawesome/free-solid-svg-icons"
|
|||
import theming from "../theming"
|
||||
import SubMenu from "./SubMenu"
|
||||
|
||||
interface StateProps {
|
||||
interface SidebarProps {}
|
||||
interface SidebarState {
|
||||
isSidebarOpen: boolean
|
||||
}
|
||||
|
||||
const CommonSidebarToggleButtonStyle = css`
|
||||
${theming.styles.navbarButtonStyle}
|
||||
font-size: "1.5rem";
|
||||
width: 1.5rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
margin: 0.1rem;
|
||||
@media only screen and (min-width: ${theming.size.screen_size1}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledToggleSidebarButton = styled.div`
|
||||
${CommonSidebarToggleButtonStyle}
|
||||
`
|
||||
|
||||
const StyledToggleSidebarButton2 = styled.div`
|
||||
${CommonSidebarToggleButtonStyle}
|
||||
border-radius: 0;
|
||||
margin: auto;
|
||||
width: 90%;
|
||||
height: 2rem;
|
||||
font-size: 1.1rem;
|
||||
`
|
||||
|
||||
const StyledOverlay = styled.div<StateProps>`
|
||||
display: ${(props) => (props.isSidebarOpen ? "block" : "none")};
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 20;
|
||||
transition-property: opacity;
|
||||
background-color: rgba(0, 0, 0, 25%);
|
||||
|
||||
* {
|
||||
overflow: scroll;
|
||||
}
|
||||
`
|
||||
|
||||
const SidebarNav = styled.nav<StateProps>`
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: ${(props) => (props.isSidebarOpen ? "0" : "-100%")};
|
||||
transition: 350ms;
|
||||
z-index: 30;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.backgroundColor0,
|
||||
dark: theming.dark.backgroundColor0,
|
||||
})};
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color0,
|
||||
dark: theming.dark.color0,
|
||||
})};
|
||||
`
|
||||
|
||||
const SidebarWrap = styled.div`
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const Sidebar = () => {
|
||||
const [isSidebarOpen, setSidebar] = useState(false)
|
||||
|
||||
function toggleSidebar() {
|
||||
setSidebar(!isSidebarOpen)
|
||||
document.body.style.overflow = isSidebarOpen ? "scroll" : "hidden"
|
||||
export default class Sidebar extends React.Component<
|
||||
SidebarProps,
|
||||
SidebarState
|
||||
> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
isSidebarOpen: false,
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledOverlay
|
||||
isSidebarOpen={isSidebarOpen}
|
||||
onClick={toggleSidebar}
|
||||
/>
|
||||
// for some reason this.setState only works if this is an arrow function
|
||||
toggleSidebar = () => {
|
||||
this.setState({ isSidebarOpen: !this.state.isSidebarOpen })
|
||||
document.body.style.overflow = this.state.isSidebarOpen
|
||||
? "scroll"
|
||||
: "hidden"
|
||||
}
|
||||
|
||||
<StyledToggleSidebarButton
|
||||
data-tip
|
||||
data-for="sidebar"
|
||||
onClick={toggleSidebar}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEllipsisV}></FontAwesomeIcon>
|
||||
<ReactTooltip id="sidebar" type="dark" effect="solid">
|
||||
<span>open sidebar</span>
|
||||
</ReactTooltip>
|
||||
</StyledToggleSidebarButton>
|
||||
CommonSidebarToggleButtonStyle = css`
|
||||
${theming.styles.navbarButtonStyle}
|
||||
width: 1.5rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
@media only screen and (min-width: ${theming.size.screen_size1}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
<SidebarNav isSidebarOpen={isSidebarOpen}>
|
||||
<SidebarWrap>
|
||||
<StyledToggleSidebarButton2 onClick={toggleSidebar}>
|
||||
<FontAwesomeIcon icon={faTimes}></FontAwesomeIcon> Close
|
||||
</StyledToggleSidebarButton2>
|
||||
{NavbarData.map((item, index) => {
|
||||
return <SubMenu item={item} key={index} />
|
||||
})}
|
||||
</SidebarWrap>
|
||||
</SidebarNav>
|
||||
</>
|
||||
)
|
||||
StyledToggleSidebarButton = styled.div`
|
||||
${this.CommonSidebarToggleButtonStyle}
|
||||
`
|
||||
|
||||
StyledToggleSidebarButton2 = styled.div`
|
||||
${this.CommonSidebarToggleButtonStyle}
|
||||
border-radius: 0;
|
||||
margin: auto;
|
||||
width: 90%;
|
||||
height: 2rem;
|
||||
font-size: 1.1rem;
|
||||
`
|
||||
|
||||
StyledOverlay = styled.div<SidebarState>`
|
||||
display: ${(props) => (props.isSidebarOpen ? "block" : "none")};
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 20;
|
||||
transition-property: opacity;
|
||||
background-color: rgba(0, 0, 0, 25%);
|
||||
|
||||
* {
|
||||
overflow: scroll;
|
||||
}
|
||||
`
|
||||
|
||||
SidebarNav = styled.nav<SidebarState>`
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: ${(props) => (props.isSidebarOpen ? "0" : "-100%")};
|
||||
transition: 350ms;
|
||||
z-index: 30;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
background-color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.backgroundColor0,
|
||||
dark: theming.dark.backgroundColor0,
|
||||
})};
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: theming.light.color0,
|
||||
dark: theming.dark.color0,
|
||||
})};
|
||||
`
|
||||
|
||||
SidebarWrap = styled.div`
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<this.StyledOverlay
|
||||
isSidebarOpen={this.state.isSidebarOpen}
|
||||
onClick={this.toggleSidebar}
|
||||
/>
|
||||
|
||||
<this.StyledToggleSidebarButton
|
||||
data-tip
|
||||
data-for="sidebar"
|
||||
onClick={this.toggleSidebar}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEllipsisV}></FontAwesomeIcon>
|
||||
<ReactTooltip id="sidebar" type="dark" effect="solid">
|
||||
<span>open sidebar</span>
|
||||
</ReactTooltip>
|
||||
</this.StyledToggleSidebarButton>
|
||||
|
||||
<this.SidebarNav isSidebarOpen={this.state.isSidebarOpen}>
|
||||
<this.SidebarWrap>
|
||||
<this.StyledToggleSidebarButton2
|
||||
onClick={this.toggleSidebar}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTimes}></FontAwesomeIcon>{" "}
|
||||
Close
|
||||
</this.StyledToggleSidebarButton2>
|
||||
{NavbarData.map((item: Item, index) => {
|
||||
return <SubMenu item={item} key={index} />
|
||||
})}
|
||||
</this.SidebarWrap>
|
||||
</this.SidebarNav>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Sidebar
|
||||
|
|
|
@ -2,115 +2,116 @@
|
|||
* inspired by https://github.com/dmitrymorozoff/react-spinners-kit/tree/master/src/components/whisper
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
import styled, { keyframes } from "styled-components"
|
||||
|
||||
const motion = keyframes`
|
||||
0% {
|
||||
transform: scale(1, 1);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: scale(0, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
`
|
||||
|
||||
const spin = keyframes`
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
75% {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
`
|
||||
|
||||
interface BallInterface {
|
||||
color: string
|
||||
size: number
|
||||
key: string
|
||||
index: number
|
||||
}
|
||||
|
||||
const Ball = styled.div<BallInterface>`
|
||||
float: left;
|
||||
clear: right;
|
||||
margin: ${(props) => props.size / 15}px;
|
||||
width: ${(props) => props.size / 5}px;
|
||||
height: ${(props) => props.size / 5}px;
|
||||
border-radius: 2px;
|
||||
background-color: ${(props) => props.color};
|
||||
animation-name: ${motion};
|
||||
animation-direction: alternate;
|
||||
animation-duration: 800ms;
|
||||
animation-iteration-count: infinite;
|
||||
&:nth-child(1) {
|
||||
animation-delay: 200ms;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
animation-delay: 400ms;
|
||||
}
|
||||
&:nth-child(3) {
|
||||
animation-delay: 600ms;
|
||||
}
|
||||
&:nth-child(4) {
|
||||
animation-delay: 400ms;
|
||||
}
|
||||
&:nth-child(5) {
|
||||
animation-delay: 600ms;
|
||||
}
|
||||
&:nth-child(6) {
|
||||
animation-delay: 800ms;
|
||||
}
|
||||
&:nth-child(7) {
|
||||
animation-delay: 600ms;
|
||||
}
|
||||
&:nth-child(8) {
|
||||
animation-delay: 800ms;
|
||||
}
|
||||
&:nth-child(9) {
|
||||
animation-delay: 1000ms;
|
||||
}
|
||||
`
|
||||
|
||||
interface WrapperInterface {
|
||||
size: number
|
||||
}
|
||||
|
||||
const Wrapper = styled.div<WrapperInterface>`
|
||||
margin: 5rem auto 0 auto;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
animation: ${spin} 10s infinite;
|
||||
`
|
||||
interface SpinnerProps {
|
||||
size: number
|
||||
}
|
||||
|
||||
function Spinner({ size, color }) {
|
||||
const balls: unknown[] = []
|
||||
let keyValue = 0
|
||||
const countBallsInLine = 3
|
||||
for (let i = 0; i < countBallsInLine; i++) {
|
||||
for (let j = 0; j < countBallsInLine; j++) {
|
||||
balls.push(
|
||||
<Ball
|
||||
color={color}
|
||||
size={size}
|
||||
key={keyValue.toString()}
|
||||
index={keyValue}
|
||||
/>
|
||||
)
|
||||
keyValue++
|
||||
export default class Spinner extends React.Component<SpinnerProps> {
|
||||
motion = keyframes`
|
||||
from {
|
||||
transform: scale(1, 1);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: scale(0, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
`
|
||||
|
||||
spin = keyframes`
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
`
|
||||
|
||||
Ball = styled.div<BallInterface>`
|
||||
float: left;
|
||||
clear: right;
|
||||
margin: ${(props) => props.size / 15}px;
|
||||
width: ${(props) => props.size / 5}px;
|
||||
height: ${(props) => props.size / 5}px;
|
||||
border-radius: 2px;
|
||||
background-color: lightgrey;
|
||||
animation-name: ${this.motion};
|
||||
animation-direction: alternate;
|
||||
animation-duration: 800ms;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
|
||||
/* use for loops here? */
|
||||
&:nth-child(1) {
|
||||
animation-delay: 200ms;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
animation-delay: 400ms;
|
||||
}
|
||||
&:nth-child(3) {
|
||||
animation-delay: 600ms;
|
||||
}
|
||||
&:nth-child(4) {
|
||||
animation-delay: 400ms;
|
||||
}
|
||||
&:nth-child(5) {
|
||||
animation-delay: 600ms;
|
||||
}
|
||||
&:nth-child(6) {
|
||||
animation-delay: 800ms;
|
||||
}
|
||||
&:nth-child(7) {
|
||||
animation-delay: 600ms;
|
||||
}
|
||||
&:nth-child(8) {
|
||||
animation-delay: 800ms;
|
||||
}
|
||||
&:nth-child(9) {
|
||||
animation-delay: 1000ms;
|
||||
}
|
||||
`
|
||||
|
||||
Wrapper = styled.div<WrapperInterface>`
|
||||
margin: 5rem auto 0 auto;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
animation-timing-function: linear;
|
||||
animation: ${this.spin} 10s infinite;
|
||||
`
|
||||
|
||||
balls: unknown[] = []
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
let keyValue = 0
|
||||
const countBallsInLine = 3
|
||||
for (let i = 0; i < countBallsInLine; i++) {
|
||||
for (let j = 0; j < countBallsInLine; j++) {
|
||||
this.balls.push(
|
||||
<this.Ball
|
||||
size={this.props.size}
|
||||
key={keyValue.toString()}
|
||||
index={keyValue}
|
||||
/>
|
||||
)
|
||||
keyValue++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <Wrapper size={size}>{balls}</Wrapper>
|
||||
render() {
|
||||
return <this.Wrapper size={this.props.size}>{this.balls}</this.Wrapper>
|
||||
}
|
||||
}
|
||||
|
||||
export default Spinner
|
||||
|
|
|
@ -1,74 +1,100 @@
|
|||
import { useState } from "react"
|
||||
import React from "react"
|
||||
import { Link } from "react-router-dom"
|
||||
import styled from "styled-components"
|
||||
import theming from "../theming"
|
||||
import { Item } from "../data/NavbarData"
|
||||
|
||||
const SidebarLink = styled(Link)`
|
||||
${theming.styles.navbarButtonStyle};
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
justify-content: space-between;
|
||||
height: 2rem;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
list-style: none;
|
||||
`
|
||||
|
||||
const SidebarLabel = styled.span`
|
||||
margin-left: 16px;
|
||||
`
|
||||
|
||||
const DropdownLink = styled(Link)`
|
||||
background: #414757;
|
||||
height: 60px;
|
||||
padding-left: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
color: #f5f5f5;
|
||||
font-size: 18px;
|
||||
|
||||
&:hover {
|
||||
background: #632ce4;
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
function SubMenu({ item }) {
|
||||
const [isSubNavOpen, setSubNav] = useState(false)
|
||||
|
||||
const showSubNav = () => setSubNav(!isSubNavOpen)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SidebarLink to={item.path} onClick={item.subNav && showSubNav}>
|
||||
<div>
|
||||
{item.icon}
|
||||
<SidebarLabel>{item.title}</SidebarLabel>
|
||||
</div>
|
||||
<div>
|
||||
{item.subNav && isSubNavOpen
|
||||
? item.iconOpened
|
||||
: item.subNav
|
||||
? item.iconClosed
|
||||
: null}
|
||||
</div>
|
||||
</SidebarLink>
|
||||
|
||||
{/* not used as of the moment */}
|
||||
{isSubNavOpen &&
|
||||
item.subNav.map((item, index) => {
|
||||
return (
|
||||
<DropdownLink to={item.path} key={index}>
|
||||
{item.icon}
|
||||
<SidebarLabel>{item.title}</SidebarLabel>
|
||||
</DropdownLink>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
interface SubMenuProps {
|
||||
item: Item
|
||||
}
|
||||
|
||||
export default SubMenu
|
||||
interface SubMenuState {
|
||||
isSubNavOpen: boolean
|
||||
}
|
||||
|
||||
export default class SubMenu extends React.Component<
|
||||
SubMenuProps,
|
||||
SubMenuState
|
||||
> {
|
||||
SidebarLink = styled(Link)`
|
||||
${theming.styles.navbarButtonStyle};
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
justify-content: space-between;
|
||||
height: 2rem;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
list-style: none;
|
||||
`
|
||||
|
||||
SidebarLabel = styled.span`
|
||||
margin-left: 16px;
|
||||
`
|
||||
|
||||
DropdownLink = styled(Link)`
|
||||
background: #414757;
|
||||
height: 60px;
|
||||
padding-left: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
color: #f5f5f5;
|
||||
font-size: 18px;
|
||||
|
||||
&:hover {
|
||||
background: #632ce4;
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
isSubNavOpen: false,
|
||||
}
|
||||
}
|
||||
|
||||
showSubNav = () => this.setState({ isSubNavOpen: !this.state.isSubNavOpen })
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<this.SidebarLink
|
||||
to={this.props.item.path}
|
||||
onClick={this.props.item.subNav && this.showSubNav}
|
||||
>
|
||||
<div>
|
||||
{this.props.item.icon}
|
||||
<this.SidebarLabel>
|
||||
{this.props.item.title}
|
||||
</this.SidebarLabel>
|
||||
</div>
|
||||
<div>
|
||||
{this.props.item.subNav && this.state.isSubNavOpen
|
||||
? this.props.item.iconOpened
|
||||
: this.props.item.subNav
|
||||
? this.props.item.iconClosed
|
||||
: null}
|
||||
</div>
|
||||
</this.SidebarLink>
|
||||
|
||||
{/* not used as of the moment */}
|
||||
{this.state.isSubNavOpen && // check if subNav is open
|
||||
this.props.item.subNav && // check if subNav exists in that item
|
||||
this.props.item.subNav.map((item, index) => {
|
||||
// shows all items in subNav
|
||||
return (
|
||||
<this.DropdownLink to={item.path} key={index}>
|
||||
{item.icon}
|
||||
<this.SidebarLabel>
|
||||
{item.title}
|
||||
</this.SidebarLabel>
|
||||
</this.DropdownLink>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import React from "react"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faMoon, faSun } from "@fortawesome/free-solid-svg-icons"
|
||||
import styled, { ThemeConsumer } from "styled-components"
|
||||
|
@ -6,45 +7,46 @@ import ReactTooltip from "react-tooltip"
|
|||
|
||||
import theming from "../theming"
|
||||
|
||||
const StyledThemeButton = styled.div`
|
||||
${theming.styles.navbarButtonStyle}
|
||||
${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "",
|
||||
dark: "transform: scaleX(-1);\
|
||||
export default class Navbar extends React.Component {
|
||||
StyledThemeButton = styled.div`
|
||||
${theming.styles.navbarButtonStyle}
|
||||
${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "",
|
||||
dark: "transform: scaleX(-1);\
|
||||
-moz-transform: scaleX(-1);\
|
||||
-webkit-transform: scaleX(-1);\
|
||||
-ms-transform: scaleX(-1);",
|
||||
})};
|
||||
`
|
||||
|
||||
function Navbar() {
|
||||
return (
|
||||
<ThemeConsumer>
|
||||
{({ currentTheme, setTheme }) => (
|
||||
<>
|
||||
<StyledThemeButton
|
||||
data-tip
|
||||
data-for="theme"
|
||||
className="right"
|
||||
onClick={() =>
|
||||
setTheme(currentTheme === "dark" ? "light" : "dark")
|
||||
}
|
||||
>
|
||||
{currentTheme == "dark" && (
|
||||
<FontAwesomeIcon icon={faMoon} />
|
||||
)}
|
||||
{currentTheme == "light" && (
|
||||
<FontAwesomeIcon icon={faSun} />
|
||||
)}
|
||||
</StyledThemeButton>
|
||||
<ReactTooltip id="theme" type="dark" effect="solid">
|
||||
<span>Using {currentTheme} theme</span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
)}
|
||||
</ThemeConsumer>
|
||||
)
|
||||
})};
|
||||
`
|
||||
render() {
|
||||
return (
|
||||
<ThemeConsumer>
|
||||
{({ currentTheme, setTheme }) => (
|
||||
<>
|
||||
<this.StyledThemeButton
|
||||
data-tip
|
||||
data-for="theme"
|
||||
className="right"
|
||||
onClick={() =>
|
||||
setTheme(
|
||||
currentTheme === "dark" ? "light" : "dark"
|
||||
)
|
||||
}
|
||||
>
|
||||
{currentTheme == "dark" && (
|
||||
<FontAwesomeIcon icon={faMoon} />
|
||||
)}
|
||||
{currentTheme == "light" && (
|
||||
<FontAwesomeIcon icon={faSun} />
|
||||
)}
|
||||
</this.StyledThemeButton>
|
||||
<ReactTooltip id="theme" type="dark" effect="solid">
|
||||
<span>Using {currentTheme} theme</span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
)}
|
||||
</ThemeConsumer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Navbar
|
||||
|
|
|
@ -8,7 +8,17 @@ import {
|
|||
faHiking,
|
||||
} from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
export default [
|
||||
// item from sidebar data
|
||||
export type Item = {
|
||||
path: string
|
||||
subNav?: Array<Item>
|
||||
icon: JSX.Element
|
||||
title: string
|
||||
iconOpened?: JSX.Element
|
||||
iconClosed?: JSX.Element
|
||||
}
|
||||
|
||||
const NavbarData: Array<Item> = [
|
||||
{
|
||||
title: "Home",
|
||||
path: "/",
|
||||
|
@ -40,3 +50,5 @@ export default [
|
|||
icon: <FontAwesomeIcon icon={faUserTie} />,
|
||||
},
|
||||
]
|
||||
|
||||
export default NavbarData
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import React from "react"
|
||||
import { Link } from "react-router-dom"
|
||||
import styled from "styled-components"
|
||||
import theming from "../theming"
|
||||
|
@ -6,115 +7,131 @@ import { Helmet } from "react-helmet-async"
|
|||
|
||||
import pages from "../pages.json"
|
||||
|
||||
const StyledPostList = styled.div`
|
||||
padding-top: 2rem;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "#111111",
|
||||
dark: "#EEEEEE",
|
||||
})};
|
||||
`
|
||||
|
||||
const StyledH1 = styled.h1`
|
||||
margin-bottom: 20px;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
`
|
||||
|
||||
const StyledTitle = styled.h1`
|
||||
font-size: 2rem;
|
||||
font-style: bold;
|
||||
`
|
||||
|
||||
const StyledLink = styled(Link)`
|
||||
text-decoration: none;
|
||||
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "black",
|
||||
dark: "white",
|
||||
})};
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledPostCard = styled.div`
|
||||
box-shadow: 0 4px 10px rgb(0 0 0 / 10%);
|
||||
text-align: left;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px 20px;
|
||||
`
|
||||
|
||||
function Home(props) {
|
||||
const PostCards: Array<unknown> = []
|
||||
let howMany = props.howMany
|
||||
const isLimited = Boolean(howMany)
|
||||
|
||||
let h1Text = "All posts"
|
||||
if (isLimited) {
|
||||
h1Text = `${howMany} recent posts`
|
||||
}
|
||||
|
||||
for (const pagePath in pages) {
|
||||
if (isLimited && howMany <= 0) continue
|
||||
|
||||
const post = pages[pagePath]
|
||||
post.title = post.meta?.title ? post.meta.title : "Unknown title"
|
||||
post.date = post.meta?.date ? post.meta.date : "Unknown date"
|
||||
post.author = post.meta?.author ? post.meta.author : "Unknown author"
|
||||
|
||||
PostCards.push(
|
||||
<StyledPostCard key={pagePath} className="card main-content">
|
||||
<StyledTitle>
|
||||
<StyledLink to={pagePath}>{post.title}</StyledLink>
|
||||
</StyledTitle>
|
||||
<small>
|
||||
Published on {post.date} by {post.author}
|
||||
</small>
|
||||
<hr />
|
||||
<div
|
||||
className="link-color"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: marked(
|
||||
post.content.split(" ").slice(0, 20).join(" ") +
|
||||
"..."
|
||||
),
|
||||
}}
|
||||
></div>
|
||||
<small>
|
||||
<StyledLink to={pagePath}>Read more</StyledLink>
|
||||
</small>
|
||||
</StyledPostCard>
|
||||
)
|
||||
howMany--
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>pomp | {props.title}</title>
|
||||
|
||||
<meta property="og:title" content={props.title} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="http://developomp.com" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="http://developomp.com/icon/icon.svg"
|
||||
/>
|
||||
<meta property="og:description" content="" />
|
||||
</Helmet>
|
||||
|
||||
<StyledPostList>
|
||||
<StyledH1>{h1Text}</StyledH1>
|
||||
<br />
|
||||
{PostCards}
|
||||
</StyledPostList>
|
||||
</>
|
||||
)
|
||||
interface HomeProps {
|
||||
title?: string
|
||||
howMany?: number
|
||||
}
|
||||
|
||||
export default Home
|
||||
export default class Home extends React.Component<HomeProps, { lol: boolean }> {
|
||||
h1Text: string
|
||||
PostCards: Array<unknown> = []
|
||||
|
||||
StyledPostList = styled.div`
|
||||
padding-top: 2rem;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "#111111",
|
||||
dark: "#EEEEEE",
|
||||
})};
|
||||
`
|
||||
|
||||
StyledH1 = styled.h1`
|
||||
margin-bottom: 20px;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
`
|
||||
|
||||
StyledTitle = styled.h1`
|
||||
font-size: 2rem;
|
||||
font-style: bold;
|
||||
`
|
||||
|
||||
StyledLink = styled(Link)`
|
||||
text-decoration: none;
|
||||
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "black",
|
||||
dark: "white",
|
||||
})};
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`
|
||||
|
||||
StyledPostCard = styled.div`
|
||||
box-shadow: 0 4px 10px rgb(0 0 0 / 10%);
|
||||
text-align: left;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px 20px;
|
||||
`
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
let howMany = props.howMany
|
||||
const isLimited = Boolean(howMany)
|
||||
|
||||
this.h1Text = isLimited ? "All posts" : `${howMany} recent posts`
|
||||
|
||||
for (const pagePath in pages) {
|
||||
if (isLimited && howMany <= 0) continue
|
||||
|
||||
const post = pages[pagePath]
|
||||
post.title = post.meta?.title ? post.meta.title : "Unknown title"
|
||||
post.date = post.meta?.date ? post.meta.date : "Unknown date"
|
||||
post.author = post.meta?.author
|
||||
? post.meta.author
|
||||
: "Unknown author"
|
||||
|
||||
this.PostCards.push(
|
||||
<this.StyledPostCard
|
||||
key={pagePath}
|
||||
className="card main-content"
|
||||
>
|
||||
<this.StyledTitle>
|
||||
<this.StyledLink to={pagePath}>
|
||||
{post.title}
|
||||
</this.StyledLink>
|
||||
</this.StyledTitle>
|
||||
<small>
|
||||
Published on {post.date} by {post.author}
|
||||
</small>
|
||||
<hr />
|
||||
<div
|
||||
className="link-color"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: marked(
|
||||
post.content.split(" ").slice(0, 20).join(" ") +
|
||||
"..."
|
||||
),
|
||||
}}
|
||||
></div>
|
||||
<small>
|
||||
<this.StyledLink to={pagePath}>
|
||||
Read more
|
||||
</this.StyledLink>
|
||||
</small>
|
||||
</this.StyledPostCard>
|
||||
)
|
||||
howMany--
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>pomp | {this.props.title}</title>
|
||||
|
||||
<meta property="og:title" content={this.props.title} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="http://developomp.com" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="http://developomp.com/icon/icon.svg"
|
||||
/>
|
||||
<meta property="og:description" content="" />
|
||||
</Helmet>
|
||||
|
||||
<this.StyledPostList>
|
||||
<this.StyledH1>{this.h1Text}</this.StyledH1>
|
||||
<br />
|
||||
{this.PostCards}
|
||||
</this.StyledPostList>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +1,47 @@
|
|||
import React from "react"
|
||||
import styled from "styled-components"
|
||||
import theming from "../theming"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
|
||||
const StyledNotFound = styled.div`
|
||||
margin: auto;
|
||||
margin-top: 2rem;
|
||||
text-align: center;
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "#111111",
|
||||
dark: "#EEEEEE",
|
||||
})};
|
||||
`
|
||||
export default class NotFound extends React.Component {
|
||||
StyledNotFound = styled.div`
|
||||
margin: auto;
|
||||
margin-top: 2rem;
|
||||
text-align: center;
|
||||
color: ${(props) =>
|
||||
theming.theme(props.theme.currentTheme, {
|
||||
light: "#111111",
|
||||
dark: "#EEEEEE",
|
||||
})};
|
||||
`
|
||||
|
||||
const Styled404 = styled.h1`
|
||||
font-size: 3rem;
|
||||
`
|
||||
Styled404 = styled.h1`
|
||||
font-size: 3rem;
|
||||
`
|
||||
|
||||
function NotFound() {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>pomp | 404</title>
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>pomp | 404</title>
|
||||
|
||||
<meta property="og:title" content="Page Not Found" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="http://developomp.com" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="http://developomp.com/icon/icon.svg"
|
||||
/>
|
||||
<meta property="og:description" content="Page does not exist" />
|
||||
</Helmet>
|
||||
<StyledNotFound className="card main-content">
|
||||
<Styled404>404</Styled404>
|
||||
the page you are looking for does not exist. :(
|
||||
</StyledNotFound>
|
||||
</>
|
||||
)
|
||||
<meta property="og:title" content="Page Not Found" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="http://developomp.com" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="http://developomp.com/icon/icon.svg"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Page does not exist"
|
||||
/>
|
||||
</Helmet>
|
||||
<this.StyledNotFound className="card main-content">
|
||||
<this.Styled404>404</this.Styled404>
|
||||
the page you are looking for does not exist. :(
|
||||
</this.StyledNotFound>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default NotFound
|
||||
|
|
|
@ -1,71 +1,83 @@
|
|||
import React from "react"
|
||||
import marked from "marked"
|
||||
import NotFound from "./notfound"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
|
||||
import pages from "../pages.json"
|
||||
import { useParams } from "react-router-dom"
|
||||
|
||||
function Page() {
|
||||
const path = `/${useParams().path}`
|
||||
const fetched = pages[path]
|
||||
if (!fetched) return <NotFound />
|
||||
export default class Page extends React.Component {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fetched: any
|
||||
|
||||
// to prevent wrapping. I don't want to touch prettier stuff
|
||||
const idk = "Unknown"
|
||||
fetched.content = fetched?.content ? fetched.content : "No content"
|
||||
fetched.toc = fetched.meta?.toc ? fetched.meta.toc : undefined
|
||||
fetched.title = fetched.meta?.title ? fetched.meta.title : "No title"
|
||||
fetched.date = fetched.meta?.date ? fetched.meta.date : `${idk} date`
|
||||
fetched.author = fetched.meta?.author
|
||||
? fetched.meta.author
|
||||
: `${idk} author`
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
const TableOfContents = fetched.toc && (
|
||||
<>
|
||||
<div className="card">
|
||||
<strong>Table of Content:</strong>
|
||||
<div
|
||||
className="link-color"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: marked(fetched.toc),
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<hr />
|
||||
</>
|
||||
) // add toc if it exists
|
||||
const fetched = pages[props.location.pathname]
|
||||
if (!fetched) return
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>pomp | {fetched.title}</title>
|
||||
fetched.content = fetched?.content ? fetched.content : "No content"
|
||||
fetched.toc = fetched.meta?.toc ? fetched.meta.toc : undefined
|
||||
fetched.title = fetched.meta?.title ? fetched.meta.title : "No title"
|
||||
fetched.date = fetched.meta?.date ? fetched.meta.date : "Unknown date"
|
||||
fetched.author = fetched.meta?.author
|
||||
? fetched.meta.author
|
||||
: "Unknown author"
|
||||
|
||||
<meta property="og:title" content="Page Not Found" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="http://developomp.com" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="http://developomp.com/icon/icon.svg"
|
||||
/>
|
||||
<meta property="og:description" content="Page does not exist" />
|
||||
</Helmet>
|
||||
this.fetched = fetched
|
||||
}
|
||||
|
||||
<div className="card main-content">
|
||||
<h2>{fetched.title}</h2>
|
||||
<small>
|
||||
Published on {fetched.date} by {fetched.author}
|
||||
</small>
|
||||
<hr />
|
||||
{TableOfContents}
|
||||
<div
|
||||
className="link-color"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: marked(fetched.content),
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
render() {
|
||||
if (!this.fetched) return <NotFound />
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>pomp | {this.fetched.title}</title>
|
||||
|
||||
<meta property="og:title" content="Page Not Found" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="http://developomp.com" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="http://developomp.com/icon/icon.svg"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Page does not exist"
|
||||
/>
|
||||
</Helmet>
|
||||
|
||||
<div className="card main-content">
|
||||
<h2>{this.fetched.title}</h2>
|
||||
<small>
|
||||
Published on {this.fetched.date} by{" "}
|
||||
{this.fetched.author}
|
||||
</small>
|
||||
<hr />
|
||||
{
|
||||
this.fetched.toc && (
|
||||
<>
|
||||
<div className="card">
|
||||
<strong>Table of Content:</strong>
|
||||
<div
|
||||
className="link-color"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: marked(this.fetched.toc),
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<hr />
|
||||
</>
|
||||
) // add toc if it exists
|
||||
}
|
||||
<div
|
||||
className="link-color"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: marked(this.fetched.content),
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Page
|
||||
|
|
|
@ -1,36 +1,41 @@
|
|||
import React from "react"
|
||||
|
||||
import styled from "styled-components"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
|
||||
const StyledPortfolio = styled.div``
|
||||
export default class Portfolio extends React.Component {
|
||||
StyledPortfolio = styled.div``
|
||||
|
||||
const StyledH1 = styled.h1`
|
||||
font-size: 3rem;
|
||||
`
|
||||
StyledH1 = styled.h1`
|
||||
font-size: 3rem;
|
||||
`
|
||||
|
||||
function Portfolio() {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>pomp | Portfolio</title>
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>pomp | Portfolio</title>
|
||||
|
||||
<meta property="og:title" content="developomp's Portfolio" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="http://developomp.com" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="http://developomp.com/icon/icon.svg"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="developomp's Portfolio"
|
||||
/>
|
||||
</Helmet>
|
||||
<StyledPortfolio className="card main-content">
|
||||
<StyledH1>Portfolio</StyledH1>
|
||||
My projects
|
||||
</StyledPortfolio>
|
||||
</>
|
||||
)
|
||||
<meta
|
||||
property="og:title"
|
||||
content="developomp's Portfolio"
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="http://developomp.com" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="http://developomp.com/icon/icon.svg"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="developomp's Portfolio"
|
||||
/>
|
||||
</Helmet>
|
||||
<this.StyledPortfolio className="card main-content">
|
||||
<this.StyledH1>Portfolio</this.StyledH1>
|
||||
My projects
|
||||
</this.StyledPortfolio>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Portfolio
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue