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]",