diff --git a/Dockerfile b/Dockerfile
index b3ec4dbd..d827fbe9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -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
diff --git a/README.md b/README.md
index aa1605dd..5a3a145e 100644
--- a/README.md
+++ b/README.md
@@ -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
```
diff --git a/app/api/projects.py b/app/api/projects.py
index 1c143d57..5463a587 100644
--- a/app/api/projects.py
+++ b/app/api/projects.py
@@ -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:
diff --git a/app/static/app/bundles/.gitignore b/app/static/app/bundles/.gitignore
index a6c7c285..f59ec20a 100644
--- a/app/static/app/bundles/.gitignore
+++ b/app/static/app/bundles/.gitignore
@@ -1 +1 @@
-*.js
+*
\ No newline at end of file
diff --git a/app/static/app/css/main.css b/app/static/app/css/main.css
deleted file mode 100644
index cd13e661..00000000
--- a/app/static/app/css/main.css
+++ /dev/null
@@ -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 */
diff --git a/app/static/app/css/main.css.map b/app/static/app/css/main.css.map
deleted file mode 100644
index 167109f5..00000000
--- a/app/static/app/css/main.css.map
+++ /dev/null
@@ -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"
-}
\ No newline at end of file
diff --git a/app/static/app/js/Dashboard.jsx b/app/static/app/js/Dashboard.jsx
new file mode 100644
index 00000000..53468f57
--- /dev/null
+++ b/app/static/app/js/Dashboard.jsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import './css/Dashboard.scss';
+import ProjectList from './components/ProjectList';
+
+class Dashboard extends React.Component {
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default Dashboard;
diff --git a/app/static/app/js/app.jsx b/app/static/app/js/app.jsx
deleted file mode 100644
index 79a3ba05..00000000
--- a/app/static/app/js/app.jsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-
-class App extends React.Component {
- render() {
- return (
-
Hello World!
- );
- }
-}
-
-export default App;
diff --git a/app/static/app/js/components/ProjectList.jsx b/app/static/app/js/components/ProjectList.jsx
new file mode 100644
index 00000000..452765ad
--- /dev/null
+++ b/app/static/app/js/components/ProjectList.jsx
@@ -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 (Loading projects...
);
+ }
+ else if (this.state.projects){
+ return (
+ {this.state.projects.map(p => (
+
+ ))}
+
);
+ }else if (this.state.error){
+ return (An error occurred: {this.state.error}
);
+ }else{
+ return (); // should never happen
+ }
+ }
+}
+
+export default ProjectList;
diff --git a/app/static/app/js/components/ProjectListItem.jsx b/app/static/app/js/components/ProjectListItem.jsx
new file mode 100644
index 00000000..2dc26c55
--- /dev/null
+++ b/app/static/app/js/components/ProjectListItem.jsx
@@ -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 (
+
+ {this.props.data.name} {this.props.data.created_at}
+
+ {this.props.collapsed ? "" :
}
+
+ );
+ }
+}
+
+export default ProjectListItem;
diff --git a/app/static/app/js/components/ProjectListItemPanel.jsx b/app/static/app/js/components/ProjectListItemPanel.jsx
new file mode 100644
index 00000000..f4122340
--- /dev/null
+++ b/app/static/app/js/components/ProjectListItemPanel.jsx
@@ -0,0 +1,17 @@
+import React from 'react';
+
+class ProjectListItemPanel extends React.Component {
+ constructor(){
+ super();
+ }
+
+ render() {
+ return (
+
+ Sup!
+
+ );
+ }
+}
+
+export default ProjectListItemPanel;
diff --git a/app/static/app/js/css/Dashboard.scss b/app/static/app/js/css/Dashboard.scss
new file mode 100644
index 00000000..0d005282
--- /dev/null
+++ b/app/static/app/js/css/Dashboard.scss
@@ -0,0 +1,3 @@
+#dashboard-app{
+ background-color: yellow;
+}
\ No newline at end of file
diff --git a/app/static/app/js/index.jsx b/app/static/app/js/index.jsx
deleted file mode 100644
index 3b5773cb..00000000
--- a/app/static/app/js/index.jsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import App from './app';
-
-ReactDOM.render(, document.getElementById('react-app'))
diff --git a/app/static/app/js/main.js b/app/static/app/js/main.js
deleted file mode 100644
index 61c4d351..00000000
--- a/app/static/app/js/main.js
+++ /dev/null
@@ -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;
- }
- }
-});
diff --git a/app/static/app/js/main.jsx b/app/static/app/js/main.jsx
new file mode 100644
index 00000000..383668a6
--- /dev/null
+++ b/app/static/app/js/main.jsx
@@ -0,0 +1,6 @@
+import '../css/main.scss';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Dashboard from './Dashboard';
+
+ReactDOM.render(, document.getElementById('dashboard-app'));
diff --git a/app/templates/app/base.html b/app/templates/app/base.html
index 5bf688e0..eb6d7602 100644
--- a/app/templates/app/base.html
+++ b/app/templates/app/base.html
@@ -22,9 +22,8 @@
{% block extra-headers %}{% endblock %}
-
+
-
{{title|default:"Login"}} - WebODM
@@ -71,5 +70,44 @@