diff --git a/components/tests/ui/resources/web/tree.txt b/components/tests/ui/resources/web/tree.txt
index 7b4319398e7..6aea0fd01c7 100644
--- a/components/tests/ui/resources/web/tree.txt
+++ b/components/tests/ui/resources/web/tree.txt
@@ -299,7 +299,7 @@ Wait Until Right Panel Loads Everything
Wait Until Center Panel Loads
[Arguments] ${containerType}
Run Keyword If '${containerType}' == 'Dataset' Wait Until Element Is Visible //*[@id="content_details"]/div[1]
- Run Keyword If '${containerType}' == 'Dataset' Wait Until Element Is Visible xpath=//*[@id="filtersearch"]/div/label[contains(text(),'Filter Images')]
+ Run Keyword If '${containerType}' == 'Dataset' Wait Until Element Is Visible xpath=//*[@id="filtersearch"]
Run Keyword If '${containerType}' == 'Dataset' Wait Until Element Is Visible xpath=//*[@id="icon_layout"][contains(@title,"View as Thumbnails")][contains(@class,"checked")]
Run Keyword If '${containerType}' == 'Dataset' Wait Until Element Is Visible //*[@id="table_layout"][contains(@title,"View as List")]
@@ -512,9 +512,10 @@ Shift Click Node
[Arguments] ${nodeId}
Shift Click Element "#${nodeId}>a"
-Shift Click Thumbnail
- [Arguments] ${iid}
- Shift Click Element "#image_icon-${iid}"
+# Conflicts with similar jQuery events in IconTable.jsx
+# Shift Click Thumbnail
+# [Arguments] ${iid}
+# Shift Click Element "#image_icon-${iid}"
Shift Click Element
[Arguments] ${elementSelector}
@@ -524,9 +525,10 @@ Meta Click Node
[Arguments] ${nodeId}
Meta Click Element "#${nodeId}>a"
-Meta Click Thumbnail
- [Arguments] ${iid}
- Meta Click Element "#image_icon-${iid}"
+# Conflicts with similar jQuery events in IconTable.jsx
+# Meta Click Thumbnail
+# [Arguments] ${iid}
+# Meta Click Element "#image_icon-${iid}"
Meta Click Element
[Arguments] ${elementSelector}
diff --git a/components/tests/ui/testcases/web/center_right_panel_tests.txt b/components/tests/ui/testcases/web/center_right_panel_tests.txt
index 543e505f231..44934a5e6d0 100644
--- a/components/tests/ui/testcases/web/center_right_panel_tests.txt
+++ b/components/tests/ui/testcases/web/center_right_panel_tests.txt
@@ -94,10 +94,16 @@ Check Multi Selections
${imageId} Select First Orphaned Image
Click Next Thumbnail
${imageId1} Get Id From Selected Thumbnail
- Meta Click Thumbnail ${imageId}
+ Should Not Be Equal ${imageId} ${imageId1}
+
+ # Meta Click Thumbnail uses jQuery - conflicts with similar events in IconTable.jsx
+ # Meta Click on Node instead
+ ${nodeId}= Wait For Image Node ${imageId}
+ Meta Click Node ${nodeId}
${numOfObjects} Get Number Of Selected Objects From Tree
${numOfObjects1} Get Number Of Selected Objects From Center Panel
+ Should Be Equal ${numOfObjects} 2
Should Be Equal ${numOfObjects} ${numOfObjects1}
Wait Until Right Panel Loads For MultiSelection ${numOfObjects}
diff --git a/components/tests/ui/testcases/web/spw_test.txt b/components/tests/ui/testcases/web/spw_test.txt
index a9e7b7ac20d..e87d36fdb3f 100644
--- a/components/tests/ui/testcases/web/spw_test.txt
+++ b/components/tests/ui/testcases/web/spw_test.txt
@@ -13,10 +13,8 @@ Suite Teardown Close all browsers
Click Well By Name
[Arguments] ${name}
- Wait Until Page Contains Element xpath=//td[contains(@class,'well')]/img[@name='${name}']
- # Have to be sure that thumbnail itself has loaded before image is clickable!
- Sleep 1
- Click Element xpath=//td[contains(@class,'well')]/img[@name='${name}']
+ Wait Until Page Contains Element xpath=//td[contains(@class,'well')][@title='${name}']
+ Click Element xpath=//td[contains(@class,'well')][@title='${name}']
Bulk Annotation Should Contain Row
[Arguments] ${key} ${value}
diff --git a/components/tools/OmeroWeb/omeroweb/.gitignore b/components/tools/OmeroWeb/omeroweb/.gitignore
index d8d770c7114..5203f4d3bd9 100644
--- a/components/tools/OmeroWeb/omeroweb/.gitignore
+++ b/components/tools/OmeroWeb/omeroweb/.gitignore
@@ -1,2 +1,3 @@
db.sqlite3
django
+node_modules/
diff --git a/components/tools/OmeroWeb/omeroweb/package.json b/components/tools/OmeroWeb/omeroweb/package.json
new file mode 100644
index 00000000000..cef18c9e6cb
--- /dev/null
+++ b/components/tools/OmeroWeb/omeroweb/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "omeroweb",
+ "version": "1.0.0",
+ "description": "'OMERO.web application'",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "GPL-3.0",
+ "devDependencies": {
+ "babel-core": "^6.4.5",
+ "babel-loader": "^6.2.2",
+ "babel-preset-es2015": "^6.3.13",
+ "babel-preset-react": "^6.3.13",
+ "jsx-loader": "^0.13.2",
+ "react": "^0.14.7",
+ "react-dom": "^0.14.7",
+ "webpack": "^1.12.13"
+ }
+}
diff --git a/components/tools/OmeroWeb/omeroweb/webclient/decorators.py b/components/tools/OmeroWeb/omeroweb/webclient/decorators.py
index d0d3c1a1459..53cb1594b7f 100644
--- a/components/tools/OmeroWeb/omeroweb/webclient/decorators.py
+++ b/components/tools/OmeroWeb/omeroweb/webclient/decorators.py
@@ -183,3 +183,5 @@ def load_settings(self, request, context, conn):
c_plugins.append({
"label": label, "include": include, "plugin_id": plugin_id})
context['ome']['center_plugins'] = c_plugins
+
+ context['ome']['page_size'] = settings.PAGE
diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/css/dusty.css b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/css/dusty.css
index ec2fb7ee0e1..68a19034e40 100755
--- a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/css/dusty.css
+++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/css/dusty.css
@@ -930,8 +930,42 @@ button::-moz-focus-inner {
+ .iconTableHeader, .iconTableFooter, .iconTable, .plateContainer, .centrePanel {
+ position: absolute;
+ left: 0;
+ right: 0;
+ border-right: 0;
+ }
+
+ .centrePanel {
+ top: 0;
+ bottom: 0;
+ }
+
+ .iconTableHeader {
+ top: 0;
+ height: 29px;
+ }
+
+ .iconTableFooter {
+ bottom: 0;
+ height: 25px;
+ }
+ .iconTable, .plateContainer {
+ bottom:0;
+ overflow:auto;
+ margin-top:0px;
+ }
+
+ .iconTable {
+ top:29px;
+ }
+ .plateContainer {
+ top: 0;
+
+ }
@@ -993,6 +1027,14 @@ button::-moz-focus-inner {
transition: opacity .2s linear;
}
+
+ .filterCounter {
+ padding: 8px 10px;
+ position: absolute;
+ left: 170px;
+ color: #555;
+ font-size: 13px
+ }
diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/css/layout.css b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/css/layout.css
index 95749d993e9..6030b41e45c 100644
--- a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/css/layout.css
+++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/css/layout.css
@@ -1493,6 +1493,7 @@
.iconLayout li {
display: inline-block;
+ outline: none;
background-color:hsl(0,0%,95%);
margin:5px;
padding:5px;
@@ -2136,11 +2137,19 @@ div.paging {
margin: 0;
padding: 10px 0px 30px 0px;
}
+/* prev/next buttons */
+div.paging button {
+ margin-left: 5px;
+}
+/* page numbers */
div.paging input.button_pagination {
border: 0;
background-color: transparent;
color: #1b7bc7;
- padding: 0;
+ padding: 2px;
+}
+div.paging input.button_pagination:disabled {
+ color: black;
}
#spw {
@@ -2156,7 +2165,7 @@ div.paging input.button_pagination {
vertical-align: middle;
}
#spw table td {
- padding: 1px;
+ padding: 2px;
border: 1px solid #ccc;
width: 25px;
}
@@ -2167,8 +2176,8 @@ div.paging input.button_pagination {
vertical-align: top;
}
-#spw table td.ui-selecting { border: 1px dashed #555; padding: 1px; background-color:#87ABD2;}
-#spw table td.ui-selected { border: 1px dashed #555; padding: 1px; background-color:#87ABD2;}
+#spw table td.ui-selecting { border: 1px dashed #555; background-color:#87ABD2;}
+#spw table td.ui-selected { border: 1px dashed #555; background-color:#87ABD2;}
diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/jquery.jstree.filter_plugin.js b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/jquery.jstree.filter_plugin.js
new file mode 100644
index 00000000000..0622171808d
--- /dev/null
+++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/jquery.jstree.filter_plugin.js
@@ -0,0 +1,49 @@
+
+
+(function ($, undefined) {
+ "use strict";
+
+ $.jstree.plugins.filter = function (options, parent) {
+
+ this.filter = function(obj, filterString) {
+ // we are filtering images within a single parent obj (Dataset)
+ if (!obj || !obj.id || obj.id === "#") {return false;}
+ obj = this.get_node(obj);
+
+ var selectedNodeIds = [],
+ inst = this;
+
+ // If parent node not selected,
+ // get IDs of currently selected nodes
+ if (!this.is_selected(obj)) {
+ this.get_selected(true).forEach(function(n){
+ selectedNodeIds.push(n.type + "-" + n.data.obj.id);
+ });
+ this.deselect_all(true);
+ }
+
+ // One-time callback when refresh completes (triggers 'load_node')
+ $("#dataTree").one("load_node.jstree", function reselectNodes() {
+ // Try to reselect images that have not been filtered
+ selectedNodeIds.forEach(function(id){
+ var n = inst.locate_node(id, obj)[0];
+ if (n) {
+ inst.select_node(n);
+ }
+ });
+ // if nothing was re-selected, need to trigger refresh
+ if (inst.get_selected().length === 0) {
+ inst.select_node(obj, true); // silent
+ inst.deselect_all();
+ }
+ });
+
+ // add this to the data object...
+ obj.data.obj.filter = filterString;
+
+ // refresh parent node (will re-load filtered data)
+ this.refresh_node(obj);
+ };
+ };
+
+})(jQuery);
\ No newline at end of file
diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/jquery.jstree.pagination_plugin.js b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/jquery.jstree.pagination_plugin.js
index 83a403ebd85..b17a8f3725b 100644
--- a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/jquery.jstree.pagination_plugin.js
+++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/jquery.jstree.pagination_plugin.js
@@ -16,6 +16,11 @@
$.jstree.plugins.pagination = function (options, parent) {
+ this.get_page_size = function(node) {
+ // nicer way to acces this global variable
+ return WEBCLIENT.PAGE;
+ };
+
this.change_page = function(node, page) {
this._set_page(node, page);
this.refresh_node(node);
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 c3ae3682072..7f9829591da 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
@@ -21,20 +21,128 @@
// jQuery load callback...
$(function() {
+
+ var converter = function (data) {
+ var jstree_data = [],
+ node;
+
+ function makeNode(value, type) {
+ var rv = {
+ 'data': {'id': value.id, 'obj': value},
+ 'text': value.name,
+ 'children': value.childCount ? true : false,
+ 'type': type,
+ 'li_attr': {
+ 'data-id': value.id
+ }
+ };
+ if (type === 'experimenter') {
+ rv.text = value.firstName + ' ' + value.lastName;
+ rv.state = {'opened': true};
+ rv.children = true;
+ } else if (type === 'tag') {
+ // We don't count children for Tags (too expensive?) Assume they have children
+ rv.children = true;
+ rv.type = value.set ? 'tagset' : 'tag';
+ rv.text = value.value;
+ }
+ return rv;
+ }
+
+ if (data.hasOwnProperty('experimenter')) {
+ node = makeNode(data.experimenter, 'experimenter');
+ jstree_data.push(node);
+ }
+
+ // Add tags to the jstree data structure
+ if (data.hasOwnProperty('tags')) {
+ $.each(data.tags, function(index, value) {
+ var node = makeNode(value, 'tag');
+ jstree_data.push(node);
+ });
+ }
+
+ // Add projects to the jstree data structure
+ if (data.hasOwnProperty('projects')) {
+ $.each(data.projects, function(index, value) {
+ var node = makeNode(value, 'project');
+ jstree_data.push(node);
+ });
+ }
+
+ // Add datasets to the jstree data structure
+ if (data.hasOwnProperty('datasets')) {
+ $.each(data.datasets, function(index, value) {
+ var node = makeNode(value, 'dataset');
+ jstree_data.push(node);
+ });
+ }
+
+ // Add images to the jstree data structure
+ if (data.hasOwnProperty('images')) {
+ $.each(data.images, function(index, value) {
+ var node = makeNode(value, 'image');
+ jstree_data.push(node);
+ });
+ }
+
+ // Add screens to the jstree data structure
+ if (data.hasOwnProperty('screens')) {
+ $.each(data.screens, function(index, value) {
+ var node = makeNode(value, 'screen');
+ jstree_data.push(node);
+ });
+ }
+
+ // Add plates to the jstree data structure
+ if (data.hasOwnProperty('plates')) {
+ $.each(data.plates, function(index, value) {
+ var node = makeNode(value, 'plate');
+ jstree_data.push(node);
+ });
+ }
+
+ // Add plates to the jstree data structure
+ if (data.hasOwnProperty('acquisitions')) {
+ $.each(data.acquisitions, function(index, value) {
+ var node = makeNode(value, 'acquisition');
+ jstree_data.push(node);
+ });
+ }
+
+ if (data.hasOwnProperty('orphaned')) {
+ node = {
+ 'data': {'obj': data.orphaned},
+ 'text': 'Orphaned Images',
+ 'children': data.orphaned.childCount > 0 ? true : false,
+ 'type': 'orphaned'
+ };
+ jstree_data.push(node);
+ }
+
+ return jstree_data;
+ };
+
// Select jstree and then cascade handle events and setup the tree.
var jstree = $("#dataTree")
.on('changed.jstree', function (e, data) {
+ // E.g. notify of de-selection
+ OME.tree_selection_changed(data, e);
+ })
+ // var inst = data.instance;
+ // buttonsShowHide(inst.get_selected(true), inst);
+
+ // // Load on selection, but not open because that breaks key navigation
+ // if (data.node &&
+ // inst.is_parent(data.node) &&
+ // !inst.is_loaded(data.node) &&
+ // !inst.is_loading(data.node)) {
+ // inst.load_node(data.node);
+ // }
+ .on('select_node.jstree', function(e, data) {
+ // When selection changes, trigger change for other panels
var inst = data.instance;
buttonsShowHide(inst.get_selected(true), inst);
-
- // Load on selection, but not open because that breaks key navigation
- if (data.node &&
- inst.is_parent(data.node) &&
- !inst.is_loaded(data.node) &&
- !inst.is_loading(data.node)) {
- inst.load_node(data.node);
- }
-
OME.tree_selection_changed(data, e);
})
.on('copy_node.jstree', function(e, data) {
@@ -335,7 +443,7 @@ $(function() {
.jstree({
'plugins': ['types', 'contextmenu', 'dnd', 'sort', 'locate',
'ometools', 'conditionalselect', 'pagination', 'fields',
- 'truncatetext', 'childcount', 'omecut'],
+ 'truncatetext', 'childcount', 'omecut', 'filter', 'search'],
// The jstree core
'locate' : {
// Returns a key for this node
@@ -352,6 +460,10 @@ $(function() {
}
},
+ 'search' : {
+ 'show_only_matches': true
+ },
+
'conditionalselect' : {
// Checks if a selection should be allowed
'conditionalselect_function': function(node) {
@@ -459,6 +571,14 @@ $(function() {
}
}
+ // If the node is currently being filtered...
+ if (node.id !== '#') {
+ var filter_text = node.data.obj.filter;
+ if (filter_text && filter_text.length > 0) {
+ payload['filter'] = filter_text;
+ }
+ }
+
// Always add the group_id from the current context
payload['group'] = WEBCLIENT.active_group_id;
@@ -519,143 +639,21 @@ $(function() {
data: payload,
cache: false,
success: function (data, textStatus, jqXHR) {
+
+ // if we have a 'count' of images, update this in parent node
+ if (data.count !== undefined && node.data) {
+ node.data.obj.filterCount = data.count;
+ }
+
+ // we convert the data into format expected by jstree...
+ data = converter(data);
+
+ // ...and pass to callback
callback.call(this, data);
},
error: function (jqXHR, textStatus, errorThrown) {
// Global error handling is sufficient here
},
- // Converter is required because the JSON format being returned is not
- // jstree specific.
- 'converters' : {
- "text json": function (json) {
- var data = JSON.parse(json),
- jstree_data = [],
- node;
-
- // Add experimenters to the jstree data structure
- // This handles multiple experimenters in the tree
- // if (data.hasOwnProperty('experimenters')) {
- // $.each(data.experimenters, function(index, value) {
- // var node = {
- // 'data': {'id': value.id, 'obj': value},
- // 'text': value.firstName + ' ' + value.lastName,
- // 'children': true,
- // 'type': 'experimenter',
- // 'state': {
- // },
- // 'li_attr': {
- // 'data-id': value.id
- // }
- // };
-
- // // Add 'state' opened for the current user by default
- // {% if active_user %}
- // if (value.id == {{ active_user.getId }}) {
- // node.state['opened'] = true;
- // }
- // {% endif %}
-
- // jstree_data.push(node);
- // });
- // }
- function makeNode(value, type) {
- var rv = {
- 'data': {'id': value.id, 'obj': value},
- 'text': value.name,
- 'children': value.childCount ? true : false,
- 'type': type,
- 'li_attr': {
- 'data-id': value.id
- }
- };
- if (type === 'experimenter') {
- rv.text = value.firstName + ' ' + value.lastName;
- rv.state = {'opened': true};
- rv.children = true;
- } else if (type === 'tag') {
- // We don't count children for Tags (too expensive?) Assume they have children
- rv.children = true;
- rv.type = value.set ? 'tagset' : 'tag';
- rv.text = value.value;
- }
- return rv;
- }
-
- if (data.hasOwnProperty('experimenter')) {
- node = makeNode(data.experimenter, 'experimenter');
- jstree_data.push(node);
- }
-
- // Add tags to the jstree data structure
- if (data.hasOwnProperty('tags')) {
- $.each(data.tags, function(index, value) {
- var node = makeNode(value, 'tag');
- jstree_data.push(node);
- });
- }
-
- // Add projects to the jstree data structure
- if (data.hasOwnProperty('projects')) {
- $.each(data.projects, function(index, value) {
- var node = makeNode(value, 'project');
- jstree_data.push(node);
- });
- }
-
- // Add datasets to the jstree data structure
- if (data.hasOwnProperty('datasets')) {
- $.each(data.datasets, function(index, value) {
- var node = makeNode(value, 'dataset');
- jstree_data.push(node);
- });
- }
-
- // Add images to the jstree data structure
- if (data.hasOwnProperty('images')) {
- $.each(data.images, function(index, value) {
- var node = makeNode(value, 'image');
- jstree_data.push(node);
- });
- }
-
- // Add screens to the jstree data structure
- if (data.hasOwnProperty('screens')) {
- $.each(data.screens, function(index, value) {
- var node = makeNode(value, 'screen');
- jstree_data.push(node);
- });
- }
-
- // Add plates to the jstree data structure
- if (data.hasOwnProperty('plates')) {
- $.each(data.plates, function(index, value) {
- var node = makeNode(value, 'plate');
- jstree_data.push(node);
- });
- }
-
- // Add plates to the jstree data structure
- if (data.hasOwnProperty('acquisitions')) {
- $.each(data.acquisitions, function(index, value) {
- var node = makeNode(value, 'acquisition');
- jstree_data.push(node);
- });
- }
-
- if (data.hasOwnProperty('orphaned')) {
- node = {
- 'data': {'obj': data.orphaned},
- 'text': data.orphaned.name,
- 'children': data.orphaned.childCount > 0 ? true : false,
- 'type': 'orphaned'
- };
- jstree_data.push(node);
- }
-
- return jstree_data;
- }
-
- }
});
},
'check_callback': function(operation, node, node_parent, node_position, more) {
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 01776087c45..81c0188a66a 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
@@ -213,14 +213,17 @@ OME.handleTableClickSelection = function(event) {
};
// called from click events on plate. Selected wells
-OME.well_selection_changed = function($selected, well_index, plate_class) {
+OME.well_selection_changed = function(selectedWellIds, well_index) {
- var selected_objs = [];
- $selected.each(function(i){
- selected_objs.push( {"id":$(this).attr('id').replace("=","-"),
- "rel":$(this).attr('rel'),
- "index":well_index,
- "class":plate_class} ); // assume every well has same permissions as plate
+ // Update the buttons in the jstree as if nothing selected.
+ if (buttonsShowHide) {
+ var datatree = $.jstree.reference('#dataTree');
+ buttonsShowHide([], datatree);
+ }
+
+ var selected_objs = selectedWellIds.map(function(i){
+ return {"id":"well-" + i,
+ "index":well_index};
});
$("body")
@@ -1008,3 +1011,33 @@ jQuery.fn.tooltip_init = function() {
});
return this;
};
+
+
+// Polyfill, from https://gist.github.com/hsablonniere/2581101
+if (!Element.prototype.scrollIntoViewIfNeeded) {
+ Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
+ centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
+
+ var parent = this.parentNode,
+ parentComputedStyle = window.getComputedStyle(parent, null),
+ parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width'), 10),
+ parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width'), 10),
+ overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
+ overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
+ overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft,
+ overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
+ alignWithTop = overTop && !overBottom;
+
+ if ((overTop || overBottom) && centerIfNeeded) {
+ parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
+ }
+
+ if ((overLeft || overRight) && centerIfNeeded) {
+ parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
+ }
+
+ if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
+ this.scrollIntoView(alignWithTop);
+ }
+ };
+}
diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/react_centre_panel.jsx b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/react_centre_panel.jsx
new file mode 100644
index 00000000000..e7ca8d9dc5f
--- /dev/null
+++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/react_centre_panel.jsx
@@ -0,0 +1,491 @@
+
+
+(function(){
+ var CentrePanel = React.createClass({
+
+ parentTypes: ["dataset", "orphaned", "tag", "share", "plate", "acquisition"],
+
+ getInitialState: function() {
+ return {
+ iconSize: 65,
+ };
+ },
+
+ setIconSize: function(size) {
+ this.setState({iconSize: size});
+ },
+
+ renderNothing: function(selected) {
+ if (selected.length === 0) {
+ if (this.previousParent) {
+ return false;
+ }
+ return true;
+ }
+ var dtype = selected[0].type;
+ if (dtype === "image") {
+ return false;
+ }
+ if (selected.length > 1 && dtype !== "image") {
+ return true;
+ }
+ if (this.parentTypes.indexOf(dtype) === -1) {
+ return true;
+ }
+ },
+
+ componentWillReceiveProps: function(nextProps) {
+ // When props change...
+ // If nothing is selected AND the previous node is valid
+ // We continue to render that node (Dataset)
+ if (nextProps.selected.length !== 0) {
+ delete(this.previousParent);
+ }
+ },
+
+ getParentNode: function(selected, inst) {
+ if (this.renderNothing(selected)) {
+ return;
+ }
+ if (selected.length === 0 && this.previousParent) {
+ return this.previousParent;
+ }
+ var dtype = selected[0].type;
+ if (this.parentTypes.indexOf(dtype) > -1) {
+ return selected[0];
+ }
+ if (dtype === "image") {
+ return inst.get_node(inst.get_parent(selected[0]));
+ }
+ },
+
+ // Most render nothing unless we've selected a Dataset or Image(s)
+ render: function() {
+ var selected = this.props.selected,
+ inst = this.props.jstree,
+ imgNodes = [],
+ dtype;
+
+ var iconTable;
+
+ var parentNode = this.getParentNode(selected, inst);
+
+ if (parentNode) {
+
+ dtype = parentNode.type;
+
+ if (dtype === "plate" || dtype === "acquisition") {
+ var plateId = parentNode.data.id;
+ if (dtype === "acquisition") {
+ plateId = inst.get_node(inst.get_parent(parentNode)).data.id;
+ }
+ iconTable = (
+
| + {columnNames} + |
|---|