diff --git a/Dockerfile b/Dockerfile
index 43930c8b..2a74ac19 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -36,7 +36,7 @@ WORKDIR /webodm/nodeodm/external/node-OpenDroneMap
RUN npm install --quiet
WORKDIR /webodm
-RUN npm install --quiet -g webpack && npm install --quiet -g webpack-cli && npm install --quiet && webpack
+RUN npm install --quiet -g webpack && npm install --quiet -g webpack-cli && npm install --quiet && webpack --mode production
RUN python manage.py collectstatic --noinput
RUN rm /webodm/webodm/secret_key.py
diff --git a/README.md b/README.md
index f87c0273..202e0fd6 100644
--- a/README.md
+++ b/README.md
@@ -358,7 +358,7 @@ pip install -r requirements.txt
sudo npm install -g webpack
sudo npm install -g webpack-cli
npm install
-webpack
+webpack --mode production
python manage.py collectstatic --noinput
chmod +x start.sh && ./start.sh --no-gunicorn
```
diff --git a/app/plugins/functions.py b/app/plugins/functions.py
index 7932bf8d..1db76f8a 100644
--- a/app/plugins/functions.py
+++ b/app/plugins/functions.py
@@ -7,6 +7,9 @@ import django
import json
from django.conf.urls import url
from functools import reduce
+from string import Template
+
+from django.http import HttpResponse
from webodm import settings
@@ -18,9 +21,36 @@ def register_plugins():
# Check for package.json in public directory
# and run npm install if needed
if plugin.path_exists("public/package.json") and not plugin.path_exists("public/node_modules"):
- logger.info("Running npm install for {}".format(plugin.get_name()))
+ logger.info("Running npm install for {}".format(plugin))
subprocess.call(['npm', 'install'], cwd=plugin.get_path("public"))
+ # Check if we need to generate a webpack.config.js
+ if len(plugin.build_jsx_components()) > 0 and plugin.path_exists('public'):
+ logger.info("Generating webpack.config.js for {}".format(plugin))
+
+ build_paths = map(lambda p: os.path.join(plugin.get_path('public'), p), plugin.build_jsx_components())
+ paths_ok = not (False in map(lambda p: os.path.exists, build_paths))
+
+ if paths_ok:
+ wpc_path = os.path.join(settings.BASE_DIR, 'app', 'plugins', 'templates', 'webpack.config.js.tmpl')
+ with open(wpc_path) as f:
+ tmpl = Template(f.read())
+
+ # Create entry configuration
+ entry = {}
+ for e in plugin.build_jsx_components():
+ entry[os.path.splitext(os.path.basename(e))[0]] = [os.path.join('.', e)]
+ wpc_content = tmpl.substitute({
+ 'entry_json': json.dumps(entry)
+ })
+
+ with open(plugin.get_path('public/webpack.config.js'), 'w') as f:
+ f.write(wpc_content)
+ logger.info('Wrote public/webpack.config.js for {}'.format(plugin))
+ else:
+ logger.warning("Cannot generate webpack.config.js for {}, a path is missing: {}".format(plugin, ' '.join(build_paths)))
+
+
# Check for webpack.config.js (if we need to build it)
if plugin.path_exists("public/webpack.config.js") and not plugin.path_exists("public/build"):
logger.info("Running webpack for {}".format(plugin.get_name()))
@@ -125,6 +155,22 @@ def get_plugins_path():
return os.path.abspath(os.path.join(current_path, "..", "..", "plugins"))
+def get_dynamic_script_handler(script_path, callback=None, **kwargs):
+ def handleRequest(request):
+ if callback is not None:
+ template_params = callback(request, **kwargs)
+ if not template_params:
+ return HttpResponse("")
+ else:
+ template_params = kwargs
+
+ with open(script_path) as f:
+ tmpl = Template(f.read())
+ return HttpResponse(tmpl.substitute(template_params))
+
+ return handleRequest
+
+
def versionToInt(version):
"""
Converts a WebODM version string (major.minor.build) to a integer value
diff --git a/app/plugins/grass_engine.py b/app/plugins/grass_engine.py
index 021824ee..7ac08a98 100644
--- a/app/plugins/grass_engine.py
+++ b/app/plugins/grass_engine.py
@@ -3,7 +3,6 @@ import shutil
import tempfile
import subprocess
import os
-import geojson
from string import Template
diff --git a/app/plugins/plugin_base.py b/app/plugins/plugin_base.py
index a28c1fb9..15c9c6a7 100644
--- a/app/plugins/plugin_base.py
+++ b/app/plugins/plugin_base.py
@@ -82,6 +82,15 @@ class PluginBase(ABC):
"""
return []
+ def build_jsx_components(self):
+ """
+ Experimental
+ Should be overriden by plugins that want to automatically
+ build JSX files.
+ All paths are relative to a plugin's /public folder.
+ """
+ return []
+
def main_menu(self):
"""
Should be overriden by plugins that want to add
@@ -106,5 +115,20 @@ class PluginBase(ABC):
"""
return []
+ def get_dynamic_script(self, script_path, callback = None, **template_args):
+ """
+ Retrieves a view handler that serves a dynamic script from
+ the plugin's directory. Dynamic scripts are normal Javascript
+ files that optionally support Template variable substitution
+ via ${vars}, computed on the server.
+ :param script_path: path to script relative to plugin's directory.
+ :param callback: optional callback. The callback can prevent the script from being returned if it returns False.
+ If it returns a dictionary, the dictionary items are used for variable substitution.
+ :param template_args: Parameters to use for variable substitution (unless a callback is specified)
+ :return: Django view
+ """
+ from app.plugins import get_dynamic_script_handler
+ return get_dynamic_script_handler(self.get_path(script_path), callback, **template_args)
+
def __str__(self):
return "[{}]".format(self.get_module_name())
\ No newline at end of file
diff --git a/plugins/measure/public/webpack.config.js b/app/plugins/templates/webpack.config.js.tmpl
similarity index 94%
rename from plugins/measure/public/webpack.config.js
rename to app/plugins/templates/webpack.config.js.tmpl
index 607d9ef3..b28f0450 100644
--- a/plugins/measure/public/webpack.config.js
+++ b/app/plugins/templates/webpack.config.js.tmpl
@@ -10,9 +10,7 @@ module.exports = {
mode: 'production',
context: __dirname,
- entry: {
- app: ['./app.jsx']
- },
+ entry: ${entry_json},
output: {
path: path.join(__dirname, './build'),
@@ -30,7 +28,7 @@ module.exports = {
module: {
rules: [
{
- test: /\.jsx?$/,
+ test: /\.jsx?$$/,
exclude: /(node_modules|bower_components)/,
use: [
{
@@ -49,7 +47,7 @@ module.exports = {
],
},
{
- test: /\.s?css$/,
+ test: /\.s?css$$/,
use: ExtractTextPlugin.extract({
use: 'css-loader!sass-loader'
})
diff --git a/app/static/app/js/classes/plugins/ApiFactory.js b/app/static/app/js/classes/plugins/ApiFactory.js
index 749b6bfd..39aeb09d 100644
--- a/app/static/app/js/classes/plugins/ApiFactory.js
+++ b/app/static/app/js/classes/plugins/ApiFactory.js
@@ -46,8 +46,20 @@ export default class ApiFactory{
obj[triggerEventName] = (params, responseCb) => {
preTrigger(params, responseCb);
+ if (responseCb){
+ this.events.addListener(`${api.namespace}::${eventName}::Response`, (...args) => {
+ // Give time to all listeners to receive the replies
+ // then remove the listener to avoid sending duplicate responses
+ const curSub = this.events._currentSubscription;
+
+ setTimeout(() => {
+ curSub.remove();
+ }, 0);
+
+ responseCb(...args);
+ });
+ }
this.events.emit(`${api.namespace}::${eventName}`, params);
- if (responseCb) this.events.addListener(`${api.namespace}::${eventName}::Response`, responseCb);
};
}
diff --git a/app/static/app/js/components/TaskListItem.jsx b/app/static/app/js/components/TaskListItem.jsx
index 37bf24a4..34a43de9 100644
--- a/app/static/app/js/components/TaskListItem.jsx
+++ b/app/static/app/js/components/TaskListItem.jsx
@@ -514,7 +514,7 @@ class TaskListItem extends React.Component {