Update dependencies and add blog

This commit is contained in:
Corentin 2021-10-07 09:02:43 +09:00
commit 7a8fb3449d
22 changed files with 948 additions and 332 deletions

View file

@ -1,24 +1,24 @@
export const themeActions = {
THEME_CHANGE: 'THEME_CHANGE'
CHANGE: 'THEME_CHANGE'
}
export const changeTheme = (new_theme) => ({
type: themeActions.THEME_CHANGE,
type: themeActions.CHANGE,
theme: new_theme
})
export const langActions = {
LANG_CHANGE_REQUEST: 'LANG_CHANGE_REQUEST',
LANG_CHANGE_RESPONSE: 'LANG_CHANGE_RESPONSE'
CHANGE_REQUEST: 'LANG_CHANGE_REQUEST',
CHANGE_RESPONSE: 'LANG_CHANGE_RESPONSE'
}
export const changeLang = (new_lang) => ({
type: langActions.LANG_CHANGE_REQUEST,
type: langActions.CHANGE_REQUEST,
lang: new_lang
})
export const receiveLang = (new_lang, strings) => ({
type: langActions.LANG_CHANGE_RESPONSE,
type: langActions.CHANGE_RESPONSE,
lang: new_lang,
strings: strings
})
@ -47,4 +47,82 @@ export const fetchLang = (new_lang) => {
dispatch(changeLang(null))
})
}
}
export const blogActions = {
LIST_REQUEST: 'BLOG_LIST_REQUEST',
LIST_RESPONSE: 'BLOG_LIST_RESPONSE',
ENTRY_REQUEST: 'BLOG_ENTRY_REQUEST',
ENTRY_RESPONSE: 'BLOG_ENTRY_RESPONSE'
}
export const blogListRequest = () => ({
type: blogActions.LIST_REQUEST,
})
export const blogListResponse = (list = []) => ({
type: blogActions.LIST_RESPONSE,
list: list
})
export const fetchBlogList = () => {
return (dispatch, getState) => {
const state = getState()
if(state.blog.list_requesting)
{
return
}
dispatch(blogListRequest())
return fetch('/blog_list.json')
.then((response) => {
if(!response.ok)
{
console.log('Couldn\'t fetch blog_list.json')
throw Error(response.statusText)
}
return response.json()
})
.then(json => {
dispatch(blogListResponse(json));
})
.catch(() => {
dispatch(blogListResponse())
})
}
}
export const blogEntryRequest = () => ({
type: blogActions.ENTRY_REQUEST,
})
export const blogEntryResponse = (name= null, entry = null) => ({
type: blogActions.ENTRY_RESPONSE,
entry: entry,
name: name
})
export const fetchBlogEntry = (entry_date, entry_name) => {
return (dispatch, getState) => {
const state = getState()
if(state.blog.entry_requesting)
{
return
}
dispatch(blogEntryRequest())
return fetch('/blog/' + entry_date + '_' + entry_name + '/' + entry_name + '.html')
.then((response) => {
if(!response.ok)
{
console.log('Couldn\'t fetch blog entry : ' + entry_name)
throw Error(response.statusText)
}
return response.text()
})
.then(text => {
dispatch(blogEntryResponse(entry_name, text))
})
.catch(() => {
dispatch(blogEntryResponse())
})
}
}

28
src/blog.css Normal file
View file

@ -0,0 +1,28 @@
.blog
{
flex-grow: 1;
width: 100%;
padding: 20px;
}
.blog > a
{
display: flex;
text-decoration: none;
max-width: 1000px;
margin: 20px auto;
padding: 10px;
background-color: var(--dim-bg-color);
}
.blog a h2
{
flex-grow: 1;
margin: 20px 60px 20px 40px;
}
.blog a .date
{
font-size: 0.7em;
align-self: end;
}

56
src/blog.jsx Normal file
View file

@ -0,0 +1,56 @@
`use strict`
import { Component } from 'inferno'
import { Link } from 'inferno-router'
import { connect } from 'inferno-redux'
import { fetchBlogList } from './actions'
import './blog.css'
class BlogComponent extends Component
{
componentDidMount()
{
if(this.props.blog_list.length === 0)
{
this.props.fetchBlogList()
}
}
render()
{
return (
<div className="blog">
{this.props.blog_list.map((blog_info, index) => {
return (
<Link to={'/blog/' + blog_info.date + '/' + encodeURIComponent(blog_info.name)} key={index}>
<h2>
{blog_info.name}
</h2>
<span className="date">
{this.props.strings.publish_date
+ new Date(blog_info.date).toLocaleDateString()}
</span>
</Link>
)
})}
</div>
)
}
}
const mapStateToProps = state => ({
strings: state.lang.strings,
blog_list: state.blog.list
})
const mapDispatchToProps = dispatch => ({
fetchBlogList: () => dispatch(fetchBlogList())
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(BlogComponent)

57
src/blog_entry.jsx Normal file
View file

@ -0,0 +1,57 @@
`use strict`
import { Component } from 'inferno'
import { connect } from 'inferno-redux'
import { fetchBlogList, fetchBlogEntry } from './actions'
import './blog_entry.scss'
class BlogEntryComponent extends Component
{
constructor(props) {
super(props)
this.entry_date = this.props.match.params.entry_date
this.entry_name = this.props.match.params.entry_name
}
componentDidMount()
{
if(this.props.blog_list.length === 0)
{
this.props.fetchBlogList()
}
if(this.props.blog_entry_name !== this.entry_name)
{
this.props.fetchBlogEntry(this.entry_date, this.entry_name)
}
}
render()
{
return (
<div className="blog-entry"
dangerouslySetInnerHTML={this.props.blog_entry_name === this.entry_name ?
{__html: this.props.blog_entry} : null}>
</div>
)
}
}
const mapStateToProps = state => ({
strings: state.lang.strings,
blog_list: state.blog.list,
blog_entry: state.blog.entry,
blog_entry_name: state.blog.entry_name
})
const mapDispatchToProps = dispatch => ({
fetchBlogList: () => dispatch(fetchBlogList()),
fetchBlogEntry: (entry_date, entry_name) => dispatch(fetchBlogEntry(entry_date, entry_name))
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(BlogEntryComponent)

81
src/blog_entry.scss Normal file
View file

@ -0,0 +1,81 @@
.blog-entry
{
flex-grow: 1;
width: 100%;
padding: 20px;
max-width: 1200px;
line-height: 1.8rem;
font-family: "open sans", sans;
h1, h2, h3
{
font-weight: lighter;
margin: 8px 0;
color: var(--highlight-fg-color);
}
h1
{
padding: 1rem 0 1.2rem 0;
}
h2
{
padding: 0.5rem 0 1rem 0;
border-bottom: 1px solid var(--dim-bg-color);
}
code, pre
{
background-color: var(--lighter-bg-color);
color: var(--lighter-fg-color);
padding: 0 4px;
}
pre
{
padding: 10px;
}
em
{
color: var(--lighter-fg-color);
}
img
{
min-height: 160px;
max-height: 50vh;
width: auto;
}
strong
{
color: var(--lighter-fg-color);
}
.image-row
{
h3
{
font-size: 0.9rem;
text-align: center;
font-weight: normal;
font-style: italic;
}
p
{
display: flex;
align-items: center;
justify-content: center;
}
img
{
margin: 0 10px;
object-fit: contain;
width: 100%;
}
}
}

50
src/home.jsx Normal file
View file

@ -0,0 +1,50 @@
`use strict`
import { Component } from 'inferno'
import { connect } from 'inferno-redux'
import { Svg } from './svg.jsx'
import ArrowSvg from '../assets/icons/arrow_forward.svg'
import Flow1Svg from '../assets/images/undraw_maintenance_cn7j.svg'
import Flow2Svg from '../assets/images/undraw_design_data_khdb.svg'
import Flow3Svg from '../assets/images/undraw_Artificial_intelligence_oyxx.svg'
class HomeComponent extends Component
{
render()
{
return (
<div className="content">
<div className="process-flow">
<div>
<Svg src={Flow1Svg}/>
<h3>{this.props.strings.flow1}</h3>
</div>
<Svg src={ArrowSvg}/>
<div>
<Svg src={Flow2Svg}/>
<h3>{this.props.strings.flow2}</h3>
</div>
<Svg src={ArrowSvg}/>
<div>
<Svg src={Flow3Svg}/>
<h3>{this.props.strings.flow3}</h3>
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => ({
strings: state.lang.strings
})
const mapDispatchToProps = dispatch => ({
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(HomeComponent)

View file

@ -3,7 +3,7 @@ import { render } from 'inferno'
import { Provider } from 'inferno-redux';
import { applyMiddleware, createStore, combineReducers } from 'redux';
import { langReducer, themeReducer } from './reducers';
import { blogReducer, langReducer, themeReducer } from './reducers';
import { fetchLang } from './actions'
import Main from './main.jsx'
@ -33,7 +33,8 @@ export const init_app = () => {
const reducers = combineReducers({
theme: themeReducer,
lang: langReducer
lang: langReducer,
blog: blogReducer
})
const store = createStore(

View file

@ -6,5 +6,10 @@
"inferno_credit": "Website built with ",
"flow1": "From repetitive tasks done by human",
"flow2": "With the help of data gathered",
"flow3": "To automated systems"
"flow3": "To automated systems",
"section": {
"home": "Home",
"blog": "Blog"
},
"publish_date": "Publish date : "
}

View file

@ -6,5 +6,10 @@
"inferno_credit": "Website built with ",
"flow1": "人によって\u200b繰り返される\u200b作業",
"flow2": "収集された\u200bデータ\u200bの助け\u200bによって",
"flow3": "システム\u200bを自動化するため"
"flow3": "システム\u200bを自動化するため",
"section": {
"home": "ホーム",
"blog": "ブログ"
},
"publish_date": "Publish date : "
}

View file

@ -1,216 +1,217 @@
*, ::after, ::before
{
box-sizing: border-box;
box-sizing: border-box;
}
a, a:visited
{
color: inherit;
color: inherit;
}
body
{
background-color: var(--main-bg-color);
color: var(--main-fg-color);
margin: 0;
font-family: sans;
background-color: var(--main-bg-color);
color: var(--main-fg-color);
margin: 0;
font-family: sans;
}
body::before {
content: "";
display: block;
position: absolute;
top: 0;
left: 0;
background-image: url(/assets/back.webp);
width: 100%;
height: 100%;
opacity: 0.15;
z-index: -1;
}
@media (max-width: 420px)
{
body
{
font-size: 10px;
}
.process-flow
{
padding: 3px 3vw;
}
.process-flow .svg:nth-child(even) > svg
{
width: 18px;
margin: 2px;
}
body
{
font-size: 10px;
}
.process-flow
{
padding: 3px 3vw;
}
.process-flow .svg:nth-child(even) > svg
{
width: 18px;
margin: 2px;
}
}
@media (min-width: 420px) and (max-width: 600px)
{
body
{
font-size: 12px;
}
.process-flow
{
padding: 5px 5vw;
}
.process-flow .svg:nth-child(even) > svg
{
width: 5vw;
margin: 5px;
}
body
{
font-size: 12px;
}
.process-flow
{
padding: 5px 5vw;
}
.process-flow .svg:nth-child(even) > svg
{
width: 5vw;
margin: 5px;
}
}
@media (min-width: 600px)
{
body
{
font-size: 14px;
}
.process-flow
{
padding: 10px 10vw;
}
.process-flow .svg:nth-child(even) > svg
{
width: 5vw;
margin: 10px;
}
body
{
font-size: 14px;
}
.process-flow
{
padding: 10px 10vw;
}
.process-flow .svg:nth-child(even) > svg
{
width: 5vw;
margin: 10px;
}
}
@media (orientation: portrait)
{
.process-flow
{
flex-direction: column;
}
.process-flow .svg
{
height: 15vh;
}
.process-flow > .svg
{
transform: rotate(90deg)
}
.process-flow
{
flex-direction: column;
}
.process-flow .svg
{
height: 15vh;
}
.process-flow > .svg
{
transform: rotate(90deg)
}
}
footer > section
{
display: flex;
justify-content: center;
align-items: center;
margin: 0 10px;
display: flex;
justify-content: center;
align-items: center;
margin: 0 10px;
}
footer .legal div
{
padding: 10px;
}
footer .credit
{
font-size: 0.8rem;
padding: 10px;
}
footer > section > div
{
word-break: keep-all;
word-break: keep-all;
}
h1, h2, h3, h4, h5, h5
{
margin: 0;
margin: 0;
color: var(--lighter-fg-color);
}
main
{
min-height: 100vh;
display: flex;
flex-direction: column;
min-width: 375px;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
min-width: 375px;
}
nav
{
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
background-color: var(--lighterbg-color);
border-bottom: 1px solid var(--highlight-bg-color);
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
padding: 10px;
background-color: var(--lighterbg-color);
border-bottom: 1px solid var(--highlight-bg-color);
word-break: keep-all;
}
nav a
{
text-decoration: none;
}
nav .actions
{
display: inline-flex;
justify-content: flex-end;
align-items: center;
flex: 1 1 0;
display: inline-flex;
justify-content: flex-end;
align-items: center;
}
nav .main-logo
{
display: inline-flex;
justify-content: center;
align-items: center;
display: inline-flex;
justify-content: center;
align-items: center;
}
nav .sections
{
display: inline-flex;
justify-content: center;
align-items: center;
}
nav .sections a
{
font-size: 1.5em;
margin: 0 20px;
}
svg
{
fill: var(--main-fg-color);
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
fill: var(--main-fg-color);
height: auto;
max-width: 100%;
max-height: 100%;
}
.content
{
flex-grow: 1;
flex-grow: 1;
}
.process-flow
{
background-color: var(--lighter-bg-color);
display: flex;
justify-content: space-between;
align-items: center;
background-color: var(--lighter-bg-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.process-flow h3
{
margin: 10px 0;
text-align: center;
margin: 10px 0;
text-align: center;
}
.process-flow .svg
{
display: inline-flex;
justify-content: center;
align-items: center;
display: inline-flex;
justify-content: center;
align-items: center;
}
.toggle
{
display: inline-flex;
justify-content: center;
align-items: center;
cursor: pointer;
padding: 4px;
border-radius: 12px;
display: inline-flex;
justify-content: center;
align-items: center;
cursor: pointer;
padding: 4px;
border-radius: 12px;
}
.toggle:hover
{
background-color: var(--highlight-bg-color);
color: var(--highlight-fg-color);
fill: var(--highlight-fg-color);
background-color: var(--highlight-bg-color);
color: var(--highlight-fg-color);
fill: var(--highlight-fg-color);
}
#ayo_logo
{
height: 42px;
height: 42px;
}

View file

@ -1,37 +1,26 @@
`use strict`
import { Component, version } from 'inferno'
import { Component } from 'inferno'
import { BrowserRouter, Link, Route, Switch } from 'inferno-router'
import { connect } from 'inferno-redux'
import { changeTheme, fetchLang } from './actions'
import { Svg } from './svg.jsx'
import { SvgToggle } from './svg_toggle.jsx'
import './main.css'
import LogoSvg from '../assets/logo.svg'
import LangSvg from '../assets/translate.svg'
import LightSvg from '../assets/brightness_high.svg'
import DarkSvg from '../assets/brightness_medium.svg'
import Blog from './blog.jsx'
import BlogEntry from './blog_entry.jsx'
import Home from './home.jsx'
import './main.css'
import LogoSvg from '../assets/icons/logo.svg'
import LangSvg from '../assets/icons/translate.svg'
import LightSvg from '../assets/icons/brightness_high.svg'
import DarkSvg from '../assets/icons/brightness_medium.svg'
import ArrowSvg from '../assets/arrow_forward.svg'
import Flow1Svg from '../assets/undraw_maintenance_cn7j.svg'
import Flow2Svg from '../assets/undraw_design_data_khdb.svg'
import Flow3Svg from '../assets/undraw_Artificial_intelligence_oyxx.svg'
class MainComponent extends Component
{
componentDidMount()
{
// Smooth color transitions (after rendering to avoid flash on reload)
// setTimeout(() => {
// const styleEl = document.head.getElementsByTagName('style')[0]
// document.head.appendChild(styleEl)
// const styleSheet = styleEl.sheet
// styleSheet.insertRule(
// '*, ::after, ::before { transition-property: background-color, color, fill; transition-duration: 1s;}')
// },
// 500)
}
toggleTheme(checked)
{
this.props.changeTheme(checked ? 'dark' : 'light')
@ -47,51 +36,35 @@ class MainComponent extends Component
return (
<main>
<link rel="stylesheet" type="text/css" href={'/assets/theme/' + this.props.theme + '.css'} />
<nav>
<div className="actions"> </div>
<div className="main-logo">
<Svg src={LogoSvg}/>
</div>
<div className="actions">
<SvgToggle default={LangSvg} text={this.props.strings.lang}
toggle={(checked) => this.toggleLang(checked)} value={this.props.lang === 'en'}/>
<SvgToggle default={LightSvg} checked={DarkSvg}
toggle={(checked) => this.toggleTheme(checked)} value={this.props.theme === 'dark'}/>
</div>
</nav>
<div className="content">
<div className="process-flow">
<div>
<Svg src={Flow1Svg}/>
<h3>{this.props.strings.flow1}</h3>
<BrowserRouter>
<nav>
<div className="main-logo">
<Svg src={LogoSvg}/>
</div>
<Svg src={ArrowSvg}/>
<div>
<Svg src={Flow2Svg}/>
<h3>{this.props.strings.flow2}</h3>
<div className="sections">
<Link to="/">{this.props.strings.section.home}</Link>
<Link to="/blog">{this.props.strings.section.blog}</Link>
</div>
<Svg src={ArrowSvg}/>
<div>
<Svg src={Flow3Svg}/>
<h3>{this.props.strings.flow3}</h3>
<div className="actions">
<SvgToggle default={LangSvg} text={this.props.strings.lang}
toggle={(checked) => this.toggleLang(checked)} value={this.props.lang === 'en'}/>
<SvgToggle default={LightSvg} checked={DarkSvg}
toggle={(checked) => this.toggleTheme(checked)} value={this.props.theme === 'dark'}/>
</div>
</div>
</div>
<footer>
<section className="legal">
<div>{this.props.strings.company_name}</div>
<div>{this.props.strings.address_one_line}</div>
<div>{this.props.strings.email_contact}</div>
</section>
<section className="credit">
<div>
{this.props.strings.inferno_credit}
<a target="_blank" rel="noopener noreferrer" href="https://infernojs.org/">
{`Inferno ` + version}
</a>
</div>
</section>
</footer>
</nav>
<Switch>
<Route path="/blog/:entry_date/:entry_name" component={BlogEntry}/>
<Route path="/blog" component={Blog}/>
<Route path="/" component={Home}/>
</Switch>
<footer>
<section className="legal">
<div>{this.props.strings.company_name}</div>
<div>{this.props.strings.address_one_line}</div>
<div>{this.props.strings.email_contact}</div>
</section>
</footer>
</BrowserRouter>
</main>)
}
}

View file

@ -1,9 +1,9 @@
import { langActions, themeActions } from './actions.js'
import { blogActions, langActions, themeActions } from './actions.js'
export const themeReducer = (state = 'light', action) => {
export const themeReducer = (state = 'dark', action) => {
switch (action.type)
{
case themeActions.THEME_CHANGE:
case themeActions.CHANGE:
return action.theme
default:
return state
@ -13,12 +13,12 @@ export const themeReducer = (state = 'light', action) => {
export const langReducer = (state = {lang: 'jp', request: null, strings: null}, action) => {
switch (action.type)
{
case langActions.LANG_CHANGE_REQUEST:
case langActions.CHANGE_REQUEST:
return {
...state,
request: action.lang
}
case langActions.LANG_CHANGE_RESPONSE:
case langActions.CHANGE_RESPONSE:
return {
...state,
lang: action.lang,
@ -28,4 +28,41 @@ export const langReducer = (state = {lang: 'jp', request: null, strings: null},
default:
return state
}
}
export const blogReducer = (state = {
list: [],
list_requesting: false,
entry: null,
entry_name: null,
entry_requesting: false
}, action) => {
switch (action.type)
{
case blogActions.LIST_REQUEST:
return {
...state,
list_requesting: true
}
case blogActions.LIST_RESPONSE:
return {
...state,
list: action.list,
list_requesting: false
}
case blogActions.ENTRY_REQUEST:
return {
...state,
entry_requesting: true
}
case blogActions.ENTRY_RESPONSE:
return {
...state,
entry: action.entry,
entry_name: action.name,
entry_requesting: false
}
default:
return state
}
}