From f58e0b2accb245d82efd823a7492567c69637b36 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Thu, 1 Jul 2021 15:30:45 -0400 Subject: [PATCH] Short links plugin --- app/admin.py | 23 +++--- app/plugins/functions.py | 1 + app/plugins/plugin_base.py | 20 +++++ app/plugins/views.py | 12 ++- app/static/app/js/classes/plugins/API.js | 2 + .../app/js/classes/plugins/SharePopup.js | 8 ++ app/static/app/js/components/SharePopup.jsx | 27 ++++++- app/urls.py | 4 +- coreplugins/shortlinks/__init__.py | 1 + coreplugins/shortlinks/api.py | 58 ++++++++++++++ coreplugins/shortlinks/disabled | 0 coreplugins/shortlinks/manifest.json | 13 +++ coreplugins/shortlinks/plugin.py | 23 ++++++ coreplugins/shortlinks/public/SLCheckbox.jsx | 79 +++++++++++++++++++ coreplugins/shortlinks/public/SLCheckbox.scss | 4 + coreplugins/shortlinks/public/main.js | 7 ++ package.json | 2 +- 17 files changed, 267 insertions(+), 17 deletions(-) create mode 100644 app/static/app/js/classes/plugins/SharePopup.js create mode 100644 coreplugins/shortlinks/__init__.py create mode 100644 coreplugins/shortlinks/api.py create mode 100644 coreplugins/shortlinks/disabled create mode 100644 coreplugins/shortlinks/manifest.json create mode 100644 coreplugins/shortlinks/plugin.py create mode 100644 coreplugins/shortlinks/public/SLCheckbox.jsx create mode 100644 coreplugins/shortlinks/public/SLCheckbox.scss create mode 100644 coreplugins/shortlinks/public/main.js diff --git a/app/admin.py b/app/admin.py index 522f78ef..aa5f7ee7 100644 --- a/app/admin.py +++ b/app/admin.py @@ -139,9 +139,11 @@ class PluginAdmin(admin.ModelAdmin): def plugin_enable(self, request, plugin_name, *args, **kwargs): try: - enable_plugin(plugin_name) + p = enable_plugin(plugin_name) + if p.requires_restart(): + messages.warning(request, _("Restart required. Please restart WebODM to enable %(plugin)s") % {'plugin': plugin_name}) except Exception as e: - messages.warning(request, "Cannot enable plugin {}: {}".format(plugin_name, str(e))) + messages.warning(request, _("Cannot enable plugin %(plugin)s: %(message)s") % {'plugin': plugin_name, 'message': str(e)}) return HttpResponseRedirect(reverse('admin:app_plugin_changelist')) @@ -149,7 +151,7 @@ class PluginAdmin(admin.ModelAdmin): try: disable_plugin(plugin_name) except Exception as e: - messages.warning(request, "Cannot disable plugin {}: {}".format(plugin_name, str(e))) + messages.warning(request, _("Cannot disable plugin %(plugin)s: %(message)s") % {'plugin': plugin_name, 'message': str(e)}) return HttpResponseRedirect(reverse('admin:app_plugin_changelist')) @@ -157,7 +159,7 @@ class PluginAdmin(admin.ModelAdmin): try: delete_plugin(plugin_name) except Exception as e: - messages.warning(request, "Cannot delete plugin {}: {}".format(plugin_name, str(e))) + messages.warning(request, _("Cannot delete plugin %(plugin)s: %(message)s") % {'plugin': plugin_name, 'message': str(e)}) return HttpResponseRedirect(reverse('admin:app_plugin_changelist')) @@ -201,15 +203,15 @@ class PluginAdmin(admin.ModelAdmin): clear_plugins_cache() init_plugins() - messages.info(request, "Plugin added successfully") + messages.info(request, _("Plugin added successfully")) except Exception as e: - messages.warning(request, "Cannot load plugin: {}".format(str(e))) + messages.warning(request, _("Cannot load plugin: %(message)s") % {'message': str(e)}) if os.path.exists(tmp_zip_path): os.remove(tmp_zip_path) if os.path.exists(tmp_extract_path): shutil.rmtree(tmp_extract_path) else: - messages.error(request, "You need to upload a zip file") + messages.error(request, _("You need to upload a zip file")) return HttpResponseRedirect(reverse('admin:app_plugin_changelist')) @@ -217,15 +219,16 @@ class PluginAdmin(admin.ModelAdmin): def plugin_actions(self, obj): plugin = get_plugin_by_name(obj.name, only_active=False) return format_html( - 'Disable ' - 'Enable' + '{} ' + '{}' + (' ' if not plugin.is_persistent() else '      ') , reverse('admin:plugin-disable', args=[obj.pk]) if obj.enabled else '#', 'disabled' if not obj.enabled else '', + _('Disable'), reverse('admin:plugin-enable', args=[obj.pk]) if not obj.enabled else '#', 'disabled' if obj.enabled else '', - + _('Enable'), reverse('admin:plugin-delete', args=[obj.pk]), obj.name ) diff --git a/app/plugins/functions.py b/app/plugins/functions.py index 733c2c8f..d11c23ca 100644 --- a/app/plugins/functions.py +++ b/app/plugins/functions.py @@ -325,6 +325,7 @@ def enable_plugin(plugin_name): p = get_plugin_by_name(plugin_name, only_active=False) p.register() Plugin.objects.get(pk=plugin_name).enable() + return p def disable_plugin(plugin_name): Plugin.objects.get(pk=plugin_name).disable() diff --git a/app/plugins/plugin_base.py b/app/plugins/plugin_base.py index c2ff9e8a..9b1ef035 100644 --- a/app/plugins/plugin_base.py +++ b/app/plugins/plugin_base.py @@ -165,6 +165,13 @@ class PluginBase(ABC): """ return [] + def requires_restart(self): + """ + Whether the plugin requires an app restart to + function properly + """ + return len(self.root_mount_points()) > 0 + def main_menu(self): """ Should be overriden by plugins that want to add @@ -173,6 +180,19 @@ class PluginBase(ABC): """ return [] + def root_mount_points(self): + """ + Should be overriden by plugins that want to + add routes to the root view controller. + CAUTION: this should be used sparingly, as + routes could conflict with other plugins and + future versions of WebODM might break the routes. + It's recommended to use app_mount_points, unless + you know what you are doing. + :return: [] of MountPoint objects + """ + return [] + def app_mount_points(self): """ Should be overriden by plugins that want to connect diff --git a/app/plugins/views.py b/app/plugins/views.py index cea6e624..8944a0a9 100644 --- a/app/plugins/views.py +++ b/app/plugins/views.py @@ -5,7 +5,7 @@ from app.api.workers import CheckTask as CheckTask from app.api.workers import GetTaskResult as GetTaskResult from django.http import HttpResponse, Http404 -from .functions import get_plugin_by_name +from .functions import get_plugin_by_name, get_active_plugins from django.conf.urls import url from django.views.static import serve from urllib.parse import urlparse @@ -58,4 +58,12 @@ def api_view_handler(request, plugin_name=None): if view: return view(request, *args, **kwargs) - raise Http404("No valid routes") \ No newline at end of file + raise Http404("No valid routes") + +def root_url_patterns(): + result = [] + for p in get_active_plugins(): + for mount_point in p.root_mount_points(): + result.append(url(mount_point.url, mount_point.view, *mount_point.args, **mount_point.kwargs)) + + return result \ No newline at end of file diff --git a/app/static/app/js/classes/plugins/API.js b/app/static/app/js/classes/plugins/API.js index 259dcc2d..f41bd4f7 100644 --- a/app/static/app/js/classes/plugins/API.js +++ b/app/static/app/js/classes/plugins/API.js @@ -3,6 +3,7 @@ import ApiFactory from './ApiFactory'; import Map from './Map'; import Dashboard from './Dashboard'; import App from './App'; +import SharePopup from './SharePopup'; import SystemJS from 'SystemJS'; if (!window.PluginsAPI){ @@ -31,6 +32,7 @@ if (!window.PluginsAPI){ Map: factory.create(Map), Dashboard: factory.create(Dashboard), App: factory.create(App), + SharePopup: factory.create(SharePopup), SystemJS, events diff --git a/app/static/app/js/classes/plugins/SharePopup.js b/app/static/app/js/classes/plugins/SharePopup.js new file mode 100644 index 00000000..6dd21405 --- /dev/null +++ b/app/static/app/js/classes/plugins/SharePopup.js @@ -0,0 +1,8 @@ +export default { + namespace: "SharePopup", + + endpoints: [ + "addLinkControl", + ] +}; + diff --git a/app/static/app/js/components/SharePopup.jsx b/app/static/app/js/components/SharePopup.jsx index d6815384..b33dda15 100644 --- a/app/static/app/js/components/SharePopup.jsx +++ b/app/static/app/js/components/SharePopup.jsx @@ -5,6 +5,7 @@ import ErrorMessage from './ErrorMessage'; import Utils from '../classes/Utils'; import ClipboardInput from './ClipboardInput'; import QRCode from 'qrcode.react'; +import update from 'immutability-helper'; import $ from 'jquery'; import { _ } from '../classes/gettext'; @@ -27,16 +28,32 @@ class SharePopup extends React.Component{ task: props.task, togglingShare: false, error: "", - showQR: false + showQR: false, + linkControls: [], // coming from plugins, + relShareLink: this.getRelShareLink() }; this.handleEnableSharing = this.handleEnableSharing.bind(this); } + getRelShareLink = () => { + return `/public/task/${this.props.task.id}/${this.props.linksTarget}/`; + } + componentDidMount(){ if (!this.state.task.public){ this.handleEnableSharing(); } + + PluginsAPI.SharePopup.triggerAddLinkControl({ + sharePopup: this + }, (ctrl) => { + if (!ctrl) return; + + this.setState(update(this.state, { + linkControls: {$push: [ctrl]} + })); + }); } handleEnableSharing(e){ @@ -68,7 +85,7 @@ class SharePopup extends React.Component{ } render(){ - const shareLink = Utils.absoluteUrl(`/public/task/${this.state.task.id}/${this.props.linksTarget}/`); + const shareLink = Utils.absoluteUrl(this.state.relShareLink); const iframeUrl = Utils.absoluteUrl(`public/task/${this.state.task.id}/iframe/${this.props.linksTarget}/`); const iframeCode = ``; @@ -112,6 +129,12 @@ class SharePopup extends React.Component{ /> +
+ {this.state.linkControls.map((Ctrl, i) => + )} +