Skip to content
Snippets Groups Projects
Unverified Commit 69bf7d29 authored by aeschylus's avatar aeschylus Committed by GitHub
Browse files

Merge pull request #1568 from ProjectMirador/update-from-poc

Update projectmirador/mirador with work from research and demos proof of concept work
parents 7188cdec 08f499b8
Branches
Tags
No related merge requests found
Showing
with 434 additions and 375 deletions
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
dist/
config/
coverage/
{
"extends": "airbnb-base/legacy",
"env": {
"jest/globals": true
},
"extends": ["airbnb","react-app"],
"globals": {
"Mirador": true,
"jQuery": true
"page": true,
"document": true
},
"parser": "babel-eslint",
"plugins": ["jest"],
"rules": {
"no-param-reassign": [
2, {
"props": true,
"ignorePropertyModificationsFor": [
"$"
]
"no-console": "off",
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"require-jsdoc": ["error", {
"require": {
"FunctionDeclaration": true,
"MethodDefinition": true,
"ClassDeclaration": true,
"ArrowFunctionExpression": true,
"FunctionExpression": true
}
]
}],
"react/prefer-stateless-function": "off"
}
}
.grunt/
_SpecRunner.html
bkp
css/discovery.css
css/mirador.css
css/jquery-ui-scoped.css
dev.html
discovery.html
js/discovery
node_modules/
npm-debug.log
dist/
coverage/
data/ecodices
data/BnF
data/Harvard
build/
.idea/
bower_components/
node_modules/
package-lock.json
{
"expr":true
}
language: node_js
sudo: false
node_js:
- '5'
- 'v6.10.3'
before_install:
- 'node'
before_script:
- npm install
script:
- npm run travis
module.exports = function(grunt) {
// ----------
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');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-git-describe');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-githooks');
grunt.loadNpmTasks('grunt-css-selectors');
// ----------
var distribution = 'build/mirador/mirador.js',
minified = 'build/mirador/mirador.min.js',
releaseRoot = '../site-build/built-mirador/',
// libraries/plugins
vendors = [
'node_modules/jquery/dist/jquery.min.js',
'node_modules/jquery-migrate/dist/jquery-migrate.min.js',
'node_modules/jquery-ui-dist/jquery-ui.min.js',
'node_modules/bootstrap/js/modal.js',
'node_modules/bootstrap/js/transition.js',
'node_modules/bootbox/bootbox.js',
'node_modules/jquery.scrollto/jquery.scrollTo.min.js',
'node_modules/jstree/dist/jstree.min.js',
'node_modules/qtip2/dist/jquery.qtip.min.js',
'node_modules/javascript-state-machine/state-machine.min.js',
'node_modules/tinymce/tinymce.min.js',
'node_modules/handlebars/dist/handlebars.js',
'node_modules/openseadragon/build/openseadragon/openseadragon.js',
'node_modules/d3/d3.min.js',
'node_modules/jquery-plugin/dist/ba-tiny-pubsub.js',
'node_modules/urijs/src/URI.min.js',
'node_modules/mousetrap/mousetrap.min.js',
'js/lib/isfahan.js',
'node_modules/paper/dist/paper-core.min.js',
'node_modules/spectrum-colorpicker/spectrum.js',
'node_modules/i18next/i18next.min.js',
'node_modules/i18next-browser-languagedetector/i18nextBrowserLanguageDetector.min.js',
'node_modules/i18next-xhr-backend/i18nextXHRBackend.min.js',
'node_modules/simple-pagination.js/jquery.simplePagination.js',
'js/lib/modernizr.custom.js',
'node_modules/sanitize-html/dist/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',
'node_modules/select2/dist/js/select2.full.min.js'
],
// source files
sources = [
'js/src/mirador.js',
'js/src/utils/handlebars.js',
'js/src/*.js',
'js/src/viewer/*.js',
'js/src/manifests/*.js',
'js/src/annotations/*.js',
'js/src/workspaces/*.js',
'js/src/widgets/*.js',
'js/src/utils/*.js'
];
// ----------
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
clean: {
build: ['build'],
release: {
src: [releaseRoot],
options: {
force: true
}
}
},
concat: {
js: {
options: {
banner: '//! <%= pkg.name %> <%= pkg.version %>\n' + '//! Built on <%= grunt.template.today("yyyy-mm-dd") %>\n',
process: true
},
src: [ "<banner>" ].concat(vendors, sources),
dest: distribution
},
css: {
src: [
'css/bootstrap.modals.css',
'css/normalize.css',
'node_modules/font-awesome/css/font-awesome.min.css',
'css/jquery-ui-scoped.css',
'node_modules/jstree/dist/themes/default/style.min.css',
'css/collection-tree-mod.css',
'node_modules/qtip2/dist/jquery.qtip.min.css',
'node_modules/spectrum-colorpicker/spectrum.css',
'node_modules/select2/dist/css/select2.min.css',
'css/mirador.css',
'css/material-icons.css',
'node_modules/simple-pagination.js/simplePagination.css'
],
dest: 'build/mirador/css/mirador-combined.css'
}
},
less: {
compile: {
files: {
'css/mirador.css': 'css/less/main.less'
}
}
},
cssmin: {
minify: {
src: 'build/mirador/css/mirador-combined.css',
dest: 'build/mirador/css/mirador-combined.min.css'
}
},
uglify: {
options: {
preserveComments: 'some',
mangle: false,
sourceMap: true
},
mirador: {
src: [vendors, sources],
dest: minified
}
},
copy: {
main: {
files: [{
expand: true,
src: 'css/images/**',
dest: 'build/mirador/'
}, {
expand: true,
cwd: 'node_modules/tinymce',
src: 'themes/**',
dest: 'build/mirador'
}, {
expand: true,
cwd: 'node_modules/tinymce',
src: 'skins/**',
dest: 'build/mirador'
}, {
expand: true,
cwd: 'node_modules/tinymce',
src: 'plugins/**',
dest: 'build/mirador'
}, {
expand: true,
src: 'images/**',
dest: 'build/mirador'
}, {
expand: true,
cwd: 'node_modules/font-awesome',
src: 'fonts/*',
dest: 'build/mirador'
}, {
expand: true,
cwd: 'node_modules/material-design-icons/iconfont',
src: 'MaterialIcons*',
dest: 'build/mirador/fonts'
}, {
src: 'js/lib/parse.min.js',
dest: 'build/mirador/parse.min.js'
}, {
expand: true,
src: 'locales/**',
dest: 'build/mirador'
}]
}
},
connect: {
server: {
options: {
port: 8000,
base: '.'
}
}
},
watch: {
all: {
options: {
livereload: {
options: { livereload: true },
files: ['build/**/*']
}
},
files: [
'Gruntfile.js',
'js/src/*.js',
'js/src/*/*.js',
'locales/*/*.json',
'images/*',
'css/*.css',
'css/less/**/*.less',
'index.html'
],
tasks: 'dev_build'
}
},
eslint: {
options: {
silent: true
},
src: sources
},
jshint: {
options: {
browser: true,
eqeqeq: false,
loopfunc: false,
indent: false,
jshintrc: '.jshintrc',
globals: {
Mirador: true
}
},
beforeconcat: sources
},
'git-describe': {
build: {
options: {
prop: 'gitInfo'
}
}
},
coveralls: {
options: {
src: 'reports/coverage/PhantomJS*/lcov.info',
force: 'true'
},
ci: {
src: 'reports/coverage/PhantomJS*/lcov.info'
}
},
css_selectors: {
options: {
mutations: [{
prefix: '.mirador-container'
}]
},
your_target: {
files: {
'css/jquery-ui-scoped.css': ['css/jquery-ui.min.css']
}
}
}
});
// ----------
// Copy:release task.
// Copies the contents of the build folder into the release folder.
grunt.registerTask('copy:release', function() {
grunt.file.recurse('build', function(abspath, rootdir, subdir, filename) {
var dest = releaseRoot +
(subdir ? subdir + '/' : '/') +
filename;
grunt.file.copy(abspath, dest);
});
});
// ----------
// Lint task
grunt.registerTask('lint', ['jshint', 'eslint']);
// ----------
// jQueryUI CSS task.
// Scopes all jQueryUI CSS selectors with '.mirador-container'.
grunt.registerTask('jqueryui_css', ['css_selectors']);
// ----------
// Build task.
// Cleans out the build folder and builds the code and images into it, checking lint.
grunt.registerTask('build', [ 'clean:build', 'git-describe', 'lint', 'less', 'jqueryui_css', 'concat', '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', 'lint', 'less', 'jqueryui_css', 'concat', 'copy']);
// ----------
// Default task.
// Does a normal build.
grunt.registerTask('default', ['build']);
// ----------
// Connect task.
// Runs server at specified port
grunt.registerTask('serve', ['dev_build', 'connect:server', 'watch']);
// ----------
// Runs this on travis.
grunt.registerTask('ci', [
'lint'
]);
};
Copyright © 2014 The Board of Trustees of the Leland Stanford Junior University
Copyright 2018 The Board of Trustees of the Leland Stanford Junior University
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
......
[![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)
## Running Mirador locally
# Mirador
![mirador banner](https://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.**
1. Run `npm install` to install the dependencies.
### [See a Demo](http://projectmirador.org/demo/)
### [Getting Started](http://projectmirador.org/docs/docs/getting-started.html)
## Starting the project
### Run in Development
Mirador uses [Node.js](https://nodejs.org/) and a build system to assemble, test, and manage the development resources. If you have never used these tools before, you may need to install them.
```sh
$ npm start
```
1. Install [Node.js](https://nodejs.org/)
2. Install the Grunt command line runner i.e. `npm install -g grunt-cli`
1. Clone the Mirador repository
1. Change into the Mirador directory
1. Install all dependencies with `npm install`. Run `npm start`.
Then navigate to [http://127.0.0.1:4444/__tests__/integration/mirador/](http://127.0.0.1:4444/__tests__/integration/mirador/)
### Run Tests
`npm test`
### Instantiating Mirador
For more information, see the [Docs](http://projectmirador.org/docs/docs/getting-started.html), submit an [issue](https://github.com/projectmirador/mirador/issues), or ask on [Slack](http://bit.ly/iiif-slack).
```javascript
var miradorInstance = Mirador.viewer({
id: 'mirador' // id selector where Mirador should be instantiated
});
### Project Diagnostics
[![Coverage Status](https://coveralls.io/repos/github/ProjectMirador/mirador/badge.svg?branch=master&upToDate=true)](https://coveralls.io/github/ProjectMirador/mirador?branch=master&upToDate=true)
> miradorInstance
{ actions, store }
```
### Example Action
Add a window:
```javascript
store.dispatch(actions.addWindow());
```
To focus a window run:
```javascript
store.dispatch(actions.focusWindow('window-1'))
```
### Check current state
```javascript
store.getState()
```
## Running the tests
```sh
$ npm test # For headless CI=true npm test
```
or to continually watch the source files
```sh
$ npm run test:watch
```
## Linting the project
```sh
$ npm run lint
```
## Debugging
Useful browser extensions for debugging/development purposes
- [React DevTools](https://github.com/facebook/react-devtools)
- [Redux DevTools](https://github.com/zalmoxisus/redux-devtools-extension)
{
"@context": "http://iiif.io/api/presentation/2/context.json",
"@id": "http://iiif.io/api/presentation/2.0/example/fixtures/2/manifest.json",
"@type": "sc:Manifest",
"label": "Test 2 Manifest: Metadata Pairs",
"metadata": [
{
"label": "date",
"value": "some date"
}
],
"within": "http://iiif.io/api/presentation/2.0/example/fixtures/collection.json",
"sequences": [
{
"@type": "sc:Sequence",
"label": "Test 2 Sequence 1",
"canvases": [
{
"@id": "http://iiif.io/api/presentation/2.0/example/fixtures/canvas/2/c1.json",
"@type": "sc:Canvas",
"label": "Test 2 Canvas: 1",
"height": 1800,
"width": 1200,
"images": [
{
"@type": "oa:Annotation",
"motivation": "sc:painting",
"resource": {
"@id": "http://iiif.io/api/presentation/2.0/example/fixtures/resources/page1-full.png",
"@type": "dctypes:Image",
"height": 1800,
"width": 1200
},
"on": "http://iiif.io/api/presentation/2.0/example/fixtures/canvas/2/c1.json"
}
]
}
]
}
]
}
{
"@context": "http://iiif.io/api/presentation/2/context.json",
"@id": "http://iiif.io/api/presentation/2.0/example/fixtures/24/manifest.json",
"@type": "sc:Manifest",
"label": "Test 24 Manifest: Image with IIIF Service - adapted with real image",
"within": "http://iiif.io/api/presentation/2.0/example/fixtures/collection.json",
"sequences": [
{
"@type": "sc:Sequence",
"label": "Test 24 Sequence 1",
"canvases": [
{
"@id": "http://iiif.io/api/presentation/2.0/example/fixtures/canvas/24/c1.json",
"@type": "sc:Canvas",
"label": "Test 24 Canvas: 1",
"height": 1800,
"width": 1200,
"images": [
{
"@type": "oa:Annotation",
"motivation": "sc:painting",
"resource": {
"@id": "https://stacks.stanford.edu/image/iiif/hg676jb4964%2F0380_796-44/full/full/0/default.jpg",
"@type": "dctypes:Image",
"format": "image/jpeg",
"height": 3820,
"width": 5426,
"service": {
"@context": "http://iiif.io/api/image/2/context.json",
"@id": "https://stacks.stanford.edu/image/iiif/hg676jb4964%2F0380_796-44",
"profile": "http://iiif.io/api/image/2/level2.json"
}
},
"on": "http://iiif.io/api/presentation/2.0/example/fixtures/canvas/24/c1.json"
}
]
}
]
}
]
}
describe('Basic end to end Mirador', () => {
beforeAll(async () => {
await page.goto('http://127.0.0.1:4488/__tests__/integration/mirador/');
});
it('has the correct page title', async () => {
const title = await page.title();
expect(title).toBe('Mirador');
});
it('loads a manifest and displays it', async () => {
await expect(page).toFill('#manifestURL', 'https://purl.stanford.edu/sn904cj3429/iiif/manifest');
await expect(page).toClick('#fetchBtn');
// TODO: Refactor the app so we get rid of the wait
await page.waitFor(1000);
await expect(page).toMatchElement('li', { text: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest' });
await expect(page).toMatchElement(
'h3',
"Peter's San Francisco Locator. The Birds-Eye-View Map of the Exposition City. Published by Locator Publishing Co",
);
await expect(page).toMatchElement('div', /Color/);
});
});
/* global miradorInstance */
describe('Config updating from instance', () => {
beforeAll(async () => {
await page.goto('http://127.0.0.1:4488/__tests__/integration/mirador/');
});
it('can modify the config via api', async () => {
await page.evaluate(() => {
const a = miradorInstance.actions.updateConfig({ foo: 'bat' });
miradorInstance.store.dispatch(a);
});
const config = await page.evaluate(() => (
miradorInstance.store.getState().config
));
await expect(config.foo).toBe('bat');
});
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<title>Mirador</title>
</head>
<body>
<div id="mirador"></div>
<script>document.write("<script type='text/javascript' src='../../../dist/mirador.min.js?v=" + Date.now() + "'><\/script>");</script>
<script type="text/javascript">
var miradorInstance = Mirador.viewer({
id: 'mirador'
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<title>Mirador</title>
</head>
<body>
<div id="mirador"></div>
<script src="../../../node_modules/react/umd/react.development.js"></script>
<script src="../../../node_modules/react-dom/umd/react-dom.development.js"></script>
<script>document.write("<script type='text/javascript' src='../../../dist/mirador.min.js?v=" + Date.now() + "'><\/script>");</script>
<script type="text/javascript">
class MiradorShareButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
alert('Share this stuff')
}
render() {
return React.createElement('button', { className: 'share', onClick: this.handleClick}, 'Share');
}
}
const miradorShareButton = {
name: 'miradorShareButton',
component: MiradorShareButton,
parent: 'WindowTopBarButtons',
}
Mirador.plugins.miradorShareButton = miradorShareButton;
class MiradorRuler extends React.Component {
constructor(props) {
super(props);
this._isMounted = false;
this.state = {
zooming: false,
}
this.zoomToColor = this.zoomToColor.bind(this);
}
componentDidMount() {
this._isMounted = true;
const that = this;
this.props.pluginParent().viewer.addHandler('zoom', (e) => {
if (that._isMounted) {
that.setState({
zooming: true
})
}
})
// Super hacky don't do this for real
function resetStyle() {
if (that._isMounted) {
that.setState({
zooming: false
})
}
setTimeout(resetStyle, 750)
}
resetStyle();
}
componentWillUnmount() {
this._isMounted = false;
if (this.props.pluginParent()) {
this.props.pluginParent().viewer.removeHandler('zoom');
}
}
zoomToColor(zooming) {
if (zooming) {
return 'red'
}
return 'black'
}
render() {
return React.createElement('div', {className: 'mirador-ruler', style: { position: 'absolute', bottom: 0, color: this.zoomToColor(this.state.zooming)}}, 'I am a ruler')
}
}
const miradorRuler = {
name: 'miradorRuler',
component: MiradorRuler,
parent: 'OpenSeadragonViewer',
mapStateToProps: ({ manifests }, props) => {
return {
manifests // return the part of the state I need here.
}
},
mapDispatchToProps: (dispatch) => {
return {}
},
}
Mirador.plugins.miradorRuler = miradorRuler;
var miradorInstance = Mirador.viewer({
id: 'mirador',
plugins: ['miradorShareButton', 'miradorRuler']
});
</script>
</body>
</html>
describe('Mirador plugin use', () => {
beforeAll(async () => {
await page.goto('http://127.0.0.1:4488/__tests__/integration/mirador/plugins.html');
await expect(page).toFill('#manifestURL', 'https://purl.stanford.edu/sn904cj3429/iiif/manifest');
await expect(page).toClick('#fetchBtn');
// TODO: Refactor the app so we get rid of the wait
await page.waitFor(1000);
await expect(page).toClick('li button');
});
it('displays "Share Button" plugin', async () => {
await expect(page).toMatchElement('button', { text: 'Share' });
page.on('dialog', async (dialog) => {
expect(dialog.message()).toBe('Share this stuff');
await dialog.dismiss();
});
await expect(page).toClick('button.share');
});
it('displays "Ruler" plugin', async () => {
await expect(page).toMatchElement('.mirador-ruler');
});
});
describe('Window actions', () => {
beforeAll(async () => {
await page.goto('http://127.0.0.1:4488/__tests__/integration/mirador/');
});
it('opens a window and closes it', async () => {
await expect(page).toFill('#manifestURL', 'https://purl.stanford.edu/sn904cj3429/iiif/manifest');
await expect(page).toClick('#fetchBtn');
// TODO: Refactor the app so we get rid of the wait
await page.waitFor(1000);
await expect(page).toClick('li button');
await expect(page).toMatchElement('.mirador-window');
await expect(page).toClick('.mirador-window-close');
await expect(page).not.toMatchElement('.mirador-window');
});
});
describe('Plain JavaScript example', () => {
beforeAll(async () => {
await page.goto('http://127.0.0.1:4488/__tests__/integration/vanilla-js/');
});
it('has the correct page title', async () => {
const title = await page.title();
expect(title).toBe('Examples');
});
it('loads a manifest and displays it', async () => {
await expect(page).toFill('#manifestURL', 'https://purl.stanford.edu/sn904cj3429/iiif/manifest');
await expect(page).toClick('#fetchBtn');
// TODO: Refactor the app so we get rid of the wait
await page.waitFor(1000);
const manifest = await page.$eval('#exampleManifest', e => e.innerHTML);
await expect(manifest).toMatch(/http:\/\/iiif\.io\/api\/presentation\/2\/context\.json/);
});
});
## Notes
To load external manifests, you will need to run the example pages from a serve. One easy way to do this is to run PythonHTTPServer:
`python -m SimpleHTTPServer`
To inspect the Mirador 3 prototype in the web console with (code completion), you can refer to it as:
`m3core`
This name is found in the `minimal_redux_poc/webpack.config.js` as the value of the `library` property.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Examples</title>
<script src="../../../dist/m3core.umd.js"></script>
</head>
<body>
<input id="manifestURL" type="text"/><button id="fetchBtn" type="submit">Fetch Manifest</button>
<pre id="exampleManifest">
</pre>
<script>
document.getElementById("fetchBtn").addEventListener("click", function(){
let val = document.getElementById("manifestURL").value;
let f = m3core.actions.fetchManifest(val);
m3core.store.dispatch(f);
});
m3core.store.subscribe(() => {
let contents = ''
let state = m3core.store.getState();
let manifest = state.manifests[document.getElementById("manifestURL").value];
switch (manifest.isFetching) {
case true:
contents = 'spinner';
break;
case false:
if(manifest.error){
contents = manifest.error.message;
} else {
contents = JSON.stringify(manifest.manifestation.__jsonld, 0, 3);
}
break;
default: contents = ''
}
document.getElementById("exampleManifest").innerHTML = contents;
});
</script>
</body>
</html>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment