Initial commit
This commit is contained in:
commit
56d40196d6
15 changed files with 490 additions and 0 deletions
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
node_modules
|
||||||
|
public
|
||||||
|
|
||||||
|
*.png
|
||||||
|
*.jpg
|
||||||
|
*.svg
|
||||||
|
*.log
|
||||||
|
*.lock
|
||||||
8
assets/theme/dark.css
Normal file
8
assets/theme/dark.css
Normal 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
8
assets/theme/light.css
Normal 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
16
index.html
Normal 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
33
package.json
Normal 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
61
server.js
Normal 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
8
src/actions.js
Normal 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
35
src/index.jsx
Normal 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
77
src/main.css
Normal 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
60
src/main.jsx
Normal 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
11
src/reducers.js
Normal 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
35
src/svg.jsx
Normal 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
40
webpack.base.js
Normal 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
61
webpack.dev.js
Normal 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
29
webpack.prod.js
Normal 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/
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
Loading…
Add table
Add a link
Reference in a new issue