kopia lustrzana https://github.com/OpenDroneMap/WebODM
Short links plugin
rodzic
a061af7727
commit
f58e0b2acc
23
app/admin.py
23
app/admin.py
|
@ -139,9 +139,11 @@ class PluginAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
def plugin_enable(self, request, plugin_name, *args, **kwargs):
|
def plugin_enable(self, request, plugin_name, *args, **kwargs):
|
||||||
try:
|
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:
|
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'))
|
return HttpResponseRedirect(reverse('admin:app_plugin_changelist'))
|
||||||
|
|
||||||
|
@ -149,7 +151,7 @@ class PluginAdmin(admin.ModelAdmin):
|
||||||
try:
|
try:
|
||||||
disable_plugin(plugin_name)
|
disable_plugin(plugin_name)
|
||||||
except Exception as e:
|
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'))
|
return HttpResponseRedirect(reverse('admin:app_plugin_changelist'))
|
||||||
|
|
||||||
|
@ -157,7 +159,7 @@ class PluginAdmin(admin.ModelAdmin):
|
||||||
try:
|
try:
|
||||||
delete_plugin(plugin_name)
|
delete_plugin(plugin_name)
|
||||||
except Exception as e:
|
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'))
|
return HttpResponseRedirect(reverse('admin:app_plugin_changelist'))
|
||||||
|
|
||||||
|
@ -201,15 +203,15 @@ class PluginAdmin(admin.ModelAdmin):
|
||||||
clear_plugins_cache()
|
clear_plugins_cache()
|
||||||
init_plugins()
|
init_plugins()
|
||||||
|
|
||||||
messages.info(request, "Plugin added successfully")
|
messages.info(request, _("Plugin added successfully"))
|
||||||
except Exception as e:
|
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):
|
if os.path.exists(tmp_zip_path):
|
||||||
os.remove(tmp_zip_path)
|
os.remove(tmp_zip_path)
|
||||||
if os.path.exists(tmp_extract_path):
|
if os.path.exists(tmp_extract_path):
|
||||||
shutil.rmtree(tmp_extract_path)
|
shutil.rmtree(tmp_extract_path)
|
||||||
else:
|
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'))
|
return HttpResponseRedirect(reverse('admin:app_plugin_changelist'))
|
||||||
|
|
||||||
|
@ -217,15 +219,16 @@ class PluginAdmin(admin.ModelAdmin):
|
||||||
def plugin_actions(self, obj):
|
def plugin_actions(self, obj):
|
||||||
plugin = get_plugin_by_name(obj.name, only_active=False)
|
plugin = get_plugin_by_name(obj.name, only_active=False)
|
||||||
return format_html(
|
return format_html(
|
||||||
'<a class="button" href="{}" {}>Disable</a> '
|
'<a class="button" href="{}" {}>{}</a> '
|
||||||
'<a class="button" href="{}" {}>Enable</a>'
|
'<a class="button" href="{}" {}>{}</a>'
|
||||||
+ (' <a class="button" href="{}" onclick="return confirm(\'Are you sure you want to delete {}?\')"><i class="fa fa-trash"></i></a>' if not plugin.is_persistent() else ' ')
|
+ (' <a class="button" href="{}" onclick="return confirm(\'Are you sure you want to delete {}?\')"><i class="fa fa-trash"></i></a>' if not plugin.is_persistent() else ' ')
|
||||||
,
|
,
|
||||||
reverse('admin:plugin-disable', args=[obj.pk]) if obj.enabled else '#',
|
reverse('admin:plugin-disable', args=[obj.pk]) if obj.enabled else '#',
|
||||||
'disabled' if not obj.enabled else '',
|
'disabled' if not obj.enabled else '',
|
||||||
|
_('Disable'),
|
||||||
reverse('admin:plugin-enable', args=[obj.pk]) if not obj.enabled else '#',
|
reverse('admin:plugin-enable', args=[obj.pk]) if not obj.enabled else '#',
|
||||||
'disabled' if obj.enabled else '',
|
'disabled' if obj.enabled else '',
|
||||||
|
_('Enable'),
|
||||||
reverse('admin:plugin-delete', args=[obj.pk]),
|
reverse('admin:plugin-delete', args=[obj.pk]),
|
||||||
obj.name
|
obj.name
|
||||||
)
|
)
|
||||||
|
|
|
@ -325,6 +325,7 @@ def enable_plugin(plugin_name):
|
||||||
p = get_plugin_by_name(plugin_name, only_active=False)
|
p = get_plugin_by_name(plugin_name, only_active=False)
|
||||||
p.register()
|
p.register()
|
||||||
Plugin.objects.get(pk=plugin_name).enable()
|
Plugin.objects.get(pk=plugin_name).enable()
|
||||||
|
return p
|
||||||
|
|
||||||
def disable_plugin(plugin_name):
|
def disable_plugin(plugin_name):
|
||||||
Plugin.objects.get(pk=plugin_name).disable()
|
Plugin.objects.get(pk=plugin_name).disable()
|
||||||
|
|
|
@ -165,6 +165,13 @@ class PluginBase(ABC):
|
||||||
"""
|
"""
|
||||||
return []
|
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):
|
def main_menu(self):
|
||||||
"""
|
"""
|
||||||
Should be overriden by plugins that want to add
|
Should be overriden by plugins that want to add
|
||||||
|
@ -173,6 +180,19 @@ class PluginBase(ABC):
|
||||||
"""
|
"""
|
||||||
return []
|
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):
|
def app_mount_points(self):
|
||||||
"""
|
"""
|
||||||
Should be overriden by plugins that want to connect
|
Should be overriden by plugins that want to connect
|
||||||
|
|
|
@ -5,7 +5,7 @@ from app.api.workers import CheckTask as CheckTask
|
||||||
from app.api.workers import GetTaskResult as GetTaskResult
|
from app.api.workers import GetTaskResult as GetTaskResult
|
||||||
|
|
||||||
from django.http import HttpResponse, Http404
|
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.conf.urls import url
|
||||||
from django.views.static import serve
|
from django.views.static import serve
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
@ -58,4 +58,12 @@ def api_view_handler(request, plugin_name=None):
|
||||||
if view:
|
if view:
|
||||||
return view(request, *args, **kwargs)
|
return view(request, *args, **kwargs)
|
||||||
|
|
||||||
raise Http404("No valid routes")
|
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
|
|
@ -3,6 +3,7 @@ import ApiFactory from './ApiFactory';
|
||||||
import Map from './Map';
|
import Map from './Map';
|
||||||
import Dashboard from './Dashboard';
|
import Dashboard from './Dashboard';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import SharePopup from './SharePopup';
|
||||||
import SystemJS from 'SystemJS';
|
import SystemJS from 'SystemJS';
|
||||||
|
|
||||||
if (!window.PluginsAPI){
|
if (!window.PluginsAPI){
|
||||||
|
@ -31,6 +32,7 @@ if (!window.PluginsAPI){
|
||||||
Map: factory.create(Map),
|
Map: factory.create(Map),
|
||||||
Dashboard: factory.create(Dashboard),
|
Dashboard: factory.create(Dashboard),
|
||||||
App: factory.create(App),
|
App: factory.create(App),
|
||||||
|
SharePopup: factory.create(SharePopup),
|
||||||
|
|
||||||
SystemJS,
|
SystemJS,
|
||||||
events
|
events
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
export default {
|
||||||
|
namespace: "SharePopup",
|
||||||
|
|
||||||
|
endpoints: [
|
||||||
|
"addLinkControl",
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@ import ErrorMessage from './ErrorMessage';
|
||||||
import Utils from '../classes/Utils';
|
import Utils from '../classes/Utils';
|
||||||
import ClipboardInput from './ClipboardInput';
|
import ClipboardInput from './ClipboardInput';
|
||||||
import QRCode from 'qrcode.react';
|
import QRCode from 'qrcode.react';
|
||||||
|
import update from 'immutability-helper';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { _ } from '../classes/gettext';
|
import { _ } from '../classes/gettext';
|
||||||
|
|
||||||
|
@ -27,16 +28,32 @@ class SharePopup extends React.Component{
|
||||||
task: props.task,
|
task: props.task,
|
||||||
togglingShare: false,
|
togglingShare: false,
|
||||||
error: "",
|
error: "",
|
||||||
showQR: false
|
showQR: false,
|
||||||
|
linkControls: [], // coming from plugins,
|
||||||
|
relShareLink: this.getRelShareLink()
|
||||||
};
|
};
|
||||||
|
|
||||||
this.handleEnableSharing = this.handleEnableSharing.bind(this);
|
this.handleEnableSharing = this.handleEnableSharing.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRelShareLink = () => {
|
||||||
|
return `/public/task/${this.props.task.id}/${this.props.linksTarget}/`;
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount(){
|
componentDidMount(){
|
||||||
if (!this.state.task.public){
|
if (!this.state.task.public){
|
||||||
this.handleEnableSharing();
|
this.handleEnableSharing();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PluginsAPI.SharePopup.triggerAddLinkControl({
|
||||||
|
sharePopup: this
|
||||||
|
}, (ctrl) => {
|
||||||
|
if (!ctrl) return;
|
||||||
|
|
||||||
|
this.setState(update(this.state, {
|
||||||
|
linkControls: {$push: [ctrl]}
|
||||||
|
}));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEnableSharing(e){
|
handleEnableSharing(e){
|
||||||
|
@ -68,7 +85,7 @@ class SharePopup extends React.Component{
|
||||||
}
|
}
|
||||||
|
|
||||||
render(){
|
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 iframeUrl = Utils.absoluteUrl(`public/task/${this.state.task.id}/iframe/${this.props.linksTarget}/`);
|
||||||
const iframeCode = `<iframe scrolling="no" title="WebODM" width="61.8033%" height="360" frameBorder="0" src="${iframeUrl}"></iframe>`;
|
const iframeCode = `<iframe scrolling="no" title="WebODM" width="61.8033%" height="360" frameBorder="0" src="${iframeUrl}"></iframe>`;
|
||||||
|
|
||||||
|
@ -112,6 +129,12 @@ class SharePopup extends React.Component{
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={"form-group " + (this.state.showQR || this.state.linkControls.length === 0 ? "hide" : "")}>
|
||||||
|
{this.state.linkControls.map((Ctrl, i) =>
|
||||||
|
<Ctrl key={i}
|
||||||
|
sharePopup={this}
|
||||||
|
/>)}
|
||||||
|
</div>
|
||||||
<div className={"form-group " + (this.state.showQR ? "hide" : "")}>
|
<div className={"form-group " + (this.state.showQR ? "hide" : "")}>
|
||||||
<label>
|
<label>
|
||||||
{_("HTML iframe:")}
|
{_("HTML iframe:")}
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django.conf.urls import url, include
|
||||||
from django.views.i18n import JavaScriptCatalog
|
from django.views.i18n import JavaScriptCatalog
|
||||||
|
|
||||||
from .views import app as app_views, public as public_views, dev as dev_views
|
from .views import app as app_views, public as public_views, dev as dev_views
|
||||||
from .plugins.views import app_view_handler
|
from .plugins.views import app_view_handler, root_url_patterns
|
||||||
|
|
||||||
from app.boot import boot
|
from app.boot import boot
|
||||||
from webodm import settings
|
from webodm import settings
|
||||||
|
@ -45,7 +45,7 @@ urlpatterns = [
|
||||||
# TODO: add caching: https://docs.djangoproject.com/en/3.1/topics/i18n/translation/#note-on-performance
|
# TODO: add caching: https://docs.djangoproject.com/en/3.1/topics/i18n/translation/#note-on-performance
|
||||||
url(r'^jsi18n/', JavaScriptCatalog.as_view(packages=['app']), name='javascript-catalog'),
|
url(r'^jsi18n/', JavaScriptCatalog.as_view(packages=['app']), name='javascript-catalog'),
|
||||||
url(r'^i18n/', include('django.conf.urls.i18n')),
|
url(r'^i18n/', include('django.conf.urls.i18n')),
|
||||||
]
|
] + root_url_patterns()
|
||||||
|
|
||||||
handler404 = app_views.handler404
|
handler404 = app_views.handler404
|
||||||
handler500 = app_views.handler500
|
handler500 = app_views.handler500
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
from .plugin import *
|
|
@ -0,0 +1,58 @@
|
||||||
|
import math
|
||||||
|
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from app.plugins.views import TaskView
|
||||||
|
from app.plugins import get_current_plugin
|
||||||
|
from app.plugins import GlobalDataStore
|
||||||
|
from django.http import Http404
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
|
||||||
|
ds = GlobalDataStore('shortlinks')
|
||||||
|
|
||||||
|
def gen_short_string(num):
|
||||||
|
num = int(abs(num))
|
||||||
|
|
||||||
|
def nbase(num, numerals="abcdefghijklmnopqrstuvwxyz0123456789"):
|
||||||
|
return ((num == 0) and numerals[0]) or (nbase(num // len(numerals), numerals).lstrip(numerals[0]) + numerals[num % len(numerals)])
|
||||||
|
|
||||||
|
return nbase(num)
|
||||||
|
|
||||||
|
|
||||||
|
class GetShortLink(TaskView):
|
||||||
|
def post(self, request, pk=None):
|
||||||
|
task = self.get_and_check_task(request, pk)
|
||||||
|
key = str(task.id)
|
||||||
|
|
||||||
|
if ds.has_key(key):
|
||||||
|
# Return existing short link
|
||||||
|
return Response({'shortId': ds.get_string(key)}, status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
# Compute short link, store it
|
||||||
|
|
||||||
|
# Not atomic, but this shouldn't be a big problem
|
||||||
|
counter = ds.get_int("counter", 0)
|
||||||
|
ds.set_int("counter", counter + 1)
|
||||||
|
|
||||||
|
short_id = gen_short_string(counter)
|
||||||
|
|
||||||
|
# TaskId --> short id
|
||||||
|
ds.set_string(key, short_id)
|
||||||
|
|
||||||
|
# short id --> taskId
|
||||||
|
ds.set_string(short_id, str(task.id))
|
||||||
|
|
||||||
|
return Response({'shortId': short_id}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
def HandleShortLink(request, view_type, short_id):
|
||||||
|
if ds.has_key(short_id):
|
||||||
|
task_id = ds.get_string(short_id)
|
||||||
|
if view_type == 'm':
|
||||||
|
v = 'map'
|
||||||
|
elif view_type == '3':
|
||||||
|
v = '3d'
|
||||||
|
|
||||||
|
return redirect('/public/task/{}/{}/'.format(task_id, v))
|
||||||
|
else:
|
||||||
|
raise Http404()
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"name": "Short Links",
|
||||||
|
"webodmMinVersion": "1.9.3",
|
||||||
|
"description": "Create short links when sharing task URLs",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "Piero Toffanin",
|
||||||
|
"email": "pt@uav4geo.com",
|
||||||
|
"repository": "https://github.com/OpenDroneMap/WebODM",
|
||||||
|
"tags": ["short", "links"],
|
||||||
|
"homepage": "https://github.com/OpenDroneMap/WebODM",
|
||||||
|
"experimental": false,
|
||||||
|
"deprecated": false
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
from app.plugins import PluginBase, Menu, MountPoint
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
from .api import GetShortLink, HandleShortLink
|
||||||
|
|
||||||
|
class Plugin(PluginBase):
|
||||||
|
def build_jsx_components(self):
|
||||||
|
return ['SLCheckbox.jsx']
|
||||||
|
|
||||||
|
def include_js_files(self):
|
||||||
|
return ['main.js']
|
||||||
|
|
||||||
|
def root_mount_points(self):
|
||||||
|
return [
|
||||||
|
MountPoint(r'^s(?P<view_type>[m3])(?P<short_id>[a-z0-9]+)/?$', HandleShortLink)
|
||||||
|
]
|
||||||
|
|
||||||
|
def api_mount_points(self):
|
||||||
|
return [
|
||||||
|
MountPoint('task/(?P<pk>[^/.]+)/shortlink', GetShortLink.as_view()),
|
||||||
|
]
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import './SLCheckbox.scss';
|
||||||
|
import ErrorMessage from 'webodm/components/ErrorMessage';
|
||||||
|
import { _, interpolate } from 'webodm/classes/gettext';
|
||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
export default class SLCheckbox extends React.Component{
|
||||||
|
static defaultProps = {
|
||||||
|
sharePopup: null
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
sharePopup: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
error: '',
|
||||||
|
loading: false,
|
||||||
|
useShortLink: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(){
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleShortLinks = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!this.state.useShortLink && !this.state.loading){
|
||||||
|
this.setState({loading: true});
|
||||||
|
|
||||||
|
const task = this.props.sharePopup.props.task;
|
||||||
|
const linksTarget = this.props.sharePopup.props.linksTarget;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: `/api/plugins/shortlinks/task/${task.id}/shortlink`,
|
||||||
|
contentType: 'application/json'
|
||||||
|
}).done(res => {
|
||||||
|
const shortId = res.shortId;
|
||||||
|
const linksTargetChar = linksTarget === '3d' ? '3' : 'm';
|
||||||
|
|
||||||
|
const relShareLink = `s${linksTargetChar}${shortId}`;
|
||||||
|
|
||||||
|
if (shortId) this.props.sharePopup.setState({relShareLink});
|
||||||
|
else this.setState({error: interpolate(_('Invalid response from server: %(error)s'), { error: JSON.stringify(res)})});
|
||||||
|
|
||||||
|
this.setState({loading: false, useShortLink: !this.state.useShortLink});
|
||||||
|
}).fail(error => {
|
||||||
|
this.setState({error: interpolate(_('Invalid response from server: %(error)s'), { error }), loading: false});
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
this.props.sharePopup.setState({relShareLink: this.props.sharePopup.getRelShareLink()});
|
||||||
|
this.setState({useShortLink: !this.state.useShortLink});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
const { error, loading, useShortLink } = this.state;
|
||||||
|
|
||||||
|
if (error) return (<ErrorMessage bind={[this, "error"]} />);
|
||||||
|
|
||||||
|
return (<label className="slcheckbox">
|
||||||
|
{loading ?
|
||||||
|
<i className="fa fa-sync fa-spin fa-fw"></i>
|
||||||
|
: ""}
|
||||||
|
|
||||||
|
<input
|
||||||
|
className={this.props.sharePopup.state.togglingShare ? "hide" : ""}
|
||||||
|
type="checkbox"
|
||||||
|
checked={useShortLink}
|
||||||
|
onChange={this.toggleShortLinks}
|
||||||
|
/> {_("Use Short Link")}
|
||||||
|
</label>);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
.slcheckbox{
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
PluginsAPI.SharePopup.addLinkControl([
|
||||||
|
'shortlinks/build/SLCheckbox.js',
|
||||||
|
'shortlinks/build/SLCheckbox.css'
|
||||||
|
],function(args, SLCheckbox){
|
||||||
|
return SLCheckbox;
|
||||||
|
}
|
||||||
|
);
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "WebODM",
|
"name": "WebODM",
|
||||||
"version": "1.9.2",
|
"version": "1.9.3",
|
||||||
"description": "User-friendly, extendable application and API for processing aerial imagery.",
|
"description": "User-friendly, extendable application and API for processing aerial imagery.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
Ładowanie…
Reference in New Issue