diff --git a/components/tests/python/library/__init__.py b/components/tests/python/library/__init__.py index 4825ca12388..991bde3e29c 100644 --- a/components/tests/python/library/__init__.py +++ b/components/tests/python/library/__init__.py @@ -117,6 +117,20 @@ def teardown_class(cls): cls.root = None cls.__clients.__del__() + def keepRootAlive(self): + """ + Keeps root connection alive. + """ + try: + if self.root.sf is None: + p = Ice.createProperties(sys.argv) + rootpass = p.getProperty("omero.rootpass") + self.root.createSession("root", rootpass) + else: + self.root.sf.keepAlive(None) + except Exception: + raise + @classmethod def omeropydir(self): count = 10 diff --git a/components/tests/ui/resources/web/tree.txt b/components/tests/ui/resources/web/tree.txt index 21f421ef09f..f00fa4a42f2 100644 --- a/components/tests/ui/resources/web/tree.txt +++ b/components/tests/ui/resources/web/tree.txt @@ -283,11 +283,11 @@ Wait Until Right Panel Loads Everything Wait Until Right Panel Loads ${containerType} ${containerId} Wait Until Element Is Visible xpath=//*[@id="general_tab"]/h1[contains(text(),'${containerType} Details')] - Wait Until Element Is Visible xpath=//*[@id="general_tab"]/h1[contains(text(),'Tags')] - Wait Until Element Is Visible xpath=//*[@id="general_tab"]/h1[contains(text(),'Key-Value Pairs')] - Wait Until Element Is Visible xpath=//*[@id="general_tab"]/h1[contains(text(),'Attachments')] - Wait Until Element Is Visible xpath=//*[@id="general_tab"]/h1[contains(text(),'Ratings')] - Wait Until Element Is Visible xpath=//*[@id="general_tab"]/h1[contains(text(),'Comments')] + Wait Until Element Is Visible xpath=//*[@id="general_tab"]/div/h1[contains(text(),'Tags')] + Wait Until Element Is Visible xpath=//*[@id="general_tab"]/div/h1[contains(text(),'Key-Value Pairs')] + Wait Until Element Is Visible xpath=//*[@id="general_tab"]/div/h1[contains(text(),'Attachments')] + Wait Until Element Is Visible xpath=//*[@id="general_tab"]/div/h1[contains(text(),'Ratings')] + Wait Until Element Is Visible xpath=//*[@id="general_tab"]/div/h1[contains(text(),'Comments')] Run Keyword If '${containerType}' == 'Project' Wait Until Element Is Visible ${detailsPane}//th[contains(text(),'Creation Date:')] Run Keyword If '${containerType}' == 'Dataset' Wait Until Element Is Visible ${detailsPane}//th[contains(text(),'Creation Date:')] Run Keyword If '${containerType}' == 'Image' Check Right Panel Image diff --git a/components/tests/ui/robot_setup.sh b/components/tests/ui/robot_setup.sh index 10b6ee71489..9cad4094e6a 100644 --- a/components/tests/ui/robot_setup.sh +++ b/components/tests/ui/robot_setup.sh @@ -21,6 +21,7 @@ TINY_IMAGE_NAME=${TINY_IMAGE_NAME:-test&acquisitionDate=2012-01-01_00-00-00.fake MIF_IMAGE_NAME=${MIF_IMAGE_NAME:-test&series=3.fake} PLATE_NAME=${PLATE_NAME:-test&plates=1&plateAcqs=1&plateRows=2&plateCols=3&fields=1&screens=0.fake} BULK_ANNOTATION_CSV=${BULK_ANNOTATION_CSV:-bulk_annotation.csv} +FILE_ANNOTATION=${FILE_ANNOTATION:-robot_file_annotation.txt} # Create robot user and group bin/omero login root@$HOSTNAME:$PORT -w $ROOT_PASSWORD @@ -41,6 +42,9 @@ echo "Well,Well Type,Concentration" > "$BULK_ANNOTATION_CSV" echo "A1,Control,0" >> "$BULK_ANNOTATION_CSV" echo "A2,Treatment,10" >> "$BULK_ANNOTATION_CSV" +# Create file for upload as File Annotation +echo "Robot test file annotations" > "$FILE_ANNOTATION" + # Create robot setup bin/omero login $USER_NAME@$HOSTNAME:$PORT -w $USER_PASSWORD # Parse the sessions file to get session key @@ -113,6 +117,11 @@ do bin/omero import $TINY_IMAGE_NAME --debug ERROR done +# Uplodad file and create FileAnnotation +bin/omero upload $FILE_ANNOTATION > file_upload.log +fileId=$(sed "s/^Uploaded $FILE_ANNOTATION as //" file_upload.log) +bin/omero obj new FileAnnotation file=OriginalFile:$fileId + # Logout bin/omero logout diff --git a/components/tests/ui/testcases/web/annotate_test.txt b/components/tests/ui/testcases/web/annotate_test.txt new file mode 100644 index 00000000000..6d4fa6c3138 --- /dev/null +++ b/components/tests/ui/testcases/web/annotate_test.txt @@ -0,0 +1,221 @@ +*** Settings *** +Documentation Tests submission of forms. + +Resource ../../resources/config.txt +Resource ../../resources/web/login.txt +Resource ../../resources/web/tree.txt + +Suite Setup Run Keywords User "${USERNAME}" logs in with password "${PASSWORD}" Maximize Browser Window +Suite Teardown Close all browsers + +*** Variables *** +${commentText} Robot test adding this comment +${commentTextTwo} A second comment added by Robot test +${commentTextThree} This will be added to Two Datasets +${commentTextFour} I (Robot) just love adding comments! +${fileName} robot_file_annotation.txt +${fileNameTwo} bulk_annotations +${SEARCH URL} ${WELCOME URL}search/ + + +*** Keywords *** + +Check For Comment + [Arguments] ${text} + Wait Until Page Contains Element xpath=//div[@class='commentText'][contains(text(), '${text}')] + +Check Comment Gone + [Arguments] ${text} + Page Should Not Contain Element xpath=//div[@class='commentText'][contains(text(), '${text}')] + +Add Comment + [Arguments] ${text} + Wait Until Element Is Visible id=id_comment + Input Text comment ${text} + Submit Form add_comment_form + Check For Comment ${text} + +Remove Comment + [Arguments] ${text} + Click Element xpath=//div[contains(@class, 'ann_comment_text')][descendant::div[contains(text(), '${text}')]]/img[@class='removeComment'] + Click Dialog Button OK + Wait Until Keyword Succeeds ${TIMEOUT} ${INTERVAL} Check Comment Gone ${text} + + +Add File Annotation + [Arguments] ${fileAnnotationName} + Wait Until Element Is Visible id=choose_file_anns + Click Element id=choose_file_anns + Wait Until Page Contains Element id=id_files + # Click Element xpath=//select[@id='id_files']/option[contains(text(), ${fileName})] + Select From List By Label id=id_files ${fileAnnotationName} + Click Dialog Button Accept + Check For File Annotation ${fileAnnotationName} + +Check For File Annotation + [Arguments] ${fileAnnotationName} + Wait Until Element Is Visible xpath=//li[contains(@class, 'file_ann_wrapper')][descendant::a[contains(text(), '${fileAnnotationName}')]]//*[@class='removeFile action'] + Wait Until Element Is Visible xpath=//li[contains(@class, 'file_ann_wrapper')][descendant::a[contains(text(), '${fileAnnotationName}')]]//a[@class='deleteFile action'] + Wait Until Element Is Visible xpath=//ul[@id="fileanns_container"]//a[contains(text(), '${fileAnnotationName}')] + Wait Until Element Is Visible xpath=//li[contains(@class, 'file_ann_wrapper')][descendant::a[contains(text(), '${fileAnnotationName}')]]//span + # Mouse Over //ul[@id="fileanns_container"]//a[contains(@class, 'tooltip')] + # Wait Until Element Is Visible //ul[@id="fileanns_container"]//a[contains(@class, 'tooltip')][contains(@aria-describedby, 'ui-tooltip')] + +Remove File Annotation + [Arguments] ${fileAnnotationName} + + Wait Until Element Is Visible xpath=//li[contains(@class, 'file_ann_wrapper')][descendant::a[contains(text(), '${fileAnnotationName}')]]//*[@class='removeFile action'] + Click Element xpath=//li[contains(@class, 'file_ann_wrapper')][descendant::*[contains(text(), '${fileAnnotationName}')]]//*[@class='removeFile action'] + + Click Dialog Button OK + Wait Until Keyword Succeeds ${TIMEOUT} ${INTERVAL} Check File Annotation Gone ${fileAnnotationName} + +Check File Annotation Gone + [Arguments] ${fileAnnotationName} + Page Should Not Contain Element xpath=//ul[@id="fileanns_container"]//a[contains(text(), '${fileAnnotationName}')] + + +*** Test Cases *** + +Test Comments + + Select Experimenter + ${dsId_One}= Create Dataset robot test comments_1 + ${dsId_Two}= Create Dataset robot test comments_2 + + # Comment a single Dataset + Click Element xpath=//h1[@data-name='comments'] + Add Comment ${commentText} + + # Refresh (select other Dataset and re-select) + ${nodeId}= Select Dataset By Id ${dsId_One} + Wait Until Right Panel Loads Dataset ${dsId_One} + Select Dataset By Id ${dsId_Two} + # Check and add another Comment + Check For Comment ${commentText} + Add Comment ${commentTextTwo} + # Remove first comment + Remove Comment ${commentText} + + # Now select both Datasets... + Meta Click Node ${nodeId} + Wait Until Page Contains Element id=batch_ann_title + # Previously added Comment will show up + Check For Comment ${commentTextTwo} + Page Should Not Contain Element xpath=//div[@class='commentText'][contains(text(), '${commentText}')] + # Add Comments to Both Datasets + Add Comment ${commentTextThree} + Add Comment ${commentTextFour} + + # Select each single Dataset to check for Comment(s) + Select Dataset By Id ${dsId_One} + Check Comment Gone ${commentTextTwo} + Check For Comment ${commentTextThree} + Select Dataset By Id ${dsId_Two} + Check For Comment ${commentTextTwo} + Check For Comment ${commentTextThree} + + # Select both Datasets and Remove Comments + Meta Click Node ${nodeId} + Wait Until Page Contains Element id=batch_ann_title + Remove Comment ${commentTextTwo} + Remove Comment ${commentTextThree} + + # Select each single Dataset again to check for Comment(s) + Select Dataset By Id ${dsId_One} + Check For Comment ${commentTextFour} + Check Comment Gone ${commentTextTwo} + Check Comment Gone ${commentTextThree} + Select Dataset By Id ${dsId_Two} + Check For Comment ${commentTextFour} + Check Comment Gone ${commentTextTwo} + Check Comment Gone ${commentTextThree} + + Select Dataset By Id ${dsId_One} + Delete Container + + Select Dataset By Id ${dsId_Two} + Delete Container + + +Test File Annotations + + Select Experimenter + ${sId_One}= Create Screen robot file annotations_1 + ${sId_Two}= Create Screen robot file annotations_2 + + # Annotate single Screen + Click Element xpath=//h1[@data-name='attachments'] + Add File Annotation ${fileName} + + # Refresh (select other Screen and re-select) + ${nodeId}= Select Screen By Id ${sId_One} + Wait Until Right Panel Loads Screen ${sId_One} + Select Screen By Id ${sId_Two} + # Check and add another File Annotation + Check For File Annotation ${fileName} + Add File Annotation ${fileNameTwo} + # Remove first File Annotation + Remove File Annotation ${fileName} + + # Now select both Screens... + Meta Click Node ${nodeId} + Wait Until Page Contains Element id=batch_ann_title + # Previously added File Annotation will show up + Check For File Annotation ${fileNameTwo} + Check File Annotation Gone ${fileName} + # Add File Annotation to Both Screens + Add File Annotation ${fileName} + Select Screen By Id ${sId_One} + Check For File Annotation ${fileName} + Select Screen By Id ${sId_Two} + Check For File Annotation ${fileName} + + # Now select both Screens... + Meta Click Node ${nodeId} + Wait Until Page Contains Element id=batch_ann_title + # Remove first File Annotation + Remove File Annotation ${fileName} + Select Screen By Id ${sId_One} + Check File Annotation Gone ${fileName} + Select Screen By Id ${sId_Two} + Check File Annotation Gone ${fileName} + + Delete Container + Select Screen By Id ${sId_One} + Delete Container + +# Test Rating + +Test Search Results File Annotations + + Select Experimenter + ${sId_One}= Create Screen robot file annotations_1 + + # Annotate single Screen + Add File Annotation ${fileNameTwo} + + Input Text id=id_search_query ${fileNameTwo} + Submit Form + Location Should Be ${SEARCH URL} + + Wait Until Page Contains Element xpath=//img[contains(@alt, 'plate')] + Click Element xpath=//img[contains(@alt, 'plate')] + Wait Until Page Contains Element //*[@id="general_tab"]//th[contains(text(), 'Plate ID:')] + + #Minimal checking to check if you can add Annotations on the search result page + Click Element xpath=//h1[@data-name='attachments'] + Check For File Annotation ${fileNameTwo} + Remove File Annotation ${fileNameTwo} + Add File Annotation ${fileNameTwo} + Click Element xpath=//h1[@data-name='comments'] + Add Comment ${commentText} + + Go To ${WELCOME URL} + Select Experimenter + Select Screen By Id ${sId_One} + Delete Container + + + + \ No newline at end of file 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..63492e0607d 100644 --- a/components/tests/ui/testcases/web/center_right_panel_tests.txt +++ b/components/tests/ui/testcases/web/center_right_panel_tests.txt @@ -34,22 +34,25 @@ Wait Until Right Panel Loads For MultiSelection Wait Until Element Is Visible xpath=//*[@id="metadata_general"]//div/button[contains(@title, 'Download Image as...')] Wait Until Element Is Visible xpath=//*[@id="show_link_btn"]/span - Wait Until Element Is Visible xpath=//*[@id="metadata_general"]//div/h2[contains(text(), 'Annotations')] + Wait Until Element Is Visible xpath=//*[@id="metadata_general"]//div/h1[contains(text(), 'Attachments')] Wait Until Element Is Visible xpath=//*[@id="annotationFilter"] - Wait Until Element Is Visible xpath=//*[@id="metadata_general"]//div/h2[contains(text(), 'Rating')] - Wait Until Element Is Visible xpath=//*[@id="add_rating"]/span + Wait Until Element Is Visible xpath=//*[@id="metadata_general"]//div/h1[contains(text(), 'Ratings')] + Click Element xpath=//*[@id="metadata_general"]//div/h1[contains(text(), 'Ratings')] + Wait Until Element Is Visible xpath=//*[@id="rating_annotations"] - Wait Until Element Is Visible xpath=//*[@id="metadata_general"]//div/h2[contains(text(), 'Tags')] + Wait Until Element Is Visible xpath=//*[@id="metadata_general"]//div/h1[contains(text(), 'Tags')] + Click Element xpath=//*[@id="metadata_general"]//div/h1[contains(text(), 'Tags')] Wait Until Element Is Visible xpath=//*[@id="launch_tags_form"]/span - Wait Until Element Is Visible xpath=//*[@id="metadata_general"]//div/h2[contains(text(), 'Attach')] - Wait Until Element Is Visible xpath=//*[@id="metadata_general"]//div/input[contains(@title, 'Select Files for Scripts')] + Wait Until Element Is Visible xpath=//*[@id="metadata_general"]//div/h1[contains(text(), 'Attachments')] + Click Element xpath=//*[@id="metadata_general"]//div/h1[contains(text(), 'Attachments')] + Wait Until Element Is Visible xpath=//*[@id="metadata_general"]//div/input[contains(@title, 'Select files for scripts')] Wait Until Element Is Visible xpath=//*[@id="choose_file_anns"]/span - Wait Until Element Is Visible xpath=//*[@id="metadata_general"]//div/h2[contains(text(), 'Comment')] + Wait Until Element Is Visible xpath=//*[@id="metadata_general"]//div/h1[contains(text(), 'Comments')] + Click Element xpath=//*[@id="metadata_general"]//div/h1[contains(text(), 'Comments')] Wait Until Element Is Visible xpath=//*[@id="id_comment"] - Wait Until Element Is Visible xpath=//*[@id="add_comment_form"]//td/input[contains(@value, 'Add Comment')] Element Should Not Be Visible xpath=//*[@id="general_tab"] diff --git a/components/tests/ui/testcases/web/forms_test.txt b/components/tests/ui/testcases/web/forms_test.txt index 3303c2232e2..69124220f1f 100644 --- a/components/tests/ui/testcases/web/forms_test.txt +++ b/components/tests/ui/testcases/web/forms_test.txt @@ -144,11 +144,15 @@ Test Batch Annotate Wait Until Page Contains Element id=batch_ann_title # Comment Form + Click Element xpath=//*[@id="metadata_general"]//div/h1[contains(text(), 'Comments')] + Wait Until Element Is Visible id=id_comment Input Text comment test add comment - Click Button Add Comment + Submit Form add_comment_form Wait Until Page Contains test add comment # Tags + Click Element xpath=//*[@id="metadata_general"]//div/h1[contains(text(), 'Tags')] + Wait Until Element Is Visible id=launch_tags_form Click Element launch_tags_form Wait Until Page Contains Element id_tag # Wait for tag panel being loded @@ -160,6 +164,8 @@ Test Batch Annotate Wait Until Page Contains Element xpath=//div[@class='tag']/a[contains(text(), testSeleniumTag${pid})] ${WAIT} # Files + Click Element xpath=//*[@id="metadata_general"]//div/h1[contains(text(), 'Attachments')] + Wait Until Element Is Visible id=choose_file_anns Click Element choose_file_anns Wait Until Page Contains Element id_files Click Element xpath=//select[@id='id_files']/option # just pick first file diff --git a/components/tests/ui/testcases/web/map_annotations_test.txt b/components/tests/ui/testcases/web/map_annotations_test.txt index cff946c4e34..57a67dc2ce1 100644 --- a/components/tests/ui/testcases/web/map_annotations_test.txt +++ b/components/tests/ui/testcases/web/map_annotations_test.txt @@ -17,7 +17,7 @@ Test Map Annotation Wait Until Page Contains Element css=div.mapAnnContainer Click Element xpath=//h1[@data-name='keyvaluepairs'] # No rows selected. Toolbar should allow 'Insert' only. - Xpath Should Not Have Class xpath=//ul[contains(@class, 'mapAnnToolbar')]//input[@title='Insert rows'] button-disabled + Xpath Should Have Class xpath=//ul[contains(@class, 'mapAnnToolbar')]//input[@title='Insert row'] button-disabled Xpath Should Have Class xpath=//ul[contains(@class, 'mapAnnToolbar')]//input[@title='Copy rows'] button-disabled Xpath Should Have Class xpath=//ul[contains(@class, 'mapAnnToolbar')]//input[@title='Paste rows'] button-disabled Xpath Should Have Class xpath=//ul[contains(@class, 'mapAnnToolbar')]//input[@title='Delete rows'] button-disabled diff --git a/components/tests/ui/testcases/web/search.txt b/components/tests/ui/testcases/web/search.txt index 2e15d72e8b3..4241c3bea22 100644 --- a/components/tests/ui/testcases/web/search.txt +++ b/components/tests/ui/testcases/web/search.txt @@ -21,7 +21,7 @@ ${XpathProjectThumb} xpath=//table[@id='dataTable']//img[contains(@src,'folde ${XpathPlate} xpath=//table[@id='dataTable']//img[@alt='plate'] ${XpathPlateThumb} xpath=//table[@id='dataTable']//img[contains(@src,'folder_plate16.png')] ${XpathScreen} xpath=//table[@id='dataTable']//img[@alt='screen'] -${XpathScreenValid} xpath=//table[@id='dataTable']//img[contains(@src,'folder_screen16.png')] +${XpathScreenThumb} xpath=//table[@id='dataTable']//img[contains(@src,'folder_screen16.png')] ${XpathTable} xpath=//table[@id='dataTable'] ${XpathTableColumn} xpath=//table[@id='dataTable']//tr/td[position()=3] ${XPathAlertText} xpath=//div[contains(@class,'ui-dialog')][contains(@style,'display: block')]//p diff --git a/components/tests/ui/testcases/web/tagging_test.txt b/components/tests/ui/testcases/web/tagging_test.txt index 680e43c48cc..57576cc9fa1 100644 --- a/components/tests/ui/testcases/web/tagging_test.txt +++ b/components/tests/ui/testcases/web/tagging_test.txt @@ -24,19 +24,20 @@ Test Tag Click Element xpath=//h1[@data-name='tags'] Click Element launch_tags_form Wait Until Page Contains Element id_tag - Sleep 5 # allow tags to load + Wait Until Page Contains Element xpath=//div[contains(@class,'ui-progressbar')][@aria-valuenow='100'] ${WAIT} Input Text id_tag robotTagTest${pid}TagOne Click Element id_add_new_tag Click Dialog Button Save Wait Until Page Contains Element xpath=//div[@class='tag']/a[contains(text(), 'robotTagTest${pid}TagOne')] 10 - # Refresh, check and add another Tag, remove first one - Go To ${WELCOME URL}?show=project-${pId} - Wait Until Page Contains Element xpath=//h1[@data-name='tags'] - Click Element xpath=//h1[@data-name='tags'] + # Refresh (select other Project and re-select) + ${nodeId}= Select Project By Id ${projectId} + Wait Until Right Panel Loads Project ${projectId} + Select Project By Id ${pId} + # Check and add another Tag, remove first one Wait Until Page Contains Element xpath=//div[@class='tag']/a[contains(text(), 'robotTagTest${pid}TagOne')] 10 Click Element launch_tags_form Wait Until Page Contains Element id_tag - Sleep 5 # allow tags to load + Wait Until Page Contains Element xpath=//div[contains(@class,'ui-progressbar')][@aria-valuenow='100'] ${WAIT} # Create a second Tag Input Text id_tag robotTagTest${pid}TagTwo Click Element id_add_new_tag @@ -51,12 +52,14 @@ Test Tag # Now select both Projects... - Go To ${WELCOME URL}?show=project-${projectId}|project-${pId} + Meta Click Node ${nodeId} Wait Until Page Contains Element id=batch_ann_title - + # Previously added Tag will show up + Wait Until Page Contains Element xpath=//div[@class='tag']/a[contains(text(), 'robotTagTest${pid}TagTwo')] + Wait Until Element Is Visible launch_tags_form Click Element launch_tags_form Wait Until Page Contains Element id_tag - Sleep 5 # allow tags to load + Wait Until Page Contains Element xpath=//div[contains(@class,'ui-progressbar')][@aria-valuenow='100'] ${WAIT} # Tags created above should be available to add to second Project Wait Until Page Contains Element xpath=//div[@id='id_all_tags']/div[contains(text(),'robotTagTest${pid}TagOne')] @@ -82,7 +85,7 @@ Test Tag # Open Tag dialog again... Click Element launch_tags_form Wait Until Page Contains Element id_tag - Sleep 5 # allow tags to load + Wait Until Page Contains Element xpath=//div[contains(@class,'ui-progressbar')][@aria-valuenow='100'] ${WAIT} # Same tag as before should be on right Wait Until Page Contains Element xpath=//div[@id='id_selected_tags']/div[contains(text(),'robotTagTest${pid}TagOne')] Page Should Not Contain Element xpath=//div[@id='id_selected_tags']/div[contains(text(),'robotTagTest${pid}TagTwo')] @@ -96,6 +99,8 @@ Test Tag # After Save, Tags Two and Three should be added, Tag One removed # Seems we need a proper refresh here to be sure that TagOne is removed. Go To ${WELCOME URL}?show=project-${projectId}|project-${pId} + Wait Until Element Is Visible xpath=//h1[@data-name='tags'] + Click Element xpath=//h1[@data-name='tags'] Wait Until Page Contains Element xpath=//div[@class='tag']/a[contains(text(), 'robotTagTest${pid}TagThree')] 10 Page Should Contain Element xpath=//div[@class='tag']/a[contains(text(), 'robotTagTest${pid}TagTwo')] Page Should Not Contain Element xpath=//div[@class='tag']/a[contains(text(), 'robotTagTest${pid}TagOne')] diff --git a/components/tools/OmeroPy/src/omero/plugins/web.py b/components/tools/OmeroPy/src/omero/plugins/web.py index 91956092058..bed0922225b 100644 --- a/components/tools/OmeroPy/src/omero/plugins/web.py +++ b/components/tools/OmeroPy/src/omero/plugins/web.py @@ -58,6 +58,12 @@ """ +APACHE_MOD_WSGI_ERR = ("[ERROR] You are deploying OMERO.web using Apache and" + " mod_wsgi. OMERO.web does not provide any management" + " for the daemon process which communicates with" + " Apache child processes using UNIX sockets to handle" + " a request.") + def config_required(func): """Decorator validating Django dependencies and omeroweb/settings.py""" @@ -516,11 +522,10 @@ def start(self, args, settings): deploy = getattr(settings, 'APPLICATION_SERVER') if deploy in (settings.WSGI,): - self.ctx.die(609, "You are deploying OMERO.web using apache and" - " mod_wsgi. Generate apache config using" + self.ctx.die(609, "%s\nGenerate apache config using" " 'omero web config apache' or" " 'omero web config apache24' and reload" - " web server.") + " web server." % APACHE_MOD_WSGI_ERR) else: self.ctx.out("Starting OMERO.web... ", newline=False) @@ -615,8 +620,8 @@ def status(self, args, settings): else: self.ctx.err("[NOT STARTED]") elif deploy in (settings.WSGI,): - self.ctx.err("You are deploying OMERO.web using apache and" - " mod_wsgi. Cannot check status.") + self.ctx.err("%s Please check Apache " + "directly." % APACHE_MOD_WSGI_ERR) elif deploy in (settings.DEVELOPMENT,): self.ctx.err( "DEVELOPMENT: You will have to kill processes by hand!") diff --git a/components/tools/OmeroPy/test/integration/clitest/test_import.py b/components/tools/OmeroPy/test/integration/clitest/test_import.py index beb1e93f596..04661c1aee6 100644 --- a/components/tools/OmeroPy/test/integration/clitest/test_import.py +++ b/components/tools/OmeroPy/test/integration/clitest/test_import.py @@ -120,6 +120,7 @@ def setup_method(self, method): self.cli.register("import", plugin.ImportControl, "TEST") self.args += ["import"] self.add_client_dir() + self.keepRootAlive() def set_conn_args(self): host = self.root.getProperty("omero.host") diff --git a/components/tools/OmeroPy/test/unit/clitest/test_web.py b/components/tools/OmeroPy/test/unit/clitest/test_web.py index 0d600d0bd52..a838fc32cbc 100644 --- a/components/tools/OmeroPy/test/unit/clitest/test_web.py +++ b/components/tools/OmeroPy/test/unit/clitest/test_web.py @@ -185,13 +185,17 @@ def testWebStart(self, app_server, monkeypatch, capsys): csout = "Clearing expired sessions. This may take some time... [OK]" assert csout == o.split(os.linesep)[0] if app_server in ('wsgi',): - stderr = ( - ("You are deploying OMERO.web using apache and mod_wsgi. " - "Generate apache config using 'omero web config apache' " - "or 'omero web config apache24' " - "and reload web server.")) - assert stderr == e.split(os.linesep)[0] - assert 1 == len(e.split(os.linesep))-1 + stderr0 = ("[ERROR] You are deploying OMERO.web using Apache and" + " mod_wsgi. OMERO.web does not provide any management" + " for the daemon process which communicates" + " with Apache child processes using UNIX sockets" + " to handle a request.") + stderr1 = ("Generate apache config using" + " 'omero web config apache' or" + " 'omero web config apache24' and reload web server.") + assert stderr0 == e.split(os.linesep)[0] + assert stderr1 == e.split(os.linesep)[1] + assert 2 == len(e.split(os.linesep))-1 elif app_server in ('wsgi-tcp',): startout = "Starting OMERO.web... [OK]" assert startout == o.split(os.linesep)[1] diff --git a/components/tools/OmeroWeb/omeroweb/webclient/controller/container.py b/components/tools/OmeroWeb/omeroweb/webclient/controller/container.py index b5d31368c0f..7864a6fb371 100644 --- a/components/tools/OmeroWeb/omeroweb/webclient/controller/container.py +++ b/components/tools/OmeroWeb/omeroweb/webclient/controller/container.py @@ -241,7 +241,7 @@ def canDownload(self, objDict=None): # As used in metadata_general panel else: return self.image.canDownload() or \ - self.well.canDownload() or self.plate.canDonwload() + self.well.canDownload() or self.plate.canDownload() def listFigureScripts(self, objDict=None): """ @@ -1223,8 +1223,8 @@ def move(self, parent, destination): def remove(self, parents, index, tag_owner_id=None): """ Removes the current object (file, tag, comment, dataset, plate, image) - from its parents by manually deleting the link. - For Comments, we check whether it becomes an orphan & delete if true + from its parents by manually deleting the link. Orphaned comments will + be deleted server side. If self.tag and owner_id is specified, only remove the tag if it is owned by that owner diff --git a/components/tools/OmeroWeb/omeroweb/webclient/decorators.py b/components/tools/OmeroWeb/omeroweb/webclient/decorators.py index d0d3c1a1459..c8f7158cc6f 100644 --- a/components/tools/OmeroWeb/omeroweb/webclient/decorators.py +++ b/components/tools/OmeroWeb/omeroweb/webclient/decorators.py @@ -26,6 +26,7 @@ import logging import omeroweb.decorators +from omero import constants from django.http import HttpResponse from django.conf import settings @@ -115,6 +116,13 @@ def prepare_context(self, request, context, *args, **kwargs): return conn = kwargs['conn'] + # omero constants + context['omero'] = {'constants': { + 'NSCOMPANIONFILE': constants.namespaces.NSCOMPANIONFILE, + 'ORIGINALMETADATA': constants.annotation.file.ORIGINALMETADATA, + 'NSCLIENTMAPANNOTATION': constants.metadata.NSCLIENTMAPANNOTATION + }} + context.setdefault('ome', {}) # don't overwrite existing ome context['ome']['eventContext'] = conn.getEventContext context['ome']['user'] = conn.getUser 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 78871dc8302..047a44c674c 100755 --- a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/css/dusty.css +++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/css/dusty.css @@ -512,7 +512,7 @@ button::-moz-focus-inner { margin-left:5px; overflow:hidden; display:block !important; - background:url(../image/icon_tag_delete.png) center center no-repeat; + background:url(../image/icon_tag_remove.png) center center no-repeat; text-indent:-99px; } 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 c50546b447b..ec4ac382aaa 100644 --- a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/css/layout.css +++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/css/layout.css @@ -430,7 +430,7 @@ } /* Rating */ - #rating_annotations { + #rating_annotations ul { border-color: transparent; } .rating { @@ -442,7 +442,7 @@ border-bottom: solid 1px hsl(210,20%,85%) !important; } .lnfiles:empty { - display: none; + opacity: 0.1; } .myRating img, .addRating img{ cursor: pointer; @@ -980,7 +980,10 @@ - + /* Bulk Annotations */ + .bulk_annotations_table td { + white-space: normal !important; + } diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/image/icon_tag_remove.png b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/image/icon_tag_remove.png new file mode 100644 index 00000000000..a7da64a5d01 Binary files /dev/null and b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/image/icon_tag_remove.png differ diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_comments_pane.js b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_comments_pane.js new file mode 100644 index 00000000000..fbddf834602 --- /dev/null +++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_comments_pane.js @@ -0,0 +1,154 @@ +// Copyright (C) 2016 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 . + + +var CommentsPane = function CommentsPane($element, opts) { + + var $header = $element.children('h1'), + $body = $element.children('div'), + $comments_container = $("#comments_container"), + objects = opts.selected; + var self = this; + + var tmplText = $('#comments_template').html(); + var commentsTempl = _.template(tmplText); + + + var initEvents = (function initEvents() { + + $header.click(function(){ + $header.toggleClass('closed'); + $body.slideToggle(); + + var expanded = !$header.hasClass('closed'); + OME.setPaneExpanded('comments', expanded); + + if (expanded && $comments_container.is(":empty")) { + this.render(); + } + }.bind(this)); + }).bind(this); + + // Comment field - show/hide placeholder and submit button. + $("#add_comment_wrapper label").inFieldLabels(); + $("#id_comment") + .blur(function(event){ + setTimeout(function(){ + $("#add_comment_form input[type='submit']").hide(); + }, 200); // Delay allows clicking on the submit button! + }) + .focus(function(){ + $("#add_comment_form input[type='submit']").show(); + }); + + // bind removeItem to various [-] buttons + $("#comments_container").on("click", ".removeComment", function(event){ + var url = $(this).attr('url'); + var objId = objects.join("|"); + OME.removeItem(event, ".ann_comment_wrapper", url, objId); + return false; + }); + + // handle submit of Add Comment form + $("#add_comment_form").ajaxForm({ + beforeSubmit: function(data, $form, options) { + var textArea = $('#add_comment_form textarea'); + if ($.trim(textArea.val()).length === 0) return false; + // here we specify what objects are to be annotated + objects.forEach(function(o){ + var dtypeId = o.split("-"); + data.push({"name": dtypeId[0], "value": dtypeId[1]}); + }); + }, + success: function(html) { + $("#id_comment").val(""); + self.render(); + }, + }); + + + this.render = function render() { + + if ($comments_container.is(":visible")) { + + if ($comments_container.is(":empty")) { + $comments_container.html("Loading comments..."); + } + + var request = objects.map(function(o){ + return o.replace("-", "="); + }); + request = request.join("&"); + + $.getJSON(WEBCLIENT.URLS.webindex + "api/annotations/?type=comment&" + request, function(data){ + + + // manipulate data... + // make an object of eid: experimenter + var experimenters = data.experimenters.reduce(function(prev, exp){ + prev[exp.id + ""] = exp; + return prev; + }, {}); + + // Populate experimenters within anns + var anns = data.annotations.map(function(ann){ + ann.owner = experimenters[ann.owner.id]; + if (ann.link && ann.link.owner) { + ann.link.owner = experimenters[ann.link.owner.id]; + } + ann.addedBy = [ann.link.owner.id]; + ann.textValue = _.escape(ann.textValue); + return ann; + }); + + // Show most recent comments at the top + anns.sort(function(a, b) { + return a.date < b.date ? 1 : -1; + }); + + // Remove duplicates (same comment on multiple objects) + anns = anns.filter(function(ann, idx){ + // already sorted, so just compare with last item + return (idx === 0 || anns[idx - 1].id !== ann.id); + }); + + // Update html... + var html = ""; + if (anns.length > 0) { + html = commentsTempl({'anns': anns, + 'static': WEBCLIENT.URLS.static_webclient, + 'webindex': WEBCLIENT.URLS.webindex}); + } + $comments_container.html(html); + + // Finish up... + OME.linkify_element($( ".commentText" )); + OME.filterAnnotationsAddedBy(); + $(".tooltip", $comments_container).tooltip_init(); + }); + } + }; + + + initEvents(); + + if (OME.getPaneExpanded('comments')) { + $header.toggleClass('closed'); + $body.show(); + } + + this.render(); +}; \ No newline at end of file diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_customanns_pane.js b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_customanns_pane.js new file mode 100644 index 00000000000..1972e2c4df9 --- /dev/null +++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_customanns_pane.js @@ -0,0 +1,133 @@ +// Copyright (C) 2016 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 . + + +var CustomAnnsPane = function CustomAnnsPane($element, opts) { + + var $header = $element.children('h1'), + $body = $element.children('div'), + $custom_annotations = $("#custom_annotations"), + objects = opts.selected; + + var tmplText = $('#customanns_template').html(); + var customannsTempl = _.template(tmplText); + + + var initEvents = (function initEvents() { + + $header.click(function(){ + $header.toggleClass('closed'); + $body.slideToggle(); + + var expanded = !$header.hasClass('closed'); + OME.setPaneExpanded('others', expanded); + + if (expanded && $custom_annotations.is(":empty")) { + this.render(); + } + }.bind(this)); + }).bind(this); + + + // display xml in a new window + $body.on( "click", ".show_xml", function(event) { + var xml = $(event.target).next().html(); + var newWindow=window.open('','','height=500,width=500,scrollbars=yes, top=50, left=100'); + newWindow.document.write(xml); + newWindow.document.close(); + return false; + }); + + + this.render = function render() { + + if ($custom_annotations.is(":visible")) { + + if ($custom_annotations.is(":empty")) { + $custom_annotations.html("Loading other annotations..."); + } + + var request = objects.map(function(o){ + return o.replace("-", "="); + }); + request = request.join("&"); + + $.getJSON(WEBCLIENT.URLS.webindex + "api/annotations/?type=custom&" + request, function(data){ + + // manipulate data... + // make an object of eid: experimenter + var experimenters = data.experimenters.reduce(function(prev, exp){ + prev[exp.id + ""] = exp; + return prev; + }, {}); + + // Populate experimenters within anns + var anns = data.annotations.map(function(ann){ + ann.owner = experimenters[ann.owner.id]; + if (ann.link && ann.link.owner) { + ann.link.owner = experimenters[ann.link.owner.id]; + } + + // AddedBy IDs for filtering + ann.addedBy = [ann.link.owner.id]; + // convert 'class' to 'type' E.g. XmlAnnotationI to Xml + ann.type = ann.class.replace('AnnotationI', ''); + var attrs = ['textValue', 'timeValue', 'termValue', 'longValue', 'doubleValue', 'boolValue']; + attrs.forEach(function(a){ + if (ann[a] !== undefined){ + ann.value = _.escape(ann[a]); + } + }); + if (objects.length > 1) { + ann.parent = { + 'class': ann.link.parent.class.slice(0, -1), // slice parent class 'ProjectI' > 'Project' + 'id': ann.link.parent.id + }; + } + return ann; + }); + + // Show most recent annotations at the top + anns.sort(function(a, b) { + return a.date < b.date; + }); + + // Update html... + var html = ""; + if (anns.length > 0) { + html = customannsTempl({'anns': anns, + 'static': WEBCLIENT.URLS.static_webclient, + 'webindex': WEBCLIENT.URLS.webindex}); + } + $custom_annotations.html(html); + + // Finish up... + OME.filterAnnotationsAddedBy(); + $(".tooltip", $custom_annotations).tooltip_init(); + }); + } + }; + + + initEvents(); + + if (OME.getPaneExpanded('others')) { + $header.toggleClass('closed'); + $body.show(); + } + + this.render(); +}; \ No newline at end of file diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_fileanns_pane.js b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_fileanns_pane.js new file mode 100644 index 00000000000..d631c728290 --- /dev/null +++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_fileanns_pane.js @@ -0,0 +1,242 @@ +// Copyright (C) 2016 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 . + + +var FileAnnsPane = function FileAnnsPane($element, opts) { + + var $header = $element.children('h1'), + $body = $element.children('div'), + $fileanns_container = $("#fileanns_container"), + objects = opts.selected; + var self = this; + + var tmplText = $('#fileanns_template').html(); + var filesTempl = _.template(tmplText); + + + var initEvents = (function initEvents() { + + $header.click(function(){ + $header.toggleClass('closed'); + $body.slideToggle(); + + var expanded = !$header.hasClass('closed'); + OME.setPaneExpanded('files', expanded); + + if (expanded && $fileanns_container.is(":empty")) { + this.render(); + } + }.bind(this)); + }).bind(this); + + + // set-up the attachment selection form to use AJAX. (requires jquery.form.js plugin) + if ($("#choose_attachments_form").length === 0) { + $("
") + .hide().appendTo('body'); + } + $('#choose_attachments_form').ajaxForm({ + beforeSubmit: function(data) { + $("#batch_attachments_form").dialog( "close" ); + $("#fileann_spinner").show(); + }, + success: function() { + $("#fileann_spinner").hide(); + // update the list of file annotations and bind actions + self.render(); + }, + error: function(data){ + $("#fileann_spinner").hide(); + alert("Upload failed [" + data.status + " " + data.statusText + "]"); + } + }); + // prepare dialog for choosing file to attach... + $("#choose_attachments_form").dialog({ + autoOpen: false, + resizable: false, + height: 420, + width:360, + modal: true, + buttons: { + "Accept": function() { + // simply submit the form (AJAX handling set-up above) + $("#choose_attachments_form").submit(); + $( this ).dialog( "close" ); + }, + "Cancel": function() { + $( this ).dialog( "close" ); + } + } + }); + // show dialog for choosing file to attach... + $("#choose_file_anns").click(function() { + // show dialog first, then do the AJAX call to load files... + var $attach_form = $( "#choose_attachments_form" ); + $attach_form.dialog( "open" ); + // load form via AJAX... + var load_url = $(this).attr('href'); + $attach_form.html(" 
Loading attachments"); + $attach_form.load(load_url); + return false; + }); + + + // Show/hide checkboxes beside files to select files for scripts + $(".toolbar input[type=button]", $body).click( + OME.toggleFileAnnotationCheckboxes + ); + $("#fileanns_container").on( + "change", "li input[type=checkbox]", + OME.fileAnnotationCheckboxChanged + ); + + $("#fileanns_container").on("click", ".removeFile", function(event) { + var url = $(this).attr('href'), + parents = objects.join("|"); // E.g image-123|image-456 + OME.removeItem(event, ".file_ann_wrapper", url, parents); + return false; + }); + + // delete action (files) + $("#fileanns_container").on("click", ".deleteFile", function(event) { + var url = $(this).attr('href'); + OME.deleteItem(event, "file_ann_wrapper", url); + }); + + + var isNotCompanionFile = function isNotCompanionFile(ann) { + return ann.ns !== OMERO.constants.namespaces.NSCOMPANIONFILE; + }; + + var compareParentName = function(a, b){ + return a.parent.name.toLowerCase() > b.parent.name.toLowerCase() ? 1 : -1; + }; + + + this.render = function render() { + + if ($fileanns_container.is(":visible")) { + + if ($fileanns_container.is(":empty")) { + $fileanns_container.html("Loading attachments..."); + } + + var request = objects.map(function(o){ + return o.replace("-", "="); + }); + request = request.join("&"); + + $.getJSON(WEBCLIENT.URLS.webindex + "api/annotations/?type=file&" + request, function(data){ + + var checkboxesAreVisible = $( + "#fileanns_container input[type=checkbox]:visible" + ).length > 0; + + // manipulate data... + // make an object of eid: experimenter + var experimenters = data.experimenters.reduce(function(prev, exp){ + prev[exp.id + ""] = exp; + return prev; + }, {}); + + // Populate experimenters within anns + var anns = data.annotations.map(function(ann){ + ann.owner = experimenters[ann.owner.id]; + if (ann.link && ann.link.owner) { + ann.link.owner = experimenters[ann.link.owner.id]; + } + // AddedBy IDs for filtering + ann.addedBy = [ann.link.owner.id]; + ann.description = _.escape(ann.description); + ann.file.size = ann.file.size.filesizeformat(); + return ann; + }); + // Don't show companion files + anns = anns.filter(isNotCompanionFile); + + + // If we are batch annotating multiple objects, we show a summary of each tag + if (objects.length > 1) { + + // Map tag.id to summary for that tag + var summary = {}; + anns.forEach(function(ann){ + var annId = ann.id, + linkOwner = ann.link.owner.id; + if (summary[annId] === undefined) { + ann.canRemove = false; + ann.canRemoveCount = 0; + ann.links = []; + ann.addedBy = []; + summary[annId] = ann; + } + // Add link to list... + var l = ann.link; + // slice parent class 'ProjectI' > 'Project' + l.parent.class = l.parent.class.slice(0, -1); + summary[annId].links.push(l); + + // ...and summarise other properties on the ann + if (l.permissions.canDelete) { + summary[annId].canRemoveCount += 1; + } + summary[annId].canRemove = summary[annId].canRemove || l.permissions.canDelete; + if (summary[annId].addedBy.indexOf(linkOwner) === -1) { + summary[annId].addedBy.push(linkOwner); + } + }); + + // convert summary back to list of 'anns' + anns = []; + for (var annId in summary) { + if (summary.hasOwnProperty(annId)) { + summary[annId].links.sort(compareParentName); + anns.push(summary[annId]); + } + } + } + + // Update html... + var html = ""; + if (anns.length > 0) { + html = filesTempl({'anns': anns, + 'webindex': WEBCLIENT.URLS.webindex, + 'userId': WEBCLIENT.USER.id}); + } + $fileanns_container.html(html); + + // Finish up... + OME.filterAnnotationsAddedBy(); + if (checkboxesAreVisible) { + $("#fileanns_container input[type=checkbox]:not(:visible)").toggle(); + } + $(".tooltip", $fileanns_container).tooltip_init(); + }); + + } + }; + + + initEvents(); + + if (OME.getPaneExpanded('files')) { + $header.toggleClass('closed'); + $body.show(); + } + + this.render(); +}; \ No newline at end of file diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_mapanns_pane.js b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_mapanns_pane.js new file mode 100644 index 00000000000..570ee24fe54 --- /dev/null +++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_mapanns_pane.js @@ -0,0 +1,134 @@ +// Copyright (C) 2016 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 . + + +var MapAnnsPane = function MapAnnsPane($element, opts) { + + var $header = $element.children('h1'), + $body = $element.children('div'), + $mapAnnContainer = $("#mapAnnContainer"), + objects = opts.selected, + canAnnotate = opts.canAnnotate; + + var tmplText = $('#mapanns_template').html(); + var mapAnnsTempl = _.template(tmplText); + + + var initEvents = (function initEvents() { + + $header.click(function(){ + $header.toggleClass('closed'); + $body.slideToggle(); + + var expanded = !$header.hasClass('closed'); + OME.setPaneExpanded('maps', expanded); + + if (expanded && $mapAnnContainer.is(":empty")) { + this.render(); + } + }.bind(this)); + }).bind(this); + + + var isClientMapAnn = function(ann) { + return ann.ns === OMERO.constants.metadata.NSCLIENTMAPANNOTATION; + }; + var isMyClientMapAnn = function(ann) { + return isClientMapAnn(ann) && ann.owner.id == WEBCLIENT.USER.id; + }; + + + this.render = function render() { + + if ($mapAnnContainer.is(":visible")) { + + if ($mapAnnContainer.is(":empty")) { + $mapAnnContainer.html("Loading key value annotations..."); + } + + var request = objects.map(function(o){ + return o.replace("-", "="); + }); + + $.getJSON(WEBCLIENT.URLS.webindex + "api/annotations/?type=map&" + request, function(data){ + + // manipulate data... + // make an object of eid: experimenter + var experimenters = data.experimenters.reduce(function(prev, exp){ + prev[exp.id + ""] = exp; + return prev; + }, {}); + + // Populate experimenters within anns + var anns = data.annotations.map(function(ann){ + ann.owner = experimenters[ann.owner.id]; + if (ann.link && ann.link.owner) { + ann.link.owner = experimenters[ann.link.owner.id]; + } + // AddedBy IDs for filtering + ann.addedBy = [ann.link.owner.id]; + return ann; + }); + + // Sort map anns into 3 lists... + var client_map_annotations = []; + var my_client_map_annotations = []; + var map_annotations = []; + + anns.forEach(function(ann){ + if (isMyClientMapAnn(ann)) { + my_client_map_annotations.push(ann); + } else if (isClientMapAnn(ann)) { + client_map_annotations.push(ann); + } else { + map_annotations.push(ann); + } + }); + + // Update html... + var html = ""; + var showHead = true; + if (canAnnotate) { + if (my_client_map_annotations.length === 0) { + showHead = false; + my_client_map_annotations = [{}]; // placeholder + } + html = mapAnnsTempl({'anns': my_client_map_annotations, + 'showTableHead': showHead, 'showNs': false, 'clientMapAnn': true}); + } + html = html + mapAnnsTempl({'anns': client_map_annotations, + 'showTableHead': false, 'showNs': false, 'clientMapAnn': true}); + html = html + mapAnnsTempl({'anns': map_annotations, + 'showTableHead': false, 'showNs': true, 'clientMapAnn': false}); + $mapAnnContainer.html(html); + + // Finish up... + OME.filterAnnotationsAddedBy(); + $(".tooltip", $mapAnnContainer).tooltip_init(); + }); + } + }; + + + initEvents(); + + if (OME.getPaneExpanded('maps')) { + $header.toggleClass('closed'); + $body.show(); + } + + this.render(); +}; \ No newline at end of file diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_ratings_pane.js b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_ratings_pane.js new file mode 100644 index 00000000000..f9047eb6a5f --- /dev/null +++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_ratings_pane.js @@ -0,0 +1,135 @@ +// Copyright (C) 2016 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 . + + +var RatingsPane = function RatingsPane($element, opts) { + + var $header = $element.children('h1'), + $body = $element.children('div'), + $rating_annotations = $("#rating_annotations"), + objects = opts.selected, + canAnnotate = opts.canAnnotate, + self = this; + + var request = objects.map(function(o){ + return o.replace("-", "="); + }); + request = request.join("&"); + + var tmplText = $('#ratings_template').html(); + var ratingsTempl = _.template(tmplText); + + + var initEvents = (function initEvents() { + + $header.click(function(){ + $header.toggleClass('closed'); + $body.slideToggle(); + + var expanded = !$header.hasClass('closed'); + OME.setPaneExpanded('ratings', expanded); + + if (expanded && $rating_annotations.is(":empty")) { + this.render(); + } + }.bind(this)); + }).bind(this); + + + $("#rating_annotations.canAnnotate").on("click", ".myRating img", function(event){ + var $rating = $(this), + clickX = event.pageX - $rating.offset().left; + var r = (clickX/ $rating.width()) * 5; + r = parseInt(Math.ceil(r), 10); + setRating(r, $rating); + }); + + $("#rating_annotations.canAnnotate").on("click", ".removeRating", function(event){ + var $liMyRating = $(this).parent(), + $ratingimg = $liMyRating.find('img'); + setRating(0, $ratingimg); + }); + + var setRating = function(rating, $rating) { + // update rating + if ($rating) { + var rating_src = WEBCLIENT.URLS.static_webclient + "image/rating" + rating + ".png"; + $rating.attr('src', rating_src); + } + // update rating annotation + var rating_url = WEBCLIENT.URLS.webindex + "annotate_rating/?" + request; + rating_url += "&rating=" + rating; + $.post(rating_url, function(data) { + // update summary + self.render(); + }); + }; + + + var isClientMapAnn = function(ann) { + return ann.ns === OMERO.constants.metadata.NSCLIENTMAPANNOTATION; + }; + var isMyClientMapAnn = function(ann) { + return isClientMapAnn(ann) && ann.owner.id == WEBCLIENT.USER.id; + }; + + + this.render = function render() { + + if ($rating_annotations.is(":visible")) { + + if ($rating_annotations.is(":empty")) { + $rating_annotations.html("Loading ratings..."); + } + + $.getJSON(WEBCLIENT.URLS.webindex + "api/annotations/?type=rating&" + request, function(data){ + + var anns = data.annotations; + var sum = anns.reduce(function(prev, ann){ + return prev + ann.longValue; + }, 0); + var myRatings = anns.filter(function(ann){ + return ann.owner.id == WEBCLIENT.USER.id; + }); + var myRating = myRatings.length > 0 ? myRatings[0].longValue : 0; + var average = Math.round(sum/anns.length); + + // Update html... + var html = ratingsTempl({'anns': anns, + 'canAnnotate': canAnnotate, + 'myRating': myRating, + 'average': average, + 'count': anns.length, + 'static': WEBCLIENT.URLS.static_webclient}); + $rating_annotations.html(html); + + // Finish up... + OME.filterAnnotationsAddedBy(); + $(".tooltip", $rating_annotations).tooltip_init(); + }); + } + }; + + + initEvents(); + + if (OME.getPaneExpanded('ratings')) { + $header.toggleClass('closed'); + $body.show(); + } + + this.render(); +}; \ No newline at end of file diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_tags_pane.js b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_tags_pane.js new file mode 100644 index 00000000000..41c516846da --- /dev/null +++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.right_panel_tags_pane.js @@ -0,0 +1,213 @@ +// Copyright (C) 2016 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 . + + +var TagPane = function TagPane($element, opts) { + + var $header = $element.children('h1'), + $body = $element.children('div'), + $tags_container = $("#tags_container"), + objects = opts.selected, + self = this; + + var tmplText = $('#tags_template').html(); + var tagTmpl = _.template(tmplText); + + + var initEvents = (function initEvents() { + + $header.click(function(){ + $header.toggleClass('closed'); + $body.slideToggle(); + + var expanded = !$header.hasClass('closed'); + OME.setPaneExpanded("tags", expanded); + + if (expanded && $tags_container.is(":empty")) { + this.render(); + } + }.bind(this)); + }).bind(this); + + + if ($("#add_tags_form").length === 0) { + $("
") + .hide().appendTo('body'); + } + + $("#launch_tags_form").click(function(event) { + $("#add_tags_form").dialog("open"); + // load form via AJAX... + var load_url = $(this).attr('href'); + $("#add_tags_form").load(load_url); + return false; + }); + // set-up the tags form to use dialog + + $("#add_tags_form").dialog({ + autoOpen: false, + resizable: false, + height: 520, + width: 780, + modal: true, + buttons: { + "Save": function() { + // simply submit the form (AJAX handling set-up above) + $("#add_tags_form").trigger('prepare-submit').submit(); + $( this ).dialog( "close" ); + }, + "Cancel": function() { + $( this ).dialog( "close" ); + }, + "Reset": function() { + // discard all changes and reload the form + $("#add_tags_form").html('').load($("#launch_tags_form").attr('href')); + } + } + }); + $('#add_tags_form').ajaxForm({ + beforeSubmit: function(data) { + $("#tagann_spinner").show(); + // $("#add_tags_form").dialog( "close" ); + }, + success: function(data) { + // hide in case it was submitted via 'Enter' + $("#add_tags_form").dialog( "close" ); + // update the list of tags: Re-render tag pane... + self.render(); + // show_batch_msg("Tags added to Objects"); + }, + error: function(data) { + $("#tagann_spinner").hide(); + } + }); + + + // bind removeItem to various [-] buttons + $("#tags_container").on("click", ".removeTag", function(event){ + var url = $(this).attr('url'), + parents = objects.join("|"); // E.g image-123|image-456 + OME.removeItem(event, ".tag_annotation_wrapper", url, parents); + return false; + }); + + var compareParentName = function(a, b){ + return a.parent.name.toLowerCase() > b.parent.name.toLowerCase() ? 1 : -1; + }; + + this.render = function render() { + + if ($tags_container.is(":visible")) { + + if ($tags_container.is(":empty")) { + $tags_container.html("Loading tags..."); + } + + var request = objects.map(function(o){ + return o.replace("-", "="); + }); + request = request.join("&"); + + $.getJSON(WEBCLIENT.URLS.webindex + "api/annotations/?type=tag&" + request, function(data){ + + // manipulate data... + // make an object of eid: experimenter + var experimenters = data.experimenters.reduce(function(prev, exp){ + prev[exp.id + ""] = exp; + return prev; + }, {}); + + // Populate experimenters within tags + // And do other tag marshalling + var tags = data.annotations.map(function(tag){ + tag.owner = experimenters[tag.owner.id]; + if (tag.link && tag.link.owner) { + tag.link.owner = experimenters[tag.link.owner.id]; + } + // AddedBy IDs for filtering + tag.addedBy = [tag.link.owner.id]; + tag.textValue = _.escape(tag.textValue); + tag.description = _.escape(tag.description); + tag.canRemove = tag.link.permissions.canDelete; + return tag; + }); + + // If we are batch annotating multiple objects, we show a summary of each tag + if (objects.length > 1) { + + // Map tag.id to summary for that tag + var summary = {}; + tags.forEach(function(tag){ + var tagId = tag.id, + linkOwner = tag.link.owner.id; + if (summary[tagId] === undefined) { + summary[tagId] = {'textValue': tag.textValue, + 'id': tag.id, + 'canRemove': false, + 'canRemoveCount': 0, + 'links': [], + 'addedBy': [] + }; + } + // Add link to list... + var l = tag.link; + // slice parent class 'ProjectI' > 'Project' + l.parent.class = l.parent.class.slice(0, -1); + summary[tagId].links.push(l); + + // ...and summarise other properties on the tag + if (l.permissions.canDelete) { + summary[tagId].canRemoveCount += 1; + } + summary[tagId].canRemove = summary[tagId].canRemove || l.permissions.canDelete; + if (summary[tagId].addedBy.indexOf(linkOwner) === -1) { + summary[tagId].addedBy.push(linkOwner); + } + }); + + // convert summary back to list of 'tags' + tags = []; + for (var tagId in summary) { + if (summary.hasOwnProperty(tagId)) { + summary[tagId].links.sort(compareParentName); + tags.push(summary[tagId]); + } + } + } + + // Update html... + var html = tagTmpl({'tags': tags, + 'webindex': WEBCLIENT.URLS.webindex, + 'userId': WEBCLIENT.USER.id}); + $tags_container.html(html); + + // Finish up... + OME.filterAnnotationsAddedBy(); + $(".tooltip", $tags_container).tooltip_init(); + }); + + } + }; + + initEvents(); + + if (OME.getPaneExpanded('tags')) { + $header.toggleClass('closed'); + $body.show(); + } + + this.render(); +}; \ No newline at end of file diff --git a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.tagging_form.js b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.tagging_form.js index 43390bbec94..cb283756f0c 100644 --- a/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.tagging_form.js +++ b/components/tools/OmeroWeb/omeroweb/webclient/static/webclient/javascript/ome.tagging_form.js @@ -2,7 +2,7 @@ eqeqeq: true, immed: true, indent: 4, latedef: true, newcap: true, noarg: true, noempty: true, nonew: true, undef: true, unused: true, trailing: true */ -/*global $, setTimeout, clearTimeout, OME */ +/*global $, setTimeout, clearTimeout, OME, WEBCLIENT */ /*exported tagging_form */ // // Copyright (C) 2013-2014 University of Dundee & Open Microscopy Environment. @@ -163,8 +163,8 @@ var tagging_form = function( link_owner = 'you and ' + link_owner; } } - var title = create_tag_title(tag.d, owners[tag.o], parent_id ? parent_id.t : null, - link_owner); + var title = create_tag_title(tag.d, owners[tag.o], + parent_id ? parent_id.t : null, link_owner); $this.tooltip({ track: false, show: false, @@ -194,7 +194,9 @@ var tagging_form = function( var num_desc_callbacks = 0; var load = function(mode, callback, offset, limit) { - var url = $("#launch_tags_form").attr('href') + "&jsonmode=" + mode; + var url = WEBCLIENT.URLS.webindex + "marshal_tagging_form_data"; + url = url + "?jsonmode=" + mode + + "&group=" + WEBCLIENT.active_group_id; if (offset !== undefined && limit !== undefined) { url += "&offset=" + offset + "&limit=" + limit; } @@ -402,7 +404,8 @@ var tagging_form = function( html += " data-description='" + encode_html(description) + "'"; } var title = create_tag_title(description, owner, tagset); - // Add content even if it is empty and handle case for no tooltip in the tooltip code + // Add content even if it is empty + // and handle case for no tooltip in the tooltip code html += " data-content='" + title.replace(/'/g, "'") + "'"; html += ">" + encode_html(text) + ""; return html; @@ -736,13 +739,13 @@ var tagging_form = function( input.val(''); input.removeClass('placeholder'); } - }).blur(function() { + }).blur(function() { var input = $(this); if (input.val() === '' || input.val() === input.attr('placeholder')) { input.addClass('placeholder'); input.val(input.attr('placeholder')); } - }).blur().parents('form').submit(function() { + }).blur().parents('form').submit(function() { $(this).find('[placeholder]').each(function() { var input = $(this); if (input.val() === input.attr('placeholder')) { 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 21a0241ccc8..a202ab9d964 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 @@ -60,13 +60,15 @@ OME.getURLParameter = function(key) { return false; }; -jQuery.fn.hide_if_empty = function() { - if ($(this).children().length === 0) { - $(this).hide(); - } else { - $(this).show(); - } - return this; +var linkify = function(input) { + var regex = /(https?|ftp|file):\/\/[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]/g; + return input.replace(regex, "$&"); +}; +OME.linkify_element = function(elements) { + elements.each(function() { + var $this = $(this); + $this.html(linkify($this.html())); + }); }; // called from OME.tree_selection_changed() below @@ -231,7 +233,7 @@ OME.well_selection_changed = function($selected, well_index, plate_class) { // handle deleting of Tag, File, Comment // on successful delete via AJAX, the parent .domClass is hidden -OME.removeItem = function(event, domClass, url, parentId, index) { +OME.removeItem = function(event, domClass, url, parentId) { var removeId = $(event.target).attr('id'); var dType = removeId.split("-")[1]; // E.g. 461-comment // /webclient/action/remove/comment/461/?parent=image-257 @@ -245,16 +247,14 @@ OME.removeItem = function(event, domClass, url, parentId, index) { $.ajax({ type: "POST", url: url, - data: {'parent':parentId, 'index':index}, + data: {'parent':parentId}, dataType: 'json', success: function(r){ if(eval(r.bad)) { OME.alert_dialog(r.errs); } else { // simply remove the item (parent class div) - //console.log("Success function"); $parent.remove(); - $annContainer.hide_if_empty(); } } }); @@ -283,7 +283,6 @@ OME.deleteItem = function(event, domClass, url) { } else { // simply remove the item (parent class div) $parent.remove(); - $annContainer.hide_if_empty(); window.parent.OME.refreshActivities(); } } @@ -1029,6 +1028,27 @@ OME.applyRenderingSettings = function(rdef_url, selected) { ); }; +// pair of methods used by right panel tab panes +// to store expanded state between right-panel reloads +OME.setPaneExpanded = function setPaneExpanded(name, expanded) { + var open_panes = $("#metadata_general").data('open_panes') || []; + if (expanded && open_panes.indexOf(name) === -1) { + open_panes.push(name); + } + if (!expanded && open_panes.indexOf(name) > -1) { + open_panes = open_panes.reduce(function(l, item){ + if (item !== name) l.push(item); + return l; + }, []); + } + $("#metadata_general").data('open_panes', open_panes); +}; + +OME.getPaneExpanded = function getPaneExpanded(name) { + var open_panes = $("#metadata_general").data('open_panes') || ["details"]; + return open_panes.indexOf(name) > -1; +}; + jQuery.fn.tooltip_init = function() { $(this).tooltip({ items: '.tooltip', diff --git a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/annotations_share.html b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/annotations_share.html index 1ee4607b2ef..a09b0ab182a 100644 --- a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/annotations_share.html +++ b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/annotations_share.html @@ -35,18 +35,6 @@ - {% endif %} - - - - - - -
    - {% include "webclient/annotations/fileanns.html" %} -
- - - - - -
- - - - - - - - - - - - {% if not annotationBlocked %} -
- -

{% trans "Comment:" %}

- - {% csrf_token %} - - - - - - - - - - - - - - -
{{ form_comment.image }}
{{ form_comment.dataset }}
{{ form_comment.project }}
{{ form_comment.screen }}
{{ form_comment.plate }}
{{ form_comment.acquisition }}
{{ form_comment.well }}
{{ form_comment.comment }}
- - -
- - -
- {% for tann in manager.text_annotations %} - {% include "webclient/annotations/comment.html" %} - {% endfor %} -
- -
- -
- {% endif %} - {% endblock %} diff --git a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/comments_underscore.html b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/comments_underscore.html new file mode 100644 index 00000000000..cb9c5e6ec1d --- /dev/null +++ b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/comments_underscore.html @@ -0,0 +1,40 @@ +<% _.each(anns, function(ann) { %> +
+ +
+ <%= ann.owner.firstName %> <%= ann.owner.lastName %> +
+ +
+
+ + <%= ann.owner.firstName %> <%= ann.owner.lastName %> + + at + <% print(OME.formatDate(ann.link.date)) %> +
+ + <% if (ann.permissions.canDelete) { %> + + <% } %> + +
+ <%= ann.textValue %> +
+
+ + <% if (ann.ns || ann.description) { %> + + <% } %> + +
+<% }) %> diff --git a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/customanns_underscore.html b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/customanns_underscore.html new file mode 100644 index 00000000000..eb8cdd8e0e0 --- /dev/null +++ b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/customanns_underscore.html @@ -0,0 +1,31 @@ + +<% _.each(anns, function(ann) { %> + + + <%= ann.type %> + +
+ <% if (ann.type === 'Xml') { %> +
<% print((ann.value + "").slice(0, 20)) %>...
+
Open in new window
+
<%= ann.value %>
+ <% } else { %> +
<%= ann.value %>
+ <% } %> +
+ + + + +<% }) %> \ No newline at end of file diff --git a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/fileanns_underscore.html b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/fileanns_underscore.html new file mode 100644 index 00000000000..936a4d3df8a --- /dev/null +++ b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/fileanns_underscore.html @@ -0,0 +1,59 @@ + +<% _.each(anns, function(ann) { %> +
  • + + + <%= ann.file.name %> + (<%= ann.file.size %>) + + + + +
    + + <% if (ann.link.permissions.canDelete) { %> + + <% } %> + + <% if (ann.permissions.canDelete) { %> + × + <% } %> + +
    +
  • + +<% }) %> \ No newline at end of file diff --git a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/includes/description.html b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/includes/description.html index 3e1864cf695..3819b5b96bb 100644 --- a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/includes/description.html +++ b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/includes/description.html @@ -26,7 +26,7 @@ {{ obj.description|default:"Add Description"|escape|linebreaksbr}} -

    diff --git a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/includes/name.html b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/includes/name.html index d2fc00dec67..45711fd4578 100644 --- a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/includes/name.html +++ b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/includes/name.html @@ -26,7 +26,7 @@

    {{ nameText|escape }}

    - diff --git a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/mapannotations.html b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/mapannotations.html index 8ba6e642e9d..67e8ae71469 100644 --- a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/mapannotations.html +++ b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/mapannotations.html @@ -248,8 +248,10 @@ var enableToolbarButtons = function enableToolbarButtons() { - var $selRows = $('.keyValueTable tr.ui-selected'), - enabled = {"Insert rows": false, + var canEdit = {% if manager.canAnnotate %}true{% else %}false{% endif %}, + visible = $(".editableKeyValueTable:visible").length > 0, + $selRows = $('.keyValueTable tr.ui-selected'), + enabled = {"Insert row": false, "Copy rows": false, "Paste rows": false, "Delete rows": false}, @@ -258,15 +260,14 @@ // Nothing selected - actions will apply to user's own table if ($selRows.length == 0) { - var canEdit = $('.editableKeyValueTable').length > 0; - enabled["Insert rows"] = canEdit; + enabled["Insert row"] = canEdit && visible; enabled["Copy rows"] = false; enabled["Paste rows"] = (canPaste && canEdit); enabled["Delete rows"] = false; } else { - var $table = $selRows.parent().parent(), - canEdit = $table.hasClass('editableKeyValueTable'); - enabled["Insert rows"] = canEdit; + var $table = $selRows.parent().parent(); + canEdit = $table.hasClass('editableKeyValueTable'); + enabled["Insert row"] = canEdit; enabled["Copy rows"] = true; enabled["Paste rows"] = (canPaste && canEdit); enabled["Delete rows"] = canEdit; @@ -334,13 +335,13 @@ case "Paste rows": pasteSelectedRows(); break; - case "Insert rows": + case "Insert row": insertRow(); } }); // Select (and Start editing?) when you click a td - $( ".keyValueTable tbody" ).on( "click", "td", function(event) { + $("#mapAnnContainer").on( "click", ".keyValueTable tbody td", function(event) { var $td = $(event.target); // ignore clicks on other elements, e.g. if ($td[0].tagName.toLowerCase() != "td") return; @@ -357,7 +358,7 @@ // Handle keys when editing // Start editing when you click a td - $( ".editableKeyValueTable tbody" ).on( "keydown", "input", function(event) { + $("#mapAnnContainer").on( "keydown", ".editableKeyValueTable tbody input", function(event) { var $td = $(event.target).parent(); switch (event.which) { @@ -387,7 +388,7 @@ if (event.which == 9) return false }); - $( ".keyValueTable tbody" ).on( "blur", "input", function(event) { + $("#mapAnnContainer").on( "blur", ".keyValueTable tbody input", function(event) { saveEdit(); }); @@ -412,7 +413,7 @@ src="{% static "webgateway/img/spinner.gif" %}" title="Saving to server..." />
  • + alt="Insert row" title="Insert row" />
  • -
    - -{% if manager.my_client_map_annotations %} - {% for ma in manager.my_client_map_annotations %} - {% with canEdit=ma.canEdit showTableHead=True %} - {% include "webclient/annotations/mapannotation.html" %} - {% endwith %} - {% endfor %} -{% elif manager.canAnnotate %} - - {% with canEdit=True %} - {% include "webclient/annotations/mapannotation.html" %} - {% endwith %} -{% endif %} - -{% if manager.client_map_annotations %} - -{% if manager.canAnnotate %} - -
    -{% endif %} - -{% for ma in manager.client_map_annotations %} - {% with canEdit=ma.canEdit %} - {% include "webclient/annotations/mapannotation.html" %} - {% endwith %} -{% endfor %} -{% endif %} - -{% if manager.map_annotations %} -
    -{% for ma in manager.map_annotations %} - {% with canEdit=False showNs=True %} - {% include "webclient/annotations/mapannotation.html" %} - {% endwith %} -{% endfor %} -{% endif %} - -
    +
    diff --git a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/mapanns_underscore.html b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/mapanns_underscore.html new file mode 100644 index 00000000000..ba6aa49c52c --- /dev/null +++ b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/mapanns_underscore.html @@ -0,0 +1,57 @@ +<% _.each(anns, function(ann) { %> + + + data-annId="<%= ann.id %>" + data-added-by="<% print (ann.addedBy.join(',')) %>" + <% } else { %> + data-added-by="<%= WEBCLIENT.USER.id %>" + <% } %> + class="keyValueTable + <% if (!ann.id || (ann.permissions.canEdit && clientMapAnn)){ %> editableKeyValueTable <% } %> + "> + + + + + <% if (showTableHead) { %> + + + + + <% } %> + + <% if (showNs && ann.ns) { %> + + + + <% } %> + + + + <% if (ann.id) { %> + <% _.each(ann.values, function(row) { %> + + + + + <% }) %> + <% } else { %> + + + + + <% } %> +
    + <% if (ann.id) { %> + Added by: <%= ann.owner.firstName %> <%= ann.owner.lastName %> + + + <% } %> +
    KeyValue
    <%= ann.ns.slice(0, 50) %>
    <% print(_.escape(row['0'])) %><% print(_.escape(row['1'])) %>
    Add KeyAdd Value
    + +<% }) %> diff --git a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/metadata_general.html b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/metadata_general.html index 2d1f53da535..f05d151a1b6 100644 --- a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/metadata_general.html +++ b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/metadata_general.html @@ -4,7 +4,7 @@ {% load common_tags %} {% comment %} -

    +
    +

    Tags

    -
    + +
    -

    +
    +

    Key-Value Pairs

    -
    + +
    {% if manager.well %} -

    +

    Tables

    -
    + @@ -938,121 +731,60 @@

    +
    +

    Attachments

    -
    +
    -

    +
    +

    Ratings

    -
    - - {% with ratings=manager.getGroupedRatings %} - - {% if manager.canAnnotate %} -
    - -
    - {% endif %} - -
    - - -
    - (avg: - {{ ratings.average }} - / {{ ratings.count }} - votes) -
    - {% endwith %} +
    -

    +
    +

    Comments

    -
    + - {% endif %}
    {% endif %} diff --git a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/ratings_underscore.html b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/ratings_underscore.html new file mode 100644 index 00000000000..8a241ba64f6 --- /dev/null +++ b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/ratings_underscore.html @@ -0,0 +1,27 @@ + +
    style="display:none"<% } %> > + (avg: + <%= average %> + / <%= count %> + votes) +
    \ No newline at end of file diff --git a/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/tags_underscore.html b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/tags_underscore.html new file mode 100644 index 00000000000..6f078c095d9 --- /dev/null +++ b/components/tools/OmeroWeb/omeroweb/webclient/templates/webclient/annotations/tags_underscore.html @@ -0,0 +1,48 @@ + +<% _.each(tags, function(tag) { %> + + +
    + + <%= tag.textValue %> + + + <% if (tag.canRemove) { %> + + - + + <% } %> + + +
    + +
    + +<% }) %> \ No newline at end of file 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 a5b074e9725..296f9fa48a6 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 @@ -53,13 +53,31 @@ - + + + + + + + @@ -69,6 +87,12 @@ + + + + + + @@ -114,6 +138,60 @@