Initial commit

This commit is contained in:
Corentin Risselin 2019-12-31 06:03:44 +09:00
commit 56d40196d6
15 changed files with 490 additions and 0 deletions

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
node_modules
public
*.png
*.jpg
*.svg
*.log
*.lock

8
assets/theme/dark.css Normal file
View file

@ -0,0 +1,8 @@
:root
{
--main-bg-color: #333;
--main-fg-color: #eee;
--main-primary-color: hsl(213, 35%, 65%);
--light-primary-color: hsl(213, 35%, 45%);
}

8
assets/theme/light.css Normal file
View file

@ -0,0 +1,8 @@
:root
{
--main-bg-color: #eee;
--main-fg-color: #333;
--main-primary-color: hsl(213, 35%, 45%);
--light-primary-color: hsl(213, 35%, 65%);
}

16
index.html Normal file
View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="icon" href="data:,">
<title>Inferno - Hello World</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="root"></div>
<script src="/public/bundle.js.gz"></script>
</body>
</html>

33
package.json Normal file
View file

@ -0,0 +1,33 @@
{
"name": "ayo",
"version": "0.1.0",
"main": "index.js",
"author": "Risselin Corentin",
"license": "MIT",
"dependencies": {
"@babel/cli": "^7.7.0",
"@babel/core": "^7.7.2",
"@babel/preset-env": "^7.7.1",
"babel-loader": "^8.0.6",
"babel-plugin-inferno": "^6.1.0",
"compression-webpack-plugin": "^3.0.0",
"inferno": "^7.3.2",
"inferno-redux": "^7.3.3",
"inferno-router": "^7.3.2",
"redux": "^4.0.5",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10"
},
"devDependencies": {
"css-loader": "^3.4.0",
"file-loader": "^5.0.2",
"style-loader": "^1.1.2",
"webpack-dev-server": "^3.9.0"
},
"scripts": {
"build": "webpack --config webpack.prod.js",
"dev": "webpack --config webpack.dev.js",
"start": "node server.js",
"start-dev": "webpack-dev-server --config webpack.dev.js"
}
}

61
server.js Normal file
View file

@ -0,0 +1,61 @@
const fs = require('fs');
const http = require('http');
const path = require('path');
const url = require('url');
const port = 8080; // Prefer port > 1024 to avoid super-user permission
const server = http.createServer((request, response) => {
const mimeType = {
'.html': 'text/html',
'.js': 'text/javascript',
'.gz': 'application/javascript',
'.svg': 'image/svg+xml',
'.css': 'text/css'
};
const mimeEncoding = {
'.gz': 'gzip',
};
let pathname = '.' + url.parse(request.url).pathname;
fs.exists(pathname, function (exist) {
if(!exist)
{
// If the file is not found, return 404
response.statusCode = 404;
response.end(`File ${pathname} not found!`);
return;
}
// If is a directory, then look for index.html
if (fs.statSync(pathname).isDirectory()) {
pathname += '/index.html';
}
// Read file from file system
fs.readFile(pathname, function(error, data){
if(error)
{
response.statusCode = 500;
response.end(`Error getting the file: ${error}.`);
}
else
{
// Based on the URL path, extract the file extention. e.g. .js, .doc, ...
const extension = path.parse(pathname).ext;
// Set Content-Type
response.setHeader('Content-Type', mimeType[extension] || 'text/plain' );
// If the file is found, set Content-Encoding
if(mimeEncoding[extension])
{
response.setHeader('Content-Encoding', mimeEncoding[extension]);
}
response.end(data);
}
});
});
});
server.listen(port, (error) => {
if (error) {
return console.log(`Server cannot listen port ${port} :`, error);
}
console.log(`Server is listening on port ${port}`);
});

8
src/actions.js Normal file
View file

@ -0,0 +1,8 @@
export const themeActions = {
THEME_CHANGE: 'THEME_CHANGE'
}
export const changeTheme = (new_theme) => ({
type: themeActions.THEME_CHANGE,
theme: new_theme
})

35
src/index.jsx Normal file
View file

@ -0,0 +1,35 @@
`use strict`
import { render } from 'inferno'
import { Provider } from 'inferno-redux';
import { applyMiddleware, createStore, combineReducers } from 'redux';
import { themeReducer } from './reducers';
import Main from './main.jsx'
const logger = process.env.DEBUG ? store => next => action => {
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
console.groupEnd()
return result
} : null
const persistedState = localStorage.getItem('reduxState')
const initState = persistedState ? JSON.parse(persistedState) : {}
const reducers = combineReducers(
{theme: themeReducer})
const store = process.env.DEBUG ?
createStore(reducers, initState, applyMiddleware(logger)) : createStore(reducers, initState)
store.subscribe(() => {
localStorage.setItem('reduxState', JSON.stringify(store.getState()))
})
render(
<Provider store={store}>
<Main />
</Provider>,
document.getElementById("root"))

77
src/main.css Normal file
View file

@ -0,0 +1,77 @@
*, ::after, ::before
{
box-sizing: border-box;
}
body
{
background-color: var(--main-bg-color);
color: var(--main-fg-color);
margin: 0;
}
nav
{
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
}
/* START switch */
input:checked + .slider::before {
transform: translateX(20px);
}
.switch input {
display: none;
}
.slider.round
{
border-radius: 34px;
}
.slider
{
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--light-primary-color);
border: 1px solid var(--light-primary-color);
}
.slider.round::before
{
border-radius: 50%;
}
.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);
}

60
src/main.jsx Normal file
View file

@ -0,0 +1,60 @@
`use strict`
import { Component } from 'inferno'
import { connect } from 'inferno-redux';
import { changeTheme } from './actions';
import { Svg } from './svg.jsx'
import './main.css'
import Logo from '../assets/logo.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(event)
{
this.props.changeTheme(event.target.checked ? 'dark' : 'light')
}
render()
{
return (
<div>
<link rel="stylesheet" type="text/css" href={'/assets/theme/' + this.props.theme + '.css'} />
<nav>
<Svg url={Logo}/>
<label className="switch" htmlFor="switch-theme">
<input type="checkbox" id="switch-theme"
defaultChecked={this.props.theme == 'dark'}
onChange={(event) => this.toggleTheme(event)}/>
<div className="slider round"></div>
</label>
</nav>
</div>)
}
}
const mapStateToProps = state => ({
theme: state.theme
})
const mapDispatchToProps = dispatch => ({
changeTheme: theme => dispatch(changeTheme(theme))
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(MainComponent)

11
src/reducers.js Normal file
View file

@ -0,0 +1,11 @@
import { themeActions } from './actions.js'
export const themeReducer = (state = 'light', action) => {
switch (action.type)
{
case themeActions.THEME_CHANGE:
return action.theme
default:
return state
}
}

35
src/svg.jsx Normal file
View file

@ -0,0 +1,35 @@
`use strict`
import { Component } from 'inferno'
export class Svg extends Component
{
constructor(props)
{
super(props)
this.state = {
svg: null,
loading: false
}
}
componentDidMount()
{
fetch(this.props.url)
.then(res => res.text())
.then(text => this.setState({ svg: text }))
}
render()
{
const { loading, svg } = this.state;
if (loading)
{
return <span className="spinner"/>
}
else if(!svg)
{
return <span className="error"/>
}
return <span dangerouslySetInnerHTML={{ __html: this.state.svg}}/>
}
}

40
webpack.base.js Normal file
View file

@ -0,0 +1,40 @@
const path = require('path');
module.exports = {
mode: "none",
entry: './src/index.jsx',
module:
{
rules: [
{
test: /\.jsx$/,
loader: "babel-loader",
options:
{
presets: ["@babel/preset-env"],
plugins: [
["babel-plugin-inferno",
{
"imports": true
}]
]
}
},
{
test: /src.*\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /assets.*\.(jpg|png|svg|css)$/,
loader: 'file-loader',
options: {
name: '[path][name].[ext]',
},
}]
},
output:
{
filename: 'bundle.js',
path: path.resolve(__dirname, 'public')
}
};

61
webpack.dev.js Normal file
View file

@ -0,0 +1,61 @@
const path = require('path');
const webpack = require('webpack');
const CompressionPlugin = require("compression-webpack-plugin")
const config = require('./webpack.base.js');
module.exports = Object.assign(
config,
{
mode: 'development',
devtool: 'source-map', // compile source map to get better debug output (error file/line)
cache: true, // caching already built files so unchanged files can be reuse when an other changes
watch: true, // watch file change and automatically rebuild the application
watchOptions:
{
ignored: /node_modules/ // avoid watching node_modules as it is usually huge, note that it can be useful when debugging packages
},
resolve:
{
// avoid warning caused by development mode (using dev inferno build)
alias:
{
inferno: require.resolve('inferno/dist/index.dev.esm.js')
}
},
plugins: [
new webpack.DefinePlugin(
{
'process.env':
{
'DEBUG': true
} // set a DEBUG flag that can be used in the scripts
}),
new CompressionPlugin(
{
filename: "[path].gz[query]",
algorithm: "gzip",
test: /\.js/
})
],
// webpack-dev-server configuration
devServer:
{
contentBase: path.join(__dirname),
compress: true,
port: 8080,
// Not elegant way to get rid of the gzip path in index.html
proxy:
{
'/$':
{
target: 'http://localhost:8080',
secure: false,
bypass: function(request, response, proxyOptions)
{
return '/index_dev.html';
}
}
}
}
}
);

29
webpack.prod.js Normal file
View file

@ -0,0 +1,29 @@
const webpack = require('webpack');
const CompressionPlugin = require("compression-webpack-plugin")
const config = require('./webpack.base.js');
module.exports = Object.assign(
config,
{
mode: "production",
optimization:
{
minimize: true
},
plugins: [
new webpack.DefinePlugin(
{
'process.env':
{
'DEBUG': false
} // set a DEBUG flag that can be used in the scripts (can be skipped)
}),
new CompressionPlugin(
{
filename: "[path].gz[query]",
algorithm: "gzip",
test: /\.js/
})
]
}
);