diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000..b8f48e4c3b --- /dev/null +++ b/.eslintrc @@ -0,0 +1,17 @@ +{ + "extends": "airbnb-base/legacy", + "globals": { + "Mirador": true, + "jQuery": true + }, + "rules": { + "no-param-reassign": [ + 2, { + "props": true, + "ignorePropertyModificationsFor": [ + "$" + ] + } + ] + } +} diff --git a/.travis.yml b/.travis.yml index 90f1958dfc..ffe6a6071d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: node_js sudo: false node_js: - '5' -- 'node' +- 'v6.10.3' before_install: - npm install script: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 09a8ff245b..8ed443851d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,10 +10,10 @@ Mirador uses [node.js](http://nodejs.org/) and a build system to assemble, test, `npm start` will run a local server that is available at `localhost:8000`. ## How to Contribute -### Making Changes +### Making Changes Contributions are always welcome, however, it will always be helpful to begin any large change by submitting an issue, or reviewing an existing issue. This will give the developer community a chance to point you in the right direction, let you know of any connected issues that may not be obvious, and provide feedback about how the feature might fit into the current roadmap. Contributions that involve major changes to the UI will need to have a design audit completed before they can be fully integrated. See the Design section below for information about the design review process. -To make a contribution, update the master and current release branches. At any time, there is one "next release" branch named after the corresponding github milestone. Currently the upcoming release is 2.1.2. Make a discrete change representing a bite-sized chunk of work, and write an informative commit message. We do not enforce any rebasing strategy, but we may ask you to rebase if you have many small and intermediate commits with unhelpful messages. "One commit per PR" is a worthy goal. +To make a contribution, update the master and develop branches. The master branch is the current stable version, though each release also has a tag. The develop branch is the current working branch into which pull requests will be merged for upcoming releases. After creating a new branch off of develop, make a discrete change representing a bite-sized chunk of work, and write an informative commit message. We do not enforce any rebasing strategy, but we may ask you to rebase if you have many small and intermediate commits with unhelpful messages. "One commit per PR" is a worthy goal. ### Making Small Changes Even small changes should follow the branching strategy outlined above, though they may not need a long discussion. It may still be helpful to create an issue for them, though it is not strictly necessary. ### Updating Documentation @@ -26,9 +26,9 @@ Create a branch for your work: e.g.: `git checkout -b my-feature-branch` or `git checkout -b my-bug-fix` ### Usual Development -Once you have built the necessary files and created a branch for your feature or bug fix work, you are ready to code. +Once you have built the necessary files and created a branch for your feature or bug fix work, you are ready to code. -Live interactive reloading of the browser each time a file is saved is enabled and used in the `npm start` command. Note that this will require middleware or a [livereload browser extension](http://feedback.livereload.com/knowledgebase/articles/86242-how-do-i-install-and-use-the-browser-extensions). +Live interactive reloading of the browser each time a file is saved is enabled and used in the `npm start` command. Note that this will require middleware or a [livereload browser extension](http://feedback.livereload.com/knowledgebase/articles/86242-how-do-i-install-and-use-the-browser-extensions). ### Submitting Your Contribution ### Publishing a New Release @@ -38,8 +38,8 @@ All development occurs on the pinned development branch. Ensure that all tests w #### 2. Change version number in package.json If the version number included in the `package.json` does not already accurately reflect the version to be released, be sure to increment the number according to [SemVer](http://semver.org/) conventions. Bump the third number for a small patch that does not change or add any new functionality; bump the second number if the branch includes any new features that do not interfere with or change existing features; and bump the first ("major") version number if the changes to be released break or change the API for existing functionality. -#### 3. Merge the Release Branch into Master -For example, if the current release branch is x.x.1, ensure that all feature branches (such as "fix_annotation_bug#1234") have been merged into the x.x.1 release branch as Github Pull Requests, and then merge the x.x.1 (which will be the default branch) into master through the Github interface (through a PR). +#### 3. Merge Develop into Master +For example, ensure that all feature branches (such as "fix_annotation_bug#1234") have been merged into the develop branch as Github Pull Requests, and then merge the develop branch (which will be the default branch) into master through the Github interface (through a PR). #### 4. Create a New Local Tag After all new changes have been merged into master, checkout master locally, and create a git tag for the new version: @@ -58,7 +58,7 @@ Now push the tagged version to github (from master): Using the github commit log, compile a bulleted list of the features and changes added to the new release. #### 9. Publish to NPM -Assuming the commiter has access to the project's package management account on npm, publishing the most recent version requires logging into npm on the command line. +Assuming the commiter has access to the project's package management account on npm, publishing the most recent version requires logging into npm on the command line. Then simply type `npm publish` to post the new package version to the registry. To configure your npm user locally, refer to the npm-adduser [documentation](https://docs.npmjs.com/cli/adduser). @@ -68,7 +68,7 @@ To configure your npm user locally, refer to the npm-adduser [documentation](htt Design review can happen in one of two ways, though both ways start with an issue or issues describing the interaction requirements. Once an issue has been created for a new UI-heavy feature, whether or not a prototype is complete, the feature goes up for design review. This is generally a three-step process: 1. An announcement about the proposed feature is put out to the Mirador-tech mailing list or on one of the bi-weekly calls with a link to the issue that documents the proposed UI feature, with links to any prototype examples or design references. 2. The community discusses the design and requirements, and produces a set of annotated mock-ups of the interaction, which the community reviews on the mailing list or on calls. -3. If the community finds major problems with the design, the feedback is incorporated into a new set of mock-ups until a relative concensus about the feature's mock-ups is complete. +3. If the community finds major problems with the design, the feedback is incorporated into a new set of mock-ups until a relative consensus about the feature's mock-ups is complete. From here, any existing work must be adjusted to reflect the mock-ups produced by the community before it will be accepted as a pull request. This is why it is so important to document a major UI change in issues before putting in too much work. Often, major feature work will have been the result of local changes to Mirador for an independent project. In this case, often a Pull Request will be encouraged simply so that the code is easily referenced in a later redesign of a completed locally-implemented feature. ## Background Information (Tooling) @@ -87,7 +87,9 @@ Some resources are managed with bower, but this is being phased out. It is recom #### Tasks ### Testing and Coverage ### Version Control -### Editorconfig and jsHint +### JavaScript Style Contributors use a variety of text editors according to circumstance and preference. This can introduce inconsistencies in the source text files, such as spaces being replaced with tabs, indentation spans being shortened, and whitespace being added or subtracted from the end of lines. Using your editor's [EditorConfig](http://editorconfig.org/) plugin resolves these inconsistencies while allowing each developer to use her own preferences while developing. -[JSHint](http://jshint.com) will notify you of inconsistencies in the style of the code. Mirador uses the AirBnB [styleguide](https://github.com/airbnb/javascript/tree/es5-deprecated/es5) for ES5. +DEPRECATED [NON-FUNCTIONAL]. [JSHint](http://jshint.com) will notify you of inconsistencies in the style of the code. Mirador uses the AirBnB [styleguide](https://github.com/airbnb/javascript/tree/es5-deprecated/es5) for ES5. + +[eslint](http://eslint.org/). Mirador uses the AirBnb Javascript styleguide for ES5 as codified by AirBnb in [eslint-config-airbnb-base](https://www.npmjs.com/package/eslint-config-airbnb-base#eslint-config-airbnb-baselegacy). Make sure any changes you make conform to this style. You can check this by running the `npm run lint`. Error checking for this is not turned on in the continuous integration build at the moment, but will be in the future. diff --git a/Gruntfile.js b/Gruntfile.js index 1ccb2e3326..a3a9c09bfe 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -4,6 +4,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-compress'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks("gruntify-eslint"); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-connect'); grunt.loadNpmTasks('grunt-contrib-watch'); @@ -45,7 +46,9 @@ module.exports = function(grunt) { 'node_modules/i18next-xhr-backend/i18nextXHRBackend.min.js', 'bower_components/simplePagination.js/jquery.simplePagination.js', 'js/lib/modernizr.custom.js', - 'js/lib/sanitize-html.min.js' + 'js/lib/sanitize-html.min.js', + 'node_modules/iiif-evented-canvas/dist/iiif-evented-canvas.umd.min.js', + 'node_modules/iiif-layout-functions/dist/iiif-layout-functions.umd.min.js', ], // source files @@ -104,7 +107,7 @@ module.exports = function(grunt) { less: { compile: { files: { - 'css/mirador.css': 'css/mirador.less/main.less' + 'css/mirador.css': 'css/less/main.less' } } }, @@ -209,8 +212,6 @@ module.exports = function(grunt) { all: { options: { livereload: { - // Here we watch the files the sass task will compile to - // These files are sent to the live reload server after sass compiles to them options: { livereload: true }, files: ['build/**/*'] } @@ -222,13 +223,20 @@ module.exports = function(grunt) { 'locales/*/*.json', 'images/*', 'css/*.css', - 'css/mirador.less/**/*.less', + 'css/less/**/*.less', 'index.html' ], tasks: 'dev_build' } }, + eslint: { + options: { + silent: true + }, + src: sources + }, + jshint: { options: { browser: true, @@ -274,16 +282,19 @@ module.exports = function(grunt) { grunt.file.copy(abspath, dest); }); }); + // ---------- + // Lint task + grunt.registerTask('lint', ['jshint', 'eslint']) // ---------- // Build task. // Cleans out the build folder and builds the code and images into it, checking lint. - grunt.registerTask('build', [ 'clean:build', 'git-describe', 'jshint', 'less', 'concat:css', 'uglify', 'cssmin', 'copy']); + grunt.registerTask('build', [ 'clean:build', 'git-describe', 'lint', 'less', 'concat:css', 'uglify', 'cssmin', 'copy']); // ---------- // Dev Build task. // Build, but skip the time-consuming and obscurantist minification and uglification. - grunt.registerTask('dev_build', [ 'clean:build', 'git-describe', 'jshint', 'less', 'concat', 'copy']); + grunt.registerTask('dev_build', [ 'clean:build', 'git-describe', 'lint', 'less', 'concat', 'copy']); // ---------- // Package task. @@ -308,6 +319,6 @@ module.exports = function(grunt) { // ---------- // Runs this on travis. grunt.registerTask('ci', [ - 'jshint' + 'lint' ]); }; diff --git a/README.md b/README.md index 4fb5642a54..105dbb3b60 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Build Status](https://travis-ci.org/ProjectMirador/mirador.svg?branch=master)](https://travis-ci.org/ProjectMirador/mirador?branch=master) [![Stories in Ready](https://badge.waffle.io/ProjectMirador/mirador.svg?label=ready&title=Ready)](http://waffle.io/iiif/mirador) -#Mirador +# Mirador ![mirador banner](http://projectmirador.github.io/mirador/img/banner.jpg) **Mirador is a multi-repository, configurable, extensible, and easy-to-integrate viewer and annotation creation and comparison environment for IIIF resources, ranging from deep-zooming artwork, to complex manuscript objects. It provides a tiling windowed environment for comparing multiple image-based resources, synchronised structural and visual navigation of content using openSeadragon, Open Annotation compliant annotation creation and viewing on deep-zoomable canvases, metadata display, bookreading, and bookmarking.** diff --git a/css/mirador.less/full-screen.less b/css/less/full-screen.less similarity index 100% rename from css/mirador.less/full-screen.less rename to css/less/full-screen.less diff --git a/css/mirador.less/global.less b/css/less/global.less similarity index 100% rename from css/mirador.less/global.less rename to css/less/global.less diff --git a/css/mirador.less/main-menu.less b/css/less/main-menu.less similarity index 100% rename from css/mirador.less/main-menu.less rename to css/less/main-menu.less diff --git a/css/mirador.less/main.less b/css/less/main.less similarity index 93% rename from css/mirador.less/main.less rename to css/less/main.less index 991921a322..7d1611e717 100644 --- a/css/mirador.less/main.less +++ b/css/less/main.less @@ -1,7 +1,7 @@ /** * This file is the entry point for Less processing */ - +@import 'mixins'; @import 'theme/colors'; @import 'theme/fonts'; @import 'overrides/jquery'; @@ -23,3 +23,4 @@ @import 'panels/bookmark-panel'; @import 'panels/metadata-view'; @import 'panels/bottom-panel'; +@import 'panels/layers-tab'; diff --git a/css/less/mixins.less b/css/less/mixins.less new file mode 100644 index 0000000000..f64330a93f --- /dev/null +++ b/css/less/mixins.less @@ -0,0 +1,6 @@ +// use () to avoid outputting this in the stylesheet. +.clearfix() { + zoom:1; + &:before, &:after{ content:""; display:table; } + &:after{ clear: both; } +} \ No newline at end of file diff --git a/css/mirador.less/osd-hud.less b/css/less/osd-hud.less similarity index 95% rename from css/mirador.less/osd-hud.less rename to css/less/osd-hud.less index cc53f66170..5c30f0e6a2 100644 --- a/css/mirador.less/osd-hud.less +++ b/css/less/osd-hud.less @@ -174,6 +174,12 @@ left: 10px; transition: none; /*width: 100%*/ + >div { + clear: both; + >a:first-child { + margin-bottom: 10px; + } + } } .mirador-osd-context-controls a { float:left; @@ -186,10 +192,14 @@ left: 0px; } .mirador-manipulation-controls { - position: absolute; - top: 30px; - left: 0px; - min-width: 330px; + min-width: 364px; + } + .mirador-mirror { + -moz-transform: scale(-1, 1); + -webkit-transform: scale(-1, 1); + -o-transform: scale(-1, 1); + transform: scale(-1, 1); + filter: FlipH; } /* color picker custom style diff --git a/css/mirador.less/overrides/jquery.less b/css/less/overrides/jquery.less similarity index 100% rename from css/mirador.less/overrides/jquery.less rename to css/less/overrides/jquery.less diff --git a/css/mirador.less/overrides/qtip.less b/css/less/overrides/qtip.less similarity index 100% rename from css/mirador.less/overrides/qtip.less rename to css/less/overrides/qtip.less diff --git a/css/mirador.less/panels/annotation-list.less b/css/less/panels/annotation-list.less similarity index 100% rename from css/mirador.less/panels/annotation-list.less rename to css/less/panels/annotation-list.less diff --git a/css/mirador.less/panels/bookmark-panel.less b/css/less/panels/bookmark-panel.less similarity index 96% rename from css/mirador.less/panels/bookmark-panel.less rename to css/less/panels/bookmark-panel.less index f695e6a064..c055fab1e7 100644 --- a/css/mirador.less/panels/bookmark-panel.less +++ b/css/less/panels/bookmark-panel.less @@ -6,7 +6,7 @@ background-color: @white-a94; display: none; overflow: hidden; - z-index: 5; + z-index: 6; right: 0; top: 0; padding: 10px; diff --git a/css/mirador.less/panels/bottom-panel.less b/css/less/panels/bottom-panel.less similarity index 100% rename from css/mirador.less/panels/bottom-panel.less rename to css/less/panels/bottom-panel.less diff --git a/css/less/panels/layers-tab.less b/css/less/panels/layers-tab.less new file mode 100644 index 0000000000..1d469b479a --- /dev/null +++ b/css/less/panels/layers-tab.less @@ -0,0 +1,111 @@ +// TODO: Go back through less files and remove mirador-container +// wherever it appears explicitly, as it is +// causing specificity problems. +.mirador-container .layersPanel h3 { + margin:10px 0; +} +.layersPanel { + position: absolute; + width: 285px; + left: 0; + right: 0; + top: 0; + bottom: 0; + overflow-y: scroll; + .layers-listing { + padding: 0; + list-style-type:none; + margin-top:0; + } + .layers-list-item { + .clearfix(); + border-top: 1px solid gray; + border-bottom: 1px solid gray; + padding: 10px 10px 10px 0; + margin-right: 20px; + &>h4 { + margin-bottom: 5px; + } + .thumb-container { + position:relative; + float:left; + width: 60px; + height: 60px; + margin-right:10px; + img { + position: relative; + margin: 0 auto; + max-height:60px; + max-width: 60px; + display:block; + opacity:0; + border: 1px solid gray; + box-shadow: inset 0 0 12px gray; + top: 50%; + transform: translateY(-50%); + &.loaded { + transition: all 0.2s ease-out; + opacity:1; + } + } + .spinner, .failed, .thumb-failed { + position:absolute; + display:inline-block; + width:100%; + margin:0 auto; + top:50%; + transform: translateY(-50%); + opacity:0; + transition: all 0.2s ease-out; + color: #666; + } + &.awaiting-thumbnail { + .spinner { + opacity:1; + } + } + } + &.requested { + .spinner { + opacity: 1; + transition: all 0.2s ease-out; + } + .thumb-container img { + transition: all 0.2s ease-out; + opacity: 0.5; + } + } + &.failed { + .failed { + opacity:1; + transition: all 0.2s ease-out; + } + .thumb-container img { + transition: all 0.2s ease-out; + opacity: 0.5; + } + .thumb-failed { + opacity: 0; + } + .thumb-container.awaiting-thumbnail .spinner { + opacity:0; + transition: all 0.2s ease-out; + } + } + .visibility-toggle { + margin-bottom: 10px; + } + } + .disabled-overlay { + display: none; + } +} +.layersPanel.inactive>*:not(.disabled-overlay){ + display: none; +} +.layersPanel.inactive .disabled-overlay { + padding: 10px; + display: block; + box-sizing: border-box; + color: dimgray; +} \ No newline at end of file diff --git a/css/mirador.less/panels/manifest-select-menu.less b/css/less/panels/manifest-select-menu.less similarity index 100% rename from css/mirador.less/panels/manifest-select-menu.less rename to css/less/panels/manifest-select-menu.less diff --git a/css/mirador.less/panels/metadata-view.less b/css/less/panels/metadata-view.less similarity index 100% rename from css/mirador.less/panels/metadata-view.less rename to css/less/panels/metadata-view.less diff --git a/css/mirador.less/panels/side-panel.less b/css/less/panels/side-panel.less similarity index 100% rename from css/mirador.less/panels/side-panel.less rename to css/less/panels/side-panel.less diff --git a/css/mirador.less/panels/workspace-select-menu.less b/css/less/panels/workspace-select-menu.less similarity index 94% rename from css/mirador.less/panels/workspace-select-menu.less rename to css/less/panels/workspace-select-menu.less index ee8ec7d907..adc2a7000e 100644 --- a/css/mirador.less/panels/workspace-select-menu.less +++ b/css/less/panels/workspace-select-menu.less @@ -1,13 +1,12 @@ .mirador-container { - #workspace-select-menu, - #workspaceContainer { + #workspace-select-menu { position: absolute; height: 100%; width: 100%; background-color: @white-a94; display: none; overflow: hidden; - z-index: 5; + z-index: 6; } #workspace-select-menu { diff --git a/css/mirador.less/status-bar.less b/css/less/status-bar.less similarity index 100% rename from css/mirador.less/status-bar.less rename to css/less/status-bar.less diff --git a/css/mirador.less/theme/colors.less b/css/less/theme/colors.less similarity index 100% rename from css/mirador.less/theme/colors.less rename to css/less/theme/colors.less diff --git a/css/mirador.less/theme/fonts.less b/css/less/theme/fonts.less similarity index 100% rename from css/mirador.less/theme/fonts.less rename to css/less/theme/fonts.less diff --git a/css/mirador.less/views/gallery-view.less b/css/less/views/gallery-view.less similarity index 100% rename from css/mirador.less/views/gallery-view.less rename to css/less/views/gallery-view.less diff --git a/css/mirador.less/views/image-view.less b/css/less/views/image-view.less similarity index 100% rename from css/mirador.less/views/image-view.less rename to css/less/views/image-view.less diff --git a/css/mirador.less/views/scroll-view.less b/css/less/views/scroll-view.less similarity index 100% rename from css/mirador.less/views/scroll-view.less rename to css/less/views/scroll-view.less diff --git a/css/mirador.less/window.less b/css/less/window.less similarity index 98% rename from css/mirador.less/window.less rename to css/less/window.less index 85ae0ed5bf..8b8a91a5b2 100644 --- a/css/mirador.less/window.less +++ b/css/less/window.less @@ -51,7 +51,7 @@ top: 0; box-sizing: border-box; background-color: @white-a90; - z-index: 4; + z-index: 5; width: 45%; overflow-y: scroll; height: 80%; diff --git a/css/mirador.less/workspace.less b/css/less/workspace.less similarity index 100% rename from css/mirador.less/workspace.less rename to css/less/workspace.less diff --git a/examples/imageLayers.html b/examples/imageLayers.html new file mode 100644 index 0000000000..c502d5eaf7 --- /dev/null +++ b/examples/imageLayers.html @@ -0,0 +1,45 @@ + + + + + + + Mirador Viewer + + + +
+ + + + + + + + diff --git a/examples/layersAndSearchWithinPanels.html b/examples/layersAndSearchWithinPanels.html new file mode 100644 index 0000000000..49886117a2 --- /dev/null +++ b/examples/layersAndSearchWithinPanels.html @@ -0,0 +1,48 @@ + + + + + + + Mirador Viewer + + + +
+ + + + + + + + diff --git a/examples/searchWithin.html b/examples/searchWithin.html new file mode 100644 index 0000000000..409b7507a7 --- /dev/null +++ b/examples/searchWithin.html @@ -0,0 +1,44 @@ + + + + + + + Mirador Viewer + + + +
+ + + + + + + + diff --git a/index.html b/index.html index 94bda2f823..8d9839d8bd 100644 --- a/index.html +++ b/index.html @@ -36,11 +36,11 @@ { "manifestUri": "http://iiif.harvardartmuseums.org/manifests/object/304136", "location": "Harvard University"}, { "manifestUri": "http://iiif.harvardartmuseums.org/manifests/object/198021", "location": "Harvard University"}, { "manifestUri": "http://iiif.harvardartmuseums.org/manifests/object/320567", "location": "Harvard University"}, - { "manifestUri": "https://purl.stanford.edu/qm670kv1873/iiif/manifest.json", "location": "Stanford University"}, - { "manifestUri": "https://purl.stanford.edu/jr903ng8662/iiif/manifest.json", "location": "Stanford University"}, - { "manifestUri": "https://purl.stanford.edu/ch264fq0568/iiif/manifest.json", "location": "Stanford University"}, - { "manifestUri": "https://purl.stanford.edu/wh234bz9013/iiif/manifest.json", "location": "Stanford University"}, - { "manifestUri": "https://purl.stanford.edu/rd447dz7630/iiif/manifest.json", "location": "Stanford University"}, + { "manifestUri": "https://purl.stanford.edu/qm670kv1873/iiif/manifest", "location": "Stanford University"}, + { "manifestUri": "https://purl.stanford.edu/jr903ng8662/iiif/manifest", "location": "Stanford University"}, + { "manifestUri": "https://purl.stanford.edu/ch264fq0568/iiif/manifest", "location": "Stanford University"}, + { "manifestUri": "https://purl.stanford.edu/wh234bz9013/iiif/manifest", "location": "Stanford University"}, + { "manifestUri": "https://purl.stanford.edu/rd447dz7630/iiif/manifest", "location": "Stanford University"}, { "manifestUri": "http://dms-data.stanford.edu/data/manifests/Stanford/ege1/manifest.json", "location": "Stanford University"}, { "manifestUri": "http://dams.llgc.org.uk/iiif/4574752/manifest.json", "location": "National Library of Wales"}, { "manifestUri": "http://dev.llgc.org.uk/iiif/ww1posters.json", "location": "National Library of Wales"}, @@ -68,10 +68,19 @@ { "manifestUri": "http://dzkimgs.l.u-tokyo.ac.jp/iiif/zuzoubu/12b02/manifest.json", "location": "University of Tokyo"}, { "manifestUri": "http://www2.dhii.jp/nijl/NIJL0018/099-0014/manifest_tags.json", "location": "NIJL"}, { "manifestUri": "http://digi.vatlib.it/iiif/MSS_Vat.lat.3225/manifest.json", "location": "Vatican Library"}, - { "manifestUri": "http://media.nga.gov/public/manifests/nga_highlights.json", "location": "National Gallery of Art"} + { "manifestUri": "http://media.nga.gov/public/manifests/nga_highlights.json", "location": "National Gallery of Art"}, + { "manifestUri": "http://demos.biblissima-condorcet.fr/iiif/metadata/BVMM/chateauroux/manifest.json", "location": "Biblissima"}, + { "manifestUri": "https://manifests.britishart.yale.edu/Osbornfa1", "location": "Yale Beinecke"}, + /* { "manifestUri": "http://demos.biblissima-condorcet.fr/iiif/metadata/florus-dispersus/manifest.json", "location": "Biblissima"}*/ + { "manifestUri": "https://media.nga.gov/public/manifests/multispectral_demo.json", "location": "National Gallery of Art"} ], "windowObjects": [], - "annotationEndpoint": { "name":"Local Storage", "module": "LocalStorageEndpoint" } + "annotationEndpoint": { "name":"Local Storage", "module": "LocalStorageEndpoint" }, + "sidePanelOptions" : { + "tocTabAvailable": true, + "layersTabAvailable": true, + "searchTabAvailable": true + } }); }); diff --git a/js/src/annotations/annotationTooltip.js b/js/src/annotations/annotationTooltip.js index 3e6a26bddf..9a1815f894 100644 --- a/js/src/annotations/annotationTooltip.js +++ b/js/src/annotations/annotationTooltip.js @@ -125,7 +125,7 @@ initializeViewerUpgradableToEditor: function(params) { var _this = this; _this.activeEditorTip = jQuery(_this.targetElement).qtip({ - overwrite: false, + overwrite: true, content: { text: '' }, @@ -144,35 +144,36 @@ classes: 'qtip-bootstrap qtip-viewer', tip: false }, + show: { + event: false + }, hide: { fixed: true, delay: 300, event: false }, events: { - shown: function(event, api) { + show: function(event, api) { if (params.onTooltipShown) { params.onTooltipShown(event, api); } + api.cache.hidden = false; }, hidden: function(event, api) { if (params.onTooltipHidden) { params.onTooltipHidden(event, api); } _this.removeAllEvents(api, params); + api.cache.hidden = true; }, visible: function (event, api) { - _this.removeAllEvents(api, params); - _this.addViewerEvents(api, params); + }, move: function (event, api) { - if (api.cache.contentUpdated) { - _this.removeAllEvents(api, params); - _this.addViewerEvents(api, params); - } - api.cache.contentUpdated = false; + } } }); var api = jQuery(_this.targetElement).qtip('api'); api.cache.annotations = []; api.cache.hidden = true; + api.cache.params = params; }, removeAllEvents: function(api, viewerParams) { @@ -210,6 +211,7 @@ var display = jQuery(elem).parents('.annotation-display'); var id = display.attr('data-anno-id'); var callback = function(){ + _this.removeAllEvents(); api.hide(); display.remove(); }; @@ -233,6 +235,7 @@ _this.addEditorEvents(api, viewerParams); } else { _this.eventEmitter.publish('annotationInEditMode.' + _this.windowId,[oaAnno]); + _this.removeAllEvents(viewerParams); api.destroy(); jQuery(api.tooltip).remove(); } @@ -263,7 +266,6 @@ _this.activeEditor.updateAnnotation(oaAnno); _this.eventEmitter.publish('annotationEditSave.'+_this.windowId,[oaAnno]); - }); jQuery(selector + ' a.cancel').on("click", function(event) { @@ -271,8 +273,8 @@ var display = jQuery(this).parents('.annotation-editor'); var id = display.attr('data-anno-id'); var oaAnno = viewerParams.getAnnoFromRegion(id)[0]; + _this.removeAllEvents(); _this.unFreezeQtip(api, oaAnno, viewerParams); - _this.eventEmitter.publish('annotationEditCancel.' + _this.windowId,[id]); }); @@ -316,14 +318,14 @@ } if (api.cache.hidden || !isTheSame) { api.disable(false); - api.cache.contentUpdated = false; _this.setTooltipContent(params.annotations); api.cache.origin = params.triggerEvent; api.reposition(params.triggerEvent, true); api.show(params.triggerEvent); api.cache.annotations = params.annotations; api.cache.hidden = false; - api.disable(true); + _this.removeAllEvents(); + _this.addViewerEvents(api,api.cache.params); } } }, @@ -332,7 +334,6 @@ var _this = this; var api = jQuery(this.targetElement).qtip('api'); if (api) { - api.cache.contentUpdated = true; api.set({'content.text': this.getViewerContent(annotations)}); _this.eventEmitter.publish('tooltipViewerSet.' + this.windowId); } @@ -364,11 +365,11 @@ //if it is a manifest annotation, don't allow editing or deletion //otherwise, check annotation in endpoint var showUpdate = false; - if (typeof annotation.endpoint !== 'undefined' && annotation.endpoint !== 'manifest') { + if (annotation.endpoint !== 'manifest') { showUpdate = annotation.endpoint.userAuthorize('update', annotation); } var showDelete = false; - if (typeof annotation.endpoint !== 'undefined' && annotation.endpoint !== 'manifest') { + if (annotation.endpoint !== 'manifest') { showDelete = annotation.endpoint.userAuthorize('delete', annotation); } htmlAnnotations.push({ @@ -468,4 +469,4 @@ ].join('')) }; -}(Mirador)); +}(Mirador)); \ No newline at end of file diff --git a/js/src/annotations/osd-region-draw-tool.js b/js/src/annotations/osd-region-draw-tool.js index 1686102978..08ef90c3e8 100644 --- a/js/src/annotations/osd-region-draw-tool.js +++ b/js/src/annotations/osd-region-draw-tool.js @@ -21,6 +21,8 @@ this.svgOverlay.show(); this.svgOverlay.disable(); + this.horizontallyFlipped = false; + this.listenForActions(); }, @@ -162,6 +164,9 @@ }; var hoverColor = this.state.getStateProperty('drawingToolsSettings').hoverColor; var annotations = []; + if (this.horizontallyFlipped) { + location.x = this.svgOverlay.viewer.tileSources.width - location.x; + } for (var key in _this.annotationsToShapesMap) { if (_this.annotationsToShapesMap.hasOwnProperty(key)) { var shapeArray = _this.annotationsToShapesMap[key]; @@ -281,6 +286,17 @@ this.eventsSubscriptions.push(_this.eventEmitter.subscribe('refreshOverlay.' + _this.windowId, function (event) { _this.render(); })); + + this.eventsSubscriptions.push(this.eventEmitter.subscribe("enableManipulation",function(event, tool){ + if(tool === 'mirror') { + _this.horizontallyFlipped = true; + } + })); + this.eventsSubscriptions.push(this.eventEmitter.subscribe("disableManipulation",function(event, tool){ + if(tool === 'mirror') { + _this.horizontallyFlipped = false; + } + })); }, getAnnoFromRegion: function(regionId) { diff --git a/js/src/annotations/osd-svg-overlay.js b/js/src/annotations/osd-svg-overlay.js index 820eff0498..8e05c6aa17 100644 --- a/js/src/annotations/osd-svg-overlay.js +++ b/js/src/annotations/osd-svg-overlay.js @@ -12,6 +12,7 @@ return new $.Overlay(this, osdViewerId, windowId, state, eventEmitter); }; + // Wat? TODO:... var FILL_COLOR_ALPHA_WORKAROUND = 0.00001; $.Overlay = function(viewer, osdViewerId, windowId, state, eventEmitter) { @@ -72,6 +73,8 @@ this.eventEmitter = eventEmitter; this.eventsSubscriptions = []; + this.horizontallyFlipped = false; + this.resize(); this.show(); this.init(); @@ -447,6 +450,17 @@ } })); + this.eventsSubscriptions.push(this.eventEmitter.subscribe("enableManipulation",function(event, tool){ + if(tool === 'mirror') { + _this.horizontallyFlipped = true; + } + })); + + this.eventsSubscriptions.push(this.eventEmitter.subscribe("disableManipulation",function(event, tool){ + if(tool === 'mirror') { + _this.horizontallyFlipped = false; + } + })); }, deleteShape:function(shape){ @@ -462,27 +476,37 @@ }, getMousePositionInImage: function(mousePosition) { + var _this = this; + var originWindow = this.state.getWindowObjectById(this.windowId); + var currentCanvasModel = originWindow.canvases[originWindow.canvasID]; + if (mousePosition.x < 0) { mousePosition.x = 0; } - if (mousePosition.x > this.viewer.tileSources.width) { - mousePosition.x = this.viewer.tileSources.width; + if (mousePosition.x > currentCanvasModel.getBounds().width) { + mousePosition.x = currentCanvasModel.getBounds().width; } if (mousePosition.y < 0) { mousePosition.y = 0; } - if (mousePosition.y > this.viewer.tileSources.height) { - mousePosition.y = this.viewer.tileSources.height; + if (mousePosition.y > currentCanvasModel.getBounds().height) { + mousePosition.y = currentCanvasModel.getBounds().height; + } + if (this.horizontallyFlipped) { + mousePosition.x = this.viewer.tileSources.width - mousePosition.x; } return mousePosition; }, adjustDeltaForShape: function(lastPoint, currentPoint, delta, bounds) { + var originWindow = this.state.getWindowObjectById(this.windowId); + var currentCanvasModel = originWindow.canvases[originWindow.canvasID]; + //first check along x axis if (lastPoint.x < currentPoint.x) { //moving to the right, delta should be based on the right most edge - if (bounds.x + bounds.width > this.viewer.tileSources.width) { - delta.x = this.viewer.tileSources.width - (bounds.x + bounds.width); + if (bounds.x + bounds.width > currentCanvasModel.getBounds().width) { + delta.x = currentCanvasModel.getBounds().width - (bounds.x + bounds.width); } } else { //moving to the left, prevent it from going past the left edge. if it does, use the shapes x value as the delta @@ -490,12 +514,15 @@ delta.x = Math.abs(bounds.x); } } + if (this.horizontallyFlipped) { + delta.x = -delta.x; + } //check along y axis if (lastPoint.y < currentPoint.y) { // moving to the bottom - if (bounds.y + bounds.height > this.viewer.tileSources.height) { - delta.y = this.viewer.tileSources.height - (bounds.y + bounds.height); + if (bounds.y + bounds.height > currentCanvasModel.getBounds().height) { + delta.y = currentCanvasModel.getBounds().height - (bounds.y + bounds.height); } } else { //moving to the top @@ -678,7 +705,12 @@ }, resize: function() { + var _this = this; var viewportBounds = this.viewer.viewport.getBounds(true); + // var originWindow = this.state.getWindowObjectById(this.windowId); + // if ( !originWindow.canvases ) { return; } // no-op if canvases are not initialised. + // var currentCanvasModel = originWindow.canvases[originWindow.canvasID]; + /* in viewport coordinates */ this.canvas.width = this.viewer.viewport.containerSize.x; this.canvas.height = this.viewer.viewport.containerSize.y; @@ -690,10 +722,12 @@ this.canvas.style.marginTop = '0px'; if (this.paperScope && this.paperScope.view) { this.paperScope.view.viewSize = new this.paperScope.Size(this.canvas.width, this.canvas.height); - this.paperScope.view.zoom = this.viewer.viewport.viewportToImageZoom(this.viewer.viewport.getZoom(true)); + this.paperScope.view.zoom = _this.canvas.width * this.viewer.viewport.getZoom(true); this.paperScope.view.center = new this.paperScope.Size( - this.viewer.world.getItemAt(0).source.dimensions.x * viewportBounds.x + this.paperScope.view.bounds.width / 2, - this.viewer.world.getItemAt(0).source.dimensions.x * viewportBounds.y + this.paperScope.view.bounds.height / 2); + this.viewer.viewport.getCenter(true).x, + this.viewer.viewport.getCenter(true).y + ); + this.paperScope.view.update(true); var allItems = this.paperScope.project.getItems({ name: /_/ @@ -714,7 +748,6 @@ }, hover: function() { - if(!this.currentTool){ return; } diff --git a/js/src/manifests/manifest.js b/js/src/manifests/manifest.js index 88eeb7259d..3e3e738993 100644 --- a/js/src/manifests/manifest.js +++ b/js/src/manifests/manifest.js @@ -3,10 +3,10 @@ $.Manifest = function(manifestUri, location, manifestContent) { if (manifestContent) { jQuery.extend(true, this, { - jsonLd: null, - location: location, - uri: manifestUri, - request: null + jsonLd: null, + location: location, + uri: manifestUri, + request: null }); this.initFromManifestContent(manifestContent); } else if (manifestUri.indexOf('info.json') !== -1) { @@ -31,7 +31,7 @@ jsonLd: null, location: location, uri: manifestUri, - request: null + request: null, }); this.initFromInfoJson(manifestUri); @@ -58,7 +58,6 @@ this.request.done(function(jsonLd) { _this.jsonLd = jsonLd; - _this.buildCanvasMap(); }); }, buildCanvasMap: function() { @@ -168,7 +167,7 @@ // Takes in info.json and creates the // dummy manifest wrapper around it // that will allow it to behave like a - // manifest with one canvas in it, with + // manifest with one canvas in it, with // one image on it. Some of the metadata // of the image will be used as the // label, and so on, of the manifest. diff --git a/js/src/settings.js b/js/src/settings.js index 94db6b224c..7e7e15e216 100644 --- a/js/src/settings.js +++ b/js/src/settings.js @@ -54,8 +54,8 @@ "toc" : true, "annotations" : false, "tocTabAvailable": true, - // "layersTabAvailable": true, - "searchTabAvailable": false, + "layersTabAvailable": false, + "searchTabAvailable": false }, "sidePanelVisible" : true, //whether or not to make the side panel visible in this window on load. This setting is dependent on sidePanel being true "overlay" : true, //whether or not to make the metadata overlay available/visible in this window @@ -76,7 +76,8 @@ "contrast" : true, "saturate" : true, "grayscale" : true, - "invert" : true + "invert" : true, + "mirror" : false } } }, diff --git a/js/src/utils/iiif.js b/js/src/utils/iiif.js index 995999b5eb..4e896df8ea 100644 --- a/js/src/utils/iiif.js +++ b/js/src/utils/iiif.js @@ -17,6 +17,11 @@ }, getVersionFromContext: function(context) { + if (context instanceof Array) { + context = context.filter(function(ctx) { + return typeof ctx === "string" && ctx.indexOf('http://iiif.io') === 0; + })[0]; + } if (context == "http://iiif.io/api/image/2/context.json") { return "2.0"; } else { @@ -75,7 +80,7 @@ regex = new RegExp('(.*)\/(.*)$'); matches = regex.exec(json.image_host); - if (matches.length > 1) { + if (matches && matches.length > 1) { json.image_host = matches[1]; json.identifier = matches[2]; } diff --git a/js/src/utils/utils.js b/js/src/utils/utils.js index 82a63f8ff9..0d12427af8 100644 --- a/js/src/utils/utils.js +++ b/js/src/utils/utils.js @@ -37,10 +37,10 @@ } else if (canvas.thumbnail.hasOwnProperty('service')) { service = canvas.thumbnail.service; if(service.hasOwnProperty('profile')) { - compliance = $.Iiif.getComplianceLevelFromProfile(service.profile); + compliance = $.Iiif.getComplianceLevelFromProfile(service.profile); } if(compliance === 0){ - // don't change existing behaviour unless compliance is explicitly 0 + // don't change existing behaviour unless compliance is explicitly 0 thumbnailUrl = canvas.thumbnail['@id']; } else { // Get the IIIF Image API via the @context @@ -64,7 +64,7 @@ return thumbnailUrl; }; - /* + /* miscellaneous utilities */ @@ -199,17 +199,17 @@ // Javascript does not have range expansions quite yet, // long live the humble for loop. // Use a closure to contain the column and row variables. - for (var i = 0, c = columns; i < c; i++) { + for (var i = 0, c = columns; i < c; i++) { var column = { type: 'column'}; if (rowsPerColumn > 1) { column.children = []; - for (var j = 0, r = rowsPerColumn; j < r; j++) { + for (var j = 0, r = rowsPerColumn; j < r; j++) { column.children.push({ type: 'row' }); } - } + } layoutDescription.children.push(column); } diff --git a/js/src/widgets/annotationsTab.js b/js/src/widgets/annotationsTab.js index 03d3f8c4ff..854baa43da 100644 --- a/js/src/widgets/annotationsTab.js +++ b/js/src/widgets/annotationsTab.js @@ -115,7 +115,7 @@ }); _this.eventEmitter.subscribe('tabStateUpdated.' + _this.windowId, function(_, data) { - _this.tabStateUpdated(data.annotationsTab); + _this.tabStateUpdated(data.tabs[data.selectedTabIndex].options.id == 'annotationsTab'); }); @@ -125,9 +125,9 @@ _this.eventEmitter.subscribe('currentCanvasIDUpdated.' + _this.windowId, function(event) { - _this.eventEmitter.subscribe('annotationListLoaded.' + _this.windowId, function(event) { - _this.annotationListLoaded(); - }); + _this.eventEmitter.subscribe('annotationListLoaded.' + _this.windowId, function(event) { + _this.annotationListLoaded(); + }); _this.selectList(_this.localState().selectedList); @@ -168,7 +168,7 @@ if (!this.element) { this.element = jQuery(_this.template(templateData)).appendTo(_this.appendTo); } else { - _this.appendTo.find(".annotationsPanel").remove(); + jQuery(_this.appendTo).find(".annotationsPanel").remove(); this.element = jQuery(_this.template(templateData)).appendTo(_this.appendTo); } _this.bindEvents(); diff --git a/js/src/widgets/contextControls.js b/js/src/widgets/contextControls.js index 27efe0aeeb..ebbd15d783 100644 --- a/js/src/widgets/contextControls.js +++ b/js/src/widgets/contextControls.js @@ -77,7 +77,8 @@ showContrast: this.canvasControls.imageManipulation.controls.contrast, showSaturate: this.canvasControls.imageManipulation.controls.saturate, showGrayscale: this.canvasControls.imageManipulation.controls.grayscale, - showInvert: this.canvasControls.imageManipulation.controls.invert + showInvert: this.canvasControls.imageManipulation.controls.invert, + showMirror: this.canvasControls.imageManipulation.controls.mirror })).appendTo(this.container.find('.mirador-manipulation-controls')); this.setQtips(this.container.find('.mirador-manipulation-controls')); this.manipulationElement.hide(); @@ -339,6 +340,11 @@ 'invert_colors', '', '{{/if}}', + '{{#if showMirror}}', + '', + 'swap_horiz', + '', + '{{/if}}', '', '', '', diff --git a/js/src/widgets/imageView.js b/js/src/widgets/imageView.js index 7d89b13099..177cb2f19f 100755 --- a/js/src/widgets/imageView.js +++ b/js/src/widgets/imageView.js @@ -6,7 +6,8 @@ currentImg: null, windowId: null, currentImgIndex: 0, - canvasID: null, + canvasID: null, + canvases: null, imagesList: [], element: null, elemOsd: null, @@ -31,6 +32,9 @@ init: function() { var _this = this; + this.horizontallyFlipped = false; + this.originalDragHandler = null; + // check (for thumbnail view) if the canvasID is set. // If not, make it page/item 1. if (this.canvasID !== null) { @@ -46,10 +50,10 @@ this.currentImg = this.imagesList[this.currentImgIndex]; this.element = jQuery(this.template()).appendTo(this.appendTo); this.elemAnno = jQuery('
') - .addClass(this.annoCls) - .appendTo(this.element); + .addClass(this.annoCls) + .appendTo(this.element); + - this.createOpenSeadragonInstance($.Iiif.getImageUrl(this.currentImg)); _this.eventEmitter.publish('UPDATE_FOCUS_IMAGES.' + this.windowId, {array: [this.canvasID]}); var allTools = $.getTools(this.state.getStateProperty('drawingToolsSettings')); @@ -80,6 +84,7 @@ eventEmitter: this.eventEmitter }); + this.initialiseImageCanvas(); this.bindEvents(); this.listenForActions(); @@ -91,14 +96,13 @@ }, template: $.Handlebars.compile([ - '
', - '
' + '
', + '
' + ].join('')), listenForActions: function() { - var _this = this, - firstCanvasId = _this.imagesList[0]['@id'], - lastCanvasId = _this.imagesList[_this.imagesList.length-1]['@id']; + var _this = this; _this.eventEmitter.subscribe('bottomPanelSet.' + _this.windowId, function(event, visible) { var dodgers = _this.element.find('.mirador-osd-toggle-bottom-panel, .mirador-pan-zoom-controls'); @@ -113,24 +117,22 @@ }); _this.eventEmitter.subscribe('fitBounds.' + _this.windowId, function(event, bounds) { - var rect = _this.osd.viewport.imageToViewportRectangle(Number(bounds.x), Number(bounds.y), Number(bounds.width), Number(bounds.height)); + var rect = new OpenSeadragon.Rect(bounds.x, bounds.y, bounds.width, bounds.height); _this.osd.viewport.fitBoundsWithConstraints(rect, false); }); - _this.eventEmitter.subscribe('currentCanvasIDUpdated.' + _this.windowId, function(event, canvasId) { - // If it is the first canvas, hide the "go to previous" button, otherwise show it. - if (canvasId === firstCanvasId) { - _this.element.find('.mirador-osd-previous').hide(); - _this.element.find('.mirador-osd-next').show(); - } else if (canvasId === lastCanvasId) { - _this.element.find('.mirador-osd-next').hide(); - _this.element.find('.mirador-osd-previous').show(); - } else { - _this.element.find('.mirador-osd-next').show(); - _this.element.find('.mirador-osd-previous').show(); - } - // If it is the last canvas, hide the "go to previous" button, otherwise show it. - }); + _this.eventEmitter.subscribe('currentCanvasIDUpdated.' + _this.windowId, _this.currentCanvasIDUpdated.bind(_this)); + _this.eventEmitter.subscribe('image-needed' + _this.windowId, _this.loadImage.bind(_this)); + _this.eventEmitter.subscribe('image-show' + _this.windowId, _this.showImage.bind(_this)); + _this.eventEmitter.subscribe('image-hide' + _this.windowId, _this.hideImage.bind(_this)); + _this.eventEmitter.subscribe('image-removed' + _this.windowId, _this.removeImage.bind(_this)); + _this.eventEmitter.subscribe('image-opacity-updated' + _this.windowId, _this.updateImageOpacity.bind(_this)); + // These will come soon. + // _this.eventEmitter.subscribe('image-layering-index-updated'); + // _this.eventEmitter.subscribe('image-position-updated'); + // _this.eventEmitter.subscribe('image-rotation-updated'); + // _this.eventEmitter.subscribe('image-scale-updated'); + // _this.eventEmitter.subscribe('canvas-position-updated'); //Related to Annotations HUD _this.eventEmitter.subscribe('HUD_REMOVE_CLASS.' + _this.windowId, function(event, elementSelector, className) { @@ -209,7 +211,11 @@ }); this.element.find('.mirador-osd-right').on('click', function() { var panBy = _this.getPanByValue(); - _this.osd.viewport.panBy(new OpenSeadragon.Point(panBy.x, 0)); + if (_this.horizontallyFlipped) { + _this.osd.viewport.panBy(new OpenSeadragon.Point(-panBy.x, 0)); + } else { + _this.osd.viewport.panBy(new OpenSeadragon.Point(panBy.x, 0)); + } _this.osd.viewport.applyConstraints(); }); this.element.find('.mirador-osd-down').on('click', function() { @@ -219,7 +225,11 @@ }); this.element.find('.mirador-osd-left').on('click', function() { var panBy = _this.getPanByValue(); - _this.osd.viewport.panBy(new OpenSeadragon.Point(-panBy.x, 0)); + if (_this.horizontallyFlipped) { + _this.osd.viewport.panBy(new OpenSeadragon.Point(panBy.x, 0)); + } else { + _this.osd.viewport.panBy(new OpenSeadragon.Point(-panBy.x, 0)); + } _this.osd.viewport.applyConstraints(); }); @@ -246,7 +256,7 @@ _this.eventEmitter.publish('TOGGLE_BOTTOM_PANEL_VISIBILITY.' + _this.windowId); }); - //Annotation specific controls + // Annotation specific controls this.element.find('.mirador-osd-edit-mode').on('click', function() { var shape = jQuery(this).find('.material-icons').html(); if (_this.hud.annoState.current === 'pointer') { @@ -286,7 +296,7 @@ function setFilterCSS() { var filterCSS = jQuery.map(filterValues, function(value, key) { return value; }).join(" "), - osdCanvas = jQuery(_this.osd.drawer.canvas); + osdCanvas = jQuery(_this.osd.drawer.canvas); osdCanvas.css({ 'filter' : filterCSS, '-webkit-filter' : filterCSS, @@ -325,6 +335,12 @@ filterValues.invert = "invert(0%)"; _this.element.find('.mirador-osd-invert').removeClass('selected'); + //reset mirror + jQuery(_this.osd.canvas).removeClass('mirador-mirror'); + if (_this.originalDragHandler) { + _this.osd.viewport.viewer.innerTracker.dragHandler = _this.originalDragHandler; + } + setFilterCSS(); } @@ -362,12 +378,12 @@ }).hide(); this.element.find('.mirador-osd-brightness').on('mouseenter', - function() { - _this.element.find('.mirador-osd-brightness-slider').stop(true, true).show(); - }).on('mouseleave', - function() { - _this.element.find('.mirador-osd-brightness-slider').stop(true, true).hide(); - }); + function() { + _this.element.find('.mirador-osd-brightness-slider').stop(true, true).show(); + }).on('mouseleave', + function() { + _this.element.find('.mirador-osd-brightness-slider').stop(true, true).hide(); + }); this.element.find('.mirador-osd-contrast-slider').slider({ orientation: "vertical", @@ -389,12 +405,12 @@ }).hide(); this.element.find('.mirador-osd-contrast').on('mouseenter', - function() { - _this.element.find('.mirador-osd-contrast-slider').stop(true, true).show(); - }).on('mouseleave', - function() { - _this.element.find('.mirador-osd-contrast-slider').stop(true, true).hide(); - }); + function() { + _this.element.find('.mirador-osd-contrast-slider').stop(true, true).show(); + }).on('mouseleave', + function() { + _this.element.find('.mirador-osd-contrast-slider').stop(true, true).hide(); + }); this.element.find('.mirador-osd-saturation-slider').slider({ orientation: "vertical", @@ -416,12 +432,12 @@ }).hide(); this.element.find('.mirador-osd-saturation').on('mouseenter', - function() { - _this.element.find('.mirador-osd-saturation-slider').stop(true, true).show(); - }).on('mouseleave', - function() { - _this.element.find('.mirador-osd-saturation-slider').stop(true, true).hide(); - }); + function() { + _this.element.find('.mirador-osd-saturation-slider').stop(true, true).show(); + }).on('mouseleave', + function() { + _this.element.find('.mirador-osd-saturation-slider').stop(true, true).hide(); + }); this.element.find('.mirador-osd-grayscale').on('click', function() { if (jQuery(this).hasClass('selected')) { @@ -445,6 +461,33 @@ setFilterCSS(); }); + this.element.find('.mirador-osd-mirror').on('click', function() { + if (!_this.originalDragHandler) { + _this.originalDragHandler = _this.osd.viewport && _this.osd.viewport.viewer.innerTracker.dragHandler; + } + if (jQuery(this).hasClass('selected')) { + jQuery(_this.osd.canvas).removeClass('mirador-mirror'); + jQuery(this).removeClass('selected'); + _this.eventEmitter.publish('disableManipulation', 'mirror'); + if (_this.osd.viewport) { + _this.osd.viewport.viewer.innerTracker.dragHandler = _this.originalDragHandler; + } + _this.horizontallyFlipped = false; + } else { + jQuery(_this.osd.canvas).addClass('mirador-mirror'); + jQuery(this).addClass('selected'); + _this.eventEmitter.publish('enableManipulation', 'mirror'); + if (_this.osd.viewport) { + var viewer = _this.osd.viewport.viewer; + viewer.innerTracker.dragHandler = OpenSeadragon.delegate(viewer, function(event) { + event.delta.x = -event.delta.x; + _this.originalDragHandler(event); + }); + } + _this.horizontallyFlipped = true; + } + }); + this.element.find('.mirador-osd-reset').on('click', function() { resetImageManipulationControls(); }); @@ -455,6 +498,111 @@ //Image manipulation controls }, + currentCanvasIDUpdated: function(event, canvasId) { + var _this = this, + firstCanvasId = _this.imagesList[0]['@id'], + lastCanvasId = _this.imagesList[_this.imagesList.length-1]['@id']; + + // If it is the first canvas, hide the "go to previous" button, otherwise show it. + if (canvasId === firstCanvasId) { + _this.element.find('.mirador-osd-previous').hide(); + _this.element.find('.mirador-osd-next').show(); + } else if (canvasId === lastCanvasId) { + _this.element.find('.mirador-osd-next').hide(); + _this.element.find('.mirador-osd-previous').show(); + } else { + _this.element.find('.mirador-osd-next').show(); + _this.element.find('.mirador-osd-previous').show(); + } + // If it is the last canvas, hide the "go to previous" button, otherwise show it. + }, + + loadImage: function(event, imageResource) { + var _this = this; + + // We've already loaded this tilesource + if(imageResource.status === 'drawn') { + return; + } + + imageResource.setStatus('requested'); + var bounds = imageResource.getGlobalBounds(); + + _this.osd.addTiledImage({ + x: bounds.x, + y: bounds.y, + width: bounds.width, + tileSource: imageResource.tileSource, + opacity: imageResource.opacity, + clip: imageResource.clipRegion, + index: imageResource.zIndex, + + success: function(event) { + var tiledImage = event.item; + + imageResource.osdTiledImage = tiledImage; + imageResource.setStatus('loaded'); + _this.syncAllImageResourceProperties(imageResource); + + var tileDrawnHandler = function(event) { + if (event.tiledImage === tiledImage) { + imageResource.setStatus('drawn'); + _this.osd.removeHandler('tile-drawn', tileDrawnHandler); + } + }; + _this.osd.addHandler('tile-drawn', tileDrawnHandler); + }, + + error: function(event) { + // Add any auth information here. + // + // var errorInfo = { + // id: imageResource.osdTileSource, + // message: event.message, + // source: event.source + // }; + imageResource.setStatus('failed'); + } + }); + }, + showImage: function(event, imageResource) { + // Check whether or not this item has been drawn. + // This implies that the request has been issued already + // and the opacity can be updated. + if (imageResource.getStatus() === 'drawn') { + this.updateImageOpacity(null, imageResource); + } + }, + hideImage: function(event, imageResource) { + if (imageResource.getStatus() === 'drawn') { + imageResource.osdTiledImage.setOpacity(0); + } + }, + removeImage: function() { + }, + updateImageOpacity: function(event, imageResource) { + if(imageResource.osdTiledImage) { + imageResource.osdTiledImage.setOpacity(imageResource.opacity * imageResource.parent.getOpacity()); + } + }, + syncAllImageResourceProperties: function(imageResource) { + if(imageResource.osdTiledImage) { + var bounds = imageResource.getGlobalBounds(); + // If ever the clipRegion parameter becomes + // writable, add it here. + imageResource.osdTiledImage.setPosition({ + x:bounds.x, + y:bounds.y + }, true); + imageResource.osdTiledImage.setWidth(bounds.width, true); + imageResource.osdTiledImage.setOpacity( + imageResource.getOpacity() * imageResource.parent.getOpacity() + ); + // This will be for the drag and drop functionality. + // _this.updateImageLayeringIndex(imageResource); + } + }, + getPanByValue: function() { var bounds = this.osd.viewport.getBounds(true); //for now, let's keep 50% of the image on the screen @@ -467,17 +615,18 @@ setBounds: function() { var _this = this; + this.osdOptions.osdBounds = this.osd.viewport.getBounds(true); _this.eventEmitter.publish("imageBoundsUpdated", { id: _this.windowId, - osdBounds: { - x: _this.osdOptions.osdBounds.x, - y: _this.osdOptions.osdBounds.y, - width: _this.osdOptions.osdBounds.width, - height: _this.osdOptions.osdBounds.height - } + osdBounds: { + x: _this.osdOptions.osdBounds.x, + y: _this.osdOptions.osdBounds.y, + width: _this.osdOptions.osdBounds.width, + height: _this.osdOptions.osdBounds.height + } }); - var rectangle = this.osd.viewport.viewportToImageRectangle(this.osdOptions.osdBounds); + var rectangle = this.osdOptions.osdBounds; // In ImageView, viewport coordinates are in the same as Canvas Coordinates. _this.eventEmitter.publish("imageRectangleUpdated", { id: _this.windowId, osdBounds: { @@ -485,7 +634,8 @@ y: Math.round(rectangle.y), width: Math.round(rectangle.width), height: Math.round(rectangle.height) - } + }, + warning: 'Warning, image rectangle now based on canvas dimensions, not the constituent images.' }); }, @@ -528,117 +678,99 @@ } }, - createOpenSeadragonInstance: function(imageUrl) { - var infoJsonUrl = imageUrl + '/info.json', - uniqueID = $.genUUID(), - osdID = 'mirador-osd-' + uniqueID, - infoJson, - _this = this; - - this.element.find('.' + this.osdCls).remove(); + initialiseImageCanvas: function() { + var _this = this, + osdID = 'mirador-osd-' + $.genUUID(), + canvasModel = _this.canvases[_this.canvasID]; - jQuery.getJSON(infoJsonUrl).done(function (infoJson, status, jqXHR) { - _this.elemOsd = - jQuery('
') + _this.elemOsd = + jQuery('
') .addClass(_this.osdCls) .attr('id', osdID) .appendTo(_this.element); - _this.osd = $.OpenSeadragon({ - 'id': osdID, - 'tileSources': infoJson, - 'uniqueID' : uniqueID - }); - - _this.osd.addHandler('zoom', $.debounce(function(){ - var point = { - 'x': -10000000, - 'y': -10000000 - }; - _this.eventEmitter.publish('updateTooltips.' + _this.windowId, [point, point]); - }, 30)); + _this.osd = $.OpenSeadragon({ + id: osdID, + uniqueID: osdID, + preserveViewport: true, + blendTime: 0.1, + alwaysBlend: false, + showNavigationControl: false + }); + + var canvasBounds = canvasModel.getBounds(); + var rect = new OpenSeadragon.Rect( + canvasBounds.x, + canvasBounds.y, + canvasBounds.width, + canvasBounds.height + ); + _this.osd.viewport.fitBounds(rect, true); // center viewport before image is placed. + + canvasModel.show(); + canvasModel.getVisibleImages().forEach(function(imageResource) { + _this.loadImage(null, imageResource); + }); + + _this.osd.addHandler('zoom', $.debounce(function(){ + var point = { + 'x': -10000000, + 'y': -10000000 + }; + _this.eventEmitter.publish('updateTooltips.' + _this.windowId, [point, point]); + }, 30)); - _this.osd.addHandler('pan', $.debounce(function(){ - var point = { - 'x': -10000000, - 'y': -10000000 - }; - _this.eventEmitter.publish('updateTooltips.' + _this.windowId, [point, point]); - }, 30)); - -// if (_this.state.getStateProperty('autoHideControls')) { -// var timeoutID = null, -// fadeDuration = _this.state.getStateProperty('fadeDuration'), -// timeoutDuration = _this.state.getStateProperty('timeoutDuration'); -// var hideHUD = function() { -// _this.element.find(".hud-control").stop(true, true).addClass('hidden', fadeDuration); -// }; -// hideHUD(); -// jQuery(_this.element).on('mousemove', function() { -// window.clearTimeout(timeoutID); -// _this.element.find(".hud-control").stop(true, true).removeClass('hidden', fadeDuration); -// // When a user is in annotation create mode, force show the controls so they don't disappear when in a qtip, so check for that -// if (!_this.forceShowControls) { -// timeoutID = window.setTimeout(hideHUD, timeoutDuration); -// } -// }).on('mouseleave', function() { -// if (!_this.forceShowControls) { -// window.clearTimeout(timeoutID); -// hideHUD(); -// } -// }); -// } - - _this.osd.addHandler('open', function(){ - _this.eventEmitter.publish('osdOpen.'+_this.windowId); - if (_this.osdOptions.osdBounds) { - var rect = new OpenSeadragon.Rect(_this.osdOptions.osdBounds.x, _this.osdOptions.osdBounds.y, _this.osdOptions.osdBounds.width, _this.osdOptions.osdBounds.height); - _this.osd.viewport.fitBounds(rect, true); - } else { - // else reset bounds for this image - _this.setBounds(); - } + _this.osd.addHandler('pan', $.debounce(function(){ + var point = { + 'x': -10000000, + 'y': -10000000 + }; + _this.eventEmitter.publish('updateTooltips.' + _this.windowId, [point, point]); + }, 30)); - if (_this.boundsToFocusOnNextOpen) { - _this.eventEmitter.publish('fitBounds.' + _this.windowId, _this.boundsToFocusOnNextOpen); - _this.boundsToFocusOnNextOpen = null; - } + // Maintain this as an external API. + _this.eventEmitter.publish('osdOpen.'+_this.windowId); + _this.addAnnotationsLayer(_this.elemAnno); - _this.addAnnotationsLayer(_this.elemAnno); + if (_this.osdOptions.osdBounds) { + var newBounds = new OpenSeadragon.Rect(_this.osdOptions.osdBounds.x, _this.osdOptions.osdBounds.y, _this.osdOptions.osdBounds.width, _this.osdOptions.osdBounds.height); + _this.osd.viewport.fitBounds(newBounds, true); + } else { + // else reset bounds for this image + _this.setBounds(); + } - // get the state before resetting it so we can get back to that state - var originalState = _this.hud.annoState.current; - var selected = _this.element.find('.mirador-osd-edit-mode.selected'); - var shape = null; - if (selected) { - shape = selected.find('.material-icons').html(); - } - if (originalState === 'none') { - _this.hud.annoState.startup(); - } else if (originalState === 'off' || _this.annotationState === 'off') { - //original state is off, so don't need to do anything - } else { - _this.hud.annoState.displayOff(); - } + // get the state before resetting it so we can get back to that state + var originalState = _this.hud.annoState.current; + var selected = _this.element.find('.mirador-osd-edit-mode.selected'); + var shape = null; + if (selected) { + shape = selected.find('.material-icons').html(); + } + if (originalState === 'none') { + _this.hud.annoState.startup(); + } else if (originalState === 'off' || _this.annotationState === 'off') { + //original state is off, so don't need to do anything + } else { + _this.hud.annoState.displayOff(); + } - if (originalState === 'pointer' || _this.annotationState === 'on') { - _this.hud.annoState.displayOn(); - } else if (originalState === 'shape') { - _this.hud.annoState.displayOn(); - _this.hud.annoState.chooseShape(shape); - } else { - //original state is off, so don't need to do anything - } + if (originalState === 'pointer' || _this.annotationState === 'on') { + _this.hud.annoState.displayOn(); + } else if (originalState === 'shape') { + _this.hud.annoState.displayOn(); + _this.hud.annoState.chooseShape(shape); + } else { + //original state is off, so don't need to do anything + } - _this.osd.addHandler('zoom', $.debounce(function() { - _this.setBounds(); - }, 500)); + _this.osd.addHandler('zoom', $.debounce(function() { + _this.setBounds(); + }, 500)); - _this.osd.addHandler('pan', $.debounce(function(){ - _this.setBounds(); - }, 500)); - }); - }); + _this.osd.addHandler('pan', $.debounce(function(){ + _this.setBounds(); + }, 500)); }, //TODO reuse annotationsLayer with IIIFManifestLayouts @@ -657,21 +789,32 @@ updateImage: function(canvasID) { var _this = this; if (this.canvasID !== canvasID) { + this.canvases[_this.canvasID].getVisibleImages().forEach(function(imageResource){ + imageResource.hide(); + }); this.canvasID = canvasID; this.currentImgIndex = $.getImageIndexById(this.imagesList, canvasID); this.currentImg = this.imagesList[this.currentImgIndex]; + + var newCanvas = this.canvases[_this.canvasID]; + var canvasBounds = newCanvas.getBounds(); + var rect = new OpenSeadragon.Rect( + canvasBounds.x, + canvasBounds.y, + canvasBounds.width, + canvasBounds.height + ); + _this.osd.viewport.fitBounds(rect, true); // center viewport before image is placed. + newCanvas.show(); + this.osdOptions = { osdBounds: null, zoomLevel: null }; this.eventEmitter.publish('resetImageManipulationControls.'+this.windowId); - this.osd.close(); - this.createOpenSeadragonInstance($.Iiif.getImageUrl(this.currentImg)); - _this.eventEmitter.publish('UPDATE_FOCUS_IMAGES.' + this.windowId, {array: [canvasID]}); - } else { - _this.eventEmitter.publish('UPDATE_FOCUS_IMAGES.' + this.windowId, {array: [canvasID]}); } - }, + _this.eventEmitter.publish('UPDATE_FOCUS_IMAGES.' + this.windowId, {array: [canvasID]}); + }, next: function() { var _this = this; diff --git a/js/src/widgets/layersTab.js b/js/src/widgets/layersTab.js index 65ff94b993..36b228331e 100644 --- a/js/src/widgets/layersTab.js +++ b/js/src/widgets/layersTab.js @@ -2,12 +2,15 @@ $.LayersTab = function(options) { jQuery.extend(true, this, { + windowId: null, element: null, appendTo: null, manifest: null, visible: null, state: null, - eventEmitter: null + eventEmitter: null, + canvasID: null, + canvases: null }, options); this.init(); @@ -16,19 +19,18 @@ $.LayersTab.prototype = { init: function() { var _this = this; - this.windowId = this.windowId; this.localState({ id: 'layersTab', visible: this.visible, - selectedList: null, - empty: true + active: true, // needs to be a function of the window state + canvasID: _this.canvasID, + empty: false // needs to be a function of the canvasModel }, true); - this.listenForActions(); this.render(this.localState()); - this.loadTabComponents(); this.bindEvents(); + this.listenForActions(); }, localState: function(state, initial) { @@ -43,25 +45,51 @@ return this.layerTabState; }, - loadTabComponents: function() { - var _this = this; + tabStateUpdated: function(visible) { + var localState = this.localState(); + localState.visible = visible; + this.localState(localState); }, - tabStateUpdated: function(visible) { + canvasIdUpdated: function(event, canvasID) { var localState = this.localState(); - localState.visible = localState.visible ? false : true; + localState.canvasID = canvasID; this.localState(localState); }, imageFocusUpdated: function(focus) { var localState = this.localState(); - localState.active = (focus === 'ThumbnailsView') ? false : true; + localState.active = (focus !== 'ImageView') ? false : true; this.localState(localState); }, + updateImageResourceStatus: function(event, imageResource) { + var _this = this; + + ['loaded', 'requested', 'initialized', 'drawn'].forEach(function(statusString){ + _this.element.find('.layers-list-item[data-imageid="'+ imageResource.id + '"]').removeClass(statusString); + }); + + this.element.find('.layers-list-item[data-imageid="'+ imageResource.id + '"]').addClass(imageResource.getStatus()); + }, + showImageResource: function(event, imageResource) { + this.element.find('.visibility-toggle[data-imageid="'+ imageResource.id + '"]').prop('checked', true); + this.element.find('.opacity-slider[data-imageid="'+ imageResource.id + '"]').prop('disabled', false); + this.element.find('.opacity-label[data-imageid="'+ imageResource.id + '"]').removeClass('disabled').text("(" + Math.ceil(imageResource.getOpacity()*100) + ")%"); + }, + hideImageResource: function(event, imageResource) { + this.element.find('.visibility-toggle[data-imageid="'+ imageResource.id + '"]').prop('checked', false); + this.element.find('.opacity-slider[data-imageid="'+ imageResource.id + '"]').prop('disabled', true); + this.element.find('.opacity-label[data-imageid="'+ imageResource.id + '"]').addClass('disabled').text("(" + i18next.t('disabledOpacityMessage') + ")"); + }, + updateImageResourceOpacity: function(event, imageResource) { + this.element.find('.opacity-slider[data-imageid="'+ imageResource.id + '"]').val(imageResource.getOpacity()*100); + this.element.find('.opacity-label[data-imageid="'+ imageResource.id + '"]').text("(" + Math.ceil(imageResource.getOpacity()*100) + ")%"); + }, + listenForActions: function() { var _this = this; @@ -71,16 +99,22 @@ }); _this.eventEmitter.subscribe('tabStateUpdated.' + _this.windowId, function(_, data) { - _this.tabStateUpdated(data.layersTab); + var visible = data.tabs[data.selectedTabIndex].options.id === 'layersTab'; + _this.tabStateUpdated(visible); }); - _this.eventEmitter.subscribe('currentCanvasIDUpdated.' + _this.windowId, function(event, canvasID) { - //update layers for this canvasID - }); + _this.eventEmitter.subscribe('currentCanvasIDUpdated.' + _this.windowId, _this.canvasIdUpdated.bind(_this)); + _this.eventEmitter.subscribe('image-status-updated' + _this.windowId, _this.updateImageResourceStatus.bind(_this)); + _this.eventEmitter.subscribe('image-show' + _this.windowId, _this.showImageResource.bind(_this)); + _this.eventEmitter.subscribe('image-hide' + _this.windowId, _this.hideImageResource.bind(_this)); + _this.eventEmitter.subscribe('image-opacity-updated' + _this.windowId, _this.updateImageResourceOpacity.bind(_this)); _this.eventEmitter.subscribe('focusUpdated' + _this.windowId, function(event, focus) { + + // update the disabled state of the layersTab // since it cannot be used in overview mode + // but is visible/available in image mode. _this.imageFocusUpdated(focus); }); }, @@ -88,12 +122,60 @@ bindEvents: function() { var _this = this; + this.element.find('img').on('load', function(event) { + // fades in thumbs when they finish loading. + jQuery(this).addClass('loaded'); + jQuery(this).closest('.thumb-container').removeClass('awaiting-thumbnail'); + }); + + this.element.find('img').on('error', function(event) { + // prevents failed images from showing. + jQuery(this).addClass('failed'); + jQuery(this).closest('.thumb-container').removeClass('awaiting-thumbnail'); + jQuery(this).closest('.thumb-container').addClass('thumb-failed'); + }); + + this.element.on('input', '.opacity-slider', function(event) { + var canvasModel = _this.canvases[_this.localState().canvasID], + eventedImageResource = canvasModel.getImageById(event.currentTarget.attributes['data-imageid'].nodeValue); + + eventedImageResource.setOpacity(event.currentTarget.value/100); + }); + + this.element.on('change', '.visibility-toggle', function(event) { + var canvasModel = _this.canvases[_this.localState().canvasID], + eventedImageResource = canvasModel.getImageById(event.currentTarget.attributes['data-imageid'].nodeValue); + if(event.currentTarget.checked) { + eventedImageResource.show(); + } else { + eventedImageResource.hide(); + } + }); }, render: function(state) { var _this = this, + canvasModel = _this.canvases[state.canvasID], templateData = { - active: state.active ? '' : 'inactive' + active: state.active ? '' : 'inactive', + imagesFor: i18next.t('imagesFor'), + hasLayers: canvasModel.images.length > 0, + canvasTitle: canvasModel.label, + disabledLayersTabMessage: i18next.t('disabledLayersTabMessage'), + layers: canvasModel.images.map(function(imageResource){ + return { + visibleLabel: i18next.t('visibleLabel'), + opacityLabel: i18next.t('opacityLabel'), + disabledOpacityMessage: i18next.t('disabledOpacityMessage'), + emptyTemplateMessage: i18next.t('emptyTemplateMessage'), + imageId: imageResource.id, + title: imageResource.label, + opacity: imageResource.getOpacity()*100, // scale factor for limitations of html5 slider element + loadingStatus: imageResource.getStatus(), + visibility: imageResource.getVisible(), + url: imageResource.thumbUrl + }; + }) }; if (this.element) { @@ -110,8 +192,39 @@ } }, - template: Handlebars.compile([ + template: $.Handlebars.compile([ '
', + '

{{imagesFor}} {{canvasTitle}}

', + '{{#if hasLayers}}', + '
    ', + '{{#each layers}}', + '
  • ', + '

    {{this.title}}

    ', + '
    ', + '{{canvasTitle}} title=', + '', + '', + '', + '
    ', + '
    ', + '
    ', + '', + '', + '
    ', + '', + '', + '
    ', + '
  • ', + '{{/each}}', + '
', + '{{else}}', + '

{{emptyTemplateMessage}}

', + '{{/if}}', + '
', + '

{{disabledLayersTabMessage}}

', + '
', '
', ].join('')) }; diff --git a/js/src/widgets/metadataView.js b/js/src/widgets/metadataView.js index a5f5e46817..06e2508eee 100755 --- a/js/src/widgets/metadataView.js +++ b/js/src/widgets/metadataView.js @@ -38,6 +38,7 @@ if (typeof itm.value === 'string' && itm.value !== '') { tplData[metadataKey].push({ + identifier: itm.identifier || '', label: _this.extractLabelFromAttribute(itm.label), value: (metadataKey === 'links') ? itm.value : _this.addLinksToUris(itm.value) }); @@ -147,21 +148,38 @@ return mdList; }, - getMetadataRights: function(jsonLd) { - return [ - {label: i18next.t('license'), value: jsonLd.license || ''}, - {label: i18next.t('attribution'), value: $.JsonLd.getTextValue(jsonLd.attribution) || ''} - ]; - }, + getMetadataRights: function(jsonLd) { + return [ + { + identifier: 'license', + label: i18next.t('license'), + value: jsonLd.license || '' + }, { + identifier: 'attribution', + label: i18next.t('attribution'), + value: $.JsonLd.getTextValue(jsonLd.attribution) || '' + } + ]; + }, - getMetadataLinks: function(jsonLd) { - // #414 - return [ - {label: i18next.t('related'), value: this.stringifyRelated(jsonLd.related || '')}, - {label: i18next.t('seeAlso'), value: this.stringifyRelated(jsonLd.seeAlso || '')}, - {label: i18next.t('within'), value: this.getWithin(jsonLd.within || '')} - ]; - }, + getMetadataLinks: function(jsonLd) { + // #414 + return [ + { + identifier: 'related', + label: i18next.t('related'), + value: this.stringifyRelated(jsonLd.related || '') + }, { + identifier: 'seeAlso', + label: i18next.t('seeAlso'), + value: this.stringifyRelated(jsonLd.seeAlso || '') + }, { + identifier: 'within', + label: i18next.t('within'), + value: this.getWithin(jsonLd.within || '') + } + ]; + }, getWithin: function(within) { if (typeof within === 'object' && within['@type'] === 'sc:Collection') { @@ -248,7 +266,7 @@ '{{#if rights}}', '
', '{{#each rights}}', - '', + '', '{{/each}}', '{{#if logo}}', '', @@ -263,7 +281,7 @@ '
{{t "links"}}:
', '
', '{{#each links}}', - '', + '', '{{/each}}', // '{{#if relatedLinks}}', // '
{{label}}:
{{{value}}}
', diff --git a/js/src/widgets/searchTab.js b/js/src/widgets/searchTab.js index 0fa3e2e4b5..24723e68d0 100644 --- a/js/src/widgets/searchTab.js +++ b/js/src/widgets/searchTab.js @@ -156,7 +156,7 @@ } }, - template: Handlebars.compile([ + template: $.Handlebars.compile([ '
', '{{#if searchService}}', '