diff --git a/app/plugins/plugin_base.py b/app/plugins/plugin_base.py index 02c75659..de562a3e 100644 --- a/app/plugins/plugin_base.py +++ b/app/plugins/plugin_base.py @@ -1,5 +1,5 @@ import logging, os, sys -from abc import ABC, abstractmethod +from abc import ABC logger = logging.getLogger('app.logger') @@ -7,7 +7,6 @@ class PluginBase(ABC): def __init__(self): self.name = self.get_module_name().split(".")[-2] - @abstractmethod def register(self): pass @@ -27,6 +26,9 @@ class PluginBase(ABC): def get_include_js_urls(self): return ["/plugins/{}/{}".format(self.get_name(), js_file) for js_file in self.include_js_files()] + def get_include_css_urls(self): + return ["/plugins/{}/{}".format(self.get_name(), css_file) for css_file in self.include_css_files()] + def has_public_path(self): return os.path.isdir(self.get_path("public")) @@ -38,5 +40,13 @@ class PluginBase(ABC): """ return [] + def include_css_files(self): + """ + Should be overriden by plugins to communicate + which CSS files should be included in the WebODM interface + All paths are relative to a plugin's /public folder. + """ + return [] + def __str__(self): return "[{}]".format(self.get_module_name()) \ No newline at end of file diff --git a/app/static/app/css/theme.scss b/app/static/app/css/theme.scss index dfcec22c..b9c7b148 100644 --- a/app/static/app/css/theme.scss +++ b/app/static/app/css/theme.scss @@ -9,6 +9,9 @@ ul#side-menu.nav a, { color: theme("primary"); } +.theme-border-primary{ + border-color: theme("primary"); +} .tooltip{ .tooltip-inner{ background-color: theme("primary"); @@ -162,6 +165,9 @@ footer, .popover-title{ border-bottom-color: theme("border"); } +.theme-border{ + border-color: theme("border"); +} /* Highlight */ .task-list-item:nth-child(odd), diff --git a/app/static/app/js/classes/PluginsAPI.js b/app/static/app/js/classes/PluginsAPI.js deleted file mode 100644 index ab1e5375..00000000 --- a/app/static/app/js/classes/PluginsAPI.js +++ /dev/null @@ -1,26 +0,0 @@ -import { EventEmitter } from 'fbemitter'; -import Utils from './Utils'; - -const { assert } = Utils; - -if (!window.PluginsAPI){ - const events = new EventEmitter(); - - window.PluginsAPI = { - Map: { - AddPanel: (callback) => { - events.addListener('Map::Loaded', callback); - }, - - Loaded: (params) => { - assert(params.map !== undefined); - events.emit('Map::Loaded', params); - } - }, - - events - }; -} - -export default window.PluginsAPI; - diff --git a/app/static/app/js/classes/Utils.js b/app/static/app/js/classes/Utils.js index 4e4c528b..5f429602 100644 --- a/app/static/app/js/classes/Utils.js +++ b/app/static/app/js/classes/Utils.js @@ -73,6 +73,13 @@ export default { } throw message; // Fallback } + }, + + getCurrentScriptDir: function(){ + let scripts= document.getElementsByTagName('script'); + let path= scripts[scripts.length-1].src.split('?')[0]; // remove any ?query + let mydir= path.split('/').slice(0, -1).join('/')+'/'; // remove last filename part of path + return mydir; } }; diff --git a/app/static/app/js/classes/plugins/API.js b/app/static/app/js/classes/plugins/API.js new file mode 100644 index 00000000..c604cc82 --- /dev/null +++ b/app/static/app/js/classes/plugins/API.js @@ -0,0 +1,30 @@ +import { EventEmitter } from 'fbemitter'; +import ApiFactory from './ApiFactory'; +import Map from './Map'; +import $ from 'jquery'; +import SystemJS from 'SystemJS'; + +if (!window.PluginsAPI){ + const events = new EventEmitter(); + const factory = new ApiFactory(events); + + SystemJS.config({ + baseURL: '/plugins', + map: { + css: '/static/app/js/vendor/css.js' + }, + meta: { + '*.css': { loader: 'css' } + } + }); + + window.PluginsAPI = { + Map: factory.create(Map), + + SystemJS, + events + }; +} + +export default window.PluginsAPI; + diff --git a/app/static/app/js/classes/plugins/ApiFactory.js b/app/static/app/js/classes/plugins/ApiFactory.js new file mode 100644 index 00000000..03205c68 --- /dev/null +++ b/app/static/app/js/classes/plugins/ApiFactory.js @@ -0,0 +1,53 @@ +import SystemJS from 'SystemJS'; + +export default class ApiFactory{ + // @param events {EventEmitter} + constructor(events){ + this.events = events; + } + + // @param api {Object} + create(api){ + + // Adds two functions to obj + // - eventName + // - triggerEventName + // We could just use events, but methods + // are more robust as we can detect more easily if + // things break + const addEndpoint = (obj, eventName, preTrigger = () => {}) => { + obj[eventName] = (callbackOrDeps, callbackOrUndef) => { + if (Array.isArray(callbackOrDeps)){ + // Deps + // Load dependencies, then raise event as usual + // by appending the dependencies to the argument list + this.events.addListener(`${api.namespace}::${eventName}`, (...args) => { + Promise.all(callbackOrDeps.map(dep => SystemJS.import(dep))) + .then((...deps) => { + callbackOrUndef(...(Array.from(args).concat(...deps))); + }); + }); + }else{ + // Callback + this.events.addListener(`${api.namespace}::${eventName}`, callbackOrDeps); + } + } + + const triggerEventName = "trigger" + eventName[0].toUpperCase() + eventName.slice(1); + + obj[triggerEventName] = (...args) => { + preTrigger(...args); + this.events.emit(`${api.namespace}::${eventName}`, ...args); + }; + } + + const obj = {}; + api.endpoints.forEach(endpoint => { + if (!Array.isArray(endpoint)) endpoint = [endpoint]; + addEndpoint(obj, ...endpoint); + }); + return obj; + } + +} + diff --git a/app/static/app/js/classes/plugins/Map.js b/app/static/app/js/classes/plugins/Map.js new file mode 100644 index 00000000..368c861d --- /dev/null +++ b/app/static/app/js/classes/plugins/Map.js @@ -0,0 +1,17 @@ +import Utils from '../Utils'; + +const { assert } = Utils; + +const leafletPreCheck = (options) => { + assert(options.map !== undefined); +}; + +export default { + namespace: "Map", + + endpoints: [ + ["willAddControls", leafletPreCheck], + ["didAddControls", leafletPreCheck] + ] +}; + diff --git a/app/static/app/js/components/Map.jsx b/app/static/app/js/components/Map.jsx index 11f47ca7..9d96478f 100644 --- a/app/static/app/js/components/Map.jsx +++ b/app/static/app/js/components/Map.jsx @@ -3,8 +3,6 @@ import '../css/Map.scss'; import 'leaflet/dist/leaflet.css'; import Leaflet from 'leaflet'; import async from 'async'; -import 'leaflet-measure/dist/leaflet-measure.css'; -import 'leaflet-measure/dist/leaflet-measure'; import '../vendor/leaflet/L.Control.MousePosition.css'; import '../vendor/leaflet/L.Control.MousePosition'; import '../vendor/leaflet/Leaflet.Autolayers/css/leaflet.auto-layers.css'; @@ -15,7 +13,7 @@ import SwitchModeButton from './SwitchModeButton'; import ShareButton from './ShareButton'; import AssetDownloads from '../classes/AssetDownloads'; import PropTypes from 'prop-types'; -import PluginsAPI from '../classes/PluginsAPI'; +import PluginsAPI from '../classes/plugins/API'; class Map extends React.Component { static defaultProps = { @@ -173,16 +171,22 @@ class Map extends React.Component { this.map = Leaflet.map(this.container, { scrollWheelZoom: true, - positionControl: true + positionControl: true, + zoomControl: false }); - const measureControl = Leaflet.control.measure({ - primaryLengthUnit: 'meters', - secondaryLengthUnit: 'feet', - primaryAreaUnit: 'sqmeters', - secondaryAreaUnit: 'acres' + PluginsAPI.Map.triggerWillAddControls({ + map: this.map }); - measureControl.addTo(this.map); + + Leaflet.control.scale({ + maxWidth: 250, + }).addTo(this.map); + + //add zoom control with your options + Leaflet.control.zoom({ + position:'bottomleft' + }).addTo(this.map); if (showBackground) { this.basemaps = { @@ -215,10 +219,6 @@ class Map extends React.Component { }).addTo(this.map); this.map.fitWorld(); - - Leaflet.control.scale({ - maxWidth: 250, - }).addTo(this.map); this.map.attributionControl.setPrefix(""); this.loadImageryLayers(true).then(() => { @@ -239,7 +239,7 @@ class Map extends React.Component { // PluginsAPI.events.addListener('Map::AddPanel', (e) => { // console.log("Received response: " + e); // }); - PluginsAPI.Map.Loaded({ + PluginsAPI.Map.triggerDidAddControls({ map: this.map }); } @@ -275,6 +275,7 @@ class Map extends React.Component { return (