diff --git a/components/tools/OmeroWeb/omeroweb/settings.py b/components/tools/OmeroWeb/omeroweb/settings.py index 5521024f9a2..2fd550da489 100644 --- a/components/tools/OmeroWeb/omeroweb/settings.py +++ b/components/tools/OmeroWeb/omeroweb/settings.py @@ -534,6 +534,21 @@ def leave_none_unset_int(s): ("Django view which handles display of, or redirection to, the " "desired full image viewer.")], + # OPEN WITH + "omero.web.open_with": + ["OPEN_WITH", + ('[["Image viewer", "webindex", {"supported_objects": ["image"],' + '"script_url": "webclient/javascript/ome.openwith_viewer.js"}]]'), + json.loads, + ("A list of viewers that can be used to display selected Images " + "or other objects. Each viewer is defined as " + "``[\"Name\", \"url\", options]``. Url is reverse(url). " + "Selected objects are added to the url as ?image=:1&image=2" + "Objects supported must be specified in options with" + "E.g. ``{\"supported_objects\":[\"images\"]}`` " + "to enable viewer for one or more images, " + "``{\"target\":\"_blank\"}`` to open in new tab.")], + # PIPELINE 1.3.20 # Pipeline is an asset packaging library for Django, providing both CSS diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.openwith_viewer.js b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.openwith_viewer.js new file mode 100644 index 00000000000..043ccd452bf --- /dev/null +++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.openwith_viewer.js @@ -0,0 +1,24 @@ + +// openwith.js + + +// This example 'enabledHandler' code is not needed since we configure +// {'supported_objects': ['image']} to only enable the viewer when a +// single image is selected. +// However, it can be used if you need more flexibility to set enable/disable +// status of your Open with option. +// OME.setOpenWithEnabledHandler("Image viewer", function(selected) { +// // selected is a list of {'id':1, 'name': 'test.tiff', 'type': 'image'} +// // Only enabled for single objects... +// if (selected.length !== 1) return false; +// // Only enable for images +// return (selected[0].type !== 'image') return false; +// }); + + +// We have already configured the base url to be 'webindex' /webclient/ so +// we just need to add 'img_detail/' and the selected image ID +OME.setOpenWithUrlProvider("Image viewer", function(selected, url) { + url += "img_detail/" + selected[0].id + "/"; + return url; +}); diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.tree.js b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.tree.js index f052546d52d..458336349ee 100644 --- a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.tree.js +++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.tree.js @@ -987,6 +987,79 @@ $(function() { } } }; + if (WEBCLIENT.OPEN_WITH.length > 0) { + // build a submenu of viewers... + var viewers = WEBCLIENT.OPEN_WITH.map(function(v){ + return { + "label": v.label, + "action": function() { + var inst = $.jstree.reference('#dataTree'), + sel = inst.get_selected(true), + dtypes = sel.map(function(s){ + return s.type + "=" + s.data.id; + }), + query = dtypes.join("&"), + // default url includes objects in query + url = v.url + "?" + query; + // if plugin has added a url provider, + // use it to update the url... + if (v.getUrl) { + // prepare json of selected objects to pass to function + var selJson = sel.map(function(s){ + // var o = $.extend({}, s.data.obj); + var o = {'id': s.data.obj.id, + 'name': s.data.obj.name, + 'type': s.type}; + return o; + }); + url = v.getUrl(selJson, v.url); + } + // ...otherwise we use default handling... + if (v.target) { + // E.g. target '_blank' tries to open in a new tab + window.open(url, v.target); + } else { + OME.openPopup(url); + } + }, + "_disabled": function() { + var sel = $.jstree.reference('#dataTree').get_selected(true), + // selType = 'image' or 'images' or 'dataset' + selType = sel.reduce(function(prev, s){ + return s.type + (sel.length > 1 ? "s" : ""); + }, "undefined"), + enabled = false; + if (typeof v.isEnabled === "function") { + // If plugin has provided a function 'isEnabled'... + // prepare json of selected objects to pass to function + var selJson = sel.map(function(s){ + var o = {'id': s.data.obj.id, + 'name': s.data.obj.name, + 'type': s.type}; + return o; + }); + enabled = v.isEnabled(selJson); + return !enabled; + } + // ...Otherwise if supported_objects list is configured... + // v.supported_objects is ['image'] or ['dataset', 'images'] etc. + if (typeof v.supported_objects === "object" && v.supported_objects.length > 0) { + enabled = v.supported_objects.reduce(function(prev, supported){ + // E.g. If supported_objects is 'images'... + return prev || supported.indexOf(selType) > -1; // ... selType 'image' OR 'images' are > -1 + }, false); + } + return !enabled; + } + }; + }); + config["open_with"] = { + "label": "Open With...", + "_disabled": false, + "action": false, + "submenu": viewers + }; + } // List of permissions related disabling // use canLink, canDelete etc classes on each node to enable/disable right-click menu diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.webclient.actions.js b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.webclient.actions.js index 7e38be275a2..d1b9ffefb77 100644 --- a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.webclient.actions.js +++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.webclient.actions.js @@ -945,6 +945,38 @@ OME.hideScriptList = function() { $("#scriptList").hide(); }; +// Helper can be used by 'open with' plugins to add isEnabled() +// handlers to the OPEN_WITH object. +OME.setOpenWithEnabledHandler = function(label, fn) { + // look for label in OPEN_WITH + WEBCLIENT.OPEN_WITH.forEach(function(ow){ + if (ow.label === label) { + ow.isEnabled = function() { + // wrap fn with try/catch, since error here will break jsTree menu + var args = Array.from(arguments); + var enabled = false; + try { + enabled = fn.apply(this, args); + } catch (e) { + // Give user a clue as to what went wrong + console.log("Open with " + label + ": " + e); + } + return enabled; + } + } + }); +}; +// Helper can be used by 'open with' plugins to provide +// a url for the selected objects +OME.setOpenWithUrlProvider = function(label, fn) { + // look for label in OPEN_WITH + WEBCLIENT.OPEN_WITH.forEach(function(ow){ + if (ow.label === label) { + ow.getUrl = fn; + } + }); +}; + OME.toggleFileAnnotationCheckboxes = function(event) { var checkboxes = $("#fileanns_container input[type=checkbox]"); checkboxes.toggle().prop("checked", false); diff --git a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/base/base_container.html b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/base/base_container.html index d090678ed1f..8d24a965c6e 100644 --- a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/base/base_container.html +++ b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/base/base_container.html @@ -196,6 +196,24 @@ {% if page_size %} var PAGE_SIZE = {{ page_size }}; {% endif %} + + + // ** "Open With" config used by E.g. ome.tree.js ** + // Loaded scripts can call OME.setOpenWithEnabledHandler and/or + // OME.setOpenWithActionHandler to override default behaviour + WEBCLIENT.OPEN_WITH = []; + $.getJSON("{% url 'webgateway.views.open_with_options' %}", function(data){ + if (data && data.open_with_options) { + WEBCLIENT.OPEN_WITH = data.open_with_options; + // Try to load scripts if specified: + WEBCLIENT.OPEN_WITH.forEach(function(ow){ + if (ow.script_url) { + $.getScript(ow.script_url); + } + }) + } + }); + $(document).ready(function(){ // initially hidden $("#user_dropdown ul").css('visibility', 'hidden'); diff --git a/components/tools/OmeroWeb/omeroweb/webgateway/urls.py b/components/tools/OmeroWeb/omeroweb/webgateway/urls.py index 76d1d395773..15f539d73b7 100644 --- a/components/tools/OmeroWeb/omeroweb/webgateway/urls.py +++ b/components/tools/OmeroWeb/omeroweb/webgateway/urls.py @@ -413,6 +413,13 @@ 'client' is a list of paths for original files on the client when imported """ +open_with_options = url(r'^open_with/$', 'webgateway.views.open_with_options', + name='open_with_options') +""" +This makes the settings.OPEN_WITH configuration available via json +""" + + get_image_rdefs_json = url(r'^get_image_rdefs_json/(?P[0-9]+)/$', 'webgateway.views.get_image_rdefs_json', name="webgateway_get_image_rdefs_json") @@ -470,7 +477,7 @@ annotations, table_query, object_table_query, - + open_with_options, # Debug stuff ) diff --git a/components/tools/OmeroWeb/omeroweb/webgateway/views.py b/components/tools/OmeroWeb/omeroweb/webgateway/views.py index a913dd58a79..585cf9eadcd 100644 --- a/components/tools/OmeroWeb/omeroweb/webgateway/views.py +++ b/components/tools/OmeroWeb/omeroweb/webgateway/views.py @@ -23,7 +23,7 @@ from django.http import HttpResponseRedirect, HttpResponseNotAllowed, Http404 from django.template import loader as template_loader from django.views.decorators.http import require_POST -from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse, NoReverseMatch from django.conf import settings from django.template import RequestContext as Context from django.core.servers.basehttp import FileWrapper @@ -33,6 +33,7 @@ from plategrid import PlateGrid from omero_version import build_year from marshal import imageMarshal, shapeMarshal, rgb_int2rgba +from django.contrib.staticfiles.templatetags.staticfiles import static try: from hashlib import md5 @@ -1522,6 +1523,44 @@ def projectDetail_json(request, pid, conn=None, **kwargs): return rv +@jsonp +def open_with_options(request, **kwargs): + """ + Make the settings.OPEN_WITH available via JSON + """ + open_with = settings.OPEN_WITH + viewers = [] + for ow in open_with: + if len(ow) < 2: + continue + viewer = {} + viewer['label'] = ow[0] + try: + viewer['url'] = reverse(ow[1]) + except NoReverseMatch: + viewer['url'] = ow[1] + # try non-essential parameters... + # NB: Need supported_objects OR script_url to enable plugin + try: + if len(ow) > 2: + if 'supported_objects' in ow[2]: + viewer['supported_objects'] = ow[2]['supported_objects'] + if 'target' in ow[2]: + viewer['target'] = ow[2]['target'] + if 'script_url' in ow[2]: + # If we have an absolute url, use it... + if ow[2]['script_url'].startswith('http'): + viewer['script_url'] = ow[2]['script_url'] + else: + # ...otherwise, assume within static + viewer['script_url'] = static(ow[2]['script_url']) + except: + # ignore invalid params + pass + viewers.append(viewer) + return {'open_with_options': viewers} + + def searchOptFromRequest(request): """ Returns a dict of options for searching, based on