Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Idr prototype #25

Merged
merged 110 commits into from
May 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
f6a4a6f
first prototype of idr UI, showed at first meeting
will-moore Apr 25, 2019
9c79fa2
Don't show Key:Value in study for current filter key
will-moore Apr 25, 2019
2639639
Add /study_thumbnail/project/1 for study thumbnail
will-moore Apr 25, 2019
ac70010
Show thumbnail for each study
will-moore Apr 25, 2019
4bbc43c
CSS layout tweaks
will-moore Apr 25, 2019
682e40b
study_thumbnail don't pick empty Well
will-moore Apr 25, 2019
4be721f
Each study links to idr.openmicroscopy.org/webclient/?show...
will-moore Apr 25, 2019
d3d9e48
CSS tweak of studies layout
will-moore Apr 26, 2019
9e2db9a
Link to e.g. mapr/gene/?value=CDC10&show=project-1 when filtering by …
will-moore Apr 26, 2019
1f27cd5
Study links open in new tab
will-moore Apr 26, 2019
483de92
support /cells and /tissues pages
will-moore Apr 26, 2019
90b3541
Use hard-coded thumb ID for each study
will-moore Apr 29, 2019
79944c6
Add auto-complete for Study Key-Values. Hard-code Keys for <select>
will-moore Apr 29, 2019
7b77323
Add docstring about setting omero.web.mapr.config
will-moore Apr 29, 2019
b8b7dd3
Add idr/categories/ page to list interesting studies
will-moore Apr 29, 2019
a21a6f2
Fix title on categories/ page
will-moore Apr 29, 2019
b1d5566
placeholder improvement
will-moore Apr 30, 2019
6727045
Move filter input to top, replace Search field
will-moore Apr 30, 2019
5a6bb0b
Categories specified by study key-value queries
will-moore Apr 30, 2019
b9ddfee
Search page for filtering
will-moore Apr 30, 2019
44b6c6f
flake8 fixes
will-moore Apr 30, 2019
800929e
Don't use local mapr config. Temp hard-coded
will-moore Apr 30, 2019
00eeebb
Use http://idr... for annotations to avoid CORS error
will-moore May 1, 2019
4159f3b
Show text overlaying images for each study
will-moore May 1, 2019
ea48e4d
Use render_image instead of render_thumbnail for each study
will-moore May 1, 2019
402505f
Show study authors on hover
will-moore May 1, 2019
47229b4
Remove @login_required from search page
will-moore May 1, 2019
5239f22
Workaround CORS caching issue when loading annotations
will-moore May 1, 2019
4a4e634
Fix 'None' in Welcome title
will-moore May 1, 2019
6437026
More CORS cache-busting workarounds. Open-viewer link
will-moore May 1, 2019
779f045
Move js from index.html to index.js
will-moore May 1, 2019
b22806d
Move js to util.js and model.js
will-moore May 2, 2019
27a41d8
Search.html js to search.js and other cleanup + fixes
will-moore May 2, 2019
eed40ce
Improve JS and CSS code reuse
will-moore May 2, 2019
ea38cec
BASE_URL set in gallery_settings
will-moore May 2, 2019
74ce81e
studies.css and removed IDR-specific links from header and footer
will-moore May 2, 2019
f1932be
Move category_queries to gallery_settings config
will-moore May 3, 2019
c86174e
Move studyHtml() and template into index.html where used
will-moore May 3, 2019
06e1674
Fix issues with auto-complete. Case insensitive. Sort by best match
will-moore May 4, 2019
dbba286
Hide filter count message when showing no results
will-moore May 4, 2019
6c29362
Pass config values to search.html global variables same as index.html
will-moore May 4, 2019
56fe3aa
Fix css for auto-complete dialog
will-moore May 5, 2019
09329e9
flake8 fixes
will-moore May 5, 2019
cf9fff5
Show MAPR results with child images based on mapr queries
will-moore May 6, 2019
0d07579
Don't show 'Organism' option in 'Image Attributes'
will-moore May 6, 2019
31b9de2
Improve auto-complete for Key-Value pairs. Show on key-change
will-moore May 7, 2019
ee33d77
Show scrollbars on categories on Mac
will-moore May 7, 2019
9c5aeb9
config super_categories and urls to them
will-moore May 7, 2019
5bdb076
Don't hard-code THUMB_IDS. Load via AJAX
will-moore May 8, 2019
6767b06
Add /gallery/study_image/project/1/ to get image_id
will-moore May 8, 2019
1ad4738
flake8 fixes
will-moore May 9, 2019
c223f76
flake8 fixes
will-moore May 9, 2019
3b1f262
Header links to /gallery/ home page
will-moore May 9, 2019
ed84cfa
Use GALLERY_INDEX as base for urls
will-moore May 9, 2019
344a19a
SUPER_CATEGORY is a simple query. Filter all studies on load
will-moore May 9, 2019
290b7b1
Move FILTER_KEYS from constants.js to gallery_settings
will-moore May 9, 2019
17b3cff
Add FILTER_MAPR_KEYS config to gallery_settings
will-moore May 9, 2019
9504828
Revert to loadImageId via JSON api for index.html
will-moore May 9, 2019
05b1709
Handle duplicate Keys in Key-Value auto-complete
will-moore May 10, 2019
e1e2126
Fix load image for Big images and SPW with empty Wells
will-moore May 10, 2019
102c7e5
Show all Studies at /search/ with no filter
will-moore May 10, 2019
a2b13e9
Show 'Most Recent' studies on home page
will-moore May 10, 2019
1d6b583
Don't show 'empty' categories
will-moore May 10, 2019
f621163
Update Category labels
will-moore May 10, 2019
f5a6fb7
Show auto-complete on focus()
will-moore May 10, 2019
fc88cde
Allow to filter by Name (idr number)
will-moore May 10, 2019
1122c0f
Link to static css on idr
will-moore May 13, 2019
aec3b72
Filter studies by cells / tissue at search page
will-moore May 13, 2019
3c0b79a
Update cells and tissue query to return Frances' list of studies
will-moore May 13, 2019
a7654d6
Fix whiteshace and lowerCase bugs in filtering by query
will-moore May 13, 2019
c75ecce
flake8 fix
will-moore May 13, 2019
5a16de0
Remove old commented-out urls
will-moore May 13, 2019
d8853ab
Fix comment
will-moore May 13, 2019
e376a3f
Move title 'IDR' to gallery_settings
will-moore May 13, 2019
e1c3bcd
Add 'Licence' to FILTER_KEYS config in gallery_settings
will-moore May 14, 2019
3e1df85
Handle search?query=Key:Val:ue Only split on first :
will-moore May 14, 2019
d780015
flake8 fix
will-moore May 14, 2019
e39be56
Add browser history to search page
will-moore May 14, 2019
cd0a551
Handle 'Enter' for partial filter without picking auto-complete
will-moore May 14, 2019
f208fbe
Renamed /idr/ directory to /categories/
will-moore May 14, 2019
bd16ba4
Remove unuse static foundation css & js
will-moore May 14, 2019
bc6faa6
Remove unused font-awesome.min.css
will-moore May 14, 2019
d1eb0b3
Add links to Cells & Tissue to header
will-moore May 15, 2019
b2f19af
Use 'cell' not 'cells'
will-moore May 15, 2019
750e774
Show 'Cell - IDR' title on search page
will-moore May 15, 2019
e8246ba
Add 'title' to super_categories. e.g. 'Welcome to Tissue - IDR'
will-moore May 15, 2019
9f6758e
Add to gallery_settings doc string
will-moore May 15, 2019
e15cd7b
Load top-level MAPR matches with empty auto-complete
will-moore May 15, 2019
30b2fef
Use host for CORS cache-bust. Allows caching for each host
will-moore May 15, 2019
7d06819
Remove group, experimenter, page from MAPR requests
will-moore May 15, 2019
5d39c46
Don't fail if studies have no map-annotations or description
will-moore May 16, 2019
d9d33ab
Remove &_ = random() cache buster from urls
will-moore May 16, 2019
732dffb
Revert "Remove &_ = random() cache buster from urls"
will-moore May 16, 2019
9de4d85
Show e.g. 'Try Cell-IDR' if filter matches nothing
will-moore May 16, 2019
253d089
use window.location.host for all CACHE_BUSTER
will-moore May 16, 2019
b616216
Show spinner while loading OMERO.mapr auto-complete items
will-moore May 16, 2019
c63a802
Don't handle mapr auto-complete for empty input
will-moore May 17, 2019
d7d1814
Use foundation.min.js from idr
will-moore May 17, 2019
b4c4b0c
Empty search filter - show NO studies instead of ALL
will-moore May 17, 2019
18972af
Handle Projects with no Datasets in loadImage()
will-moore May 17, 2019
856ea54
Use Thumbnails for categories and search
will-moore May 18, 2019
234db1b
Update cell/tissue queries to use 'Sample Type:cell/tissue'
will-moore May 20, 2019
9c6266a
Switch FIRST/LAST sort order for query
will-moore May 20, 2019
0aab5e8
Remove setting values from gallery_settings.py
will-moore May 20, 2019
5473f0a
Support /api/projects/:id/images/ with omero-marshal
will-moore May 21, 2019
cf70231
flake8 fix
will-moore May 21, 2019
04500d1
Add TITLE_KEY to lookup Study Title from Key:Value
will-moore May 21, 2019
b7b7be3
Batch loading of thumbnails
will-moore May 21, 2019
d755d62
flake8 fixes
will-moore May 21, 2019
883065f
Batch load thumbs on search page too
will-moore May 21, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions omero_gallery/gallery_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright (C) 2019 University of Dundee & Open Microscopy Environment.
# All rights reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""Settings for the OMERO.gallery app."""

import sys
from omeroweb.settings import process_custom_settings, report_settings
import json

# load settings
GALLERY_SETTINGS_MAPPING = {

"omero.web.gallery.base_url":
["BASE_URL",
None,
str,
("Base URL to use for JSON AJAX requests."
" e.g. 'https://demo.openmicroscopy.org'."
" This allows data to be loaded from another OMERO server."
" The default behaviour is to use the current server.")],

"omero.web.gallery.category_queries":
["CATEGORY_QUERIES",
('{}'),
json.loads,
("If this is configured then the gallery Home Page shows a list"
" of categories containing Projects and Screens that match the"
" relevant query. Each category is defined by"
" 'id': {'label':'Cool data', 'query': 'Key:Value', 'index': 0}"
" Query is by Key:Value on Map Annotations linked"
" to Projects and Screens, e.g. 'Study Type:light sheet'"
" OR e.g. 'FIRST5:Name' or 'LAST10:date"
" to sort by Name or date.")],

"omero.web.gallery.filter_keys":
["FILTER_KEYS",
('[]'),
json.loads,
("If this is configured then we allow filtering of Screens and"
" Projects by Key:Value pairs linked to them. This list allows us"
" to specify which Keys the user can choose in the UI."
" Each item is simple string or object with 'label' and 'value'")],

"omero.web.gallery.filter_mapr_keys":
["FILTER_MAPR_KEYS",
('[]'),
json.loads,
("If this is configured then we allow filtering of Screens and"
" Projects by OMERO.mapr. This is a list of mapr_config IDs, such"
" as 'gene', 'antibody' etc. which allows us"
" to specify which Keys the user can choose in the UI.")],

"omero.web.gallery.title_keys":
["TITLE_KEYS",
('["Publication Title", "Study Title"]'),
json.loads,
("Supports lookup of a Title for Screens and Projects using"
" Map Annotations on those objects and the specified Key(s)."
" Each Key in this list will be checked in turn until a"
" Value is found")],

"omero.web.gallery.super_categories":
["SUPER_CATEGORIES",
('{}'),
json.loads,
("Optional config to provide top-level categories, similar to"
" category_queries, using the same config format and 'query' syntax."
" Each will create a landing page /:id/ that will filter Projects"
" and Screens by the query. Optional 'title' for each category"
" can be used for page title, otherwise the 'label' is used.")],

"omero.web.gallery.title":
["GALLERY_TITLE", "Welcome to OMERO.gallery", str,
"Title for the home page shown when category_queries is set."]

}

process_custom_settings(sys.modules[__name__], 'GALLERY_SETTINGS_MAPPING')
report_settings(sys.modules[__name__])
295 changes: 295 additions & 0 deletions omero_gallery/static/gallery/categories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
// loaded below
let mapr_settings = {};

// Model for loading Projects, Screens and their Map Annotations
let model = new StudiesModel();


// ----- event handling --------

document.getElementById('maprConfig').onchange = (event) => {
document.getElementById('maprQuery').value = '';
let value = event.target.value.replace('mapr_', '');
let placeholder = mapr_settings[value] ? mapr_settings[value].default[0] : value;
document.getElementById('maprQuery').placeholder = placeholder;
// Show all autocomplete options...
$("#maprQuery").focus();
render();
}

document.getElementById('maprQuery').onfocus = (event) => {
$("#maprQuery").autocomplete("search", event.target.value);
}

// ------ AUTO-COMPLETE -------------------

function showSpinner() {
document.getElementById('spinner').style.visibility = 'visible';
}
function hideSpinner() {
document.getElementById('spinner').style.visibility = 'hidden';
}

$("#maprQuery")
.keyup(event => {
if (event.which == 13) {
let configId = document.getElementById("maprConfig").value;
document.location.href = `search/?query=${ configId }:${ event.target.value }`;
}
})
.autocomplete({
autoFocus: false,
delay: 1000,
source: function( request, response ) {

// if configId is not from mapr, we filter on mapValues...
let configId = document.getElementById("maprConfig").value;
if (configId.indexOf('mapr_') != 0) {

let matches;
if (configId === 'Name') {
matches = model.getStudiesNames(request.term);
} else {
matches = model.getKeyValueAutoComplete(configId, request.term);
}
response(matches);
return;
}

// Don't handle empty query for mapr
if (request.term.length == 0) {
return;
}

// Auto-complete to filter by mapr...
configId = configId.replace('mapr_', '');
let case_sensitive = false;

let requestData = {
case_sensitive: case_sensitive,
'_': CACHE_BUSTER, // CORS cache-buster
}
let url;
if (request.term.length === 0) {
// Try to list all top-level values.
// This works for 'wild-card' configs where number of values is small e.g. Organism
// But will return empty list for e.g. Gene
url = `${ BASE_URL }/mapr/api/${ configId }/`;
requestData.orphaned = true
} else {
// Find auto-complete matches
url = `${ BASE_URL }/mapr/api/autocomplete/${ configId }/`;
requestData.value = case_sensitive ? request.term : request.term.toLowerCase();
requestData.query = true; // use a 'like' HQL query
}
showSpinner();
$.ajax({
dataType: "json",
type : 'GET',
url: url,
data: requestData,
success: function(data) {
hideSpinner();
if (request.term.length === 0) {
// Top-level terms in 'maps'
if (data.maps && data.maps.length > 0) {
let terms = data.maps.map(m => m.id);
terms.sort();
response(terms);
}
}
else if (data.length > 0) {
response( $.map( data, function(item) {
return item;
}));
} else {
response([{ label: 'No results found.', value: -1 }]);
}
},
error: function(data) {
hideSpinner();
response([{ label: 'Error occured.', value: -1 }]);
}
});
},
minLength: 0,
open: function() {},
close: function() {
// $(this).val('');
return false;
},
focus: function(event,ui) {},
select: function(event, ui) {
let configId = document.getElementById("maprConfig").value;
document.location.href = `search/?query=${ configId }:${ ui.item.value }`;
return false;
}
}).data("ui-autocomplete")._renderItem = function( ul, item ) {
return $( "<li>" )
.append( "<a>" + item.label + "</a>" )
.appendTo( ul );
}

// ------------ Render -------------------------

function render() {
document.getElementById('studies').innerHTML = "";

let categories = Object.keys(CATEGORY_QUERIES);
// Sort by index
categories.sort(function(a, b) {
let idxA = CATEGORY_QUERIES[a].index;
let idxB = CATEGORY_QUERIES[b].index;
return (idxA > idxB ? 1 : idxA < idxB ? -1 : 0);
});

categories.forEach(category => {
let cat = CATEGORY_QUERIES[category];
let query = cat.query;

// Find matching studies
let matches = model.filterStudiesByMapQuery(query);
if (matches.length == 0) return;

var div = document.createElement( "div" );
div.innerHTML = `<h1 title="${query}">${cat.label} (${ matches.length })</h1>
<div style="width100%; overflow:auto; background: white">
<div id="${cat.label}" style="width: 5000px"></div>
</div>
`;
div.className = "row";
document.getElementById('studies').appendChild(div);

// By default, we link to the study itself in IDR...
let linkFunc = (studyData) => {
let type = studyData['@type'].split('#')[1].toLowerCase();
return `${ BASE_URL }/webclient/?show=${ type }-${ studyData['@id'] }`;
}

matches.forEach(study => renderStudy(study, cat.label, linkFunc));
});

// Now we iterate all Studies in DOM, loading image ID for link and thumbnail
loadStudyThumbnails();
}


function renderStudy(studyData, elementId, linkFunc) {

// Add Project or Screen to the page
let title;
for (let i=0; i<TITLE_KEYS.length; i++) {
title = model.getStudyValue(studyData, TITLE_KEYS[i]);
if (title) {
break;
}
}
if (!title) {
title = studyData.Name;
}
let type = studyData['@type'].split('#')[1].toLowerCase();
let studyLink = linkFunc(studyData);
// save for later
studyData.title = title;

let desc = studyData.Description;
let studyDesc;
if (desc) {
// If description contains title, use the text that follows
if (title.length > 0 && desc.indexOf(title) > -1) {
desc = desc.split(title)[1];
}
// Remove blank lines
studyDesc = desc.split('\n').filter(l => l.length > 0).join('\n');
}

let idrId = studyData.Name.split('-')[0]; // idr0001
let authors = model.getStudyValue(studyData, "Publication Authors") || "";

// Function (and template) are defined where used in index.html
let html = studyHtml(studyLink, studyDesc, idrId, title, authors, BASE_URL)

var div = document.createElement( "div" );
div.innerHTML = html;
div.className = "row study ";
div.dataset.obj_type = type;
div.dataset.obj_id = studyData['@id'];
document.getElementById(elementId).appendChild(div);
}

// --------- Render utils -----------
function loadStudyThumbnails() {

let ids = [];
// Collect study IDs 'project-1', 'screen-2' etc
document.querySelectorAll('div.study').forEach(element => {
let obj_id = element.dataset.obj_id;
let obj_type = element.dataset.obj_type;
if (obj_id && obj_type) {
ids.push(obj_type + '=' + obj_id);
}
});

// Load images
model.loadStudiesThumbnails(ids, (data) => {
// data is e.g. { project-1: {thumbnail: base64data, image: {id:1}} }
for (id in data) {
let obj_type = id.split('-')[0];
let obj_id = id.split('-')[1];
let elements = document.querySelectorAll(`div[data-obj_type="${obj_type}"][data-obj_id="${obj_id}"]`);
for (let e=0; e<elements.length; e++) {
// Find all studies matching the study ID and set src on image
let element = elements[e];
let studyImage = element.querySelector('img.studyImage');
studyImage.src = data[id].thumbnail;
// viewer link
let iid = data[id].image.id;
let link = `${ BASE_URL }/webclient/img_detail/${ iid }/`;
element.querySelector('a.viewerLink').href = link;
}
}
});
}

function renderStudyKeys() {
let html = FILTER_KEYS
.map(key => {
if (key.label && key.value) {
return `<option value="${ key.value }">${ key.label }</option>`
}
return `<option value="${ key }">${ key }</option>`
})
.join("\n");
document.getElementById('studyKeys').innerHTML = html;
}
renderStudyKeys();


// ----------- Load / Filter Studies --------------------

// Do the loading and render() when done...
model.loadStudies(() => {
// Immediately filter by Super category
if (SUPER_CATEGORY && SUPER_CATEGORY.query) {
model.studies = model.filterStudiesByMapQuery(SUPER_CATEGORY.query);
}
render();
});


// Load MAPR config
fetch(GALLERY_INDEX + 'idr/mapr/config/')
.then(response => response.json())
.then(data => {
mapr_settings = data;

let html = FILTER_MAPR_KEYS.map(key => {
let config = mapr_settings[key];
if (config) {
return `<option value="mapr_${ key }">${ config.label }</option>`;
} else {
return "";
}
}).join("\n");
document.getElementById('maprKeys').innerHTML = html;
});
Loading