React setup, ajax retrieval example, live reload, auto SCSS compile

pull/32/head
Piero Toffanin 2016-10-11 13:42:17 -04:00
rodzic 03cc307e96
commit d688c60b49
22 zmienionych plików z 237 dodań i 134 usunięć

Wyświetl plik

@ -32,8 +32,3 @@ RUN npm install
WORKDIR /webodm
RUN npm install -g webpack
RUN npm install
RUN webpack
# Make sure all scripts are executable
RUN chmod +x *.sh

Wyświetl plik

@ -64,7 +64,10 @@ Then:
```
pip install -r requirements.txt
pip install --upgrade git+git://github.com/Yelp/swagger_spec_validator git+https://github.com/pierotofy/django-knockout
pip install --upgrade git+git://github.com/Yelp/swagger_spec_validator
npm install -g webpack
npm install
webpack
chmod +x start.sh && ./start.sh
```

Wyświetl plik

@ -4,6 +4,7 @@ from app import models, permissions
from guardian.shortcuts import get_objects_for_user
class ProjectSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.ReadOnlyField()
owner = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
class Meta:

Wyświetl plik

@ -1 +1 @@
*.js
*

Wyświetl plik

@ -1,55 +0,0 @@
#navbar-top {
height: 50px;
min-height: 50px;
background-color: #18bc9c; }
#navbar-top .navbar-brand {
height: auto;
padding: 8px; }
#navbar-top .navbar-text {
margin-top: 14px;
margin-left: 4px; }
#navbar-top .navbar-link:hover {
color: white;
text-decoration: none; }
#navbar-top .navbar-top-links a.dropdown-toggle {
color: white; }
#navbar-top .navbar-top-links a:hover, #navbar-top .navbar-top-links a:focus, #navbar-top .navbar-top-links .open > a {
background-color: #2c3e50; }
#navbar-top ul#side-menu a:hover, #navbar-top ul#side-menu a:focus {
color: white;
text-decoration: none;
background-color: #2c3e50; }
#navbar-top ul#side-menu li {
word-break: break-word; }
#navbar-top .navbar-top-links li a.dropdown-toggle {
height: 50px; }
#navbar-top .user-profile {
padding: 0 8px; }
#navbar-top .user-profile .email {
font-size: 90%;
color: gray; }
ul#side-menu.nav a {
color: #111; }
.content {
clear: both; }
.content h1, .content h2, .content h3, .content h4, .content h5 {
padding-top: 4px; }
.top-buffer {
margin-top: 15px; }
.navbar-default {
background-color: white; }
.alert {
margin-bottom: 10px; }
table.table-first-col-bold td:first-child {
font-weight: bold; }
button span.glyphicon {
margin-right: 4px; }
/*# sourceMappingURL=main.css.map */

Wyświetl plik

@ -1,7 +0,0 @@
{
"version": 3,
"mappings": "AAAA,WAAW;EACP,MAAM,EAAE,IAAI;EACZ,UAAU,EAAE,IAAI;EAChB,gBAAgB,EAAE,OAAO;EAEzB,yBAAa;IACT,MAAM,EAAE,IAAI;IACZ,OAAO,EAAE,GAAG;EAEhB,wBAAY;IACR,UAAU,EAAE,IAAI;IAChB,WAAW,EAAE,GAAG;EAGpB,8BAAkB;IACd,KAAK,EAAE,KAAK;IACZ,eAAe,EAAE,IAAI;EAIrB,+CAAiB;IACb,KAAK,EAAE,KAAK;EAGhB,qHAAyB;IACrB,gBAAgB,EAAE,OAAO;EAM7B,kEAAgB;IACZ,KAAK,EAAE,KAAK;IACZ,eAAe,EAAE,IAAI;IACrB,gBAAgB,EAAE,OAAO;EAG7B,2BAAE;IACE,UAAU,EAAE,UAAU;EAI9B,kDAAsC;IAClC,MAAM,EAAE,IAAI;EAGhB,yBAAa;IACT,OAAO,EAAE,KAAK;IAEd,gCAAM;MACF,SAAS,EAAE,GAAG;MACd,KAAK,EAAE,IAAI;;AAMnB,kBAAC;EACG,KAAK,EAAE,IAAI;;AAInB,QAAQ;EACJ,KAAK,EAAE,IAAI;EACX,+DAAc;IACV,WAAW,EAAE,GAAG;;AAIxB,WAAY;EACR,UAAU,EAAE,IAAI;;AAGpB,eAAe;EACX,gBAAgB,EAAE,KAAK;;AAG3B,MAAM;EACF,aAAa,EAAE,IAAI;;AAInB,yCAAc;EACV,WAAW,EAAE,IAAI;;AAIzB,qBAAqB;EACjB,YAAY,EAAE,GAAG",
"sources": ["main.scss"],
"names": [],
"file": "main.css"
}

Wyświetl plik

@ -0,0 +1,13 @@
import React from 'react';
import './css/Dashboard.scss';
import ProjectList from './components/ProjectList';
class Dashboard extends React.Component {
render() {
return (
<ProjectList source="/api/projects/"/>
);
}
}
export default Dashboard;

Wyświetl plik

@ -1,11 +0,0 @@
import React from 'react';
class App extends React.Component {
render() {
return (
<div>Hello World!</div>
);
}
}
export default App;

Wyświetl plik

@ -0,0 +1,62 @@
import React from 'react';
import $ from 'jquery';
import ProjectListItem from './ProjectListItem';
class ProjectList extends React.Component {
constructor(){
super();
this.state = {
loading: true,
error: "",
projects: null
}
}
componentDidMount(){
// Load projects from API
this.serverRequest = $.getJSON(this.props.source, json => {
if (json.results){
this.setState({
projects: json.results,
loading: false
});
}else{
this.setState({
error: `Invalid JSON response: ${JSON.stringify(json)}`,
loading: false
});
}
})
.fail((jqXHR, textStatus, errorThrown) => {
this.setState({
error: `Could not load projects list: ${textStatus}`,
loading: false
});
})
}
componentWillUnmount(){
this.serverRequest.abort();
}
render() {
if (this.state.loading){
return (<div>Loading projects...</div>);
}
else if (this.state.projects){
return (<div>
{this.state.projects.map(p => (
<ProjectListItem key={p.id} data={p} />
))}
</div>);
}else if (this.state.error){
return (<div>An error occurred: {this.state.error}</div>);
}else{
return (<div></div>); // should never happen
}
}
}
export default ProjectList;

Wyświetl plik

@ -0,0 +1,29 @@
import React from 'react';
import ProjectListItemPanel from './ProjectListItemPanel';
class ProjectListItem extends React.Component {
constructor(){
super();
this.state = {
collapsed: true
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
this.state.collapsed = !this.state.collapsed;
}
render() {
return (
<div onClick={this.handleClick}>
{this.props.data.name} {this.props.data.created_at}
{this.props.collapsed ? "" : <ProjectListItemPanel />}
</div>
);
}
}
export default ProjectListItem;

Wyświetl plik

@ -0,0 +1,17 @@
import React from 'react';
class ProjectListItemPanel extends React.Component {
constructor(){
super();
}
render() {
return (
<div>
Sup!
</div>
);
}
}
export default ProjectListItemPanel;

Wyświetl plik

@ -0,0 +1,3 @@
#dashboard-app{
background-color: yellow;
}

Wyświetl plik

@ -1,5 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';
ReactDOM.render(<App/>, document.getElementById('react-app'))

Wyświetl plik

@ -1,38 +0,0 @@
$(function(){
console.log("Hello!");
$('#side-menu').metisMenu();
$(window).bind("load resize", function() {
var topOffset = 50;
var width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width;
if (width < 768) {
$('div.navbar-collapse').addClass('collapse');
topOffset = 100; // 2-row-menu
} else {
$('div.navbar-collapse').removeClass('collapse');
}
var height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1;
height = height - topOffset;
if (height < 1) height = 1;
if (height > topOffset) {
$("#page-wrapper").css("min-height", (height) + "px");
}
});
var url = window.location;
// var element = $('ul.nav a').filter(function() {
// return this.href == url;
// }).addClass('active').parent().parent().addClass('in').parent();
var element = $('ul.nav a').filter(function() {
return this.href == url;
}).addClass('active').parent();
while(true){
if (element.is('li')){
element = element.parent().addClass('in').parent();
} else {
break;
}
}
});

Wyświetl plik

@ -0,0 +1,6 @@
import '../css/main.scss';
import React from 'react';
import ReactDOM from 'react-dom';
import Dashboard from './Dashboard';
ReactDOM.render(<Dashboard/>, document.getElementById('dashboard-app'));

Wyświetl plik

@ -22,9 +22,8 @@
<link href="{% static 'app/css/metisMenu.min.css' %}" rel="stylesheet">
<link href="{% static 'app/css/sb-admin-2.css' %}" rel="stylesheet">
{% block extra-headers %}{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'app/css/main.css' %}" />
<link rel="stylesheet" type="text/css" href="{% static 'app/bundles/css/main.css' %}" />
<script src="{% static 'app/js/vendor/modernizr-2.8.3.min.js' %}"></script>
<script src="{% static 'app/js/vendor/knockout-3.4.0.js' %}"></script>
<script src="{% static 'app/js/vendor/jquery-1.11.2.min.js' %}"></script>
<title>{{title|default:"Login"}} - WebODM</title>
</head>
@ -71,5 +70,44 @@
</body>
<script src="{% static 'app/js/vendor/bootstrap.min.js' %}"></script>
<script src="{% static 'app/js/vendor/metisMenu.min.js' %}"></script>
<script src="{% static 'app/js/main.js' %}"></script>
<script>
$(function(){
$('#side-menu').metisMenu();
$(window).bind("load resize", function() {
let topOffset = 50;
let width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width;
if (width < 768) {
$('div.navbar-collapse').addClass('collapse');
topOffset = 100; // 2-row-menu
} else {
$('div.navbar-collapse').removeClass('collapse');
}
let height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1;
height = height - topOffset;
if (height < 1) height = 1;
if (height > topOffset) {
$("#page-wrapper").css("min-height", (height) + "px");
}
});
let url = window.location;
// let element = $('ul.nav a').filter(function() {
// return this.href == url;
// }).addClass('active').parent().parent().addClass('in').parent();
let element = $('ul.nav a').filter(function() {
return this.href == url;
}).addClass('active').parent();
while(true){
if (element.is('li')){
element = element.parent().addClass('in').parent();
} else {
break;
}
}
});
</script>
</html>

Wyświetl plik

@ -37,6 +37,6 @@
{% endif %}
<!-- <h4>Projects</h4> -->
<div id="react-app"></div>
<div id="dashboard-app"></div>
{% render_bundle 'main' %}
{% endblock %}

Wyświetl plik

@ -12,6 +12,7 @@ services:
volumes:
- .:/webodm
- /webodm/nodeodm/external/node-OpenDroneMap/node_modules
- /webodm/node_modules
ports:
- "8000:8000"
depends_on:

Wyświetl plik

@ -24,9 +24,18 @@
"babel-loader": "^6.2.5",
"babel-preset-es2015": "^6.16.0",
"babel-preset-react": "^6.16.0",
"css-loader": "^0.25.0",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.9.0",
"node-sass": "^3.10.1",
"react": "^15.3.2",
"react-dom": "^15.3.2",
"react-hot-loader": "^3.0.0-beta.5",
"sass-loader": "^4.0.2",
"style-loader": "^0.13.1",
"webpack": "^1.13.2",
"webpack-bundle-tracker": "0.0.93"
"webpack-bundle-tracker": "0.0.93",
"webpack-dev-server": "^1.16.2",
"webpack-livereload-plugin": "^0.9.0"
}
}

Wyświetl plik

@ -1,4 +1,7 @@
#!/bin/bash
echo Building asssets...
webpack
echo Running migrations
python manage.py makemigrations
python manage.py migrate

15
webpack-server.js 100644
Wyświetl plik

@ -0,0 +1,15 @@
let webpack = require('webpack');
let WebpackDevServer = require('webpack-dev-server');
let config = require('./webpack.config');
new WebpackDevServer(webpack(config), {
publicPath: config.output.publicPath,
hot: true,
inline: true
}).listen(3000, '0.0.0.0', (err, result) => {
if (err) {
console.log(err);
}
console.log('Listening at 0.0.0.0:3000');
})

Wyświetl plik

@ -1,19 +1,32 @@
var path = require("path")
var webpack = require('webpack')
var BundleTracker = require('webpack-bundle-tracker')
let path = require("path");
let webpack = require('webpack');
let BundleTracker = require('webpack-bundle-tracker');
let ExtractTextPlugin = require('extract-text-webpack-plugin');
let LiveReloadPlugin = require('webpack-livereload-plugin');
module.exports = {
context: __dirname,
entry: './app/static/app/js/index.jsx', // entry point of our app.
entry: [
// 'webpack-dev-server/client?http://localhost:3000',
// 'webpack/hot/only-dev-server',
'./app/static/app/js/main.jsx',
],
output: {
path: path.resolve('./app/static/app/bundles/'),
filename: "[name]-[hash].js",
path: path.join(__dirname, './app/static/app/bundles/'),
filename: "[name]-[hash].js"
// publicPath: 'http://localhost:3000/app/static/app/bundles/', // Tell django to use this URL to load packages and not use STATIC_URL + bundle_name
},
plugins: [
// new webpack.HotModuleReplacementPlugin(),
// new webpack.NoErrorsPlugin(), // don't reload if there is an error
new LiveReloadPlugin(),
new BundleTracker({filename: './webpack-stats.json'}),
new ExtractTextPlugin('css/main.css', {
allChunks: true
})
],
module: {
@ -23,8 +36,13 @@ module.exports = {
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader',
query: {
// plugins: ['react-hot-loader/babel'],
presets: ['es2015', 'react']
}
},
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract('css!sass')
}
]
},
@ -32,5 +50,11 @@ module.exports = {
resolve: {
modulesDirectories: ['node_modules', 'bower_components'],
extensions: ['', '.js', '.jsx']
},
externals: {
// require("jquery") is external and available
// on the global let jQuery
"jquery": "jQuery"
}
}