diff --git a/assets/theme/dark.css b/assets/theme/dark.css
index 1d185db..68f312d 100644
--- a/assets/theme/dark.css
+++ b/assets/theme/dark.css
@@ -1,7 +1,11 @@
:root
{
- --main-bg-color: #333;
- --main-fg-color: #eee;
+ --main-bg-color: #21262b;
+ --main-fg-color: #bbb;
+ --lighter-bg-color: #31363b;
+ --lighter-fg-color: #ccc;
+ --highlight-bg-color: #41464b;
+ --highlight-fg-color: #ddd;
--main-primary-color: hsl(213, 35%, 65%);
--light-primary-color: hsl(213, 35%, 45%);
diff --git a/assets/theme/light.css b/assets/theme/light.css
index 1ba7885..ff0aaa3 100644
--- a/assets/theme/light.css
+++ b/assets/theme/light.css
@@ -1,7 +1,11 @@
:root
{
- --main-bg-color: #eee;
- --main-fg-color: #333;
+ --main-bg-color: #ddd;
+ --main-fg-color: #21262b;
+ --lighter-bg-color: #ccc;
+ --lighter-fg-color: #31363b;
+ --highlight-bg-color: #bbb;
+ --highlight-fg-color: #41464b;
--main-primary-color: hsl(213, 35%, 45%);
--light-primary-color: hsl(213, 35%, 65%);
diff --git a/package.json b/package.json
index ded21dc..6dd64a3 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"webpack-cli": "^3.3.10"
},
"devDependencies": {
+ "copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.4.0",
"file-loader": "^5.0.2",
"style-loader": "^1.1.2",
diff --git a/server.js b/server.js
index c71f1fc..b3c5e98 100644
--- a/server.js
+++ b/server.js
@@ -11,7 +11,8 @@ const server = http.createServer((request, response) => {
'.js': 'text/javascript',
'.gz': 'application/javascript',
'.svg': 'image/svg+xml',
- '.css': 'text/css'
+ '.css': 'text/css',
+ '.json': 'application/json'
};
const mimeEncoding = {
'.gz': 'gzip',
diff --git a/src/actions.js b/src/actions.js
index be10263..2dff057 100644
--- a/src/actions.js
+++ b/src/actions.js
@@ -1,8 +1,50 @@
export const themeActions = {
- THEME_CHANGE: 'THEME_CHANGE'
+ THEME_CHANGE: 'THEME_CHANGE'
}
export const changeTheme = (new_theme) => ({
- type: themeActions.THEME_CHANGE,
- theme: new_theme
-})
\ No newline at end of file
+ type: themeActions.THEME_CHANGE,
+ theme: new_theme
+})
+
+export const langActions = {
+ LANG_CHANGE_REQUEST: 'LANG_CHANGE_REQUEST',
+ LANG_CHANGE_RESPONSE: 'LANG_CHANGE_RESPONSE'
+}
+
+export const changeLang = (new_lang) => ({
+ type: langActions.LANG_CHANGE_REQUEST,
+ lang: new_lang
+})
+
+export const receiveLang = (new_lang, strings) => ({
+ type: langActions.LANG_CHANGE_RESPONSE,
+ lang: new_lang,
+ strings: strings
+})
+
+export const fetchLang = (new_lang) => {
+ return (dispatch, getState) => {
+ const state = getState()
+ if(state.lang.strings !== null && state.lang.lang === new_lang)
+ {
+ return
+ }
+ dispatch(changeLang(new_lang))
+ return fetch('/public/lang/' + new_lang + '.json')
+ .then((response) => {
+ if(!response.ok)
+ {
+ console.log('Couldn\'t fetch ' + new_lang + '.json')
+ throw Error(response.statusText)
+ }
+ return response.json()
+ })
+ .then(json => {
+ dispatch(receiveLang(new_lang, json));
+ })
+ .catch(() => {
+ dispatch(changeLang(null))
+ })
+ }
+}
\ No newline at end of file
diff --git a/src/index.jsx b/src/index.jsx
index 7f692ee..11d3bbe 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -3,7 +3,8 @@ import { render } from 'inferno'
import { Provider } from 'inferno-redux';
import { applyMiddleware, createStore, combineReducers } from 'redux';
-import { themeReducer } from './reducers';
+import { langReducer, themeReducer } from './reducers';
+import { fetchLang } from './actions'
import Main from './main.jsx'
@@ -16,20 +17,43 @@ const logger = process.env.DEBUG ? store => next => action => {
return result
} : null
-const persistedState = localStorage.getItem('reduxState')
-const initState = persistedState ? JSON.parse(persistedState) : {}
+const thunk = store => next => action =>
+ typeof action === 'function'
+ ? action(store.dispatch, store.getState)
+ : next(action)
-const reducers = combineReducers(
- {theme: themeReducer})
-const store = process.env.DEBUG ?
- createStore(reducers, initState, applyMiddleware(logger)) : createStore(reducers, initState)
+const persistedState = {
+ theme: localStorage.getItem('theme') || themeReducer(undefined, {type: null}),
+ lang: localStorage.getItem('lang') ?
+ {...langReducer(undefined, {type: null}), lang: localStorage.getItem('lang')} :
+ langReducer(undefined, {type: null})
+}
+//const initState = persistedState ? JSON.parse(persistedState) : {}
-store.subscribe(() => {
- localStorage.setItem('reduxState', JSON.stringify(store.getState()))
+const reducers = combineReducers({
+ theme: themeReducer,
+ lang: langReducer
})
-render(
-
-
- ,
- document.getElementById("root"))
+const store = createStore(
+ reducers,
+ persistedState,
+ process.env.DEBUG ? applyMiddleware(thunk, logger) : applyMiddleware(thunk))
+
+let savedState = {
+ theme: store.getState().theme,
+ lang: store.getState().lang.lang
+}
+store.subscribe(() => {
+ const state = store.getState()
+ if(savedState.theme !== state.theme) localStorage.setItem('theme', store.getState().theme)
+ if(savedState.lang !== state.lang.lang) localStorage.setItem('lang', store.getState().lang.lang)
+})
+
+store.dispatch(fetchLang(store.getState().lang.lang)).then(() => {
+ render(
+
+
+ ,
+ document.getElementById("root"))
+})
diff --git a/src/lang/en.json b/src/lang/en.json
new file mode 100644
index 0000000..f1650d6
--- /dev/null
+++ b/src/lang/en.json
@@ -0,0 +1,4 @@
+{
+ "lang": "English",
+ "test": "Cloud"
+}
\ No newline at end of file
diff --git a/src/lang/jp.json b/src/lang/jp.json
new file mode 100644
index 0000000..a8327ea
--- /dev/null
+++ b/src/lang/jp.json
@@ -0,0 +1,4 @@
+{
+ "lang": "日本語",
+ "test": "雲"
+}
\ No newline at end of file
diff --git a/src/main.css b/src/main.css
index d2194ff..2c9d32a 100644
--- a/src/main.css
+++ b/src/main.css
@@ -10,68 +10,52 @@ body
margin: 0;
}
+h1, h2, h3, h4, h5, h5
+{
+ margin: 0;
+}
+
nav
{
display: flex;
justify-content: space-between;
align-items: center;
- padding: 20px;
+ padding: 10px;
+ background-color: var(--lighterbg-color);
+ border-bottom: 1px solid var(--highlight-bg-color);
}
-/* START switch */
-input:checked + .slider::before {
- transform: translateX(20px);
-}
-
-.switch input {
- display: none;
-}
-
-.slider.round
+nav .actions
{
- border-radius: 34px;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
}
-.slider
+svg
{
- position: absolute;
+ fill: var(--main-fg-color);
+}
+
+.toggle
+{
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
cursor: pointer;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: var(--light-primary-color);
- border: 1px solid var(--light-primary-color);
+ padding: 4px;
+ border-radius: 12px;
+ transition-duration: 0.3s;
}
-.slider.round::before
+.toggle:hover
{
- border-radius: 50%;
+ background-color: var(--highlight-bg-color);
+ color: var(--highlight-fg-color);
+ fill: var(--highlight-fg-color);
}
-.slider::before
-{
- background-color: var(--main-primary-color);
- position: absolute;
- content: "";
- height: 15px;
- width: 15px;
- left: 2px;
- bottom: 1px;
- transition: background-color 1s, transform .4s;
-}
-
-.switch
-{
- position: relative;
- display: inline-block;
- width: 40px;
- height: 19px;
-}
-/* END switch */
-
#ayo_logo
{
- width: 200px;
- fill: var(--main-fg-color);
+ height: 42px;
}
\ No newline at end of file
diff --git a/src/main.jsx b/src/main.jsx
index 827c342..3d2b28e 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -1,12 +1,16 @@
`use strict`
import { Component } from 'inferno'
-import { connect } from 'inferno-redux';
+import { connect } from 'inferno-redux'
-import { changeTheme } from './actions';
+import { changeTheme, fetchLang } from './actions'
import { Svg } from './svg.jsx'
+import { SvgToggle } from './svg_toggle.jsx'
import './main.css'
-import Logo from '../assets/logo.svg'
+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'
class MainComponent extends Component
{
@@ -23,9 +27,14 @@ class MainComponent extends Component
500)
}
- toggleTheme(event)
+ toggleTheme(checked)
{
- this.props.changeTheme(event.target.checked ? 'dark' : 'light')
+ this.props.changeTheme(checked ? 'dark' : 'light')
+ }
+
+ toggleLang(checked)
+ {
+ this.props.fetchLang(checked ? 'en' : 'jp')
}
render()
@@ -34,24 +43,28 @@ class MainComponent extends Component
)
}
}
const mapStateToProps = state => ({
+ strings: state.lang.strings,
+ lang: state.lang,
theme: state.theme
})
const mapDispatchToProps = dispatch => ({
- changeTheme: theme => dispatch(changeTheme(theme))
+ changeTheme: theme => dispatch(changeTheme(theme)),
+ fetchLang: lang => dispatch(fetchLang(lang))
})
export default connect(
diff --git a/src/reducers.js b/src/reducers.js
index 393003c..f5c0eca 100644
--- a/src/reducers.js
+++ b/src/reducers.js
@@ -1,11 +1,31 @@
-import { themeActions } from './actions.js'
+import { langActions, themeActions } from './actions.js'
export const themeReducer = (state = 'light', action) => {
- switch (action.type)
- {
- case themeActions.THEME_CHANGE:
- return action.theme
- default:
- return state
- }
+ switch (action.type)
+ {
+ case themeActions.THEME_CHANGE:
+ return action.theme
+ default:
+ return state
+ }
+}
+
+export const langReducer = (state = {lang: 'jp', request: null, strings: null}, action) => {
+ switch (action.type)
+ {
+ case langActions.LANG_CHANGE_REQUEST:
+ return {
+ ...state,
+ request: action.lang
+ }
+ case langActions.LANG_CHANGE_RESPONSE:
+ return {
+ ...state,
+ lang: action.lang,
+ request: null,
+ strings: action.strings
+ }
+ default:
+ return state
+ }
}
\ No newline at end of file
diff --git a/src/svg_toggle.jsx b/src/svg_toggle.jsx
new file mode 100644
index 0000000..c85c03a
--- /dev/null
+++ b/src/svg_toggle.jsx
@@ -0,0 +1,54 @@
+`use strict`
+import { Component } from 'inferno'
+
+export class SvgToggle extends Component
+{
+ constructor(props)
+ {
+ super(props)
+ this.state = {
+ checked: props.value !== undefined ? props.value : false,
+ svg_default: null,
+ svg_checked: null
+ }
+ }
+
+ componentDidMount()
+ {
+ fetch(this.props.default)
+ .then(res => res.text())
+ .then(text => {
+ if(this.props.checked)
+ {
+ this.setState({ svg_default: text })
+ }
+ else
+ {
+ this.setState({ svg_default: text, svg_checked: text})
+ }
+ })
+ if(this.props.checked)
+ {
+ fetch(this.props.checked)
+ .then(res => res.text())
+ .then(text => this.setState({ svg_checked: text }))
+ }
+ }
+
+ onToggle()
+ {
+ if(this.props.toggle !== null)
+ {
+ this.props.toggle(!this.state.checked)
+ }
+ this.setState((state, props) => ({checked: !state.checked}))
+ }
+
+ render()
+ {
+ return this.onToggle()}
+ dangerouslySetInnerHTML={
+ { __html: (this.state.checked ? this.state.svg_checked : this.state.svg_default) +
+ (this.props.text || '') }}/>
+ }
+}
\ No newline at end of file
diff --git a/webpack.dev.js b/webpack.dev.js
index 792c921..1afd5a8 100644
--- a/webpack.dev.js
+++ b/webpack.dev.js
@@ -1,5 +1,6 @@
const path = require('path');
const webpack = require('webpack');
+const CopyPlugin = require('copy-webpack-plugin');
const CompressionPlugin = require("compression-webpack-plugin")
const config = require('./webpack.base.js');
@@ -30,6 +31,9 @@ module.exports = Object.assign(
'DEBUG': true
} // set a DEBUG flag that can be used in the scripts
}),
+ new CopyPlugin([
+ {from: 'src/lang', to: 'lang'}
+ ]),
new CompressionPlugin(
{
filename: "[path].gz[query]",
diff --git a/webpack.prod.js b/webpack.prod.js
index ce853fb..5b5f4ad 100644
--- a/webpack.prod.js
+++ b/webpack.prod.js
@@ -1,4 +1,5 @@
const webpack = require('webpack');
+const CopyPlugin = require('copy-webpack-plugin');
const CompressionPlugin = require("compression-webpack-plugin")
const config = require('./webpack.base.js');
@@ -18,6 +19,9 @@ module.exports = Object.assign(
'DEBUG': false
} // set a DEBUG flag that can be used in the scripts (can be skipped)
}),
+ new CopyPlugin([
+ {from: 'src/lang', to: 'lang'}
+ ]),
new CompressionPlugin(
{
filename: "[path].gz[query]",