diff --git a/.gitignore b/.gitignore
index 48fb168f..3d413911 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,10 +8,58 @@
/.bundle
# Ignore all logfiles and tempfiles.
-/log/*
-/tmp/*
+*/log/*
+*/tmp/*
!/log/.keep
!/tmp/.keep
# Ignore Byebug command history file.
.byebug_history
+
+# Ignore Mac files
+*.DS_Store
+
+# Ignore Visual Studio files
+.vscode
+
+# Ignore caches
+*.sass-cache
+
+/public/swagger/
+
+*.idea
+
+# React app stuff
+
+# dependencies
+*/node_modules
+
+# testing
+*/coverage
+
+# production
+*/build
+
+# documentation
+styleguide
+
+# misc
+*.DS_Store
+
+*.env.local
+*.env.development.local
+*.env.test.local
+*.env.production.local
+
+*npm-debug.log*
+*yarn-debug.log*
+*yarn-error.log*
+
+coverage
+jest_0
+test.log
+*.xml
+!/viscoll-api/spec/fixtures/*.xml
+
+# DIY images
+viscoll-api/uploads/*
diff --git a/.ruby-gemset b/.ruby-gemset
deleted file mode 100644
index 7d2389a7..00000000
--- a/.ruby-gemset
+++ /dev/null
@@ -1 +0,0 @@
-viscollobns
diff --git a/.ruby-version b/.ruby-version
deleted file mode 100644
index 262714f1..00000000
--- a/.ruby-version
+++ /dev/null
@@ -1 +0,0 @@
-ruby-2.4.0
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..f90f583b
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,23 @@
+FROM ubuntu:16.04
+
+# Install tools & libs to compile everything
+RUN apt-get update && \
+ apt-get install -y curl tzdata build-essential libssl-dev libreadline-dev wget && \
+ apt-get clean
+
+# Install nodejs
+RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
+RUN apt-get install -y nodejs && apt-get clean
+
+# Install ruby-build
+RUN apt-get install -y git-core && apt-get clean
+RUN git clone https://github.com/sstephenson/ruby-build.git && cd ruby-build && ./install.sh
+
+# Install ruby 2.4.1
+ENV CONFIGURE_OPTS --disable-install-rdoc
+RUN ruby-build 2.6.4 /usr/local
+RUN gem install bundler
+RUN gem install tzinfo-data
+
+# Clean up downloaded packages
+RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
\ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
deleted file mode 100644
index 7dbe4e28..00000000
--- a/Gemfile.lock
+++ /dev/null
@@ -1,269 +0,0 @@
-GEM
- remote: https://rubygems.org/
- specs:
- actioncable (5.0.2)
- actionpack (= 5.0.2)
- nio4r (>= 1.2, < 3.0)
- websocket-driver (~> 0.6.1)
- actionmailer (5.0.2)
- actionpack (= 5.0.2)
- actionview (= 5.0.2)
- activejob (= 5.0.2)
- mail (~> 2.5, >= 2.5.4)
- rails-dom-testing (~> 2.0)
- actionpack (5.0.2)
- actionview (= 5.0.2)
- activesupport (= 5.0.2)
- rack (~> 2.0)
- rack-test (~> 0.6.3)
- rails-dom-testing (~> 2.0)
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (5.0.2)
- activesupport (= 5.0.2)
- builder (~> 3.1)
- erubis (~> 2.7.0)
- rails-dom-testing (~> 2.0)
- rails-html-sanitizer (~> 1.0, >= 1.0.3)
- activejob (5.0.2)
- activesupport (= 5.0.2)
- globalid (>= 0.3.6)
- activemodel (5.0.2)
- activesupport (= 5.0.2)
- activerecord (5.0.2)
- activemodel (= 5.0.2)
- activesupport (= 5.0.2)
- arel (~> 7.0)
- activesupport (5.0.2)
- concurrent-ruby (~> 1.0, >= 1.0.2)
- i18n (~> 0.7)
- minitest (~> 5.1)
- tzinfo (~> 1.1)
- addressable (2.5.0)
- public_suffix (~> 2.0, >= 2.0.2)
- arel (7.1.4)
- autoprefixer-rails (6.7.6)
- execjs
- bcrypt (3.1.11)
- bootstrap-sass (3.3.7)
- autoprefixer-rails (>= 5.2.1)
- sass (>= 3.3.4)
- bson (4.2.1)
- builder (3.2.3)
- byebug (9.0.6)
- capybara (2.12.1)
- addressable
- mime-types (>= 1.16)
- nokogiri (>= 1.3.3)
- rack (>= 1.0.0)
- rack-test (>= 0.5.4)
- xpath (~> 2.0)
- concurrent-ruby (1.0.5)
- cucumber (2.4.0)
- builder (>= 2.1.2)
- cucumber-core (~> 1.5.0)
- cucumber-wire (~> 0.0.1)
- diff-lcs (>= 1.1.3)
- gherkin (~> 4.0)
- multi_json (>= 1.7.5, < 2.0)
- multi_test (>= 0.1.2)
- cucumber-core (1.5.0)
- gherkin (~> 4.0)
- cucumber-rails (1.4.5)
- capybara (>= 1.1.2, < 3)
- cucumber (>= 1.3.8, < 4)
- mime-types (>= 1.16, < 4)
- nokogiri (~> 1.5)
- railties (>= 3, < 5.1)
- cucumber-wire (0.0.1)
- database_cleaner (1.5.3)
- debug_inspector (0.0.2)
- devise (4.2.0)
- bcrypt (~> 3.0)
- orm_adapter (~> 0.1)
- railties (>= 4.1.0, < 5.1)
- responders
- warden (~> 1.2.3)
- diff-lcs (1.3)
- email_spec (2.1.0)
- htmlentities (~> 4.3.3)
- launchy (~> 2.1)
- mail (~> 2.6.3)
- erubis (2.7.0)
- execjs (2.7.0)
- factory_girl (4.8.0)
- activesupport (>= 3.0.0)
- factory_girl_rails (4.8.0)
- factory_girl (~> 4.8.0)
- railties (>= 3.0.0)
- faker (1.7.3)
- i18n (~> 0.5)
- ffi (1.9.18)
- gherkin (4.0.0)
- globalid (0.3.7)
- activesupport (>= 4.1.0)
- htmlentities (4.3.4)
- i18n (0.8.1)
- jbuilder (2.6.3)
- activesupport (>= 3.0.0, < 5.2)
- multi_json (~> 1.2)
- jquery-rails (4.2.2)
- rails-dom-testing (>= 1, < 3)
- railties (>= 4.2.0)
- thor (>= 0.14, < 2.0)
- launchy (2.4.3)
- addressable (~> 2.3)
- listen (3.0.8)
- rb-fsevent (~> 0.9, >= 0.9.4)
- rb-inotify (~> 0.9, >= 0.9.7)
- loofah (2.0.3)
- nokogiri (>= 1.5.9)
- mail (2.6.4)
- mime-types (>= 1.16, < 4)
- method_source (0.8.2)
- mime-types (3.1)
- mime-types-data (~> 3.2015)
- mime-types-data (3.2016.0521)
- mini_portile2 (2.1.0)
- minitest (5.10.1)
- mongo (2.4.1)
- bson (>= 4.2.1, < 5.0.0)
- mongoid (6.1.0)
- activemodel (~> 5.0)
- mongo (>= 2.4.1, < 3.0.0)
- mongoid-rspec (1.10.0)
- mongoid (>= 3.0.1)
- rake
- rspec (>= 2.14)
- multi_json (1.12.1)
- multi_test (0.1.2)
- nio4r (2.0.0)
- nokogiri (1.7.0.1)
- mini_portile2 (~> 2.1.0)
- orm_adapter (0.5.0)
- public_suffix (2.0.5)
- puma (3.7.1)
- rack (2.0.1)
- rack-test (0.6.3)
- rack (>= 1.0)
- rails (5.0.2)
- actioncable (= 5.0.2)
- actionmailer (= 5.0.2)
- actionpack (= 5.0.2)
- actionview (= 5.0.2)
- activejob (= 5.0.2)
- activemodel (= 5.0.2)
- activerecord (= 5.0.2)
- activesupport (= 5.0.2)
- bundler (>= 1.3.0, < 2.0)
- railties (= 5.0.2)
- sprockets-rails (>= 2.0.0)
- rails-dom-testing (2.0.2)
- activesupport (>= 4.2.0, < 6.0)
- nokogiri (~> 1.6)
- rails-html-sanitizer (1.0.3)
- loofah (~> 2.0)
- railties (5.0.2)
- actionpack (= 5.0.2)
- activesupport (= 5.0.2)
- method_source
- rake (>= 0.8.7)
- thor (>= 0.18.1, < 2.0)
- rake (12.0.0)
- rb-fsevent (0.9.8)
- rb-inotify (0.9.8)
- ffi (>= 0.5.0)
- responders (2.3.0)
- railties (>= 4.2.0, < 5.1)
- rspec (3.5.0)
- rspec-core (~> 3.5.0)
- rspec-expectations (~> 3.5.0)
- rspec-mocks (~> 3.5.0)
- rspec-core (3.5.4)
- rspec-support (~> 3.5.0)
- rspec-expectations (3.5.0)
- diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.5.0)
- rspec-mocks (3.5.0)
- diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.5.0)
- rspec-rails (3.5.2)
- actionpack (>= 3.0)
- activesupport (>= 3.0)
- railties (>= 3.0)
- rspec-core (~> 3.5.0)
- rspec-expectations (~> 3.5.0)
- rspec-mocks (~> 3.5.0)
- rspec-support (~> 3.5.0)
- rspec-support (3.5.0)
- sass (3.4.23)
- sass-rails (5.0.6)
- railties (>= 4.0.0, < 6)
- sass (~> 3.1)
- sprockets (>= 2.8, < 4.0)
- sprockets-rails (>= 2.0, < 4.0)
- tilt (>= 1.1, < 3)
- spring (2.0.1)
- activesupport (>= 4.2)
- spring-watcher-listen (2.0.1)
- listen (>= 2.7, < 4.0)
- spring (>= 1.2, < 3.0)
- sprockets (3.7.1)
- concurrent-ruby (~> 1.0)
- rack (> 1, < 3)
- sprockets-rails (3.2.0)
- actionpack (>= 4.0)
- activesupport (>= 4.0)
- sprockets (>= 3.0.0)
- thor (0.19.4)
- thread_safe (0.3.6)
- tilt (2.0.6)
- tzinfo (1.2.2)
- thread_safe (~> 0.1)
- uglifier (3.1.5)
- execjs (>= 0.3.0, < 3)
- warden (1.2.7)
- rack (>= 1.0)
- web-console (3.4.0)
- actionview (>= 5.0)
- activemodel (>= 5.0)
- debug_inspector
- railties (>= 5.0)
- websocket-driver (0.6.5)
- websocket-extensions (>= 0.1.0)
- websocket-extensions (0.1.2)
- xpath (2.0.0)
- nokogiri (~> 1.3)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- bcrypt (~> 3.1.7)
- bootstrap-sass
- byebug
- capybara
- cucumber-rails
- database_cleaner
- devise
- email_spec
- factory_girl_rails
- faker
- jbuilder (~> 2.5)
- jquery-rails
- launchy
- listen (~> 3.0.5)
- mongoid
- mongoid-rspec
- puma (~> 3.0)
- rails (~> 5.0.2)
- rspec
- rspec-rails
- sass-rails (~> 5.0)
- spring
- spring-watcher-listen (~> 2.0.0)
- tzinfo-data
- uglifier (>= 1.3.0)
- web-console (>= 3.3.0)
-
-BUNDLED WITH
- 1.14.6
diff --git a/README.md b/README.md
index 7db80e4c..90bc1d25 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,165 @@
-# README
+## Introduction
-This README would normally document whatever steps are necessary to get the
-application up and running.
+VisCodex is for building models of the physical collation of manuscripts, and then visualizing them in various ways. The VisCodex project is led by Dot Porter at the [Schoenberg Institute for Manuscript Studies](https://schoenberginstitute.org/) at the University of Pennsylvania, in collaboration with the [University of Toronto Libraries](https://onesearch.library.utoronto.ca/about) and the [Old Books New Science lab](https://oldbooksnewscience.com/). Collaborators include Alexandra Gillespie, Alberto Campagnolo, and Conal Tuohy.
-Things you may want to cover:
+## System Requirements
-* Ruby version
+- `rvm` (>= 1.29.1)
+- `ruby` (>= 2.4.1)
+- `node` (>= 6.11.4)
+- `npm` (>= 3.10.10)
-* System dependencies
+### Additional Requirements for Development:
-* Configuration
+- [`mailcatcher`](https://mailcatcher.me/) (>= 0.6.5)
+- [Redux DevTools for Firefox or Chrome](https://github.com/zalmoxisus/redux-devtools-extension) (>= 2.15.1)
-* Database creation
+## Development setup with Docker
-* Database initialization
+Instead of manually installing the dependencies locally on your machine for development, you can use Docker with the provided Dockerfile and docker-compose.yml.
-* How to run the test suite
+Update the mongo host name on line 12 in `viscoll-api/config/mongoid.yml` from `localhost` to `mongo` (this is the Docker service name defined in docker-compose.yml).
-* Services (job queues, cache servers, search engines, etc.)
+Bring up the containers with:
-* Deployment instructions
+```
+docker-compose up
+```
-* ...
+To access emails being sent by the app (for user account activation, password reset, etc), set up Ethereal with the following credentials:
+
+```
+:user_name => 'libby.corkery17@ethereal.email',
+:password => 'RP4P6zMm3rVW9adMZF'
+```
+This configuration is located at `viscoll-api/config/environments/development.rb`.
+
+## Installation and Setup
+
+Skip this section if you are using Docker for development.
+
+### VisCodex API (Rails)
+
+Rails-driven back-end for VisCodex
+
+#### System Requirements
+
+- `rvm` (>= 1.29.1)
+- `ruby` (>= 2.4.1)
+
+##### Additional Requirements for Development:
+
+- [`mailcatcher`](https://mailcatcher.me/) (>= 0.6.5)
+
+#### Setup
+
+Run the following commands to install the dependencies:
+```
+rvm --ruby-version use 2.4.1@viscollobns
+bundle install
+```
+
+Set the admin email address in two locations:
+
+`viscoll-api/app/mailers/mailer.rb` on line 18:
+
+```
+toEmail = Rails.application.secrets.admin_email || "dummy-admin@library.utoronto.ca"
+```
+
+and `viscoll-api/app/mailers/feedback_mailer.rb` on line 10:
+
+```
+to:"utlviscoll@library.utoronto.ca",
+```
+
+Then run this to start the API server:
+```
+rails s -p 3001
+```
+
+If you wish to receive confirmation and password reset emails while developing, also start the mailcatcher daemon:
+```
+mailcatcher
+```
+
+#### Testing
+
+Run this command to test once:
+```
+rspec
+```
+
+Alternatively, run this command to test continually while monitoring for changes:
+```
+guard
+```
+
+### VisCodex App (React-Redux)
+
+Redux-driven user interface for VisCodex
+
+#### System Requirements
+
+- `node` (>= 6.11.4)
+- `npm` (>= 3.10.10)
+
+##### Additional Requirements for Development:
+
+- [Redux DevTools for Firefox or Chrome](https://github.com/zalmoxisus/redux-devtools-extension) (>= 2.15.1)
+
+#### Setup
+
+Run this to install the dependencies:
+```
+npm install
+```
+
+Then run the dev server which brings up a browser window serving the user interface:
+```
+npm start
+```
+
+#### Testing
+
+Run this command to test once:
+```
+npm test
+```
+
+Alternatively, run this command to test continually while monitoring for changes:
+```
+npm test -- --watch
+```
+
+#### Building
+
+Before building the app, edit line 3 in `viscoll-app/src/store/axiosConfig.js` to contain the correct root endpoint of the VisCodex API:
+
+```Javascript
+export let API_URL = '/api';
+
+```
+
+Build the app with:
+```
+npm build
+```
+
+
+
+## Copyright and License
+
+Copyright 2020 University of Toronto Libraries
+
+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
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
\ No newline at end of file
diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js
deleted file mode 100644
index b16e53d6..00000000
--- a/app/assets/config/manifest.js
+++ /dev/null
@@ -1,3 +0,0 @@
-//= link_tree ../images
-//= link_directory ../javascripts .js
-//= link_directory ../stylesheets .css
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
deleted file mode 100644
index fb35a858..00000000
--- a/app/assets/javascripts/application.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// This is a manifest file that'll be compiled into application.js, which will include all the files
-// listed below.
-//
-// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
-// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
-//
-// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// compiled file. JavaScript code in this file should be added after the last require_* statement.
-//
-// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
-// about supported directives.
-//
-//= require jquery
-//= require jquery_ujs
-//= require bootstrap-sprockets
-//= require_tree .
diff --git a/app/assets/javascripts/cable.js b/app/assets/javascripts/cable.js
deleted file mode 100644
index 71ee1e66..00000000
--- a/app/assets/javascripts/cable.js
+++ /dev/null
@@ -1,13 +0,0 @@
-// Action Cable provides the framework to deal with WebSockets in Rails.
-// You can generate new channels where WebSocket features live using the rails generate channel command.
-//
-//= require action_cable
-//= require_self
-//= require_tree ./channels
-
-(function() {
- this.App || (this.App = {});
-
- App.cable = ActionCable.createConsumer();
-
-}).call(this);
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
deleted file mode 100644
index 96890d08..00000000
--- a/app/assets/stylesheets/application.scss
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * This is a manifest file that'll be compiled into application.css, which will include all the files
- * listed below.
- *
- * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
- * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
- *
- * You're free to add application-wide styles to this file and they'll appear at the bottom of the
- * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
- * files in this directory. Styles in this file should be added after the last require_* statement.
- * It is generally better to create a new file per style scope.
- *
- *= require_self
- *= require_tree .
- */
-
-@import "bootstrap-sprockets";
-@import "bootstrap";
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
deleted file mode 100644
index 1c07694e..00000000
--- a/app/controllers/application_controller.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class ApplicationController < ActionController::Base
- protect_from_forgery with: :exception
-end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
deleted file mode 100644
index de6be794..00000000
--- a/app/helpers/application_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module ApplicationHelper
-end
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
deleted file mode 100644
index a69ba30d..00000000
--- a/app/views/layouts/application.html.erb
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
- ViscollObns
- <%= csrf_meta_tags %>
-
- <%= stylesheet_link_tag 'application', media: 'all' %>
- <%= javascript_include_tag 'application' %>
-
-
-
- <%= yield %>
-
-
diff --git a/bin/rake b/bin/rake
deleted file mode 100755
index 17240489..00000000
--- a/bin/rake
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env ruby
-require_relative '../config/boot'
-require 'rake'
-Rake.application.run
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
deleted file mode 100644
index 01ef3e66..00000000
--- a/config/initializers/assets.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-# Version of your assets, change this if you want to expire all your assets.
-Rails.application.config.assets.version = '1.0'
-
-# Add additional assets to the asset load path
-# Rails.application.config.assets.paths << Emoji.images_path
-
-# Precompile additional assets.
-# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
-# Rails.application.config.assets.precompile += %w( search.js )
diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb
deleted file mode 100644
index 5a6a32d3..00000000
--- a/config/initializers/cookies_serializer.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-# Specify a serializer for the signed and encrypted cookie jars.
-# Valid options are :json, :marshal, and :hybrid.
-Rails.application.config.action_dispatch.cookies_serializer = :json
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
deleted file mode 100644
index 08f5a991..00000000
--- a/config/initializers/session_store.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-Rails.application.config.session_store :cookie_store, key: '_ViscollObns_session'
diff --git a/config/routes.rb b/config/routes.rb
deleted file mode 100644
index 787824f8..00000000
--- a/config/routes.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-Rails.application.routes.draw do
- # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
-end
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..9debd861
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,50 @@
+version: '3.7'
+
+services:
+ api:
+ build: .
+ image: viscoll
+ container_name: viscoll-api
+ volumes:
+ - ./viscoll-api:/app
+ working_dir: /app
+ command: bash -c "rm -f tmp/pids/server.pid && bundle i && bundle exec rails s -p 3001 -b '0.0.0.0'"
+ ports:
+ - 3001:3001
+ depends_on:
+ - mongo
+ - mongo-express
+
+ app:
+ build: .
+ image: viscoll
+ container_name: viscoll-app
+ volumes:
+ - ./viscoll-app:/app
+ working_dir: /app
+ command: bash -c "npm install && npm start"
+ ports:
+ - 3000:3000
+ depends_on:
+ - api
+
+ mongo:
+ container_name: viscoll-mongo
+ image: mongo:4.0
+ volumes:
+ - mongo:/data/db
+
+ mongo-express:
+ image: mongo-express
+ container_name: viscoll-mongo-express
+ ports:
+ - 127.0.0.1:3002:8081
+ depends_on:
+ - mongo
+ environment:
+ ME_CONFIG_MONGODB_SERVER: mongo
+ depends_on:
+ - mongo
+
+volumes:
+ mongo:
diff --git a/lib/tasks/.keep b/lib/tasks/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/log/.keep b/log/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/public/404.html b/public/404.html
deleted file mode 100644
index b612547f..00000000
--- a/public/404.html
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
- The page you were looking for doesn't exist (404)
-
-
-
-
-
-
-
-
-
The page you were looking for doesn't exist.
-
You may have mistyped the address or the page may have moved.
-
-
If you are the application owner check the logs for more information.
-
-
-
diff --git a/public/422.html b/public/422.html
deleted file mode 100644
index a21f82b3..00000000
--- a/public/422.html
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
- The change you wanted was rejected (422)
-
-
-
-
-
-
-
-
-
The change you wanted was rejected.
-
Maybe you tried to change something you didn't have access to.
-
-
If you are the application owner check the logs for more information.
-
-
-
diff --git a/public/500.html b/public/500.html
deleted file mode 100644
index 061abc58..00000000
--- a/public/500.html
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
- We're sorry, but something went wrong (500)
-
-
-
-
-
-
-
-
-
We're sorry, but something went wrong.
-
-
If you are the application owner check the logs for more information.
-
-
-
diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png
deleted file mode 100644
index e69de29b..00000000
diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png
deleted file mode 100644
index e69de29b..00000000
diff --git a/public/favicon.ico b/public/favicon.ico
deleted file mode 100644
index e69de29b..00000000
diff --git a/public/robots.txt b/public/robots.txt
deleted file mode 100644
index 3c9c7c01..00000000
--- a/public/robots.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
-#
-# To ban all spiders from the entire site uncomment the next two lines:
-# User-agent: *
-# Disallow: /
diff --git a/tmp/.keep b/tmp/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/vendor/assets/javascripts/.keep b/vendor/assets/javascripts/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/vendor/assets/stylesheets/.keep b/vendor/assets/stylesheets/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/.rspec b/viscoll-api/.rspec
similarity index 56%
rename from .rspec
rename to viscoll-api/.rspec
index 83e16f80..4e33a322 100644
--- a/.rspec
+++ b/viscoll-api/.rspec
@@ -1,2 +1,3 @@
---color
--require spec_helper
+--color
+--format documentation
diff --git a/Gemfile b/viscoll-api/Gemfile
similarity index 56%
rename from Gemfile
rename to viscoll-api/Gemfile
index 6325e251..6da243cd 100644
--- a/Gemfile
+++ b/viscoll-api/Gemfile
@@ -7,26 +7,15 @@ end
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
-gem 'rails', '~> 5.0.2'
+gem 'rails', '5.2.5.0'
# Use Puma as the app server
gem 'puma', '~> 3.0'
-# Use SCSS for stylesheets
-gem 'sass-rails', '~> 5.0'
-# Use Uglifier as compressor for JavaScript assets
-gem 'uglifier', '>= 1.3.0'
-# Use CoffeeScript for .coffee assets and views
-# gem 'coffee-rails', '~> 4.2'
-# See https://github.com/rails/execjs#readme for more supported runtimes
-# gem 'therubyracer', platforms: :ruby
-
-# Use jquery as the JavaScript library
-gem 'jquery-rails'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
-gem 'jbuilder', '~> 2.5'
+gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password
-gem 'bcrypt', '~> 3.1.7'
+# gem 'bcrypt', '~> 3.1.7'
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
@@ -34,14 +23,19 @@ gem 'bcrypt', '~> 3.1.7'
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platform: :mri
- gem 'factory_girl_rails'
- gem 'rspec-rails'
- gem 'faker'
+ gem 'rspec-rails', '~> 3.8.2'
+ gem 'factory_girl_rails', '~> 4.8'
+ gem 'shoulda-matchers', '~> 3.1', '>= 3.1.1'
+ gem 'faker', '~> 1.7', '>= 1.7.3'
+ gem 'database_cleaner', '~> 1.6', '>= 1.6.1'
+ gem 'simplecov', :require => false
+ gem 'mongoid-rspec', github: 'mongoid-rspec/mongoid-rspec'
+ gem 'guard-rspec'
+ gem 'rspec_junit_formatter', '~> 0.3.0'
+ gem 'webmock', '~> 3.1.0'
end
group :development do
- # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
- gem 'web-console', '>= 3.3.0'
gem 'listen', '~> 3.0.5'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
@@ -51,18 +45,10 @@ end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
+gem 'mongoid', '~> 6.2'
+gem 'rails_jwt_auth', '0.16.1'
+gem "shrine"
+gem 'rubyzip', '1.3.0'
-gem 'devise'
-gem 'mongoid'
-gem 'bootstrap-sass'
-
-
-group :test do
- gem 'rspec'
- gem 'mongoid-rspec'
- gem 'capybara'
- gem 'cucumber-rails', require: false
- gem 'database_cleaner'
- gem 'email_spec'
- gem 'launchy'
-end
+# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
+gem 'rack-cors', '1.1.1'
diff --git a/viscoll-api/Gemfile.lock b/viscoll-api/Gemfile.lock
new file mode 100644
index 00000000..49edf36c
--- /dev/null
+++ b/viscoll-api/Gemfile.lock
@@ -0,0 +1,272 @@
+GIT
+ remote: https://github.com/mongoid-rspec/mongoid-rspec.git
+ revision: fbbed8f9b63f8479ca5983835e20080e95ddc9db
+ specs:
+ mongoid-rspec (4.1.1)
+ activesupport (>= 3.0.0)
+ mongoid (>= 3.1)
+ mongoid-compatibility (>= 0.5.1)
+ rspec-core (~> 3.3)
+ rspec-expectations (~> 3.3)
+ rspec-mocks (~> 3.3)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ actioncable (5.2.5)
+ actionpack (= 5.2.5)
+ nio4r (~> 2.0)
+ websocket-driver (>= 0.6.1)
+ actionmailer (5.2.5)
+ actionpack (= 5.2.5)
+ actionview (= 5.2.5)
+ activejob (= 5.2.5)
+ mail (~> 2.5, >= 2.5.4)
+ rails-dom-testing (~> 2.0)
+ actionpack (5.2.5)
+ actionview (= 5.2.5)
+ activesupport (= 5.2.5)
+ rack (~> 2.0, >= 2.0.8)
+ rack-test (>= 0.6.3)
+ rails-dom-testing (~> 2.0)
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
+ actionview (5.2.5)
+ activesupport (= 5.2.5)
+ builder (~> 3.1)
+ erubi (~> 1.4)
+ rails-dom-testing (~> 2.0)
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
+ activejob (5.2.5)
+ activesupport (= 5.2.5)
+ globalid (>= 0.3.6)
+ activemodel (5.2.5)
+ activesupport (= 5.2.5)
+ activerecord (5.2.5)
+ activemodel (= 5.2.5)
+ activesupport (= 5.2.5)
+ arel (>= 9.0)
+ activestorage (5.2.5)
+ actionpack (= 5.2.5)
+ activerecord (= 5.2.5)
+ marcel (~> 1.0.0)
+ activesupport (5.2.5)
+ concurrent-ruby (~> 1.0, >= 1.0.2)
+ i18n (>= 0.7, < 2)
+ minitest (~> 5.1)
+ tzinfo (~> 1.1)
+ addressable (2.7.0)
+ public_suffix (>= 2.0.2, < 5.0)
+ arel (9.0.0)
+ bcrypt (3.1.16)
+ bson (4.12.0)
+ builder (3.2.4)
+ byebug (11.1.3)
+ coderay (1.1.3)
+ concurrent-ruby (1.1.8)
+ content_disposition (1.0.0)
+ crack (0.4.5)
+ rexml
+ crass (1.0.6)
+ database_cleaner (1.99.0)
+ diff-lcs (1.4.4)
+ docile (1.3.5)
+ down (5.2.1)
+ addressable (~> 2.5)
+ erubi (1.10.0)
+ factory_girl (4.9.0)
+ activesupport (>= 3.0.0)
+ factory_girl_rails (4.9.0)
+ factory_girl (~> 4.9.0)
+ railties (>= 3.0.0)
+ faker (1.9.6)
+ i18n (>= 0.7)
+ ffi (1.15.0)
+ formatador (0.2.5)
+ globalid (0.4.2)
+ activesupport (>= 4.2.0)
+ guard (2.16.2)
+ formatador (>= 0.2.4)
+ listen (>= 2.7, < 4.0)
+ lumberjack (>= 1.0.12, < 2.0)
+ nenv (~> 0.1)
+ notiffany (~> 0.0)
+ pry (>= 0.9.12)
+ shellany (~> 0.0)
+ thor (>= 0.18.1)
+ guard-compat (1.2.1)
+ guard-rspec (4.7.3)
+ guard (~> 2.1)
+ guard-compat (~> 1.1)
+ rspec (>= 2.99.0, < 4.0)
+ hashdiff (1.0.1)
+ i18n (1.8.10)
+ concurrent-ruby (~> 1.0)
+ jbuilder (2.11.2)
+ activesupport (>= 5.0.0)
+ jwt (1.5.6)
+ listen (3.0.8)
+ rb-fsevent (~> 0.9, >= 0.9.4)
+ rb-inotify (~> 0.9, >= 0.9.7)
+ loofah (2.9.1)
+ crass (~> 1.0.2)
+ nokogiri (>= 1.5.9)
+ lumberjack (1.2.8)
+ mail (2.7.1)
+ mini_mime (>= 0.1.1)
+ marcel (1.0.1)
+ method_source (1.0.0)
+ mini_mime (1.1.0)
+ mini_portile2 (2.5.1)
+ minitest (5.14.4)
+ mongo (2.14.0)
+ bson (>= 4.8.2, < 5.0.0)
+ mongoid (6.4.8)
+ activemodel (>= 5.1, < 6.0.0)
+ mongo (>= 2.5.1, < 3.0.0)
+ mongoid-compatibility (0.5.1)
+ activesupport
+ mongoid (>= 2.0)
+ nenv (0.3.0)
+ nio4r (2.5.7)
+ nokogiri (1.11.3)
+ mini_portile2 (~> 2.5.0)
+ racc (~> 1.4)
+ notiffany (0.1.3)
+ nenv (~> 0.1)
+ shellany (~> 0.0)
+ pry (0.14.1)
+ coderay (~> 1.1)
+ method_source (~> 1.0)
+ public_suffix (4.0.6)
+ puma (3.12.6)
+ racc (1.5.2)
+ rack (2.2.3)
+ rack-cors (1.1.1)
+ rack (>= 2.0.0)
+ rack-test (1.1.0)
+ rack (>= 1.0, < 3)
+ rails (5.2.5)
+ actioncable (= 5.2.5)
+ actionmailer (= 5.2.5)
+ actionpack (= 5.2.5)
+ actionview (= 5.2.5)
+ activejob (= 5.2.5)
+ activemodel (= 5.2.5)
+ activerecord (= 5.2.5)
+ activestorage (= 5.2.5)
+ activesupport (= 5.2.5)
+ bundler (>= 1.3.0)
+ railties (= 5.2.5)
+ sprockets-rails (>= 2.0.0)
+ rails-dom-testing (2.0.3)
+ activesupport (>= 4.2.0)
+ nokogiri (>= 1.6)
+ rails-html-sanitizer (1.3.0)
+ loofah (~> 2.3)
+ rails_jwt_auth (0.16.1)
+ bcrypt (~> 3.1)
+ jwt (~> 1.5)
+ rails (~> 5.0)
+ warden (~> 1.2)
+ railties (5.2.5)
+ actionpack (= 5.2.5)
+ activesupport (= 5.2.5)
+ method_source
+ rake (>= 0.8.7)
+ thor (>= 0.19.0, < 2.0)
+ rake (13.0.3)
+ rb-fsevent (0.10.4)
+ rb-inotify (0.10.1)
+ ffi (~> 1.0)
+ rexml (3.2.5)
+ rspec (3.8.0)
+ rspec-core (~> 3.8.0)
+ rspec-expectations (~> 3.8.0)
+ rspec-mocks (~> 3.8.0)
+ rspec-core (3.8.2)
+ rspec-support (~> 3.8.0)
+ rspec-expectations (3.8.6)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.8.0)
+ rspec-mocks (3.8.2)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.8.0)
+ rspec-rails (3.8.3)
+ actionpack (>= 3.0)
+ activesupport (>= 3.0)
+ railties (>= 3.0)
+ rspec-core (~> 3.8.0)
+ rspec-expectations (~> 3.8.0)
+ rspec-mocks (~> 3.8.0)
+ rspec-support (~> 3.8.0)
+ rspec-support (3.8.3)
+ rspec_junit_formatter (0.3.0)
+ rspec-core (>= 2, < 4, != 2.12.0)
+ rubyzip (1.3.0)
+ shellany (0.0.1)
+ shoulda-matchers (3.1.3)
+ activesupport (>= 4.0.0)
+ shrine (3.3.0)
+ content_disposition (~> 1.0)
+ down (~> 5.1)
+ simplecov (0.21.2)
+ docile (~> 1.1)
+ simplecov-html (~> 0.11)
+ simplecov_json_formatter (~> 0.1)
+ simplecov-html (0.12.3)
+ simplecov_json_formatter (0.1.3)
+ spring (2.1.1)
+ spring-watcher-listen (2.0.1)
+ listen (>= 2.7, < 4.0)
+ spring (>= 1.2, < 3.0)
+ sprockets (4.0.2)
+ concurrent-ruby (~> 1.0)
+ rack (> 1, < 3)
+ sprockets-rails (3.2.2)
+ actionpack (>= 4.0)
+ activesupport (>= 4.0)
+ sprockets (>= 3.0.0)
+ thor (1.1.0)
+ thread_safe (0.3.6)
+ tzinfo (1.2.9)
+ thread_safe (~> 0.1)
+ warden (1.2.9)
+ rack (>= 2.0.9)
+ webmock (3.1.1)
+ addressable (>= 2.3.6)
+ crack (>= 0.3.2)
+ hashdiff
+ websocket-driver (0.7.3)
+ websocket-extensions (>= 0.1.0)
+ websocket-extensions (0.1.5)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ byebug
+ database_cleaner (~> 1.6, >= 1.6.1)
+ factory_girl_rails (~> 4.8)
+ faker (~> 1.7, >= 1.7.3)
+ guard-rspec
+ jbuilder (~> 2.7)
+ listen (~> 3.0.5)
+ mongoid (~> 6.2)
+ mongoid-rspec!
+ puma (~> 3.0)
+ rack-cors (= 1.1.1)
+ rails (= 5.2.5.0)
+ rails_jwt_auth (= 0.16.1)
+ rspec-rails (~> 3.8.2)
+ rspec_junit_formatter (~> 0.3.0)
+ rubyzip (= 1.3.0)
+ shoulda-matchers (~> 3.1, >= 3.1.1)
+ shrine
+ simplecov
+ spring
+ spring-watcher-listen (~> 2.0.0)
+ tzinfo-data
+ webmock (~> 3.1.0)
+
+BUNDLED WITH
+ 2.1.4
diff --git a/viscoll-api/Guardfile b/viscoll-api/Guardfile
new file mode 100644
index 00000000..08a70b17
--- /dev/null
+++ b/viscoll-api/Guardfile
@@ -0,0 +1,14 @@
+guard :rspec, cmd: "bundle exec rspec" do
+ require "guard/rspec/dsl"
+ dsl = Guard::RSpec::Dsl.new(self)
+
+ # RSpec files
+ rspec = dsl.rspec
+ watch(rspec.spec_files)
+
+ # Rails files
+ watch(%r{^app/controllers/*}) { rspec.spec_dir }
+ watch(%r{^app/models/*}) { rspec.spec_dir }
+ watch(%r{^app/views/*}) { rspec.spec_dir }
+ watch(%r{^app/config/*}) { rspec.spec_dir }
+end
diff --git a/viscoll-api/README.md b/viscoll-api/README.md
new file mode 100644
index 00000000..5c6dfa07
--- /dev/null
+++ b/viscoll-api/README.md
@@ -0,0 +1,58 @@
+# VisColl (Rails API Back-End)
+
+## Introduction
+
+This is the the Rails-driven back-end for Viscoll.
+
+## System Requirements
+
+- `rvm` (>= 1.29.1)
+- `ruby` (>= 2.4.1)
+
+### Additional Requirements for Development:
+
+- [`mailcatcher`](https://mailcatcher.me/) (>= 0.6.5)
+
+## Setup
+
+Run the following commands to install the dependencies:
+```
+rvm --ruby-version use 2.4.1@viscollobns
+bundle install
+```
+
+Set the admin email address in two locations:
+
+`viscoll-api/app/mailers/mailer.rb` on line 18:
+
+```
+toEmail = Rails.application.secrets.admin_email || "dummy-admin@library.utoronto.ca"
+```
+
+and `viscoll-api/app/mailers/feedback_mailer.rb` on line 10:
+
+```
+to:"utlviscoll@library.utoronto.ca",
+```
+
+Then run this to start the API server:
+```
+rails s -p 3001
+```
+
+If you wish to receive confirmation and password reset emails while developing, also start the mailcatcher daemon:
+```
+mailcatcher
+```
+
+## Testing
+
+Run this command to test once:
+```
+rspec
+```
+
+Alternatively, run this command to test continually while monitoring for changes:
+```
+guard
+```
diff --git a/Rakefile b/viscoll-api/Rakefile
similarity index 100%
rename from Rakefile
rename to viscoll-api/Rakefile
diff --git a/app/channels/application_cable/channel.rb b/viscoll-api/app/channels/application_cable/channel.rb
similarity index 100%
rename from app/channels/application_cable/channel.rb
rename to viscoll-api/app/channels/application_cable/channel.rb
diff --git a/app/channels/application_cable/connection.rb b/viscoll-api/app/channels/application_cable/connection.rb
similarity index 100%
rename from app/channels/application_cable/connection.rb
rename to viscoll-api/app/channels/application_cable/connection.rb
diff --git a/viscoll-api/app/controllers/application_controller.rb b/viscoll-api/app/controllers/application_controller.rb
new file mode 100644
index 00000000..553e9438
--- /dev/null
+++ b/viscoll-api/app/controllers/application_controller.rb
@@ -0,0 +1,19 @@
+class ApplicationController < ActionController::API
+ before_action :set_base_api_url
+ def set_base_api_url
+ @base_api_url = Rails.application.secrets.api_url ? Rails.application.secrets.api_url : 'https://dummy.library.utoronto.ca/api'
+ end
+
+ include RailsJwtAuth::WardenHelper
+ include ControllerHelper::ProjectsHelper
+ include ControllerHelper::GroupsHelper
+ include ControllerHelper::LeafsHelper
+ include ControllerHelper::FilterHelper
+ include ControllerHelper::ImportJsonHelper
+ include ControllerHelper::ImportXmlHelper
+ include ControllerHelper::ImportMappingHelper
+ include ControllerHelper::ExportHelper
+ include ValidationHelper::ProjectValidationHelper
+ include ValidationHelper::GroupValidationHelper
+ include ValidationHelper::LeafValidationHelper
+end
diff --git a/app/assets/images/.keep b/viscoll-api/app/controllers/concerns/.keep
similarity index 100%
rename from app/assets/images/.keep
rename to viscoll-api/app/controllers/concerns/.keep
diff --git a/viscoll-api/app/controllers/concerns/rails_jwt_auth/warden_helper.rb b/viscoll-api/app/controllers/concerns/rails_jwt_auth/warden_helper.rb
new file mode 100644
index 00000000..9b4b40e9
--- /dev/null
+++ b/viscoll-api/app/controllers/concerns/rails_jwt_auth/warden_helper.rb
@@ -0,0 +1,36 @@
+module RailsJwtAuth
+ module WardenHelper
+ def signed_in?
+ !current_user.nil?
+ end
+
+ def current_user
+ warden.user
+ end
+
+ def warden
+ request.env['warden']
+ end
+
+ def authenticate!
+ begin
+ warden.authenticate!(store: false)
+ rescue Exception => e
+ render json: {error: "Authorization Token: "+e.message}, status: :bad_request
+ return false
+ end
+ end
+
+ def authenticateDestroy!
+ warden.authenticate!(store: false)
+ end
+
+ def self.included(base)
+ return unless Rails.env.test? && base.name == 'ApplicationController'
+
+ base.send(:rescue_from, RailsJwtAuth::Spec::NotAuthorized) do
+ render json: {}, status: 401
+ end
+ end
+ end
+end
diff --git a/viscoll-api/app/controllers/confirmations_controller.rb b/viscoll-api/app/controllers/confirmations_controller.rb
new file mode 100644
index 00000000..4bf38439
--- /dev/null
+++ b/viscoll-api/app/controllers/confirmations_controller.rb
@@ -0,0 +1,24 @@
+class ConfirmationsController < ApplicationController
+ def update
+ if params[:confirmation_token].blank?
+ return render_422(confirmation_token: [I18n.t('rails_jwt_auth.errors.not_found')])
+ end
+ user = RailsJwtAuth.model.where(confirmation_token: params[:confirmation_token]).first
+ return render_422(confirmation_token: [I18n.t('rails_jwt_auth.errors.not_found')]) unless user
+ if user.confirm!
+ AccountApprovalMailer.sendApprovalStatus(user).deliver_now
+ render_204
+ else
+ render_422(user.errors)
+ end
+ end
+
+ def render_204
+ render json: {}, status: 204
+ end
+
+ def render_422(errors)
+ render json: {errors: errors}, status: 422
+ end
+
+end
diff --git a/viscoll-api/app/controllers/export_controller.rb b/viscoll-api/app/controllers/export_controller.rb
new file mode 100644
index 00000000..1c2aea4a
--- /dev/null
+++ b/viscoll-api/app/controllers/export_controller.rb
@@ -0,0 +1,70 @@
+require 'zip'
+
+class ExportController < ApplicationController
+ before_action :authenticate!
+ before_action :set_project, only: [:show]
+
+ # GET /projects/:id/export/:format
+ def show
+ # Zip all DIY images and provide the link to download the file
+ begin
+ @zipFilePath = nil
+ images = []
+ current_user.images.all.each do |image|
+ if image.projectIDs.include? @project.id.to_s
+ images.push(image)
+ end
+ end
+ if !images.empty?
+ basePath = "#{Rails.root}/public/uploads/"
+ zipFilename = "#{basePath}#{@project.id.to_s}_images.zip"
+ File.delete(zipFilename) if File.exist?(zipFilename)
+ ::Zip::File.open(zipFilename, Zip::File::CREATE) do |zipFile|
+ images.each do |image|
+ fileExtension = image.metadata['mime_type'].split('/')[1]
+ filenameOnly = image.filename.rpartition(".")[0]
+ zipFile.add("#{filenameOnly}_#{image.fileID}.#{fileExtension}", "#{basePath}#{image.fileID}")
+ end
+ end
+ @zipFilePath = "#{@base_api_url}/images/zip/#{@project.id.to_s}"
+ end
+ rescue Exception => e
+ end
+
+ begin
+ case @format
+ when "xml"
+ exportData = buildDotModel(@project)
+ xml = Nokogiri::XML(exportData)
+ schema = Nokogiri::XML::RelaxNG(File.open("public/viscoll-datamodel2.rng"))
+ errors = schema.validate(xml)
+ if errors.empty?
+ render json: {data: exportData, type: @format, Images: {exportedImages:@zipFilePath ? @zipFilePath : false}}, status: :ok and return
+ else
+ render json: {data: errors, type: @format}, status: :unprocessable_entity and return
+ end
+ when "json"
+ @data = buildJSON(@project)
+ render :'exports/show', status: :ok and return
+ else
+ render json: {error: "Export format must be one of [json, xml]"}, status: :unprocessable_entity and return
+ end
+ rescue Exception => e
+ render json: {error: e.message}, status: :internal_server_error and return
+ end
+ end
+
+ private
+ def set_project
+ begin
+ @project = Project.find(params[:id])
+ if (@project.user_id!=current_user.id)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ @format = params[:format]
+ rescue Exception => e
+ render json: {error: "project not found with id "+params[:id]}, status: :not_found and return
+ end
+ end
+
+end
diff --git a/viscoll-api/app/controllers/feedback_controller.rb b/viscoll-api/app/controllers/feedback_controller.rb
new file mode 100644
index 00000000..64a0badb
--- /dev/null
+++ b/viscoll-api/app/controllers/feedback_controller.rb
@@ -0,0 +1,34 @@
+class FeedbackController < ApplicationController
+ before_action :authenticate!
+
+ # POST /feedback
+ def create
+ begin
+ if not current_user
+ render json: {}, status: :unprocessable_entity and return
+ end
+ @title = feedback_params[:title]
+ @message = feedback_params[:message]
+ @browserInformation = feedback_params[:browserInformation]
+ @projectJSONExport = feedback_params[:project]
+ if @title.blank? or @message.blank?
+ render json: {error: "[title] and [message] params required."}, status: :unprocessable_entity and return
+ end
+ FeedbackMailer.sendFeedback(
+ @title,
+ @message,
+ @browserInformation,
+ @projectJSONExport,
+ current_user
+ ).deliver_now
+ render json: {}, status: :ok and return
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+ private
+ def feedback_params
+ params.require(:feedback).permit(:title, :message, :browserInformation, :project)
+ end
+end
diff --git a/viscoll-api/app/controllers/filter_controller.rb b/viscoll-api/app/controllers/filter_controller.rb
new file mode 100644
index 00000000..f87fdc38
--- /dev/null
+++ b/viscoll-api/app/controllers/filter_controller.rb
@@ -0,0 +1,219 @@
+class FilterController < ApplicationController
+ before_action :authenticate!
+ before_action :set_project, only: [:show]
+
+ # PUT /projects/filter
+ def show
+ begin
+ queries = filter_params.to_h[:queries]
+ errors = runValidations(queries)
+ if errors != []
+ render json: {errors: errors}, status: :unprocessable_entity and return
+ end
+ @objectIDs = {Groups: [], Leafs: [], Sides: [], Notes: []}
+ @visibleAttributes = {
+ group: {type:false, title:false},
+ leaf: {type:false, material:false, conjoined_leaf_order:false, attached_below:false, attached_above:false, stub:false},
+ side: {folio_number:false, texture:false, script_direction:false, uri:false}
+ }
+ combinedResult = performFilter(queries)
+ finalResponse = buildResponse(combinedResult)
+ @groups = finalResponse[:Groups]
+ @leafs = finalResponse[:Leafs]
+ @sides = finalResponse[:Sides]
+ @notes = finalResponse[:Notes]
+ @groupsOfMatchingLeafs = finalResponse[:GroupsOfMatchingLeafs]
+ @leafsOfMatchingSides = finalResponse[:LeafsOfMatchingSides]
+ @groupsOfMatchingSides = finalResponse[:GroupsOfMatchingSides]
+ @groupsOfMatchingNotes = finalResponse[:GroupsOfMatchingNotes]
+ @leafsOfMatchingNotes = finalResponse[:LeafsOfMatchingNotes]
+ @sidesOfMatchingNotes = finalResponse[:SidesOfMatchingNotes]
+ if @groups == []
+ @visibleAttributes[:group] = {type:false, title:false}
+ end
+ if @leafs == []
+ @visibleAttributes[:leaf] = {type:false, material:false, conjoined_leaf_order:false, attached_below:false, attached_above:false, stub:false}
+ end
+ if @sides == []
+ @visibleAttributes[:side] = {folio_number:false, texture:false, script_direction:false, uri:false}
+ end
+ rescue Exception => e
+ render json: {errors: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+
+
+ def performFilter(queries)
+ sets = []
+ conjunctions = []
+ queries.each do |query|
+ type = query[:type]
+ old_attribute = nil
+ attribute = query[:attribute]
+ condition = query[:condition]
+ values = query[:values]
+ conjunction = query[:conjunction]
+ groups = []
+ leafs = []
+ sides = []
+ notes = []
+
+ if attribute == 'conjoined_leaf_order'
+ old_attribute = attribute
+ attribute = 'conjoined_to'
+ values = values.map { |val| val=="None" ? nil : val }
+ end
+ if attribute == 'conjoined_to'
+ values = values.map { |val| val=="None" ? nil : val }
+ end
+
+ query_condition_params = { attribute => { '$in': [] } }
+
+ case condition
+ when 'equals'
+ query_condition_params = { attribute => (values.length > 1) ? { '$in': values } : values[0] }
+ when 'not equals'
+ query_condition_params = { attribute => (values.length > 1) ? { '$nin': values } : { '$ne': values[0] } }
+ when 'contains'
+ query_condition_params = { attribute => (values.length > 1) ? { '$in': values.map { |x| /^#{Regexp.escape(x)}/} } : /#{Regexp.escape(values[0])}/ }
+ when 'not contains'
+ query_condition_params = { attribute => (values.length > 1) ? { '$nin': values.map { |x| /^#{Regexp.escape(x)}/} } : { '$not': /#{Regexp.escape(values[0])}/} }
+ end
+
+ case type
+ when 'group'
+ groupQueryResult = @project.groups.only(:id).where(query_condition_params)
+ groups = groupQueryResult.collect { |gqr| gqr.id.to_s }
+ @objectIDs[:Groups] += groups
+ if groups.length > 0
+ @visibleAttributes[:group][attribute] = true
+ end
+ when 'leaf'
+ leafQueryResult = @project.leafs.only(:id).where(query_condition_params)
+ leafs = leafQueryResult.collect { |lqr| lqr.id.to_s }
+ if leafs.length > 0
+ if old_attribute
+ @visibleAttributes[:leaf][old_attribute] = true
+ else
+ @visibleAttributes[:leaf][attribute] = true
+ end
+ end
+ @objectIDs[:Leafs] += leafs
+ when 'side'
+ sideQueryResult = @project.sides.only(:id).where(query_condition_params)
+ sides = sideQueryResult.collect { |sqr| sqr.id.to_s }
+ sideQueryResult.each do |sideID|
+ sides.push(sideID.id.to_s)
+ end
+ if sides.length > 0
+ @visibleAttributes[:side][attribute] = true
+ end
+ @objectIDs[:Sides] += sides
+ when 'note'
+ noteQueryResult = @project.notes.only(:id).where(query_condition_params)
+ notes = noteQueryResult.collect { |nqr| nqr.id.to_s }
+ @objectIDs[:Notes] += notes
+ end
+ sets.push(Set.new([*groups, *leafs, *sides, *notes]))
+ conjunctions.push(conjunction)
+ end
+ conjunctions.pop
+ result = sets[0]
+ conjunctions.each_with_index do |conjunction, index|
+ if (index+1 <= sets.length-1)
+ if conjunction == "AND"
+ result = result & sets[index+1]
+ else
+ result = result | sets[index+1]
+ end
+ end
+ end
+ return result
+ end
+
+
+ def buildResponse(combinedResult)
+ response = {Groups: [], Leafs: [], Sides: [], Notes: [], GroupsOfMatchingNotes: [], LeafsOfMatchingNotes: [], SidesOfMatchingNotes:[], LeafsOfMatchingSides:[], GroupsOfMatchingSides:[], GroupsOfMatchingLeafs:[]}
+ combinedResult.each do |objectID|
+ if @objectIDs[:Groups].include?(objectID)
+ response[:Groups].push(objectID)
+ elsif @objectIDs[:Leafs].include?(objectID)
+ response[:Leafs].push(objectID)
+ elsif @objectIDs[:Sides].include?(objectID)
+ response[:Sides].push(objectID)
+ elsif @objectIDs[:Notes].include?(objectID)
+ note = Note.find(objectID)
+ groupIDs = note.objects[:Group]
+ leafIDs = note.objects[:Leaf]
+ rectoIDs = note.objects[:Recto]
+ versoIDs = note.objects[:Verso]
+ groupIDs.each do |groupID|
+ if !(response[:Groups].include?(groupID))
+ response[:Groups].push(groupID)
+ response[:GroupsOfMatchingNotes].push(groupID)
+ end
+ end
+ leafIDs.each do |leafID|
+ if !(response[:Leafs].include?(leafID))
+ response[:Leafs].push(leafID)
+ response[:LeafsOfMatchingNotes].push(leafID)
+ end
+ end
+ rectoIDs.each do |sideID|
+ if !(response[:Sides].include?(sideID))
+ response[:Sides].push(sideID)
+ response[:SidesOfMatchingNotes].push(sideID)
+ end
+ end
+ versoIDs.each do |sideID|
+ if !(response[:Sides].include?(sideID))
+ response[:Sides].push(sideID)
+ response[:SidesOfMatchingNotes].push(sideID)
+ end
+ end
+ response[:Notes].push(objectID)
+ end
+ end
+ response[:Sides].each do |sideID|
+ leafID = Side.find(sideID).parentID
+ if (!(response[:LeafsOfMatchingSides].include?(leafID)) and !(@objectIDs[:Leafs].include?(leafID)))
+ response[:LeafsOfMatchingSides].push(leafID)
+ end
+ end
+ response[:LeafsOfMatchingSides].each do |leafID|
+ groupID = Leaf.find(leafID).parentID
+ if (!(response[:GroupsOfMatchingSides].include?(groupID)) and !(@objectIDs[:Groups].include?(groupID)))
+ response[:GroupsOfMatchingSides].push(groupID)
+ end
+ end
+ response[:Leafs].each do |leafID|
+ groupID = Leaf.find(leafID).parentID
+ if (!(response[:GroupsOfMatchingLeafs].include?(groupID)) and !(@objectIDs[:Groups].include?(groupID)))
+ response[:GroupsOfMatchingLeafs].push(groupID)
+ end
+ end
+ return response
+ end
+
+
+ private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_project
+ begin
+ @project = Project.find(params[:id])
+ if (@project.user_id!=current_user.id)
+ render status: :unauthorized and return
+ end
+ rescue Exception => e
+ render json: {error: "project not found with id "+params[:id]}, status: :not_found and return
+ end
+ end
+
+ # Never trust parameters from the scary internet, only allow the white list through.
+ def filter_params
+ params.permit(:queries => [:type, :attribute, :condition, :conjunction, :values => []])
+ end
+
+
+end
diff --git a/viscoll-api/app/controllers/groups_controller.rb b/viscoll-api/app/controllers/groups_controller.rb
new file mode 100644
index 00000000..01db43f4
--- /dev/null
+++ b/viscoll-api/app/controllers/groups_controller.rb
@@ -0,0 +1,186 @@
+class GroupsController < ApplicationController
+ before_action :authenticate!
+ before_action :set_group, only: [:update, :destroy]
+
+ # POST /groups
+ def create
+ begin
+ noOfGroups = additional_params.to_h[:noOfGroups]
+ memberOrder = additional_params.to_h[:memberOrder]
+ parentGroupID = additional_params.to_h[:parentGroupID]
+ noOfLeafs = additional_params.to_h[:noOfLeafs]
+ conjoin = additional_params.to_h[:conjoin]
+ oddMemberLeftOut = additional_params.to_h[:oddMemberLeftOut]
+ groupIDs = additional_params.to_h[:groupIDs]
+ leafIDs = additional_params.to_h[:leafIDs]
+ sideIDs = additional_params.to_h[:sideIDs]
+ project_id = group_params.to_h[:project_id]
+ order = additional_params.to_h[:order]
+ direction = group_params.to_h[:direction]
+ # Validate group parameters
+ @additionalErrors = validateAdditionalGroupParams(noOfGroups, parentGroupID, memberOrder, noOfLeafs, conjoin, oddMemberLeftOut)
+ hasAdditionalErrors = false
+ @additionalErrors.each_value do |value|
+ if value.length>0
+ hasAdditionalErrors = true
+ end
+ end
+ if (hasAdditionalErrors)
+ render json: {additional: @additionalErrors}, status: :unprocessable_entity and return
+ end
+ @groupErrors = {project_id: []}
+ if (project_id == nil)
+ @groupErrors[:project_id].push("not found")
+ render json: {group: @groupErrors}, status: :unprocessable_entity and return
+ end
+ begin
+ @project = Project.find(project_id)
+ rescue Exception => e
+ @groupErrors[:project_id].push("project not found with id "+project_id)
+ render json: {group: @groupErrors}, status: :unprocessable_entity and return
+ end
+ new_groups = []
+ new_group_ids = []
+ groupIDIndex = 0
+ parent_group = nil
+ if parentGroupID != nil
+ parent_group = @project.groups.find(parentGroupID)
+ end
+ # Create groups
+ noOfGroups.times do |i|
+ group = Group.new(group_params)
+ if groupIDs
+ group.id = groupIDs[i]
+ end
+ if parentGroupID != nil
+ group.parentID = parentGroupID
+ group.nestLevel = parent_group.nestLevel + 1
+ end
+ if group.save
+ new_groups.push(group)
+ new_group_ids.push(group.id.to_s)
+ else
+ render json: {group: group.errors}, status: :unprocessable_entity and return
+ end
+ end
+ # Add new group(s) to parent
+ if parentGroupID != nil
+ parent_group.add_members(new_group_ids, memberOrder)
+ end
+ # Add group(s) to global list
+ @project.add_groupIDs(new_group_ids, order.to_i - 1)
+ # Add leaves inside each new group
+ new_groups.each_with_index do |group, index|
+ if noOfLeafs
+ if (leafIDs and sideIDs)
+ addLeavesInside(project_id, group, noOfLeafs, conjoin, oddMemberLeftOut, leafIDs[index*noOfLeafs..index*noOfLeafs+noOfLeafs-1], sideIDs[index*2*noOfLeafs..index*2*noOfLeafs+noOfLeafs*2-1])
+ else
+ addLeavesInside(project_id, group, noOfLeafs, conjoin, oddMemberLeftOut)
+ end
+ end
+ end
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+ # PATCH/PUT /groups/1
+ def update
+ begin
+ if !@group.update(group_params)
+ render json: @group.errors, status: :unprocessable_entity and return
+ end
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+ # PATCH/PUT /groups
+ def updateMultiple
+ begin
+ allGroups = group_params_batch_update.to_h[:groups]
+ # Run validations
+ errors = validateGroupBatchUpdate(allGroups)
+ if not errors.empty?
+ render json: {groups: errors}, status: :unprocessable_entity and return
+ end
+ allGroups.each do |group_params|
+ @group = Group.find(group_params[:id])
+ @project = Project.find(@group.project_id)
+ if (@project.user_id!=current_user.id)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ if !@group.update(group_params[:attributes])
+ render json: @group.errors, status: :unprocessable_entity and return
+ end
+ end
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+ # DELETE /groups/1
+ def destroy
+ begin
+ @group = Group.find(params[:id])
+ @group.destroy
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+ # DELETE /groups
+ def destroyMultiple
+ begin
+ groupIDs = group_params_batch_delete.to_h[:groups]
+ projectID = group_params_batch_delete.to_h[:projectID]
+ # Delete groups
+ groupIDs.each do |groupID|
+ # Wrapping destroy in begin/rescue because group may no longer exist when it's nested
+ begin
+ group = Group.find(groupID)
+ @project = Project.find(group.project_id)
+ if (@project.user_id!=current_user.id)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ group.destroy
+ rescue Exception => e
+ next
+ end
+ end
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+
+ private
+ def set_group
+ begin
+ @group = Group.find(params[:id])
+ @project = Project.find(@group.project_id)
+ if (@project.user_id!=current_user.id)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ rescue Exception => e
+ render json: {error: "group not found"}, status: :not_found and return
+ end
+ end
+
+ def group_params
+ params.require(:group).permit(:project_id, :type, :title, :direction, :tacketed=>[], :sewing=>[])
+ end
+
+ def additional_params
+ params.require(:additional).permit(:order, :noOfGroups, :memberOrder, :parentGroupID, :noOfLeafs, :conjoin, :oddMemberLeftOut, :groupIDs=>[], :leafIDs=>[], :sideIDs=>[])
+ end
+
+ def group_params_batch_update
+ params.permit(:groups => [:id, :attributes=>[:type, :title, :direction, :tacketed=>[], :sewing=>[]]])
+ end
+
+ def group_params_batch_delete
+ params.permit(:projectID, :groups => [])
+ end
+
+end
diff --git a/viscoll-api/app/controllers/images_controller.rb b/viscoll-api/app/controllers/images_controller.rb
new file mode 100644
index 00000000..c561df05
--- /dev/null
+++ b/viscoll-api/app/controllers/images_controller.rb
@@ -0,0 +1,225 @@
+class ImagesController < ApplicationController
+ before_action :authenticate!, except: [:show, :getZipImages]
+
+ # POST /images
+ def uploadImages
+ begin
+ projectIDs = []
+ if image_create_params.to_h.key?("projectID")
+ @project = Project.find(image_create_params.to_h[:projectID])
+ if (@project.user_id!=current_user.id)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ projectIDs.push(@project.id.to_s)
+ end
+ rescue Mongoid::Errors::DocumentNotFound
+ render json: {error: "project not found with id #{params[:projectID]}"}, status: :not_found and return
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ newImages = []
+ allImages = image_create_params.to_h[:images]
+ allImages.each do |image_data|
+ filename = image_data[:filename].parameterize.underscore
+ extension = image_data[:content].split("image/").last.split(";base64").first
+ imageIO = Shrine.data_uri(image_data[:content])
+ uploader = Shrine.new(:store)
+ uploaded_file = uploader.upload(imageIO, metadata: {"filename"=>"#{filename}.#{extension}"})
+ image = Image.new(user: current_user, filename: "#{filename}.#{extension}", fileID: uploaded_file.id, metadata: uploaded_file.metadata, projectIDs: projectIDs)
+ if image.valid?
+ image.save
+ else
+ copyCounter = 1
+ while !image.save do
+ if image.errors.key?("filename") and image.errors[:filename][0].include?("Image with filename")
+ # Duplicate filename. Create Image with new filename+"_copy(copyCounter)"
+ filename = "#{image_data[:filename].parameterize.underscore}_copy(#{copyCounter})"
+ image = Image.new(user: current_user, filename: "#{filename}.#{extension}", fileID: uploaded_file.id, metadata: uploaded_file.metadata, projectIDs: projectIDs)
+ copyCounter += 1
+ else
+ image.destroy
+ render json: image.errors, status: :unprocessable_entity and return
+ end
+ end
+ end
+ newImages.push(image)
+ end
+ @projects = current_user.projects
+ @images = newImages
+ render :'projects/index', status: :ok and return
+ end
+
+ # GET /images/:imageID
+ def show
+ begin
+ # p params[:imageID_filename]
+ imageID = params[:imageID_filename].split("_", 2)[0]
+ filename = params[:imageID_filename].split("_", 2)[1]
+ @image = Image.find(imageID)
+ rescue Mongoid::Errors::DocumentNotFound
+ render json: {error: "image not found with id #{imageID}"}, status: :not_found and return
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ # Get image file
+ path = "#{Rails.root}/public/uploads/#{@image.fileID}"
+ File.open(path, 'rb') do |image|
+ send_file image, :type => @image.metadata['mime_type'], :disposition => 'inline'
+ end
+ end
+
+
+ # GET /images/zip/:imageID_projectID
+ def getZipImages
+ begin
+ projectID = params[:id]
+ zipFilePath = "#{Rails.root}/public/uploads/#{projectID}_images.zip"
+ send_file zipFilePath, :type => 'application/zip', :disposition => 'inline'
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+
+
+ # PUT/PATCH /images/link
+ def link
+ projectIDs = image_link_unlink_params.to_h[:projectIDs]
+ imageIDs = image_link_unlink_params.to_h[:imageIDs]
+ projects = []
+ projectIDs.each do |projectID|
+ begin
+ project = Project.find(projectID)
+ if (project.user_id!=current_user.id)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ projects.push(project)
+ rescue Mongoid::Errors::DocumentNotFound
+ render json: {error: "project not found with id #{projectID}"}, status: :not_found and return
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+ images = []
+ imageIDs.each do |imageID|
+ begin
+ image = Image.find(imageID)
+ if (image.user_id!=current_user.id)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ images.push(image)
+ rescue Mongoid::Errors::DocumentNotFound
+ render json: {error: "image not found with id #{imageID}"}, status: :not_found and return
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+ projects.each do |project|
+ images.each do |image|
+ if not image.projectIDs.include? project.id.to_s
+ image.projectIDs.push(project.id.to_s)
+ image.save
+ end
+ end
+ end
+ @projects = current_user.projects
+ @images = current_user.images
+ render :'projects/index', status: :ok and return
+ end
+
+
+ # PUT/PATCH /images/unlink
+ def unlink
+ projectIDs = image_link_unlink_params.to_h[:projectIDs]
+ imageIDs = image_link_unlink_params.to_h[:imageIDs]
+ projects = []
+ projectIDs.each do |projectID|
+ begin
+ project = Project.find(projectID)
+ if (project.user_id!=current_user.id)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ projects.push(project)
+ rescue Mongoid::Errors::DocumentNotFound
+ render json: {error: "project not found with id #{projectID}"}, status: :not_found and return
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+ images = []
+ imageIDs.each do |imageID|
+ begin
+ image = Image.find(imageID.split("_", 2)[0])
+ if (image.user_id!=current_user.id)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ images.push(image)
+ rescue Mongoid::Errors::DocumentNotFound
+ render json: {error: "image not found with id #{imageID.split("_", 2)[0]}"}, status: :not_found and return
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+ projects.each do |project|
+ images.each do |image|
+ if image.projectIDs.include? project.id.to_s
+ image.projectIDs.delete(project.id.to_s)
+ # Unlink All Sides that belongs to this Project that has this Image mapped to it.
+ image.sideIDs.each do |sideID|
+ side = project.sides.where(:id => sideID).first
+ if side
+ side.image = {}
+ side.save
+ image.sideIDs.delete(sideID)
+ end
+ end
+ image.save
+ end
+ end
+ end
+ @projects = current_user.projects
+ @images = current_user.images
+ render :'projects/index', status: :ok and return
+ end
+
+
+ # DELETE /images
+ def destroy
+ images = []
+ images_destroy_params.to_h[:imageIDs].each do |imageIDParam|
+ begin
+ imageID = imageIDParam.split("_", 2)[0]
+ image = Image.find(imageID)
+ images.push(image)
+ rescue Mongoid::Errors::DocumentNotFound
+ render json: {error: "image not found with id #{imageID}"}, status: :not_found and return
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ if (image.user_id!=current_user.id)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ end
+ images.each do |image|
+ image.destroy
+ end
+ @projects = current_user.projects
+ @images = current_user.images
+ render :'projects/index', status: :ok and return
+ end
+
+
+ private
+ def image_create_params
+ params.permit(:projectID, :images => [:filename, :content])
+ end
+
+ def images_destroy_params
+ params.permit(:imageIDs => [])
+ end
+
+ def image_link_unlink_params
+ params.permit(:projectIDs => [], :imageIDs => [])
+ end
+
+end
diff --git a/viscoll-api/app/controllers/import_controller.rb b/viscoll-api/app/controllers/import_controller.rb
new file mode 100644
index 00000000..9a7bdf35
--- /dev/null
+++ b/viscoll-api/app/controllers/import_controller.rb
@@ -0,0 +1,46 @@
+class ImportController < ApplicationController
+ before_action :authenticate!
+
+ # PUT /projects/import
+ def index
+ errorMessage = "Sorry, the imported data cannot be validated. Please check your file for errors and make sure the correct import format is selected above."
+ importData = imported_data.to_h[:importData]
+ importFormat = imported_data.to_h[:importFormat]
+ imageData = imported_data.to_h[:imageData]
+ begin
+ case importFormat
+ when "json"
+ handleJSONImport(JSON.parse(importData))
+ when "xml"
+ xml = Nokogiri::XML(importData)
+ schema = Nokogiri::XML::RelaxNG(File.open("public/viscoll-datamodel2.rng"))
+ schema2 = Nokogiri::XML::RelaxNG(File.open("public/viscoll-datamodel2.0.rng"))
+ errors = schema.validate(xml)
+ errors2 = schema2.validate(xml)
+ if errors.empty? || errors2.empty?
+ handleXMLImport(xml)
+ else
+ render json: {error: errors+errors2}, status: :unprocessable_entity and return
+ end
+ end
+ newProject = current_user.projects.order_by(:updated_at => 'desc').first
+ handleMappingImport(newProject, imageData, current_user)
+ current_user.reload
+ @projects = current_user.projects.order_by(:updated_at => 'desc')
+ @images = current_user.images
+ render :'projects/index', status: :ok and return
+ rescue Exception => e
+ render json: {error: errorMessage}, status: :unprocessable_entity and return
+ ensure
+ end
+ end
+
+
+
+ private
+ # Never trust parameters from the scary Internet, only allow the white list through.
+ def imported_data
+ params.permit(:importData, :importFormat, :imageData)
+ end
+
+end
diff --git a/viscoll-api/app/controllers/leafs_controller.rb b/viscoll-api/app/controllers/leafs_controller.rb
new file mode 100644
index 00000000..ce1b4584
--- /dev/null
+++ b/viscoll-api/app/controllers/leafs_controller.rb
@@ -0,0 +1,294 @@
+class LeafsController < ApplicationController
+ before_action :authenticate!
+ before_action :set_leaf, only: [:update, :destroy]
+
+ # POST /leafs
+ def create
+ memberOrder = additional_params.to_h[:memberOrder]
+ noOfLeafs = additional_params.to_h[:noOfLeafs]
+ conjoin = additional_params.to_h[:conjoin]
+ oddMemberLeftOut = additional_params.to_h[:oddMemberLeftOut]
+ leafIDs = additional_params.to_h[:leafIDs]
+ sideIDs = additional_params.to_h[:sideIDs]
+ project_id = leaf_params.to_h[:project_id]
+ parentID = leaf_params.to_h[:parentID]
+
+ # Validation error for leaf_params
+ @leafErrors = validateLeafParams(project_id, parentID)
+ if @leafErrors[:project_id].length>0 || @leafErrors[:parentID].length>0
+ render json: {leaf: @leafErrors}, status: :unprocessable_entity and return
+ end
+
+ # Validation errors checking for additional parameters
+ @additionalErrors = validateAdditionalLeafParams(project_id, parentID, memberOrder, noOfLeafs, conjoin, oddMemberLeftOut)
+ hasAdditionalErrors = false
+ @additionalErrors.each_value do |value|
+ if value.length>0
+ hasAdditionalErrors = true
+ end
+ end
+ if hasAdditionalErrors
+ render json: {additional: @additionalErrors}, status: :unprocessable_entity and return
+ end
+
+ # Attempt to validate ownership
+ @project = Project.find(project_id)
+ if current_user.id != @project.user_id
+ render json: { leaf: { project_id: ['unauthorized project_id'] } }, status: :unauthorized and return
+ end
+
+ # Skip all callbacks for side creation if leafIDs and SideIDs were give in the request
+ begin
+ if (leafIDs and sideIDs)
+ Leaf.skip_callback(:create, :before, :create_sides)
+ end
+ newlyAddedLeafIDs = []
+ newlyAddedLeafs = []
+ sideIDIndex = 0
+ noOfLeafs.times do |leafIDIndex|
+ @leaf = Leaf.new(leaf_params)
+ if leafIDs
+ @leaf.id = leafIDs[leafIDIndex]
+ end
+ @leaf.nestLevel = @group.nestLevel
+ if @leaf.save
+ newlyAddedLeafs.push(@leaf)
+ newlyAddedLeafIDs.push(@leaf.id.to_s)
+ # Create new sides for this leaf with given SideIDs
+ if (leafIDs and sideIDs)
+ recto = Side.new({parentID: @leaf.id.to_s, project: @leaf.project, texture: "Hair", id: sideIDs[sideIDIndex]})
+ verso = Side.new({parentID: @leaf.id.to_s, project: @leaf.project, texture: "Flesh", id: sideIDs[sideIDIndex+1] })
+ recto.id = "Recto_"+recto.id.to_s
+ verso.id = "Verso_"+verso.id.to_s
+ recto.save
+ verso.save
+ @leaf.rectoID = recto.id
+ @leaf.versoID = verso.id
+ @leaf.save
+ end
+ else
+ render json: {leaf: @leaf.errors}, status: :unprocessable_entity and return
+ end
+ sideIDIndex += 2
+ end
+ rescue
+ ensure
+ if (leafIDs and sideIDs)
+ Leaf.set_callback(:create, :before, :create_sides)
+ end
+ end
+
+ # Time to Auto-Conjoin
+ autoConjoinLeaves(newlyAddedLeafs, oddMemberLeftOut) if conjoin
+
+ # Add leaves to parent group
+ @group.add_members(newlyAddedLeafIDs, memberOrder)
+
+ end
+
+
+ # PATCH/PUT /leafs/1
+ def update
+ if (leaf_params.to_h.key?(:conjoined_to))
+ # HANDLE SPECIAL CASE FOR conjoined_to
+ update_conjoined_partner(leaf_params.to_h[:conjoined_to])
+ end
+ if @leaf.update(leaf_params)
+ if (leaf_params.to_h.key?(:attached_below)||leaf_params.to_h.key?(:attached_above))
+ update_attached_to()
+ elsif leaf_params.to_h.key?(:material) and leaf_params.to_h[:material] == "Paper"
+ handle_paper_update(@leaf)
+ end
+ else
+ render json: {leaf: @leaf.errors}, status: :unprocessable_entity and return
+ end
+ end
+
+
+ # PATCH/PUT /leafs
+ def updateMultiple
+ begin
+ allLeafs = leaf_params_batch_update.to_h[:leafs]
+ begin
+ @project = Project.find(leaf_params_batch_update.to_h[:project_id])
+ rescue Mongoid::Errors::DocumentNotFound => e
+ render json: {error: "project not found with id "+params[:project_id]}, status: :unprocessable_entity and return
+ end
+ allLeafs.each do |leaf_params, index|
+ begin
+ @leaf = Leaf.find(leaf_params[:id])
+ rescue Exception => e
+ render json: {leafs: ["leaf not found with id "+leaf_params[:id]]}, status: :unprocessable_entity and return
+ end
+ if @leaf.project.user_id != current_user.id
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ if !@leaf.update(leaf_params[:attributes])
+ render json: {leafs: {attributes: {index: @leaf.errors}}}, status: :unprocessable_entity and return
+ end
+ if (leaf_params[:attributes].key?(:attached_below)||leaf_params[:attributes].key?(:attached_above))
+ update_attached_to()
+ elsif leaf_params[:attributes].key?(:material) and leaf_params[:attributes][:material] == "Paper"
+ handle_paper_update(@leaf)
+ end
+ end
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+
+ # DELETE /leafs/1
+ def destroy
+ begin
+ parent = @project.groups.find(@leaf.parentID)
+ memberOrder = parent.memberIDs.index(@leaf.id.to_s)
+ # Detach its conjoined leaf
+ if @leaf.conjoined_to
+ @project.leafs.find(@leaf.conjoined_to).update(conjoined_to: nil)
+ end
+ if @leaf.attached_above != "None"
+ # Detach its above attached leaf
+ aboveLeaf = @project.leafs.find(parent.memberIDs[memberOrder-1])
+ aboveLeaf.update(attached_below: "None")
+ end
+ if @leaf.attached_below != "None"
+ # Detach its below attached leaf
+ belowLeaf = @project.leafs.find(parent.memberIDs[memberOrder+1])
+ belowLeaf.update(attached_above: "None")
+ end
+ @leaf.remove_from_group()
+ @leaf.destroy
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+
+ # DELETE /leafs.json
+ def destroyMultiple
+ begin
+ allLeafs = leaf_params_batch_delete.to_h[:leafs]
+ project_id = Leaf.find(allLeafs[0]).project_id
+ @project = Project.find(project_id)
+
+ parentAndChildren = {}
+
+ allLeafs.each_with_index do |leafID|
+ leaf = Leaf.find(leafID)
+ if !@parent || @parent.id.to_s != leaf.parentID
+ @parent = @project.groups.find(leaf.parentID)
+ end
+ memberOrder = @parent.memberIDs.index(leaf.id.to_s)
+ if leaf.project.user_id != current_user.id
+ render json: {error: ""}, status: :unauthorized and return
+ end
+
+ # Detach its conjoined leaf if any
+ if leaf.conjoined_to
+ @project.leafs.find(leaf.conjoined_to).update(conjoined_to: nil)
+ end
+ if leaf.attached_above != "None"
+ # Detach its above attached leaf
+ aboveLeaf = @project.leafs.find(@parent.memberIDs[memberOrder-1])
+ aboveLeaf.update(attached_below: "None")
+ end
+ if leaf.attached_below != "None"
+ # Detach its below attached leaf
+ belowLeaf = @project.leafs.find(@parent.memberIDs[memberOrder+1])
+ belowLeaf.update(attached_above: "None")
+ end
+ leaf.destroy
+ # Add leaf to list of leaves to detach from parent
+ if parentAndChildren[leaf.parentID] == nil
+ parentAndChildren[leaf.parentID] = [leaf.id.to_s]
+ else
+ parentAndChildren[leaf.parentID].push(leaf.id.to_s)
+ end
+ end
+
+ # Detach all leaves from parent(s)
+ parentAndChildren.each do |parentID, leafIDs|
+ @project.groups.find(parentID).remove_members(leafIDs)
+ end
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+
+ # CONJOIN /leafs.json
+ def conjoinLeafs
+ begin
+ leafIDs = leaf_params_batch_delete.to_h[:leafs]
+ leaves = []
+ # VALIDATION ERRORS
+ @errors = []
+ haveErrors = false
+ allowed_project_ids = current_user.projects.pluck(:id).collect { |pid| pid.to_s }
+ leafIDs.each do |leafID|
+ begin
+ leaf = Leaf.find(leafID)
+ if not allowed_project_ids.include?(leaf.project_id.to_s)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ leaves.push(leaf)
+ rescue Exception => e
+ @errors.push("leaf not found with id "+leafID)
+ haveErrors = true
+ end
+ end
+ if leafIDs.size < 2
+ @errors.push("Minimum of 2 leaves required to conjoin")
+ haveErrors = true
+ end
+ if haveErrors
+ render json: {leafs: @errors}, status: :unprocessable_entity and return
+ end
+ @project = Project.find(leaves[0].project_id)
+ autoConjoinLeaves(leaves, (leaves.length+1)/2)
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+
+
+
+ private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_leaf
+ begin
+ @leaf = Leaf.find(params[:id])
+ @project = Project.find(@leaf.project_id)
+ if (@project.user_id!=current_user.id)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ rescue Mongoid::Errors::DocumentNotFound
+ render json: {error: "leaf not found with id "+params[:id]}, status: :not_found and return
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+ # Never trust parameters from the scary internet, only allow the white list through.
+ def leaf_params
+ params.require(:leaf).permit(:id, :project_id, :parentID, :material, :type, :conjoined_to, :stub, :attached_above, :attached_below)
+ end
+
+ def additional_params
+ params.require(:additional).permit(:memberOrder, :noOfLeafs, :conjoin, :oddMemberLeftOut, :leafIDs=>[], :sideIDs=>[])
+ end
+
+ def leaf_params_batch_update
+ params.permit(:project_id, :leafs => [:id, :attributes=>[:conjoined_to, :type, :material, :stub, :attached_above, :attached_below]])
+ end
+
+ def leaf_params_batch_delete
+ params.permit(:leafs => [])
+ end
+
+ def leaf_params_conjoin
+ params.permit(:leafs => [])
+ end
+
+end
diff --git a/viscoll-api/app/controllers/notes_controller.rb b/viscoll-api/app/controllers/notes_controller.rb
new file mode 100644
index 00000000..2b2c7ed7
--- /dev/null
+++ b/viscoll-api/app/controllers/notes_controller.rb
@@ -0,0 +1,216 @@
+class NotesController < ApplicationController
+ before_action :authenticate!
+ before_action :set_note, only: [:update, :link, :unlink, :destroy]
+ before_action :set_attached_project, only: [:createType, :deleteType, :updateType]
+
+ # POST /notes
+ def create
+ @note = Note.new(note_create_params)
+ begin
+ @project = Project.find(@note.project_id)
+ rescue Mongoid::Errors::DocumentNotFound
+ render json: {project_id: "project not found with id "+@note.project_id}, status: :unprocessable_entity and return
+ end
+ if @project.user != current_user
+ render json: {error: ''}, status: :unauthorized and return
+ end
+ if @note.save
+ if not Project.find(@note.project_id).noteTypes.include?(@note.type)
+ @note.delete
+ render json: {type: "should be one of " +Project.find(@note.project_id).noteTypes.to_s}, status: :unprocessable_entity and return
+ end
+ else
+ render json: @note.errors, status: :unprocessable_entity and return
+ end
+ end
+
+ # PATCH/PUT /notes/1
+ def update
+ type = note_update_params.to_h[:type]
+ if not Project.find(@note.project_id).noteTypes.include?(type)
+ render json: {type: "should be one of " +Project.find(@note.project_id).noteTypes.to_s}, status: :unprocessable_entity and return
+ end
+ if !@note.update(note_update_params)
+ render json: @note.errors, status: :unprocessable_entity and return
+ end
+ end
+
+ # DELETE /notes/1
+ def destroy
+ @note.destroy
+ end
+
+ # PUT /notes/1/link
+ def link
+ begin
+ objects = note_object_link_params.to_h[:objects]
+ objects.each do |object|
+ type = object[:type]
+ id = object[:id]
+ begin
+ case type
+ when "Group"
+ @object = Group.find(id)
+ authorized = @object.project.user_id == current_user.id
+ when "Leaf"
+ @object = Leaf.find(id)
+ authorized = @object.project.user_id == current_user.id
+ when "Recto", "Verso"
+ @object = Side.find(id)
+ authorized = @object.project.user_id == current_user.id
+ else
+ render json: {type: "object not found with type "+type}, status: :unprocessable_entity and return
+ end
+ unless authorized
+ render json: {error: ''}, status: :unauthorized and return
+ end
+ rescue Mongoid::Errors::DocumentNotFound
+ render json: {id: type + " object not found with id "+id}, status: :unprocessable_entity and return
+ end
+ @object.notes.push(@note)
+ @object.save
+ if (not @note.objects[type].include?(id))
+ @note.objects[type].push(id)
+ end
+ @note.save
+ end
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+ # PUT /notes/1/unlink
+ def unlink
+ begin
+ objects = note_object_link_params.to_h[:objects]
+ objects.each do |object|
+ type = object[:type]
+ id = object[:id]
+ begin
+ case type
+ when "Group"
+ @object = Group.find(id)
+ authorized = @object.project.user_id == current_user.id
+ when "Leaf"
+ @object = Leaf.find(id)
+ authorized = @object.project.user_id == current_user.id
+ when "Recto", "Verso"
+ @object = Side.find(id)
+ authorized = @object.project.user_id == current_user.id
+ else
+ render json: {type: "object not found with type "+type}, status: :unprocessable_entity and return
+ end
+ unless authorized
+ render json: {error: ''}, status: :unauthorized and return
+ end
+ rescue Mongoid::Errors::DocumentNotFound
+ render json: {id: type + " object not found with id "+id}, status: :unprocessable_entity and return
+ end
+ @object.notes.delete(@note)
+ @object.save
+ @note.objects[type].delete(id)
+ @note.save
+ end
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+
+
+ # POST /notes/type
+ def createType
+ type = note_type_params.to_h[:type]
+ if @project.noteTypes.include?(type)
+ render json: {type: type+" type already exists in the project"}, status: :unprocessable_entity and return
+ else
+ @project.noteTypes.push(type)
+ @project.save
+ end
+ end
+
+
+ # DELETE /notes/type
+ def deleteType
+ type = note_type_params.to_h[:type]
+ if not @project.noteTypes.include?(type)
+ render json: {type: type+" type doesn't exist in the project"}, status: :unprocessable_entity and return
+ else
+ @project.noteTypes.delete(type)
+ @project.save
+ @project.notes.where(type: type).each do |note|
+ note.update(type: "Unknown")
+ note.save
+ end
+ end
+ end
+
+
+ # PUT /notes/type
+ def updateType
+ old_type = note_type_params.to_h[:old_type]
+ type = note_type_params.to_h[:type]
+ if not @project.noteTypes.include?(old_type)
+ render json: {old_type: old_type+" type doesn't exist in the project"}, status: :unprocessable_entity and return
+ elsif @project.noteTypes.include?(type)
+ render json: {type: type+" already exists in the project"}, status: :unprocessable_entity and return
+ else
+ indexToEdit = @project.noteTypes.index(old_type)
+ @project.noteTypes[indexToEdit] = type
+ @project.save
+ @project.notes.where(type: old_type).each do |note|
+ note.update(type: type)
+ note.save
+ end
+ end
+ end
+
+
+
+ private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_note
+ begin
+ @note = Note.find(params[:id])
+ @project = Project.find(@note.project_id)
+ if (@project.user_id!=current_user.id)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ rescue Mongoid::Errors::DocumentNotFound
+ render json: {error: "note not found with id "+params[:id]}, status: :not_found and return
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+ def set_attached_project
+ project_id = note_type_params.to_h[:project_id]
+ begin
+ @project = Project.find(project_id)
+ if @project.user_id != current_user.id
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ rescue Mongoid::Errors::DocumentNotFound
+ render json: {project_id: "project not found with id "+project_id}, status: :unprocessable_entity and return
+ end
+ end
+
+ # Never trust parameters from the scary internet, only allow the white list through.
+ def note_create_params
+ params.require(:note).permit(:project_id, :id, :title, :type, :description, :show)
+ end
+
+ def note_update_params
+ params.require(:note).permit(:title, :type, :description, :show)
+ end
+
+ def note_object_link_params
+ params.permit(:objects => [:id, :type])
+ end
+
+ def note_type_params
+ params.require(:noteType).permit(:type, :project_id, :old_type)
+ end
+
+
+end
diff --git a/viscoll-api/app/controllers/projects_controller.rb b/viscoll-api/app/controllers/projects_controller.rb
new file mode 100644
index 00000000..6f2cfeb1
--- /dev/null
+++ b/viscoll-api/app/controllers/projects_controller.rb
@@ -0,0 +1,225 @@
+class ProjectsController < ApplicationController
+ before_action :authenticate!, except: [:viewOnly]
+ before_action :set_project, only: [:show, :update, :destroy, :createManifest, :updateManifest, :deleteManifest, :clone]
+
+
+ # GET /projects
+ def index
+ @projects = current_user.projects
+ @images = current_user.images
+ end
+
+ # GET /projects/1
+ def show
+ @data = generateResponse()
+ @projects = current_user.projects
+ @images = current_user.images
+ end
+
+ # GET /projects/1/viewOnly
+ def viewOnly
+ @project = Project.find(params[:id])
+ @data = generateResponse()
+ render json: @data, status: :ok and return
+ end
+
+ # POST /projects
+ def create
+ begin
+ # Run validatins for groups params
+ allGroups = group_params.to_h["groups"]
+ folioNumber = group_params.to_h["folioNumber"]
+ pageNumber = group_params.to_h["pageNumber"]
+ startingTexture = group_params.to_h["startingTexture"]
+
+ validationResult = validateProjectCreateGroupsParams(allGroups)
+ if (not validationResult[:status])
+ render json: {groups: validationResult[:errors]}, status: :unprocessable_entity and return
+ end
+ # Instantiate a new project with the given params
+ @project = Project.new(project_params)
+ # If the project contains noteTypes, add the 'Unknown' type if its not present
+ if (not @project.noteTypes.empty? and not @project.noteTypes.include?('Unknown'))
+ @project.noteTypes.push('Unknown')
+ end
+ # Associate the current logged_in user to this project
+ @project.user = current_user
+ if @project.save
+ # If groups params were given, create the Groups & Leaves & auto-conjoin if required
+ if (not allGroups.empty?)
+ addGroupsLeafsConjoin(@project, allGroups, folioNumber, pageNumber, startingTexture)
+ end
+ # Get list of all projects of current user to return in response
+ @projects = current_user.projects.order_by(:updated_at => 'desc')
+ @images = current_user.images
+ render :index, status: :ok and return
+ else
+ render json: {project: @project.errors}, status: :unprocessable_entity and return
+ end
+ rescue Exception => e
+ render json: {errors: e.message}, status: :bad_request and return
+ end
+ end
+
+ # PATCH/PUT /projects/1
+ def update
+ begin
+ @project = Project.find(params[:id])
+ if @project.update(project_params)
+ @projects = current_user.projects
+ @images = current_user.images
+ render :index, status: :ok and return
+ else
+ render json: {project: @project.errors}, status: :unprocessable_entity and return
+ end
+ rescue Exception => e
+ render json: {errors: e.message}, status: :bad_request and return
+ end
+ end
+
+ # DELETE /projects/1
+ def destroy
+ deleteUnlinkedImages = project_delete_params.to_h["deleteUnlinkedImages"]
+ begin
+ # Skip some callbacks
+ Leaf.skip_callback(:destroy, :before, :unlink_notes)
+ if deleteUnlinkedImages
+ Image.skip_callback(:destroy, :before, :unlink_sides_before_delete)
+ current_user.images.where({ "projectIDs" => { '$eq': [@project.id.to_s] } }).each do | image |
+ image.destroy
+ end
+ end
+ @project.destroy
+ @projects = current_user.projects
+ @images = current_user.images
+ render :index, status: :ok and return
+ rescue Exception => e
+ render json: {errors: e.message}, status: :bad_request and return
+ ensure
+ # Enable callbacks again
+ Image.set_callback(:destroy, :before, :unlink_sides_before_delete)
+ Leaf.set_callback(:destroy, :before, :unlink_notes)
+ end
+ end
+
+ # POST /projects/:id/manifests
+ def createManifest
+ begin
+ manifest = manifest_params.to_h
+ if not manifest.key?("id")
+ manifestID = Project.new.id.to_s
+ else
+ manifestID = manifest[:id]
+ end
+ @project.manifests[manifestID] = {id: manifestID, url: manifest[:url]}
+ @project.save
+ @data = generateResponse()
+ @projects = current_user.projects
+ @images = current_user.images
+ render :show, status: :ok and return
+ rescue Exception => e
+ render json: {errors: e}, status: :bad_request and return
+ end
+ end
+
+ # PATCH/PUT /projects/:id/manifests
+ def updateManifest
+ begin
+ manifest = manifest_params.to_h
+ if not manifest.key?("id")
+ render json: {error: "Param required: id."}, status: :unprocessable_entity and return
+ end
+ if not @project.manifests.key?(manifest["id"])
+ render json: {error: "Manifest with id: " + manifest["id"] + " not found in project with id: " + @project.id.to_s + "."}, status: :unprocessable_entity and return
+ end
+ # ONLY UPDATING MANIFEST NAME FOR NOW
+ @project.manifests[manifest["id"]]["name"] = manifest["name"]
+ @project.save
+ rescue Exception => e
+ render json: {errors: e.message}, status: :bad_request and return
+ end
+ end
+
+ # DELETE /projects/:id/manifests
+ def deleteManifest
+ begin
+ manifestIDToDelete = manifest_params.to_h[:id]
+ if not @project.manifests.key?(manifestIDToDelete)
+ render json: {error: "Manifest with id: " + manifestIDToDelete + " not found in project with id: " + @project.id.to_s + "."}, status: :unprocessable_entity and return
+ end
+ @project.manifests.delete(manifestIDToDelete)
+ # Update all sides that have the deleted manuscripts mapping
+ @project.sides.each do |side|
+ if side[:image][:manifestID] == manifestIDToDelete
+ side.update(image: {})
+ end
+ end
+ @project.save
+ rescue Exception => e
+ render json: {errors: e.message}, status: :bad_request and return
+ end
+ end
+
+
+ # GET /projects/:id/clone
+ def clone
+ begin
+ exportedData = buildJSON(@project)
+ export = {
+ project: exportedData[:project],
+ Groups: exportedData[:groups],
+ Leafs: exportedData[:leafs],
+ Rectos: exportedData[:rectos],
+ Versos: exportedData[:versos],
+ Notes: exportedData[:notes],
+ }
+ handleJSONImport(JSON.parse(export.to_json))
+ newProject = current_user.projects.order_by(:updated_at => 'desc').first
+ newProject.sides.each do |side|
+ if !side.image.empty? and side.image["manifestID"]=="DIYImages"
+ filename = side.image["label"]
+ image = current_user.images.where(:filename => filename).first
+ !(image.sideIDs.include?(side.id.to_s)) ? image.sideIDs.push(side.id.to_s) : nil
+ !(image.projectIDs.include?(newProject.id.to_s)) ? image.projectIDs.push(newProject.id.to_s) : nil
+ image.save
+ end
+ end
+ @projects = current_user.projects.order_by(:updated_at => 'desc')
+ @images = current_user.images
+ render :index, status: :ok and return
+ rescue Exception => e
+ p e.message
+ end
+ end
+
+
+ private
+ def set_project
+ begin
+ @project = Project.find(params[:id])
+ if (@project.user_id!=current_user.id)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ rescue Exception => e
+ render json: {error: "project not found with id "+params[:id]}, status: :not_found and return
+ end
+ end
+
+ # Never trust parameters from the scary Internet, only allow the white list through.
+ def project_params
+ params.require(:project).permit(:title, :shelfmark, :metadata=>{}, :noteTypes=>[], :preferences=>{})
+ end
+
+ def project_delete_params
+ params.permit(:deleteUnlinkedImages)
+ end
+
+ def group_params
+ params.permit(:folioNumber, :pageNumber, :startingTexture, :groups => [:number, :leaves, :direction, :conjoin, :oddLeaf])
+ end
+
+ def manifest_params
+ params.require(:manifest).permit(:id, :name, :url)
+ end
+
+end
diff --git a/viscoll-api/app/controllers/registrations_controller.rb b/viscoll-api/app/controllers/registrations_controller.rb
new file mode 100644
index 00000000..a0f53c9e
--- /dev/null
+++ b/viscoll-api/app/controllers/registrations_controller.rb
@@ -0,0 +1,19 @@
+class RegistrationsController < RailsJwtAuth::RegistrationsController
+
+ def create
+ begin
+ user = RailsJwtAuth.model.new(registration_create_params)
+ user.save ? render_registration(user) : render_422(user.errors)
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+
+ end
+
+ private
+ def registration_create_params
+ params.require(RailsJwtAuth.model_name.underscore).permit(
+ RailsJwtAuth.auth_field_name, :password, :password_confirmation, :name
+ )
+ end
+end
diff --git a/viscoll-api/app/controllers/sessions_controller.rb b/viscoll-api/app/controllers/sessions_controller.rb
new file mode 100644
index 00000000..3ddfc68a
--- /dev/null
+++ b/viscoll-api/app/controllers/sessions_controller.rb
@@ -0,0 +1,71 @@
+class SessionsController < RailsJwtAuth::SessionsController
+
+ def create
+ user = RailsJwtAuth.model.where(RailsJwtAuth.auth_field_name =>
+ session_create_params[RailsJwtAuth.auth_field_name].to_s.downcase).first
+
+ if !user
+ render_422 session: [create_session_error]
+ elsif user.respond_to?('confirmed?') && !user.confirmed?
+ render_422 session: [I18n.t('rails_jwt_auth.errors.unconfirmed')]
+ elsif user.authenticate(session_create_params[:password])
+ @userProjects = []
+ begin
+ @userProjects = user.projects
+ rescue
+ end
+ @userToken = get_jwt(user)
+ @user = user
+ render :index, status: :ok, location: {
+ userProjects: @userProjects,
+ userToken: @userToken,
+ user: @user
+ }
+ else
+ render_422 session: [create_session_error]
+ end
+ end
+
+ def destroy
+ begin
+ authenticateDestroy!
+ current_user.destroy_auth_token Jwt::Request.new(request).auth_token
+ render_204
+ rescue Exception => e
+ render json: {error: "Authorization Header: "+e.message}, status: :unprocessable_entity
+ end
+ end
+end
+
+
+
+module Jwt
+ class Request
+ def initialize(request)
+ return unless request.env['HTTP_AUTHORIZATION']
+ @jwt = request.env['HTTP_AUTHORIZATION'].split.last
+
+ begin
+ @jwt_info = RailsJwtAuth::Jwt::Manager.decode(@jwt)
+ rescue JWT::ExpiredSignature, JWT::VerificationError
+ @jwt_info = false
+ end
+ end
+
+ def valid?
+ @jwt && @jwt_info && RailsJwtAuth::Jwt::Manager.valid_payload?(payload)
+ end
+
+ def payload
+ @jwt_info ? @jwt_info[0] : nil
+ end
+
+ def header
+ @jwt_info ? @jwt_info[1] : nil
+ end
+
+ def auth_token
+ payload ? payload['auth_token'] : nil
+ end
+ end
+end
diff --git a/viscoll-api/app/controllers/sides_controller.rb b/viscoll-api/app/controllers/sides_controller.rb
new file mode 100644
index 00000000..a2c63e95
--- /dev/null
+++ b/viscoll-api/app/controllers/sides_controller.rb
@@ -0,0 +1,155 @@
+class SidesController < ApplicationController
+ before_action :authenticate!
+ before_action :set_side, only: [:update]
+
+ # PATCH/PUT /sides/1
+ def update
+ begin
+ if !@side.update(side_params)
+ render json: @side.errors, status: :unprocessable_entity and return
+ end
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+
+ # PATCH/PUT /sides
+ def updateMultiple
+ begin
+ allSides = side_params_batch_update.to_h[:sides]
+ # VALIDATIONS
+ @errors = []
+ haveErrors = false
+ sides = []
+ allSides.each do |sideID|
+ begin
+ side = Side.find(sideID)
+ sides.push(side)
+ rescue Exception => e
+ @errors.push("side not found with id "+sideID)
+ haveErrors = true
+ end
+ end
+ @project = Project.find(sides[0].project_id)
+ if (@project.user_id!=current_user.id)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ if haveErrors
+ render json: {sides: @errors}, status: :unprocessable_entity and return
+ end
+ allSides.each_with_index do |side_params, index|
+ side = sides[index]
+ previousSideImage = side.image.clone
+ if !side.update(side_params[:attributes])
+ render json: side.errors, status: :unprocessable_entity and return
+ else
+ # SPEICAL CASE FOR DIY IMAGE MAPPING
+ if side_params[:attributes]["image"]
+ newSideImage = side_params[:attributes]["image"].clone
+ # If an image was linked, check if it was a DIY and link this Side to that Image
+ if newSideImage and !(newSideImage.empty?) and newSideImage["manifestID"]=="DIYImages"
+ imageID = newSideImage["url"].split("/")[-1].split("_", 2)[0]
+ begin
+ imageLinked = Image.find(imageID)
+ !(imageLinked.sideIDs.include?(side.id.to_s)) ? imageLinked.sideIDs.push(side.id.to_s) : nil
+ imageLinked.save
+ rescue Exception => e
+ end
+ end
+ # If an image was linked, check if this side was previously linked to a DIY Image and unlink this Side to that Image
+ if newSideImage and !newSideImage.empty? and !previousSideImage.empty? and previousSideImage["manifestID"]=="DIYImages"
+ imageID = previousSideImage["url"].split("/")[-1].split("_", 2)[0]
+ begin
+ imageUnlinked = Image.find(imageID)
+ if !imageLinked or imageLinked.id.to_s != imageUnlinked.id.to_s
+ imageUnlinked.sideIDs.include?(side.id.to_s) ? imageUnlinked.sideIDs.delete(side.id.to_s) : nil
+ imageUnlinked.save
+ end
+ rescue Exception => e
+ end
+ end
+ # If an Image was unlinked, check if it was a DIY and unlink this Side from the Image
+ if newSideImage and newSideImage.empty? and !previousSideImage.empty? and previousSideImage["manifestID"]=="DIYImages"
+ imageID = previousSideImage["url"].split("/")[-1].split("_", 2)[0]
+ begin
+ image = Image.find(imageID)
+ image.sideIDs.include?(side.id.to_s) ? image.sideIDs.delete(side.id.to_s) : nil
+ image.save
+ rescue Exception => e
+ end
+ end
+ end
+ end
+ end
+ rescue Exception => e
+ render json: {error: e.message}, status: :unprocessable_entity and return
+ end
+ end
+
+
+ # PUT /sides/generateFolio
+ def generateFolio
+ folioNumberCount = side_params_generate.to_h[:startNumber].to_i
+ rectoIDs = side_params_generate.to_h[:rectoIDs]
+ versoIDs = side_params_generate.to_h[:versoIDs]
+ rectoIDs.each_with_index do | rectoID, index |
+ recto = Side.find(rectoID)
+ verso = Side.find(versoIDs[index])
+ recto.update_attribute(:folio_number, folioNumberCount.to_s+"R")
+ verso.update_attribute(:folio_number, folioNumberCount.to_s+"V")
+ folioNumberCount += 1
+ if index==0
+ @project = Project.find(recto.project_id)
+ end
+ end
+ end
+
+
+ # PUT /sides/generatePageNumber
+ def generatePageNumber
+ pageNumberCount = side_params_generate.to_h[:startNumber].to_i
+ rectoIDs = side_params_generate.to_h[:rectoIDs]
+ versoIDs = side_params_generate.to_h[:versoIDs]
+ rectoIDs.each_with_index do | rectoID, index |
+ recto = Side.find(rectoID)
+ verso = Side.find(versoIDs[index])
+ recto.update_attribute(:page_number, pageNumberCount.to_s)
+ pageNumberCount += 1
+ verso.update_attribute(:page_number, pageNumberCount.to_s)
+ pageNumberCount += 1
+ if index==0
+ @project = Project.find(recto.project_id)
+ end
+ end
+ end
+
+
+ private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_side
+ begin
+ @side = Side.find(params[:id])
+ @project = Project.find(@side.project_id)
+ if (@project.user_id!=current_user.id)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ rescue Exception => e
+ render json: {error: "side not found"}, status: :not_found and return
+ end
+ end
+
+ # Never trust parameters from the scary internet, only allow the white list through.
+ def side_params
+ params.require(:side).permit(:folio_number, :page_number, :texture, :script_direction, :image=>[:manifestID, :label, :url])
+ end
+
+ def side_params_batch_update
+ params.permit(:sides => [:id, :attributes=>[:folio_number, :page_number, :texture, :script_direction, :image=>[:manifestID, :label, :url]]])
+ end
+
+ def side_params_generate
+ params.permit(:startNumber, :rectoIDs => [], :versoIDs => [])
+ end
+
+end
diff --git a/viscoll-api/app/controllers/users_controller.rb b/viscoll-api/app/controllers/users_controller.rb
new file mode 100644
index 00000000..dd7b6633
--- /dev/null
+++ b/viscoll-api/app/controllers/users_controller.rb
@@ -0,0 +1,52 @@
+class UsersController < ApplicationController
+ before_action :authenticate!
+ before_action :set_user, only: [:show, :update, :destroy]
+
+ # GET /users/1
+ def show
+ end
+
+ # PATCH/PUT /users/1
+ def update
+ if user_params_with_password[:password] != nil
+ action = current_user.update_with_password(user_params_with_password)
+ else
+ action = current_user.update_attributes(user_params)
+ end
+ if action
+ @user = User.find(params[:id])
+ render :show, status: :ok and return
+ else
+ render json: current_user.errors, status: :unprocessable_entity and return
+ end
+
+ end
+
+ # DELETE /users/1
+ def destroy
+ @user.destroy
+ end
+
+ private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_user
+ begin
+ @user = User.find(params[:id])
+ if (@user!=current_user)
+ render json: {error: ""}, status: :unauthorized and return
+ end
+ rescue Exception => e
+ render json: {error: "user not found with id "+params[:id]}, status: :not_found and return
+ end
+ end
+
+ # Only allow a trusted parameter "white list" through.
+ def user_params
+ params.require(:user).permit(:email, :name)
+ end
+
+ # Only allow a trusted parameter "white list" through.
+ def user_params_with_password
+ params.require(:user).permit(:email, :name, :current_password, :password)
+ end
+end
diff --git a/viscoll-api/app/helpers/controller_helper/export_helper.rb b/viscoll-api/app/helpers/controller_helper/export_helper.rb
new file mode 100644
index 00000000..45b99e67
--- /dev/null
+++ b/viscoll-api/app/helpers/controller_helper/export_helper.rb
@@ -0,0 +1,765 @@
+module ControllerHelper
+ module ExportHelper
+
+ def buildJSON(project)
+ @project.reload
+ @projectInformation = {}
+ @groupIDs = @project.groupIDs
+ @leafIDs = []
+ @rectoIDs = []
+ @versoIDs = []
+ @groups = {}
+ @leafs = {}
+ @rectos = {}
+ @versos = {}
+ @notes = {}
+
+ @projectInformation = {
+ "title": @project.title,
+ "shelfmark": @project.shelfmark,
+ "metadata": @project.metadata,
+ "preferences": @project.preferences,
+ "manifests": @project.manifests,
+ "noteTypes": @project.noteTypes
+ }
+
+ rootMemberOrder = 1
+ @groupIDs.each_with_index do | groupID, index|
+ group = @project.groups.find(groupID)
+ @groups[index + 1] = {
+ "params": {
+ "type": group.type,
+ "title": group.title,
+ "direction": group.direction,
+ "nestLevel": group.nestLevel
+ },
+ "tacketed": group.tacketed,
+ "sewing": group.sewing,
+ "parentOrder": group.parentID,
+ "memberOrders": group.memberIDs
+ }
+ if group.nestLevel == 1
+ rootMemberOrder += 1
+ end
+ end
+
+ # Generate @leafIDs list
+ @groups.each do | groupOrder, group |
+ if group[:params][:nestLevel] == 1
+ getLeafMemberOrders(group[:memberOrders])
+ end
+ end
+
+ @leafIDs.each_with_index do | leafID, index |
+ leaf = @project.leafs.find(leafID)
+ @leafs[index + 1] = {
+ "params": {
+ "material": leaf.material,
+ "type": leaf.type,
+ "attached_above": leaf.attached_above,
+ "attached_below": leaf.attached_below,
+ "stub": leaf.stub,
+ "nestLevel": leaf.nestLevel
+ },
+ "conjoined_leaf_order": leaf.conjoined_to ? @leafIDs.index(leaf.conjoined_to) + 1 : nil,
+ "parentOrder": @groupIDs.index(leaf.parentID)+1,
+ "rectoOrder": index + 1,
+ "versoOrder": index + 1,
+ }
+ @rectoIDs.push(leaf.rectoID)
+ @versoIDs.push(leaf.versoID)
+ end
+
+ # Transform group's members to global orders
+ # Transform group's tacketed and sewing to leaf global orders
+ # Transform group's parentID to group global order
+ @groups.each do | groupID, group |
+ memberOrders = []
+ group[:memberOrders].each do |memberID|
+ if memberID[0] == "G"
+ memberOrders.push("Group_" + (@groupIDs.index(memberID)+1).to_s)
+ else
+ memberOrders.push("Leaf_" + (@leafIDs.index(memberID)+1).to_s)
+ end
+ end
+ group[:memberOrders] = memberOrders
+ tacketedLeafOrders, sewingLeafOrders = [], []
+ group[:tacketed].each do |leafID| tacketedLeafOrders.push(@leafIDs.index(leafID)+1) end
+ group[:sewing].each do |leafID| sewingLeafOrders.push(@leafIDs.index(leafID)+1) end
+ group[:tacketed], group[:sewing] = tacketedLeafOrders, sewingLeafOrders
+ group[:parentOrder] = group[:parentOrder] ? @groupIDs.index(group[:parentOrder]) + 1 : nil
+ end
+
+ @rectoIDs.each_with_index do | rectoID, index |
+ recto = @project.sides.find(rectoID)
+ parentOrder = @leafIDs.index(recto.parentID) + 1
+ @rectos[index + 1] = {
+ "params": {
+ "folio_number": recto.folio_number ? recto.folio_number : "",
+ "page_number": recto.page_number ? recto.page_number : "",
+ "texture": recto.texture,
+ "image": recto.image,
+ "script_direction": recto.script_direction
+ },
+ "parentOrder": parentOrder
+ }
+ end
+
+ @versoIDs.each_with_index do | versoID, index |
+ verso = @project.sides.find(versoID)
+ parentOrder = @leafIDs.index(verso.parentID) + 1
+ @versos[index + 1] = {
+ "params": {
+ "folio_number": verso.folio_number ? verso.folio_number : "",
+ "page_number": verso.page_number ? verso.page_number : "",
+ "texture": verso.texture,
+ "image": verso.image,
+ "script_direction": verso.script_direction
+ },
+ "parentOrder": parentOrder
+ }
+ end
+
+ @project.notes.each_with_index do | note, index |
+ @notes[index + 1] = {
+ "params": {
+ "title": note.title,
+ "type": note.type,
+ "description": note.description,
+ "show": note.show
+ },
+ "objects": {}
+ }
+ @notes[index + 1][:objects][:Group] = note.objects["Group"].map { |groupID| @groupIDs.index(groupID)+1 }
+ @notes[index + 1][:objects][:Leaf] = note.objects["Leaf"].map { |leafID| @leafIDs.index(leafID)+1 }
+ @notes[index + 1][:objects][:Recto] = note.objects["Recto"].map { |rectoID| @rectoIDs.index(rectoID)+1 }
+ @notes[index + 1][:objects][:Verso] = note.objects["Verso"].map { |versoID| @versoIDs.index(versoID)+1 }
+ end
+
+ return {
+ "project": @projectInformation,
+ "groups": @groups,
+ "leafs": @leafs,
+ "rectos": @rectos,
+ "versos": @versos,
+ "notes": @notes,
+ }
+ end
+
+
+ # Populate leaf orders recursively
+ def getLeafMemberOrders(memberIDs)
+ memberIDs.each_with_index do | memberID, index |
+ if memberID[0] == "G"
+ getLeafMemberOrders(@groups[@groupIDs.index(memberID)+1][:memberOrders])
+ elsif memberID[0] == "L"
+ @leafIDs.push(memberID)
+ end
+ end
+ end
+
+
+
+
+
+ def buildDotModel(project)
+ @groupIDs = project.groupIDs
+ @groups = {}
+ @leafIDs = []
+ @leafs = {}
+ @rectos = {}
+ @versos = {}
+ @notes = {}
+ @noteTitles = []
+ @allGroupAttributeValues = []
+ @allLeafAttributeValues = []
+ @allSideAttributeValues = []
+ @groupIDs.each_with_index do |groupID, index|
+ if @groups.key?(groupID)
+ memberOrder = @groups[groupID][:memberOrder]
+ @groups[groupID] = project.groups.find(groupID)
+ @groups[groupID][:memberOrder] = memberOrder
+ else
+ @groups[groupID] = project.groups.find(groupID)
+ @groups[groupID][:memberOrder] = index+1
+ end
+ if @groups[groupID][:memberIDs]
+ populateLeafSideObjects(@groups[groupID][:memberIDs], project)
+ end
+ end
+
+ return Nokogiri::XML::Builder.new { |xml|
+ xml.viscoll :xmlns => "http://schoenberginstitute.org/schema/collation" do
+ idPrefix = project.shelfmark.parameterize.underscore
+
+ # Project Attributes Taxonomy
+ ['preferences'].each do |attribute|
+ manuscriptAttribute = {"xml:id": 'manuscript_'+attribute}
+ xml.taxonomy manuscriptAttribute do
+ xml.label do
+ xml.text 'Manuscript ' + attribute
+ end
+ project[attribute].each do |key, value|
+ termID = {"xml:id": "manuscript_"+attribute+"_"+idPrefix+"_"+key}
+ xml.term termID do
+ xml.text value
+ end
+ end
+ end
+ end
+ if not project.manifests.empty?
+ manifestAttribute = {"xml:id": 'manifests'}
+ xml.taxonomy manifestAttribute do
+ xml.label do
+ xml.text 'List of Manifests'
+ end
+ project.manifests.each do |manifestID, manifest|
+ termID = {"xml:id": 'manifest_'+manifest["id"]}
+ xml.term termID do
+ xml.text manifest["url"]
+ end
+ end
+ end
+ end
+
+ # Group Attributes Taxonomy
+ ['title', 'type', 'direction'].each do |attribute|
+ groupAttribute = {"xml:id": 'group_'+attribute}
+ xml.taxonomy groupAttribute do
+ xml.label do
+ xml.text 'List of values for Group ' + attribute
+ end
+ groupAttributeValues = []
+ @groupIDs.each do |groupID|
+ group = @groups[groupID]
+ if not groupAttributeValues.include? group[attribute]
+ groupAttributeValues.push(group[attribute])
+ end
+ end
+ groupAttributeValues.each do |attributeValue|
+ termID = {"xml:id": "group_"+attribute+"_"+attributeValue.parameterize.underscore}
+ xml.term termID do
+ xml.text attributeValue
+ end
+ end
+ @allGroupAttributeValues = @allGroupAttributeValues + groupAttributeValues
+ end
+ end
+ ['tacketed', 'sewing'].each do |attribute|
+ groupAttribute = {"xml:id": 'group_'+attribute}
+ groupAttributeValues = []
+ @groupIDs.each do |groupID|
+ group = @groups[groupID]
+ leaves = ""
+ if not groupAttributeValues.include? group[attribute]
+ group[attribute].each do |leafID|
+ parents = parentsOrders(leafID, project)
+ leafemberOrder = parents.pop
+ idPostfix = parents.join("-")+"-"+leafemberOrder.to_s
+ leaves = leaves + " #" + idPrefix+"-"+idPostfix + " "
+ leaves = leaves.strip
+ end
+ end
+ if leaves != ""
+ xml.taxonomy groupAttribute do
+ xml.label do
+ xml.text 'List of Groups ' + attribute
+ end
+ parents = parentsOrders(groupID, project)
+ groupOrder = parents.pop
+ groupMemberOrder = group["memberOrder"]
+ idPostfix = parents.empty? ? groupOrder.to_s : parents.join("-")+"-"+groupOrder.to_s
+ termID = {"xml:id": "group_"+attribute+"_"+idPrefix+"-q-"+idPostfix}
+ xml.term termID do
+ xml.text leaves
+ end
+ end
+ @allGroupAttributeValues = @allGroupAttributeValues + [leaves]
+ end
+ end
+ end
+ # Member IDs of each Group
+ groupAttribute = {"xml:id": 'group_members'}
+ xml.taxonomy groupAttribute do
+ xml.label do
+ xml.text 'List of values for each Group\'s members'
+ end
+ @groupIDs.each do |groupID|
+ group = @groups[groupID]
+ memberIDs = []
+ group.memberIDs.each do |memberID|
+ parents = parentsOrders(memberID, project)
+ memberOrder = parents.pop
+ if memberID[0]=="G"
+ idPostfix = parents.empty? ? memberOrder.to_s : parents.join("-")+"-"+memberOrder.to_s
+ memberIDs.push(idPrefix+"-q-"+idPostfix)
+ else
+ idPostfix = parents.join("-")+"-"+memberOrder.to_s
+ memberIDs.push(idPrefix+"-"+idPostfix)
+ end
+
+ end
+ memberIDs = memberIDs.join(" #").strip
+ parents = parentsOrders(groupID, project)
+ groupOrder = parents.pop
+ groupMemberOrder = group["memberOrder"]
+ idPostfix = parents.empty? ? groupOrder.to_s : parents.join("-")+"-"+groupOrder.to_s
+ termID = {"xml:id": "group_members_"+idPrefix+"-q-"+idPostfix}
+ xml.term termID do
+ xml.text "#"+memberIDs
+ end
+ end
+ end
+
+ # Leaf Attributes Taxonomy
+ ['material'].each do |attribute|
+ leafAttribute = {"xml:id": 'leaf_'+attribute}
+ leafAttributeValues = []
+ @leafIDs.each do |leafID|
+ leaf = @leafs[leafID]
+ if not leafAttributeValues.include? leaf[attribute] and leaf[attribute] != "None"
+ leafAttributeValues.push(leaf[attribute])
+ end
+ end
+ if not leafAttributeValues.empty?
+ xml.taxonomy leafAttribute do
+ xml.label do
+ xml.text 'List of values for Leaf ' + attribute
+ end
+ leafAttributeValues.each do |attributeValue|
+ termID = {"xml:id": "leaf_"+attribute+"_"+attributeValue.parameterize.underscore}
+ xml.term termID do
+ xml.text attributeValue
+ end
+ end
+ @allLeafAttributeValues += leafAttributeValues
+ end
+ end
+ end
+ leafAttribute = {"xml:id": 'leaf_attachment_method'}
+ xml.taxonomy leafAttribute do
+ xml.label do
+ xml.text 'List of Attachment Methods'
+ end
+ ['Glued_Above_Partial', 'Glued_Above_Complete', 'Glued_Above_Drumming', 'Glued_Above_Other'].each do |attribute|
+ termID = {"xml:id": attribute}
+ xml.term termID do
+ xml.text attribute.split("_")[0]+" ("+attribute.split("_")[2]+")"
+ end
+ end
+ ['Glued_Below_Partial', 'Glued_Below_Complete', 'Glued_Below_Drumming', 'Glued_Below_Other'].each do |attribute|
+ termID = {"xml:id": attribute}
+ xml.term termID do
+ xml.text attribute.split("_")[0]+" ("+attribute.split("_")[2]+")"
+ end
+ end
+ end
+
+ # Side Attributes Taxonomy
+ ['texture', 'script_direction', 'page_number'].each do |attribute|
+ sideAttribute = {"xml:id": 'side_'+attribute}
+ sideAttributeValues = []
+ @rectos.each do |rectoID, recto|
+ if recto[attribute] == nil and not sideAttributeValues.include? "EMPTY"
+ sideAttributeValues.push("EMPTY")
+ elsif recto[attribute] != nil and not sideAttributeValues.include? recto[attribute] and recto[attribute] != "None"
+ sideAttributeValues.push(recto[attribute])
+ end
+ end
+ @versos.each do |versoID, verso|
+ if verso[attribute] == nil and not sideAttributeValues.include? "EMPTY"
+ sideAttributeValues.push("EMPTY")
+ elsif verso[attribute] != nil and not sideAttributeValues.include? verso[attribute] and verso[attribute] != "None"
+ sideAttributeValues.push(verso[attribute])
+ end
+ end
+ if not sideAttributeValues.empty?
+ xml.taxonomy sideAttribute do
+ xml.label do
+ xml.text 'List of values for Side ' + attribute
+ end
+ sideAttributeValues.each do |attributeValue|
+ if attributeValue
+ termID = {"xml:id": "side_"+attribute+"_"+attributeValue.parameterize.underscore}
+ xml.term termID do
+ xml.text attributeValue
+ end
+ end
+ end
+ @allSideAttributeValues += sideAttributeValues
+ end
+ end
+ end
+
+ # Note Attributes Taxonomy
+ if not project.notes.empty?
+ noteTitle = {"xml:id": 'note_title'}
+ xml.taxonomy noteTitle do
+ xml.label do
+ xml.text 'List of values for Note Titles'
+ end
+ project.notes.each_with_index do |note, index|
+ if not @noteTitles.include? note.title
+ @noteTitles.push(note.title)
+ end
+ end
+ @noteTitles.each do |noteTitle|
+ termID = {"xml:id": "note_title"+"_"+noteTitle.parameterize.underscore}
+ xml.term termID do
+ xml.text noteTitle
+ end
+ end
+ end
+ noteShow = {"xml:id": 'note_show'}
+ xml.taxonomy noteShow do
+ xml.label do
+ xml.text 'Whether to show Note in Visualizations'
+ end
+ termID = {"xml:id": "note_show"}
+ xml.term termID do
+ xml.text true
+ end
+ end
+ end
+
+
+ # STRUCTURE
+ xml.manuscript do
+ xml.title project.title
+ xml.shelfmark project.shelfmark
+ xml.date project.metadata[:date]
+ xml.direction :val => "l-r"
+ idPrefix = project.shelfmark.parameterize.underscore
+ xml.quires do
+ @groupIDs.each_with_index do |groupID, index|
+ group = @groups[groupID]
+ parents = parentsOrders(groupID, project)
+ groupOrder = parents.pop
+ groupMemberOrder = group["memberOrder"]
+ idPostfix = parents.empty? ? groupOrder.to_s : parents.join("-")+"-"+groupOrder.to_s
+ quireAttributes = {}
+ quireAttributes["xml:id"] = idPrefix+"-q-"+idPostfix
+ quireAttributes[:n] = index + 1
+ quireAttributes[:certainty] = 1
+ if group.parentID
+ quireAttributes[:parent] = idPrefix+"-q-"+parents.join("-")
+ end
+ xml.quire quireAttributes do
+ xml.text index + 1
+ end
+ @groups[groupID]["xmlID"] = quireAttributes["xml:id"]
+ end
+ end
+ @leafIDs.each_with_index do |leafID, index|
+ leaf = project.leafs.find(leafID)
+ parents = parentsOrders(leafID, project)
+ leafemberOrder = parents.pop
+ idPostfix = parents.join("-")+"-"+leafemberOrder.to_s
+ leafAttributes = {}
+ leafAttributes["xml:id"] = idPrefix+"-"+idPostfix
+ leafAttributes["stub"] = "yes" if leaf.stubType != "None"
+ xml.leaf leafAttributes do
+ folioNumber = {}
+ folioNumber[:val] = @leafIDs.index(leafID)+1
+ folioNumber[:certainty] = 1
+ xml.folioNumber folioNumber do
+ xml.text folioNumber[:val].to_s
+ end
+
+ mode = {}
+ if ['original', 'added', 'replaced', 'false', 'missing'].include? leaf.type.downcase
+ mode[:val] = leaf.type.downcase
+ mode[:certainty] = 1
+ end
+ xml.mode mode
+
+ qAttributes = {}
+ qAttributes[:position] = project.groups.find(leaf.parentID).memberIDs.index(leafID)+1
+ qAttributes[:leafno] = leafemberOrder
+ qAttributes[:certainty] = 1
+ qAttributes[:target] = "#"+idPrefix+"-q-"+parents.join("-")
+ qAttributes[:n] = parents[-1]
+ xml.q qAttributes do
+ if leaf.conjoined_to
+ idPostfix = parents.join("-")+"-"+@leafs[leaf.conjoined_to][:memberOrder].to_s
+ xml.conjoin :certainty => 1, :target => "#"+idPrefix+"-"+idPostfix
+ end
+ end
+
+ if not leaf.conjoined_to
+ xml.single :val => "yes"
+ end
+
+ rectoSide = project.sides.find(leaf.rectoID)
+ rectoAttributes = {}
+ rectoAttributes["xml:id"] = leafAttributes["xml:id"]
+ rectoAttributes[:type] = "Recto"
+ if rectoSide.folio_number
+ rectoAttributes[:folioNumber] = rectoSide.folio_number
+ else
+ rectoAttributes[:folioNumber] = folioNumber[:val].to_s+"R"
+ end
+ if rectoSide.page_number
+ rectoAttributes[:page_number] = rectoSide.page_number
+ else
+ rectoAttributes[:page_number] = "EMPTY"
+ end
+ rectoAttributes[:texture] = rectoSide.texture unless rectoSide.texture == "None"
+ rectoAttributes[:script_direction] = rectoSide.script_direction unless rectoSide.script_direction == "None"
+ rectoAttributes[:image] = rectoSide.image[:url] unless rectoSide.image.empty?
+ rectoAttributes[:target] = "#"+leafAttributes["xml:id"]
+ # xml.side rectoAttributes
+ @rectos[leaf.rectoID] = rectoAttributes
+ @rectos[leaf.rectoID]["recto"] = rectoSide
+ versoSide = project.sides.find(leaf.versoID)
+ versoAttributes = {}
+ versoAttributes["xml:id"] = leafAttributes["xml:id"]
+ versoAttributes[:type] = "Verso"
+ if versoSide.folio_number
+ versoAttributes[:folioNumber] = versoSide.folio_number
+ else
+ versoAttributes[:folioNumber] = folioNumber[:val].to_s+"R"
+ end
+ if versoSide.page_number
+ versoAttributes[:page_number] = versoSide.page_number
+ else
+ versoAttributes[:page_number] = "EMPTY"
+ end
+ versoAttributes[:texture] = versoSide.texture unless versoSide.texture == "None"
+ versoAttributes[:script_direction] = versoSide.script_direction unless versoSide.script_direction == "None"
+ versoAttributes[:image] = versoSide.image[:url] unless versoSide.image.empty?
+ versoAttributes[:target] = "#"+leafAttributes["xml:id"]
+ # xml.side versoAttributes
+ @versos[leaf.versoID] = versoAttributes
+ @versos[leaf.versoID]["verso"] = versoSide
+ end
+ @leafs[leafID]["xmlID"] = leafAttributes["xml:id"]
+ end
+ end
+
+ # NOTES
+ if not project.notes.empty?
+ xml.notes do
+ project.notes.each_with_index do |note, index|
+ noteAttributes = {}
+ noteAttributes["xml:id"] = idPrefix+"-n-"+(index+1).to_s
+ noteAttributes[:type] = note.type
+ xml.note noteAttributes do
+ xml.text note.description
+ end
+ @notes[note.id.to_s] = {}
+ @notes[note.id.to_s]["xml:id"] = "#"+noteAttributes["xml:id"]
+ @notes[note.id.to_s][:note] = note
+ end
+ end
+ end
+
+ # MAPPING
+ xml.mapping do
+ # Map quires to attributes and notes and memberIDs
+ @groupIDs.each do |groupID|
+ group = @groups[groupID]
+ parents = parentsOrders(groupID, project)
+ groupOrder = parents.pop
+ groupMemberOrder = group["memberOrder"]
+ idPrefix = project.shelfmark.parameterize.underscore
+ idPostfix = parents.empty? ? groupOrder.to_s : parents.join("-")+"-"+groupOrder.to_s
+ linkedNotes = (group.notes.map {|note| "#note_title"+"_"+note.title.parameterize.underscore}).join(" ")
+ linkedAttributes = []
+ ['title', 'type', 'direction'].each do |attribute|
+ attributeValue = group[attribute]
+ if @allGroupAttributeValues.include? attributeValue
+ linkedAttributes.push("group_"+attribute+"_"+attributeValue.parameterize.underscore)
+ end
+ end
+ ['tacketed', 'sewing'].each do |attribute|
+ attributeValue = ""
+ group[attribute].each do |leafID|
+ parents = parentsOrders(leafID, project)
+ leafemberOrder = parents.pop
+ idPostfix = parents.join("-")+"-"+leafemberOrder.to_s
+ attributeValue = attributeValue + " #" + idPrefix+"-"+idPostfix + " "
+ attributeValue = attributeValue.strip
+ end
+ if @allGroupAttributeValues.include? attributeValue
+ parents = parentsOrders(groupID, project)
+ groupOrder = parents.pop
+ groupMemberOrder = group["memberOrder"]
+ idPostfix = parents.empty? ? groupOrder.to_s : parents.join("-")+"-"+groupOrder.to_s
+ linkedAttributes.push("group_"+attribute+"_"+idPrefix+"-q-"+idPostfix)
+ end
+ end
+ linkedAttributes = linkedAttributes.join(" #")
+ if linkedNotes+linkedAttributes != ""
+ xml.map :target => "#"+idPrefix+"-q-"+idPostfix do
+ if linkedAttributes != ""
+ xml.term :target => linkedNotes+" #"+linkedAttributes+" #group_members_"+idPrefix+"-q-"+idPostfix
+ else
+ xml.term :target => linkedNotes+" #group_members_"+idPrefix+"-q-"+idPostfix
+ end
+ end
+ end
+ end
+ # Map leaves to attributes and notes
+ @leafIDs.each do |leafID|
+ leaf = @leafs[leafID]
+ parents = parentsOrders(leafID, project)
+ leafemberOrder = parents.pop
+ idPostfix = parents.join("-")+"-"+leafemberOrder.to_s
+ linkedNotes = (leaf.notes.map {|note| "#note_title"+"_"+note.title.parameterize.underscore}).join(" ")
+ attachementMethods = []
+ if leaf.attached_above != "None"
+ if leaf.attached_above == "Other"
+ attachementMethods.push("#Glued_Above_Other")
+ else
+ attachementMethods.push("#Glued_Above_"+leaf.attached_above.split(" ")[1][1..-2])
+ end
+ end
+ if leaf.attached_below != "None"
+ if leaf.attached_below == "Other"
+ attachementMethods.push("#Glued_Below_Other")
+ else
+ attachementMethods.push("#Glued_Below_"+leaf.attached_below.split(" ")[1][1..-2])
+ end
+ end
+ attachementMethods = attachementMethods.join(" ").strip
+ material = ""
+ if @allLeafAttributeValues.include? leaf.material and material != ""
+ material = "#leaf_material_"+leaf.material.parameterize.underscore.strip
+ end
+ if linkedNotes+attachementMethods+material != ""
+ xml.map :target => "#"+idPrefix+"-"+idPostfix do
+ xml.term :target => (linkedNotes+" "+material+attachementMethods).strip
+ end
+ end
+ end
+ # Map rectos to attributes and notes and sides
+ @rectos.each do |rectoID, attributes|
+ recto = attributes["recto"]
+ linkedNotes = (recto.notes.map {|note| "#note_title"+"_"+note.title.parameterize.underscore}).join(" ")
+ linkedImage = recto.image.empty? ? "" : recto.image[:url]
+ linkedAttributes = []
+ ['texture', 'script_direction', 'page_number'].each do |attribute|
+ attributeValue = recto[attribute]
+ if @allSideAttributeValues.include? attributeValue and attributeValue
+ linkedAttributes.push("side_"+attribute+"_"+attributeValue.parameterize.underscore)
+ elsif attributeValue==nil and attribute=="page_number" and @allSideAttributeValues.include? "EMPTY"
+ linkedAttributes.push("side_"+attribute+"_EMPTY")
+ end
+ end
+ linkedAttributes = linkedAttributes.empty? ? "" : linkedAttributes.join(" #")
+ if linkedNotes+linkedImage+linkedAttributes.strip != ""
+ if linkedAttributes != ""
+ termText = linkedNotes.strip+" #"+linkedAttributes
+ if linkedImage != ""
+ termText = termText+" "+linkedImage+" #manifest_"+recto.image[:manifestID]
+ end
+ xml.map :side => 'recto', :target => "#"+attributes["xml:id"] do
+ xml.term :target => termText.strip
+ end
+ else
+ termText = linkedNotes.strip
+ if linkedImage != ""
+ termText = termText+" "+linkedImage+" #manifest_"+recto.image[:manifestID]
+ end
+ xml.map :side => 'recto', :target => "#"+attributes["xml:id"] do
+ xml.term :target => termText.strip
+ end
+ end
+ end
+ end
+ # Map versos to attributes and notes and sides
+ @versos.each do |versoID, attributes|
+ verso = attributes["verso"]
+ linkedNotes = (verso.notes.map {|note| "#note_title"+"_"+note.title.parameterize.underscore}).join(" ")
+ linkedImage = verso.image.empty? ? "" : verso.image[:url]
+ linkedAttributes = []
+ ['texture', 'script_direction', 'page_number'].each do |attribute|
+ attributeValue = verso[attribute]
+ if @allSideAttributeValues.include? attributeValue and attributeValue
+ linkedAttributes.push("side_"+attribute+"_"+attributeValue.parameterize.underscore)
+ elsif attributeValue==nil and attribute=="page_number" and @allSideAttributeValues.include? "EMPTY"
+ linkedAttributes.push("side_"+attribute+"_EMPTY")
+ end
+ end
+ linkedAttributes = linkedAttributes.empty? ? "" : linkedAttributes.join(" #")
+ if linkedNotes+linkedImage+linkedAttributes.strip != ""
+ if linkedAttributes != ""
+ termText = linkedNotes.strip+" #"+linkedAttributes
+ if linkedImage != ""
+ termText = termText+" "+linkedImage+" #manifest_"+verso.image[:manifestID]
+ end
+ xml.map :side => 'verso', :target => "#"+attributes["xml:id"] do
+ xml.term :target => termText.strip
+ end
+ else
+ termText = linkedNotes.strip
+ if linkedImage != ""
+ termText = termText+" "+linkedImage+" #manifest_"+verso.image[:manifestID]
+ end
+ xml.map :side => 'verso', :target => "#"+attributes["xml:id"] do
+ xml.term :target => termText.strip
+ end
+ end
+ end
+ end
+ # Map notes to noteTitles
+ @notes.each do |noteID, attributes|
+ note = attributes[:note]
+ xml.map :target => attributes["xml:id"] do
+ termText = []
+ if @noteTitles.include? note.title
+ termText.push("note_title"+"_"+note.title.parameterize.underscore)
+ end
+ termText = termText.empty? ? "" : termText.join(" #")
+ note.show ? termText=termText+" #note_show" : nil
+ xml.term :target => "#"+termText.strip
+ end
+ end
+ end
+
+ end
+ }.to_xml
+ end
+
+
+ # Populate leaf and side objects in ascending order
+ def populateLeafSideObjects(memberIDs, project, leafMember=1)
+ groupMember = 1
+ memberIDs.each_with_index do | memberID, index |
+ if memberID[0] == "G"
+ @groups[memberID] = {"memberOrder": groupMember}
+ populateLeafSideObjects(project.groups.find(memberID).memberIDs, project, leafMember)
+ groupMember += 1
+ elsif memberID[0] == "L"
+ if not @leafIDs.include? memberID
+ leaf = project.leafs.find(memberID)
+ @leafIDs.push(memberID)
+ @leafs[memberID] = leaf
+ @leafs[memberID]["memberOrder"] = leafMember
+ @rectos[leaf.rectoID] = project.sides.find(leaf.rectoID)
+ @versos[leaf.versoID] = project.sides.find(leaf.versoID)
+ leafMember += 1
+ end
+ end
+ end
+ end
+
+
+ # Get all parent orders upto root
+ def parentsOrders(memberID, project)
+ result = []
+ if memberID
+ if memberID[0] == "G"
+ result = parentsOrders(project.groups.find(memberID).parentID, project) + [(@groupIDs.index(memberID)+1).to_s]
+ else
+ result = parentsOrders(project.leafs.find(memberID).parentID, project) + [@leafs[memberID][:memberOrder].to_s]
+ end
+ end
+ return result
+ end
+
+
+
+ end
+end
diff --git a/viscoll-api/app/helpers/controller_helper/filter_helper.rb b/viscoll-api/app/helpers/controller_helper/filter_helper.rb
new file mode 100644
index 00000000..18b0b3dd
--- /dev/null
+++ b/viscoll-api/app/helpers/controller_helper/filter_helper.rb
@@ -0,0 +1,76 @@
+module ControllerHelper
+ module FilterHelper
+
+ def runValidations(queries)
+ errors = []
+ haveErrors = false
+ if queries == []
+ return ["should contain at least 1 query"]
+ end
+ queries.each_with_index do |query, index|
+ error = {type: "", attribute: "", condition: "", values: "", conjunction: ""}
+ if (qc = query_types['type'][query['type']]).nil?
+ error['type'] = "type should be one of: [#{query_types['type'].keys.join(', ')}]"
+ haveErrors = true
+ elsif (qc = qc[query['attribute']]).nil?
+ error['attribute'] = "valid attributes for #{query['type']}: [#{query_types['type'][query['type']].keys.join(', ')}]"
+ haveErrors = true
+ elsif not qc.include?(query['condition'])
+ error['condition'] = "valid conditions for #{query['type']} attribute #{query['attribute']} : [#{qc.join(', ')}]"
+ haveErrors = true
+ end
+
+ if queries.length > 1 && index {
+ 'group' => {
+ 'type' => ['equals', 'not equals'],
+ 'title' => ['equals', 'not equals', 'contains', 'not contains']
+ },
+ 'leaf' => {
+ 'type' => ['equals', 'not equals'],
+ 'material' => ['equals', 'not equals'],
+ 'conjoined_to' => ['equals', 'not equals'],
+ 'conjoined_leaf_order' => ['equals', 'not equals'], # Legacy attribute
+ 'attached_above' => ['equals', 'not equals'],
+ 'attached_below' => ['equals', 'not equals'],
+ 'stub' => ['equals', 'not equals']
+ },
+ 'side' => {
+ 'folio_number' => ['equals', 'not equals', 'contains', 'not contains'],
+ 'page_number' => ['equals', 'not equals', 'contains', 'not contains'],
+ 'texture' => ['equals', 'not equals'],
+ 'script_direction' => ['equals', 'not equals'],
+ 'uri' => ['equals', 'not equals', 'contains', 'not contains'],
+ },
+ 'note' => {
+ 'title' => ['equals', 'not equals', 'contains', 'not contains'],
+ 'type' => ['equals', 'not equals'],
+ 'description' => ['equals', 'not equals', 'contains', 'not contains']
+ }
+ },
+ 'conjunction' => ['AND', 'OR']
+ }
+ end
+
+ end
+end
diff --git a/viscoll-api/app/helpers/controller_helper/groups_helper.rb b/viscoll-api/app/helpers/controller_helper/groups_helper.rb
new file mode 100644
index 00000000..57c02e36
--- /dev/null
+++ b/viscoll-api/app/helpers/controller_helper/groups_helper.rb
@@ -0,0 +1,49 @@
+module ControllerHelper
+ module GroupsHelper
+ include ControllerHelper::LeafsHelper
+
+ def addLeavesInside(project_id, group, noOfLeafs, conjoin, oddMemberLeftOut, leafIDs=false, sideIDs=false)
+ begin
+ if (leafIDs and sideIDs)
+ Leaf.skip_callback(:create, :before, :create_sides)
+ end
+ newlyAddedLeafs = []
+ newlyAddedLeafIDs = []
+ sideIDIndex = 0
+ noOfLeafs.times do |leafIDIndex|
+ leaf = Leaf.new({project_id: project_id, parentID:group.id.to_s, nestLevel: group.nestLevel})
+ if (leafIDs and sideIDs)
+ leaf.id = leafIDs[leafIDIndex]
+ end
+ leaf.save()
+ newlyAddedLeafs.push(leaf)
+ newlyAddedLeafIDs.push(leaf.id.to_s)
+ # Create new sides for this leaf with given SideIDs
+ if (leafIDs and sideIDs)
+ recto = Side.new({parentID: leaf.id.to_s, project: leaf.project, texture: "Hair", id: sideIDs[sideIDIndex]})
+ verso = Side.new({parentID: leaf.id.to_s, project: leaf.project, texture: "Flesh", id: sideIDs[sideIDIndex+1] })
+ recto.id = "Recto_"+recto.id.to_s
+ verso.id = "Verso_"+verso.id.to_s
+ recto.save
+ verso.save
+ leaf.rectoID = recto.id
+ leaf.versoID = verso.id
+ leaf.save
+ end
+ sideIDIndex += 2
+ end
+ # Add newly created leaves to this group
+ group.add_members(newlyAddedLeafIDs, 1)
+ # Auto-Conjoin newly added leaves in this group
+ if conjoin
+ autoConjoinLeaves(newlyAddedLeafs, oddMemberLeftOut)
+ end
+ rescue
+ ensure
+ if (leafIDs and sideIDs)
+ Leaf.set_callback(:create, :before, :create_sides)
+ end
+ end
+ end
+ end
+end
diff --git a/viscoll-api/app/helpers/controller_helper/import_helper.rb b/viscoll-api/app/helpers/controller_helper/import_helper.rb
new file mode 100644
index 00000000..3381d65a
--- /dev/null
+++ b/viscoll-api/app/helpers/controller_helper/import_helper.rb
@@ -0,0 +1,219 @@
+module ControllerHelper
+ module ImportHelper
+
+ # JSON IMPORT
+ def handleJSONImport(data)
+ # reference variables
+ allGroupsIDsInOrder = []
+ allLeafsIDsInOrder = []
+ allRectosIDsInOrder = []
+ allVersosIDsInOrder = []
+
+ # Create the Project
+ begin
+ Project.find_by(title: data["project"]["title"])
+ data["project"]["title"] = "Copy of " + data["project"]["title"]+" @ " + Time.now.to_s
+ rescue Exception => e
+ end
+ data["project"]["user_id"] = current_user.id
+ project = Project.create(data["project"])
+
+ # Create all Leafs
+ data["Leafs"].each do |leafOrder, data|
+ data["params"]["project_id"] = project.id
+ leaf = Leaf.create(data["params"])
+ allLeafsIDsInOrder.push(leaf.id.to_s)
+ allRectosIDsInOrder.push(leaf.rectoID)
+ allVersosIDsInOrder.push(leaf.versoID)
+ end
+
+ # Create all Groups
+ data["Groups"].each do |groupOrder, data|
+ tacketed, sewing = [], []
+ data["tacketed"].each do |leafOrder|
+ tacketed.push(allLeafsIDsInOrder[leafOrder-1])
+ end
+ data["sewing"].each do |leafOrder|
+ sewing.push(allLeafsIDsInOrder[leafOrder-1])
+ end
+ data["params"]["tacketed"] = tacketed
+ data["params"]["sewing"] = sewing
+ data["params"]["project_id"] = project.id
+ group = Group.create(data["params"])
+ allGroupsIDsInOrder.push(group.id.to_s)
+ end
+
+ project.reload
+ # Update all Group membersIDs and parentID
+ data["Groups"].each do |groupOrder, data|
+ group = project.groups.find(allGroupsIDsInOrder[groupOrder.to_i-1])
+ parentID = data["parentOrder"] ? allGroupsIDsInOrder[data["parentOrder"]-1] : nil
+ memberIDs = []
+ data["memberOrders"].each do |memberOrder|
+ memberType, memberOrder = memberOrder.split("_")
+ if memberType=="Group"
+ memberIDs.push(allGroupsIDsInOrder[memberOrder.to_i-1])
+ else
+ memberIDs.push(allLeafsIDsInOrder[memberOrder.to_i-1])
+ leaf = project.leafs.find(allLeafsIDsInOrder[memberOrder.to_i-1])
+ leaf.update(parentID: group.id.to_s)
+ end
+ end
+ group.update(parentID: parentID, memberIDs: memberIDs)
+ end
+
+ # Update all leafs with correct conjoinedTo leafID
+ data["Leafs"].each do |leafOrder, data|
+ if data["conjoined_leaf_order"]
+ leafIDConjoinedTo = allLeafsIDsInOrder[data["conjoined_leaf_order"]-1]
+ leaf = project.leafs.find(allLeafsIDsInOrder[leafOrder.to_i-1])
+ leaf.update(conjoined_to: leafIDConjoinedTo)
+ end
+ end
+
+ # Update all Rectos
+ allRectosIDsInOrder.each_with_index do |rectoID, order|
+ recto = project.sides.find(rectoID)
+ rectoParams = data["Rectos"][(order+1).to_s]["params"]
+ recto.update(rectoParams)
+ end
+
+ # Update all Verso
+ allVersosIDsInOrder.each_with_index do |versoID, order|
+ verso = project.sides.find(versoID)
+ versoParams = data["Versos"][(order+1).to_s]["params"]
+ verso.update(versoParams)
+ end
+
+ project.reload
+ # Create all Notes
+ data["Notes"].each do |noteOrder, data|
+ data["params"]["project_id"] = project.id
+ note = Note.new(data["params"])
+ # Generate objectIDs of Groups, Leafs, Rectos, Versos with this note
+ groupIDs = []
+ data["objects"]["Group"].each do |groupOrder|
+ groupID = allGroupsIDsInOrder[groupOrder-1]
+ group = project.groups.find(groupID)
+ group.notes.push(note)
+ group.save
+ groupIDs.push(groupID)
+ end
+ leafIDs = []
+ data["objects"]["Leaf"].each do |leafOrder|
+ leafID = allLeafsIDsInOrder[leafOrder-1]
+ leaf = project.leafs.find(leafID)
+ leaf.notes.push(note)
+ leaf.save
+ leafIDs.push(leafID)
+ end
+ rectoIDs = []
+ data["objects"]["Recto"].each do |rectoOrder|
+ rectoID = allRectosIDsInOrder[rectoOrder-1]
+ recto = project.sides.find(rectoID)
+ recto.notes.push(note)
+ recto.save
+ rectoIDs.push(rectoID)
+ end
+ versoIDs = []
+ data["objects"]["Verso"].each do |versoOrder|
+ versoID = allVersosIDsInOrder[versoOrder-1]
+ verso = project.sides.find(versoID)
+ verso.notes.push(note)
+ verso.save
+ versoIDs.push(versoID)
+ end
+ note.objects[:Group] = groupIDs
+ note.objects[:Leaf] = leafIDs
+ note.objects[:Recto] = rectoIDs
+ note.objects[:Verso] = versoIDs
+ note.save
+ end
+
+ # Update project groupIDs
+ project.groupIDs = allGroupsIDsInOrder
+ project.save
+ end
+
+
+
+ # XML IMPORT
+ def handleXMLImport(data, xml)
+ # reference variables
+ allGroupsIDsInOrder = []
+ allLeafsIDsInOrder = []
+ allRectosIDsInOrder = []
+ allVersosIDsInOrder = []
+ @groups = {}
+ @leafs = {}
+ @rectos = {}
+ @versos = {}
+
+ allGroups = xml.xpath('//x:quire', "x" => "http://schoenberginstitute.org/schema/collation")
+ allLeaves = xml.xpath('//x:leaf', "x" => "http://schoenberginstitute.org/schema/collation")
+ allNotes = xml.xpath('//x:note', "x" => "http://schoenberginstitute.org/schema/collation")
+
+ # Create the Project
+ projectInformation = {}
+ projectInformation[:title] = data["title"]
+ if not projectInformation[:title]
+ projectInformation[:title] = "XML_Import_@_" + Time.now.to_s
+ end
+ begin
+ Project.find_by(title: projectInformation[:title])
+ projectInformation[:title] = "Copy of " + projectInformation[:title] + " @ " + Time.now.to_s
+ rescue Exception => e
+ end
+ projectInformation[:shelfmark] = data["shelfmark"]
+ projectInformation[:metadata] = {date: data["date"]}
+
+ # p projectInformation
+ # @project = Project.create(projectInformation)
+ allLeaves.each do |leaf|
+ leafID = nil
+ leafAttributes = {}
+ leaf.attributes.each do |attr|
+ if attr[1].name == "id"
+ leafID = attr[1].value
+ end
+ leafAttributes[attr[1].name] = attr[1].value
+ end
+ leafChildren = {}
+ leaf.getChildren.each do |child|
+ childAttributes = {}
+ child.attributes.each do |attr|
+ if attr[1].name == "id"
+ leafID = attr[1].value
+ end
+ childAttributes[attr[1].name] = attr[1].value
+ end
+ end
+ @leafs[leafID] = leafAttributes
+ end
+ p @leafs
+
+ end
+
+ def getAttributes(node)
+ attributes = node.attributes.dup
+ attributes.keys.each do |key|
+ attributes[key.to_sym] = attributes.delete(key).to_s
+ end
+ return attributes
+ end
+
+ def getChildren(node)
+ return node.children.filter { |child| child.next_element.class != 'Nokogiri::XML::Text' }
+ end
+
+ def getNodeID(node)
+ node.attributes.each do |attr|
+ if attr[1].name == "id"
+ return attr[1].value
+ end
+ end
+ end
+
+
+ end
+end
\ No newline at end of file
diff --git a/viscoll-api/app/helpers/controller_helper/import_json_helper.rb b/viscoll-api/app/helpers/controller_helper/import_json_helper.rb
new file mode 100644
index 00000000..bd73c42a
--- /dev/null
+++ b/viscoll-api/app/helpers/controller_helper/import_json_helper.rb
@@ -0,0 +1,138 @@
+module ControllerHelper
+ module ImportJsonHelper
+
+ # JSON IMPORT
+ def handleJSONImport(data)
+ # reference variables
+ allGroupsIDsInOrder = []
+ allLeafsIDsInOrder = []
+ allRectosIDsInOrder = []
+ allVersosIDsInOrder = []
+
+ # Create the Project
+ begin
+ Project.find_by(title: data["project"]["title"])
+ data["project"]["title"] = "Copy of " + data["project"]["title"]+" @ " + Time.now.to_s
+ rescue Exception => e
+ end
+ data["project"]["user_id"] = current_user.id
+ project = Project.create(data["project"])
+
+ # Create all Leafs
+ data["Leafs"].each do |leafOrder, data|
+ data["params"]["project_id"] = project.id
+ leaf = Leaf.create(data["params"])
+ allLeafsIDsInOrder.push(leaf.id.to_s)
+ allRectosIDsInOrder.push(leaf.rectoID)
+ allVersosIDsInOrder.push(leaf.versoID)
+ end
+
+ # Create all Groups
+ data["Groups"].each do |groupOrder, data|
+ tacketed, sewing = [], []
+ data["tacketed"].each do |leafOrder|
+ tacketed.push(allLeafsIDsInOrder[leafOrder-1])
+ end
+ data["sewing"].each do |leafOrder|
+ sewing.push(allLeafsIDsInOrder[leafOrder-1])
+ end
+ data["params"]["tacketed"] = tacketed
+ data["params"]["sewing"] = sewing
+ data["params"]["project_id"] = project.id
+ group = Group.create(data["params"])
+ allGroupsIDsInOrder.push(group.id.to_s)
+ end
+
+ project.reload
+ # Update all Group membersIDs and parentID
+ data["Groups"].each do |groupOrder, data|
+ group = project.groups.find(allGroupsIDsInOrder[groupOrder.to_i-1])
+ parentID = data["parentOrder"] ? allGroupsIDsInOrder[data["parentOrder"]-1] : nil
+ memberIDs = []
+ data["memberOrders"].each do |memberOrder|
+ memberType, memberOrder = memberOrder.split("_")
+ if memberType=="Group"
+ memberIDs.push(allGroupsIDsInOrder[memberOrder.to_i-1])
+ else
+ memberIDs.push(allLeafsIDsInOrder[memberOrder.to_i-1])
+ leaf = project.leafs.find(allLeafsIDsInOrder[memberOrder.to_i-1])
+ leaf.update(parentID: group.id.to_s)
+ end
+ end
+ group.update(parentID: parentID, memberIDs: memberIDs)
+ end
+
+ # Update all leafs with correct conjoinedTo leafID
+ data["Leafs"].each do |leafOrder, data|
+ if data["conjoined_leaf_order"]
+ leafIDConjoinedTo = allLeafsIDsInOrder[data["conjoined_leaf_order"]-1]
+ leaf = project.leafs.find(allLeafsIDsInOrder[leafOrder.to_i-1])
+ leaf.update(conjoined_to: leafIDConjoinedTo)
+ end
+ end
+
+ # Update all Rectos
+ allRectosIDsInOrder.each_with_index do |rectoID, order|
+ recto = project.sides.find(rectoID)
+ rectoParams = data["Rectos"][(order+1).to_s]["params"]
+ recto.update(rectoParams)
+ end
+
+ # Update all Verso
+ allVersosIDsInOrder.each_with_index do |versoID, order|
+ verso = project.sides.find(versoID)
+ versoParams = data["Versos"][(order+1).to_s]["params"]
+ verso.update(versoParams)
+ end
+
+ project.reload
+ # Create all Notes
+ data["Notes"].each do |noteOrder, data|
+ data["params"]["project_id"] = project.id
+ note = Note.new(data["params"])
+ # Generate objectIDs of Groups, Leafs, Rectos, Versos with this note
+ groupIDs = []
+ data["objects"]["Group"].each do |groupOrder|
+ groupID = allGroupsIDsInOrder[groupOrder-1]
+ group = project.groups.find(groupID)
+ group.notes.push(note)
+ group.save
+ groupIDs.push(groupID)
+ end
+ leafIDs = []
+ data["objects"]["Leaf"].each do |leafOrder|
+ leafID = allLeafsIDsInOrder[leafOrder-1]
+ leaf = project.leafs.find(leafID)
+ leaf.notes.push(note)
+ leaf.save
+ leafIDs.push(leafID)
+ end
+ rectoIDs = []
+ data["objects"]["Recto"].each do |rectoOrder|
+ rectoID = allRectosIDsInOrder[rectoOrder-1]
+ recto = project.sides.find(rectoID)
+ recto.notes.push(note)
+ recto.save
+ rectoIDs.push(rectoID)
+ end
+ versoIDs = []
+ data["objects"]["Verso"].each do |versoOrder|
+ versoID = allVersosIDsInOrder[versoOrder-1]
+ verso = project.sides.find(versoID)
+ verso.notes.push(note)
+ verso.save
+ versoIDs.push(versoID)
+ end
+ note.objects[:Group] = groupIDs
+ note.objects[:Leaf] = leafIDs
+ note.objects[:Recto] = rectoIDs
+ note.objects[:Verso] = versoIDs
+ note.save
+ end
+
+ # Update project groupIDs
+ project.groupIDs = allGroupsIDsInOrder
+ project.save
+ end
+ end
+end
\ No newline at end of file
diff --git a/viscoll-api/app/helpers/controller_helper/import_mapping_helper.rb b/viscoll-api/app/helpers/controller_helper/import_mapping_helper.rb
new file mode 100644
index 00000000..e8efad2a
--- /dev/null
+++ b/viscoll-api/app/helpers/controller_helper/import_mapping_helper.rb
@@ -0,0 +1,112 @@
+require 'zip'
+
+module ControllerHelper
+ module ImportMappingHelper
+ def decodeZip(imageData)
+ # Get the zip
+ tempzip = Tempfile.new('images.zip')
+ tempzip.binmode
+ regexp = /\Adata:([-\w]+\/[-\w\+\.]+)?;base64,(.*)/m
+ parts = imageData.match(regexp)
+ data = StringIO.new(Base64.decode64(parts[-1] || ""))
+ while part = data.read(16*1024)
+ tempzip.write(part)
+ end
+ tempzip.rewind
+ return tempzip
+ end
+
+ def handleMappingImport(newProject, imageData, current_user)
+ begin
+ uploadedImages = {}
+ if imageData.present?
+ tempzip = decodeZip(imageData)
+ Zip::File.open(tempzip.path) do |zip_file|
+ zip_file.each do |file|
+ # Go through each file and collect its info
+ # The exported filename structure is: userGivenFilename_fileID.fileExtension
+ filename = file.name.rpartition('_')[0]
+ fileID = file.name.rpartition('_')[2].split('.')[0]
+ extension = file.name.split('.')[1]
+ tempfile = Tempfile.new("#{filename}")
+ tempfile.binmode
+ tempfile.write file.get_input_stream.read
+ tempfile.rewind
+ uploadedImages["#{filename}.#{extension}"] = {:fileID => fileID, :file => tempfile, :extension => extension}
+ end
+ end
+ end
+ # Go though all the sides in the newProject that are mapped to DIYImages.
+ # If it is not linked to Image that belongs to the current_user, unlink; otherwise update the link.
+ newProject.sides.each do |side|
+ if !side.image.empty? and side.image["manifestID"]=="DIYImages"
+ imageID = side.image["url"].split("/")[-1].split("_", 2)[0]
+ filename = side.image["url"].split("/")[-1].split("_", 2)[1]
+ image = current_user.images.where(:id => imageID).first
+ if not image
+ # Image object doesn't exist for current_user
+ # Check if any Image with 'filename' was uploaded during import.
+ if uploadedImages.key?(filename)
+ # Check if filename already exists for current_user
+ existingImage = current_user.images.where(:filename => filename).first
+ if existingImage
+ # Check if this new Image is different from the existing Image
+ if uploadedImages[filename][:fileID] == existingImage.fileID
+ # Same Image, so link this Image to the Side
+ side.image["url"]=@base_api_url+"/images/"+existingImage.id.to_s+"_"+existingImage.filename
+ side.save
+ !(existingImage.sideIDs.include?(side.id.to_s)) ? existingImage.sideIDs.push(side.id.to_s) : nil
+ !(existingImage.projectIDs.include?(newProject.id.to_s)) ? existingImage.projectIDs.push(newProject.id.to_s) : nil
+ existingImage.save
+ else
+ # Different Image, but with already existing filename. Rename the newImage and link to this Side.
+ filenameOnly = filename.rpartition(".")[0]
+ newFilename = "#{filenameOnly}(copy).#{uploadedImages[filename][:extension]}"
+ # check if filename already exists, if it does, add another "(copy)"
+ imageWithFilename = current_user.images.where(:filename => newFilename).first
+ while imageWithFilename
+ newFilename = "#{newFilename.rpartition(".")[0]}(copy).#{uploadedImages[filename][:extension]}"
+ imageWithFilename = current_user.images.where(:filename => newFilename).first
+ end
+ uploader = Shrine.new(:store)
+ uploaded_file = uploader.upload(uploadedImages[filename][:file], metadata: {"filename"=>newFilename, "mime_type": "image/#{uploadedImages[filename][:extension]}"})
+ newImage = Image.new(user: current_user, filename: newFilename, fileID: uploaded_file.id, metadata: uploaded_file.metadata, projectIDs: [newProject.id.to_s])
+ side.image["url"]=@base_api_url+"/images/"+newImage.id.to_s+"_"+newFilename
+ side.save
+ !(newImage.sideIDs.include?(side.id.to_s)) ? newImage.sideIDs.push(side.id.to_s) : nil
+ !(newImage.projectIDs.include?(newProject.id.to_s)) ? newImage.projectIDs.push(newProject.id.to_s) : nil
+ newImage.save
+ end
+ else
+ # Image object doesn't exist with filename
+ # Create Image
+ uploader = Shrine.new(:store)
+ uploaded_file = uploader.upload(uploadedImages[filename][:file], metadata: {"filename"=>"#{filename}", "mime_type": "image/#{uploadedImages[filename][:extension]}"})
+ newImage = Image.new(user: current_user, filename: filename, fileID: uploaded_file.id, metadata: uploaded_file.metadata, projectIDs: [newProject.id.to_s])
+ side.image["url"]=@base_api_url+"/images/"+newImage.id.to_s+"_"+newImage.filename
+ side.save
+ !(newImage.sideIDs.include?(side.id.to_s)) ? newImage.sideIDs.push(side.id.to_s) : nil
+ !(newImage.projectIDs.include?(newProject.id.to_s)) ? newImage.projectIDs.push(newProject.id.to_s) : nil
+ newImage.save
+ end
+ else
+ # No Image with with 'filename' was uploaded. So unlink this Side from existing mapping.
+ side.image = {}
+ side.save
+ end
+ else
+ # Image already exists with the curent_user. Link that Image to this Side.
+ side.image["url"]=@base_api_url+"/images/"+image.id.to_s+"_"+image.filename
+ side.save
+ !(image.sideIDs.include?(side.id.to_s)) ? image.sideIDs.push(side.id.to_s) : nil
+ !(image.projectIDs.include?(newProject.id.to_s)) ? image.projectIDs.push(newProject.id.to_s) : nil
+ image.save
+ end
+ end
+ end
+ rescue Exception => e
+ p e.message
+ end
+ end
+ end
+end
diff --git a/viscoll-api/app/helpers/controller_helper/import_xml_helper.rb b/viscoll-api/app/helpers/controller_helper/import_xml_helper.rb
new file mode 100644
index 00000000..30d1e90e
--- /dev/null
+++ b/viscoll-api/app/helpers/controller_helper/import_xml_helper.rb
@@ -0,0 +1,387 @@
+require 'uri'
+
+module ControllerHelper
+ module ImportXmlHelper
+ # XML IMPORT
+ def handleXMLImport(xml)
+ @allGroupNodeIDsInOrder = []
+ @allLeafNodeIDsInOrder = []
+ @groups = {}
+ @leafs = {}
+ @rectos = {}
+ @versos = {}
+ @notes = {}
+
+ # Project Information
+ @projectInformation = {
+ title: "",
+ shelfmark: "",
+ metadata: {date: ""},
+ preferences: {showTips: true},
+ manifests: {},
+ noteTypes: ["Unknown"]
+ }
+ # Grab project Title
+ projectTitleNode = xml.xpath("//x:title", "x" => "http://schoenberginstitute.org/schema/collation")
+ if projectTitleNode.text.empty?
+ @projectInformation[:title] = "No title"
+ else
+ @projectInformation[:title] = projectTitleNode.text
+ end
+ if not @projectInformation[:title]
+ @projectInformation[:title] = "XML_Import_@_" + Time.now.to_s
+ end
+ begin
+ Project.find_by(title: @projectInformation[:title])
+ @projectInformation[:title] = "Copy of " + @projectInformation[:title] + " @ " + Time.now.to_s
+ rescue Exception => e
+ end
+ # grab project Shelfmark
+ projectShelfmarkNode = xml.xpath("//x:shelfmark", "x" => "http://schoenberginstitute.org/schema/collation")
+ @projectInformation[:shelfmark] = projectShelfmarkNode.text
+ # grap prohect Date
+ projectDateNode = xml.xpath("//x:date", "x" => "http://schoenberginstitute.org/schema/collation")
+ if not projectDateNode.empty?
+ @projectInformation[:metadata][:date] = projectDateNode.text
+ end
+ # Map manifests to Project
+ manifestTaxonomy = xml.xpath("//x:taxonomy[@xml:id='manifests']", "x" => "http://schoenberginstitute.org/schema/collation")
+ if not manifestTaxonomy.empty?
+ manifestTaxonomy.children.each do |child|
+ if child.name=="term"
+ id = child.attributes["id"].value.split("_")[-1]
+ url = child.text
+ @projectInformation[:manifests][id] = {:id => id, :url => url}
+ end
+ end
+ end
+
+ # Groups Information
+ allGroupNodes = xml.xpath('//x:quire', "x" => "http://schoenberginstitute.org/schema/collation")
+ # Generate all attributes for Groups
+ allGroupNodes.each_with_index do |groupNode, index|
+ groupNodeID = groupNode.attributes["id"].value
+ parentNodeID = groupNode.attributes["parent"]? groupNode.attributes["parent"].value : nil
+ groupOrder = index+1
+ @allGroupNodeIDsInOrder.push(groupNodeID)
+ nestLevel = 1
+ while parentNodeID do
+ nodeSearchText = "//x:quire[@xml:id='"+parentNodeID+"']"
+ parentGroupNode = xml.xpath(nodeSearchText, "x" => "http://schoenberginstitute.org/schema/collation")
+ if not parentGroupNode.empty?
+ parentNodeID = parentGroupNode[0].attributes["parent"]? parentGroupNode[0].attributes["parent"].value : nil
+ else
+ parentNodeID = nil
+ end
+ nestLevel += 1
+ end
+ parentNodeID = groupNode.attributes["parent"]? groupNode.attributes["parent"].value : nil
+ parentOrder = parentNodeID ? @allGroupNodeIDsInOrder.index(parentNodeID)+1 : nil
+ @groups[groupOrder] = {
+ params: {
+ type: "Quire",
+ title: "",
+ direction: "",
+ nestLevel: nestLevel
+ },
+ tacketed: [],
+ sewing: [],
+ parentOrder: parentOrder,
+ memberOrders: [],
+ noteTitles: []
+ }
+ end
+ # MAP attributes for all groups
+ @groups.each do |groupOrder, attributes|
+ groupNodeID = @allGroupNodeIDsInOrder[groupOrder-1]
+ mapTargetSearchText = "//x:map[@target='#"+groupNodeID+"']"
+ groupMappingNodes = xml.xpath(mapTargetSearchText, "x" => "http://schoenberginstitute.org/schema/collation")
+ if not groupMappingNodes.empty?
+ groupMappingNode = groupMappingNodes[0] # Only 1 mapping per group
+ groupTermTargets = groupMappingNode.children[1].attributes["target"].value.split(" ")
+ groupTermTargets.each do |target|
+ termSearchText = "//x:term[@xml:id='"+target[1..-1]+"']"
+ groupTerm = xml.xpath(termSearchText, "x" => "http://schoenberginstitute.org/schema/collation")[0]
+ groupTermTaxonomyID = groupTerm.parent.attributes["id"].value
+ groupTermTaxonomyID=="group_title" ? @groups[groupOrder][:params][:title]=groupTerm.text : nil
+ groupTermTaxonomyID=="group_type" ? @groups[groupOrder][:params][:type]=groupTerm.text : nil
+ groupTermTaxonomyID=="group_direction" ? @groups[groupOrder][:params][:direction]=groupTerm.text : nil
+ groupTermTaxonomyID=="group_sewing" ? @groups[groupOrder][:sewing]=groupTerm.text.split(" ") : nil
+ groupTermTaxonomyID=="group_tacketed" ? @groups[groupOrder][:tacketed]=groupTerm.text.split(" ") : nil
+ groupTermTaxonomyID=="group_members" ? @groups[groupOrder][:memberOrders]=groupTerm.text.split(" ") : nil
+ if groupTermTaxonomyID=="note_title"
+ @groups[groupOrder][:noteTitles].push(groupTerm.text) unless @groups[groupOrder][:noteTitles].include? groupTerm.text
+ end
+ end
+ end
+ end
+
+
+ # Generate all attributes for Leafs
+ allLeafNodes = xml.xpath('//x:leaf', "x" => "http://schoenberginstitute.org/schema/collation")
+ allLeafNodes.each_with_index do |leafNode, index|
+ leafNodeID = leafNode.attributes["id"].value
+ stub = leafNode.attributes["stub"] ? "Original" : "None"
+ type = "None"
+ conjoinedToNodeID = nil
+ leafOrder = index+1
+ parentNodeID = nil
+ leafNode.children.each do |child|
+ if child.name == "mode"
+ type = child.attributes["val"] ? child.attributes["val"].value.capitalize : "None"
+ end
+ if child.name == "q"
+ parentNodeID = child.attributes["target"] ? child.attributes["target"].value : nil
+ child.children.each do |subChild|
+ if subChild.attributes["target"]
+ conjoinedToNodeID = subChild.attributes["target"].value[1..-1]
+ end
+ end
+ end
+ end
+ @allLeafNodeIDsInOrder.push(leafNodeID)
+ nestLevel = 1
+ parentOrder = 1
+ if parentNodeID
+ parentOrder = @allGroupNodeIDsInOrder.index(parentNodeID[1..-1])+1
+ parentGroup = @groups[parentOrder]
+ nestLevel = parentGroup[:params][:nestLevel]
+ end
+ @leafs[leafOrder] = {
+ params: {
+ material: "None",
+ type: type,
+ attached_above: "None",
+ attached_below: "None",
+ stub: stub,
+ nestLevel: nestLevel
+ },
+ conjoined_leaf_order: conjoinedToNodeID,
+ parentOrder: parentOrder,
+ rectoOrder: leafOrder,
+ versoOrder: leafOrder,
+ noteTitles: []
+ }
+ @rectos[leafOrder] = {
+ params: {
+ folio_number: nil,
+ page_number: nil,
+ texture: "None",
+ image: {},
+ script_direction: "None"
+ },
+ parentOrder: leafOrder,
+ noteTitles: []
+ }
+ @versos[leafOrder] = {
+ params: {
+ folio_number: nil,
+ page_number: nil,
+ texture: "None",
+ image: {},
+ script_direction: "None"
+ },
+ parentOrder: leafOrder,
+ noteTitles: []
+ }
+ end
+
+ # In @groups, Update sewing, tacketed and memberOrders from nodeIDs to globalOrders
+ @groups.each do |groupOrder, attributes|
+ sewing = attributes[:sewing].map {|leafNodeID| @allLeafNodeIDsInOrder.index(leafNodeID[1..-1])+1}
+ tacketed = attributes[:tacketed].map {|leafNodeID| @allLeafNodeIDsInOrder.index(leafNodeID[1..-1])+1}
+ memberOrders = []
+ attributes[:memberOrders].each do |memberNodeID|
+ if memberNodeID.include? "q"
+ memberOrder = @allGroupNodeIDsInOrder.index(memberNodeID[1..-1])+1
+ memberOrders.push("Group_"+memberOrder.to_s)
+ else
+ memberOrder = @allLeafNodeIDsInOrder.index(memberNodeID[1..-1])+1
+ memberOrders.push("Leaf_"+memberOrder.to_s)
+ end
+ end
+ @groups[groupOrder][:sewing] = sewing
+ @groups[groupOrder][:tacketed] = tacketed
+ @groups[groupOrder][:memberOrders] = memberOrders
+ end
+
+ # In @leafs, Update conjoined_to from nodeIDs to globalOrders.
+ # Also Map material, attachment_methods (for Leaves), texture, script_direction, page_number (for Sides) and noteTitles.
+ @leafs.each do |leafOrder, attributes|
+ if @leafs[leafOrder][:conjoined_leaf_order]
+ @leafs[leafOrder][:conjoined_leaf_order] = @allLeafNodeIDsInOrder.index(attributes[:conjoined_leaf_order])+1
+ end
+ leafNodeID = @allLeafNodeIDsInOrder[leafOrder-1]
+ mapTargetSearchText = "//x:map[@target='#"+leafNodeID+"']"
+ leafMappingNodes = xml.xpath(mapTargetSearchText, "x" => "http://schoenberginstitute.org/schema/collation")
+ if not leafMappingNodes.empty?
+ leafMappingNodes.each do |leafMappingNode|
+ if leafMappingNode.attributes["side"]
+ sideTermTargets = leafMappingNode.children[1].attributes["target"].value.split(" ")
+ sideTermTargets.each do |target|
+ if target =~ URI::regexp
+ # This is an Image URL Map
+ if leafMappingNode.attributes["side"].value=="recto"
+ @rectos[leafOrder][:params][:image][:url] = target
+ @rectos[leafOrder][:params][:image][:label] = target.split("/")[-1]
+ else
+ @versos[leafOrder][:params][:image][:url] = target
+ @versos[leafOrder][:params][:image][:label] = target.split("/")[-1]
+ end
+ elsif target[1..-1]=="manifest_DIYImages"
+ if leafMappingNode.attributes["side"].value=="recto"
+ @rectos[leafOrder][:params][:image][:manifestID]="DIYImages"
+ @rectos[leafOrder][:params][:image][:label] = @rectos[leafOrder][:params][:image][:label].split("_", 2)[1]
+ else
+ @versos[leafOrder][:params][:image][:manifestID]="DIYImages"
+ @versos[leafOrder][:params][:image][:label] = @versos[leafOrder][:params][:image][:label].split("_", 2)[1]
+ end
+ else
+ termSearchText = "//x:term[@xml:id='"+target[1..-1]+"']"
+ sideTerms = xml.xpath(termSearchText, "x" => "http://schoenberginstitute.org/schema/collation")
+ if not sideTerms.empty?
+ sideTerm = sideTerms[0]
+ sideTermTaxonomyID = sideTerm.parent.attributes["id"].value
+ if leafMappingNode.attributes["side"].value=="recto"
+ sideTermTaxonomyID=="side_texture" ? @rectos[leafOrder][:params][:texture]=sideTerm.text : nil
+ sideTermTaxonomyID=="side_script_direction" ? @rectos[leafOrder][:params][:script_direction]=sideTerm.text : nil
+ sideTermTaxonomyID=="side_page_number" ? @rectos[leafOrder][:params][:page_number]=sideTerm.text : nil
+ sideTermTaxonomyID=="manifests" ? @rectos[leafOrder][:params][:image][:manifestID]=sideTerm.attributes["id"].value.split("_")[1] : nil
+ if sideTermTaxonomyID=="note_title"
+ @rectos[leafOrder][:noteTitles].push(sideTerm.text) unless @rectos[leafOrder][:noteTitles].include? sideTerm.text
+ end
+ else
+ sideTermTaxonomyID=="side_texture" ? @versos[leafOrder][:params][:texture]=sideTerm.text : nil
+ sideTermTaxonomyID=="side_script_direction" ? @versos[leafOrder][:params][:script_direction]=sideTerm.text : nil
+ sideTermTaxonomyID=="side_page_number" ? @versos[leafOrder][:params][:page_number]=sideTerm.text : nil
+ sideTermTaxonomyID=="manifests" ? @versos[leafOrder][:params][:image][:manifestID]=sideTerm.attributes["id"].value.split("_")[1] : nil
+ if sideTermTaxonomyID=="note_title"
+ @versos[leafOrder][:noteTitles].push(sideTerm.text) unless @versos[leafOrder][:noteTitles].include? sideTerm.text
+ end
+ end
+ end
+ end
+ end
+ else
+ leafTermTargets = leafMappingNode.children[1].attributes["target"].value.split(" ")
+ leafTermTargets.each do |target|
+ termSearchText = "//x:term[@xml:id='"+target[1..-1]+"']"
+ leafTerms = xml.xpath(termSearchText, "x" => "http://schoenberginstitute.org/schema/collation")
+ if not leafTerms.empty?
+ leafTerm = leafTerms[0]
+ leafTermTaxonomyID = leafTerm.parent.attributes["id"].value
+ leafTermTaxonomyID=="leaf_material" ? @leafs[leafOrder][:params][:material]=leafTerm.text : nil
+ if leafTermTaxonomyID=="note_title"
+ @leafs[leafOrder][:noteTitles].push(leafTerm.text) unless @leafs[leafOrder][:noteTitles].include? leafTerm.text
+ end
+ if leafTermTaxonomyID=='leaf_attachment_method'
+ leafTerm.attributes["id"].value.include?("Above") ? @leafs[leafOrder][:params][:attached_above]=leafTerm.text : nil
+ leafTerm.attributes["id"].value.include?("Below") ? @leafs[leafOrder][:params][:attached_below]=leafTerm.text : nil
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ # Generate all attributes for Notes
+ allNotes = xml.xpath('//x:note', "x" => "http://schoenberginstitute.org/schema/collation")
+ allNotes.each_with_index do |noteNode, noteOrder|
+ noteNodeID = noteNode.attributes["id"].value
+ type = noteNode.attributes["type"].value
+ title = ""
+ description = noteNode.text
+ show = false
+ @projectInformation[:noteTypes].push(type)
+ # MAP the noteTitle and show for all notes
+ mapTargetSearchText = "//x:map[@target='#"+noteNodeID+"']"
+ noteMappingNodes = xml.xpath(mapTargetSearchText, "x" => "http://schoenberginstitute.org/schema/collation")
+ if not noteMappingNodes.empty?
+ noteMappingNode = noteMappingNodes[0] # Only 1 mapping per note
+ noteTermTargets = noteMappingNode.children[1].attributes["target"].value.split(" ")
+ noteTermTargets.each do |target|
+ termSearchText = "//x:term[@xml:id='"+target[1..-1]+"']"
+ noteTerms = xml.xpath(termSearchText, "x" => "http://schoenberginstitute.org/schema/collation")
+ if not noteTerms.empty?
+ noteTerm = noteTerms[0]
+ noteTermTaxonomyID = noteTerm.parent.attributes["id"].value
+ noteTermTaxonomyID=="note_title" ? title=noteTerm.text : nil
+ noteTermTaxonomyID=="note_show" ? show=true : nil
+ end
+ end
+ end
+ # MAP Groups, Leafs, Rectos, Versos for this Note
+ groupOrders = []
+ @groups.each do |groupOrder, attributes|
+ if attributes[:noteTitles].include? title
+ groupOrders.push(groupOrder)
+ end
+ end
+ leafOrders = []
+ @leafs.each do |leafOrder, attributes|
+ if attributes[:noteTitles].include? title
+ leafOrders.push(leafOrder)
+ end
+ end
+ rectoOrders = []
+ @rectos.each do |rectoOrder, attributes|
+ if attributes[:noteTitles].include? title
+ rectoOrders.push(rectoOrder)
+ end
+ end
+ versoOrders = []
+ @versos.each do |versoOrder, attributes|
+ if attributes[:noteTitles].include? title
+ versoOrders.push(versoOrder)
+ end
+ end
+ @notes[noteOrder] = {
+ params: {
+ title: title,
+ type: type,
+ description: description,
+ show: show
+ },
+ objects: {
+ Group: groupOrders,
+ Leaf: leafOrders,
+ Recto: rectoOrders,
+ Verso: versoOrders
+ }
+ }
+ end
+
+ # Everything is fine upto this point unless the xml import is driectly from Dot's Model.
+ # In that case, we have to generate the memberOrders attribute for each Group manually.
+ # We will loose the actual memberOrders. Here we add the Group members first and then Leaf members.
+ taxonomySearchText = "//x:taxonomy[@xml:id='group_members']"
+ groupMembersTermNodes = xml.xpath(taxonomySearchText, "x" => "http://schoenberginstitute.org/schema/collation")
+ if groupMembersTermNodes.empty?
+ # Need to handle adding members to Groups
+ @groups.each do |groupOrder, attributes|
+ if attributes[:parentOrder]
+ @groups[attributes[:parentOrder]][:memberOrders].push("Group_"+groupOrder.to_s)
+ end
+ end
+ @leafs.each do |leafOrder, attributes|
+ if attributes[:parentOrder]
+ @groups[attributes[:parentOrder]][:memberOrders].push("Leaf_"+leafOrder.to_s)
+ end
+ end
+ end
+
+ jsonImport = {
+ project: @projectInformation,
+ Groups: @groups,
+ Leafs: @leafs,
+ Rectos: @rectos,
+ Versos: @versos,
+ Notes: @notes
+ }
+
+ handleJSONImport(JSON.parse(jsonImport.to_json))
+
+ end
+ end
+end
diff --git a/viscoll-api/app/helpers/controller_helper/leafs_helper.rb b/viscoll-api/app/helpers/controller_helper/leafs_helper.rb
new file mode 100644
index 00000000..a40ac773
--- /dev/null
+++ b/viscoll-api/app/helpers/controller_helper/leafs_helper.rb
@@ -0,0 +1,83 @@
+module ControllerHelper
+ module LeafsHelper
+
+ # Auto-Conjoin the given leaves
+ def autoConjoinLeaves(leaves, oddLeafNumber)
+ targetLeaves = leaves.dup
+ leafIds = leaves.collect { |leaf| leaf.id.to_s }
+ if targetLeaves.size.odd?
+ oddLeaf = targetLeaves[oddLeafNumber-1]
+ unless oddLeaf.conjoined_to.blank?
+ @project.leafs.find(oddLeaf.conjoined_to).update(conjoined_to: nil)
+ oddLeaf.update(conjoined_to: nil)
+ end
+ targetLeaves.delete_at(oddLeafNumber-1)
+ leafIds.delete_at(oddLeafNumber-1)
+ end
+ targetLeaves.each do |leaf|
+ if leaf.conjoined_to && !leafIds.include?(leaf.conjoined_to)
+ old_conjoined_to_leaf = @project.leafs.find(leaf.conjoined_to)
+ if (old_conjoined_to_leaf.conjoined_to)
+ old_conjoined_to_leaf.update(conjoined_to: nil)
+ end
+ end
+ end
+ (targetLeaves.size/2).times do |i|
+ leafOne = targetLeaves[i]
+ leafTwo = targetLeaves[-i-1]
+ leafOne.update(conjoined_to: leafTwo.id.to_s)
+ leafTwo.update(conjoined_to: leafOne.id.to_s)
+ end
+ end
+
+ def update_attached_to
+ parent = @project.groups.find(@leaf.parentID)
+ memberOrder = parent.memberIDs.index(@leaf.id.to_s)
+ if memberOrder > 0
+ # This leaf is not the first leaf in the group
+ aboveLeaf = @project.leafs.find(parent.memberIDs[memberOrder-1])
+ aboveLeaf.update(attached_below: @leaf.attached_above)
+ end
+ if memberOrder < parent.memberIDs.length - 1
+ belowLeaf = @project.leafs.find(parent.memberIDs[memberOrder+1])
+ belowLeaf.update(attached_above: @leaf.attached_below)
+ end
+ end
+
+ def update_conjoined_partner(new_conjoined_to_leafID)
+ # VALIDATIONS
+ conjoinedToErrors = []
+ begin
+ new_conjoined_to_leaf = @project.leafs.find(new_conjoined_to_leafID)
+ rescue Exception => e
+ if new_conjoined_to_leafID
+ conjoinedToErrors.push("leaf not found with id "+new_conjoined_to_leafID)
+ render json: {leaf: conjoinedToErrors}, status: :unprocessable_entity
+ return
+ end
+ end
+ if (@leaf.conjoined_to)
+ @old_conjoined_to_leaf = @project.leafs.find(@leaf.conjoined_to)
+ end
+ if (@old_conjoined_to_leaf)
+ @old_conjoined_to_leaf.update(conjoined_to: nil)
+ end
+ if new_conjoined_to_leaf
+ if (new_conjoined_to_leaf.conjoined_to)
+ new_conjoined_to_leaf_conjoined_to_leaf = @project.leafs.find(new_conjoined_to_leaf.conjoined_to)
+ if (new_conjoined_to_leaf_conjoined_to_leaf)
+ new_conjoined_to_leaf_conjoined_to_leaf.update(conjoined_to: nil)
+ end
+ end
+ new_conjoined_to_leaf.update(conjoined_to: @leaf.id.to_s)
+ end
+ end
+
+ def handle_paper_update(leaf)
+ recto = @project.sides.find(leaf.rectoID)
+ verso = @project.sides.find(leaf.versoID)
+ recto.update(:texture => "None")
+ verso.update(:texture => "None")
+ end
+ end
+end
diff --git a/viscoll-api/app/helpers/controller_helper/projects_helper.rb b/viscoll-api/app/helpers/controller_helper/projects_helper.rb
new file mode 100644
index 00000000..8f810e28
--- /dev/null
+++ b/viscoll-api/app/helpers/controller_helper/projects_helper.rb
@@ -0,0 +1,261 @@
+require 'net/http'
+module ControllerHelper
+ module ProjectsHelper
+ include ControllerHelper::LeafsHelper
+ def addGroupsLeafsConjoin(project, allGroups, folioNumber, pageNumber, startingTexture)
+ groupIDs = []
+ allGroups.each do |groupInfo|
+ direction = groupInfo["direction"]
+ group = Group.new({project_id: project, title:"Default", type:"Quire", direction: direction})
+
+ # Create leaves
+ newlyAddedLeafs = []
+ newlyAddedLeafIDs = []
+ groupInfo["leaves"].times do |i|
+ leaf = Leaf.new({project_id: project, parentID: "Group_" + group.id.to_s})
+ leaf.save()
+ newlyAddedLeafs.push(leaf)
+ newlyAddedLeafIDs.push(leaf.id.to_s)
+ end
+ # Add newly created leaves to this group
+ group = group.add_members(newlyAddedLeafIDs, 1, false)
+ # Auto-Conjoin newly added leaves in this group
+ if groupInfo["conjoin"]
+ autoConjoinLeaves(newlyAddedLeafs, groupInfo["oddLeaf"])
+ end
+ group.save
+ groupIDs.push(group.id.to_s)
+ # Add folio numbers
+ if folioNumber
+ newlyAddedLeafs.each do |leaf|
+ recto = Side.find(leaf.rectoID)
+ verso = Side.find(leaf.versoID)
+ recto.update_attribute(:folio_number, folioNumber.to_s+"R")
+ verso.update_attribute(:folio_number, folioNumber.to_s+"V")
+ folioNumber += 1
+ end
+ elsif pageNumber
+ newlyAddedLeafs.each do |leaf|
+ recto = Side.find(leaf.rectoID)
+ verso = Side.find(leaf.versoID)
+ recto.update_attribute(:page_number, pageNumber.to_s)
+ pageNumber += 1
+ verso.update_attribute(:page_number, pageNumber.to_s)
+ pageNumber += 1
+ end
+ end
+ # Assign side texture
+ assignTexture(newlyAddedLeafs, startingTexture)
+ end
+ # Add groups to project
+ project.add_groupIDs(groupIDs, 0)
+ end
+
+ def getManifestInformation(url)
+ images = []
+ begin
+ response = JSON.parse(Net::HTTP.get(URI(url)))
+ response["sequences"][0]["canvases"].each do |canvas|
+ images.push({label: canvas["label"], url: canvas["images"][0]["resource"]["service"]["@id"]})
+ end
+ rescue
+ return {name: "Unparseable manifest URL", images: images}
+ end
+ return {name: response["label"][0..150], images: images}
+ end
+
+ def assignTexture(leaves, startingTexture)
+ # Create pattern of hair and flesh depending on starting texture value
+ textures = [startingTexture]
+ textureOptions = []
+ if startingTexture == "Hair"
+ textureOptions += ["Flesh", "Hair"]
+ else
+ textureOptions += ["Hair", "Flesh"]
+ end
+ leaves.count.times do |i|
+ textures += [textureOptions[i%2], textureOptions[i%2]]
+ end
+ # Update sides to have hair/flesh
+ i = 0
+ leaves.each do | leaf|
+ recto = Side.find(leaf.rectoID)
+ verso = Side.find(leaf.versoID)
+ if leaf.conjoined_to != nil
+ recto.update_attribute(:texture, textures[i])
+ i += 1
+ verso.update_attribute(:texture, textures[i])
+ i += 1
+ else
+ recto.update_attribute(:texture, "Hair")
+ verso.update_attribute(:texture, "Flesh")
+ end
+ end
+ end
+
+ def generateResponse()
+ @project.reload
+ @projectInformation = {}
+ @groupIDs = @project.groupIDs
+ @leafIDs = []
+ @rectoIDs = []
+ @versoIDs = []
+ @groups = {}
+ @leafs = {}
+ @rectos = {}
+ @versos = {}
+ @notes = {}
+
+ @projectInformation = {
+ "id": @project.id.to_s,
+ "title": @project.title,
+ "shelfmark": @project.shelfmark,
+ "metadata": @project.metadata,
+ "preferences": @project.preferences,
+ "manifests": @project.manifests,
+ "noteTypes": @project.noteTypes
+ }
+ @project.manifests.each do |manifestID, manifest|
+ manifestInformation = getManifestInformation(manifest[:url])
+ manifestName = manifest[:name] ? manifest[:name] : manifestInformation[:name]
+ if manifestName.length>50
+ manifestName = manifestName[0,47] + "..."
+ end
+ @projectInformation[:manifests][manifestID][:images] = manifestInformation[:images].map { |image| image.merge({manifestID: manifestID})}
+ @projectInformation[:manifests][manifestID][:name] = manifestName
+ end
+ # Generate all DIY images for this Project
+ @diyImages = []
+ User.find(@project.user_id).images.all.each do |image|
+ if image.projectIDs.include? @project.id.to_s
+ @diyImages.push({
+ label: image.filename,
+ url: @base_api_url+"/images/"+image.id.to_s+"_"+image.filename,
+ manifestID: "DIYImages"
+ })
+ end
+ end
+ # @projectInformation[:manifests][:DIYImages] = {
+ # id: "DIYImages",
+ # images: @diyImages,
+ # name: "Uploaded Images"
+ # }
+
+ @groupIDs.each_with_index do | groupID, index|
+ group = @project.groups.find(groupID)
+ # group = Group.find(groupID)
+ @groups[group.id.to_s] = {
+ "id": group.id.to_s,
+ "type": group.type,
+ "direction": group.direction,
+ "title": group.title,
+ "tacketed": group.tacketed,
+ "sewing": group.sewing,
+ "nestLevel": group.nestLevel,
+ "parentID": group.parentID,
+ "notes": [],
+ "memberIDs": group.memberIDs,
+ "memberType": "Group",
+ }
+ end
+ @groups.each do | groupID, group |
+ if group[:nestLevel] == 1
+ getLeafMembers(group[:memberIDs])
+ end
+ end
+ @project.leafs.each do | leaf |
+ @leafs[leaf.id.to_s] = {
+ "id": leaf.id.to_s,
+ "material": leaf.material,
+ "type": leaf.type,
+ "conjoined_to": leaf.conjoined_to,
+ "attached_above": leaf.attached_above,
+ "attached_below": leaf.attached_below,
+ "stub": leaf.stub,
+ "nestLevel": leaf.nestLevel,
+ "parentID": leaf.parentID,
+ "rectoID": leaf.rectoID,
+ "versoID": leaf.versoID,
+ "notes": [],
+ "memberType": "Leaf",
+ }
+ end
+
+
+
+ @project.sides.each do | side |
+ parentOrder = @leafIDs.index(side.parentID) + 1
+ obj = {
+ "id": side.id.to_s,
+ "parentID": side.parentID,
+ "folio_number": side.folio_number,
+ "page_number": side.page_number,
+ "texture": side.texture,
+ "image": side.image,
+ "script_direction": side.script_direction,
+ "notes": [],
+ "memberType": side.id[0] == "R" ? "Recto" : "Verso"
+ }
+ if side.id[0] == "R"
+ @rectos[side.id.to_s] = obj
+ elsif side.id[0] == "V"
+ @versos[side.id.to_s] = obj
+ end
+ end
+
+ # Generate list of recto and verso ID's
+ @leafIDs.each do | leafID |
+ leaf = @leafs[leafID]
+ @rectoIDs.push(leaf[:rectoID])
+ @versoIDs.push(leaf[:versoID])
+ end
+
+ @project.notes.each do | note |
+ @notes[note.id.to_s] = {
+ "id": note.id.to_s,
+ "title": note.title,
+ "type": note.type,
+ "description": note.description,
+ "show": note.show,
+ "objects": note.objects,
+ }
+ note.objects["Group"].each do | id |
+ @groups[id][:notes].append(note.id.to_s)
+ end
+ note.objects["Leaf"].each do | id |
+ @leafs[id][:notes].append(note.id.to_s)
+ end
+ note.objects["Recto"].each do | id |
+ @rectos[id][:notes].append(note.id.to_s)
+ end
+ note.objects["Verso"].each do | id |
+ @versos[id][:notes].append(note.id.to_s)
+ end
+ end
+
+ return {
+ "project": @projectInformation,
+ "groupIDs": @groupIDs,
+ "leafIDs": @leafIDs,
+ "rectoIDs": @rectoIDs,
+ "versoIDs": @versoIDs,
+ "groups": @groups,
+ "leafs": @leafs,
+ "rectos": @rectos,
+ "versos": @versos,
+ "notes": @notes,
+ }
+ end
+
+ # Populate @leafIDs recursively
+ def getLeafMembers(memberIDs)
+ memberIDs.each_with_index do | memberID, index |
+ if memberID[0] == "G"
+ getLeafMembers(@groups[memberID][:memberIDs])
+ elsif memberID[0] == "L"
+ @leafIDs.push(memberID)
+ end
+ end
+ end
+ end
+end
diff --git a/viscoll-api/app/helpers/validation_helper/group_validation_helper.rb b/viscoll-api/app/helpers/validation_helper/group_validation_helper.rb
new file mode 100644
index 00000000..cbecf78d
--- /dev/null
+++ b/viscoll-api/app/helpers/validation_helper/group_validation_helper.rb
@@ -0,0 +1,113 @@
+module ValidationHelper
+ module GroupValidationHelper
+ def validateAdditionalGroupParams(noOfGroups, parentGroupID, memberOrder, noOfLeafs, conjoin, oddMemberLeftOut)
+ additionalErrors = {noOfGroups:[], parentGroupID:[], memberOrder:[], noOfLeafs: [], conjoin: [], oddMemberLeftOut: []}
+ haveErrors = false
+ if (noOfGroups==nil)
+ additionalErrors[:noOfGroups].push("is required")
+ haveErrors = true
+ elsif (!noOfGroups.is_a?(Integer))
+ additionalErrors[:noOfGroups].push("should be an Integer")
+ haveErrors = true
+ elsif (noOfGroups < 1 or noOfGroups > 999)
+ additionalErrors[:noOfGroups].push("should range from 1 to 999")
+ haveErrors = true
+ end
+ if parentGroupID != nil && !Group.where(id: parentGroupID).exists?
+ haveErrors = true
+ additionalErrors[:parentGroupID].push("group not found with id "+parentGroupID)
+ end
+ if (parentGroupID!=nil && memberOrder==nil)
+ additionalErrors[:memberOrder].push("is required")
+ haveErrors = true
+ elsif (parentGroupID!=nil && !memberOrder.is_a?(Integer))
+ additionalErrors[:memberOrder].push("should be an Integer")
+ haveErrors = true
+ elsif (parentGroupID!=nil && memberOrder < 1)
+ additionalErrors[:memberOrder].push("should be greater than 0")
+ haveErrors = true
+ end
+ if (noOfLeafs != nil and !noOfLeafs.is_a?(Integer))
+ additionalErrors[:noOfLeafs].push("should be an Integer")
+ haveErrors = true
+ elsif (noOfLeafs != nil and (noOfLeafs < 1 or noOfLeafs > 999))
+ additionalErrors[:noOfLeafs].push("should range from 1 to 999")
+ haveErrors = true
+ end
+ if (conjoin != nil)
+ if (!conjoin.is_a?(Boolean))
+ additionalErrors[:conjoin].push("should be a Boolean")
+ haveErrors = true
+ elsif (conjoin and (noOfLeafs != nil and noOfLeafs == 1))
+ additionalErrors[:conjoin].push("should be false if the number of leaves is 1")
+ haveErrors = true
+ end
+ end
+ if (oddMemberLeftOut != nil)
+ if (!oddMemberLeftOut.is_a?(Integer))
+ additionalErrors[:oddMemberLeftOut].push("should be an Integer")
+ haveErrors = true
+ elsif (oddMemberLeftOut < 1 or oddMemberLeftOut > noOfLeafs)
+ additionalErrors[:oddMemberLeftOut].push("should range from 1 to the number of leaves")
+ haveErrors = true
+ elsif (noOfLeafs.even?)
+ additionalErrors[:oddMemberLeftOut].push("should be empty if the number of leaves is even")
+ haveErrors = true
+ end
+ end
+
+ if additionalErrors[:noOfGroups] == []
+ additionalErrors = additionalErrors.without(:noOfGroups)
+ end
+ if additionalErrors[:parentGroupID] == []
+ additionalErrors = additionalErrors.without(:parentGroupID)
+ end
+ if additionalErrors[:memberOrder] == []
+ additionalErrors = additionalErrors.without(:memberOrder)
+ end
+ if additionalErrors[:noOfLeafs] == []
+ additionalErrors = additionalErrors.without(:noOfLeafs)
+ end
+ if additionalErrors[:conjoin] == []
+ additionalErrors = additionalErrors.without(:conjoin)
+ end
+ if additionalErrors[:oddMemberLeftOut] == []
+ additionalErrors = additionalErrors.without(:oddMemberLeftOut)
+ end
+ return additionalErrors
+ end
+
+ def validateGroupBatchDelete(allGroups)
+ errors = []
+ allGroups.each do |groupID|
+ unless Group.where(id: groupID).exists?
+ errors.push("group not found with id "+groupID)
+ end
+ end
+ return errors
+ end
+
+ def validateGroupBatchUpdate(allGroups)
+ errors = []
+ allGroups.each do |group_params|
+ haveError = false
+ error = {id: [], attributes: {type: []}}
+ groupID = group_params[:id]
+ type = group_params[:attributes][:type]
+ unless Group.where(id: groupID).exists?
+ haveError = true
+ error[:id].push("group not found with id "+groupID)
+ end
+ if (type != nil and type!="Quire" and type!="Booklet")
+ error[:attributes][:type].push("should be either Quire or Booklet")
+ haveError = true
+ end
+ if haveError
+ errors.push(error)
+ end
+ end
+ return errors
+ end
+
+ end
+end
diff --git a/viscoll-api/app/helpers/validation_helper/leaf_validation_helper.rb b/viscoll-api/app/helpers/validation_helper/leaf_validation_helper.rb
new file mode 100644
index 00000000..00cc21b9
--- /dev/null
+++ b/viscoll-api/app/helpers/validation_helper/leaf_validation_helper.rb
@@ -0,0 +1,70 @@
+module ValidationHelper
+ module LeafValidationHelper
+
+ def validateLeafParams(project_id, parentID)
+ leafErrors = {project_id: [], parentID: []}
+ if (project_id==nil)
+ leafErrors[:project_id].push("is required")
+ elsif (!project_id.is_a?(String))
+ leafErrors[:project_id].push("should be a String")
+ else
+ begin
+ @project = Project.find(project_id)
+ rescue Exception => e
+ leafErrors[:project_id].push("project not found")
+ end
+ end
+ if (parentID==nil)
+ leafErrors[:parentID].push("is required")
+ elsif (!parentID.is_a?(String))
+ leafErrors[:parentID].push("should be a String")
+ else
+ begin
+ @group = Group.find(parentID)
+ if (@group.project_id.to_s != project_id)
+ leafErrors[:parentID].push("Group with parentID does not have project_id as a member")
+ end
+ rescue Exception => e
+ leafErrors[:parentID].push("group not found")
+ end
+ end
+ return leafErrors
+ end
+
+ def validateAdditionalLeafParams(project_id, parentGroupID, memberOrder, noOfLeafs, conjoin, oddMemberLeftOut)
+ additionalErrors = {memberOrder: [], noOfLeafs: [], conjoin: [], oddMemberLeftOut: []}
+ if (memberOrder==nil)
+ additionalErrors[:memberOrder].push("is required")
+ elsif (!memberOrder.is_a?(Integer))
+ additionalErrors[:memberOrder].push("should be an Integer")
+ elsif (memberOrder < 1)
+ additionalErrors[:memberOrder].push("should be greater than 0")
+ end
+ if (noOfLeafs==nil)
+ additionalErrors[:noOfLeafs].push("is required")
+ elsif (!noOfLeafs.is_a?(Integer))
+ additionalErrors[:noOfLeafs].push("should be an Integer")
+ elsif (noOfLeafs < 1 or noOfLeafs > 999)
+ additionalErrors[:noOfLeafs].push("should range from 1 to 999")
+ end
+ if (conjoin != nil)
+ if (!conjoin.is_a?(Boolean))
+ additionalErrors[:conjoin].push("should be a Boolean")
+ elsif (conjoin and noOfLeafs == 1)
+ additionalErrors[:conjoin].push("should be false if the number of leaves is 1")
+ end
+ end
+ if (oddMemberLeftOut != nil)
+ if (!oddMemberLeftOut.is_a?(Integer))
+ additionalErrors[:oddMemberLeftOut].push("should be an Integer")
+ elsif (oddMemberLeftOut < 1 or oddMemberLeftOut > noOfLeafs)
+ additionalErrors[:oddMemberLeftOut].push("should range from 1 to the number of leaves")
+ elsif (noOfLeafs.even?)
+ additionalErrors[:oddMemberLeftOut].push("should be present only if the number of leaves is odd")
+ end
+ end
+ return additionalErrors
+ end
+
+ end
+end
diff --git a/viscoll-api/app/helpers/validation_helper/project_validation_helper.rb b/viscoll-api/app/helpers/validation_helper/project_validation_helper.rb
new file mode 100644
index 00000000..1e6a7bcc
--- /dev/null
+++ b/viscoll-api/app/helpers/validation_helper/project_validation_helper.rb
@@ -0,0 +1,50 @@
+module ValidationHelper
+ module ProjectValidationHelper
+ def validateProjectCreateGroupsParams(allGroups)
+ @group_errors = []
+ if not allGroups
+ allGroups = []
+ end
+ allGroups.each_with_index do |group, index|
+ haveGroupError = false
+ @group_error = {groupID: (index+1)}
+ @group_error[:leaves] = []
+ @group_error[:oddLeaf] = []
+ @group_error[:conjoin] = []
+ leaves = group["leaves"]
+ oddLeaf = group["oddLeaf"]
+ conjoin = group["conjoin"]
+ if (!leaves.is_a?(Integer))
+ @group_error[:leaves].push("should be an Integer")
+ haveGroupError = true
+ elsif (leaves < 1)
+ @group_error[:leaves].push("should be greater than 0")
+ haveGroupError = true
+ end
+ if (leaves.is_a?(Integer) and leaves.odd?)
+ if (!oddLeaf.is_a?(Integer))
+ @group_error[:oddLeaf].push("should be an Integer")
+ haveGroupError = true
+ else
+ if (oddLeaf < 1)
+ @group_error[:oddLeaf].push("should be greater than 0")
+ haveGroupError = true
+ end
+ if (oddLeaf > leaves)
+ @group_error[:oddLeaf].push("cannot be greater than leaves")
+ haveGroupError = true
+ end
+ end
+ end
+ if (!conjoin.is_a?(Boolean))
+ @group_error[:conjoin].push("should be a Boolean")
+ haveGroupError = true
+ end
+ if (haveGroupError)
+ @group_errors.push(@group_error)
+ end
+ end
+ return {status: @group_errors.empty?, errors: @group_errors}
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/jobs/application_job.rb b/viscoll-api/app/jobs/application_job.rb
similarity index 100%
rename from app/jobs/application_job.rb
rename to viscoll-api/app/jobs/application_job.rb
diff --git a/viscoll-api/app/mailers/account_approval_mailer.rb b/viscoll-api/app/mailers/account_approval_mailer.rb
new file mode 100644
index 00000000..9b1e01a0
--- /dev/null
+++ b/viscoll-api/app/mailers/account_approval_mailer.rb
@@ -0,0 +1,11 @@
+class AccountApprovalMailer < ApplicationMailer
+ default from: RailsJwtAuth.mailer_sender
+
+ def sendApprovalStatus(user)
+ @user = User.find(user)
+ mail(
+ subject: "VisColl Account Approval",
+ to: @user.email,
+ )
+ end
+end
\ No newline at end of file
diff --git a/app/mailers/application_mailer.rb b/viscoll-api/app/mailers/application_mailer.rb
similarity index 57%
rename from app/mailers/application_mailer.rb
rename to viscoll-api/app/mailers/application_mailer.rb
index 286b2239..0060a22f 100644
--- a/app/mailers/application_mailer.rb
+++ b/viscoll-api/app/mailers/application_mailer.rb
@@ -1,4 +1,4 @@
class ApplicationMailer < ActionMailer::Base
- default from: 'from@example.com'
+ default from: 'utlviscoll@library.utoronto.ca'
layout 'mailer'
end
diff --git a/viscoll-api/app/mailers/feedback_mailer.rb b/viscoll-api/app/mailers/feedback_mailer.rb
new file mode 100644
index 00000000..39b7d4b9
--- /dev/null
+++ b/viscoll-api/app/mailers/feedback_mailer.rb
@@ -0,0 +1,13 @@
+class FeedbackMailer < ApplicationMailer
+ def sendFeedback(title, message, browserInformation, projectJSONExport, current_user)
+ @title = title
+ @message = message
+ @browserInformation = browserInformation
+ @projectJSONExport = projectJSONExport
+ @user = User.find(current_user)
+ mail(
+ subject: title,
+ to:"utlviscoll@library.utoronto.ca",
+ )
+ end
+end
diff --git a/viscoll-api/app/mailers/mailer.rb b/viscoll-api/app/mailers/mailer.rb
new file mode 100644
index 00000000..37ae24af
--- /dev/null
+++ b/viscoll-api/app/mailers/mailer.rb
@@ -0,0 +1,56 @@
+if defined?(ActionMailer)
+ class RailsJwtAuth::Mailer < ApplicationMailer
+ default from: RailsJwtAuth.mailer_sender
+
+ def confirmation_instructions(user)
+ @user = user
+ if RailsJwtAuth.confirmation_url
+ url, params = RailsJwtAuth.confirmation_url.split('?')
+ params = params ? params.split('&') : []
+ params.push("confirmation_token=#{@user.confirmation_token}")
+
+ @confirmation_url = "#{url}?#{params.join('&')}"
+ else
+ @confirmation_url = confirmation_url(confirmation_token: @user.confirmation_token)
+ end
+ subject = I18n.t('rails_jwt_auth.mailer.confirmation_instructions.subject')
+ # mail(to: @user.unconfirmed_email || @user.email, subject: subject)
+ toEmail = Rails.application.secrets.admin_email || "dummy-admin@library.utoronto.ca"
+ mail(to: toEmail, subject: subject)
+ end
+
+ def reset_password_instructions(user)
+ @user = user
+
+ if RailsJwtAuth.reset_password_url
+ url, params = RailsJwtAuth.reset_password_url.split('?')
+ params = params ? params.split('&') : []
+ params.push("reset_password_token=#{@user.reset_password_token}")
+
+ @reset_password_url = "#{url}?#{params.join('&')}"
+ else
+ @reset_password_url = password_url(reset_password_token: @user.reset_password_token)
+ end
+
+ subject = I18n.t('rails_jwt_auth.mailer.reset_password_instructions.subject')
+ mail(to: @user.email, subject: subject)
+ end
+
+ def set_password_instructions(user)
+ @user = user
+
+ if RailsJwtAuth.set_password_url
+ url, params = RailsJwtAuth.set_password_url.split('?')
+ params = params ? params.split('&') : []
+ params.push("reset_password_token=#{@user.reset_password_token}")
+
+ @reset_password_url = "#{url}?#{params.join('&')}"
+ else
+ @reset_password_url = password_url(reset_password_token: @user.reset_password_token)
+ end
+
+ subject = I18n.t('rails_jwt_auth.mailer.set_password_instructions.subject')
+ mail(to: @user.email, subject: subject)
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/assets/javascripts/channels/.keep b/viscoll-api/app/models/concerns/.keep
similarity index 100%
rename from app/assets/javascripts/channels/.keep
rename to viscoll-api/app/models/concerns/.keep
diff --git a/viscoll-api/app/models/group.rb b/viscoll-api/app/models/group.rb
new file mode 100644
index 00000000..994516ec
--- /dev/null
+++ b/viscoll-api/app/models/group.rb
@@ -0,0 +1,79 @@
+class Group
+ include Mongoid::Document
+ include Mongoid::Timestamps
+
+ # Fields
+ field :title, type: String, default: "None"
+ field :type, type: String, default: "Quire"
+ field :direction, type: String
+ field :tacketed, type: Array, default: []
+ field :sewing, type: Array, default: []
+ field :nestLevel, type: Integer, default: 1
+ field :parentID, type: String
+ field :memberIDs, type: Array, default: [] # eg [ id1, id2, ... ]
+
+ # Relations
+ belongs_to :project
+ has_and_belongs_to_many :notes, inverse_of: nil
+
+ # Callbacks
+ before_create :edit_ID
+ before_destroy :unlink_notes, :unlink_project, :unlink_group, :destroy_members
+
+
+ def edit_ID
+ self.id = "Group_"+self.id.to_s unless self.id.to_s[0] == "G"
+ end
+
+ # Add new members to this group
+ def add_members(memberIDs, startOrder, save=true)
+ if self.memberIDs.length==0
+ self.memberIDs = memberIDs
+ elsif
+ self.memberIDs.insert(startOrder-1, *memberIDs)
+ end
+ if save
+ self.save
+ end
+ return self
+ end
+
+ def remove_members(ids)
+ newList = self.memberIDs.reject{|id| ids.include?(id)}
+ self.memberIDs = newList
+ self.save
+ end
+
+ # If linked to note(s), remove link from the note(s)'s side
+ def unlink_notes
+ if self.notes
+ self.notes.each do | note |
+ note.objects[:Group].delete(self.id.to_s)
+ note.save
+ end
+ end
+ end
+
+ # Remove itself from project
+ def unlink_project
+ self.project.remove_groupID(self.id.to_s)
+ end
+
+ # Remove itself from parent group (if nested)
+ def unlink_group
+ if self.parentID != nil
+ Group.find(self.parentID).remove_members([self.id.to_s])
+ end
+ end
+
+ def destroy_members
+ self.memberIDs.each do | memberID |
+ if memberID[0] === "G"
+ Group.find(memberID).destroy
+ elsif memberID[0] === "L"
+ Leaf.find(memberID).destroy
+ end
+ end
+ end
+
+end
diff --git a/viscoll-api/app/models/image.rb b/viscoll-api/app/models/image.rb
new file mode 100644
index 00000000..7a17057f
--- /dev/null
+++ b/viscoll-api/app/models/image.rb
@@ -0,0 +1,36 @@
+class Image
+ include Mongoid::Document
+
+ # Fields
+ field :filename, type: String
+ field :fileID, type: String
+ field :metadata, type: Hash
+ field :projectIDs, type: Array, default: [] # List of projectIDs this image belongs to
+ field :sideIDs, type: Array, default: [] # List of sideIDs this image is mapped to
+
+ # Relations
+ belongs_to :user, inverse_of: :images
+
+ # Callbacks
+ before_destroy :unlink_sides_before_delete, :delete_file
+ validates_uniqueness_of :filename, :message => "Image with filename: '%{value}', already exists.", scope: :user
+
+ protected
+ # If linked to side(s), remove link from the side(s)
+ def unlink_sides_before_delete
+ self.sideIDs.each do |sideID|
+ if side = Side.where(:id => sideID).first
+ side.image = {}
+ side.save
+ end
+ end
+ end
+
+ def delete_file
+ path = "#{Rails.root}/public/uploads/#{self.fileID}"
+ if File.file?(path)
+ File.delete(path)
+ end
+ end
+
+end
diff --git a/viscoll-api/app/models/leaf.rb b/viscoll-api/app/models/leaf.rb
new file mode 100644
index 00000000..0d836263
--- /dev/null
+++ b/viscoll-api/app/models/leaf.rb
@@ -0,0 +1,85 @@
+class Leaf
+ include Mongoid::Document
+ include Mongoid::Timestamps
+
+ # Fields
+ field :material, type: String, default: "None"
+ field :type, type: String, default: "None"
+ field :conjoined_to, type: String
+ field :attached_above, type: String, default: "None"
+ field :attached_below, type: String, default: "None"
+ field :stubType, :as => :stub, type: String, default: "None"
+ field :parentID, type: String
+ field :nestLevel, type: Integer, default: 1
+ field :rectoID, type: String
+ field :versoID, type: String
+
+ # Relations
+ belongs_to :project
+ has_and_belongs_to_many :notes, inverse_of: nil
+
+ # Callbacks
+ before_create :edit_ID, :create_sides
+ before_destroy :unlink_notes, :destroy_sides, :update_parent_group
+
+
+ # Remove itself from its parent group
+ def remove_from_group
+ Group.find(self.parentID).remove_members([self.id.to_s])
+ end
+
+ protected
+ def edit_ID
+ self.id = "Leaf_"+self.id.to_s unless self.id.to_s[0]=="L"
+ end
+
+ # If linked to note(s), remove link from the note(s)'s side
+ def unlink_notes
+ self.notes.each do | note |
+ note.objects[:Leaf].delete(self.id.to_s)
+ note.save
+ end
+ end
+
+ # Create 2 sides(Recto & Verso) for this new leaf.
+ def create_sides
+ recto = Side.new({parentID: self.id.to_s, project: self.project})
+ verso = Side.new({parentID: self.id.to_s, project: self.project})
+ recto.id = "Recto_"+recto.id.to_s
+ verso.id = "Verso_"+verso.id.to_s
+ recto.save
+ verso.save
+ self.rectoID = recto.id
+ self.versoID = verso.id
+ end
+
+ # Destroy its two sides
+ def destroy_sides
+ Side.find(self.rectoID).destroy
+ Side.find(self.versoID).destroy
+ end
+
+ def update_attached_to
+ project = Project.find(self.project_id)
+ parent = project.groups.find(self.parentID)
+ memberOrder = parent.memberIDs.index(self.id.to_s)
+ if memberOrder > 0
+ # This leaf is not the first leaf in the group
+ aboveLeaf = project.leafs.find(parent.memberIDs[memberOrder-1])
+ aboveLeaf.update(attached_below: self.attached_above)
+ end
+ if memberOrder < parent.memberIDs.length - 1
+ belowLeaf = project.leafs.find(parent.memberIDs[memberOrder+1])
+ belowLeaf.update(attached_above: self.attached_below)
+ end
+ end
+
+ # Update leaf's parent Group's Tacketed & Sewing if it contains this leafID
+ def update_parent_group
+ group = Group.find(self.parentID)
+ group.tacketed.include?(self.id.to_s) ? group.tacketed.delete(self.id.to_s) : nil
+ group.sewing.include?(self.id.to_s) ? group.sewing.delete(self.id.to_s) : nil
+ group.save
+ end
+end
+
diff --git a/viscoll-api/app/models/note.rb b/viscoll-api/app/models/note.rb
new file mode 100644
index 00000000..84de2eb1
--- /dev/null
+++ b/viscoll-api/app/models/note.rb
@@ -0,0 +1,49 @@
+class Note
+ include Mongoid::Document
+ include Mongoid::Timestamps
+
+ # Fields
+ field :title, type: String, default: "None"
+ field :type, type: String, default: ""
+ field :description, type: String, default: ""
+ field :objects, type: Hash, default: {Group: [], Leaf: [], Recto: [], Verso: []}
+ field :show, type: Boolean, default: false
+
+ # Relations
+ belongs_to :project, inverse_of: :notes
+
+ # Validations
+ validates_presence_of :title, :message => "Note title is required."
+ validates_uniqueness_of :title, :message => "Note title should be unique.", scope: :project
+ validates_presence_of :type, :message => "Note type is required."
+
+ # Callbacks
+ before_destroy :update_objects_before_delete
+
+ def update_objects_before_delete
+ self.objects[:Group].each do |groupID|
+ if group = Group.where(:id => groupID).first
+ group.notes.delete(self)
+ group.save
+ end
+ end
+ self.objects[:Leaf].each do |leafID|
+ if leaf = Leaf.where(:id => leafID).first
+ leaf.notes.delete(self)
+ leaf.save
+ end
+ end
+ self.objects[:Recto].each do |sideID|
+ if side = Side.where(:id => sideID).first
+ side.notes.delete(self)
+ side.save
+ end
+ end
+ self.objects[:Verso].each do |sideID|
+ if side = Side.where(:id => sideID).first
+ side.notes.delete(self)
+ side.save
+ end
+ end
+ end
+end
diff --git a/viscoll-api/app/models/project.rb b/viscoll-api/app/models/project.rb
new file mode 100644
index 00000000..54bcdc0f
--- /dev/null
+++ b/viscoll-api/app/models/project.rb
@@ -0,0 +1,57 @@
+class Project
+ include Mongoid::Document
+ include Mongoid::Timestamps
+
+ # Fields
+ field :title, type: String
+ field :shelfmark, type: String # (eg) "MS 1754"
+ field :metadata, type: Hash, default: lambda { { } } # (eg) {date: "19th century"}
+ field :manifests, type: Hash, default: lambda { { } } # (eg) { "1234556": { id: "123456, url: ""} }
+ field :noteTypes, type: Array, default: ["Unknown"] # custom notetypes
+ field :preferences, type: Hash, default: lambda { { :showTips => true } }
+ field :groupIDs, type: Array, default: []
+
+ # Relations
+ belongs_to :user, inverse_of: :projects
+ has_many :groups, dependent: :delete
+ has_many :leafs, dependent: :delete
+ has_many :sides, dependent: :delete
+ has_many :notes, dependent: :delete
+
+ # Callbacks
+ before_destroy :unlink_images_before_delete
+
+ # Validations
+ validates_presence_of :title, :message => "Project title is required."
+ validates_uniqueness_of :title, :message => "Project title: '%{value}', must be unique.", scope: :user
+
+ def add_groupIDs(groupIDs, index)
+ if self.groupIDs.length == 0
+ self.groupIDs = groupIDs
+ else
+ self.groupIDs.insert(index, *groupIDs)
+ end
+ self.save()
+ end
+
+ def remove_groupID(groupID)
+ self.groupIDs.delete(groupID)
+ self.save()
+ end
+
+ def unlink_images_before_delete
+ Image.where(:user_id => self.user.id).each do |image|
+ # Unlink All Sides that belongs to this Project that has this Image mapped to it.
+ image.sideIDs.each do |sideID|
+ side = self.sides.where(:id => sideID).first
+ if side
+ side.image = {}
+ side.save
+ image.sideIDs.include?(sideID) ? image.sideIDs.delete(sideID) : nil
+ end
+ end
+ image.projectIDs.include?(self.id.to_s) ? image.projectIDs.delete(self.id.to_s) : nil
+ image.save
+ end
+ end
+end
diff --git a/viscoll-api/app/models/side.rb b/viscoll-api/app/models/side.rb
new file mode 100644
index 00000000..727819dd
--- /dev/null
+++ b/viscoll-api/app/models/side.rb
@@ -0,0 +1,39 @@
+class Side
+ include Mongoid::Document
+ include Mongoid::Timestamps
+
+ # Fields
+ field :folio_number, type: String, default: nil
+ field :page_number, type: String, default: nil
+ field :texture, type: String, default: "None"
+ field :script_direction, type: String, default: "None"
+ field :image, type: Hash, default: lambda { { } } # {manifestID: 123, label: "bla, " url: "https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3002_0001"}
+ field :parentID, type: String
+
+ # Relations
+ belongs_to :project
+ has_and_belongs_to_many :notes, inverse_of: nil
+
+ # Callbacks
+ before_destroy :unlink_notes, :unlink_image
+
+ protected
+ # If linked to note(s), remove link from the note(s)'s side
+ def unlink_notes
+ self.notes.each do | note |
+ note.objects[:Recto].delete(self.id.to_s)
+ note.objects[:Verso].delete(self.id.to_s)
+ note.save
+ end
+ end
+
+ # If linked to image, remove link from the image's sides list
+ def unlink_image
+ if not self.image.empty?
+ if (image = Image.where(:id => self.image[:url].split("/")[-1].split("_", 2)[0]).first)
+ image.sideIDs.delete(self.id.to_s)
+ image.save
+ end
+ end
+ end
+end
diff --git a/viscoll-api/app/models/user.rb b/viscoll-api/app/models/user.rb
new file mode 100644
index 00000000..4b2df830
--- /dev/null
+++ b/viscoll-api/app/models/user.rb
@@ -0,0 +1,13 @@
+class User
+ include Mongoid::Document
+ include RailsJwtAuth::Authenticatable
+ include RailsJwtAuth::Confirmable
+ include RailsJwtAuth::Recoverable
+ include RailsJwtAuth::Trackable
+
+ field :name, type: String, default: ""
+
+ has_many :images, dependent: :destroy
+ has_many :projects, dependent: :destroy
+
+end
diff --git a/viscoll-api/app/views/account_approval_mailer/sendApprovalStatus.html.erb b/viscoll-api/app/views/account_approval_mailer/sendApprovalStatus.html.erb
new file mode 100644
index 00000000..44dac600
--- /dev/null
+++ b/viscoll-api/app/views/account_approval_mailer/sendApprovalStatus.html.erb
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
Hi <%= @user.name %>,
+
Congratulations! Your request to join VisColl has been successsfully approved.
+
You can now log in with the credentials that you used to register.
+
+
\ No newline at end of file
diff --git a/viscoll-api/app/views/exports/show.json.jbuilder b/viscoll-api/app/views/exports/show.json.jbuilder
new file mode 100644
index 00000000..ecaa5533
--- /dev/null
+++ b/viscoll-api/app/views/exports/show.json.jbuilder
@@ -0,0 +1,16 @@
+json.set! 'Export' do
+ json.project @data[:project]
+ json.Groups @data[:groups]
+ json.Leafs @data[:leafs]
+ json.Rectos @data[:rectos]
+ json.Versos @data[:versos]
+ json.Notes @data[:notes]
+end
+
+json.set! 'Images' do
+ if @zipFilePath
+ json.exportedImages @zipFilePath
+ else
+ json.exportedImages ""
+ end
+end
\ No newline at end of file
diff --git a/viscoll-api/app/views/feedback_mailer/sendFeedback.html.erb b/viscoll-api/app/views/feedback_mailer/sendFeedback.html.erb
new file mode 100644
index 00000000..6cd9fc3e
--- /dev/null
+++ b/viscoll-api/app/views/feedback_mailer/sendFeedback.html.erb
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
Feedback from: <%= @user.email %>
+
Message: <%= @message %>
+
+
Browser Information:
+ <%= @browserInformation %>
+
+
+ <% if @projectJSONExport!=nil %>
+
Project JSON Export:
+ <%= @projectJSONExport %>
+
+ <% end %>
+
+
\ No newline at end of file
diff --git a/viscoll-api/app/views/filter/show.json.jbuilder b/viscoll-api/app/views/filter/show.json.jbuilder
new file mode 100644
index 00000000..fe28a93a
--- /dev/null
+++ b/viscoll-api/app/views/filter/show.json.jbuilder
@@ -0,0 +1,11 @@
+json.Groups @groups
+json.Leafs @leafs
+json.Sides @sides
+json.Notes @notes
+json.GroupsOfMatchingLeafs @groupsOfMatchingLeafs
+json.LeafsOfMatchingSides @leafsOfMatchingSides
+json.GroupsOfMatchingSides @groupsOfMatchingSides
+json.GroupsOfMatchingNotes @groupsOfMatchingNotes
+json.LeafsOfMatchingNotes @leafsOfMatchingNotes
+json.SidesOfMatchingNotes @sidesOfMatchingNotes
+json.visibleAttributes @visibleAttributes
diff --git a/app/views/layouts/mailer.html.erb b/viscoll-api/app/views/layouts/mailer.html.erb
similarity index 100%
rename from app/views/layouts/mailer.html.erb
rename to viscoll-api/app/views/layouts/mailer.html.erb
diff --git a/app/views/layouts/mailer.text.erb b/viscoll-api/app/views/layouts/mailer.text.erb
similarity index 100%
rename from app/views/layouts/mailer.text.erb
rename to viscoll-api/app/views/layouts/mailer.text.erb
diff --git a/viscoll-api/app/views/projects/index.json.jbuilder b/viscoll-api/app/views/projects/index.json.jbuilder
new file mode 100644
index 00000000..0aebab9d
--- /dev/null
+++ b/viscoll-api/app/views/projects/index.json.jbuilder
@@ -0,0 +1,13 @@
+json.set! "projects" do
+ json.array!(@projects.desc(:updated_at)) do | project |
+ json.extract! project, :id, :title, :shelfmark, :metadata, :created_at, :updated_at
+ end
+end
+
+json.set! "images" do
+ json.array!(@images) do | image |
+ json.extract! image, :id, :projectIDs, :sideIDs
+ json.url @base_api_url+"/images/"+image.id.to_s+"_"+image.filename
+ json.label image.filename
+ end
+end
\ No newline at end of file
diff --git a/viscoll-api/app/views/projects/show.json.jbuilder b/viscoll-api/app/views/projects/show.json.jbuilder
new file mode 100644
index 00000000..2bdc2f6e
--- /dev/null
+++ b/viscoll-api/app/views/projects/show.json.jbuilder
@@ -0,0 +1,44 @@
+json.set! "active" do
+ json.id @data[:project][:id]
+ json.title @data[:project][:title]
+ json.shelfmark @data[:project][:shelfmark]
+ json.metadata @data[:project][:metadata]
+ json.preferences @data[:project][:preferences]
+ json.noteTypes @data[:project][:noteTypes]
+
+ json.set! "manifests" do
+ json.set! "DIYImages" do
+ json.id "DIYImages"
+ json.images @diyImages
+ json.name "Uploaded Images"
+ end
+ json.merge! @data[:project][:manifests]
+ end
+
+ json.groupIDs @data[:groupIDs]
+ json.leafIDs @data[:leafIDs]
+ json.rectoIDs @data[:rectoIDs]
+ json.versoIDs @data[:versoIDs]
+
+ json.Groups @data[:groups]
+ json.Leafs @data[:leafs]
+ json.Rectos @data[:rectos]
+ json.Versos @data[:versos]
+ json.Notes @data[:notes]
+end
+
+json.set! "dashboard" do
+ json.set! "projects" do
+ json.array!(@projects.desc(:updated_at)) do | project |
+ json.extract! project, :id, :title, :shelfmark, :metadata, :created_at, :updated_at
+ end
+ end
+
+ json.set! "images" do
+ json.array!(@images) do | image |
+ json.extract! image, :id, :projectIDs, :sideIDs
+ json.url @base_api_url+"/images/"+image.id.to_s+"_"+image.filename
+ json.label image.filename
+ end
+ end
+end
\ No newline at end of file
diff --git a/viscoll-api/app/views/rails_jwt_auth/mailer/confirmation_instructions.html.erb b/viscoll-api/app/views/rails_jwt_auth/mailer/confirmation_instructions.html.erb
new file mode 100644
index 00000000..468db6dc
--- /dev/null
+++ b/viscoll-api/app/views/rails_jwt_auth/mailer/confirmation_instructions.html.erb
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
Hello Admin,
+
The following user has requested to join VisColl. You can confirm the account through the link below.
+
Once successfully confirmed, the user will be notified by email.
+
+
+
+
Name: <%= @user.name %>
+
Email: <%= @user.email %>
+
+
<%= link_to 'Confirm Account', @confirmation_url.html_safe, {:style => 'color: #4ED6CB'} %>
+
+
\ No newline at end of file
diff --git a/viscoll-api/app/views/rails_jwt_auth/mailer/reset_password_instructions.html.erb b/viscoll-api/app/views/rails_jwt_auth/mailer/reset_password_instructions.html.erb
new file mode 100644
index 00000000..52ade26c
--- /dev/null
+++ b/viscoll-api/app/views/rails_jwt_auth/mailer/reset_password_instructions.html.erb
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
Hi <%= @user.name %>!
+
Someone has requested a link to change your password. You can do this through the link below.
+
+
<%= link_to 'Change my password', @reset_password_url.html_safe, {:style => 'color: #4ED6CB'} %>
+
+
If you didn't request this, please ignore this email.
+
Your password won't change until you access the link above and create a new one.
+
+
+
\ No newline at end of file
diff --git a/viscoll-api/app/views/rails_jwt_auth/mailer/set_password_instructions.html.erb b/viscoll-api/app/views/rails_jwt_auth/mailer/set_password_instructions.html.erb
new file mode 100644
index 00000000..ab990d02
--- /dev/null
+++ b/viscoll-api/app/views/rails_jwt_auth/mailer/set_password_instructions.html.erb
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
Hi <%= @user.name %>!
+
+
You need to define your password to complete registration. You can do this through the link below.
+
+
<%= link_to 'Set my password', @reset_password_url.html_safe, {:style => 'color: #4ED6CB'} %>
+
+
\ No newline at end of file
diff --git a/viscoll-api/app/views/sessions/index.json.jbuilder b/viscoll-api/app/views/sessions/index.json.jbuilder
new file mode 100644
index 00000000..ff05c143
--- /dev/null
+++ b/viscoll-api/app/views/sessions/index.json.jbuilder
@@ -0,0 +1,13 @@
+json.session do
+ json.jwt @userToken
+ json.id @user.id
+ json.email @user.email
+ json.name @user.name
+ json.lastLoggedIn @user.last_sign_in_at
+
+ json.projects(@userProjects) do | project |
+ json.id project.id
+ json.merge! project.attributes.except("_id", "user_id")
+ end
+
+end
diff --git a/viscoll-api/app/views/users/show.json.jbuilder b/viscoll-api/app/views/users/show.json.jbuilder
new file mode 100644
index 00000000..05481d57
--- /dev/null
+++ b/viscoll-api/app/views/users/show.json.jbuilder
@@ -0,0 +1,5 @@
+json.extract! @user, :id, :name, :email
+json.projects(@user.projects) do | project |
+ json.id project.id
+ json.merge! project.attributes.except("_id", "user_id")
+end
\ No newline at end of file
diff --git a/bin/bundle b/viscoll-api/bin/bundle
similarity index 100%
rename from bin/bundle
rename to viscoll-api/bin/bundle
diff --git a/bin/rails b/viscoll-api/bin/rails
similarity index 53%
rename from bin/rails
rename to viscoll-api/bin/rails
index 07396602..5badb2fd 100755
--- a/bin/rails
+++ b/viscoll-api/bin/rails
@@ -1,4 +1,9 @@
#!/usr/bin/env ruby
+begin
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
+end
APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot'
require 'rails/commands'
diff --git a/viscoll-api/bin/rake b/viscoll-api/bin/rake
new file mode 100755
index 00000000..d87d5f57
--- /dev/null
+++ b/viscoll-api/bin/rake
@@ -0,0 +1,9 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
+end
+require_relative '../config/boot'
+require 'rake'
+Rake.application.run
diff --git a/bin/setup b/viscoll-api/bin/setup
similarity index 100%
rename from bin/setup
rename to viscoll-api/bin/setup
diff --git a/viscoll-api/bin/spring b/viscoll-api/bin/spring
new file mode 100755
index 00000000..fb2ec2eb
--- /dev/null
+++ b/viscoll-api/bin/spring
@@ -0,0 +1,17 @@
+#!/usr/bin/env ruby
+
+# This file loads spring without using Bundler, in order to be fast.
+# It gets overwritten when you run the `spring binstub` command.
+
+unless defined?(Spring)
+ require 'rubygems'
+ require 'bundler'
+
+ lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
+ spring = lockfile.specs.detect { |spec| spec.name == "spring" }
+ if spring
+ Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
+ gem 'spring', spring.version
+ require 'spring/binstub'
+ end
+end
diff --git a/bin/update b/viscoll-api/bin/update
similarity index 100%
rename from bin/update
rename to viscoll-api/bin/update
diff --git a/config.ru b/viscoll-api/config.ru
similarity index 100%
rename from config.ru
rename to viscoll-api/config.ru
diff --git a/config/application.rb b/viscoll-api/config/application.rb
similarity index 50%
rename from config/application.rb
rename to viscoll-api/config/application.rb
index 0b45f014..20dac125 100644
--- a/config/application.rb
+++ b/viscoll-api/config/application.rb
@@ -1,4 +1,5 @@
require_relative 'boot'
+require_relative 'shrine'
require "rails"
# Pick the frameworks you want:
@@ -9,17 +10,36 @@
require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
-require "sprockets/railtie"
+# require "sprockets/railtie"
# require "rails/test_unit/railtie"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
-module ViscollObns
+module ViscollApi
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
+
+ # Only loads a smaller set of middleware suitable for API only apps.
+ # Middleware like session, flash, cookies can be added back manually.
+ # Skip views, helpers and assets when generating a new resource.
+ config.api_only = true
+
+ Mongo::Logger.logger.level = Logger::FATAL
+ config.log_level = :warn
+
+ # Rack CORS for handling Cross-Origin Resource Sharing (CORS)
+ config.middleware.use Rack::Cors do
+ allow do
+ origins '*'
+ resource '*',
+ :headers => :any,
+ :expose => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
+ :methods => [:get, :patch, :put, :delete, :post, :options]
+ end
+ end
end
end
diff --git a/config/boot.rb b/viscoll-api/config/boot.rb
similarity index 100%
rename from config/boot.rb
rename to viscoll-api/config/boot.rb
diff --git a/config/cable.yml b/viscoll-api/config/cable.yml
similarity index 100%
rename from config/cable.yml
rename to viscoll-api/config/cable.yml
diff --git a/config/environment.rb b/viscoll-api/config/environment.rb
similarity index 100%
rename from config/environment.rb
rename to viscoll-api/config/environment.rb
diff --git a/config/environments/development.rb b/viscoll-api/config/environments/development.rb
similarity index 74%
rename from config/environments/development.rb
rename to viscoll-api/config/environments/development.rb
index 58d20de6..397a2e9b 100644
--- a/config/environments/development.rb
+++ b/viscoll-api/config/environments/development.rb
@@ -30,17 +30,18 @@
config.action_mailer.raise_delivery_errors = false
config.action_mailer.perform_caching = false
+ config.action_mailer.delivery_method = :smtp
+ # config.action_mailer.default_url_options = { :host => "localhost", :port => 3000 }
+ config.action_mailer.smtp_settings = {
+ :address => 'smtp.ethereal.email',
+ :port => 587,
+ :user_name => 'libby.corkery17@ethereal.email',
+ :password => 'RP4P6zMm3rVW9adMZF'
+ }
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
- # Debug mode disables concatenation and preprocessing of assets.
- # This option may cause significant delays in view rendering with a large
- # number of complex assets.
- config.assets.debug = true
-
- # Suppress logger output for asset requests.
- config.assets.quiet = true
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
@@ -48,4 +49,12 @@
# Use an evented file watcher to asynchronously detect changes in source code,
# routes, locales, etc. This feature depends on the listen gem.
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
+
+ config.middleware.insert_before 0, Rack::Cors do
+ allow do
+ origins '*'
+ resource '*', :headers => :any, :methods => [:get, :post, :put, :patch, :options, :delete]
+ end
+ end
+
end
diff --git a/config/environments/production.rb b/viscoll-api/config/environments/production.rb
similarity index 88%
rename from config/environments/production.rb
rename to viscoll-api/config/environments/production.rb
index 5db900a8..4355c7d5 100644
--- a/config/environments/production.rb
+++ b/viscoll-api/config/environments/production.rb
@@ -18,14 +18,6 @@
# Apache or NGINX already handles this.
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
- # Compress JavaScripts and CSS.
- config.assets.js_compressor = :uglifier
- # config.assets.css_compressor = :sass
-
- # Do not fallback to assets pipeline if a precompiled asset is missed.
- config.assets.compile = false
-
- # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = 'http://assets.example.com'
@@ -54,8 +46,14 @@
# Use a real queuing backend for Active Job (and separate queues per environment)
# config.active_job.queue_adapter = :resque
- # config.active_job.queue_name_prefix = "ViscollObns_#{Rails.env}"
+ # config.active_job.queue_name_prefix = "viscoll-api_#{Rails.env}"
config.action_mailer.perform_caching = false
+ config.action_mailer.default_url_options = { :host => "utlviscoll.library.utoronto.ca" }
+ config.action_mailer.smtp_settings = {
+ address: 'mailer.library.utoronto.ca',
+ port: 25,
+ enable_starttls_auto: false
+ }
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
diff --git a/config/environments/test.rb b/viscoll-api/config/environments/test.rb
similarity index 95%
rename from config/environments/test.rb
rename to viscoll-api/config/environments/test.rb
index 30587ef6..7c76952d 100644
--- a/config/environments/test.rb
+++ b/viscoll-api/config/environments/test.rb
@@ -33,6 +33,7 @@
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
+ config.action_mailer.default_url_options = { :host => "localhost", :port => 3000 }
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
diff --git a/config/initializers/application_controller_renderer.rb b/viscoll-api/config/initializers/application_controller_renderer.rb
similarity index 100%
rename from config/initializers/application_controller_renderer.rb
rename to viscoll-api/config/initializers/application_controller_renderer.rb
diff --git a/config/initializers/backtrace_silencers.rb b/viscoll-api/config/initializers/backtrace_silencers.rb
similarity index 100%
rename from config/initializers/backtrace_silencers.rb
rename to viscoll-api/config/initializers/backtrace_silencers.rb
diff --git a/viscoll-api/config/initializers/cors.rb b/viscoll-api/config/initializers/cors.rb
new file mode 100644
index 00000000..3b1c1b5e
--- /dev/null
+++ b/viscoll-api/config/initializers/cors.rb
@@ -0,0 +1,16 @@
+# Be sure to restart your server when you modify this file.
+
+# Avoid CORS issues when API is called from the frontend app.
+# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.
+
+# Read more: https://github.com/cyu/rack-cors
+
+# Rails.application.config.middleware.insert_before 0, Rack::Cors do
+# allow do
+# origins 'example.com'
+#
+# resource '*',
+# headers: :any,
+# methods: [:get, :post, :put, :patch, :delete, :options, :head]
+# end
+# end
diff --git a/config/initializers/filter_parameter_logging.rb b/viscoll-api/config/initializers/filter_parameter_logging.rb
similarity index 100%
rename from config/initializers/filter_parameter_logging.rb
rename to viscoll-api/config/initializers/filter_parameter_logging.rb
diff --git a/config/initializers/inflections.rb b/viscoll-api/config/initializers/inflections.rb
similarity index 100%
rename from config/initializers/inflections.rb
rename to viscoll-api/config/initializers/inflections.rb
diff --git a/config/initializers/mime_types.rb b/viscoll-api/config/initializers/mime_types.rb
similarity index 100%
rename from config/initializers/mime_types.rb
rename to viscoll-api/config/initializers/mime_types.rb
diff --git a/viscoll-api/config/initializers/mongoid.rb b/viscoll-api/config/initializers/mongoid.rb
new file mode 100644
index 00000000..9172e037
--- /dev/null
+++ b/viscoll-api/config/initializers/mongoid.rb
@@ -0,0 +1,11 @@
+module BSON
+ class ObjectId
+ def to_json(*args)
+ to_s.to_json
+ end
+
+ def as_json(*args)
+ to_s.as_json
+ end
+ end
+end
diff --git a/config/initializers/new_framework_defaults.rb b/viscoll-api/config/initializers/new_framework_defaults.rb
similarity index 64%
rename from config/initializers/new_framework_defaults.rb
rename to viscoll-api/config/initializers/new_framework_defaults.rb
index e34c66ce..351d7371 100644
--- a/config/initializers/new_framework_defaults.rb
+++ b/viscoll-api/config/initializers/new_framework_defaults.rb
@@ -4,18 +4,16 @@
#
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
-# Enable per-form CSRF tokens. Previous versions had false.
-Rails.application.config.action_controller.per_form_csrf_tokens = true
-
-# Enable origin-checking CSRF mitigation. Previous versions had false.
-Rails.application.config.action_controller.forgery_protection_origin_check = true
+Rails.application.config.raise_on_unfiltered_parameters = true
# Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`.
# Previous versions had false.
ActiveSupport.to_time_preserves_timezone = true
# Do not halt callback chains when a callback returns false. Previous versions had true.
-ActiveSupport.halt_callback_chains_on_return_false = false
+# DEPRECATION WARNING: ActiveSupport.halt_callback_chains_on_return_false= is deprecated
+# and will be removed in Rails 5.2.
+# ActiveSupport.halt_callback_chains_on_return_false = false
# Configure SSL options to enable HSTS with subdomains. Previous versions had false.
Rails.application.config.ssl_options = { hsts: { subdomains: true } }
diff --git a/viscoll-api/config/initializers/rails_jwt_auth.rb b/viscoll-api/config/initializers/rails_jwt_auth.rb
new file mode 100644
index 00000000..15b7f935
--- /dev/null
+++ b/viscoll-api/config/initializers/rails_jwt_auth.rb
@@ -0,0 +1,38 @@
+RailsJwtAuth.setup do |config|
+ # authentication model class name
+ #config.model_name = 'User'
+
+ # field name used to authentication with password
+ #config.auth_field_name = 'email'
+
+ # set to true to validate auth_field email format
+ #config.auth_field_email = true
+
+ # expiration time for generated tokens
+ #config.jwt_expiration_time = 7.days
+
+ # the "iss" (issuer) claim identifies the principal that issued the JWT
+ #config.jwt_issuer = 'RailsJwtAuth'
+
+ # number of simultaneously sessions for an user
+ #config.simultaneously_sessions = 3
+
+ # mailer sender
+ config.mailer_sender = 'noreply-dummy@library.utoronto.ca'
+
+ # url used to create email link with confirmation token
+ config.confirmation_url = if Rails.env.production? then 'https://dummy.library.utoronto.ca/confirmation' else 'http://127.0.0.1:3000/confirmation' end
+
+ # expiration time for confirmation tokens
+ #config.confirmation_expiration_time = 1.day
+
+ # url used to create email link with reset password token
+ config.reset_password_url = if Rails.env.production? then 'https://dummy.library.utoronto.ca/password' else 'http://127.0.0.1:3000/password' end
+
+
+ # expiration time for reset password tokens
+ #config.reset_password_expiration_time = 1.day
+
+ # uses deliver_later to send emails instead of deliver method
+ #config.deliver_later = false
+end
diff --git a/config/initializers/wrap_parameters.rb b/viscoll-api/config/initializers/wrap_parameters.rb
similarity index 100%
rename from config/initializers/wrap_parameters.rb
rename to viscoll-api/config/initializers/wrap_parameters.rb
diff --git a/config/locales/en.yml b/viscoll-api/config/locales/en.yml
similarity index 100%
rename from config/locales/en.yml
rename to viscoll-api/config/locales/en.yml
diff --git a/config/mongoid.yml b/viscoll-api/config/mongoid.yml
similarity index 87%
rename from config/mongoid.yml
rename to viscoll-api/config/mongoid.yml
index b4f74dd1..b4db75e2 100644
--- a/config/mongoid.yml
+++ b/viscoll-api/config/mongoid.yml
@@ -5,11 +5,11 @@ development:
default:
# Defines the name of the default database that Mongoid can connect to.
# (required).
- database: viscoll_obns_development
+ database: viscoll_api_development
# Provides the hosts the default client can connect to. Must be an array
# of host:port pairs. (required)
hosts:
- - localhost:27017
+ - mongo:27017
options:
# Change the default write concern. (default = { w: 1 })
# write:
@@ -34,8 +34,9 @@ development:
# - 'dbOwner'
# Change the default authentication mechanism. Valid options are: :scram,
- # :mongodb_cr, :mongodb_x509, and :plain. (default on 3.0 is :scram, default
- # on 2.4 and 2.6 is :plain)
+ # :mongodb_cr, :mongodb_x509, and :plain. Note that all authentication
+ # mechanisms require username and password, with the exception of :mongodb_x509.
+ # Default on mongoDB 3.0 is :scram, default on 2.4 and 2.6 is :plain.
# auth_mech: :scram
# The database or source to authenticate the user against.
@@ -121,6 +122,10 @@ development:
# existing method. (default: false)
# scope_overwrite_exception: false
+ # Raise an error when defining a field with the same name as an
+ # existing method. (default: false)
+ # duplicate_fields_exception: false
+
# Use Active Support's time zone in conversions. (default: true)
# use_activesupport_time_zone: true
@@ -132,15 +137,18 @@ development:
# otherwise.(default: :info)
# log_level: :info
+ # Control whether `belongs_to` association is required. By default
+ # `belongs_to` will trigger a validation error if the association
+ # is not present. (default: true)
+ # belongs_to_required_by_default: true
+
# Application name that is printed to the mongodb logs upon establishing a
# connection in server versions >= 3.4. Note that the name cannot exceed 128 bytes.
# app_name: MyApplicationName
test:
clients:
default:
- database: viscoll_obns_test
- hosts:
- - localhost:27017
+ uri: mongodb://localhost:27017/viscoll_test
options:
read:
mode: :primary
diff --git a/config/puma.rb b/viscoll-api/config/puma.rb
similarity index 100%
rename from config/puma.rb
rename to viscoll-api/config/puma.rb
diff --git a/viscoll-api/config/routes.rb b/viscoll-api/config/routes.rb
new file mode 100644
index 00000000..789aa716
--- /dev/null
+++ b/viscoll-api/config/routes.rb
@@ -0,0 +1,66 @@
+Rails.application.routes.draw do
+
+ # AUTHENTICATION ENDPOINTS
+ resource :session, controller: 'sessions', only: [:create, :destroy], defaults: {format: :json}
+ resource :registration, controller: 'registrations', only: [:create], defaults: {format: :json}
+ resource :registration, controller: 'rails_jwt_auth/registrations', only: [:create, :update, :destroy]
+ resource :password, controller: 'rails_jwt_auth/passwords', only: [:create, :update]
+ resource :confirmation, controller: 'rails_jwt_auth/confirmations', only: [:create]
+ resource :confirmation, controller: 'confirmations', only: [:update]
+
+ # USER ENDPOINTS
+ resources :users, defaults: {format: :json}, only: [:show, :update, :destroy]
+ post '/feedback', to: 'feedback#create', defaults: {format: :json}
+
+ # PROJECT ENDPOINTS
+ put '/projects/:id/filter', to: 'filter#show', defaults: {format: :json}
+ get '/projects/:id/export/:format', to: 'export#show', defaults: {format: :json}
+ get '/projects/:id/clone', to: 'projects#clone', defaults: {format: :json}
+ put '/projects/import', to: 'import#index', defaults: {format: :json}
+ post '/projects/:id/manifests', to: 'projects#createManifest', defaults: {format: :json}
+ put '/projects/:id/manifests', to: 'projects#updateManifest', defaults: {format: :json}
+ delete '/projects/:id/manifests', to: 'projects#deleteManifest', defaults: {format: :json}
+ get '/projects/:id/viewOnly', to: 'projects#viewOnly', defaults: {format: :json}
+ resources :projects, defaults: {format: :json}, only: [:index, :show, :update, :destroy, :create]
+
+ # DIY IMAGE ENDPOINTS
+ post '/images', to: 'images#uploadImages', defaults: {format: :json}
+ put '/images/link', to: 'images#link', defaults: {format: :json}
+ put '/images/unlink', to: 'images#unlink', defaults: {format: :json}
+ get '/images/:imageID_filename', to: 'images#show', defaults: {format: :json}
+ get '/images/zip/:id', to: 'images#getZipImages', defaults: {format: :json}
+ delete '/images', to: 'images#destroy', defaults: {format: :json}
+
+ # GROUP ENDPOINTS
+ resources :groups, defaults: {format: :json}, only: [:update, :destroy, :create]
+ put '/groups', to: 'groups#updateMultiple', defaults: {format: :json}, only: [:update]
+ delete '/groups', to: 'groups#destroyMultiple', defaults: {format: :json}, only: [:destroy]
+
+ # LEAF ENDPOINTS
+ put '/leafs/conjoin', to: 'leafs#conjoinLeafs', defaults: {format: :json}, only: [:update]
+ put '/leafs', to: 'leafs#updateMultiple', defaults: {format: :json}, only: [:update]
+ delete '/leafs', to: 'leafs#destroyMultiple', defaults: {format: :json}, only: [:destroy]
+ resources :leafs, defaults: {format: :json}, only: [:update, :destroy, :create]
+
+ # SIDE ENDPOINTS
+ put '/sides/generateFolio', to: 'sides#generateFolio', defaults: {format: :json}, only: [:update]
+ put '/sides/generatePageNumber', to: 'sides#generatePageNumber', defaults: {format: :json}, only: [:update]
+ put '/sides/:id', to: 'sides#update', defaults: {format: :json}, only: [:update]
+ put '/sides', to: 'sides#updateMultiple', defaults: {format: :json}, only: [:update]
+
+ # NOTE ENDPOINTS
+ put '/notes/:id/link', to: 'notes#link', defaults: {format: :json}, only: [:update]
+ put '/notes/:id/unlink', to: 'notes#unlink', defaults: {format: :json}, only: [:update]
+ post '/notes/type', to: 'notes#createType', defaults: {format: :json}, only: [:create]
+ put '/notes/type', to: 'notes#updateType', defaults: {format: :json}, only: [:update]
+ delete '/notes/type', to: 'notes#deleteType', defaults: {format: :json}, only: [:destroy]
+ resources :notes, defaults: {format: :json}, only: [:show, :update, :destroy, :create]
+
+ # DOCUMENTATION
+ get '/docs' => redirect('/docs/index.html')
+
+ # ROOT ENPOINT
+ get '/', to: proc { [200, {}, ['']] }
+
+
+end
diff --git a/config/secrets.yml b/viscoll-api/config/secrets.yml
similarity index 64%
rename from config/secrets.yml
rename to viscoll-api/config/secrets.yml
index 34116365..e0f98441 100644
--- a/config/secrets.yml
+++ b/viscoll-api/config/secrets.yml
@@ -11,10 +11,12 @@
# if you're sharing your code publicly.
development:
- secret_key_base: fe3e18f8b5a59698d997c8e5dd20303ed84a86ed28f4a94469adcebf8e2c4905e635761aa743ce87d4b88275ad44c696ee83cc2cd8859f0cdc393c494caf092c
-
+ secret_key_base: 98eb3bcbe406c141ad93c58e5f5ff08ab7348c82d688b78ee1fb1a30559d7104081e0dd9bf97c8e080a54f1d408f7f9d22710439c44cbbddc332861994b1c531
+ admin_email: 'smtp://localhost:1025'
+ api_url: 'http://localhost:3001'
+
test:
- secret_key_base: 3cf10572eca25c5e5785dd56e638a47d51523510d298d839023940487dc82982fc798add37eeb1b1abee64d4c159deecdb7f30e0bc1b812d2af264bb94b1d582
+ secret_key_base: a8986cd44e89b6547fe0c8ebd320706dc2dbd6aa617e42c5625b9420fa8ab8347beeedb0690138e178e79583b0f7c71b45fac99409d96653030c6a94c14d9d9d
# Do not keep production secrets in the repository,
# instead read values from the environment.
diff --git a/viscoll-api/config/shrine.rb b/viscoll-api/config/shrine.rb
new file mode 100644
index 00000000..b7aafb40
--- /dev/null
+++ b/viscoll-api/config/shrine.rb
@@ -0,0 +1,7 @@
+require "shrine"
+require "shrine/storage/file_system"
+Shrine.storages = {
+ cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), # temporary
+ store: Shrine::Storage::FileSystem.new("public", prefix: "uploads"), # permanent
+}
+Shrine.plugin :data_uri
diff --git a/config/spring.rb b/viscoll-api/config/spring.rb
similarity index 100%
rename from config/spring.rb
rename to viscoll-api/config/spring.rb
diff --git a/db/seeds.rb b/viscoll-api/db/seeds.rb
similarity index 100%
rename from db/seeds.rb
rename to viscoll-api/db/seeds.rb
diff --git a/app/controllers/concerns/.keep b/viscoll-api/lib/tasks/.keep
similarity index 100%
rename from app/controllers/concerns/.keep
rename to viscoll-api/lib/tasks/.keep
diff --git a/app/models/concerns/.keep b/viscoll-api/log/.keep
similarity index 100%
rename from app/models/concerns/.keep
rename to viscoll-api/log/.keep
diff --git a/viscoll-api/public/docs/index.html b/viscoll-api/public/docs/index.html
new file mode 100644
index 00000000..a619f74b
--- /dev/null
+++ b/viscoll-api/public/docs/index.html
@@ -0,0 +1,60 @@
+
+
+
+
+
+ API Docs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/viscoll-api/public/docs/viscoll_api.yaml b/viscoll-api/public/docs/viscoll_api.yaml
new file mode 100644
index 00000000..25eb74cc
--- /dev/null
+++ b/viscoll-api/public/docs/viscoll_api.yaml
@@ -0,0 +1,2910 @@
+swagger: '2.0'
+info:
+ description: Documentation of all endpoints
+ version: 1.0.0
+ title: VisColl API
+
+
+# tags are used for organizing operations
+tags:
+- name: Authentication
+ description: JWT based authentication
+- name: Users
+ description: Operations on User model
+- name: Projects
+ description: Operations on Project model
+- name: Groups
+ description: Operations on Group model
+- name: Leafs
+ description: Operations on Leaf model
+- name: Sides
+ description: Operations on Side model
+- name: Notes
+ description: Operations on Note model
+
+paths:
+ /session:
+ post:
+ tags:
+ - Authentication
+ summary: creates a session for a user
+ operationId: loginUser
+ description: |
+ By passing in the appropriate options, you can login a user and create a session
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: body
+ name: session
+ required: true
+ description: session object to create
+ schema:
+ $ref: '#/definitions/UserLoginParams'
+ responses:
+ 200:
+ description: user session successfully created
+ schema:
+ $ref: '#/definitions/UserLoginSuccess'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/UserLoginError'
+ delete:
+ tags:
+ - Authentication
+ summary: deletes the session for a user
+ operationId: logoutUser
+ description: |
+ By passing in the appropriate options, you can logout a user and delete the session
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: Authentication token
+ responses:
+ 204:
+ description: user session successfully deleted
+ 401:
+ description: Unauthorized Action
+ 422:
+ description: bad token header
+ schema:
+ $ref: '#/definitions/UserLogoutError'
+ /registration:
+ post:
+ tags:
+ - Authentication
+ summary: creates a new user
+ operationId: addUser
+ description: |
+ By passing in the appropriate options, you can add a new user to the database
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: body
+ name: user
+ required: true
+ description: user object to create
+ schema:
+ $ref: '#/definitions/UserRegisterParams'
+ responses:
+ 201:
+ description: user object successfully created and confirmation email sent to activate
+ schema:
+ $ref: '#/definitions/UserRegisterSuccess'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/UserRegisterError'
+ /confirmation:
+ put:
+ tags:
+ - Authentication
+ summary: confirms a user
+ operationId: confirmUser
+ description: |
+ By passing in the appropriate options, you can confirm a new user
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: body
+ name: token
+ required: true
+ description: confirmation token sent by email
+ schema:
+ $ref: '#/definitions/UserConfirmParams'
+ responses:
+ 204:
+ description: user successfully confirmed
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/UserConfirmError'
+ /password:
+ post:
+ tags:
+ - Authentication
+ summary: sends email to reset password
+ operationId: resetPasswordRequest
+ description: |
+ By passing in the appropriate options, you can request an email for password reset
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: body
+ name: password
+ required: true
+ description: email address to send password reset link
+ schema:
+ $ref: '#/definitions/UserPasswordResetRequestParams'
+ responses:
+ 204:
+ description: email was sent with password reset link
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/UserPasswordResetRequestError'
+ put:
+ tags:
+ - Authentication
+ summary: resets the user's password
+ operationId: resetPassword
+ description: |
+ By passing in the appropriate options, you can reset the password of the user
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: body
+ name: passwordReset
+ required: true
+ description: reset password token and new password
+ schema:
+ $ref: '#/definitions/UserPasswordResetParams'
+ responses:
+ 204:
+ description: password was successfully reset
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/UserPasswordResetError'
+ /users/{userID}:
+ get:
+ tags:
+ - Users
+ summary: gets information about a user
+ operationId: getUser
+ description: |
+ By passing in the appropriate options, you can view the user's information
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: userID
+ type: string
+ required: true
+ description: ID of the user
+ responses:
+ 200:
+ description: successfully retrieved the user's information
+ schema:
+ $ref: '#/definitions/UserResponseSimple'
+ 404:
+ description: user not found with id userID
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: user not found with id userID
+ 401:
+ description: Unauthorized Action
+ 400:
+ description: Bad request due to token authorization
+ schema:
+ $ref: '#/definitions/TokenError'
+ delete:
+ tags:
+ - Users
+ summary: deletes a user
+ operationId: deleteUser
+ description: |
+ By passing in the appropriate options, you can delete a user
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: userID
+ type: string
+ required: true
+ description: ID of the user
+ responses:
+ 204:
+ description: successfully deleted the user
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: user with userID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: user not found with id 5951303fc9bf3c7b9a573a3f
+ put:
+ tags:
+ - Users
+ summary: updates information about a user
+ operationId: updateUser
+ description: |
+ By passing in the appropriate options, you can update the user's information
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: userID
+ type: string
+ required: true
+ description: ID of the user
+ - in: body
+ name: user
+ required: true
+ description: |
+ Passing a new email address will invoke a confirmation mail sent which needs to be activated.
+ schema:
+ $ref: '#/definitions/UserUpdateParams'
+ responses:
+ 200:
+ description: successfully updated the user's information
+ schema:
+ $ref: '#/definitions/UserResponseSimple'
+ 401:
+ description: Unauthorized Action
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/UserUpdateError'
+ 404:
+ description: user not found with id userID
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: user not found with id userID
+ 400:
+ description: Bad request due to token authorization
+ schema:
+ $ref: '#/definitions/TokenError'
+ /projects:
+ post:
+ tags:
+ - Projects
+ summary: creates a new project
+ operationId: createProject
+ description: |
+ By passing in the appropriate options, you can create a new project
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: project
+ required: true
+ description: project, manuscript and groups information
+ schema:
+ $ref: '#/definitions/ProjectCreateParams'
+ responses:
+ 200:
+ description: successfully created the project
+ schema:
+ $ref: '#/definitions/ProjectResponseSimple'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/ProjectCreateError'
+ 401:
+ description: Unauthorized Action
+ get:
+ tags:
+ - Projects
+ summary: gets list of all user projects
+ operationId: getProjects
+ description: |
+ By passing in the appropriate options, you can view all projects of the current user
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ responses:
+ 200:
+ description: successfully retrieved the user's projects
+ schema:
+ type: array
+ items:
+ $ref: '#/definitions/ProjectResponseSimple'
+ 401:
+ description: Unauthorized Action
+ /projects/{projectID}:
+ get:
+ tags:
+ - Projects
+ summary: gets information about a project
+ operationId: getProject
+ description: |
+ By passing in the appropriate options, you can view the project's information
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: projectID
+ type: string
+ required: true
+ description: ID of the project
+ responses:
+ 200:
+ description: successfully retrieved the project's information
+ schema:
+ $ref: '#/definitions/ProjectResponseFull'
+ 404:
+ description: project not found with id sad84d709c9bf3c1f76fd3fb
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: project not found
+ 401:
+ description: Unauthorized Action
+ put:
+ tags:
+ - Projects
+ summary: creates a new project
+ operationId: updateProject
+ description: |
+ By passing in the appropriate options, you can update a project
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: projectID
+ type: string
+ required: true
+ description: ID of the project
+ - in: body
+ name: project
+ required: true
+ description: project and manuscript information
+ schema:
+ $ref: '#/definitions/ProjectUpdateParams'
+ responses:
+ 200:
+ description: successfully created the project
+ schema:
+ $ref: '#/definitions/ProjectResponseSimple'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/ProjectUpdateError'
+ 404:
+ description: project not found with id sad84d709c9bf3c1f76fd3fb
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: project not found
+ 401:
+ description: Unauthorized Action
+ delete:
+ tags:
+ - Projects
+ summary: deletes a project
+ operationId: deleteProject
+ description: |
+ By passing in the appropriate options, you can delete a project
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: projectID
+ type: string
+ required: true
+ description: ID of the project
+ responses:
+ 204:
+ description: successfully deleted the project
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: project not found with id sad84d709c9bf3c1f76fd3fb
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: project not found
+ /projects/{projectID}/filter:
+ get:
+ tags:
+ - Projects
+ summary: filter the project
+ operationId: filterProject
+ description: |
+ By passing in the appropriate options, you can filter objects from the project
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: projectID
+ type: string
+ required: true
+ description: ID of the project
+ - in: body
+ name: filter
+ required: true
+ description: filter information
+ schema:
+ $ref: '#/definitions/ProjectFilterParams'
+ responses:
+ 200:
+ description: successfully filtered the project's information
+ schema:
+ $ref: '#/definitions/ProjectFilterResponse'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/ProjectFilterError'
+ 404:
+ description: project not found with id sad84d709c9bf3c1f76fd3fb
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: project not found
+ 401:
+ description: Unauthorized Action
+ /projects/{projectID}/notes:
+ get:
+ tags:
+ - Projects
+ summary: gets all notes in a project
+ operationId: getNotes
+ description: |
+ By passing in the appropriate options, you can view all notes in a project
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: projectID
+ type: string
+ required: true
+ description: ID of the project
+ responses:
+ 200:
+ description: successfully retrieved all notes for the project
+ schema:
+ $ref: '#/definitions/NotesFullResponse'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: project not found with id sad84d709c9bf3c1f76fd3fb
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: project not found with id sad84d709c9bf3c1f76fd3fb
+ /projects/{projectID}/children:
+ get:
+ tags:
+ - Projects
+ summary: gets all children objects of a project
+ operationId: getChildren
+ description: |
+ By passing in the appropriate options, you can view all children objects of a project
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: projectID
+ type: string
+ required: true
+ description: ID of the project
+ responses:
+ 200:
+ description: successfully retrieved all children objects of the project
+ schema:
+ $ref: '#/definitions/ProjectChildrenResponse'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: project not found with id sad84d709c9bf3c1f76fd3fb
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: project not found with id sad84d709c9bf3c1f76fd3fb
+
+ /groups:
+ post:
+ tags:
+ - Groups
+ summary: creates a new group
+ operationId: createGroup
+ description: |
+ By passing in the appropriate options, you can create a new group
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: group
+ required: true
+ description: group information
+ schema:
+ $ref: '#/definitions/GroupCreateParams'
+ responses:
+ 200:
+ description: successfully created the group
+ schema:
+ $ref: '#/definitions/ProjectResponseFull'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/GroupCreateError'
+ 401:
+ description: Unauthorized Action
+ put:
+ tags:
+ - Groups
+ summary: updates list of groups
+ operationId: updateGroups
+ description: |
+ By passing in the appropriate options, you can update a list of groups
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: groups
+ required: true
+ description: groups information
+ schema:
+ $ref: '#/definitions/GroupUpdateMultipleParams'
+ responses:
+ 200:
+ description: successfully updated the groups
+ schema:
+ $ref: '#/definitions/ProjectResponseFull'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/GroupUpdateMultipleError'
+ 401:
+ description: Unauthorized Action
+ delete:
+ tags:
+ - Groups
+ summary: deletes list of groups
+ operationId: deletegroups
+ description: |
+ By passing in the appropriate options, you can delete the given groups
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: groups
+ required: true
+ description: groups information
+ schema:
+ $ref: '#/definitions/GroupDeleteMultipleParams'
+ responses:
+ 200:
+ description: successfully updated the groups
+ schema:
+ $ref: '#/definitions/ProjectResponseFull'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/GroupDeleteMultipleError'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: group with groupID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: group not found
+ /groups/{groupID}:
+ put:
+ tags:
+ - Groups
+ summary: updates a group
+ operationId: updateGroup
+ description: |
+ By passing in the appropriate options, you can update a group
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: groupID
+ type: string
+ required: true
+ description: ID of the group
+ - in: body
+ name: group
+ required: true
+ description: group information
+ schema:
+ $ref: '#/definitions/GroupUpdateParams'
+ responses:
+ 200:
+ description: successfully updated the group
+ schema:
+ $ref: '#/definitions/ProjectResponseFull'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/GroupUpdateError'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: group with groupID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: group not found
+ delete:
+ tags:
+ - Groups
+ summary: deletes a group
+ operationId: deleteGroup
+ description: |
+ By passing in the appropriate options, you can delete a group
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: groupID
+ type: string
+ required: true
+ description: ID of the group
+ responses:
+ 204:
+ description: successfully deleted the group
+ schema:
+ $ref: '#/definitions/ProjectResponseFull'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: group with groupID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: group not found
+
+
+
+ /leafs:
+ post:
+ tags:
+ - Leafs
+ summary: creates a new leaf
+ operationId: createLeaf
+ description: |
+ By passing in the appropriate options, you can create a new leaf
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: leaf
+ required: true
+ description: leaf information
+ schema:
+ $ref: '#/definitions/LeafCreateParams'
+ responses:
+ 200:
+ description: successfully created the leaf
+ schema:
+ $ref: '#/definitions/ProjectResponseFull'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/LeafCreateError'
+ 401:
+ description: Unauthorized Action
+ put:
+ tags:
+ - Leafs
+ summary: updates list of leaves
+ operationId: updateLeafs
+ description: |
+ By passing in the appropriate options, you can update a list of leaves
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: leafs
+ required: true
+ description: leafs information
+ schema:
+ $ref: '#/definitions/LeafUpdateMultipleParams'
+ responses:
+ 200:
+ description: successfully updated the leafs
+ schema:
+ $ref: '#/definitions/ProjectResponseFull'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/LeafUpdateMultipleError'
+ 401:
+ description: Unauthorized Action
+ delete:
+ tags:
+ - Leafs
+ summary: deletes list of leaves
+ operationId: deleteLeafs
+ description: |
+ By passing in the appropriate options, you can delete the give leaevs
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: leafs
+ required: true
+ description: leafs information
+ schema:
+ $ref: '#/definitions/LeafDeleteMultipleParams'
+ responses:
+ 200:
+ description: successfully updated the leaf
+ schema:
+ $ref: '#/definitions/ProjectResponseFull'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/LeafDeleteMultipleError'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: leaf with leafID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: leaf not found
+ /leafs/{leafID}:
+ put:
+ tags:
+ - Leafs
+ summary: updates a leaf
+ operationId: updateLeaf
+ description: |
+ By passing in the appropriate options, you can update a leaf
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: leafID
+ type: string
+ required: true
+ description: ID of the leaf
+ - in: body
+ name: leaf
+ required: true
+ description: leaf information
+ schema:
+ $ref: '#/definitions/LeafUpdateParams'
+ responses:
+ 200:
+ description: successfully updated the leaf
+ schema:
+ $ref: '#/definitions/ProjectResponseFull'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/LeafUpdateError'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: leaf with leafID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: leaf not found
+ delete:
+ tags:
+ - Leafs
+ summary: deletes a leaf
+ operationId: deleteLeaf
+ description: |
+ By passing in the appropriate options, you can delete a leaf
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: leafID
+ type: string
+ required: true
+ description: ID of the leaf
+ responses:
+ 204:
+ description: successfully deleted the leaf
+ schema:
+ $ref: '#/definitions/ProjectResponseFull'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: leaf with leafID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: leaf not found
+
+
+ /sides:
+ put:
+ tags:
+ - Sides
+ summary: updates list of sides
+ operationId: updateSides
+ description: |
+ By passing in the appropriate options, you can update a list of sides
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: sides
+ required: true
+ description: sides information
+ schema:
+ $ref: '#/definitions/SideUpdateMultipleParams'
+ responses:
+ 200:
+ description: successfully updated the sides
+ schema:
+ $ref: '#/definitions/ProjectResponseFull'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/SideUpdateMultipleError'
+ 401:
+ description: Unauthorized Action
+ /sides/{sideID}:
+ put:
+ tags:
+ - Sides
+ summary: updates a side
+ operationId: updateSide
+ description: |
+ By passing in the appropriate options, you can update a side
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: sideID
+ type: string
+ required: true
+ description: ID of the side
+ - in: body
+ name: side
+ required: true
+ description: side information
+ schema:
+ $ref: '#/definitions/SideUpdateParams'
+ responses:
+ 200:
+ description: successfully updated the side
+ schema:
+ $ref: '#/definitions/ProjectResponseFull'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/SideUpdateError'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: side with sideID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: side not found
+
+ /notes:
+ post:
+ tags:
+ - Notes
+ summary: creates a new note
+ operationId: createNote
+ description: |
+ By passing in the appropriate options, you can create a new note
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: note
+ required: true
+ description: note information
+ schema:
+ $ref: '#/definitions/NoteCreateParams'
+ responses:
+ 200:
+ description: successfully created the note
+ schema:
+ $ref: '#/definitions/NotesFullResponse'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/NoteCreateError'
+ 401:
+ description: Unauthorized Action
+
+
+
+ /notes/{noteID}:
+ put:
+ tags:
+ - Notes
+ summary: updates a notes
+ operationId: updateNote
+ description: |
+ By passing in the appropriate options, you can update a note
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: noteID
+ type: string
+ required: true
+ description: ID of the note
+ - in: body
+ name: note
+ required: true
+ description: note information
+ schema:
+ $ref: '#/definitions/NoteUpdateParams'
+ responses:
+ 200:
+ description: successfully updated the leaf
+ schema:
+ $ref: '#/definitions/NotesFullResponse'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/NoteCreateError'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: note with noteID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: note not found
+ delete:
+ tags:
+ - Notes
+ summary: deletes a note
+ operationId: deleteNote
+ description: |
+ By passing in the appropriate options, you can delete a note
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: noteID
+ type: string
+ required: true
+ description: ID of the note
+ responses:
+ 204:
+ description: successfully deleted the note
+ schema:
+ $ref: '#/definitions/NotesFullResponse'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: note with noteID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: note not found
+ /notes/{noteID}/link:
+ put:
+ tags:
+ - Notes
+ summary: links a note to the given objects
+ operationId: linkNote
+ description: |
+ By passing in the appropriate options, you can link a note to objects
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: noteID
+ type: string
+ required: true
+ description: ID of the note
+ - in: body
+ name: note
+ required: true
+ description: note information
+ schema:
+ $ref: '#/definitions/NoteLinkParams'
+ responses:
+ 200:
+ description: successfully linked the note
+ schema:
+ $ref: '#/definitions/NotesFullResponse'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/NoteLinkError'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: note with noteID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: note not found
+ /notes/{noteID}/unlink:
+ put:
+ tags:
+ - Notes
+ summary: unlinks a note from the given objects
+ operationId: unlinkNote
+ description: |
+ By passing in the appropriate options, you can unlink a note from objects
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: noteID
+ type: string
+ required: true
+ description: ID of the note
+ - in: body
+ name: note
+ required: true
+ description: note information
+ schema:
+ $ref: '#/definitions/NoteLinkParams'
+ responses:
+ 200:
+ description: successfully unlinked the note
+ schema:
+ $ref: '#/definitions/NotesFullResponse'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/NoteLinkError'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: note with noteID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: note not found
+ /notes/type:
+ post:
+ tags:
+ - Notes
+ summary: creates a note type
+ operationId: createNoteType
+ description: |
+ By passing in the appropriate options, you can create a note type
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: noteType
+ required: true
+ description: note information
+ schema:
+ $ref: '#/definitions/NoteTypeCreateParams'
+ responses:
+ 200:
+ description: successfully created the note type
+ schema:
+ $ref: '#/definitions/NoteTypeResponse'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/NoteTypeCreateError'
+ 401:
+ description: Unauthorized Action
+ put:
+ tags:
+ - Notes
+ summary: updates a note type
+ operationId: updateNoteType
+ description: |
+ By passing in the appropriate options, you can update a note type
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: noteType
+ required: true
+ description: note information
+ schema:
+ $ref: '#/definitions/NoteTypeUpdateParams'
+ responses:
+ 200:
+ description: successfully updated the note type
+ schema:
+ $ref: '#/definitions/NoteTypeResponse'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/NoteTypeUpdateError'
+ 401:
+ description: Unauthorized Action
+ delete:
+ tags:
+ - Notes
+ summary: deletes a note type
+ operationId: deleteNoteType
+ description: |
+ By passing in the appropriate options, you can delete a note type
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: noteType
+ required: true
+ description: note information
+ schema:
+ $ref: '#/definitions/NoteTypeCreateParams'
+ responses:
+ 200:
+ description: successfully deleted the note type
+ schema:
+ $ref: '#/definitions/NoteTypeResponse'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/NoteTypeDeleteError'
+ 401:
+ description: Unauthorized Action
+
+
+
+
+definitions:
+ UserRegisterParams:
+ type: object
+ properties:
+ user:
+ type: object
+ required:
+ - email
+ - password
+ properties:
+ email:
+ type: string
+ example: example@mail.com
+ password:
+ type: string
+ example: secret123
+ name:
+ type: string
+ example: John
+ UserRegisterSuccess:
+ type: object
+ properties:
+ user:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ example: 5951303fc9bf3c7b9a573a3f
+ email:
+ type: string
+ example: example@mail.com
+ name:
+ type: string
+ example: John
+ password_digest:
+ type: string
+ format: password encrypt
+ example: $2a$10$/CY5b5qDleekbFbMVIFTZ.61VIjAsNGaOB5vQ4zrSWwyHvVL.G/P6
+ confirmation_token:
+ type: string
+ example: LTn4sV79adhRDyc5k1r3yaQk
+ confirmation_sent_at:
+ type: string
+ format: date-time
+ example: "2017-07-12T14:04:34.799Z"
+ UserRegisterError:
+ type: object
+ properties:
+ errors:
+ type: object
+ properties:
+ email:
+ type: array
+ items:
+ type: string
+ example: [can't be blank, is not an email, is already taken]
+ password:
+ type: array
+ items:
+ type: string
+ example: [can't be blank]
+ UserConfirmParams:
+ type: object
+ required:
+ - confirmation_token
+ properties:
+ confirmation_token:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ UserConfirmError:
+ type: object
+ properties:
+ errors:
+ type: object
+ properties:
+ confirmation_token:
+ type: array
+ items:
+ type: string
+ example: [not found]
+ UserLoginParams:
+ type: object
+ properties:
+ session:
+ type: object
+ required:
+ - email
+ - password
+ properties:
+ email:
+ type: string
+ example: example@mail.com
+ password:
+ type: string
+ example: secret123
+ UserLoginSuccess:
+ type: object
+ properties:
+ session:
+ type: object
+ properties:
+ jwt:
+ type: string
+ example: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdXRoX3Rva2VuIjoiTjVGZkN
+ id:
+ type: string
+ format: uuid
+ example: 5951303fc9bf3c7b9a573a3f
+ email:
+ type: string
+ example: example@mail.com
+ name:
+ type: string
+ example: John
+ lastLoggedIn:
+ type: string
+ format: date-time
+ example: 2017-07-12T14:04:34.799Z
+ projects:
+ type: array
+ items:
+ $ref: '#/definitions/ProjectResponseSimple'
+ UserLoginError:
+ type: object
+ properties:
+ errors:
+ type: object
+ properties:
+ session:
+ type: array
+ items:
+ type: string
+ example: [invalid email / password, unconfirmed email]
+ UserLogoutError:
+ type: object
+ properties:
+ error:
+ type: string
+ example: Authorization Header Signature verification raised, Not enough or too many segments
+ UserPasswordResetRequestParams:
+ type: object
+ properties:
+ password:
+ type: object
+ required:
+ - email
+ properties:
+ email:
+ type: string
+ example: example@mail.com
+ UserPasswordResetRequestError:
+ type: object
+ properties:
+ errors:
+ type: object
+ properties:
+ email:
+ type: array
+ items:
+ type: string
+ example: [unconfirmed email, not found]
+ UserPasswordResetParams:
+ type: object
+ required:
+ - reset_password_token
+ properties:
+ reset_password_token:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ password:
+ type: object
+ required:
+ - password
+ - password_confirmation
+ properties:
+ password:
+ type: string
+ example: secret123
+ password_confirmation:
+ type: string
+ example: secret123
+ UserPasswordResetError:
+ type: object
+ properties:
+ errors:
+ type: object
+ properties:
+ reset_password_token:
+ type: array
+ items:
+ type: string
+ example: [not found, has expired please request a new one]
+ password:
+ type: array
+ items:
+ type: string
+ example: [blank]
+ password_confirmation:
+ type: array
+ items:
+ type: string
+ example: [doesn't match Password]
+ UserUpdateParams:
+ type: object
+ properties:
+ user:
+ type: object
+ properties:
+ name:
+ type: string
+ example: John
+ email:
+ type: string
+ example: example@mail.com
+ current_password:
+ type: string
+ example: secret123
+ password:
+ type: string
+ example: new_secret123
+ UserUpdateError:
+ type: object
+ properties:
+ email:
+ type: array
+ items:
+ type: string
+ example: [is already taken, is not at email]
+ current_password:
+ type: array
+ items:
+ type: string
+ example: [invalid, blank, nil]
+ password:
+ type: array
+ items:
+ type: string
+ example: [invalid, blank, nil]
+ UserResponseSimple:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ example: 5951303fc9bf3c7b9a573a3f
+ email:
+ type: string
+ example: example@mail.com
+ name:
+ type: string
+ example: John
+ projects:
+ type: array
+ items:
+ $ref: '#/definitions/ProjectResponseSimple'
+ TokenError:
+ type: object
+ properties:
+ error:
+ type: string
+ example: Authorization Token Signature verification raised, Nil JSON web token, Not enough or too many segments
+ ProjectResponseSimple:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ example: 5951303fc9bf3c7b9a573a3f
+ title:
+ type: string
+ example: My first project
+ created_at:
+ type: string
+ example: "2017-07-12T14:04:34.799Z"
+ updated_at:
+ type: string
+ example: "2017-07-12T14:04:34.799Z"
+ manuscript:
+ $ref: '#/definitions/ManuscriptResponseSimple'
+ ManuscriptResponseSimple:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ example: 5951303fc9bf3c7b9a573a3f
+ shelfmark:
+ type: string
+ example: MSS 123
+ uri:
+ type: string
+ format: url
+ example: some iiif manifest url
+ date:
+ type: string
+ example: 18th century
+ created_at:
+ type: string
+ example: "2017-07-12T14:04:34.799Z"
+ updated_at:
+ type: string
+ example: "2017-07-12T14:04:34.799Z"
+ ProjectCreateParams:
+ type: object
+ properties:
+ project:
+ type: object
+ properties:
+ title:
+ type: string
+ example: My first project
+ manuscript:
+ type: object
+ properties:
+ shelfmark:
+ type: string
+ example: MSS 123
+ uri:
+ type: string
+ format: url
+ example: some iiif manifest url
+ date:
+ type: string
+ example: 18th century
+ groups:
+ type: array
+ items:
+ type: object
+ properties:
+ number:
+ type: integer
+ example: 1
+ description: the group order amoung other groups
+ leaves:
+ type: integer
+ example: 4
+ description: number of leaves in this group
+ conjoin:
+ type: boolean
+ example: true
+ description: whether to auto-conjoin or not
+ oddLeaf:
+ type: integer
+ example: 3
+ description: if auto-conjoining odd number of leaves, the leaf number to exclude
+ ProjectCreateError:
+ type: object
+ properties:
+ project:
+ type: object
+ properties:
+ title:
+ type: array
+ items:
+ type: string
+ example: [Project title is required, Project title should be unique]
+ manuscript:
+ type: object
+ properties:
+ shelfmark:
+ type: array
+ items:
+ type: string
+ example: [Manuscript shelfmark is required]
+ groups:
+ type: array
+ items:
+ type: object
+ properties:
+ groupID:
+ type: integer
+ example: 1
+ description: the group number that has errors
+ number:
+ type: array
+ items:
+ type: string
+ example: [should be an Integer, should be greater than 0, should be equal to 1]
+ leaves:
+ type: array
+ items:
+ type: string
+ example: [should be an Integer, should be greater than 0]
+ conjoin:
+ type: array
+ items:
+ type: string
+ example: [should be a Boolean]
+ oddLeaf:
+ type: array
+ items:
+ type: string
+ example: [should be an Integer, should be greater than 0, cannot be greater than leaves]
+
+ ProjectUpdateParams:
+ type: object
+ properties:
+ project:
+ type: object
+ properties:
+ title:
+ type: string
+ example: My first project
+ manuscript:
+ type: object
+ properties:
+ shelfmark:
+ type: string
+ example: MSS 123
+ uri:
+ type: string
+ format: url
+ example: some iiif manifest url
+ date:
+ type: string
+ example: 18th century
+
+ ProjectUpdateError:
+ type: object
+ properties:
+ project:
+ type: object
+ properties:
+ title:
+ type: array
+ items:
+ type: string
+ example: [Project title is required, Project title should be unique]
+ manuscript:
+ type: object
+ properties:
+ shelfmark:
+ type: array
+ items:
+ type: string
+ example: [Manuscript shelfmark is required]
+
+ ProjectResponseFull:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ example: 5951303fc9bf3c7b9a573a3f
+ title:
+ type: string
+ example: My first project
+ created_at:
+ type: string
+ example: "2017-07-12T14:04:34.799Z"
+ updated_at:
+ type: string
+ example: "2017-07-12T14:04:34.799Z"
+ noteTypes:
+ type: array
+ items:
+ type: string
+ example: [Unknown, Ink, Hand]
+ notes:
+ $ref: '#/definitions/NotesFullResponse'
+ manuscript:
+ $ref: '#/definitions/ManuscriptResponseFull'
+ ManuscriptResponseFull:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ example: 5951303fc9bf3c7b9a573a3f
+ shelfmark:
+ type: string
+ example: MSS 154
+ uri:
+ type: string
+ format: url
+ example: http://universalviewer.azurewebsites.net/manifests.json
+ date:
+ type: string
+ example: 16th century
+ created_at:
+ type: string
+ example: "2017-07-12T14:04:34.799Z"
+ updated_at:
+ type: string
+ example: "2017-07-12T14:04:34.799Z"
+ numberOfLeaves:
+ type: integer
+ example: 6
+ numberOfGroups:
+ type: integer
+ example: 2
+ attachedToLeafs:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ example: 5951303fc9bf3c7b9a573a3f
+ order:
+ type: string
+ example: None or Binding
+ groups:
+ type: array
+ items:
+ $ref: '#definitions/GroupFullResponse'
+ GroupFullResponse:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ example: 5951303fc9bf3c7b9a573a3f
+ order:
+ type: integer
+ example: 1
+ description: global order within the project
+ title:
+ type: string
+ example: Deafult
+ type:
+ type: string
+ example: Quire/Booklet
+ member_type:
+ type: string
+ example: Group
+ member_order:
+ type: integer
+ example: 1
+ nestLevel:
+ type: integer
+ example: 0
+ description: nested level within groups
+ members:
+ type: array
+ items:
+ $ref: '#definitions/MemberFullResponse'
+ MemberFullResponse:
+ type: object
+ properties:
+ member_type:
+ type: string
+ example: Group/Leaf
+ member_order:
+ type: integer
+ example: 1
+ description: local order within the group
+ id:
+ type: string
+ format: uuid
+ example: 5951303fc9bf3c7b9a573a3f
+ order:
+ type: integer
+ example: 1
+ description: global order within the project
+ nestLevel:
+ type: integer
+ example: 0
+ description: nested level within groups
+ conjoined_leaf_order:
+ type: integer
+ example: 3
+ description: leaf order of this leaf's conjoined member
+ material:
+ type: string
+ example: Parchment
+ type:
+ type: string
+ example: Added
+ attachment_method:
+ type: string
+ example: Glued
+ conjoined_to:
+ type: string
+ example: 595e59f3c9bf3c6760f2e328
+ description: leafID of the conjoined leaf
+ attached_to:
+ type: array
+ items:
+ type: string
+ example: 595e59f3c9bf3c6760f2e328
+ description: leafIDs of the attached_to leafs
+ stub:
+ type: string
+ example: Original
+ created_at:
+ type: string
+ example: "2017-07-12T14:04:34.799Z"
+ updated_at:
+ type: string
+ example: "2017-07-12T14:04:34.799Z"
+ parent:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ example: 5951303fc9bf3c7b9a573a3f
+ title:
+ type: string
+ example: Deafult
+ order:
+ type: integer
+ example: 1
+ description: global order within the project
+ type:
+ type: string
+ example: Quire/Booklet
+ sides:
+ type: array
+ items:
+ $ref: '#definitions/SideFullResponse'
+ SideFullResponse:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ example: 5951303fc9bf3c7b9a573a3f
+ order:
+ type: integer
+ example: 0
+ description: either 0 or 1, Recto or Verso
+ folio_number:
+ type: string
+ example: 2v
+ texture:
+ type: string
+ example: Hair
+ uri:
+ type: string
+ format: url
+ example: some iiif image url
+ script_direction:
+ type: string
+ example: Left
+ created_at:
+ type: string
+ example: "2017-07-12T14:04:34.799Z"
+ updated_at:
+ type: string
+ example: "2017-07-12T14:04:34.799Z"
+ LeafCreateParams:
+ type: object
+ properties:
+ leaf:
+ type: object
+ required:
+ - manuscript_id
+ properties:
+ manuscript_id:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ order:
+ type: integer
+ example: 2
+ material:
+ type: string
+ example: Parchment
+ type:
+ type: string
+ example: Added
+ attachment_method:
+ type: string
+ example: Glued
+ conjoined_to:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ description: leafID of the conjoined leaf
+ atached_to:
+ type: array
+ items:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ description: leafIDs of the attached_to leafs
+ stub:
+ type: string
+ example: Original
+ additional:
+ type: object
+ required:
+ - groupID
+ - memberOrder
+ - noOfLeafs
+ - conjoin
+ - oddMemberLeftOut
+ - noOfRepeats
+ properties:
+ groupID:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ description: groupID of the group this leaf belongs to
+ memberOrder:
+ type: integer
+ example: 2
+ description: the local order within this group
+ noOfLeafs:
+ type: integer
+ example: 5
+ description: total number of leaves to add
+ conjoin:
+ type: boolean
+ example: true
+ description: whether to auto-conjoin or not
+ oddMemberLeftOut:
+ type: integer
+ example: 2
+ description: if auto-conjoining odd number of leaves, the leaf number to exclude
+ noOfRepeats:
+ type: integer
+ example: 2
+ description: if auto-conjoining, the number of times to repeat this action
+
+
+ GroupCreateParams:
+ type: object
+ properties:
+ group:
+ type: object
+ properties:
+ manuscript_id:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ type:
+ type: string
+ example: Quire/Booklet
+ title:
+ type: string
+ example: Some title
+ order:
+ type: integer
+ example: 2
+ additional:
+ type: object
+ properties:
+ parentGroupID:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ description: groupID of the group this group belongs to. can be null if this is a root group.
+ memberOrder:
+ type: integer
+ example: 2
+ description: the local order of this group within the manuscript
+ noOfGroups:
+ type: integer
+ example: 5
+ description: total number of groups to add
+ noOfLeafs:
+ type: integer
+ example: 5
+ description: total number of leaves to add in the group
+ conjoin:
+ type: boolean
+ example: true
+ description: whether to auto-conjoin or not
+ oddMemberLeftOut:
+ type: integer
+ example: 2
+ description: if auto-conjoining odd number of leaves, the leaf number to exclude
+ GroupCreateError:
+ type: object
+ properties:
+ group:
+ type: object
+ properties:
+ manuscript_id:
+ type: array
+ items:
+ type: string
+ example: [is required, should be a String, manuscript not found]
+ type:
+ type: array
+ items:
+ type: string
+ example: [is required, should be either Quire or Booklet]
+ order:
+ type: array
+ items:
+ type: string
+ example: [is required, should be an Integer]
+ additional:
+ type: object
+ properties:
+ parentGroupID:
+ type: array
+ items:
+ type: string
+ example: [is required, should be a String, Group with groupID does not exist]
+ memberOrder:
+ type: array
+ items:
+ type: integer
+ example: [is required, should be an Integer, should be greater than 0]
+ noOfGroups:
+ type: array
+ items:
+ type: integer
+ example: [is required, should be an Integer, should be greater than 0 or less than 999]
+ noOfLeafs:
+ type: array
+ items:
+ type: integer
+ example: [is required, should be an Integer, should be greater than 0 or less than 999]
+ conjoin:
+ type: array
+ items:
+ type: boolean
+ example: [is required, should be a Boolean, should be false if noOfLeafs is 1]
+ oddMemberLeftOut:
+ type: array
+ items:
+ type: integer
+ example: [is required, should be an Integer, should be greater than 0 and less than noOfLeafs, should only be 0 if noOfLeafs is even]
+
+ GroupUpdateParams:
+ type: object
+ properties:
+ group:
+ type: object
+ properties:
+ type:
+ type: string
+ example: Quire
+ title:
+ type: string
+ example: Some title
+ GroupUpdateError:
+ type: object
+ properties:
+ group:
+ type: object
+ properties:
+ type:
+ type: array
+ items:
+ type: string
+ example: [should be either Quire or Booklet]
+ GroupUpdateMultipleParams:
+ type: object
+ properties:
+ groups:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ attributes:
+ type: object
+ properties:
+ type:
+ type: string
+ example: Quire/Booklet
+ title:
+ type: string
+ example: Some title
+
+ GroupUpdateMultipleError:
+ type: object
+ properties:
+ groups:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ example: group not found with id 5971005ec9bf3c32462bdd37s
+ attributes:
+ type: object
+ properties:
+ type:
+ type: string
+ example: should be either Quire or Booklet
+ GroupDeleteMultipleParams:
+ type: object
+ properties:
+ groups:
+ type: array
+ items:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ GroupDeleteMultipleError:
+ type: object
+ properties:
+ groups:
+ type: array
+ items:
+ type: string
+ example: [group not found with id 5971005ec9bf3c32462bdd37s]
+
+
+ LeafCreateError:
+ type: object
+ properties:
+ leaf:
+ type: object
+ properties:
+ manuscript_id:
+ type: array
+ items:
+ type: string
+ example: [is required, should be a String, manuscript not found]
+ order:
+ type: array
+ items:
+ type: integer
+ example: [is required, should be an Integer, should be greater than 0]
+ additional:
+ type: object
+ properties:
+ groupID:
+ type: array
+ items:
+ type: string
+ example: [is required, should be a String, Group with groupID does not have manuscript_id as a member, group not found]
+ memberOrder:
+ type: array
+ items:
+ type: string
+ example: [is required, should be an Integer, should be greater than 0]
+ noOfLeafs:
+ type: array
+ items:
+ type: string
+ example: [is required, should be an Integer, should be greater than 0 or less than 999]
+ conjoin:
+ type: array
+ items:
+ type: string
+ example: [is required, should be a Boolean, should be false if noOfLeafs is 1]
+ oddMemberLeftOut:
+ type: array
+ items:
+ type: string
+ example: [is required, should be an Integer, should be greater than 0 and less than noOfLeafs, should only be 0 if noOfLeafs is even]
+ noOfRepeats:
+ type: array
+ items:
+ type: string
+ example: [is required, should be an Integer, should only be 1 if conjoin is false, should be greater than 1 or less than 99]
+ LeafUpdateParams:
+ type: object
+ properties:
+ leaf:
+ type: object
+ properties:
+ order:
+ type: integer
+ example: 2
+ material:
+ type: string
+ example: Parchment
+ type:
+ type: string
+ example: Added
+ attachment_method:
+ type: string
+ example: Glued
+ conjoined_to:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ description: leafID of the conjoined leaf
+ attached_to:
+ type: object
+ properties:
+ aboveID:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ aboveMethod:
+ type: string
+ example: Glued
+ belowID:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ belowMethod:
+ type: string
+ example: Sewn
+ stub:
+ type: string
+ example: Original
+ LeafUpdateError:
+ type: object
+ properties:
+ leaf:
+ type: object
+ properties:
+ order:
+ type: array
+ items:
+ type: integer
+ example: should be an Integer, should be greater than 0
+ conjoined_to:
+ type: array
+ items:
+ type: integer
+ example: conjoined_to leaf does not exist
+ attached_to:
+ type: object
+ properties:
+ aboveID:
+ type: array
+ items:
+ type: string
+ example: [Missing parameter aboveID, Leaf not found with id 5951303fc9bf3c7b9a573a3f]
+ aboveMethod:
+ type: array
+ items:
+ type: string
+ example: [Should be one of Glued Sewn or Tacketed]
+ belowID:
+ type: array
+ items:
+ type: string
+ example: [Missing parameter belowID, Leaf not found with id 5951303fc9bf3c7b9a573a3f]
+ belowMethod:
+ type: array
+ items:
+ type: string
+ example: [Should be one of Glued Sewn or Tacketed]
+ LeafUpdateMultipleParams:
+ type: object
+ properties:
+ leafs:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ attributes:
+ type: object
+ properties:
+ material:
+ type: string
+ example: Parchment
+ type:
+ type: string
+ example: Added
+ stub:
+ type: string
+ example: Original
+ LeafUpdateMultipleError:
+ type: object
+ properties:
+ leafs:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: array
+ items:
+ type: string
+ example: leaf not found with id 5951303fc9bf3c7b9a573a3f
+ attributes:
+ type: object
+ properties:
+ material:
+ type: array
+ items:
+ type: string
+ example: [Invalid]
+ type:
+ type: array
+ items:
+ type: string
+ example: [Invalid]
+ stub:
+ type: array
+ items:
+ type: string
+ example: [Invalid]
+ LeafDeleteMultipleParams:
+ type: object
+ properties:
+ leafs:
+ type: array
+ items:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+
+ LeafDeleteMultipleError:
+ type: object
+ properties:
+ leafs:
+ type: array
+ items:
+ type: string
+ example: leaf not found with id 5951303fc9bf3c7b9a573a3f
+
+
+
+ SideUpdateParams:
+ type: object
+ properties:
+ side:
+ type: object
+ properties:
+ folio_number:
+ type: string
+ example: 1v
+ texture:
+ type: string
+ example: Paper
+ uri:
+ type: string
+ example: some IIIF image url
+ script_direction:
+ type: string
+ example: left
+ SideUpdateError:
+ type: object
+ properties:
+ side:
+ type: object
+ properties:
+ folio_number:
+ type: array
+ items:
+ type: string
+ example: [Invalid]
+ texture:
+ type: array
+ items:
+ type: string
+ example: [Invalid]
+ uri:
+ type: array
+ items:
+ type: string
+ example: [Invalid URL]
+ script_direction:
+ type: array
+ items:
+ type: string
+ example: [Invalid]
+
+ SideUpdateMultipleParams:
+ type: object
+ properties:
+ sides:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ attributes:
+ type: object
+ properties:
+ texture:
+ type: string
+ example: Paper
+ script_direction:
+ type: string
+ example: left
+
+ SideUpdateMultipleError:
+ type: object
+ properties:
+ sides:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: array
+ items:
+ type: string
+ example: side not found with id 5951303fc9bf3c7b9a573a3f
+
+ NoteCreateParams:
+ type: object
+ properties:
+ note:
+ type: object
+ properties:
+ project_id:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ title:
+ type: string
+ example: some title for note
+ type:
+ type: string
+ example: Ink
+ description:
+ type: string
+ example: blue ink
+
+ NoteUpdateParams:
+ type: object
+ properties:
+ note:
+ type: object
+ properties:
+ title:
+ type: string
+ example: some title for note
+ type:
+ type: string
+ example: Ink
+ description:
+ type: string
+ example: blue ink
+
+ NotesFullResponse:
+ type: array
+ items:
+ $ref: '#definitions/NoteFullResponse'
+
+
+ NoteFullResponse:
+ type: object
+ properties:
+ id:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ title:
+ type: string
+ example: some title for note
+ type:
+ type: string
+ example: Ink
+ description:
+ type: string
+ example: blue ink
+ created_at:
+ type: string
+ example: "2017-07-12T14:04:34.799Z"
+ updated_at:
+ type: string
+ example: "2017-07-12T14:04:34.799Z"
+ objects:
+ type: object
+ properties:
+ Group:
+ type: object
+ properties:
+ 5951303fc9bf3c7b9a573a3f:
+ type: string
+ example: 1
+ Leaf:
+ type: object
+ properties:
+ 5951303fc9bf3c7b9a573a3f:
+ type: string
+ example: 2
+ Side:
+ type: object
+ properties:
+ 5951303fc9bf3c7b9a573a3f:
+ type: string
+ example: 1
+
+ NoteLinkParams:
+ type: object
+ properties:
+ objects:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ type:
+ type: string
+ example: Group
+
+ NoteCreateError:
+ type: object
+ properties:
+ title:
+ type: array
+ example: [Note title should be uniue]
+ type:
+ type: array
+ example: [Note type is required]
+
+ NoteLinkError:
+ type: object
+ properties:
+ id:
+ type: string
+ example: Group object not found with id 5984d709c9bf3c1f76fd3fb
+ type:
+ type: string
+ example: object not found with type Groupssss
+
+ NoteTypeCreateParams:
+ type: object
+ properties:
+ noteType:
+ type: object
+ properties:
+ project_id:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ type:
+ type: string
+ example: Ink
+
+ NoteTypeUpdateParams:
+ type: object
+ properties:
+ noteType:
+ type: object
+ properties:
+ project_id:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ type:
+ type: string
+ example: Ink
+ old_type:
+ type: string
+ example: Inkss
+
+ NoteTypeCreateError:
+ type: object
+ properties:
+ project_id:
+ type: string
+ example: project object not found with id 5984d709c9bf3c1f76fd3fb
+ type:
+ type: string
+ example: already exists in the project
+
+
+ NoteTypeUpdateError:
+ type: object
+ properties:
+ project_id:
+ type: string
+ example: project object not found with id 5984d709c9bf3c1f76fd3fb
+ type:
+ type: string
+ example: already exists in the project
+ old_type:
+ type: string
+ example: doesn't exist in the project
+
+
+ NoteTypeDeleteError:
+ type: object
+ properties:
+ project_id:
+ type: string
+ example: project object not found with id 5984d709c9bf3c1f76fd3fb
+ type:
+ type: string
+ example: doesn't exist in the project
+
+ NoteTypeResponse:
+ type: object
+ properties:
+ noteTypes:
+ type: array
+ items:
+ type: string
+ example: [Ink, Hand]
+
+
+ ProjectFilterParams:
+ type: object
+ properties:
+ queries:
+ type: array
+ items:
+ type: object
+ properties:
+ type:
+ type: string
+ example: Leaf
+ attribute:
+ type: string
+ example: Material
+ condition:
+ type: string
+ example: equals
+ values:
+ type: array
+ example: [paper, parchment]
+ conjunction:
+ type: string
+ example: AND
+
+ ProjectFilterResponse:
+ type: object
+ properties:
+ Groups:
+ type: array
+ items:
+ type: string
+ example: [5984d709c9bf3c1f76fd3fb, 5984d709c9bf3c1f76asdsadb, sad84d709c9bf3c1f76fd3fb]
+ Leafs:
+ type: array
+ items:
+ type: string
+ example: [5984d709c9bf3c1f76fd3fb, 5984d709c9bf3c1f76asdsadb, sad84d709c9bf3c1f76fd3fb]
+ Sides:
+ type: array
+ items:
+ type: string
+ example: [5984d709c9bf3c1f76fd3fb, 5984d709c9bf3c1f76asdsadb, sad84d709c9bf3c1f76fd3fb]
+ Notes:
+ type: array
+ items:
+ type: string
+ example: [5984d709c9bf3c1f76fd3fb, 5984d709c9bf3c1f76asdsadb, sad84d709c9bf3c1f76fd3fb]
+ GroupsOfLeafs:
+ type: array
+ items:
+ type: string
+ example: [5984d709c9bf3c1f76fd3fb, 5984d709c9bf3c1f76asdsadb, sad84d709c9bf3c1f76fd3fb]
+
+ ProjectFilterError:
+ type: object
+ properties:
+ errors:
+ type: array
+ items:
+ type: object
+ properties:
+ type:
+ type: string
+ example: valid attributes for group are type, title
+ attribute:
+ type: string
+ example: valid attributes for leafare type, material, conjoined_to, attached_to, stub
+ condition:
+ type: string
+ example: valid conditions for leaf attribute are equals, not_equals
+ values:
+ type: string
+ example: filter value cannot be empty
+ conjunction:
+ type: string
+ example: conjunction should be one of AND, OR
+
+ ProjectChildrenResponse:
+ type: object
+ properties:
+ groups:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ example: 5984d709c9bf3c1f76fd3fb
+ order:
+ type: string
+ example: 2
+ leafs:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ example: 5984d709c9bf3c1f76fd3fb
+ order:
+ type: string
+ example: 2
+ sides:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ example: 5984d709c9bf3c1f76fd3fb
+ order:
+ type: string
+ example: 2
+
+
+basePath: /api
+
diff --git a/viscoll-api/public/viscoll-datamodel2.0.rng b/viscoll-api/public/viscoll-datamodel2.0.rng
new file mode 100644
index 00000000..127f095a
--- /dev/null
+++ b/viscoll-api/public/viscoll-datamodel2.0.rng
@@ -0,0 +1,390 @@
+
+
+
+
+
+ On viscoll document may contain multiple textblock elements
+
+ Optional @url points to a record describing the textblock, e.g. a catalog
+ record
+
+
+
+
+
+
+
+ Optional
+ @title provides the title of the textblock as defined by the people
+ or group doing the cataloging (viscoll does not define
+ title)
+
+
+
+
+
+
+
+
+ @date is
+ optional and is undefined. Could be linked to a controlled
+ vocabulary in tools (e.g., a list of centuries)
+
+
+
+
+
+ origPlace is optional and is undefined. Could be linked to a
+ controlled vocabulary in tools
+
+
+
+
+
+
+ direction is required and the default value is l-r
+
+
+ l-r
+ r-l
+
+
+
+
+
+
+ refers to the format, usually of a printed book. Possible values are 2mo, 4mo, 8mo, 12mo, 16mo, 32mo, 64mo
+
+
+ 2mo
+ 4mo
+ 8mo
+ 12mo
+ 16mo
+ 24mo
+
+
+
+
+
+
+
+ refers to the category list in the "Table of Fifteenth-Century Paper Flavors" for the Needham Calculator (http://www.needhamcalculator.net/)
+
+
+ imperial
+ super-royal
+ royal
+ super-median
+ median
+ super-chancery
+ chancery
+ half-median
+
+
+
+
+
+
+ The
+ textblock begins with a list of quires
+
+
+
+
+
+
+
+ One leaf element to describe each leaf in the
+ textblock
+
+
+
+ If a leaf is a stub, the value of @stub is "yes" -
+ otherwise @stub is not there
+ yes
+
+
+
+ id
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ original
+ added
+ replaced
+ missing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ single only has the value of "yes" and is only used if the
+ leaf is a singleton
+
+
+ yes
+
+
+
+
+
+
+
+
+
+ Indicates the leaf (recto of) which is the start of the first quire.
+
+
+ yes
+
+
+
+
+
+
+
+
+
+ Indicates if the leaf includes a foldout. The @direction attribute specifies direction (out, up, or down). May be repeated if the leaf has multiple foldouts. The order of multiple foldouts must be the order in which the leaf is unfolded.
+
+
+ out
+ in
+ up
+ down
+
+
+
+
+
+
+
+
+
+ defines the values allowed for identifying the methods by which one leaf is attached to another. For "sewn" if there is a @target identified, the two leaves are sewn together, if there is no @target identified it is sewn through the fold. Use "sewn" through the fold only when you see it (i.e., in the center of a quire)
+
+
+
+
+
+
+
+ sewn
+ pasted
+ tipped
+ drummed
+ other
+
+
+
+
+
+
+
+
+
+
+
+
+ certainty
+
+
+
+
+ id
+
+
+
+
+
+
+ viscoll
+ element begins with an optional set of taxonomy definitions
+
+
+ taxonomy
+ if
+ the taxonomy is defined externally, e.g. the Getty Art & Architecture
+ Thesaurus, include a @ref pointing to it
+
+
+
+
+
+
+ id
+
+
+
+ label
+
+
+
+
+ Any defined taxonomy must include at least one term. If the taxonomy is defined external to the viscoll document, the term must have a @ref pointing to the external definition
+ term
+
+
+
+
+
+
+ id
+
+
+
+
+
+
+
+
+
+ mapping
+
+
+ map
+
+
+
+
+
+
+
+
+
+ term
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ values for @certainty: 1 = very certain, 2 = fairly certain, 3 = not certain
+
+ 1
+ 2
+ 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ subquires must point to a parent quire
+
+
+
+
+
+
+ id
+
+
+
+
+
+
+
diff --git a/viscoll-api/public/viscoll-datamodel2.rng b/viscoll-api/public/viscoll-datamodel2.rng
new file mode 100644
index 00000000..4fd9ad77
--- /dev/null
+++ b/viscoll-api/public/viscoll-datamodel2.rng
@@ -0,0 +1,299 @@
+
+
+
+
+ viscoll element begins with an optional set of taxonomy definitions
+
+
+ taxonomy
+ if the taxonomy is defined externally, e.g. the Getty Art & Architecture Thesaurus, include a @ref pointing to it
+
+
+
+
+
+
+ id
+
+
+
+
+ label
+
+
+
+
+
+ term
+
+
+
+
+
+
+ id
+
+
+
+
+
+
+
+
+
+
+
+
+ Optional @url points to a record describing the manuscript, e.g. a catalog record
+
+
+
+
+
+
+
+ Optional @title provides the title of the manuscript as defined by the people or group doing the cataloging (viscoll does not define title)
+
+
+
+
+
+
+
+
+
+ @date is optional and is undefined. Could be linked to a controlled vocabulary in tools
+
+
+
+
+
+ origPlace is optional and is undefined. Could be linked to a controlled vocabulary in tools
+
+
+
+
+
+
+ direction is required and the default value is l-r
+
+
+ l-r
+ r-l
+
+
+
+
+
+ The manuscript begins with a list of quires
+
+
+
+
+
+
+
+ One leaf element to describe each leaf in the manuscript
+
+
+
+ If a leaf is a stub, the value of @stub is "yes" - otherwise @stub is not there
+ yes
+
+
+
+ id
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ original
+ added
+ replaced
+ false
+ missing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ single only has the value of "yes" and is only used if the leaf is a singleton
+
+
+ yes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ certainty
+
+
+
+
+ id
+
+
+
+
+
+
+
+
+
+ mapping
+
+
+ map
+
+
+
+
+
+
+
+
+
+ term
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 2
+ 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+
+
+
+
diff --git a/viscoll-api/spec/factories/groups.rb b/viscoll-api/spec/factories/groups.rb
new file mode 100644
index 00000000..8eed6302
--- /dev/null
+++ b/viscoll-api/spec/factories/groups.rb
@@ -0,0 +1,73 @@
+FactoryGirl.define do
+ sequence :quire_title do |n|
+ "Quire #{n}"
+ end
+ sequence :booklet_title do |n|
+ "Booklet #{n}"
+ end
+ sequence :group_title do |n|
+ "Group #{n}"
+ end
+ factory :group, class: Group do
+ transient do
+ members []
+ end
+ after(:create) do |group, evaluator|
+ group.nestLevel ||= 1
+ unless evaluator.members.blank?
+ newmembers = evaluator.members.each do |member|
+ if member.is_a?(Group)
+ member.nestLevel = group.nestLevel+1
+ else
+ member.nestLevel = group.nestLevel
+ end
+ member.save
+ end
+ group.add_members(newmembers.collect { |member| member.id.to_s }, 1)
+ end
+ group.save
+ end
+ title { generate(:group_title) }
+ type "Quire"
+ end
+
+ factory :quire, class: Group do
+ transient do
+ leafs 0
+ conjoined true
+ leaf_properties { {} }
+ start_page 1
+ end
+ after(:create) do |group, evaluator|
+ group.nestLevel ||= 1
+ unless evaluator.leafs <= 0
+ newleafprops = evaluator.leaf_properties.merge({
+ project_id: group.project_id,
+ parentID: group.id.to_s,
+ nestLevel: group.nestLevel
+ })
+ newleafs = evaluator.leafs.times.collect { |n|
+ FactoryGirl.build(:leaf, newleafprops.merge({ folio_number: evaluator.start_page+n }))
+ }
+ if evaluator.conjoined
+ evaluator.leafs.times.each do |n|
+ unless evaluator.leafs.odd? and n == evaluator.leafs >> 1
+ conjoin_id = newleafs[-1-n].id.to_s
+ newleafs[n].conjoined_to = if conjoin_id[0..4] == 'Leaf_' then conjoin_id else "Leaf_#{conjoin_id}" end
+ end
+ newleafs[n].save
+ end
+ end
+ group.add_members(newleafs.collect { |newleaf| newleaf.id.to_s }, 1)
+ end
+ end
+ title { generate(:quire_title) }
+ type "Quire"
+ end
+
+ factory :booklet, parent: :quire do
+ title { generate(:booklet_title) }
+ type "Booklet"
+ leafs 0
+ end
+end
diff --git a/viscoll-api/spec/factories/images.rb b/viscoll-api/spec/factories/images.rb
new file mode 100644
index 00000000..58238987
--- /dev/null
+++ b/viscoll-api/spec/factories/images.rb
@@ -0,0 +1,47 @@
+FactoryGirl.define do
+ sequence :image_filename do |n|
+ "Image #{n}"
+ end
+
+ sequence :image_fileid do |n|
+ "#{n}"
+ end
+
+ sequence :image_original_filename do |n|
+ "image_#{n}"
+ end
+
+ factory :image do
+ filename { generate(:image_filename) }
+
+ factory :pixel do
+ filename { 'pixel.png'}
+ fileID { 'pixel'}
+ metadata { {
+ "filename": "pixel.png",
+ "size": 20470,
+ "mime_type": "image/png"
+ } }
+ end
+
+ factory :shiba_inu do
+ filename { 'shiba_inu.png'}
+ fileID { 'shiba_inu'}
+ metadata { {
+ "filename": "shiba_inu.png",
+ "size": 20470,
+ "mime_type": "image/png"
+ } }
+ end
+
+ factory :viscoll_logo do
+ filename { 'viscoll_logo.png'}
+ fileID { 'viscoll_logo'}
+ metadata { {
+ "filename": "viscoll_logo.png",
+ "size": 20470,
+ "mime_type": "image/png"
+ } }
+ end
+ end
+end
diff --git a/viscoll-api/spec/factories/leafs.rb b/viscoll-api/spec/factories/leafs.rb
new file mode 100644
index 00000000..c56245d9
--- /dev/null
+++ b/viscoll-api/spec/factories/leafs.rb
@@ -0,0 +1,20 @@
+include ActionDispatch::TestProcess
+FactoryGirl.define do
+ factory :leaf do
+ transient {
+ folio_number nil
+ }
+ after(:create) do |leaf, evaluator|
+ unless evaluator.folio_number.blank?
+ Side.find(leaf.rectoID).update(folio_number: "#{evaluator.folio_number}R")
+ Side.find(leaf.versoID).update(folio_number: "#{evaluator.folio_number}V")
+ end
+ end
+ material "Paper"
+ type "Original"
+
+ factory :parchment do
+ material "Parchment"
+ end
+ end
+end
diff --git a/viscoll-api/spec/factories/notes.rb b/viscoll-api/spec/factories/notes.rb
new file mode 100644
index 00000000..2f5e6869
--- /dev/null
+++ b/viscoll-api/spec/factories/notes.rb
@@ -0,0 +1,34 @@
+FactoryGirl.define do
+ sequence :note_title do |n|
+ "Note #{n}"
+ end
+ sequence :note_text do |n|
+ "Blah #{n}"
+ end
+
+ factory :note do
+ transient do
+ attachments []
+ end
+ before(:build) do |note, evaluator|
+ myobjects = {Group: [], Leaf: [], Recto: [], Verso: []}
+ evaluator.attachments.each do |attachment|
+ if attachment.is_a? Group
+ myobjects[:Group] << attachment
+ elsif attachment.is_a? Leaf
+ myobjects[:Leaf] << attachment
+ elsif attachment.is_a? Side
+ if attachment.id.to_s[0..5] == 'Verso_'
+ myobjects[:Verso] << attachment
+ else
+ myobjects[:Recto] << attachment
+ end
+ else
+ raise Exception('Notes can only be attached to groups, leafs and sides')
+ end
+ end
+ end
+ title { generate(:note_title) }
+ type "Unknown"
+ end
+end
diff --git a/viscoll-api/spec/factories/projects.rb b/viscoll-api/spec/factories/projects.rb
new file mode 100644
index 00000000..1c30e681
--- /dev/null
+++ b/viscoll-api/spec/factories/projects.rb
@@ -0,0 +1,80 @@
+include ActionDispatch::TestProcess
+FactoryGirl.define do
+ sequence :title do |n|
+ "Project #{n}"
+ end
+ sequence :manifest_id do |n|
+ "Manifest_#{n}"
+ end
+ sequence :manifest_name do |n|
+ "Manifest #{n}"
+ end
+ sequence :manifest_url do |n|
+ "https://iiif.example.org/#{n}/manifest.json"
+ end
+
+ factory :empty_project, class: Project do
+ title { generate(:title) }
+ user_id { FactoryGirl.create(:user) }
+ end
+
+ factory :project do
+ transient do
+ with_members []
+ with_manifests []
+ end
+ before(:build) do |project, evaluator|
+ evaluator.with_manifests.each do |manifest|
+ mid = evaluator.generate(:manifest_id)
+ manifest[:id] = mid
+ project.manifests[mid] = manifest
+ end
+ end
+ after(:create) do |project, evaluator|
+ evaluator.with_members.each do |member|
+ member.project_id = project.id
+ member.nestLevel ||= 1
+ member.save
+ end
+ unless evaluator.with_members.blank?
+ project.add_groupIDs(evaluator.with_members.collect { |member| member.id.to_s }, 1)
+ end
+ end
+ title { generate(:title) }
+ user_id { FactoryGirl.create(:user) }
+ end
+
+ factory :codex_project, parent: :project do
+ transient do
+ manifest_count 0
+ quire_structure { [[4, 6]] }
+ end
+ before(:build) do |project, evaluator|
+ evaluator.manifest_count.times do
+ manifest = FactoryGirl.build(:manifest)
+ project.manifests[manifest[:id]] = manifest
+ end
+ end
+ after(:create) do |project, evaluator|
+ start_page = 1
+ members = []
+ evaluator.quire_structure.each do |qs|
+ qs[0].times do
+ members << FactoryGirl.create(:quire, project_id: project.id, leafs: qs[1], start_page: start_page, nestLevel: 1)
+ start_page += qs[1]
+ end
+ end
+ unless members.blank?
+ project.add_groupIDs(members.collect { |member| member.id.to_s }, 1)
+ end
+ end
+ end
+
+ factory :manifest, class: Hash do
+ id { generate(:manifest_id) }
+ url { generate(:manifest_url) }
+ name { generate(:manifest_name) }
+ initialize_with { attributes }
+ to_create { }
+ end
+end
diff --git a/viscoll-api/spec/factories/sides.rb b/viscoll-api/spec/factories/sides.rb
new file mode 100644
index 00000000..870fbe9c
--- /dev/null
+++ b/viscoll-api/spec/factories/sides.rb
@@ -0,0 +1,5 @@
+include ActionDispatch::TestProcess
+FactoryGirl.define do
+ factory :side do
+ end
+end
diff --git a/viscoll-api/spec/factories/users.rb b/viscoll-api/spec/factories/users.rb
new file mode 100644
index 00000000..3de3818e
--- /dev/null
+++ b/viscoll-api/spec/factories/users.rb
@@ -0,0 +1,8 @@
+include ActionDispatch::TestProcess
+FactoryGirl.define do
+ factory :user do
+ name {Faker::Name.name}
+ email {Faker::Internet.email}
+ password {Faker::Internet.password}
+ end
+end
diff --git a/viscoll-api/spec/fixtures/base64zip.txt b/viscoll-api/spec/fixtures/base64zip.txt
new file mode 100644
index 00000000..513554ed
--- /dev/null
+++ b/viscoll-api/spec/fixtures/base64zip.txt
@@ -0,0 +1 @@
+data:application/zip;base64,UEsDBBQACAAIAMdVrU4AAAAAAAAAAAAAAAAfABAAMVJfNWEyODIyMWVjMTk5ODYwZTdhMmY1ZmQxLnBuZ1VYDADtkdlcNoPZXPcBFADrDPBz5+WS4mJgYOD19HAJAtKMIMzBBiTlRY90giVcHEMqbiX/+T9/oRwDexNTve0Jj/dACQZPVz+XdU4JTQBQSwcIbxz83T8AAABGAAAAUEsDBBQACAAIAMdVrU4AAAAAAAAAAAAAAAAVABAAMlJfMmY1ZmQxc2hpYmFpbnUucG5nVVgMAEGD2Vw2g9lc9wEUAOsM8HPn5ZLiYmBg4PX0cAkC0owgzMEGJOVFj3SCJVwcQypuJf/5P3+hHAN7E1O97QmP90AJBk9XP5d1TglNAFBLBwhvHPzdPwAAAEYAAABQSwMEFAAIAAgAx1WtTgAAAAAAAAAAAAAAAB8AEAAxVl81YTI4MjIxZWMxOTk4NjBlN2EyZjVmZDEucG5nVVgMAEGD2Vw2g9lc9wEUAOsM8HPn5ZLiYmBg4PX0cAkC0owgzMEGJOVFj3SCJVwcQypuJf/5P3+hHAN7E1O97QmP90AJBk9XP5d1TglNAFBLBwhvHPzdPwAAAEYAAABQSwECFQMUAAgACADHVa1Obxz83T8AAABGAAAAHwAMAAAAAAAAAABApIEAAAAAMVJfNWEyODIyMWVjMTk5ODYwZTdhMmY1ZmQxLnBuZ1VYCADtkdlcNoPZXFBLAQIVAxQACAAIAMdVrU5vHPzdPwAAAEYAAAAVAAwAAAAAAAAAAECkgZwAAAAyUl8yZjVmZDFzaGliYWludS5wbmdVWAgAQYPZXDaD2VxQSwECFQMUAAgACADHVa1Obxz83T8AAABGAAAAHwAMAAAAAAAAAABApIEuAQAAMVZfNWEyODIyMWVjMTk5ODYwZTdhMmY1ZmQxLnBuZ1VYCABBg9lcNoPZXFBLBQYAAAAAAwADAAEBAADKAQAAAAA=
\ No newline at end of file
diff --git a/viscoll-api/spec/fixtures/dots_exported.zip b/viscoll-api/spec/fixtures/dots_exported.zip
new file mode 100644
index 00000000..f429e93b
Binary files /dev/null and b/viscoll-api/spec/fixtures/dots_exported.zip differ
diff --git a/viscoll-api/spec/fixtures/pixel.png b/viscoll-api/spec/fixtures/pixel.png
new file mode 100644
index 00000000..0f2de374
Binary files /dev/null and b/viscoll-api/spec/fixtures/pixel.png differ
diff --git a/viscoll-api/spec/fixtures/sample_import_json.json b/viscoll-api/spec/fixtures/sample_import_json.json
new file mode 100644
index 00000000..8759e829
--- /dev/null
+++ b/viscoll-api/spec/fixtures/sample_import_json.json
@@ -0,0 +1,295 @@
+{
+ "project":{
+ "title":"Sample project",
+ "shelfmark":"Ravenna 384.2339",
+ "metadata":{
+ "date":"18th century"
+ },
+ "preferences":{
+ "showTips":true
+ },
+ "manifests":{
+ "12341234":{
+ "id":"12341234",
+ "url":"https://digital.library.villanova.edu/Item/vudl:99213/Manifest",
+ "name":"Boston, and Bunker Hill."
+ }
+ },
+ "noteTypes":[
+ "Hand",
+ "Ink",
+ "Unknown"
+ ]
+ },
+ "Groups":{
+ "1":{
+ "params":{
+ "type":"Quire",
+ "title":"Quire 1",
+ "nestLevel":1
+ },
+ "tacketed":[
+ ],
+ "sewing":[
+ ],
+ "parentOrder":null,
+ "memberOrders":[
+ "Leaf_1",
+ "Leaf_2",
+ "Group_2",
+ "Leaf_5",
+ "Leaf_6"
+ ]
+ },
+ "2":{
+ "params":{
+ "type":"Quire",
+ "title":"Quire 2",
+ "nestLevel":2
+ },
+ "tacketed":[
+ ],
+ "sewing":[
+ ],
+ "parentOrder":1,
+ "memberOrders":[
+ "Leaf_3",
+ "Leaf_4"
+ ]
+ }
+ },
+ "Leafs":{
+ "1":{
+ "params":{
+ "material":"Paper",
+ "type":"Original",
+ "attached_above":"None",
+ "attached_below":"None",
+ "stub":"None",
+ "nestLevel":1
+ },
+ "conjoined_leaf_order":null,
+ "parentOrder":1,
+ "rectoOrder":1,
+ "versoOrder":1
+ },
+ "2":{
+ "params":{
+ "material":"Paper",
+ "type":"Original",
+ "attached_above":"None",
+ "attached_below":"None",
+ "stub":"None",
+ "nestLevel":1
+ },
+ "conjoined_leaf_order":null,
+ "parentOrder":1,
+ "rectoOrder":2,
+ "versoOrder":2
+ },
+ "3":{
+ "params":{
+ "material":"Paper",
+ "type":"Original",
+ "attached_above":"None",
+ "attached_below":"None",
+ "stub":"None",
+ "nestLevel":2
+ },
+ "conjoined_leaf_order":4,
+ "parentOrder":2,
+ "rectoOrder":3,
+ "versoOrder":3
+ },
+ "4":{
+ "params":{
+ "material":"Paper",
+ "type":"Original",
+ "attached_above":"None",
+ "attached_below":"None",
+ "stub":"None",
+ "nestLevel":2
+ },
+ "conjoined_leaf_order":3,
+ "parentOrder":2,
+ "rectoOrder":4,
+ "versoOrder":4
+ },
+ "5":{
+ "params":{
+ "material":"Paper",
+ "type":"Original",
+ "attached_above":"None",
+ "attached_below":"None",
+ "stub":"None",
+ "nestLevel":1
+ },
+ "conjoined_leaf_order":null,
+ "parentOrder":1,
+ "rectoOrder":5,
+ "versoOrder":5
+ },
+ "6":{
+ "params":{
+ "material":"Paper",
+ "type":"Endleaf",
+ "attached_above":"None",
+ "attached_below":"None",
+ "stub":"None",
+ "nestLevel":1
+ },
+ "conjoined_leaf_order":null,
+ "parentOrder":1,
+ "rectoOrder":6,
+ "versoOrder":6
+ }
+ },
+ "Rectos":{
+ "1":{
+ "params":{
+ "folio_number":"1R",
+ "texture":"Hair",
+ "image":{
+ },
+ "script_direction":"None"
+ },
+ "parentOrder":1
+ },
+ "2":{
+ "params":{
+ "folio_number":"2R",
+ "texture":"Hair",
+ "image":{
+ },
+ "script_direction":"None"
+ },
+ "parentOrder":2
+ },
+ "3":{
+ "params":{
+ "folio_number":"3R",
+ "texture":"Hair",
+ "image":{
+ },
+ "script_direction":"None"
+ },
+ "parentOrder":3
+ },
+ "4":{
+ "params":{
+ "folio_number":"4R",
+ "texture":"Hair",
+ "image":{
+ },
+ "script_direction":"None"
+ },
+ "parentOrder":4
+ },
+ "5":{
+ "params":{
+ "folio_number":"5R",
+ "texture":"Hair",
+ "image":{
+ },
+ "script_direction":"None"
+ },
+ "parentOrder":5
+ },
+ "6":{
+ "params":{
+ "folio_number":"6R",
+ "texture":"Hair",
+ "image":{
+ },
+ "script_direction":"None"
+ },
+ "parentOrder":6
+ }
+ },
+ "Versos":{
+ "1":{
+ "params":{
+ "folio_number":"1V",
+ "texture":"Flesh",
+ "image":{
+ },
+ "script_direction":"None"
+ },
+ "parentOrder":1
+ },
+ "2":{
+ "params":{
+ "folio_number":"2V",
+ "texture":"Flesh",
+ "image":{
+ },
+ "script_direction":"None"
+ },
+ "parentOrder":2
+ },
+ "3":{
+ "params":{
+ "folio_number":"3V",
+ "texture":"Flesh",
+ "image":{
+ },
+ "script_direction":"None"
+ },
+ "parentOrder":3
+ },
+ "4":{
+ "params":{
+ "folio_number":"4V",
+ "texture":"Flesh",
+ "image":{
+ },
+ "script_direction":"None"
+ },
+ "parentOrder":4
+ },
+ "5":{
+ "params":{
+ "folio_number":"5V",
+ "texture":"Flesh",
+ "image":{
+ },
+ "script_direction":"None"
+ },
+ "parentOrder":5
+ },
+ "6":{
+ "params":{
+ "folio_number":"6V",
+ "texture":"Flesh",
+ "image":{
+ },
+ "script_direction":"None"
+ },
+ "parentOrder":6
+ }
+ },
+ "Notes":{
+ "1":{
+ "params":{
+ "title":"Test Note",
+ "type":"Ink",
+ "description":"This is a test",
+ "show":true
+ },
+ "objects":{
+ "Group":[
+ 1
+ ],
+ "Leaf":[
+ 5
+ ],
+ "Recto":[
+ 5
+ ],
+ "Verso":[
+ 5
+ ]
+ }
+ }
+ }
+}
diff --git a/viscoll-api/spec/fixtures/sample_import_xml.xml b/viscoll-api/spec/fixtures/sample_import_xml.xml
new file mode 100644
index 00000000..b976b570
--- /dev/null
+++ b/viscoll-api/spec/fixtures/sample_import_xml.xml
@@ -0,0 +1,154 @@
+
+
+
+ Manuscript preferences
+ true
+
+
+ List of Manifests
+ https://digital.library.villanova.edu/Item/vudl:99213/Manifest
+
+
+ List of values for Group title
+ Quire 1
+ Quire 2
+
+
+ List of values for Group type
+ Quire
+
+
+ List of values for each Group's members
+ #ravenna_384_2339-1-1 #ravenna_384_2339-1-2 #ravenna_384_2339-q-1-2 #ravenna_384_2339-1-3 #ravenna_384_2339-1-4
+ #ravenna_384_2339-1-2-3 #ravenna_384_2339-1-2-4
+
+
+ List of values for Leaf material
+ Paper
+
+
+ List of Attachment Methods
+ Glued (Partial)
+ Glued (Complete)
+ Glued (Drumming)
+ Glued (Other)
+ Glued (Partial)
+ Glued (Complete)
+ Glued (Drumming)
+ Glued (Other)
+
+
+ List of values for Side texture
+ Hair
+ Flesh
+
+
+ List of values for Note Titles
+ Test Note
+
+
+ Whether to show Note in Visualizations
+ true
+
+
+ Sample project
+ Ravenna 384.2339
+ 18th century
+
+
+ 1
+ 2
+
+
+ 1
+
+
+
+
+
+ 2
+
+
+
+
+
+ 3
+
+
+
+
+
+
+ 4
+
+
+
+
+
+
+ 5
+
+
+
+
+
+ 6
+
+
+
+
+
+
+ This is a test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/viscoll-api/spec/fixtures/shibainu.jpg b/viscoll-api/spec/fixtures/shibainu.jpg
new file mode 100644
index 00000000..c9580521
Binary files /dev/null and b/viscoll-api/spec/fixtures/shibainu.jpg differ
diff --git a/viscoll-api/spec/fixtures/uoft_hollar.json b/viscoll-api/spec/fixtures/uoft_hollar.json
new file mode 100644
index 00000000..4809cf1d
--- /dev/null
+++ b/viscoll-api/spec/fixtures/uoft_hollar.json
@@ -0,0 +1 @@
+{"@context":"http://iiif.io/api/presentation/2/context.json","@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest","@type":"sc:Manifest","label":"The fables of Aesop / paraphras'd in verse, and adorn'd with sculpture ; by John Ogilby.","attribution":"For rights and reproduction information please contact collections@library.utoronto.ca","sequences":[{"@type":"sc:Sequence","label":"The fables of Aesop / paraphras'd in verse, and adorn'd with sculpture ; by John Ogilby., in order","canvases":[{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0001","@type":"sc:Canvas","label":"Hollar_a_3000_0001","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0001/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0001","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0001/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0001","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0001"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0002","@type":"sc:Canvas","label":"Hollar_a_3000_0002","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0002/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0002","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0002/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0002","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0002"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0003","@type":"sc:Canvas","label":"Hollar_a_3000_0003","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0003/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0003","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0003/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0003","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0003"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0004","@type":"sc:Canvas","label":"Hollar_a_3000_0004","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0004/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0004","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0004/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0004","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0004"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0005","@type":"sc:Canvas","label":"Hollar_a_3000_0005","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0005/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0005","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0005/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0005","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0005"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0006","@type":"sc:Canvas","label":"Hollar_a_3000_0006","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0006/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0006","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0006/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0006","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0006"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0007","@type":"sc:Canvas","label":"Hollar_a_3000_0007","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0007/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0007","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0007/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0007","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0007"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0008","@type":"sc:Canvas","label":"Hollar_a_3000_0008","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0008/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0008","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0008/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0008","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0008"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0009","@type":"sc:Canvas","label":"Hollar_a_3000_0009","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0009/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0009","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0009/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0009","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0009"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0010","@type":"sc:Canvas","label":"Hollar_a_3000_0010","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0010/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0010","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0010/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0010","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0010"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0011","@type":"sc:Canvas","label":"Hollar_a_3000_0011","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0011/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0011","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0011/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0011","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0011"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0012","@type":"sc:Canvas","label":"Hollar_a_3000_0012","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0012/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0012","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0012/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0012","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0012"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0013","@type":"sc:Canvas","label":"Hollar_a_3000_0013","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0013/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0013","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0013/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0013","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0013"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0014","@type":"sc:Canvas","label":"Hollar_a_3000_0014","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0014/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0014","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0014/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0014","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0014"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0015","@type":"sc:Canvas","label":"Hollar_a_3000_0015","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0015/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0015","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0015/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0015","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0015"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0016","@type":"sc:Canvas","label":"Hollar_a_3000_0016","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0016/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0016","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4120,"width":2936,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0016/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4120,"width":2936,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0016","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0016"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0017","@type":"sc:Canvas","label":"Hollar_a_3000_0017","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0017/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0017","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2694,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0017/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2694,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0017","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0017"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0018","@type":"sc:Canvas","label":"Hollar_a_3000_0018","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0018/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0018","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2850,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0018/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2850,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0018","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0018"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0019","@type":"sc:Canvas","label":"Hollar_a_3000_0019","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0019/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0019","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2850,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0019/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2850,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0019","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0019"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0020","@type":"sc:Canvas","label":"Hollar_a_3000_0020","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0020/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0020","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2850,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0020/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2850,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0020","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0020"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0021","@type":"sc:Canvas","label":"Hollar_a_3000_0021","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0021/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0021","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2850,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0021/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2850,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0021","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0021"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0022","@type":"sc:Canvas","label":"Hollar_a_3000_0022","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0022/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0022","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2850,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0022/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2850,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0022","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0022"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0023","@type":"sc:Canvas","label":"Hollar_a_3000_0023","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0023/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0023","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2850,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0023/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2850,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0023","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0023"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0024","@type":"sc:Canvas","label":"Hollar_a_3000_0024","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0024/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0024","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2850,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0024/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2850,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0024","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0024"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0025","@type":"sc:Canvas","label":"Hollar_a_3000_0025","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0025/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0025","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2850,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0025/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2850,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0025","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0025"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0026","@type":"sc:Canvas","label":"Hollar_a_3000_0026","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0026/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0026","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2850,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0026/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2850,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0026","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0026"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0027","@type":"sc:Canvas","label":"Hollar_a_3000_0027","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0027/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0027","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2850,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0027/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2850,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0027","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0027"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0028","@type":"sc:Canvas","label":"Hollar_a_3000_0028","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0028/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0028","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2850,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0028/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2850,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0028","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0028"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0029","@type":"sc:Canvas","label":"Hollar_a_3000_0029","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0029/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0029","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2850,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0029/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2850,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0029","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0029"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0030","@type":"sc:Canvas","label":"Hollar_a_3000_0030","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0030/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0030","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2850,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0030/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2850,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0030","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0030"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0031","@type":"sc:Canvas","label":"Hollar_a_3000_0031","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0031/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0031","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2850,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0031/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2850,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0031","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0031"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0032","@type":"sc:Canvas","label":"Hollar_a_3000_0032","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0032/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0032","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2850,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0032/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2850,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0032","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0032"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0033","@type":"sc:Canvas","label":"Hollar_a_3000_0033","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0033/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0033","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2730,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0033/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2730,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0033","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0033"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0034","@type":"sc:Canvas","label":"Hollar_a_3000_0034","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0034/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0034","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2826,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0034/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2826,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0034","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0034"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0035","@type":"sc:Canvas","label":"Hollar_a_3000_0035","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0035/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0035","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2826,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0035/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2826,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0035","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0035"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0036","@type":"sc:Canvas","label":"Hollar_a_3000_0036","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0036/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0036","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2826,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0036/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2826,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0036","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0036"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0037","@type":"sc:Canvas","label":"Hollar_a_3000_0037","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0037/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0037","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2694,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0037/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2694,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0037","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0037"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0038","@type":"sc:Canvas","label":"Hollar_a_3000_0038","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0038/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0038","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0038/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0038","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0038"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0039","@type":"sc:Canvas","label":"Hollar_a_3000_0039","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0039/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0039","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0039/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0039","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0039"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0040","@type":"sc:Canvas","label":"Hollar_a_3000_0040","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0040/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0040","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0040/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0040","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0040"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0041","@type":"sc:Canvas","label":"Hollar_a_3000_0041","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0041/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0041","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0041/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0041","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0041"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0042","@type":"sc:Canvas","label":"Hollar_a_3000_0042","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0042/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0042","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0042/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0042","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0042"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0043","@type":"sc:Canvas","label":"Hollar_a_3000_0043","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0043/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0043","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0043/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0043","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0043"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0044","@type":"sc:Canvas","label":"Hollar_a_3000_0044","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0044/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0044","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0044/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0044","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0044"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0045","@type":"sc:Canvas","label":"Hollar_a_3000_0045","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0045/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0045","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0045/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0045","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0045"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0046","@type":"sc:Canvas","label":"Hollar_a_3000_0046","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0046/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0046","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0046/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0046","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0046"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0047","@type":"sc:Canvas","label":"Hollar_a_3000_0047","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0047/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0047","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0047/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0047","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0047"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0048","@type":"sc:Canvas","label":"Hollar_a_3000_0048","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0048/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0048","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0048/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0048","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0048"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0049","@type":"sc:Canvas","label":"Hollar_a_3000_0049","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0049/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0049","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0049/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0049","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0049"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0050","@type":"sc:Canvas","label":"Hollar_a_3000_0050","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0050/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0050","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0050/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0050","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0050"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0051","@type":"sc:Canvas","label":"Hollar_a_3000_0051","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0051/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0051","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0051/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0051","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0051"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0052","@type":"sc:Canvas","label":"Hollar_a_3000_0052","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0052/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0052","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0052/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0052","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0052"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0053","@type":"sc:Canvas","label":"Hollar_a_3000_0053","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0053/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0053","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0053/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0053","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0053"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0054","@type":"sc:Canvas","label":"Hollar_a_3000_0054","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0054/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0054","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0054/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0054","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0054"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0055","@type":"sc:Canvas","label":"Hollar_a_3000_0055","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0055/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0055","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0055/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0055","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0055"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0056","@type":"sc:Canvas","label":"Hollar_a_3000_0056","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0056/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0056","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0056/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0056","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0056"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0057","@type":"sc:Canvas","label":"Hollar_a_3000_0057","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0057/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0057","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0057/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0057","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0057"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0058","@type":"sc:Canvas","label":"Hollar_a_3000_0058","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0058/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0058","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0058/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0058","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0058"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0059","@type":"sc:Canvas","label":"Hollar_a_3000_0059","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0059/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0059","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0059/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0059","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0059"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0060","@type":"sc:Canvas","label":"Hollar_a_3000_0060","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0060/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0060","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0060/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0060","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0060"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0061","@type":"sc:Canvas","label":"Hollar_a_3000_0061","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0061/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0061","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0061/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0061","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0061"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0062","@type":"sc:Canvas","label":"Hollar_a_3000_0062","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0062/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0062","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0062/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0062","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0062"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0063","@type":"sc:Canvas","label":"Hollar_a_3000_0063","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0063/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0063","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0063/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0063","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0063"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0064","@type":"sc:Canvas","label":"Hollar_a_3000_0064","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0064/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0064","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0064/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0064","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0064"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0065","@type":"sc:Canvas","label":"Hollar_a_3000_0065","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0065/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0065","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0065/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0065","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0065"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0066","@type":"sc:Canvas","label":"Hollar_a_3000_0066","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0066/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0066","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0066/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0066","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0066"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0067","@type":"sc:Canvas","label":"Hollar_a_3000_0067","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0067/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0067","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0067/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0067","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0067"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0068","@type":"sc:Canvas","label":"Hollar_a_3000_0068","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0068/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0068","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0068/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0068","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0068"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0069","@type":"sc:Canvas","label":"Hollar_a_3000_0069","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0069/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0069","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0069/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0069","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0069"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0070","@type":"sc:Canvas","label":"Hollar_a_3000_0070","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0070/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0070","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0070/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0070","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0070"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0071","@type":"sc:Canvas","label":"Hollar_a_3000_0071","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0071/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0071","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0071/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0071","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0071"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0072","@type":"sc:Canvas","label":"Hollar_a_3000_0072","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0072/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0072","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0072/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0072","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0072"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0073","@type":"sc:Canvas","label":"Hollar_a_3000_0073","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0073/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0073","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0073/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0073","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0073"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0074","@type":"sc:Canvas","label":"Hollar_a_3000_0074","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0074/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0074","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0074/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0074","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0074"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0075","@type":"sc:Canvas","label":"Hollar_a_3000_0075","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0075/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0075","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0075/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0075","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0075"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0076","@type":"sc:Canvas","label":"Hollar_a_3000_0076","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0076/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0076","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0076/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0076","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0076"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0077","@type":"sc:Canvas","label":"Hollar_a_3000_0077","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0077/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0077","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0077/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0077","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0077"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0078","@type":"sc:Canvas","label":"Hollar_a_3000_0078","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0078/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0078","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0078/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0078","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0078"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0079","@type":"sc:Canvas","label":"Hollar_a_3000_0079","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0079/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0079","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0079/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0079","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0079"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0080","@type":"sc:Canvas","label":"Hollar_a_3000_0080","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0080/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0080","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0080/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0080","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0080"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0081","@type":"sc:Canvas","label":"Hollar_a_3000_0081","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0081/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0081","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0081/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0081","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0081"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0082","@type":"sc:Canvas","label":"Hollar_a_3000_0082","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0082/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0082","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0082/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0082","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0082"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0083","@type":"sc:Canvas","label":"Hollar_a_3000_0083","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0083/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0083","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0083/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0083","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0083"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0084","@type":"sc:Canvas","label":"Hollar_a_3000_0084","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0084/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0084","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0084/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0084","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0084"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0085","@type":"sc:Canvas","label":"Hollar_a_3000_0085","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0085/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0085","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0085/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0085","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0085"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0086","@type":"sc:Canvas","label":"Hollar_a_3000_0086","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0086/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0086","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0086/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0086","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0086"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0087","@type":"sc:Canvas","label":"Hollar_a_3000_0087","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0087/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0087","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0087/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0087","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0087"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0088","@type":"sc:Canvas","label":"Hollar_a_3000_0088","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0088/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0088","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0088/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0088","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0088"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0089","@type":"sc:Canvas","label":"Hollar_a_3000_0089","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0089/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0089","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0089/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0089","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0089"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0090","@type":"sc:Canvas","label":"Hollar_a_3000_0090","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0090/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0090","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0090/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0090","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0090"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0091","@type":"sc:Canvas","label":"Hollar_a_3000_0091","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0091/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0091","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0091/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0091","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0091"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0092","@type":"sc:Canvas","label":"Hollar_a_3000_0092","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0092/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0092","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0092/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0092","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0092"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0093","@type":"sc:Canvas","label":"Hollar_a_3000_0093","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0093/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0093","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0093/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0093","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0093"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0094","@type":"sc:Canvas","label":"Hollar_a_3000_0094","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0094/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0094","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0094/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0094","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0094"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0095","@type":"sc:Canvas","label":"Hollar_a_3000_0095","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0095/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0095","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0095/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0095","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0095"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0096","@type":"sc:Canvas","label":"Hollar_a_3000_0096","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0096/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0096","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0096/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0096","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0096"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0097","@type":"sc:Canvas","label":"Hollar_a_3000_0097","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0097/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0097","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0097/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0097","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0097"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0098","@type":"sc:Canvas","label":"Hollar_a_3000_0098","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0098/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0098","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0098/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0098","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0098"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0099","@type":"sc:Canvas","label":"Hollar_a_3000_0099","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0099/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0099","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0099/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0099","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0099"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0100","@type":"sc:Canvas","label":"Hollar_a_3000_0100","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0100/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0100","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0100/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0100","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0100"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0101","@type":"sc:Canvas","label":"Hollar_a_3000_0101","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0101/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0101","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0101/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0101","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0101"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0102","@type":"sc:Canvas","label":"Hollar_a_3000_0102","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0102/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0102","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0102/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0102","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0102"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0103","@type":"sc:Canvas","label":"Hollar_a_3000_0103","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0103/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0103","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0103/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0103","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0103"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0104","@type":"sc:Canvas","label":"Hollar_a_3000_0104","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0104/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0104","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0104/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0104","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0104"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0105","@type":"sc:Canvas","label":"Hollar_a_3000_0105","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0105/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0105","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0105/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0105","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0105"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0106","@type":"sc:Canvas","label":"Hollar_a_3000_0106","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0106/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0106","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0106/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0106","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0106"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0107","@type":"sc:Canvas","label":"Hollar_a_3000_0107","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0107/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0107","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0107/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0107","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0107"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0108","@type":"sc:Canvas","label":"Hollar_a_3000_0108","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0108/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0108","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0108/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0108","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0108"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0109","@type":"sc:Canvas","label":"Hollar_a_3000_0109","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0109/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0109","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0109/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0109","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0109"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0110","@type":"sc:Canvas","label":"Hollar_a_3000_0110","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0110/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0110","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0110/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0110","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0110"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0111","@type":"sc:Canvas","label":"Hollar_a_3000_0111","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0111/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0111","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0111/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0111","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0111"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0112","@type":"sc:Canvas","label":"Hollar_a_3000_0112","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0112/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0112","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0112/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0112","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0112"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0113","@type":"sc:Canvas","label":"Hollar_a_3000_0113","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0113/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0113","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0113/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0113","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0113"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0114","@type":"sc:Canvas","label":"Hollar_a_3000_0114","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0114/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0114","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0114/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0114","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0114"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0115","@type":"sc:Canvas","label":"Hollar_a_3000_0115","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0115/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0115","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0115/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0115","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0115"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0116","@type":"sc:Canvas","label":"Hollar_a_3000_0116","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0116/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0116","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0116/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0116","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0116"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0117","@type":"sc:Canvas","label":"Hollar_a_3000_0117","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0117/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0117","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0117/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0117","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0117"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0118","@type":"sc:Canvas","label":"Hollar_a_3000_0118","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0118/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0118","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0118/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0118","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0118"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0119","@type":"sc:Canvas","label":"Hollar_a_3000_0119","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0119/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0119","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0119/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0119","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0119"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0120","@type":"sc:Canvas","label":"Hollar_a_3000_0120","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0120/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0120","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0120/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0120","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0120"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0121","@type":"sc:Canvas","label":"Hollar_a_3000_0121","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0121/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0121","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0121/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0121","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0121"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0122","@type":"sc:Canvas","label":"Hollar_a_3000_0122","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0122/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0122","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0122/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0122","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0122"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0123","@type":"sc:Canvas","label":"Hollar_a_3000_0123","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0123/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0123","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0123/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0123","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0123"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0124","@type":"sc:Canvas","label":"Hollar_a_3000_0124","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0124/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0124","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0124/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0124","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0124"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0125","@type":"sc:Canvas","label":"Hollar_a_3000_0125","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0125/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0125","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0125/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0125","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0125"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0126","@type":"sc:Canvas","label":"Hollar_a_3000_0126","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0126/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0126","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0126/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0126","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0126"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0127","@type":"sc:Canvas","label":"Hollar_a_3000_0127","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0127/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0127","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0127/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0127","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0127"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0128","@type":"sc:Canvas","label":"Hollar_a_3000_0128","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0128/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0128","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0128/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0128","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0128"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0129","@type":"sc:Canvas","label":"Hollar_a_3000_0129","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0129/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0129","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0129/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0129","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0129"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0130","@type":"sc:Canvas","label":"Hollar_a_3000_0130","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0130/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0130","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0130/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0130","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0130"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0131","@type":"sc:Canvas","label":"Hollar_a_3000_0131","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0131/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0131","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0131/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0131","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0131"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0132","@type":"sc:Canvas","label":"Hollar_a_3000_0132","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0132/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0132","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0132/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0132","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0132"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0133","@type":"sc:Canvas","label":"Hollar_a_3000_0133","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0133/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0133","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0133/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0133","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0133"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0134","@type":"sc:Canvas","label":"Hollar_a_3000_0134","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0134/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0134","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0134/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0134","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0134"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0135","@type":"sc:Canvas","label":"Hollar_a_3000_0135","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0135/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0135","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0135/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0135","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0135"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0136","@type":"sc:Canvas","label":"Hollar_a_3000_0136","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0136/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0136","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0136/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0136","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0136"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0137","@type":"sc:Canvas","label":"Hollar_a_3000_0137","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0137/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0137","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0137/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0137","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0137"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0138","@type":"sc:Canvas","label":"Hollar_a_3000_0138","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0138/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0138","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0138/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0138","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0138"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0139","@type":"sc:Canvas","label":"Hollar_a_3000_0139","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0139/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0139","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0139/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0139","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0139"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0140","@type":"sc:Canvas","label":"Hollar_a_3000_0140","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0140/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0140","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0140/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0140","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0140"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0141","@type":"sc:Canvas","label":"Hollar_a_3000_0141","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0141/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0141","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0141/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0141","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0141"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0142","@type":"sc:Canvas","label":"Hollar_a_3000_0142","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0142/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0142","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0142/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0142","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0142"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0143","@type":"sc:Canvas","label":"Hollar_a_3000_0143","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0143/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0143","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0143/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0143","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0143"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0144","@type":"sc:Canvas","label":"Hollar_a_3000_0144","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0144/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0144","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0144/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0144","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0144"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0145","@type":"sc:Canvas","label":"Hollar_a_3000_0145","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0145/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0145","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0145/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0145","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0145"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0146","@type":"sc:Canvas","label":"Hollar_a_3000_0146","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0146/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0146","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0146/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0146","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0146"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0147","@type":"sc:Canvas","label":"Hollar_a_3000_0147","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0147/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0147","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0147/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0147","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0147"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0148","@type":"sc:Canvas","label":"Hollar_a_3000_0148","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0148/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0148","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0148/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0148","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0148"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0149","@type":"sc:Canvas","label":"Hollar_a_3000_0149","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0149/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0149","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0149/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0149","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0149"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0150","@type":"sc:Canvas","label":"Hollar_a_3000_0150","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0150/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0150","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0150/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0150","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0150"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0151","@type":"sc:Canvas","label":"Hollar_a_3000_0151","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0151/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0151","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0151/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0151","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0151"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0152","@type":"sc:Canvas","label":"Hollar_a_3000_0152","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0152/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0152","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0152/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0152","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0152"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0153","@type":"sc:Canvas","label":"Hollar_a_3000_0153","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0153/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0153","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0153/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0153","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0153"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0154","@type":"sc:Canvas","label":"Hollar_a_3000_0154","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0154/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0154","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0154/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0154","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0154"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0155","@type":"sc:Canvas","label":"Hollar_a_3000_0155","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0155/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0155","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0155/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0155","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0155"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0156","@type":"sc:Canvas","label":"Hollar_a_3000_0156","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0156/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0156","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0156/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0156","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0156"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0157","@type":"sc:Canvas","label":"Hollar_a_3000_0157","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0157/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0157","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0157/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0157","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0157"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0158","@type":"sc:Canvas","label":"Hollar_a_3000_0158","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0158/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0158","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0158/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0158","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0158"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0159","@type":"sc:Canvas","label":"Hollar_a_3000_0159","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0159/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0159","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0159/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0159","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0159"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0160","@type":"sc:Canvas","label":"Hollar_a_3000_0160","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0160/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0160","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0160/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0160","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0160"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0161","@type":"sc:Canvas","label":"Hollar_a_3000_0161","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0161/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0161","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0161/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0161","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0161"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0162","@type":"sc:Canvas","label":"Hollar_a_3000_0162","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0162/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0162","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0162/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0162","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0162"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0163","@type":"sc:Canvas","label":"Hollar_a_3000_0163","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0163/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0163","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0163/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0163","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0163"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0164","@type":"sc:Canvas","label":"Hollar_a_3000_0164","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0164/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0164","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0164/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0164","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0164"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0165","@type":"sc:Canvas","label":"Hollar_a_3000_0165","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0165/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0165","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0165/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0165","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0165"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0166","@type":"sc:Canvas","label":"Hollar_a_3000_0166","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0166/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0166","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0166/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0166","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0166"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0167","@type":"sc:Canvas","label":"Hollar_a_3000_0167","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0167/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0167","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0167/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0167","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0167"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0168","@type":"sc:Canvas","label":"Hollar_a_3000_0168","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0168/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0168","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0168/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0168","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0168"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0169","@type":"sc:Canvas","label":"Hollar_a_3000_0169","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0169/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0169","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0169/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0169","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0169"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0170","@type":"sc:Canvas","label":"Hollar_a_3000_0170","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0170/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0170","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0170/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0170","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0170"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0171","@type":"sc:Canvas","label":"Hollar_a_3000_0171","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0171/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0171","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0171/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0171","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0171"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0172","@type":"sc:Canvas","label":"Hollar_a_3000_0172","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0172/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0172","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0172/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0172","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0172"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0173","@type":"sc:Canvas","label":"Hollar_a_3000_0173","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0173/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0173","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0173/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0173","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0173"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0174","@type":"sc:Canvas","label":"Hollar_a_3000_0174","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0174/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0174","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0174/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0174","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0174"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0175","@type":"sc:Canvas","label":"Hollar_a_3000_0175","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0175/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0175","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0175/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0175","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0175"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0176","@type":"sc:Canvas","label":"Hollar_a_3000_0176","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0176/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0176","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0176/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0176","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0176"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0177","@type":"sc:Canvas","label":"Hollar_a_3000_0177","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0177/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0177","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0177/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0177","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0177"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0178","@type":"sc:Canvas","label":"Hollar_a_3000_0178","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0178/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0178","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0178/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0178","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0178"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0179","@type":"sc:Canvas","label":"Hollar_a_3000_0179","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0179/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0179","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0179/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0179","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0179"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0180","@type":"sc:Canvas","label":"Hollar_a_3000_0180","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0180/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0180","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0180/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0180","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0180"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0181","@type":"sc:Canvas","label":"Hollar_a_3000_0181","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0181/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0181","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0181/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0181","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0181"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0182","@type":"sc:Canvas","label":"Hollar_a_3000_0182","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0182/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0182","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0182/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0182","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0182"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0183","@type":"sc:Canvas","label":"Hollar_a_3000_0183","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0183/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0183","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0183/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0183","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0183"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0184","@type":"sc:Canvas","label":"Hollar_a_3000_0184","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0184/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0184","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0184/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0184","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0184"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0185","@type":"sc:Canvas","label":"Hollar_a_3000_0185","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0185/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0185","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0185/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0185","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0185"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0186","@type":"sc:Canvas","label":"Hollar_a_3000_0186","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0186/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0186","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0186/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0186","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0186"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0187","@type":"sc:Canvas","label":"Hollar_a_3000_0187","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0187/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0187","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0187/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0187","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0187"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0188","@type":"sc:Canvas","label":"Hollar_a_3000_0188","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0188/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0188","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0188/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0188","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0188"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0189","@type":"sc:Canvas","label":"Hollar_a_3000_0189","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0189/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0189","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0189/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0189","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0189"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0190","@type":"sc:Canvas","label":"Hollar_a_3000_0190","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0190/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0190","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0190/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0190","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0190"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0191","@type":"sc:Canvas","label":"Hollar_a_3000_0191","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0191/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0191","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0191/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0191","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0191"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0192","@type":"sc:Canvas","label":"Hollar_a_3000_0192","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0192/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0192","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0192/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0192","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0192"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0193","@type":"sc:Canvas","label":"Hollar_a_3000_0193","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0193/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0193","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0193/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0193","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0193"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0194","@type":"sc:Canvas","label":"Hollar_a_3000_0194","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0194/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0194","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0194/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0194","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0194"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0195","@type":"sc:Canvas","label":"Hollar_a_3000_0195","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0195/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0195","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0195/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0195","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0195"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0196","@type":"sc:Canvas","label":"Hollar_a_3000_0196","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0196/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0196","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0196/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0196","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0196"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0197","@type":"sc:Canvas","label":"Hollar_a_3000_0197","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0197/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0197","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0197/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0197","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0197"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0198","@type":"sc:Canvas","label":"Hollar_a_3000_0198","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0198/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0198","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0198/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0198","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0198"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0199","@type":"sc:Canvas","label":"Hollar_a_3000_0199","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0199/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0199","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0199/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0199","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0199"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0200","@type":"sc:Canvas","label":"Hollar_a_3000_0200","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0200/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0200","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0200/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0200","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0200"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0201","@type":"sc:Canvas","label":"Hollar_a_3000_0201","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0201/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0201","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0201/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0201","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0201"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0202","@type":"sc:Canvas","label":"Hollar_a_3000_0202","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0202/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0202","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0202/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0202","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0202"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0203","@type":"sc:Canvas","label":"Hollar_a_3000_0203","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0203/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0203","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0203/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0203","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0203"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0204","@type":"sc:Canvas","label":"Hollar_a_3000_0204","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0204/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0204","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0204/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0204","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0204"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0205","@type":"sc:Canvas","label":"Hollar_a_3000_0205","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0205/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0205","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0205/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0205","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0205"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0206","@type":"sc:Canvas","label":"Hollar_a_3000_0206","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0206/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0206","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0206/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0206","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0206"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0207","@type":"sc:Canvas","label":"Hollar_a_3000_0207","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0207/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0207","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0207/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0207","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0207"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0208","@type":"sc:Canvas","label":"Hollar_a_3000_0208","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0208/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0208","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0208/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0208","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0208"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0209","@type":"sc:Canvas","label":"Hollar_a_3000_0209","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0209/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0209","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0209/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0209","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0209"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0210","@type":"sc:Canvas","label":"Hollar_a_3000_0210","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0210/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0210","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0210/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0210","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0210"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0211","@type":"sc:Canvas","label":"Hollar_a_3000_0211","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0211/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0211","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0211/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0211","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0211"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0212","@type":"sc:Canvas","label":"Hollar_a_3000_0212","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0212/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0212","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0212/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0212","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0212"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0213","@type":"sc:Canvas","label":"Hollar_a_3000_0213","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0213/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0213","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0213/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0213","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0213"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0214","@type":"sc:Canvas","label":"Hollar_a_3000_0214","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0214/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0214","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0214/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0214","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0214"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0215","@type":"sc:Canvas","label":"Hollar_a_3000_0215","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0215/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0215","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0215/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0215","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0215"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0216","@type":"sc:Canvas","label":"Hollar_a_3000_0216","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0216/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0216","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0216/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0216","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0216"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0217","@type":"sc:Canvas","label":"Hollar_a_3000_0217","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0217/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0217","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0217/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0217","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0217"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0218","@type":"sc:Canvas","label":"Hollar_a_3000_0218","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0218/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0218","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0218/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0218","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0218"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0219","@type":"sc:Canvas","label":"Hollar_a_3000_0219","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0219/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0219","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0219/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0219","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0219"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0220","@type":"sc:Canvas","label":"Hollar_a_3000_0220","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0220/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0220","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0220/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0220","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0220"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0221","@type":"sc:Canvas","label":"Hollar_a_3000_0221","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0221/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0221","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0221/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0221","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0221"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0222","@type":"sc:Canvas","label":"Hollar_a_3000_0222","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0222/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0222","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0222/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0222","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0222"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0223","@type":"sc:Canvas","label":"Hollar_a_3000_0223","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0223/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0223","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0223/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0223","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0223"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0224","@type":"sc:Canvas","label":"Hollar_a_3000_0224","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0224/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0224","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0224/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0224","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0224"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0225","@type":"sc:Canvas","label":"Hollar_a_3000_0225","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0225/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0225","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0225/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0225","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0225"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0226","@type":"sc:Canvas","label":"Hollar_a_3000_0226","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0226/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0226","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0226/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0226","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0226"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0227","@type":"sc:Canvas","label":"Hollar_a_3000_0227","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0227/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0227","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0227/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0227","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0227"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0228","@type":"sc:Canvas","label":"Hollar_a_3000_0228","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0228/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0228","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0228/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0228","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0228"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0229","@type":"sc:Canvas","label":"Hollar_a_3000_0229","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0229/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0229","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0229/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0229","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0229"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0230","@type":"sc:Canvas","label":"Hollar_a_3000_0230","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0230/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0230","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0230/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0230","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0230"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0231","@type":"sc:Canvas","label":"Hollar_a_3000_0231","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0231/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0231","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0231/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0231","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0231"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0232","@type":"sc:Canvas","label":"Hollar_a_3000_0232","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0232/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0232","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0232/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0232","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0232"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0233","@type":"sc:Canvas","label":"Hollar_a_3000_0233","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0233/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0233","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4106,"width":2790,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0233/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4106,"width":2790,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0233","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0233"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0234","@type":"sc:Canvas","label":"Hollar_a_3000_0234","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0234/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0234","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0234/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0234","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0234"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0235","@type":"sc:Canvas","label":"Hollar_a_3000_0235","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0235/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0235","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0235/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0235","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0235"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0236","@type":"sc:Canvas","label":"Hollar_a_3000_0236","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0236/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0236","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0236/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0236","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0236"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0237","@type":"sc:Canvas","label":"Hollar_a_3000_0237","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0237/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0237","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0237/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0237","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0237"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0238","@type":"sc:Canvas","label":"Hollar_a_3000_0238","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0238/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0238","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0238/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0238","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0238"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0239","@type":"sc:Canvas","label":"Hollar_a_3000_0239","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0239/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0239","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0239/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0239","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0239"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0240","@type":"sc:Canvas","label":"Hollar_a_3000_0240","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0240/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0240","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0240/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0240","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0240"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0241","@type":"sc:Canvas","label":"Hollar_a_3000_0241","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0241/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0241","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0241/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0241","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0241"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0242","@type":"sc:Canvas","label":"Hollar_a_3000_0242","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0242/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0242","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0242/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0242","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0242"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0243","@type":"sc:Canvas","label":"Hollar_a_3000_0243","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0243/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0243","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0243/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0243","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0243"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0244","@type":"sc:Canvas","label":"Hollar_a_3000_0244","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0244/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0244","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0244/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0244","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0244"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0245","@type":"sc:Canvas","label":"Hollar_a_3000_0245","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0245/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0245","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0245/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0245","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0245"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0246","@type":"sc:Canvas","label":"Hollar_a_3000_0246","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0246/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0246","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0246/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0246","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0246"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0247","@type":"sc:Canvas","label":"Hollar_a_3000_0247","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0247/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0247","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0247/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0247","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0247"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0248","@type":"sc:Canvas","label":"Hollar_a_3000_0248","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0248/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0248","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0248/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0248","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0248"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0249","@type":"sc:Canvas","label":"Hollar_a_3000_0249","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0249/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0249","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0249/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0249","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0249"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0250","@type":"sc:Canvas","label":"Hollar_a_3000_0250","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0250/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0250","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0250/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0250","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0250"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0251","@type":"sc:Canvas","label":"Hollar_a_3000_0251","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0251/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0251","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0251/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0251","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0251"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0252","@type":"sc:Canvas","label":"Hollar_a_3000_0252","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0252/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0252","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0252/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0252","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0252"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0253","@type":"sc:Canvas","label":"Hollar_a_3000_0253","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0253/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0253","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0253/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0253","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0253"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0254","@type":"sc:Canvas","label":"Hollar_a_3000_0254","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0254/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0254","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0254/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0254","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0254"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0255","@type":"sc:Canvas","label":"Hollar_a_3000_0255","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0255/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0255","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0255/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0255","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0255"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0256","@type":"sc:Canvas","label":"Hollar_a_3000_0256","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0256/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0256","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0256/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0256","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0256"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0257","@type":"sc:Canvas","label":"Hollar_a_3000_0257","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0257/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0257","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0257/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0257","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0257"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0258","@type":"sc:Canvas","label":"Hollar_a_3000_0258","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0258/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0258","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0258/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0258","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0258"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0259","@type":"sc:Canvas","label":"Hollar_a_3000_0259","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0259/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0259","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0259/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0259","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0259"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0260","@type":"sc:Canvas","label":"Hollar_a_3000_0260","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0260/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0260","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0260/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0260","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0260"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0261","@type":"sc:Canvas","label":"Hollar_a_3000_0261","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0261/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0261","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0261/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0261","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0261"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0262","@type":"sc:Canvas","label":"Hollar_a_3000_0262","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0262/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0262","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0262/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0262","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0262"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0263","@type":"sc:Canvas","label":"Hollar_a_3000_0263","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0263/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0263","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0263/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0263","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0263"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0264","@type":"sc:Canvas","label":"Hollar_a_3000_0264","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0264/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0264","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0264/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0264","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0264"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0265","@type":"sc:Canvas","label":"Hollar_a_3000_0265","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0265/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0265","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2788,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0265/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2788,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0265","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0265"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0266","@type":"sc:Canvas","label":"Hollar_a_3000_0266","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0266/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0266","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2944,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0266/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2944,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0266","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0266"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0267","@type":"sc:Canvas","label":"Hollar_a_3000_0267","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0267/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0267","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2944,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0267/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2944,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0267","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0267"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0268","@type":"sc:Canvas","label":"Hollar_a_3000_0268","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0268/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0268","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2848,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0268/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2848,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0268","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0268"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0269","@type":"sc:Canvas","label":"Hollar_a_3000_0269","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0269/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0269","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2848,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0269/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2848,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0269","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0269"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0270","@type":"sc:Canvas","label":"Hollar_a_3000_0270","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0270/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0270","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2848,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0270/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2848,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0270","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0270"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0271","@type":"sc:Canvas","label":"Hollar_a_3000_0271","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0271/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0271","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2728,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0271/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2728,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0271","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0271"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0272","@type":"sc:Canvas","label":"Hollar_a_3000_0272","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0272/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0272","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0272/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0272","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0272"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0273","@type":"sc:Canvas","label":"Hollar_a_3000_0273","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0273/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0273","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0273/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0273","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0273"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0274","@type":"sc:Canvas","label":"Hollar_a_3000_0274","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0274/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0274","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0274/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0274","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0274"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0275","@type":"sc:Canvas","label":"Hollar_a_3000_0275","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0275/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0275","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0275/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0275","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0275"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0276","@type":"sc:Canvas","label":"Hollar_a_3000_0276","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0276/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0276","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0276/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0276","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0276"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0277","@type":"sc:Canvas","label":"Hollar_a_3000_0277","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0277/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0277","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0277/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0277","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0277"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0278","@type":"sc:Canvas","label":"Hollar_a_3000_0278","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0278/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0278","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0278/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0278","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0278"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0279","@type":"sc:Canvas","label":"Hollar_a_3000_0279","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0279/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0279","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0279/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0279","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0279"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0280","@type":"sc:Canvas","label":"Hollar_a_3000_0280","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0280/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0280","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0280/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0280","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0280"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0281","@type":"sc:Canvas","label":"Hollar_a_3000_0281","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0281/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0281","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0281/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0281","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0281"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0282","@type":"sc:Canvas","label":"Hollar_a_3000_0282","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0282/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0282","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0282/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0282","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0282"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0283","@type":"sc:Canvas","label":"Hollar_a_3000_0283","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0283/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0283","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0283/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0283","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0283"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0284","@type":"sc:Canvas","label":"Hollar_a_3000_0284","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0284/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0284","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0284/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0284","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0284"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0285","@type":"sc:Canvas","label":"Hollar_a_3000_0285","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0285/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0285","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0285/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0285","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0285"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0286","@type":"sc:Canvas","label":"Hollar_a_3000_0286","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0286/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0286","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0286/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0286","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0286"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0287","@type":"sc:Canvas","label":"Hollar_a_3000_0287","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0287/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0287","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0287/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0287","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0287"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0288","@type":"sc:Canvas","label":"Hollar_a_3000_0288","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0288/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0288","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0288/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0288","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0288"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0289","@type":"sc:Canvas","label":"Hollar_a_3000_0289","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0289/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0289","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0289/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0289","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0289"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0290","@type":"sc:Canvas","label":"Hollar_a_3000_0290","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0290/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0290","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0290/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0290","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0290"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0291","@type":"sc:Canvas","label":"Hollar_a_3000_0291","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0291/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0291","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0291/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0291","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0291"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0292","@type":"sc:Canvas","label":"Hollar_a_3000_0292","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0292/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0292","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0292/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0292","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0292"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0293","@type":"sc:Canvas","label":"Hollar_a_3000_0293","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0293/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0293","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0293/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0293","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0293"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0294","@type":"sc:Canvas","label":"Hollar_a_3000_0294","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0294/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0294","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0294/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0294","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0294"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0295","@type":"sc:Canvas","label":"Hollar_a_3000_0295","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0295/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0295","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4020,"width":2896,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0295/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4020,"width":2896,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0295","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0295"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0296","@type":"sc:Canvas","label":"Hollar_a_3000_0296","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0296/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0296","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0296/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0296","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0296"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0297","@type":"sc:Canvas","label":"Hollar_a_3000_0297","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0297/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0297","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0297/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0297","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0297"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0298","@type":"sc:Canvas","label":"Hollar_a_3000_0298","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0298/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0298","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0298/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0298","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0298"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0299","@type":"sc:Canvas","label":"Hollar_a_3000_0299","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0299/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0299","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0299/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0299","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0299"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0300","@type":"sc:Canvas","label":"Hollar_a_3000_0300","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0300/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0300","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0300/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0300","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0300"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0301","@type":"sc:Canvas","label":"Hollar_a_3000_0301","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0301/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0301","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0301/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0301","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0301"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0302","@type":"sc:Canvas","label":"Hollar_a_3000_0302","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0302/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0302","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0302/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0302","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0302"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0303","@type":"sc:Canvas","label":"Hollar_a_3000_0303","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0303/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0303","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0303/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0303","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0303"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0304","@type":"sc:Canvas","label":"Hollar_a_3000_0304","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0304/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0304","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0304/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0304","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0304"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0305","@type":"sc:Canvas","label":"Hollar_a_3000_0305","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0305/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0305","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0305/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0305","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0305"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0306","@type":"sc:Canvas","label":"Hollar_a_3000_0306","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0306/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0306","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0306/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0306","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0306"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0307","@type":"sc:Canvas","label":"Hollar_a_3000_0307","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0307/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0307","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0307/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0307","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0307"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0308","@type":"sc:Canvas","label":"Hollar_a_3000_0308","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0308/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0308","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0308/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0308","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0308"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0309","@type":"sc:Canvas","label":"Hollar_a_3000_0309","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0309/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0309","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0309/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0309","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0309"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0310","@type":"sc:Canvas","label":"Hollar_a_3000_0310","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0310/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0310","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0310/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0310","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0310"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0311","@type":"sc:Canvas","label":"Hollar_a_3000_0311","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0311/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0311","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0311/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0311","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0311"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0312","@type":"sc:Canvas","label":"Hollar_a_3000_0312","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0312/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0312","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0312/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0312","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0312"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0313","@type":"sc:Canvas","label":"Hollar_a_3000_0313","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0313/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0313","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0313/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0313","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0313"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0314","@type":"sc:Canvas","label":"Hollar_a_3000_0314","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0314/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0314","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0314/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0314","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0314"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0315","@type":"sc:Canvas","label":"Hollar_a_3000_0315","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0315/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0315","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0315/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0315","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0315"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0316","@type":"sc:Canvas","label":"Hollar_a_3000_0316","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0316/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0316","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0316/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0316","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0316"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0317","@type":"sc:Canvas","label":"Hollar_a_3000_0317","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0317/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0317","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0317/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0317","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0317"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0318","@type":"sc:Canvas","label":"Hollar_a_3000_0318","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0318/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0318","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0318/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0318","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0318"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0319","@type":"sc:Canvas","label":"Hollar_a_3000_0319","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0319/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0319","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0319/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0319","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0319"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0320","@type":"sc:Canvas","label":"Hollar_a_3000_0320","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0320/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0320","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0320/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0320","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0320"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0321","@type":"sc:Canvas","label":"Hollar_a_3000_0321","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0321/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0321","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0321/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0321","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0321"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0322","@type":"sc:Canvas","label":"Hollar_a_3000_0322","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0322/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0322","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0322/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0322","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0322"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0323","@type":"sc:Canvas","label":"Hollar_a_3000_0323","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0323/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0323","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0323/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0323","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0323"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0324","@type":"sc:Canvas","label":"Hollar_a_3000_0324","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0324/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0324","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0324/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0324","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0324"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0325","@type":"sc:Canvas","label":"Hollar_a_3000_0325","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0325/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0325","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0325/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0325","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0325"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0326","@type":"sc:Canvas","label":"Hollar_a_3000_0326","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0326/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0326","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0326/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0326","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0326"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0327","@type":"sc:Canvas","label":"Hollar_a_3000_0327","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0327/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0327","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0327/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0327","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0327"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0328","@type":"sc:Canvas","label":"Hollar_a_3000_0328","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0328/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0328","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0328/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0328","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0328"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0329","@type":"sc:Canvas","label":"Hollar_a_3000_0329","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0329/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0329","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0329/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0329","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0329"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0330","@type":"sc:Canvas","label":"Hollar_a_3000_0330","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0330/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0330","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0330/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0330","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0330"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0331","@type":"sc:Canvas","label":"Hollar_a_3000_0331","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0331/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0331","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0331/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0331","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0331"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0332","@type":"sc:Canvas","label":"Hollar_a_3000_0332","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0332/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0332","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0332/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0332","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0332"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0333","@type":"sc:Canvas","label":"Hollar_a_3000_0333","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0333/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0333","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4104,"width":2786,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0333/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4104,"width":2786,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0333","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0333"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0334","@type":"sc:Canvas","label":"Hollar_a_3000_0334","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0334/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0334","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3981,"width":2891,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0334/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3981,"width":2891,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0334","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0334"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0335","@type":"sc:Canvas","label":"Hollar_a_3000_0335","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0335/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0335","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3993,"width":2682,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0335/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3993,"width":2682,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0335","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0335"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0336","@type":"sc:Canvas","label":"Hollar_a_3000_0336","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0336/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0336","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3987,"width":2915,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0336/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3987,"width":2915,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0336","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0336"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0337","@type":"sc:Canvas","label":"Hollar_a_3000_0337","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0337/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0337","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3987,"width":2622,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0337/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3987,"width":2622,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0337","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0337"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0338","@type":"sc:Canvas","label":"Hollar_a_3000_0338","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0338/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0338","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3993,"width":2903,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0338/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3993,"width":2903,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0338","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0338"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0339","@type":"sc:Canvas","label":"Hollar_a_3000_0339","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0339/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0339","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3998,"width":2682,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0339/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3998,"width":2682,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0339","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0339"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0340","@type":"sc:Canvas","label":"Hollar_a_3000_0340","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0340/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0340","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3981,"width":2813,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0340/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3981,"width":2813,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0340","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0340"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0341","@type":"sc:Canvas","label":"Hollar_a_3000_0341","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0341/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0341","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3987,"width":2723,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0341/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3987,"width":2723,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0341","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0341"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0342","@type":"sc:Canvas","label":"Hollar_a_3000_0342","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0342/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0342","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3987,"width":2795,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0342/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3987,"width":2795,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0342","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0342"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0343","@type":"sc:Canvas","label":"Hollar_a_3000_0343","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0343/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0343","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3993,"width":2705,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0343/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3993,"width":2705,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0343","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0343"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0344","@type":"sc:Canvas","label":"Hollar_a_3000_0344","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0344/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0344","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3998,"width":2861,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0344/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3998,"width":2861,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0344","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0344"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0345","@type":"sc:Canvas","label":"Hollar_a_3000_0345","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0345/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0345","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3998,"width":2670,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0345/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3998,"width":2670,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0345","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0345"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0346","@type":"sc:Canvas","label":"Hollar_a_3000_0346","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0346/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0346","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3993,"width":2867,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0346/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3993,"width":2867,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0346","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0346"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0347","@type":"sc:Canvas","label":"Hollar_a_3000_0347","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0347/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0347","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3993,"width":2699,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0347/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3993,"width":2699,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0347","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0347"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0348","@type":"sc:Canvas","label":"Hollar_a_3000_0348","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0348/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0348","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3998,"width":2849,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0348/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3998,"width":2849,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0348","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0348"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0349","@type":"sc:Canvas","label":"Hollar_a_3000_0349","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0349/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0349","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3998,"width":2699,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0349/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3998,"width":2699,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0349","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0349"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0350","@type":"sc:Canvas","label":"Hollar_a_3000_0350","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0350/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0350","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3993,"width":2873,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0350/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3993,"width":2873,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0350","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0350"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0351","@type":"sc:Canvas","label":"Hollar_a_3000_0351","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0351/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0351","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3987,"width":2664,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0351/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3987,"width":2664,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0351","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0351"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0352","@type":"sc:Canvas","label":"Hollar_a_3000_0352","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0352/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0352","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3993,"width":2843,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0352/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3993,"width":2843,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0352","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0352"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0353","@type":"sc:Canvas","label":"Hollar_a_3000_0353","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0353/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0353","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3987,"width":2652,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0353/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3987,"width":2652,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0353","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0353"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0354","@type":"sc:Canvas","label":"Hollar_a_3000_0354","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0354/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0354","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3981,"width":2885,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0354/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3981,"width":2885,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0354","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0354"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0355","@type":"sc:Canvas","label":"Hollar_a_3000_0355","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0355/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0355","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3998,"width":2610,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0355/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3998,"width":2610,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0355","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0355"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0356","@type":"sc:Canvas","label":"Hollar_a_3000_0356","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0356/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0356","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3993,"width":2723,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0356/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3993,"width":2723,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0356","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0356"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0357","@type":"sc:Canvas","label":"Hollar_a_3000_0357","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0357/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0357","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3993,"width":2765,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0357/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3993,"width":2765,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0357","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0357"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0358","@type":"sc:Canvas","label":"Hollar_a_3000_0358","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0358/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0358","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3987,"width":2759,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0358/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3987,"width":2759,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0358","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0358"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0359","@type":"sc:Canvas","label":"Hollar_a_3000_0359","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0359/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0359","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3993,"width":2801,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0359/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3993,"width":2801,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0359","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0359"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0360","@type":"sc:Canvas","label":"Hollar_a_3000_0360","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0360/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0360","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3993,"width":2783,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0360/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3993,"width":2783,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0360","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0360"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0361","@type":"sc:Canvas","label":"Hollar_a_3000_0361","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0361/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0361","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3987,"width":2789,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0361/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3987,"width":2789,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0361","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0361"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0362","@type":"sc:Canvas","label":"Hollar_a_3000_0362","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0362/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0362","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3998,"width":2807,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0362/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3998,"width":2807,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0362","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0362"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0363","@type":"sc:Canvas","label":"Hollar_a_3000_0363","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0363/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0363","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3969,"width":2724,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0363/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3969,"width":2724,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0363","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0363"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0364","@type":"sc:Canvas","label":"Hollar_a_3000_0364","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0364/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0364","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3993,"width":2813,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0364/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3993,"width":2813,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0364","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0364"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0365","@type":"sc:Canvas","label":"Hollar_a_3000_0365","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0365/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0365","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3993,"width":2705,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0365/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3993,"width":2705,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0365","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0365"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0366","@type":"sc:Canvas","label":"Hollar_a_3000_0366","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0366/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0366","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3995,"width":2843,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0366/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3995,"width":2843,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0366","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0366"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0367","@type":"sc:Canvas","label":"Hollar_a_3000_0367","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0367/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0367","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3950,"width":2615,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0367/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3950,"width":2615,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0367","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0367"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0368","@type":"sc:Canvas","label":"Hollar_a_3000_0368","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0368/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0368","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3995,"width":2813,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0368/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3995,"width":2813,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0368","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0368"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0369","@type":"sc:Canvas","label":"Hollar_a_3000_0369","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0369/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0369","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4001,"width":2717,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0369/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4001,"width":2717,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0369","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0369"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0370","@type":"sc:Canvas","label":"Hollar_a_3000_0370","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0370/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0370","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4007,"width":2729,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0370/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4007,"width":2729,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0370","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0370"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0371","@type":"sc:Canvas","label":"Hollar_a_3000_0371","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0371/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0371","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4012,"width":2729,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0371/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4012,"width":2729,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0371","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0371"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0372","@type":"sc:Canvas","label":"Hollar_a_3000_0372","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0372/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0372","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4001,"width":2801,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0372/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4001,"width":2801,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0372","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0372"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0373","@type":"sc:Canvas","label":"Hollar_a_3000_0373","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0373/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0373","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4001,"width":2741,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0373/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4001,"width":2741,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0373","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0373"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0374","@type":"sc:Canvas","label":"Hollar_a_3000_0374","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0374/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0374","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4007,"width":2777,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0374/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4007,"width":2777,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0374","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0374"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0375","@type":"sc:Canvas","label":"Hollar_a_3000_0375","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0375/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0375","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4007,"width":2699,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0375/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4007,"width":2699,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0375","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0375"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0376","@type":"sc:Canvas","label":"Hollar_a_3000_0376","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0376/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0376","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3995,"width":2855,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0376/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3995,"width":2855,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0376","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0376"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0377","@type":"sc:Canvas","label":"Hollar_a_3000_0377","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0377/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0377","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4001,"width":2664,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0377/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4001,"width":2664,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0377","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0377"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0378","@type":"sc:Canvas","label":"Hollar_a_3000_0378","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0378/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0378","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4007,"width":2789,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0378/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4007,"width":2789,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0378","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0378"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0379","@type":"sc:Canvas","label":"Hollar_a_3000_0379","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0379/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0379","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4007,"width":2711,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0379/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4007,"width":2711,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0379","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0379"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0380","@type":"sc:Canvas","label":"Hollar_a_3000_0380","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0380/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0380","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3906,"width":2767,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0380/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3906,"width":2767,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0380","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0380"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0381","@type":"sc:Canvas","label":"Hollar_a_3000_0381","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0381/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0381","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3940,"width":2586,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0381/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3940,"width":2586,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0381","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0381"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0382","@type":"sc:Canvas","label":"Hollar_a_3000_0382","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0382/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0382","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3942,"width":2802,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0382/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3942,"width":2802,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0382","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0382"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0383","@type":"sc:Canvas","label":"Hollar_a_3000_0383","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0383/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0383","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3995,"width":2723,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0383/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3995,"width":2723,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0383","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0383"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0384","@type":"sc:Canvas","label":"Hollar_a_3000_0384","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0384/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0384","profile":"http://iiif.io/api/image/2/level2.json"}},"height":3935,"width":2784,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0384/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":3935,"width":2784,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0384","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0384"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0385","@type":"sc:Canvas","label":"Hollar_a_3000_0385","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0385/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0385","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4012,"width":2741,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0385/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4012,"width":2741,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0385","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0385"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0386","@type":"sc:Canvas","label":"Hollar_a_3000_0386","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0386/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0386","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4012,"width":2777,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0386/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4012,"width":2777,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0386","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0386"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0387","@type":"sc:Canvas","label":"Hollar_a_3000_0387","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0387/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0387","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4012,"width":2747,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0387/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4012,"width":2747,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0387","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0387"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0388","@type":"sc:Canvas","label":"Hollar_a_3000_0388","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0388/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0388","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4001,"width":2783,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0388/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4001,"width":2783,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0388","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0388"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0389","@type":"sc:Canvas","label":"Hollar_a_3000_0389","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0389/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0389","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4007,"width":2759,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0389/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4007,"width":2759,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0389","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0389"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0390","@type":"sc:Canvas","label":"Hollar_a_3000_0390","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0390/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0390","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4001,"width":2909,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0390/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4001,"width":2909,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0390","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0390"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0391","@type":"sc:Canvas","label":"Hollar_a_3000_0391","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0391/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0391","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4001,"width":2771,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0391/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4001,"width":2771,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0391","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0391"}]},{"@id":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0392","@type":"sc:Canvas","label":"Hollar_a_3000_0392","thumbnail":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0392/full/80,/0/default.jpg","service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0392","profile":"http://iiif.io/api/image/2/level2.json"}},"height":4096,"width":2852,"images":[{"@type":"oa:Annotation","motivation":"sc:Painting","resource":{"@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0392/full/full/0/default.jpg","@type":"dctypes:Image","format":"image/jpg","height":4096,"width":2852,"service":{"@context":"http://iiif.io/api/image/2/context.json","@id":"https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0392","profile":"http://iiif.io/api/image/2/level2.json"}},"on":"https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/canvas/hollar:Hollar_a_3000_0392"}]}]}]}
\ No newline at end of file
diff --git a/viscoll-api/spec/fixtures/villanova_boston.json b/viscoll-api/spec/fixtures/villanova_boston.json
new file mode 100644
index 00000000..16ba1032
--- /dev/null
+++ b/viscoll-api/spec/fixtures/villanova_boston.json
@@ -0,0 +1,93 @@
+{
+ "@context": "http://iiif.io/api/presentation/2/context.json",
+ "@type": "sc:Manifest",
+ "@id": "https://digital.library.villanova.edu/Item/vudl:99213/Manifest",
+ "label": "Boston, and Bunker Hill. Provided by Villanova University.",
+ "metadata": [
+ {
+ "label": "Full Title",
+ "value": "Boston, and Bunker Hill. "
+ },
+ {
+ "label": "Author",
+ "value": "Bartlett, W.H. "
+ },
+ {
+ "label": "Contributor",
+ "value": "Cousen, C. "
+ },
+ {
+ "label": "Date Added",
+ "value": "12 January 2014 "
+ },
+ {
+ "label": "Language",
+ "value": "English "
+ },
+ {
+ "label": "Topic",
+ "value": "Prints > 19th century. cultural landscapes. Engravings. Landscape prints. Cityscape prints Views > Massachusetts > Boston. History > United States > Massachusetts. Rivers. Waterfronts. Bridges. Sailing ships. Livestock. "
+ },
+ {
+ "label": "About",
+ "value": "More Details Permanent Link "
+ }
+ ],
+ "description": "Image from Patrick Coad Family Papers. These materials are owned by the American Catholic Historical Society and maintained at the Philadelphia Archdiocesan Historical Research Center, 100 E. Wynnewood Rd. Wynnewood, PA 19096. For more information please see: http://www.pahrc.net.
",
+ "license": "http://creativecommons.org/licenses/by-nc-nd/3.0/",
+ "attribution": "Digital Library@Villanova University",
+ "related": {
+ "@id": "https://digital.library.villanova.edu/Item/vudl:99213",
+ "format": "text/html"
+ },
+ "within": "https://digital.library.villanova.edu/Collection/vudl:98245/IIIF",
+ "sequences": [
+ {
+ "@type": "sc:Sequence",
+ "label": "Pages",
+ "rendering": [],
+ "viewingDirection": "left-to-right",
+ "viewingHint": "paged",
+ "canvases": [
+ {
+ "@type": "sc:Canvas",
+ "@id": "https://digital.library.villanova.edu/Item/vudl:99213/Canvas/p0",
+ "label": null,
+ "rendering": [
+ {
+ "@id": "https://digital.library.villanova.edu/files/vudl:99215/MASTER",
+ "format": "image/tiff",
+ "label": "Original source file"
+ },
+ {
+ "@id": "https://digital.library.villanova.edu/files/vudl:99215/MASTER-MD",
+ "format": "application/xml",
+ "label": "Technical Metadata"
+ }
+ ],
+ "height": 3288,
+ "width": 4260,
+ "images": [
+ {
+ "@type": "oa:Annotation",
+ "motivation": "sc:painting",
+ "resource": {
+ "@id": "https://digital.library.villanova.edu/files/vudl:99215/LARGE",
+ "@type": "dctypes:Image",
+ "format": "image/jpeg",
+ "service": {
+ "@context": "http://iiif.io/api/image/2/context.json",
+ "@id": "https://iiif.library.villanova.edu/image/vudl%3A99215",
+ "profile": "http://iiif.io/api/image/2/level1.json"
+ },
+ "height": 3288,
+ "width": 4260
+ },
+ "on": "https://digital.library.villanova.edu/Item/vudl:99213/Canvas/p0"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/viscoll-api/spec/fixtures/viscoll.png b/viscoll-api/spec/fixtures/viscoll.png
new file mode 100644
index 00000000..b31c0248
Binary files /dev/null and b/viscoll-api/spec/fixtures/viscoll.png differ
diff --git a/viscoll-api/spec/helpers/controller_helper/export_helper_spec.rb b/viscoll-api/spec/helpers/controller_helper/export_helper_spec.rb
new file mode 100644
index 00000000..adfac092
--- /dev/null
+++ b/viscoll-api/spec/helpers/controller_helper/export_helper_spec.rb
@@ -0,0 +1,125 @@
+require 'rails_helper'
+
+RSpec.describe ControllerHelper::ExportHelper, type: :helper do
+ before do
+ stub_request(:get, 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest').with(headers: { 'Accept' => '*/*', 'User-Agent' => 'Ruby' }).to_return(status: 200, body: File.read(File.dirname(__FILE__) + '/../../fixtures/villanova_boston.json'), headers: {})
+ @project = FactoryGirl.create(:project,
+ 'title' => 'Sample project',
+ 'shelfmark' => 'Ravenna 384.2339',
+ 'metadata' => { date: '18th century' },
+ 'preferences' => { 'showTips' => true },
+ 'noteTypes' => ['Ink', 'Unknown'],
+ 'manifests' => { '12341234': { 'id' => '12341234', 'url' => 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest', 'name' => 'Boston, and Bunker Hill.' } }
+ )
+ # Attach group with 2 leafs - (group with 2 leafs) - 2 conjoined leafs
+ @testgroup = FactoryGirl.create(:group, project: @project, nestLevel: 1, title: 'Group 1', direction: "left-to-right")
+ @upleafs = 2.times.collect { FactoryGirl.create(:leaf, project: @project, parentID: @testgroup.id.to_s, nestLevel: 1) }
+ @testmidgroup = FactoryGirl.create(:group, project: @project, parentID: @testgroup.id.to_s, nestLevel: 2, title: 'Group 2', direction: "left-to-right")
+ @midleafs = 2.times.collect { FactoryGirl.create(:leaf, project: @project, parentID: @testmidgroup.id.to_s, nestLevel: 2) }
+ @botleafs = 2.times.collect { FactoryGirl.create(:leaf, project: @project, parentID: @testgroup.id.to_s, nestLevel: 1) }
+ @botleafs[1].update(type: 'Endleaf')
+ @project.add_groupIDs([@testgroup.id.to_s, @testmidgroup.id.to_s], 0)
+ @testgroup.add_members([@upleafs[0].id.to_s, @upleafs[1].id.to_s, @testmidgroup.id.to_s, @botleafs[0].id.to_s, @botleafs[1].id.to_s], 0)
+ @testmidgroup.add_members([@midleafs[0].id.to_s, @midleafs[1].id.to_s], 0)
+ @testnote = FactoryGirl.create(:note, project: @project, title: 'Test Note', type: 'Ink', description: 'This is a test', show: true, objects: {Group: [@testgroup.id.to_s], Leaf: [@botleafs[0].id.to_s], Recto: [@botleafs[0].rectoID], Verso: [@botleafs[0].versoID]})
+ end
+
+ it 'builds the right JSON' do
+ result = buildJSON(@project)
+ expect(result[:project]).to eq({
+ title: 'Sample project',
+ shelfmark: 'Ravenna 384.2339',
+ metadata: { 'date' => '18th century' },
+ preferences: { 'showTips' => true },
+ manifests: { '12341234' => { 'id' => '12341234', 'url' => 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest', 'name' => 'Boston, and Bunker Hill.' } },
+ noteTypes: ['Ink', 'Unknown']
+ })
+ expect(result[:groups]).to eq({
+ 1 => {:params=>{:type=>"Quire", :title=>"Group 1", :direction=>"left-to-right", :nestLevel=>1}, :tacketed=>[], :sewing=>[], :parentOrder=>nil, :memberOrders=>["Leaf_1", "Leaf_2", "Group_2", "Leaf_5", "Leaf_6"]},
+ 2 => {:params=>{:type=>"Quire", :title=>"Group 2", :direction=>"left-to-right", :nestLevel=>2}, :tacketed=>[], :sewing=>[], :parentOrder=>1, :memberOrders=>["Leaf_3", "Leaf_4"]}
+ })
+ expect(result[:leafs]).to eq({
+ 1 => {:params=>{:material=>"Paper", :type=>"Original", :attached_above=>"None", :attached_below=>"None", :stub=>"None", :nestLevel=>1}, :conjoined_leaf_order=>nil, :parentOrder=>1, :rectoOrder=>1, :versoOrder=>1},
+ 2 => {:params=>{:material=>"Paper", :type=>"Original", :attached_above=>"None", :attached_below=>"None", :stub=>"None", :nestLevel=>1}, :conjoined_leaf_order=>nil, :parentOrder=>1, :rectoOrder=>2, :versoOrder=>2},
+ 3 => {:params=>{:material=>"Paper", :type=>"Original", :attached_above=>"None", :attached_below=>"None", :stub=>"None", :nestLevel=>2}, :conjoined_leaf_order=>nil, :parentOrder=>2, :rectoOrder=>3, :versoOrder=>3},
+ 4 => {:params=>{:material=>"Paper", :type=>"Original", :attached_above=>"None", :attached_below=>"None", :stub=>"None", :nestLevel=>2}, :conjoined_leaf_order=>nil, :parentOrder=>2, :rectoOrder=>4, :versoOrder=>4},
+ 5 => {:params=>{:material=>"Paper", :type=>"Original", :attached_above=>"None", :attached_below=>"None", :stub=>"None", :nestLevel=>1}, :conjoined_leaf_order=>nil, :parentOrder=>1, :rectoOrder=>5, :versoOrder=>5},
+ 6 => {:params=>{:material=>"Paper", :type=>"Endleaf", :attached_above=>"None", :attached_below=>"None", :stub=>"None", :nestLevel=>1}, :conjoined_leaf_order=>nil, :parentOrder=>1, :rectoOrder=>6, :versoOrder=>6}
+ })
+ expect(result[:rectos]).to eq({
+ 1 => {:params=>{:folio_number=>"", :page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>1},
+ 2 => {:params=>{:folio_number=>"", :page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>2},
+ 3 => {:params=>{:folio_number=>"", :page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>3},
+ 4 => {:params=>{:folio_number=>"", :page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>4},
+ 5 => {:params=>{:folio_number=>"", :page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>5},
+ 6 => {:params=>{:folio_number=>"", :page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>6}
+ })
+ expect(result[:versos]).to eq({
+ 1 => {:params=>{:folio_number=>"", :page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>1},
+ 2 => {:params=>{:folio_number=>"", :page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>2},
+ 3 => {:params=>{:folio_number=>"", :page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>3},
+ 4 => {:params=>{:folio_number=>"", :page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>4},
+ 5 => {:params=>{:folio_number=>"", :page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>5},
+ 6 => {:params=>{:folio_number=>"", :page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>6}
+ })
+ expect(result[:notes]).to eq({
+ 1 => {:params=>{:title=>"Test Note", :type=>"Ink", :description=>"This is a test", :show=>true}, :objects=>{:Group=>[1], :Leaf=>[5], :Recto=>[5], :Verso=>[5]}}
+ })
+ end
+
+ it 'builds the right XML' do
+ result = Nokogiri::XML(buildDotModel(@project))
+ # Metadata elements
+ expect(result.css("manuscript title").text).to eq 'Sample project'
+ expect(result.css("manuscript shelfmark").text).to eq 'Ravenna 384.2339'
+ expect(result.css("manuscript date").text).to eq '18th century'
+ expect(result.css("taxonomy[xml|id='manuscript_preferences'] term").collect { |t| [t['xml:id'], t.text] }).to include(
+ ['manuscript_preferences_ravenna_384_2339_showTips', 'true']
+ )
+ expect(result.css("taxonomy[xml|id='manifests'] term").collect { |t| [t['xml:id'], t.text] }).to include(
+ ['manifest_12341234', 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest']
+ )
+ # Quires
+ expect(result.css("taxonomy[xml|id='group_type'] term").collect { |t| [t['xml:id'], t.text] }).to include(
+ ['group_type_quire', 'Quire']
+ )
+ expect(result.css("taxonomy[xml|id='group_title'] term").collect { |t| [t['xml:id'], t.text] }).to include(
+ ['group_title_group_1', 'Group 1'],
+ ['group_title_group_2', 'Group 2'],
+ )
+ expect(result.css("taxonomy[xml|id='group_members'] term").collect { |t| [t['xml:id'], t.text] }).to include(
+ ['group_members_ravenna_384_2339-q-1', '#ravenna_384_2339-1-1 #ravenna_384_2339-1-2 #ravenna_384_2339-q-1-2 #ravenna_384_2339-1-3 #ravenna_384_2339-1-4'],
+ ['group_members_ravenna_384_2339-q-1-2', '#ravenna_384_2339-1-2-3 #ravenna_384_2339-1-2-4'],
+ )
+ # Leaves
+ expect(result.css("taxonomy[xml|id='leaf_material'] term").collect { |t| [t['xml:id'], t.text] }).to include(
+ ['leaf_material_paper', 'Paper']
+ )
+ expect(result.css("manuscript leaf").collect { |t| [t['xml:id'], t.css('folioNumber').first.text, t.css('q').first['target'], t.css('q').first['n']] }).to include(
+ ['ravenna_384_2339-1-1', '1', '#ravenna_384_2339-q-1', '1'],
+ ['ravenna_384_2339-1-2', '2', '#ravenna_384_2339-q-1', '1'],
+ ['ravenna_384_2339-1-2-3', '3', '#ravenna_384_2339-q-1-2', '2'],
+ ['ravenna_384_2339-1-2-4', '4', '#ravenna_384_2339-q-1-2', '2'],
+ ['ravenna_384_2339-1-3', '5', '#ravenna_384_2339-q-1', '1'],
+ ['ravenna_384_2339-1-4', '6', '#ravenna_384_2339-q-1', '1']
+ )
+ # Sides and Notes
+ expect(result.css("mapping map").collect { |t| [t['target'], t['side'], t.css('term').first['target']]}).to include(
+ ['#ravenna_384_2339-1-1', 'recto', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-2', 'recto', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-2-3', 'recto', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-2-4', 'recto', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-3', 'recto', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-4', 'recto', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-1', 'verso', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-2', 'verso', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-2-3', 'verso', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-2-4', 'verso', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-3', 'verso', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-4', 'verso', '#side_page_number_EMPTY']
+ )
+ expect(result.css("mapping map").collect { |t| [t['target'], t.css('term').first['target']]}).to include(
+ ['#ravenna_384_2339-n-1', '#note_title_test_note #note_show'],
+ )
+ end
+end
diff --git a/viscoll-api/spec/helpers/controller_helper/filter_helper_spec.rb b/viscoll-api/spec/helpers/controller_helper/filter_helper_spec.rb
new file mode 100644
index 00000000..240b6000
--- /dev/null
+++ b/viscoll-api/spec/helpers/controller_helper/filter_helper_spec.rb
@@ -0,0 +1,182 @@
+require 'rails_helper'
+
+RSpec.describe ControllerHelper::FilterHelper, type: :helper do
+ it 'should reject empty queries' do
+ expect(runValidations([])).to eq ['should contain at least 1 query']
+ end
+
+ it 'should reject unrecognized types' do
+ expect(runValidations([{ 'type' => 'foobar' }])).to include a_hash_including('type' => 'type should be one of: [group, leaf, side, note]')
+ end
+
+ it 'should reject unrecognized conjunctions' do
+ result = runValidations([
+ { 'type' => 'group', 'attribute' => 'type', 'condition' => 'equals', 'values' => ['Codex Taorminae 1'] },
+ { 'type' => 'group', 'attribute' => 'type', 'condition' => 'equals', 'values' => ['Codex Taorminae 2'], 'conjunction' => 'XOR' },
+ { 'type' => 'group', 'attribute' => 'type', 'condition' => 'equals', 'values' => ['Codex Taorminae 3'] }
+ ])
+ expect(result).to include a_hash_including('conjunction' => 'conjunction should be one of : [AND, OR]')
+ end
+
+ it 'should reject empty query values' do
+ result = runValidations([
+ { 'type' => 'group', 'attribute' => 'type', 'condition' => 'equals', 'values' => [] },
+ { 'type' => 'group', 'attribute' => 'type', 'condition' => 'equals', 'values' => ['Codex Taorminae 2'], 'conjunction' => 'OR' },
+ { 'type' => 'group', 'attribute' => 'type', 'condition' => 'equals', 'values' => ['Codex Taorminae 3'] }
+ ])
+ expect(result).to include a_hash_including('values' => 'query value cannot be empty')
+ end
+
+ describe 'Group queries' do
+ it 'should reject invalid attribute for groups' do
+ result = runValidations([
+ { 'type' => 'group', 'attribute' => 'waahoo', 'condition' => 'waahoo', 'values' => ['Quire'] }
+ ])
+ expect(result).to include a_hash_including('attribute' => 'valid attributes for group: [type, title]')
+ end
+
+ it 'should accept valid parameters for type' do
+ ['equals', 'not equals'].each do |op|
+ result = runValidations([
+ { 'type' => 'group', 'attribute' => 'type', 'condition' => op, 'values' => ['Quire'] }
+ ])
+ expect(result).to be_empty
+ end
+ end
+
+ it 'should reject invalid conditions for type' do
+ result = runValidations([
+ { 'type' => 'group', 'attribute' => 'type', 'condition' => 'contains', 'values' => ['Quire'] }
+ ])
+ expect(result).to include a_hash_including('condition' => 'valid conditions for group attribute type : [equals, not equals]')
+ end
+
+ it 'should accept valid parameters for title' do
+ ['equals', 'not equals', 'contains', 'not contains'].each do |op|
+ result = runValidations([
+ { 'type' => 'group', 'attribute' => 'title', 'condition' => op, 'values' => ['Codex Taorminae'] }
+ ])
+ expect(result).to be_empty
+ end
+ end
+
+ it 'should reject invalid conditions for title' do
+ result = runValidations([
+ { 'type' => 'group', 'attribute' => 'title', 'condition' => 'waahoo', 'values' => ['Codex Taorminae'] }
+ ])
+ expect(result).to include a_hash_including('condition' => "valid conditions for group attribute title : [equals, not equals, contains, not contains]")
+ end
+ end
+
+ describe 'Leaf queries' do
+ it 'should reject invalid attribute for leafs' do
+ result = runValidations([
+ { 'type' => 'leaf', 'attribute' => 'waahoo', 'condition' => 'equals', 'values' => ['3'] }
+ ])
+ expect(result).to include a_hash_including('attribute' => 'valid attributes for leaf: [type, material, conjoined_to, conjoined_leaf_order, attached_above, attached_below, stub]')
+ end
+
+ it 'should accept valid parameters for conditions' do
+ ['type', 'material', 'conjoined_to', 'conjoined_leaf_order', 'attached_above', 'attached_below', 'stub'].each do |attribute|
+ result = runValidations([
+ { 'type' => 'leaf', 'attribute' => attribute, 'condition' => 'eq', 'values' => ['Some Value'] }
+ ])
+ expect(result).to include a_hash_including('condition' => "valid conditions for leaf attribute #{attribute} : [equals, not equals]")
+ end
+ end
+ end
+
+ describe 'Side queries' do
+ it 'should reject invalid attribute for sides' do
+ result = runValidations([
+ { 'type' => 'side', 'attribute' => 'waahoo', 'condition' => 'equals', 'values' => ['3r'] }
+ ])
+ expect(result).to include a_hash_including('attribute' => 'valid attributes for side: [folio_number, page_number, texture, script_direction, uri]')
+ end
+
+ it 'should reject invalid conditions for texture and script_direction' do
+ ['texture', 'script_direction'].each do |attribute|
+ result = runValidations([
+ { 'type' => 'side', 'attribute' => attribute, 'condition' => 'waahoo', 'values' => ['value'] }
+ ])
+ expect(result).to include a_hash_including('condition' => "valid conditions for side attribute #{attribute} : [equals, not equals]")
+ end
+ end
+
+ it 'should accept valid conditions for texture and script_direction' do
+ ['texture', 'script_direction'].each do |attribute|
+ ['equals', 'not equals'].each do |condition|
+ result = runValidations([
+ { 'type' => 'side', 'attribute' => attribute, 'condition' => condition, 'values' => ['value'] }
+ ])
+ expect(result).to be_empty
+ end
+ end
+ end
+
+ it 'should reject invalid conditions for folio_number and uri' do
+ ['folio_number', 'uri'].each do |attribute|
+ result = runValidations([
+ { 'type' => 'side', 'attribute' => attribute, 'condition' => 'waahoo', 'values' => ['value'] }
+ ])
+ expect(result).to include a_hash_including('condition' => "valid conditions for side attribute #{attribute} : [equals, not equals, contains, not contains]")
+ end
+ end
+
+ it 'should accept valid conditions for folio_number and uri' do
+ ['folio_number', 'uri'].each do |attribute|
+ ['equals', 'not equals', 'contains', 'not contains'].each do |condition|
+ result = runValidations([
+ { 'type' => 'side', 'attribute' => attribute, 'condition' => condition, 'values' => ['value'] }
+ ])
+ expect(result).to be_empty
+ end
+ end
+ end
+ end
+
+ describe 'Note queries' do
+ it 'should reject invalid attribute for sides' do
+ result = runValidations([
+ { 'type' => 'note', 'attribute' => 'waahoo', 'condition' => 'equals', 'values' => ['hint'] }
+ ])
+ expect(result).to include a_hash_including('attribute' => 'valid attributes for note: [title, type, description]')
+ end
+
+ it 'should reject invalid conditions for type' do
+ result = runValidations([
+ { 'type' => 'note', 'attribute' => 'type', 'condition' => 'waahoo', 'values' => ['hint'] }
+ ])
+ expect(result).to include a_hash_including('condition' => 'valid conditions for note attribute type : [equals, not equals]')
+ end
+
+ it 'should accept valid conditions for type' do
+ ['equals', 'not equals'].each do |condition|
+ result = runValidations([
+ { 'type' => 'note', 'attribute' => 'type', 'condition' => condition, 'values' => ['hint'] }
+ ])
+ expect(result).to be_empty
+ end
+ end
+
+ it 'should reject invalid conditions for title and description' do
+ ['title', 'description'].each do |attribute|
+ result = runValidations([
+ { 'type' => 'note', 'attribute' => attribute, 'condition' => 'waahoo', 'values' => ['hint'] }
+ ])
+ expect(result).to include a_hash_including('condition' => "valid conditions for note attribute #{attribute} : [equals, not equals, contains, not contains]")
+ end
+ end
+
+ it 'should accept valid conditions for title and description' do
+ ['title', 'description'].each do |attribute|
+ ['equals', 'not equals', 'contains', 'not contains'].each do |condition|
+ result = runValidations([
+ { 'type' => 'note', 'attribute' => attribute, 'condition' => condition, 'values' => ['hint'] }
+ ])
+ expect(result).to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/viscoll-api/spec/helpers/controller_helper/groups_helper_spec.rb b/viscoll-api/spec/helpers/controller_helper/groups_helper_spec.rb
new file mode 100644
index 00000000..ff7051d5
--- /dev/null
+++ b/viscoll-api/spec/helpers/controller_helper/groups_helper_spec.rb
@@ -0,0 +1,25 @@
+require 'rails_helper'
+
+RSpec.describe ControllerHelper::GroupsHelper, type: :helper do
+ before :each do
+ @project = FactoryGirl.create(:project)
+ @group = FactoryGirl.create(:group, project: @project)
+ end
+
+ describe 'addLeavesInside' do
+ it 'adds unconjoined leaves' do
+ addLeavesInside(@project.id.to_s, @group, 4, false, nil)
+ expect(@project.leafs.count).to eq 4
+ expect(@group.memberIDs.count).to eq 4
+ expect(@project.leafs.all? { |leaf| leaf.conjoined_to.blank? }).to be true
+ end
+ it 'adds conjoined leaves' do
+ addLeavesInside(@project.id.to_s, @group, 4, true, nil)
+ expect(@project.leafs.count).to eq 4
+ expect(@group.memberIDs.count).to eq 4
+ 4.times.each do |i|
+ expect(@project.leafs[i].conjoined_to).to eq @project.leafs[3-i].id.to_s
+ end
+ end
+ end
+end
diff --git a/viscoll-api/spec/helpers/controller_helper/import_helper_spec.rb b/viscoll-api/spec/helpers/controller_helper/import_helper_spec.rb
new file mode 100644
index 00000000..c03f37a3
--- /dev/null
+++ b/viscoll-api/spec/helpers/controller_helper/import_helper_spec.rb
@@ -0,0 +1,100 @@
+require 'rails_helper'
+
+module ControllerHelper
+ module StubbedImportHelper
+ include ControllerHelper::ImportHelper
+
+ def current_user
+ User.last
+ end
+ end
+end
+
+RSpec.describe ControllerHelper::StubbedImportHelper, type: :helper do
+ describe 'JSON Import' do
+ let(:json_import_data) do
+ {
+ "project" => {
+ "title" => 'Sample project',
+ "shelfmark" => 'Ravenna 384.2339',
+ "metadata" => { 'date' => '18th century' },
+ "preferences" => { 'showTips' => true },
+ "manifests" => { '12341234' => { 'id' => '12341234', 'url' => 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest', 'name' => 'Boston, and Bunker Hill.' } },
+ "noteTypes" => ['Hand', 'Ink', 'Unknown']
+ },
+ "Groups" => {
+ "1" => {"params" => {"type" => "Quire", "title" => "Quire 1", "nestLevel" => 1}, "tacketed" => [], "sewing" => [], "parentOrder" => nil, "memberOrders" => ["Leaf_1", "Leaf_2", "Group_2", "Leaf_5", "Leaf_6"]},
+ "2" => {"params" => {"type" => "Quire", "title" => "Quire 2", "nestLevel" => 2}, "tacketed" => [], "sewing" => [], "parentOrder" => 1, "memberOrders" => ["Leaf_3", "Leaf_4"]}
+ },
+ "Leafs" => {
+ "1" => {"params" => {"material" => "Paper", "type" => "Original", "attached_above" => "None", "attached_below" => "None", "stub" => "None", "nestLevel" => 1}, "conjoined_leaf_order" => nil, "parentOrder" => 1, "rectoOrder" => 1, "versoOrder" => 1},
+ "2" => {"params" => {"material" => "Paper", "type" => "Original", "attached_above" => "None", "attached_below" => "None", "stub" => "None", "nestLevel" => 1}, "conjoined_leaf_order" => nil, "parentOrder" => 1, "rectoOrder" => 2, "versoOrder" => 2},
+ "3" => {"params" => {"material" => "Paper", "type" => "Original", "attached_above" => "None", "attached_below" => "None", "stub" => "None", "nestLevel" => 2}, "conjoined_leaf_order" => nil, "parentOrder" => 2, "rectoOrder" => 3, "versoOrder" => 3},
+ "4" => {"params" => {"material" => "Paper", "type" => "Original", "attached_above" => "None", "attached_below" => "None", "stub" => "None", "nestLevel" => 2}, "conjoined_leaf_order" => nil, "parentOrder" => 2, "rectoOrder" => 4, "versoOrder" => 4},
+ "5" => {"params" => {"material" => "Paper", "type" => "Original", "attached_above" => "None", "attached_below" => "None", "stub" => "None", "nestLevel" => 1}, "conjoined_leaf_order" => nil, "parentOrder" => 1, "rectoOrder" => 5, "versoOrder" => 5},
+ "6" => {"params" => {"material" => "Paper", "type" => "Endleaf", "attached_above" => "None", "attached_below" => "None", "stub" => "None", "nestLevel" => 1}, "conjoined_leaf_order" => nil, "parentOrder" => 1, "rectoOrder" => 6, "versoOrder" => 6}
+ },
+ "Rectos" => {
+ "1" => {"params" => {"folio_number" => "1R", "texture" => "Hair", "image" => {}, "script_direction" => "None"}, "parentOrder" => 1},
+ "2" => {"params" => {"folio_number" => "2R", "texture" => "Hair", "image" => {}, "script_direction" => "None"}, "parentOrder" => 2},
+ "3" => {"params" => {"folio_number" => "3R", "texture" => "Hair", "image" => {}, "script_direction" => "None"}, "parentOrder" => 3},
+ "4" => {"params" => {"folio_number" => "4R", "texture" => "Hair", "image" => {}, "script_direction" => "None"}, "parentOrder" => 4},
+ "5" => {"params" => {"folio_number" => "5R", "texture" => "Hair", "image" => {}, "script_direction" => "None"}, "parentOrder" => 5},
+ "6" => {"params" => {"folio_number" => "6R", "texture" => "Hair", "image" => {}, "script_direction" => "None"}, "parentOrder" => 6}
+ },
+ "Versos" => {
+ "1" => {"params" => {"folio_number" => "1V", "texture" => "Flesh", "image" => {}, "script_direction" => "None"}, "parentOrder" => 1},
+ "2" => {"params" => {"folio_number" => "2V", "texture" => "Flesh", "image" => {}, "script_direction" => "None"}, "parentOrder" => 2},
+ "3" => {"params" => {"folio_number" => "3V", "texture" => "Flesh", "image" => {}, "script_direction" => "None"}, "parentOrder" => 3},
+ "4" => {"params" => {"folio_number" => "4V", "texture" => "Flesh", "image" => {}, "script_direction" => "None"}, "parentOrder" => 4},
+ "5" => {"params" => {"folio_number" => "5V", "texture" => "Flesh", "image" => {}, "script_direction" => "None"}, "parentOrder" => 5},
+ "6" => {"params" => {"folio_number" => "6V", "texture" => "Flesh", "image" => {}, "script_direction" => "None"}, "parentOrder" => 6}
+ },
+ "Notes" => {
+ "1" => {"params" => {"title" => "Test Note", "type" => "Ink", "description" => "This is a test", "show" => true}, "objects" => {"Group" => [1], "Leaf" => [5], "Recto" => [5], "Verso" => [5]}}
+ }
+ }
+ end
+
+ it 'should import properly' do
+ user = FactoryGirl.create(:user)
+ expect{ handleJSONImport(json_import_data) }.to change{Project.count}.by(1)
+ project = Project.last
+ expect(project.title).to eq 'Sample project'
+ expect(project.shelfmark).to eq 'Ravenna 384.2339'
+ expect(project.metadata).to eq({ 'date' => '18th century' })
+ expect(project.preferences).to eq({ 'showTips' => true })
+ expect(project.noteTypes).to eq ['Hand', 'Ink', 'Unknown']
+ expect(project.manifests).to eq({ '12341234' => { 'id' => '12341234', 'url' => 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest', 'name' => 'Boston, and Bunker Hill.' } })
+ expect(project.leafs.count).to eq 6
+ expect(project.sides.count).to eq 12
+ expect(project.notes[0].title).to eq 'Test Note'
+ expect(project.notes[0].type).to eq 'Ink'
+ expect(project.notes[0].description).to eq 'This is a test'
+ expect(project.notes[0].objects).to eq({'Group' => [project.groups[0].id.to_s], 'Leaf' => [project.leafs[4].id.to_s], 'Recto' => [project.leafs[4].rectoID], 'Verso' => [project.leafs[4].versoID]})
+ end
+
+ it 'should avoid overwriting a project of the same name' do
+ user = FactoryGirl.create(:user)
+ existing_project = FactoryGirl.create(:project, title: 'Ultra waahoo project is ultra waahoo')
+ duplicated_data = json_import_data
+ duplicated_data['project']['title'] = existing_project.title
+ expect{ handleJSONImport(duplicated_data) }.to change{Project.count}.by(1)
+ existing_project.reload
+ expect(existing_project.title).to eq 'Ultra waahoo project is ultra waahoo'
+ project = Project.last
+ expect(project.title[0..46]).to eq "Copy of Ultra waahoo project is ultra waahoo @ "
+ expect(project.shelfmark).to eq 'Ravenna 384.2339'
+ expect(project.metadata).to eq({ 'date' => '18th century' })
+ expect(project.preferences).to eq({ 'showTips' => true })
+ expect(project.noteTypes).to eq ['Hand', 'Ink', 'Unknown']
+ expect(project.manifests).to eq({ '12341234' => { 'id' => '12341234', 'url' => 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest', 'name' => 'Boston, and Bunker Hill.' } })
+ expect(project.leafs.count).to eq 6
+ expect(project.sides.count).to eq 12
+ expect(project.notes[0].title).to eq 'Test Note'
+ expect(project.notes[0].type).to eq 'Ink'
+ expect(project.notes[0].description).to eq 'This is a test'
+ expect(project.notes[0].objects).to eq({'Group' => [project.groups[0].id.to_s], 'Leaf' => [project.leafs[4].id.to_s], 'Recto' => [project.leafs[4].rectoID], 'Verso' => [project.leafs[4].versoID]})
+ end
+ end
+end
diff --git a/viscoll-api/spec/helpers/controller_helper/import_json_helper_spec.rb b/viscoll-api/spec/helpers/controller_helper/import_json_helper_spec.rb
new file mode 100644
index 00000000..b110da90
--- /dev/null
+++ b/viscoll-api/spec/helpers/controller_helper/import_json_helper_spec.rb
@@ -0,0 +1,60 @@
+require 'rails_helper'
+
+module ControllerHelper
+ module StubbedImportHelper
+ include ControllerHelper::ImportJsonHelper
+
+ def current_user
+ User.last
+ end
+ end
+end
+
+RSpec.describe ControllerHelper::StubbedImportHelper, type: :helper do
+ describe 'JSON Import' do
+ let(:json_import_data) do
+ JSON.parse(File.open(File.dirname(__FILE__) + '/../../fixtures/sample_import_json.json', 'r') { |file| file.read })
+ end
+
+ it 'should import properly' do
+ user = FactoryGirl.create(:user)
+ expect{ handleJSONImport(json_import_data) }.to change{Project.count}.by(1)
+ project = Project.last
+ expect(project.title).to eq 'Sample project'
+ expect(project.shelfmark).to eq 'Ravenna 384.2339'
+ expect(project.metadata).to eq({ 'date' => '18th century' })
+ expect(project.preferences).to eq({ 'showTips' => true })
+ expect(project.noteTypes).to eq ['Hand', 'Ink', 'Unknown']
+ expect(project.manifests).to eq({ '12341234' => { 'id' => '12341234', 'url' => 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest', 'name' => 'Boston, and Bunker Hill.' } })
+ expect(project.leafs.count).to eq 6
+ expect(project.sides.count).to eq 12
+ expect(project.notes[0].title).to eq 'Test Note'
+ expect(project.notes[0].type).to eq 'Ink'
+ expect(project.notes[0].description).to eq 'This is a test'
+ expect(project.notes[0].objects).to eq({'Group' => [project.groups[0].id.to_s], 'Leaf' => [project.leafs[4].id.to_s], 'Recto' => [project.leafs[4].rectoID], 'Verso' => [project.leafs[4].versoID]})
+ end
+
+ it 'should avoid overwriting a project of the same name' do
+ user = FactoryGirl.create(:user)
+ existing_project = FactoryGirl.create(:project, title: 'Ultra waahoo project is ultra waahoo')
+ duplicated_data = json_import_data
+ duplicated_data['project']['title'] = existing_project.title
+ expect{ handleJSONImport(duplicated_data) }.to change{Project.count}.by(1)
+ existing_project.reload
+ expect(existing_project.title).to eq 'Ultra waahoo project is ultra waahoo'
+ project = Project.last
+ expect(project.title[0..46]).to eq "Copy of Ultra waahoo project is ultra waahoo @ "
+ expect(project.shelfmark).to eq 'Ravenna 384.2339'
+ expect(project.metadata).to eq({ 'date' => '18th century' })
+ expect(project.preferences).to eq({ 'showTips' => true })
+ expect(project.noteTypes).to eq ['Hand', 'Ink', 'Unknown']
+ expect(project.manifests).to eq({ '12341234' => { 'id' => '12341234', 'url' => 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest', 'name' => 'Boston, and Bunker Hill.' } })
+ expect(project.leafs.count).to eq 6
+ expect(project.sides.count).to eq 12
+ expect(project.notes[0].title).to eq 'Test Note'
+ expect(project.notes[0].type).to eq 'Ink'
+ expect(project.notes[0].description).to eq 'This is a test'
+ expect(project.notes[0].objects).to eq({'Group' => [project.groups[0].id.to_s], 'Leaf' => [project.leafs[4].id.to_s], 'Recto' => [project.leafs[4].rectoID], 'Verso' => [project.leafs[4].versoID]})
+ end
+ end
+end
diff --git a/viscoll-api/spec/helpers/controller_helper/import_mapping_helper_spec.rb b/viscoll-api/spec/helpers/controller_helper/import_mapping_helper_spec.rb
new file mode 100644
index 00000000..524f57cb
--- /dev/null
+++ b/viscoll-api/spec/helpers/controller_helper/import_mapping_helper_spec.rb
@@ -0,0 +1,68 @@
+require 'rails_helper'
+
+RSpec.describe ControllerHelper::ImportMappingHelper, type: :helper do
+ before do
+ @base_api_url = 'http://127.0.0.1:12345'
+ @user = FactoryGirl.create(:user)
+ @project = FactoryGirl.create(:codex_project, user: @user, quire_structure: [[1,3]])
+ end
+
+ after do
+ Image.where(:projectIDs => @project.id.to_s).each do | image |
+ image.destroy
+ end
+ end
+
+ describe 'handleMappingImport' do
+ it 'should run properly with images in various attachment situations' do
+ # Prep user with preloaded images
+ preloads = [
+ FactoryGirl.create(:image, user: @user, projectIDs: [@project.id.to_s], filename: '1V.png', fileID: '5a28221ec199860e7a2f5fd1'),
+ FactoryGirl.create(:image, user: @user, projectIDs: [@project.id.to_s], filename: '2R.png', fileID: '5a28221ec199860e7a1shibainu', id: '5a28221ec199860e7a2f5fd1shibainu'),
+ FactoryGirl.create(:image, user: @user, projectIDs: [@project.id.to_s], filename: '2V.png', fileID: '0e7a2f5fd1waahoo', id: '5a28221ec199860e7a2f5fd1waahoo')
+ ]
+ @user.images = preloads
+ @user.save
+ # Situation 1: Brand new image uploaded
+ @project.sides[0].update(image: {
+ manifestID: 'DIYImages',
+ label: '1R',
+ url: 'http://www.foobar.net/images/5a28221ec199860e7a2f5fd1_1R.png'
+ })
+ # Situation 2: Uploaded image same name and content as existing image
+ @project.sides[1].update(image: {
+ manifestID: 'DIYImages',
+ label: '1V',
+ url: 'http://www.foobar.net/images/5a28221ec199860e7a2f5fd1_1V.png'
+ })
+ # Situation 3: Uploaded image same name but different content from existing image
+ @project.sides[2].update(image: {
+ manifestID: 'DIYImages',
+ label: '2R',
+ url: 'http://www.foobar.net/images/5a28221ec199860e7a2f5fd1_2R.png'
+ })
+ # Situation 4: Image exists with current user but not uploaded
+ @project.sides[3].update(image: {
+ manifestID: 'DIYImages',
+ label: '2V',
+ url: 'http://www.foobar.net/images/5a28221ec199860e7a2f5fd1waahoo_2V.png'
+ })
+ # Situation 5: Image specified but not uploaded
+ @project.sides[4].update(image: {
+ manifestID: 'DIYImages',
+ label: '3R',
+ url: 'http://www.foobar.net/images/5a28221ec199860e7a2f5fd1_3R.png'
+ })
+ zipData = File.open("#{Rails.root}/spec/fixtures/base64zip.txt", "rb").read
+ handleMappingImport(@project, zipData, @user)
+ @project.reload
+ expect(@project.sides[0].image).to include('manifestID' => 'DIYImages')
+ expect(@project.sides[0].image['url']).to match(/http:\/\/127\.0\.0\.1:12345\/images\/[\w]+_1R\.png/)
+ expect(@project.sides[1].image).to include('manifestID' => 'DIYImages', 'url' => "http://127.0.0.1:12345/images/#{preloads[0].id}_1V.png")
+ expect(@project.sides[2].image).to include('manifestID' => 'DIYImages')
+ expect(@project.sides[2].image['url']).to match(/http:\/\/127\.0\.0\.1:12345\/images\/[\w]+_2R\(copy\)\.png/)
+ expect(@project.sides[3].image).to include('manifestID' => 'DIYImages', 'url' => "http://127.0.0.1:12345/images/5a28221ec199860e7a2f5fd1waahoo_2V.png")
+ expect(@project.sides[4].image).to be_empty
+ end
+ end
+end
diff --git a/viscoll-api/spec/helpers/controller_helper/import_xml_helper_spec.rb b/viscoll-api/spec/helpers/controller_helper/import_xml_helper_spec.rb
new file mode 100644
index 00000000..6220651f
--- /dev/null
+++ b/viscoll-api/spec/helpers/controller_helper/import_xml_helper_spec.rb
@@ -0,0 +1,61 @@
+require 'rails_helper'
+
+module ControllerHelper
+ module StubbedXmlImportHelper
+ include ControllerHelper::ImportXmlHelper
+ include ControllerHelper::ImportJsonHelper
+
+ def current_user
+ User.last
+ end
+ end
+end
+
+RSpec.describe ControllerHelper::StubbedXmlImportHelper, type: :helper do
+ describe 'XML Import' do
+ let(:xml_import_data) do
+ Nokogiri::XML(File.open(File.dirname(__FILE__) + '/../../fixtures/sample_import_xml.xml', 'r') { |file| file.read })
+ end
+
+ it 'should import properly' do
+ user = FactoryGirl.create(:user)
+ expect{ handleXMLImport(xml_import_data) }.to change{Project.count}.by(1)
+ project = Project.last
+ expect(project.title).to eq 'Sample project'
+ expect(project.shelfmark).to eq 'Ravenna 384.2339'
+ expect(project.metadata).to eq({ 'date' => '18th century' })
+ expect(project.preferences).to eq({ 'showTips' => true })
+ expect(project.noteTypes).to include('Ink', 'Unknown')
+ expect(project.manifests).to eq({ '12341234' => { 'id' => '12341234', 'url' => 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest' } })
+ expect(project.leafs.count).to eq 6
+ expect(project.sides.count).to eq 12
+ expect(project.notes[0].title).to eq 'Test Note'
+ expect(project.notes[0].type).to eq 'Ink'
+ expect(project.notes[0].description).to eq 'This is a test'
+ expect(project.notes[0].objects).to eq({'Group' => [project.groups[0].id.to_s], 'Leaf' => [project.leafs[4].id.to_s], 'Recto' => [project.leafs[4].rectoID], 'Verso' => [project.leafs[4].versoID]})
+ end
+
+ it 'should avoid overwriting a project of the same name' do
+ user = FactoryGirl.create(:user)
+ existing_project = FactoryGirl.create(:project, title: 'Ultra waahoo project is ultra waahoo')
+ duplicated_data = xml_import_data
+ duplicated_data.at_css('viscoll manuscript title').content = existing_project.title
+ expect{ handleXMLImport(duplicated_data) }.to change{Project.count}.by(1)
+ existing_project.reload
+ expect(existing_project.title).to eq 'Ultra waahoo project is ultra waahoo'
+ project = Project.last
+ expect(project.title[0..46]).to eq "Copy of Ultra waahoo project is ultra waahoo @ "
+ expect(project.shelfmark).to eq 'Ravenna 384.2339'
+ expect(project.metadata).to eq({ 'date' => '18th century' })
+ expect(project.preferences).to eq({ 'showTips' => true })
+ expect(project.noteTypes).to include('Ink', 'Unknown')
+ expect(project.manifests).to eq({ '12341234' => { 'id' => '12341234', 'url' => 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest' } })
+ expect(project.leafs.count).to eq 6
+ expect(project.sides.count).to eq 12
+ expect(project.notes[0].title).to eq 'Test Note'
+ expect(project.notes[0].type).to eq 'Ink'
+ expect(project.notes[0].description).to eq 'This is a test'
+ expect(project.notes[0].objects).to eq({'Group' => [project.groups[0].id.to_s], 'Leaf' => [project.leafs[4].id.to_s], 'Recto' => [project.leafs[4].rectoID], 'Verso' => [project.leafs[4].versoID]})
+ end
+ end
+end
diff --git a/viscoll-api/spec/helpers/controller_helper/leafs_helper_spec.rb b/viscoll-api/spec/helpers/controller_helper/leafs_helper_spec.rb
new file mode 100644
index 00000000..9ed42ed6
--- /dev/null
+++ b/viscoll-api/spec/helpers/controller_helper/leafs_helper_spec.rb
@@ -0,0 +1,177 @@
+require 'rails_helper'
+
+RSpec.describe ControllerHelper::LeafsHelper, type: :helper do
+ describe 'autoConjoinLeaves' do
+ describe 'even leaves' do
+ before :each do
+ @project = FactoryGirl.create(:project)
+ @group = FactoryGirl.create(:quire, project: @project)
+ @project.add_groupIDs([@group.id.to_s], 0)
+ @leaves = 4.times.collect { FactoryGirl.create(:leaf, project: @project) }
+ @group.add_members(@leaves.collect { |leaf| leaf.id.to_s }, 0)
+ @project.update({ leafs: @leaves })
+ end
+
+ it 'conjoins new leaves' do
+ autoConjoinLeaves(@leaves, nil)
+ @project.reload
+ @leaves = @project.leafs
+ expect(@leaves[0].conjoined_to).to eq @leaves[3].id.to_s
+ expect(@leaves[1].conjoined_to).to eq @leaves[2].id.to_s
+ expect(@leaves[2].conjoined_to).to eq @leaves[1].id.to_s
+ expect(@leaves[3].conjoined_to).to eq @leaves[0].id.to_s
+ end
+
+ it 'reconfigures existing leaves' do
+ @leaves[0].conjoined_to = @leaves[1].id.to_s
+ @leaves[1].conjoined_to = @leaves[0].id.to_s
+ @leaves[2].conjoined_to = @leaves[3].id.to_s
+ @leaves[3].conjoined_to = @leaves[2].id.to_s
+ @leaves.each { |leaf| leaf.save }
+ autoConjoinLeaves(@leaves, nil)
+ @project.reload
+ @leaves = @project.leafs
+ expect(@leaves[0].conjoined_to).to eq @leaves[3].id.to_s
+ expect(@leaves[1].conjoined_to).to eq @leaves[2].id.to_s
+ expect(@leaves[2].conjoined_to).to eq @leaves[1].id.to_s
+ expect(@leaves[3].conjoined_to).to eq @leaves[0].id.to_s
+ end
+ end
+
+ describe 'odd leaves' do
+ before :each do
+ @project = FactoryGirl.create(:project)
+ @group = FactoryGirl.create(:quire, project: @project)
+ @project.add_groupIDs([@group.id.to_s], 0)
+ @leaves = 5.times.collect { FactoryGirl.create(:leaf, project: @project) }
+ @group.add_members(@leaves.collect { |leaf| leaf.id.to_s }, 0)
+ @project.update({ leafs: @leaves })
+ end
+
+ it 'conjoins new leaves' do
+ autoConjoinLeaves(@leaves, 2)
+ @project.reload
+ @leaves = @project.leafs
+ expect(@leaves[0].conjoined_to).to eq @leaves[4].id.to_s
+ expect(@leaves[1].conjoined_to).to be_blank
+ expect(@leaves[2].conjoined_to).to eq @leaves[3].id.to_s
+ expect(@leaves[3].conjoined_to).to eq @leaves[2].id.to_s
+ expect(@leaves[4].conjoined_to).to eq @leaves[0].id.to_s
+ end
+
+ it 'reconfigures existing leaves' do
+ @leaves[0].conjoined_to = @leaves[1].id.to_s
+ @leaves[1].conjoined_to = @leaves[0].id.to_s
+ @leaves[3].conjoined_to = @leaves[4].id.to_s
+ @leaves[4].conjoined_to = @leaves[3].id.to_s
+ @leaves.each { |leaf| leaf.save }
+ autoConjoinLeaves(@leaves, 4)
+ @project.reload
+ @leaves = @project.leafs
+ expect(@leaves[0].conjoined_to).to eq @leaves[4].id.to_s
+ expect(@leaves[1].conjoined_to).to eq @leaves[2].id.to_s
+ expect(@leaves[2].conjoined_to).to eq @leaves[1].id.to_s
+ expect(@leaves[3].conjoined_to).to be_blank
+ expect(@leaves[4].conjoined_to).to eq @leaves[0].id.to_s
+ end
+ end
+
+ describe 'reconjoin odd subleaves' do
+ before do
+ @project = FactoryGirl.create(:codex_project, quire_structure: [[1,8]])
+ @leaves = @project.leafs
+ end
+
+ it 'reconfigures leaves properly when conjoining first 5' do
+ expect(@leaves[2].conjoined_to).to eq @leaves[5].id.to_s
+ autoConjoinLeaves(@leaves[0..4], 3)
+ @project.reload
+ @leaves = @project.leafs
+ expect(@leaves[0].conjoined_to).to eq @leaves[4].id.to_s
+ expect(@leaves[1].conjoined_to).to eq @leaves[3].id.to_s
+ expect(@leaves[2].conjoined_to).to be_blank
+ expect(@leaves[3].conjoined_to).to eq @leaves[1].id.to_s
+ expect(@leaves[4].conjoined_to).to eq @leaves[0].id.to_s
+ expect(@leaves[5].conjoined_to).to be_blank
+ expect(@leaves[6].conjoined_to).to be_blank
+ expect(@leaves[7].conjoined_to).to be_blank
+ end
+ end
+ end
+
+ describe 'update_attached_to' do
+ before :each do
+ @project = FactoryGirl.create(:project)
+ @group = FactoryGirl.create(:quire, project: @project)
+ @project.add_groupIDs([@group.id.to_s], 0)
+ @leaves = 5.times.collect { FactoryGirl.create(:leaf, project: @project, parentID: @group.id.to_s) }
+ @group.add_members(@leaves.collect { |leaf| leaf.id.to_s }, 0)
+ @project.update({ leafs: @leaves })
+ end
+
+ it 'correctly handles first leaf' do
+ @leaves[0].attached_below = 'Glued'
+ @leaves[0].save
+ @leaf = @leaves[0]
+ update_attached_to
+ @project.reload
+ @leaves = @project.leafs
+ expect(@leaves[1].attached_above).to eq 'Glued'
+ end
+
+ it 'correctly handles last leaf' do
+ @leaves[-1].attached_above = 'Sewn'
+ @leaves[-1].save
+ @leaf = @leaves[-1]
+ update_attached_to
+ @project.reload
+ @leaves = @project.leafs
+ expect(@leaves[-2].attached_below).to eq 'Sewn'
+ end
+
+ it 'correctly handles middle leaf' do
+ @leaves[2].update({ attached_above: 'Glued', attached_below: 'Sewn' })
+ @leaf = @leaves[2]
+ update_attached_to
+ @project.reload
+ @leaves = @project.leafs
+ expect(@leaves[1].attached_below).to eq 'Glued'
+ expect(@leaves[3].attached_above).to eq 'Sewn'
+ end
+ end
+
+ describe 'update_conjoined_partner' do
+ let(:helpers) { ApplicationController.helpers }
+
+ before :each do
+ @project = FactoryGirl.create(:project)
+ @group = FactoryGirl.create(:quire, project: @project)
+ @project.add_groupIDs([@group.id.to_s], 0)
+ @leaves = 3.times.collect { FactoryGirl.create(:leaf, project: @project, parentID: @group.id.to_s) }
+ @group.add_members(@leaves.collect { |leaf| leaf.id.to_s }, 0)
+ @project.update({ leafs: @leaves })
+ end
+
+ it 'should reattach 1-2 to 1-3' do
+ @leaves[0].update({ conjoined_to: @leaves[1].id.to_s })
+ @leaves[1].update({ conjoined_to: @leaves[0].id.to_s })
+ @leaf = @leaves[0]
+ update_conjoined_partner(@leaves[2].id.to_s)
+ @project.reload
+ @leaves = @project.leafs
+ expect(@leaves[1].conjoined_to).to be_blank
+ expect(@leaves[2].conjoined_to).to eq @leaves[0].id.to_s
+ end
+
+ it 'should reattach 2-3 to 1-3' do
+ @leaves[1].update({ conjoined_to: @leaves[2].id.to_s })
+ @leaves[2].update({ conjoined_to: @leaves[1].id.to_s })
+ @leaf = @leaves[0]
+ update_conjoined_partner(@leaves[2].id.to_s)
+ @project.reload
+ @leaves = @project.leafs
+ expect(@leaves[1].conjoined_to).to be_blank
+ expect(@leaves[2].conjoined_to).to eq @leaves[0].id.to_s
+ end
+ end
+end
diff --git a/viscoll-api/spec/helpers/controller_helper/projects_helper_spec.rb b/viscoll-api/spec/helpers/controller_helper/projects_helper_spec.rb
new file mode 100644
index 00000000..11bf4135
--- /dev/null
+++ b/viscoll-api/spec/helpers/controller_helper/projects_helper_spec.rb
@@ -0,0 +1,131 @@
+require 'rails_helper'
+
+RSpec.describe ControllerHelper::ProjectsHelper, type: :helper do
+ describe 'addGroupsLeafsConjoin' do
+ it 'should create a variety of groups' do
+ @project = FactoryGirl.create(:project)
+ addGroupsLeafsConjoin(@project, [
+ { 'leaves' => 2 },
+ { 'leaves' => 4, 'conjoin' => true },
+ { 'leaves' => 3, 'conjoin' => true, 'oddLeaf' => 2 }
+ ], nil, nil, "Hair")
+ expect(@project.groups.count).to eq 3
+ expect(@project.groups[0].memberIDs.count).to eq 2
+ expect(@project.groups[1].memberIDs.count).to eq 4
+ expect(Leaf.find(@project.groups[1].memberIDs[0]).conjoined_to).to eq @project.groups[1].memberIDs[3]
+ expect(Leaf.find(@project.groups[1].memberIDs[1]).conjoined_to).to eq @project.groups[1].memberIDs[2]
+ expect(Leaf.find(@project.groups[1].memberIDs[2]).conjoined_to).to eq @project.groups[1].memberIDs[1]
+ expect(Leaf.find(@project.groups[1].memberIDs[3]).conjoined_to).to eq @project.groups[1].memberIDs[0]
+ expect(@project.groups[2].memberIDs.count).to eq 3
+ expect(Leaf.find(@project.groups[2].memberIDs[1]).conjoined_to).to be_blank
+ expect(Leaf.find(@project.groups[2].memberIDs[0]).conjoined_to).to eq @project.groups[2].memberIDs[2]
+ expect(Leaf.find(@project.groups[2].memberIDs[2]).conjoined_to).to eq @project.groups[2].memberIDs[0]
+ end
+ it 'should generate folio numbers' do
+ project = FactoryGirl.create(:project)
+ addGroupsLeafsConjoin(project, [
+ { 'leaves' => 2 },
+ { 'leaves' => 4, 'conjoin' => true },
+ { 'leaves' => 3, 'conjoin' => true, 'oddLeaf' => 2 }
+ ], 2, nil, "Hair")
+ expect(Side.find(Leaf.find(project.groups[0].memberIDs[0]).rectoID).folio_number).to eq "2R"
+ expect(Side.find(Leaf.find(project.groups[0].memberIDs[0]).versoID).folio_number).to eq "2V"
+ expect(Side.find(Leaf.find(project.groups[0].memberIDs[1]).rectoID).folio_number).to eq "3R"
+ expect(Side.find(Leaf.find(project.groups[0].memberIDs[1]).versoID).folio_number).to eq "3V"
+ end
+ it 'should generate page numbers' do
+ project = FactoryGirl.create(:project)
+ addGroupsLeafsConjoin(project, [
+ { 'leaves' => 2 },
+ { 'leaves' => 4, 'conjoin' => true },
+ { 'leaves' => 3, 'conjoin' => true, 'oddLeaf' => 2 }
+ ], nil, 5, "Hair")
+ expect(Side.find(Leaf.find(project.groups[0].memberIDs[0]).rectoID).page_number).to eq "5"
+ expect(Side.find(Leaf.find(project.groups[0].memberIDs[0]).versoID).page_number).to eq "6"
+ expect(Side.find(Leaf.find(project.groups[0].memberIDs[1]).rectoID).page_number).to eq "7"
+ expect(Side.find(Leaf.find(project.groups[0].memberIDs[1]).versoID).page_number).to eq "8"
+ end
+ it 'should generate correct patterns of texture' do
+ project = FactoryGirl.create(:project)
+ addGroupsLeafsConjoin(project, [
+ { 'leaves' => 4, 'conjoin' => true},
+ { 'leaves' => 4, 'conjoin' => true },
+ { 'leaves' => 3, 'conjoin' => true, 'oddLeaf' => 2 }
+ ], nil, 5, "Flesh")
+ expect(Side.find(Leaf.find(Group.find(project.groupIDs[0]).memberIDs[0]).rectoID).texture).to eq "Flesh"
+ expect(Side.find(Leaf.find(Group.find(project.groupIDs[0]).memberIDs[0]).versoID).texture).to eq "Hair"
+ expect(Side.find(Leaf.find(Group.find(project.groupIDs[0]).memberIDs[1]).rectoID).texture).to eq "Hair"
+ expect(Side.find(Leaf.find(Group.find(project.groupIDs[0]).memberIDs[1]).versoID).texture).to eq "Flesh"
+ expect(Side.find(Leaf.find(Group.find(project.groupIDs[0]).memberIDs[2]).rectoID).texture).to eq "Flesh"
+ expect(Side.find(Leaf.find(Group.find(project.groupIDs[0]).memberIDs[2]).versoID).texture).to eq "Hair"
+ end
+ end
+
+ describe 'getManifestInformation' do
+ before do
+ stub_request(:get, 'https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest').with(headers: { 'Accept' => '*/*', 'User-Agent' => 'Ruby' }).to_return(status: 200, body: File.read(File.dirname(__FILE__) + '/../../fixtures/uoft_hollar.json'), headers: {})
+ end
+
+ it 'should pull images' do
+ result = getManifestInformation('https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest')
+ expect(result[:name]).to eq "The fables of Aesop / paraphras'd in verse, and adorn'd with sculpture ; by John Ogilby."
+ expect(result[:images].count).to eq 392
+ expect(result[:images][1]).to eq({ label: "Hollar_a_3000_0002", url: "https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0002" })
+ end
+ end
+
+ describe 'generateResponse/getLeafMembers' do
+ before do
+ stub_request(:get, 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest').with(headers: { 'Accept' => '*/*', 'User-Agent' => 'Ruby' }).to_return(status: 200, body: File.read(File.dirname(__FILE__) + '/../../fixtures/villanova_boston.json'), headers: {})
+ @project = FactoryGirl.create(:project,
+ title: 'Sample project',
+ shelfmark: 'Ravenna 384.2339',
+ metadata: { date: '18th century' },
+ preferences: { showTips: true },
+ noteTypes: ['Hand', 'Ink', 'Unknown'],
+ manifests: { '12341234': { id: '12341234', url: 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest', name: 'Boston, and Bunker Hill.' } }
+ )
+ # Attach group with 2 leafs - (group with 2 leafs) - 2 conjoined leafs
+ @testgroup = FactoryGirl.create(:group, project: @project, nestLevel: 1)
+ @upleafs = 2.times.collect { FactoryGirl.create(:leaf, project: @project, parentID: @testgroup.id.to_s, nestLevel: 1) }
+ @testmidgroup = FactoryGirl.create(:group, project: @project, parentID: @testgroup.id.to_s, nestLevel: 2)
+ @midleafs = 2.times.collect { FactoryGirl.create(:leaf, project: @project, parentID: @testmidgroup.id.to_s, nestLevel: 2) }
+ @botleafs = 2.times.collect { FactoryGirl.create(:leaf, project: @project, parentID: @testgroup.id.to_s, nestLevel: 1) }
+ @botleafs[1].update(type: 'Endleaf')
+ @project.add_groupIDs([@testgroup.id.to_s, @testmidgroup.id.to_s], 0)
+ @testgroup.add_members([@upleafs[0].id.to_s, @upleafs[1].id.to_s, @testmidgroup.id.to_s, @botleafs[0].id.to_s, @botleafs[1].id.to_s], 0)
+ @testmidgroup.add_members([@midleafs[0].id.to_s, @midleafs[1].id.to_s], 0)
+ @testnote = FactoryGirl.create(:note, project: @project, title: 'Test Note', type: 'Ink', description: 'This is a test', show: true, objects: {Group: [@testgroup.id.to_s], Leaf: [@botleafs[0].id.to_s], Recto: [@botleafs[0].rectoID], Verso: [@botleafs[0].versoID]})
+ end
+
+ it 'returns the right output for the given sample' do
+ body = generateResponse()
+ expect(body[:project]).to eq({
+ 'id': @project.id.to_s,
+ 'title': 'Sample project',
+ 'shelfmark': 'Ravenna 384.2339',
+ 'metadata': { 'date' => '18th century' },
+ 'preferences': { 'showTips' => true },
+ 'noteTypes': [ 'Hand', 'Ink', 'Unknown' ],
+ 'manifests': { '12341234' => {
+ 'id' => '12341234',
+ 'url' => 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest',
+ 'name' => 'Boston, and Bunker Hill.',
+ 'images' => [ { 'label' => nil, 'url' => 'https://iiif.library.villanova.edu/image/vudl%3A99215', 'manifestID' => '12341234' } ]
+ } },
+ })
+ expect(body[:groupIDs]).to eq([@testgroup.id.to_s, @testmidgroup.id.to_s])
+ expect(body[:leafIDs]).to eq((@upleafs+@midleafs+@botleafs).collect { |leaf| leaf.id.to_s })
+ expect(body[:rectoIDs]).to eq((@upleafs+@midleafs+@botleafs).collect { |leaf| leaf.rectoID })
+ expect(body[:versoIDs]).to eq((@upleafs+@midleafs+@botleafs).collect { |leaf| leaf.versoID })
+ expect(body[:notes]).to eq({@testnote.id.to_s => {
+ id: @testnote.id.to_s,
+ title: 'Test Note',
+ type: 'Ink',
+ description: 'This is a test',
+ show: true,
+ objects: {'Group' => [@testgroup.id.to_s], 'Leaf' => [@botleafs[0].id.to_s], 'Recto' => [@botleafs[0].rectoID], 'Verso' => [@botleafs[0].versoID]}
+ }})
+ end
+ end
+end
diff --git a/viscoll-api/spec/helpers/validation_helper/group_validation_helper_spec.rb b/viscoll-api/spec/helpers/validation_helper/group_validation_helper_spec.rb
new file mode 100644
index 00000000..2736eca4
--- /dev/null
+++ b/viscoll-api/spec/helpers/validation_helper/group_validation_helper_spec.rb
@@ -0,0 +1,186 @@
+require 'rails_helper'
+
+RSpec.describe ValidationHelper::GroupValidationHelper, type: :helper do
+ describe "validateAdditionalGroupParams" do
+ it 'should accept correct parameters' do
+ result = validateAdditionalGroupParams(1, nil, 1, 4, true, nil)
+ expect(result).to be_empty
+ end
+
+ describe "noOfGroups" do
+ it 'should be required' do
+ result = validateAdditionalGroupParams(nil, nil, 1, 4, true, nil)
+ expect(result[:noOfGroups]).to include 'is required'
+ end
+
+ it 'should be an integer' do
+ result = validateAdditionalGroupParams('waahoo', nil, 1, 4, true, nil)
+ expect(result[:noOfGroups]).to include 'should be an Integer'
+ end
+
+ it 'should range from 1 to 999' do
+ result = validateAdditionalGroupParams(0, nil, 1, 4, true, nil)
+ expect(result[:noOfGroups]).to include 'should range from 1 to 999'
+ result = validateAdditionalGroupParams(1, nil, 1, 4, true, nil)
+ expect(result).to be_empty
+ result = validateAdditionalGroupParams(999, nil, 1, 4, true, nil)
+ expect(result).to be_empty
+ result = validateAdditionalGroupParams(1000, nil, 1, 4, true, nil)
+ expect(result[:noOfGroups]).to include 'should range from 1 to 999'
+ end
+ end
+
+ describe "parentGroupID" do
+ before do
+ @project = FactoryGirl.create(:project)
+ @parent = FactoryGirl.create(:group, project: @project)
+ @project.add_groupIDs([@parent.id.to_s], 0)
+ end
+
+ it 'should be OK with an existent parent' do
+ result = validateAdditionalGroupParams(1, @parent.id.to_s, 1, 4, true, nil)
+ expect(result).to be_empty
+ end
+
+ it 'should reject a non-existent parent' do
+ result = validateAdditionalGroupParams(1, @parent.id.to_s+'missing', 1, 4, true, nil)
+ expect(result[:parentGroupID]).to include "group not found with id #{@parent.id.to_s}missing"
+ end
+ end
+
+ describe "memberOrder" do
+ it 'should not be required if there is no parent' do
+ result = validateAdditionalGroupParams(1, nil, 1, 4, true, nil)
+ expect(result).to be_empty
+ end
+
+ describe 'with parent' do
+ before do
+ @project = FactoryGirl.create(:project)
+ @parent = FactoryGirl.create(:group, project: @project)
+ @project.add_groupIDs([@parent.id.to_s], 0)
+ end
+ it 'should be required' do
+ result = validateAdditionalGroupParams(1, @parent.id.to_s, nil, 4, true, nil)
+ expect(result[:memberOrder]).to include 'is required'
+ end
+ it 'should be an Integer' do
+ result = validateAdditionalGroupParams(1, @parent.id.to_s, 'waahoo', 4, true, nil)
+ expect(result[:memberOrder]).to include 'should be an Integer'
+ end
+ it 'should be greater than 0' do
+ result = validateAdditionalGroupParams(1, @parent.id.to_s, 0, 4, true, nil)
+ expect(result[:memberOrder]).to include 'should be greater than 0'
+ end
+ end
+ end
+
+ describe "noOfLeafs" do
+ it 'should be an integer' do
+ result = validateAdditionalGroupParams(1, nil, 1, 'waahoo', false, nil)
+ expect(result[:noOfLeafs]).to include 'should be an Integer'
+ end
+
+ it 'should range from 1 to 999' do
+ result = validateAdditionalGroupParams(1, nil, 1, 0, false, nil)
+ expect(result[:noOfLeafs]).to include 'should range from 1 to 999'
+ result = validateAdditionalGroupParams(1, nil, 1, 1, false, nil)
+ expect(result).to be_empty
+ result = validateAdditionalGroupParams(1, nil, 1, 999, false, nil)
+ expect(result).to be_empty
+ result = validateAdditionalGroupParams(1, nil, 1, 1000, false, nil)
+ expect(result[:noOfLeafs]).to include 'should range from 1 to 999'
+ end
+ end
+
+ describe "conjoin" do
+ it 'should be a Boolean' do
+ result = validateAdditionalGroupParams(1, nil, 1, 4, 'waahoo', nil)
+ expect(result[:conjoin]).to include 'should be a Boolean'
+ end
+
+ it 'should be false if the number of leaves is 1' do
+ result = validateAdditionalGroupParams(1, nil, 1, 1, true, nil)
+ expect(result[:conjoin]).to include 'should be false if the number of leaves is 1'
+ end
+ end
+
+ describe "oddMemberLeftOut" do
+ it 'should be an integer' do
+ result = validateAdditionalGroupParams(1, nil, 1, 3, true, 'waahoo')
+ expect(result[:oddMemberLeftOut]).to include 'should be an Integer'
+ end
+
+ it 'should range from 1 to the number of leaves' do
+ result = validateAdditionalGroupParams(1, nil, 1, 5, true, 0)
+ expect(result[:oddMemberLeftOut]).to include 'should range from 1 to the number of leaves'
+ result = validateAdditionalGroupParams(1, nil, 1, 5, true, 1)
+ expect(result).to be_empty
+ result = validateAdditionalGroupParams(1, nil, 1, 5, true, 5)
+ expect(result).to be_empty
+ result = validateAdditionalGroupParams(1, nil, 1, 5, true, 6)
+ expect(result[:oddMemberLeftOut]).to include 'should range from 1 to the number of leaves'
+ end
+
+ it 'should be empty if the number of leaves is even' do
+ result = validateAdditionalGroupParams(1, nil, 1, 4, true, 2)
+ expect(result[:oddMemberLeftOut]).to include 'should be empty if the number of leaves is even'
+ end
+ end
+ end
+
+ describe "validateGroupBatchDelete" do
+ before do
+ @project = FactoryGirl.create(:project)
+ @group1 = FactoryGirl.create(:group, project: @project)
+ @group2 = FactoryGirl.create(:group, project: @project)
+ @params = [@group1.id.to_s, @group2.id.to_s]
+ @project.add_groupIDs(@params, 0)
+ end
+
+ it 'should accept correct parameters' do
+ result = validateGroupBatchDelete(@params)
+ expect(result).to be_empty
+ result = validateGroupBatchDelete([@group2.id.to_s])
+ expect(result).to be_empty
+ end
+
+ it 'should pick out missing groups' do
+ @params << @group1.id.to_s+'missing'
+ @params << @group2.id.to_s+'waahoo'
+ result = validateGroupBatchDelete(@params)
+ expect(result).to include "group not found with id #{@group1.id.to_s}missing"
+ expect(result).to include "group not found with id #{@group2.id.to_s}waahoo"
+ end
+ end
+
+ describe "validateGroupBatchUpdate" do
+ before do
+ @project = FactoryGirl.create(:project)
+ @group1 = FactoryGirl.create(:quire, project: @project)
+ @group2 = FactoryGirl.create(:booklet, project: @project)
+ @params = [
+ { id: @group1.id.to_s, attributes: { type: 'Quire' } },
+ { id: @group2.id.to_s, attributes: { type: 'Booklet' } }
+ ]
+ @project.add_groupIDs([@group1.id.to_s, @group2.id.to_s], 0)
+ end
+
+ it 'should accept correct parameters' do
+ result = validateGroupBatchUpdate(@params)
+ expect(result).to be_empty
+ end
+
+ it 'should pick out missing groups' do
+ @params << { id: @group1.id.to_s+'missing', attributes: { type: 'Quire' } }
+ result = validateGroupBatchUpdate(@params)
+ expect(result[0][:id]).to include "group not found with id #{@group1.id.to_s}missing"
+ end
+
+ it 'should pick out bum types' do
+ @params[0][:attributes][:type] = 'UltraWaahoo'
+ result = validateGroupBatchUpdate(@params)
+ expect(result[0][:attributes][:type]).to include 'should be either Quire or Booklet'
+ end
+ end
+end
diff --git a/viscoll-api/spec/helpers/validation_helper/leaf_validation_helper_spec.rb b/viscoll-api/spec/helpers/validation_helper/leaf_validation_helper_spec.rb
new file mode 100644
index 00000000..8dbced9d
--- /dev/null
+++ b/viscoll-api/spec/helpers/validation_helper/leaf_validation_helper_spec.rb
@@ -0,0 +1,145 @@
+require 'rails_helper'
+
+RSpec.describe ValidationHelper::LeafValidationHelper, type: :helper do
+ before :each do
+ @project = FactoryGirl.create(:project)
+ @group = FactoryGirl.create(:group, project: @project)
+ @project.add_groupIDs([@group.id.to_s], 0)
+ end
+
+ let(:project_id) { @project.id.to_s }
+ let(:group_id) { @group.id.to_s }
+
+ describe 'validateLeafParams' do
+ it 'should accept correct parameters' do
+ result = validateLeafParams(project_id, group_id)
+ expect(result[:project_id]).to be_empty
+ expect(result[:parentID]).to be_empty
+ end
+
+ describe 'Project' do
+ it 'should be required' do
+ result = validateLeafParams(nil, group_id)
+ expect(result[:project_id]).to include 'is required'
+ end
+ it 'should be a string' do
+ result = validateLeafParams(3, group_id)
+ expect(result[:project_id]).to include 'should be a String'
+ end
+ it 'should exist' do
+ result = validateLeafParams(project_id+'missing', group_id)
+ expect(result[:project_id]).to include 'project not found'
+ end
+ end
+
+ describe 'Parent' do
+ it 'should be required' do
+ result = validateLeafParams(project_id, nil)
+ expect(result[:parentID]).to include 'is required'
+ end
+ it 'should be a string' do
+ result = validateLeafParams(project_id, 3)
+ expect(result[:parentID]).to include 'should be a String'
+ end
+ it 'should belong to the same project' do
+ project2 = FactoryGirl.create(:project)
+ group2 = FactoryGirl.create(:group, project: project2)
+ project2.add_groupIDs([group2.id.to_s], 0)
+ result = validateLeafParams(project_id, group2.id.to_s)
+ expect(result[:parentID]).to include 'Group with parentID does not have project_id as a member'
+ end
+ it 'should exist' do
+ result = validateLeafParams(project_id, group_id+'missing')
+ expect(result[:parentID]).to include 'group not found'
+ end
+ end
+ end
+
+ describe 'validateAdditionalLeafParams' do
+ it 'should accept correct parameters' do
+ result = validateAdditionalLeafParams(project_id, group_id, 1, 12, true, nil)
+ expect(result[:memberOrder]).to be_empty
+ expect(result[:noOfLeafs]).to be_empty
+ expect(result[:conjoin]).to be_empty
+ expect(result[:oddMemberLeftOut]).to be_empty
+ end
+
+ describe 'memberOrder' do
+ it 'should be required' do
+ result = validateAdditionalLeafParams(project_id, group_id, nil, 12, true, nil)
+ expect(result[:memberOrder]).to include 'is required'
+ end
+
+ it 'should be an integer' do
+ result = validateAdditionalLeafParams(project_id, group_id, 'waahoo', 12, true, nil)
+ expect(result[:memberOrder]).to include 'should be an Integer'
+ end
+
+ it 'should be positive' do
+ result = validateAdditionalLeafParams(project_id, group_id, 0, 12, true, nil)
+ expect(result[:memberOrder]).to include 'should be greater than 0'
+ end
+ end
+
+ describe 'noOfLeafs' do
+ it 'should be required' do
+ result = validateAdditionalLeafParams(project_id, group_id, 1, nil, true, nil)
+ expect(result[:noOfLeafs]).to include 'is required'
+ end
+
+ it 'should be an integer' do
+ result = validateAdditionalLeafParams(project_id, group_id, 1, 'waahoo', true, nil)
+ expect(result[:noOfLeafs]).to include 'should be an Integer'
+ end
+
+ it 'should be positive' do
+ result = validateAdditionalLeafParams(project_id, group_id, 1, 0, true, nil)
+ expect(result[:noOfLeafs]).to include 'should range from 1 to 999'
+ result = validateAdditionalLeafParams(project_id, group_id, 1, 1, true, nil)
+ expect(result[:noOfLeafs]).to be_empty
+ end
+
+ it 'should be at most 3 digits' do
+ result = validateAdditionalLeafParams(project_id, group_id, 1, 999, true, nil)
+ expect(result[:noOfLeafs]).to be_empty
+ result = validateAdditionalLeafParams(project_id, group_id, 1, 1000, true, nil)
+ expect(result[:noOfLeafs]).to include 'should range from 1 to 999'
+ end
+ end
+
+ describe 'conjoin' do
+ it 'should be a Boolean if present' do
+ result = validateAdditionalLeafParams(project_id, group_id, 1, 12, 'waahoo', nil)
+ expect(result[:conjoin]).to include 'should be a Boolean'
+ end
+
+ it 'should be false if noOfLeafs is 1' do
+ result = validateAdditionalLeafParams(project_id, group_id, 1, 1, true, nil)
+ expect(result[:conjoin]).to include 'should be false if the number of leaves is 1'
+ end
+ end
+
+ describe 'oddMemberLeftOut' do
+ it 'should be an integer if present' do
+ result = validateAdditionalLeafParams(project_id, group_id, 1, 11, true, 'waahoo')
+ expect(result[:oddMemberLeftOut]).to include 'should be an Integer'
+ end
+
+ it 'should be strictly between 0 and noOfLeafs' do
+ result = validateAdditionalLeafParams(project_id, group_id, 1, 11, true, 0)
+ expect(result[:oddMemberLeftOut]).to include 'should range from 1 to the number of leaves'
+ result = validateAdditionalLeafParams(project_id, group_id, 1, 11, true, 1)
+ expect(result[:oddMemberLeftOut]).to be_empty
+ result = validateAdditionalLeafParams(project_id, group_id, 1, 11, true, 11)
+ expect(result[:oddMemberLeftOut]).to be_empty
+ result = validateAdditionalLeafParams(project_id, group_id, 1, 11, true, 12)
+ expect(result[:oddMemberLeftOut]).to include 'should range from 1 to the number of leaves'
+ end
+
+ it 'should be empty if noOfLeafs is even' do
+ result = validateAdditionalLeafParams(project_id, group_id, 1, 12, true, 2)
+ expect(result[:oddMemberLeftOut]).to include 'should be present only if the number of leaves is odd'
+ end
+ end
+ end
+end
diff --git a/viscoll-api/spec/helpers/validation_helper/project_validation_helper_spec.rb b/viscoll-api/spec/helpers/validation_helper/project_validation_helper_spec.rb
new file mode 100644
index 00000000..7a326e7b
--- /dev/null
+++ b/viscoll-api/spec/helpers/validation_helper/project_validation_helper_spec.rb
@@ -0,0 +1,72 @@
+require 'rails_helper'
+
+RSpec.describe ValidationHelper::ProjectValidationHelper, type: :helper do
+ describe "validateProjectCreateGroupsParams" do
+ before :each do
+ @params = [
+ { 'leaves' => 3, 'oddLeaf' => 1, 'conjoin' => false },
+ { 'leaves' => 6, 'oddLeaf' => 0, 'conjoin' => true }
+ ]
+ end
+
+ it 'should allow nil' do
+ result = validateProjectCreateGroupsParams(nil)
+ expect(result[:errors]).to be_empty
+ expect(result[:status]).to be true
+ end
+
+ it 'should allow the standard params' do
+ result = validateProjectCreateGroupsParams(@params)
+ expect(result[:errors]).to be_empty
+ expect(result[:status]).to be true
+ end
+
+ describe "Leaf count" do
+ it 'should be integers only' do
+ @params[0]['leaves'] = 'waahoo'
+ result = validateProjectCreateGroupsParams(@params)
+ expect(result[:errors][0][:leaves]).to include 'should be an Integer'
+ expect(result[:status]).to be false
+ end
+
+ it 'should be positive' do
+ @params[1]['leaves'] = 0
+ result = validateProjectCreateGroupsParams(@params)
+ expect(result[:errors][0][:leaves]).to include 'should be greater than 0'
+ expect(result[:status]).to be false
+ end
+ end
+
+ describe "Odd leaf parity" do
+ it 'should be integers only' do
+ @params[0]['oddLeaf'] = 'waahoo'
+ result = validateProjectCreateGroupsParams(@params)
+ expect(result[:errors][0][:oddLeaf]).to include 'should be an Integer'
+ expect(result[:status]).to be false
+ end
+
+ it 'should be positive' do
+ @params[0]['oddLeaf'] = 0
+ result = validateProjectCreateGroupsParams(@params)
+ expect(result[:errors][0][:oddLeaf]).to include 'should be greater than 0'
+ expect(result[:status]).to be false
+ end
+
+ it 'should not be greater than leaves' do
+ @params[0]['oddLeaf'] = 7
+ result = validateProjectCreateGroupsParams(@params)
+ expect(result[:errors][0][:oddLeaf]).to include 'cannot be greater than leaves'
+ expect(result[:status]).to be false
+ end
+ end
+
+ describe "Conjoin" do
+ it 'should be Boolean' do
+ @params[1]['conjoin'] = 'waahoo'
+ result = validateProjectCreateGroupsParams(@params)
+ expect(result[:errors][0][:conjoin]).to include 'should be a Boolean'
+ expect(result[:status]).to be false
+ end
+ end
+ end
+end
diff --git a/viscoll-api/spec/mailers/feedback_spec.rb b/viscoll-api/spec/mailers/feedback_spec.rb
new file mode 100644
index 00000000..b386360a
--- /dev/null
+++ b/viscoll-api/spec/mailers/feedback_spec.rb
@@ -0,0 +1,22 @@
+require "rails_helper"
+
+RSpec.describe FeedbackMailer, type: :mailer do
+ context 'user submits a feedback' do
+ before do
+ @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
+ end
+
+ let(:mail) { FeedbackMailer.sendFeedback("Title of feedback", "My message", nil, nil, @user.id)}
+
+ it "should send email" do
+ expect(mail.subject).to eq("Title of feedback")
+ expect(mail.to).to eq(["utlviscoll@library.utoronto.ca"])
+ end
+
+ it "should render body" do
+ expect(mail.body.raw_source).to include("My message")
+ expect(mail.body.raw_source).to include(@user.name)
+ expect(mail.body.raw_source).to include(@user.email)
+ end
+ end
+end
diff --git a/viscoll-api/spec/mailers/previews/feedback_preview.rb b/viscoll-api/spec/mailers/previews/feedback_preview.rb
new file mode 100644
index 00000000..41dfdef5
--- /dev/null
+++ b/viscoll-api/spec/mailers/previews/feedback_preview.rb
@@ -0,0 +1,4 @@
+# Preview all emails at http://localhost:3000/rails/mailers/feedback
+class FeedbackPreview < ActionMailer::Preview
+
+end
diff --git a/viscoll-api/spec/models/group_spec.rb b/viscoll-api/spec/models/group_spec.rb
new file mode 100644
index 00000000..8ce7f7b5
--- /dev/null
+++ b/viscoll-api/spec/models/group_spec.rb
@@ -0,0 +1,106 @@
+require 'rails_helper'
+
+RSpec.describe Group, type: :model do
+ it { is_expected.to be_mongoid_document }
+
+ it { is_expected.to have_field(:title).of_type(String) }
+ it { is_expected.to have_field(:type).of_type(String) }
+ it { is_expected.to have_field(:tacketed).of_type(Array) }
+ it { is_expected.to have_field(:sewing).of_type(Array) }
+ it { is_expected.to have_field(:nestLevel).of_type(Integer) }
+ it { is_expected.to have_field(:parentID).of_type(String) }
+ it { is_expected.to have_field(:memberIDs).of_type(Array) }
+
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_and_belong_to_many(:notes) }
+
+ before(:each) do
+ @project = FactoryGirl.create(:project)
+ @group = FactoryGirl.create(:group, project: @project)
+ @project.add_groupIDs([@group.id], 0)
+ end
+
+ describe "Initialization" do
+ it "should prefix its ID" do
+ expect(@group.id.to_s[0..5]).to eq "Group_"
+ end
+ end
+
+ describe "Member handling" do
+ it "should add member IDs" do
+ @group.add_members(['abcd', 'efgh'], 0)
+ expect(@group.memberIDs).to eq ['abcd', 'efgh']
+ end
+
+ it "should add additional member IDs" do
+ @group.add_members(['abcd', 'efgh', 'ijkl'], 0)
+ expect(@group.memberIDs).to eq ['abcd', 'efgh', 'ijkl']
+ @group.add_members(['1234', '5678'], 3)
+ expect(@group.memberIDs).to eq ['abcd', 'efgh', '1234', '5678', 'ijkl']
+ end
+
+ it "should respect the save flag" do
+ @group.add_members(['abcd', 'efgh', 'ijkl'], 0)
+ expect(@group.memberIDs).to eq ['abcd', 'efgh', 'ijkl']
+ @group.add_members(['1234', '5678'], 3, false)
+ expect(@group.memberIDs).to eq ['abcd', 'efgh', '1234', '5678', 'ijkl']
+ @group.reload
+ expect(@group.memberIDs).to eq ['abcd', 'efgh', 'ijkl']
+ end
+
+ it "should remove member IDs" do
+ @group.add_members(['abcd', 'efgh', 'ijkl'], 0)
+ expect(@group.memberIDs).to eq ['abcd', 'efgh', 'ijkl']
+ @group.remove_members(['abcd', 'ijkl'])
+ expect(@group.memberIDs).to eq ['efgh']
+ end
+ end
+
+ describe "On-destroy hooks" do
+ it "should remove itself from an associated note" do
+ note = FactoryGirl.create(:note, project: @project, objects: {Group: [@group.id], Leaf: [], Recto: [], Verso: []})
+ @group.notes << note
+ @group.save
+ @group.destroy
+ expect(note.objects[:Group]).to be_empty
+ end
+
+ it "should remove itself from an associated project" do
+ @group.destroy
+ expect(@project.groupIDs).to be_empty
+ end
+
+ it "should remove itself from a parent group" do
+ parent_group = FactoryGirl.create(:group, project: @project)
+ @project.add_groupIDs([parent_group.id.to_s], 0)
+ @group.parentID = parent_group.id
+ parent_group.add_members([@group.id.to_s], 0)
+ @group.save
+ expect(parent_group.memberIDs).not_to be_empty
+ @group.destroy
+ parent_group.reload
+ expect(parent_group.memberIDs).to be_empty
+ end
+
+ it "should remove its members" do
+ subgroup = FactoryGirl.create(:group, project: @project)
+ subgroup_id = subgroup.id
+ @project.add_groupIDs([subgroup.id.to_s], 0)
+ subgroup.parentID = @group.id
+ @group.add_members([subgroup.id.to_s], 0)
+ subgroup.save
+ expect(@group.memberIDs).to include(subgroup.id.to_s)
+
+ subleaf = FactoryGirl.create(:leaf, project: @project)
+ subleaf_id = subleaf.id
+ @group.add_members([subleaf.id.to_s], 0)
+ subleaf.parentID = @group.id.to_s
+ subleaf.save
+ expect(@group.memberIDs).to include(subleaf.id.to_s)
+
+ @group.destroy
+ expect(Group.where(id: subgroup_id).exists?).to be false
+ expect(Leaf.where(id: subleaf_id).exists?).to be false
+ end
+ end
+end
diff --git a/viscoll-api/spec/models/grouping_spec.rb b/viscoll-api/spec/models/grouping_spec.rb
new file mode 100644
index 00000000..b5e13381
--- /dev/null
+++ b/viscoll-api/spec/models/grouping_spec.rb
@@ -0,0 +1,21 @@
+# require 'rails_helper'
+
+# RSpec.describe Grouping, type: :model do
+# it { is_expected.to be_mongoid_document }
+# it { is_expected.to have_field(:order).of_type(Integer) }
+# it { is_expected.to belong_to(:group).with_foreign_key(:group_id) }
+# it { is_expected.to belong_to(:member).with_foreign_key(:member_id) }
+
+# before(:each) do
+# @project = FactoryGirl.create(:project)
+# @leaf = FactoryGirl.create(:leaf, project: @project)
+# @group = FactoryGirl.create(:quire)
+# end
+
+# it "can delete a member" do
+# @group.add_member(@leaf, 1)
+# @leaf.destroy
+# expect(@group.get_members.size).to eq 0
+# end
+
+# end
\ No newline at end of file
diff --git a/viscoll-api/spec/models/image_spec.rb b/viscoll-api/spec/models/image_spec.rb
new file mode 100644
index 00000000..5eb71eba
--- /dev/null
+++ b/viscoll-api/spec/models/image_spec.rb
@@ -0,0 +1,46 @@
+require 'rails_helper'
+
+RSpec.describe Image, type: :model do
+ it { is_expected.to be_mongoid_document }
+
+ it { is_expected.to have_field(:filename).of_type(String) }
+ it { is_expected.to have_field(:projectIDs).of_type(Array) }
+ it { is_expected.to have_field(:sideIDs).of_type(Array) }
+
+ it { is_expected.to belong_to(:user) }
+
+ before(:each) do
+ @user = FactoryGirl.create(:user)
+ @project = FactoryGirl.create(:codex_project, user: @user, quire_structure: [[1, 2]])
+ @image = FactoryGirl.create(:image, user: @user)
+ end
+
+ describe 'Validations' do
+ it 'should be valid to start with' do
+ expect(@image).to be_valid
+ end
+
+ it 'should not be valid with a duplicate file name' do
+ duplicate_image = FactoryGirl.build(:image, user: @user, filename: @image.filename)
+ expect(duplicate_image).not_to be_valid
+ end
+ end
+
+ describe 'Side unlinking hook' do
+ before do
+ @side = @project.sides[1]
+ @side.update(image: {
+ manifestID: 'DIYImages',
+ label: 'hello.png',
+ url: 'http://127.0.0.1:3001/pixel.png'
+ })
+ @image.update(sideIDs: [@side.id.to_s])
+ end
+
+ it 'should unhook the side upon deletion' do
+ @image.destroy
+ @side.reload
+ expect(@side.image).to be_blank
+ end
+ end
+end
diff --git a/viscoll-api/spec/models/leaf_spec.rb b/viscoll-api/spec/models/leaf_spec.rb
new file mode 100644
index 00000000..8f9ac8ce
--- /dev/null
+++ b/viscoll-api/spec/models/leaf_spec.rb
@@ -0,0 +1,68 @@
+require 'rails_helper'
+
+RSpec.describe Leaf, type: :model do
+ it { is_expected.to be_mongoid_document }
+
+ it { is_expected.to have_field(:material).of_type(String) }
+ it { is_expected.to have_field(:type).of_type(String) }
+ it { is_expected.to have_field(:conjoined_to).of_type(String) }
+ it { is_expected.to have_field(:attached_above).of_type(String) }
+ it { is_expected.to have_field(:attached_below).of_type(String) }
+ it { is_expected.to have_field(:stubType).of_type(String) }
+ it { is_expected.to have_field(:parentID).of_type(String) }
+ it { is_expected.to have_field(:nestLevel).of_type(Integer) }
+ it { is_expected.to have_field(:rectoID).of_type(String) }
+ it { is_expected.to have_field(:versoID).of_type(String) }
+
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_and_belong_to_many(:notes) }
+
+ before(:each) do
+ @project = FactoryGirl.create(:project)
+ @leaf = FactoryGirl.create(:leaf, project: @project)
+ @group = FactoryGirl.create(:group, project: @project)
+ @group.add_members([@leaf.id.to_s], 0)
+ @leaf.parentID = @group.id
+ @leaf.save
+ end
+
+ describe "Initialization" do
+ it "should have a prefixed ID" do
+ expect(@leaf.id.to_s[0..4]).to eq "Leaf_"
+ end
+
+ it "should add two sides" do
+ expect(Side.where(id: @leaf.rectoID).exists?).to be true
+ expect(Side.where(id: @leaf.versoID).exists?).to be true
+ end
+ end
+
+ it "should be able to unlink itself from a group" do
+ @group = FactoryGirl.create(:group, project: @project)
+ @group.add_members([@leaf.id.to_s], 0)
+ @leaf.parentID = @group.id
+ @leaf.save
+ expect(@group.memberIDs).to include(@leaf.id.to_s)
+ @leaf.remove_from_group
+ @group.reload
+ expect(@group.memberIDs).not_to include(@leaf.id.to_s)
+ end
+
+ describe "Destruction" do
+ it "should unlink its notes" do
+ subnote = FactoryGirl.create(:note, project: @project, objects: {Group: [], Leaf: [@leaf.id], Recto: [], Verso: []})
+ @leaf.notes << subnote
+ @leaf.save
+ @leaf.destroy
+ expect(subnote.objects[:Leaf]).to be_empty
+ end
+
+ it "should destroy its sides" do
+ rectoId = @leaf.rectoID
+ versoId = @leaf.versoID
+ @leaf.destroy
+ expect(Side.where(id: rectoId).exists?).to be false
+ expect(Side.where(id: versoId).exists?).to be false
+ end
+ end
+end
diff --git a/viscoll-api/spec/models/note_spec.rb b/viscoll-api/spec/models/note_spec.rb
new file mode 100644
index 00000000..2e502605
--- /dev/null
+++ b/viscoll-api/spec/models/note_spec.rb
@@ -0,0 +1,77 @@
+require 'rails_helper'
+
+RSpec.describe Note, type: :model do
+ it { is_expected.to be_mongoid_document }
+
+ it { is_expected.to have_field(:title).of_type(String) }
+ it { is_expected.to have_field(:type).of_type(String) }
+ it { is_expected.to have_field(:description).of_type(String) }
+ it { is_expected.to have_field(:objects).of_type(Hash) }
+ it { is_expected.to have_field(:show).of_type(Mongoid::Boolean) }
+
+ it { is_expected.to belong_to(:project) }
+
+ before :each do
+ @project = FactoryGirl.create(:project, noteTypes: ['Ink'])
+ @group = FactoryGirl.create(:group, project: @project)
+ @leaf = FactoryGirl.create(:leaf, project: @project, parentID: @group.id.to_s)
+ @side1 = Side.find(id: @leaf.rectoID)
+ @side2 = Side.find(id: @leaf.versoID)
+ @project.add_groupIDs([@group.id.to_s], 0)
+ @group.add_members([@leaf.id.to_s], 0)
+ @note = FactoryGirl.create(:note, project: @project, type: ['Ink'], objects: {Group: [@group.id.to_s], Leaf: [@leaf.id.to_s], Recto: [@side1.id.to_s], Verso: [@side2.id.to_s]} )
+ @group.notes << @note
+ @group.save
+ @leaf.notes << @note
+ @leaf.save
+ @side1.notes << @note
+ @side1.save
+ @side2.notes << @note
+ @side2.save
+ end
+
+ describe "Validations" do
+ it "should require a title" do
+ @note.title = ''
+ expect(@note).not_to be_valid
+ end
+ it "should be unique within the project" do
+ duplicate_note = FactoryGirl.create(:note, project: @project, type: ['Ink'], objects: {Group: [@group.id.to_s], Leaf: [], Recto: [], Verso: []} )
+ duplicate_note.title = @note.title
+ expect(duplicate_note).not_to be_valid
+ end
+ it "should not need to be unique globally" do
+ project2 = FactoryGirl.create(:project)
+ group2 = FactoryGirl.create(:group, project: project2)
+ project2.add_groupIDs([group2.id.to_s], 0)
+ note2 = FactoryGirl.create(:note, project: project2, type: ['Ink'], objects: {Group: [group2.id.to_s], Leaf: [], Recto: [], Verso: []})
+ expect(note2).to be_valid
+ end
+ it "should require a type" do
+ @note.type = ''
+ expect(@note).not_to be_valid
+ end
+ end
+
+ describe "Destroy hooks" do
+ before do
+ @note.destroy
+ end
+ it "updates linked group" do
+ @group.reload
+ expect(@group.notes).to be_empty
+ end
+ it "updates linked leaf" do
+ @leaf.reload
+ expect(@leaf.notes).to be_empty
+ end
+ it "updates linked recto side" do
+ @side1.reload
+ expect(@side1.notes).to be_empty
+ end
+ it "updates linked verso side" do
+ @side2.reload
+ expect(@side2.notes).to be_empty
+ end
+ end
+end
diff --git a/viscoll-api/spec/models/project_spec.rb b/viscoll-api/spec/models/project_spec.rb
new file mode 100644
index 00000000..e0d3b2d1
--- /dev/null
+++ b/viscoll-api/spec/models/project_spec.rb
@@ -0,0 +1,78 @@
+require 'rails_helper'
+
+RSpec.describe Project, type: :model do
+ it { is_expected.to be_mongoid_document }
+
+ it { is_expected.to have_field(:title).of_type(String) }
+ it { is_expected.to have_field(:shelfmark).of_type(String) }
+ it { is_expected.to have_field(:metadata).of_type(Hash) }
+ it { is_expected.to have_field(:manifests).of_type(Hash) }
+ it { is_expected.to have_field(:noteTypes).of_type(Array) }
+ it { is_expected.to have_field(:preferences).of_type(Hash) }
+ it { is_expected.to have_field(:groupIDs).of_type(Array) }
+
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to have_many(:groups) }
+ it { is_expected.to have_many(:leafs) }
+ it { is_expected.to have_many(:sides) }
+ it { is_expected.to have_many(:notes) }
+
+ before(:each) do
+ @user = FactoryGirl.create(:user)
+ @project = FactoryGirl.create(:project, user: @user)
+ end
+
+ describe "Validations" do
+ it "should require a title" do
+ @project.title = ''
+ expect(@project).not_to be_valid
+ end
+
+ it "should be unique to the same user" do
+ @duplicated_project = FactoryGirl.create(:project, user: @user)
+ @project.title = @duplicated_project.title
+ expect(@project).not_to be_valid
+ end
+
+ it "can be duplicated for different users" do
+ @user2 = FactoryGirl.create(:user)
+ @duplicated_project = FactoryGirl.create(:project, user: @user2)
+ @project.title = @duplicated_project.title
+ expect(@project).to be_valid
+ end
+ end
+
+ describe "Group IDs" do
+ it "should add group IDs properly" do
+ @project.add_groupIDs(['abcd', 'efgh'], 0)
+ expect(@project.groupIDs).to eq ['abcd', 'efgh']
+ end
+
+ it "should insert group IDs properly" do
+ @project.add_groupIDs(['abcd', 'efgh', 'ijkl'], 0)
+ @project.add_groupIDs(['1234', '5678'], 1)
+ expect(@project.groupIDs).to eq ['abcd', '1234', '5678', 'efgh', 'ijkl']
+ end
+
+ it "should remove group IDs properly" do
+ @project.add_groupIDs(['abcd', 'efgh', 'ijkl'], 0)
+ @project.remove_groupID('efgh')
+ expect(@project.groupIDs).to eq ['abcd', 'ijkl']
+ end
+ end
+
+ describe "Image unlinking hook" do
+ before do
+ @project1 = FactoryGirl.create(:codex_project, user: @user, quire_structure: [[1,2]])
+ @project2 = FactoryGirl.create(:codex_project, user: @user, quire_structure: [[1,2]])
+ @image = FactoryGirl.create(:pixel, user: @user, projectIDs: [@project1.id.to_s, @project2.id.to_s], sideIDs: [@project1.sides[0].id.to_s, @project2.sides[0].id.to_s])
+ end
+
+ it 'should unhook from deleted project and sides' do
+ @project2.destroy!
+ @image.reload
+ expect(@image.projectIDs).to eq [@project1.id.to_s]
+ expect(@image.sideIDs).to eq [@project1.sides[0].id.to_s]
+ end
+ end
+end
diff --git a/viscoll-api/spec/models/side_spec.rb b/viscoll-api/spec/models/side_spec.rb
new file mode 100644
index 00000000..71e92ad3
--- /dev/null
+++ b/viscoll-api/spec/models/side_spec.rb
@@ -0,0 +1,43 @@
+require 'rails_helper'
+
+RSpec.describe Side, type: :model do
+ it { is_expected.to be_mongoid_document }
+
+ it { is_expected.to have_field(:folio_number).of_type(String) }
+ it { is_expected.to have_field(:texture).of_type(String) }
+ it { is_expected.to have_field(:script_direction).of_type(String) }
+ it { is_expected.to have_field(:image).of_type(Hash) }
+ it { is_expected.to have_field(:parentID).of_type(String) }
+
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_and_belong_to_many(:notes) }
+
+ before :each do
+ @user = FactoryGirl.create(:user)
+ @project = FactoryGirl.create(:project, user: @user)
+ @leaf = FactoryGirl.create(:leaf, project: @project)
+ @side = Side.find(id: @leaf.rectoID)
+ end
+
+ describe "Destruction hooks" do
+ it "should unlink attached notes" do
+ note = FactoryGirl.create(:note, project: @project, objects: {Group: [], Leaf: [], Recto: [@side.id.to_s], Verso: []} )
+ note2 = FactoryGirl.create(:note, project: @project, objects: {Group: [], Leaf: [], Recto: [], Verso: [@side.id.to_s]} )
+ @side.notes << [note, note2]
+ @side.save
+ expect(@side.notes).to include note
+ expect(@side.notes).to include note2
+ @side.destroy
+ expect(note.objects[:Recto]).to be_empty
+ expect(note2.objects[:Verso]).to be_empty
+ end
+
+ it "should unlink attached image" do
+ image = FactoryGirl.create(:pixel, user: @user, filename: 'pixel.png', projectIDs: [@project.id.to_s], sideIDs: [@side.id.to_s])
+ @side.update(image: { url: "http://127.0.0.1:12345/images/#{image.id}_pixel.png", label: 'Pixel', manifestID: 'DIYImages' })
+ @side.destroy
+ image.reload
+ expect(image.sideIDs).to be_empty
+ end
+ end
+end
diff --git a/spec/rails_helper.rb b/viscoll-api/spec/rails_helper.rb
similarity index 68%
rename from spec/rails_helper.rb
rename to viscoll-api/spec/rails_helper.rb
index 73adb1cb..9de7ea4c 100644
--- a/spec/rails_helper.rb
+++ b/viscoll-api/spec/rails_helper.rb
@@ -1,11 +1,21 @@
+# require database cleaner at the top level
+require 'database_cleaner'
+
# This file is copied to spec/ when you run 'rails generate rspec:install'
+require 'spec_helper'
+
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
+
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
-require 'spec_helper'
+
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
+require 'mongoid-rspec'
+require 'rails_jwt_auth/spec/helpers'
+
+# Rails.application.eager_load!
# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
@@ -22,6 +32,14 @@
#
# Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
+# configure shoulda matchers to use rspec as the test framework and full matcher libraries for rails
+Shoulda::Matchers.configure do |config|
+ config.integrate do |with|
+ with.test_framework :rspec
+ with.library :rails
+ end
+end
+
RSpec.configure do |config|
# RSpec Rails can automatically mix in different behaviours to your tests
# based on their file location, for example enabling you to call `get` and
@@ -42,4 +60,25 @@
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
+
+ # add `FactoryGirl` methods
+ config.include FactoryGirl::Syntax::Methods
+
+ # add 'Mongoid' matchers
+ config.include Mongoid::Matchers, type: :model
+
+ # add 'WardenHelper'
+ config.include RailsJwtAuth::Spec::Helpers, :type => :request
+
+ # start by truncating all the tables but then use the faster transaction strategy the rest of the time.
+ config.before(:suite) do
+ DatabaseCleaner.clean_with(:truncation)
+ end
+
+ # start the transaction strategy as examples are run
+ config.around(:each) do |example|
+ DatabaseCleaner.cleaning do
+ example.run
+ end
+ end
end
diff --git a/viscoll-api/spec/requests/authentication/delete_session_spec.rb b/viscoll-api/spec/requests/authentication/delete_session_spec.rb
new file mode 100644
index 00000000..e6a21893
--- /dev/null
+++ b/viscoll-api/spec/requests/authentication/delete_session_spec.rb
@@ -0,0 +1,73 @@
+require 'rails_helper'
+
+describe "DELETE /session", :type => :request do
+ context 'without token in header' do
+ before do
+ @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
+ @user.confirmation_token = nil
+ @user.confirmed_at = "2017-07-12T16:08:25.278Z"
+ @user.save
+ delete '/session'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'with token in header' do
+ before do
+ @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
+ @user.confirmation_token = nil
+ @user.confirmed_at = "2017-07-12T16:08:25.278Z"
+ @user.save
+ end
+
+ context 'and token is invalid' do
+ before do
+ post '/session', params: {:session => { :email=> "user@mail.com", :password => "user" }}
+ authToken = JSON.parse(response.body)['session']['jwt']+"someInvalidStuff"
+ delete '/session', params: '', headers: {'Authorization' => authToken}
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Header: Signature verification raised')
+ end
+ end
+
+ context 'and token format is wrong' do
+ before do
+ post '/session', params: {:session => { :email=> "user@mail.com", :password => "user" }}
+ delete '/session', params: '', headers: {'Authorization' => "invalidTokenFormat"}
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Header: Not enough or too many segments')
+ end
+ end
+
+ context 'and token is valid' do
+ before do
+ post '/session', params: {:session => { :email=> "user@mail.com", :password => "user" }}
+ authToken = JSON.parse(response.body)['session']['jwt']
+ delete '/session', params: '', headers: {'Authorization' => authToken}
+ end
+
+ it 'returns 204 no content response' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'clears the auth tokens of the user' do
+ expect(User.find(@user.id).auth_tokens).to be_empty
+ end
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/authentication/post_password_spec.rb b/viscoll-api/spec/requests/authentication/post_password_spec.rb
new file mode 100644
index 00000000..389a5902
--- /dev/null
+++ b/viscoll-api/spec/requests/authentication/post_password_spec.rb
@@ -0,0 +1,58 @@
+require 'rails_helper'
+
+describe "POST /password", :type => :request do
+ context 'with valid params' do
+ before do
+ @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
+ @user.confirmation_token = nil
+ @user.confirmed_at = "2017-07-12T16:08:25.278Z"
+ @user.save
+ post '/password', params: {:password => {:email => "user@mail.com"}}
+ end
+
+ it 'returns a successful no_content response' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'creates fields for reset_password in user record' do
+ expect(User.find(@user.id).reset_password_token).not_to eq(nil)
+ expect(User.find(@user.id).reset_password_sent_at).not_to eq(nil)
+ end
+ end
+
+ context 'with invalid params' do
+ context 'and unconfirmed user' do
+ before do
+ @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
+ post '/password', params: {:password => {:email => "user@mail.com"}}
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['errors']['email']).to eq(['unconfirmed email'])
+ end
+
+ it 'doest not create fields for reset_password in user record' do
+ expect(User.find(@user.id).reset_password_token).to eq(nil)
+ expect(User.find(@user.id).reset_password_sent_at).to eq(nil)
+ end
+ end
+
+ context 'and no valid user' do
+ before do
+ post '/password', params: {:password => {:email => "user@mail.com"}}
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['errors']['email']).to eq(['not found'])
+ end
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/authentication/post_registration_spec.rb b/viscoll-api/spec/requests/authentication/post_registration_spec.rb
new file mode 100644
index 00000000..324c393c
--- /dev/null
+++ b/viscoll-api/spec/requests/authentication/post_registration_spec.rb
@@ -0,0 +1,125 @@
+require 'rails_helper'
+
+describe "POST /registration", :type => :request do
+ context 'with valid params' do
+ before do
+ post '/registration', params: {:user => { :email=> "user@mail.com", :password => "user", :name=>"user" }}
+ end
+
+ it 'returns with a successful 200 response' do
+ expect(response).to have_http_status(:created)
+ end
+
+ it 'returns an user object in the response body' do
+ expect(JSON.parse(response.body)['user']).not_to be_empty
+ expect(JSON.parse(response.body)['user']['email']).to eq('user@mail.com')
+ expect(JSON.parse(response.body)['user']['name']).to eq('user')
+ end
+
+ it 'returns an email confirmation token with the response body' do
+ expect(JSON.parse(response.body)['user']['confirmation_token']).not_to be_empty
+ expect(JSON.parse(response.body)['user']['confirmation_sent_at']).not_to be_empty
+ end
+
+ it 'creates an User object in the database' do
+ expect(User.count).to eq(1)
+ end
+ end
+
+ context 'with invalid params' do
+ before do
+ @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
+ @user.confirmation_token = nil
+ @user.confirmed_at = "2017-07-12T16:08:25.278Z"
+ @user.save
+ end
+
+ context 'where email is empty' do
+ before do
+ post '/registration', params: {:user => { :email=> "", :password => "newUser", :name=>"newUser" }}
+ end
+
+ it 'returns an appropriate error message with 422 code' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(JSON.parse(response.body)['errors']['email']).to eq(['can\'t be blank', 'is not an email'])
+ end
+
+ it 'does not create another User object in the database' do
+ expect(User.count).to eq(1)
+ end
+ end
+
+ context 'where email is invalid' do
+ before do
+ post '/registration', params: {:user => { :email=> "ghost", :password => "newUser", :name=>"newUser" }}
+ end
+
+ it 'returns an appropriate error message with 422 code' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(JSON.parse(response.body)['errors']['email']).to eq(['is not an email'])
+ end
+
+ it 'does not create another User object in the database' do
+ expect(User.count).to eq(1)
+ end
+ end
+
+ context 'where email is already taken' do
+ before do
+ post '/registration', params: {:user => { :email=> "user@mail.com", :password => "user", :name=>"user" }}
+ end
+
+ it 'returns an appropriate error message with 422 code' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(JSON.parse(response.body)['errors']['email']).to eq(['is already taken'])
+ end
+
+ it 'does not create another User object in the database' do
+ expect(User.count).to eq(1)
+ end
+ end
+
+ context 'where password is empty' do
+ before do
+ post '/registration', params: {:user => { :email=> "newUser@mail.com", :password => "", :name=>"newUser" }}
+ end
+
+ it 'returns an appropriate error message with 422 code' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(JSON.parse(response.body)['errors']['password']).to eq(['can\'t be blank'])
+ end
+
+ it 'does not create another User object in the database' do
+ expect(User.count).to eq(1)
+ end
+ end
+
+ context 'where email and password are invalid' do
+ before do
+ post '/registration', params: {:user => { :email=> "ghost", :password => "", :name=>"newUser" }}
+ end
+
+ it 'returns an appropriate error message with 422 code' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(JSON.parse(response.body)['errors']['email']).to eq(['is not an email'])
+ expect(JSON.parse(response.body)['errors']['password']).to eq(['can\'t be blank'])
+ end
+
+ it 'does not create another User object in the database' do
+ expect(User.count).to eq(1)
+ end
+ end
+
+ context 'where an exception is thrown' do
+ before do
+ allow_any_instance_of(RailsJwtAuth.model).to receive(:save).and_raise('Exception')
+ post '/registration', params: {:user => { :email=> "user@mail.com", :password => "user", :name=>"user" }}
+ end
+
+ it 'returns an appropriate error message with 422 code' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(JSON.parse(response.body)['error']).to eq 'Exception'
+ end
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/authentication/post_session_spec.rb b/viscoll-api/spec/requests/authentication/post_session_spec.rb
new file mode 100644
index 00000000..5c5732d9
--- /dev/null
+++ b/viscoll-api/spec/requests/authentication/post_session_spec.rb
@@ -0,0 +1,84 @@
+require 'rails_helper'
+
+describe "POST /session", :type => :request do
+ context 'when the user does not exist' do
+ before do
+ post '/session', params: {:session => { :email=> "ghost@mail.com", :password => "ghost" }}
+ end
+
+ it 'returns an invalid email / password error message' do
+ expect(JSON.parse(response.body)['errors']['session'][0]).to eq('invalid email / password')
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'when the user exist' do
+ before do
+ @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
+ post '/session', params: {:session => { :email=> "user@mail.com", :password => "user" }}
+ end
+
+ context 'and user email is not confirmed' do
+ it 'returns unconfirmed email error' do
+ expect(JSON.parse(response.body)['errors']['session'][0]).to eq('unconfirmed email')
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and user email is confirmed' do
+ before do
+ @user.confirmation_token = nil
+ @user.confirmed_at = "2017-07-12T16:08:25.278Z"
+ @user.save
+ @project1 = Project.create(:title => "first project", :user_id => @user.id)
+ @project2 = Project.create(:title => "second project", :user_id => @user.id)
+ @project3 = Project.create(:title => "some other user project", :user_id => "")
+ end
+
+ context 'and request with invalid params' do
+ before do
+ post '/session', params: {:session => { :email=> "user@mail.com", :password => "wrong" }}
+ end
+
+ it 'returns an invalid email / password error message' do
+ expect(JSON.parse(response.body)['errors']['session'][0]).to eq('invalid email / password')
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and request with valid params' do
+ before do
+ post '/session', params: {:session => { :email=> "user@mail.com", :password => "user" }}
+ end
+
+ it 'returns the user session token' do
+ expect(JSON.parse(response.body)['session']['jwt']).not_to be_empty
+ expect(JSON.parse(response.body)['session']['email']).to eq("user@mail.com")
+ end
+
+ it 'creates the auth_tokens for the user' do
+ expect(User.find(@user.id).auth_tokens).not_to be_empty
+ end
+
+ it 'returns all the projects of this user' do
+ expect(JSON.parse(response.body)['session']['projects'].size).to eq(2)
+ expect(JSON.parse(response.body)['session']['projects'][0]["title"]).to eq("first project")
+ expect(JSON.parse(response.body)['session']['projects'][1]["title"]).to eq("second project")
+ end
+
+ it 'returns an ok status' do
+ expect(response).to have_http_status(:ok)
+ end
+ end
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/authentication/put_confirmation_spec.rb b/viscoll-api/spec/requests/authentication/put_confirmation_spec.rb
new file mode 100644
index 00000000..01f5a231
--- /dev/null
+++ b/viscoll-api/spec/requests/authentication/put_confirmation_spec.rb
@@ -0,0 +1,32 @@
+require 'rails_helper'
+
+describe "PUT /confirmation", :type => :request do
+ context 'with invalid token' do
+ before do
+ put '/confirmation', params: {:confirmation_token => "invalidToken"}
+ end
+
+ it 'returns an invalid token message' do
+ expect(JSON.parse(response.body)['errors']['confirmation_token']).to eq(['not found'])
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'with valid token' do
+ before do
+ @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ end
+
+ it 'returns successful response code' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'clears the confirmation token in user record' do
+ expect(User.find(@user.id).confirmation_token).to eq(nil)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/authentication/put_password_spec.rb b/viscoll-api/spec/requests/authentication/put_password_spec.rb
new file mode 100644
index 00000000..fd0b6d3f
--- /dev/null
+++ b/viscoll-api/spec/requests/authentication/put_password_spec.rb
@@ -0,0 +1,100 @@
+require 'rails_helper'
+
+describe "PUT /password", :type => :request do
+ before do
+ @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
+ @user.confirmation_token = nil
+ @user.confirmed_at = "2017-07-12T16:08:25.278Z"
+ @user.save
+ post '/password', params: {:password => {:email => "user@mail.com"}}
+ @user = User.find(@user.id)
+ end
+
+ context 'with valid params' do
+ before do
+ put '/password', params: {:reset_password_token => @user.reset_password_token, :password => {:password => "newUser", :password_confirmation => "newUser"}}
+ end
+
+ it 'returns a successful no_content response' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'clears the field for reset_password_token in user record' do
+ expect(User.find(@user.id).reset_password_token).to eq(nil)
+ end
+
+ it 'updates the user password in the database' do
+ post '/session', params: {:session => { :email=> "user@mail.com", :password => "newUser" }}
+ expect(JSON.parse(response.body)['session']['jwt']).not_to be_empty
+ expect(JSON.parse(response.body)['session']['email']).to eq("user@mail.com")
+ end
+ end
+
+ context 'with invalid params' do
+ context 'and reset token expired' do
+ before do
+ @user.reset_password_sent_at = "2017-07-12T16:08:30.278Z"
+ @user.save
+ put '/password', params: {:reset_password_token => @user.reset_password_token, :password => {:password => "newUser", :password_confirmation => "newUser"}}
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['errors']['reset_password_token']).to eq(['has expired, please request a new one'])
+ end
+
+ it 'does not not clear field for reset_password_token in user record' do
+ expect(User.find(@user.id).reset_password_token).not_to eq(nil)
+ end
+ end
+
+ context 'and invalid reset token' do
+ before do
+ put '/password', params: {:reset_password_token => "invalidToken", :password => {:password => "newUser", :password_confirmation => "newUser"}}
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['errors']['reset_password_token']).to eq(['not found'])
+ end
+
+ it 'does not not clear field for reset_password_token in user record' do
+ expect(User.find(@user.id).reset_password_token).not_to eq(nil)
+ end
+ end
+
+ context 'and blank password' do
+ before do
+ put '/password', params: {:reset_password_token => @user.reset_password_token, :password => {:password => "", :password_confirmation => "newUser"}}
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['errors']['password']).to eq(['blank'])
+ end
+ end
+
+ context 'and no matching passwords' do
+ before do
+ put '/password', params: {:reset_password_token => @user.reset_password_token, :password => {:password => "newUser", :password_confirmation => "newUserGhost"}}
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['errors']['password_confirmation']).to eq(['doesn\'t match Password'])
+ end
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/feedback/create_feedback_spec.rb b/viscoll-api/spec/requests/feedback/create_feedback_spec.rb
new file mode 100644
index 00000000..927435ac
--- /dev/null
+++ b/viscoll-api/spec/requests/feedback/create_feedback_spec.rb
@@ -0,0 +1,103 @@
+require 'rails_helper'
+
+describe "POST /feedback", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @parameters = {
+ feedback: {
+ title: "Something is weird",
+ message: "Hey can you look into this"
+ }
+ }
+ end
+
+ context 'with valid authentication' do
+ context 'and valid user ID' do
+ it 'sends an email' do
+ expect {
+ post '/feedback', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ }.to change { ActionMailer::Base.deliveries.count }.by 1
+ end
+
+ it 'requires a title' do
+ @parameters[:feedback][:title] = ''
+ post '/feedback', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(JSON.parse(response.body)['error']).to eq '[title] and [message] params required.'
+ end
+
+ it 'requires a message' do
+ @parameters[:feedback][:message] = ''
+ post '/feedback', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(JSON.parse(response.body)['error']).to eq '[title] and [message] params required.'
+ end
+
+ it 'handles exceptions' do
+ expect(FeedbackMailer).to receive(:sendFeedback).and_raise('AnException')
+ post '/feedback', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(JSON.parse(response.body)['error']).to eq 'AnException'
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ post '/feedback', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ post '/feedback', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ post '/feedback', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ post '/feedback'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/groups/groups_create_spec.rb b/viscoll-api/spec/requests/groups/groups_create_spec.rb
new file mode 100644
index 00000000..031d29ae
--- /dev/null
+++ b/viscoll-api/spec/requests/groups/groups_create_spec.rb
@@ -0,0 +1,179 @@
+require 'rails_helper'
+
+describe "POST /groups", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {user: @user, noteTypes: ["Ink"]})
+ @parameters = {
+ "group": {
+ "project_id": @project.id.to_str,
+ "title": "New Quire",
+ "direction": "left-to-right",
+ "type": "Quire",
+ },
+ "additional": {
+ "order": 1,
+ "memberOrder": 1,
+ "noOfGroups": 1,
+ "noOfLeafs": 5,
+ "conjoin": true,
+ "oddMemberLeftOut": 2
+ }
+ }
+ end
+
+ context 'and valid authorization' do
+ context 'and standard group' do
+ before do
+ post '/groups', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'adds a group to the project' do
+ expect(@project.groups).to include an_object_having_attributes(title: "New Quire")
+ end
+ end
+
+ context 'and as a sub-group' do
+ before do
+ @group2 = FactoryGirl.create(:quire, { title: "Existing Quire", project: @project })
+ @project.add_groupIDs([@group2.id.to_s], 0)
+ @parameters[:additional][:parentGroupID] = @group2.id.to_s
+ @parameters[:additional][:order] = 2
+ post '/groups', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @group2.reload
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'adds a group to the project' do
+ expect(@group2.memberIDs.length).to eq 1
+ expect(@project.groups).to include an_object_having_attributes(title: "New Quire")
+ end
+ end
+
+ context 'and missing parameter' do
+ before do
+ @parameters[:group].delete(:project_id)
+ post '/groups', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns the error message' do
+ expect(@body['group']['project_id']).to include("not found")
+ end
+ end
+
+ context 'and missing project' do
+ before do
+ @parameters[:group][:project_id] += 'missing'
+ post '/groups', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns the error message' do
+ expect(@body['group']['project_id']).to include("project not found with id #{@project.id.to_str}missing")
+ end
+ end
+
+ context 'and failing params for the note' do
+ before do
+ allow_any_instance_of(Group).to receive(:save).and_return(false)
+ post '/groups', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and uncaught exception' do
+ before do
+ allow_any_instance_of(Group).to receive(:save).and_raise("Exception")
+ post '/groups', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns the error message' do
+ expect(@body['error']).to eq "Exception"
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ post '/groups', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ post '/groups', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ post '/groups', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ post '/groups'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/groups/groups_destroy_multiple_spec.rb b/viscoll-api/spec/requests/groups/groups_destroy_multiple_spec.rb
new file mode 100644
index 00000000..baf0263f
--- /dev/null
+++ b/viscoll-api/spec/requests/groups/groups_destroy_multiple_spec.rb
@@ -0,0 +1,148 @@
+require 'rails_helper'
+
+describe "DELETE /groups", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {
+ user: @user,
+ noteTypes: ["Ink"],
+ })
+ groupIDs = []
+ 5.times do |n|
+ group = (FactoryGirl.create(:quire, {
+ project: @project,
+ title: "QUIRE #{n+1}"
+ }))
+ groupIDs.push(group.id.to_s)
+ end
+ @project.add_groupIDs(groupIDs, 0)
+ @project.save
+ @parameters = {
+ projectID: @project.id.to_s,
+ groups: [
+ @project.groups[1].id.to_str,
+ @project.groups[2].id.to_str,
+ ],
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'and standard group specs' do
+ before do
+ delete '/groups', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'deletes only the specified groups' do
+ expect(@project.groups).to include an_object_having_attributes(title: "QUIRE 1")
+ expect(@project.groups).not_to include an_object_having_attributes(title: "QUIRE 2")
+ expect(@project.groups).not_to include an_object_having_attributes(title: "QUIRE 3")
+ expect(@project.groups).to include an_object_having_attributes(title: "QUIRE 4")
+ expect(@project.groups).to include an_object_having_attributes(title: "QUIRE 5")
+ end
+ end
+
+ context 'and missing group' do
+ before do
+ @parameters[:groups][0] += 'missing'
+ @parameters[:groups][1] += 'missing'
+ delete "/groups", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'leaves the groups alone' do
+ expect(@project.groups).to include an_object_having_attributes(title: "QUIRE 1")
+ expect(@project.groups).to include an_object_having_attributes(title: "QUIRE 2")
+ expect(@project.groups).to include an_object_having_attributes(title: "QUIRE 3")
+ expect(@project.groups).to include an_object_having_attributes(title: "QUIRE 4")
+ expect(@project.groups).to include an_object_having_attributes(title: "QUIRE 5")
+ end
+ end
+
+ context 'and unauthorized group' do
+ before do
+ @project.user = FactoryGirl.create(:user)
+ @project.save
+ delete '/groups', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'leaves the groups alone' do
+ expect(@project.groups).to include an_object_having_attributes(title: "QUIRE 1")
+ expect(@project.groups).to include an_object_having_attributes(title: "QUIRE 2")
+ expect(@project.groups).to include an_object_having_attributes(title: "QUIRE 3")
+ expect(@project.groups).to include an_object_having_attributes(title: "QUIRE 4")
+ expect(@project.groups).to include an_object_having_attributes(title: "QUIRE 5")
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ delete '/groups', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ delete '/groups', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ delete '/groups', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ delete '/groups'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/groups/groups_destroy_spec.rb b/viscoll-api/spec/requests/groups/groups_destroy_spec.rb
new file mode 100644
index 00000000..75854507
--- /dev/null
+++ b/viscoll-api/spec/requests/groups/groups_destroy_spec.rb
@@ -0,0 +1,149 @@
+require 'rails_helper'
+
+describe "DELETE /groups/id", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {
+ user: @user,
+ noteTypes: ["Ink"],
+ })
+ @groupIDs = []
+ 5.times do |n|
+ group = FactoryGirl.create(:quire, { project: @project })
+ @groupIDs.push(group.id.to_s)
+ end
+ @group = @project.groups.find(@groupIDs[3])
+ @project.add_groupIDs(@groupIDs, 0)
+ @parameters = {
+ projectID: @project.id.to_s,
+ group: {
+ type: "Booklet",
+ title: "Changed title"
+ },
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'and standard group specs' do
+ before do
+ delete "/groups/#{@group.id.to_str}", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'destroys the group' do
+ expect(@project.groups).not_to include an_object_having_attributes(id: @group.id)
+ end
+ end
+
+ context 'and missing group' do
+ before do
+ delete "/groups/#{@group.id.to_str}missing", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+
+ it 'returns the right error message' do
+ expect(@body['error']).to eq "group not found"
+ end
+ end
+
+ context 'and unauthorized group' do
+ before do
+ @project.user = FactoryGirl.create(:user)
+ @project.save
+ delete "/groups/#{@group.id.to_str}", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @group.reload
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'retains the group' do
+ expect(@project.groups).to include an_object_having_attributes(id: @group.id)
+ end
+ end
+
+ context 'and raised exception' do
+ before do
+ allow_any_instance_of(Group).to receive(:destroy).and_raise('MyException')
+ delete "/groups/#{@group.id.to_str}", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'shows the exception' do
+ expect(@body['error']).to eq 'MyException'
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ delete "/groups/#{@group.id.to_str}", params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ delete "/groups/#{@group.id.to_str}", params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ delete "/groups/#{@group.id.to_str}", params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ delete "/groups/#{@group.id.to_str}"
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/groups/groups_update_multiple_spec.rb b/viscoll-api/spec/requests/groups/groups_update_multiple_spec.rb
new file mode 100644
index 00000000..454f0a3e
--- /dev/null
+++ b/viscoll-api/spec/requests/groups/groups_update_multiple_spec.rb
@@ -0,0 +1,169 @@
+require 'rails_helper'
+
+describe "PUT /groups", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {
+ user: @user,
+ noteTypes: ["Ink"],
+ })
+ 5.times do |n|
+ @project.groups << FactoryGirl.create(:quire, {
+ project: @project,
+ })
+ end
+ @project.save
+ @parameters = {
+ groups: [
+ {
+ id: @project.groups[1].id.to_str,
+ attributes: {
+ title: "Changed title 1"
+ }
+ },
+ {
+ id: @project.groups[2].id.to_str,
+ attributes: {
+ title: "Changed title 2"
+ }
+ }
+ ],
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'and standard group specs' do
+ before do
+ put '/groups', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'edits the group' do
+ expect(@project.groups[1].title).to eq "Changed title 1"
+ expect(@project.groups[2].title).to eq "Changed title 2"
+ end
+ end
+
+ context 'and missing group' do
+ before do
+ @parameters[:groups][0][:id] += 'missing'
+ @parameters[:groups][1][:id] += 'missing'
+ put "/groups", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and unauthorized group' do
+ before do
+ @project.user = FactoryGirl.create(:user)
+ @project.save
+ put '/groups', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'leaves the targets unaltered' do
+ expect(@project.groups[1].title).not_to eq "Changed title 1"
+ expect(@project.groups[2].title).not_to eq "Changed title 2"
+ end
+ end
+
+ context 'and failed update' do
+ before do
+ allow_any_instance_of(Group).to receive(:update).and_return(false)
+ put '/groups', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and raised exception' do
+ before do
+ allow_any_instance_of(Group).to receive(:update).and_raise('MyException')
+ put '/groups', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'shows the exception' do
+ expect(@body['error']).to eq 'MyException'
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/groups', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put '/groups', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put '/groups', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put '/groups'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/groups/groups_update_spec.rb b/viscoll-api/spec/requests/groups/groups_update_spec.rb
new file mode 100644
index 00000000..a92e782b
--- /dev/null
+++ b/viscoll-api/spec/requests/groups/groups_update_spec.rb
@@ -0,0 +1,155 @@
+require 'rails_helper'
+
+describe "PUT /groups/id", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {
+ user: @user,
+ noteTypes: ["Ink"],
+ })
+ 2.times do
+ @project.groups << FactoryGirl.create(:quire, { project: @project })
+ end
+ @project.save
+ @group = @project.groups[0]
+ @parameters = {
+ group: {
+ type: "Booklet",
+ title: "Changed title"
+ },
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'and standard group specs' do
+ before do
+ put "/groups/#{@group.id.to_str}", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @group.reload
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'edits the group' do
+ expect(@group.type).to eq "Booklet"
+ expect(@group.title).to eq "Changed title"
+ end
+ end
+
+ context 'and missing group' do
+ before do
+ put "/groups/#{@group.id.to_str}missing", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+
+ it 'returns the right error message' do
+ expect(@body['error']).to eq "group not found"
+ end
+ end
+
+ context 'and unauthorized group' do
+ before do
+ @project.user = FactoryGirl.create(:user)
+ @project.save
+ put "/groups/#{@group.id.to_str}", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @group.reload
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'and failed update' do
+ before do
+ allow_any_instance_of(Group).to receive(:update).and_return(false)
+ put "/groups/#{@group.id.to_str}", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and raised exception' do
+ before do
+ allow_any_instance_of(Group).to receive(:update).and_raise('MyException')
+ put "/groups/#{@group.id.to_str}", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'shows the exception' do
+ expect(@body['error']).to eq 'MyException'
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put "/groups/#{@group.id.to_str}", params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put "/groups/#{@group.id.to_str}", params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put "/groups/#{@group.id.to_str}", params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put "/groups/#{@group.id.to_str}"
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/images/destroy_images_spec.rb b/viscoll-api/spec/requests/images/destroy_images_spec.rb
new file mode 100644
index 00000000..3d47825a
--- /dev/null
+++ b/viscoll-api/spec/requests/images/destroy_images_spec.rb
@@ -0,0 +1,134 @@
+require 'rails_helper'
+
+describe "DELETE /images", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:codex_project, user: @user, quire_structure: [[1, 2]])
+ @image1 = FactoryGirl.create(:image, user: @user)
+ @image2 = FactoryGirl.create(:image, user: @user)
+ @parameters = {
+ "imageIDs": [@image1.id.to_s]
+ }
+ end
+
+ context 'and valid authorization' do
+ context 'and valid image' do
+ before do
+ delete '/images', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'deletes the right image' do
+ expect(Image.where(id: @image1.id)).not_to exist
+ expect(Image.where(id: @image2.id)).to exist
+ end
+ end
+
+ context 'and missing image' do
+ before do
+ @parameters[:imageIDs][0] += 'missing'
+ delete '/images', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+
+ it 'returns the error message' do
+ expect(@body['error']).to eq("image not found with id #{@image1.id.to_str}missing")
+ end
+ end
+
+ context 'and unauthorized image' do
+ before do
+ @image1.update(user: FactoryGirl.create(:user))
+ delete '/images', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'and uncaught exception' do
+ before do
+ allow(Image).to receive(:find).and_raise("Exception")
+ delete '/images', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns the error message' do
+ expect(@body['error']).to eq "Exception"
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ delete '/images', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ delete '/images', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ delete '/images', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ delete '/images'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/images/link_images_spec.rb b/viscoll-api/spec/requests/images/link_images_spec.rb
new file mode 100644
index 00000000..f5368562
--- /dev/null
+++ b/viscoll-api/spec/requests/images/link_images_spec.rb
@@ -0,0 +1,253 @@
+require 'rails_helper'
+
+describe "PUT /images/link", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project1 = FactoryGirl.create(:project, user: @user)
+ @project2 = FactoryGirl.create(:project, user: @user)
+ @image1 = FactoryGirl.create(:pixel, user: @user)
+ @image2 = FactoryGirl.create(:shiba_inu, user: @user)
+ @parameters = {
+ "projectIDs": [],
+ "imageIDs": []
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'and valid image and project' do
+ before do
+ @parameters[:projectIDs] = [@project1.id.to_s]
+ @parameters[:imageIDs] = [@image1.id.to_s]
+ put '/images/link', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @image1.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'establishes the link' do
+ expect(@image1.projectIDs).to include @project1.id.to_s
+ end
+ end
+
+ context 'and multiple valid images and multiple projects' do
+ before do
+ @parameters[:projectIDs] = [@project1.id.to_s, @project2.id.to_s]
+ @parameters[:imageIDs] = [@image1.id.to_s, @image2.id.to_s]
+ put '/images/link', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @image1.reload
+ @image2.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'establishes the link' do
+ expect(@image1.projectIDs).to include @project1.id.to_s
+ expect(@image1.projectIDs).to include @project2.id.to_s
+ expect(@image2.projectIDs).to include @project1.id.to_s
+ expect(@image2.projectIDs).to include @project2.id.to_s
+ end
+ end
+
+ context 'and valid image but missing project' do
+ before do
+ @parameters[:projectIDs] = [@project1.id.to_s, @project2.id.to_s+'missing']
+ @parameters[:imageIDs] = [@image1.id.to_s, @image2.id.to_s]
+ put '/images/link', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @image1.reload
+ @image2.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+
+ it 'shows the error' do
+ expect(@body['error']).to eq "project not found with id #{@project2.id}missing"
+ end
+
+ it 'leaves the images alone' do
+ expect(@image1.projectIDs).to be_empty
+ expect(@image2.projectIDs).to be_empty
+ end
+ end
+
+ context 'and valid image but unauthorized project' do
+ before do
+ @project2.update(user: FactoryGirl.create(:user))
+ @parameters[:projectIDs] = [@project1.id.to_s, @project2.id.to_s]
+ @parameters[:imageIDs] = [@image1.id.to_s, @image2.id.to_s]
+ put '/images/link', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @image1.reload
+ @image2.reload
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'leaves the images alone' do
+ expect(@image1.projectIDs).to be_empty
+ expect(@image2.projectIDs).to be_empty
+ end
+ end
+
+ context 'and missing image but valid project' do
+ before do
+ @parameters[:projectIDs] = [@project1.id.to_s, @project2.id.to_s]
+ @parameters[:imageIDs] = [@image1.id.to_s, @image2.id.to_s+'missing']
+ put '/images/link', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @image1.reload
+ @image2.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+
+ it 'shows the error' do
+ expect(@body['error']).to eq "image not found with id #{@image2.id}missing"
+ end
+
+ it 'leaves the images alone' do
+ expect(@image1.projectIDs).to be_empty
+ expect(@image2.projectIDs).to be_empty
+ end
+ end
+
+ context 'and unauthorized image but valid project' do
+ before do
+ @image2.update(user: FactoryGirl.create(:user))
+ @parameters[:projectIDs] = [@project1.id.to_s, @project2.id.to_s]
+ @parameters[:imageIDs] = [@image1.id.to_s, @image2.id.to_s]
+ put '/images/link', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @image1.reload
+ @image2.reload
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'leaves the images alone' do
+ expect(@image1.projectIDs).to be_empty
+ expect(@image2.projectIDs).to be_empty
+ end
+ end
+
+ context 'and exception in projects' do
+ before do
+ allow(Project).to receive(:find).and_raise('waahooexception')
+ @parameters[:projectIDs] = [@project1.id.to_s]
+ @parameters[:imageIDs] = [@image1.id.to_s]
+ put '/images/link', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @image1.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'shows the error' do
+ expect(@body['error']).to eq 'waahooexception'
+ end
+
+ it 'leaves the images alone' do
+ expect(@image1.projectIDs).to be_empty
+ expect(@image2.projectIDs).to be_empty
+ end
+ end
+
+ context 'and exception in images' do
+ before do
+ allow(Image).to receive(:find).and_raise('waahooexception')
+ @parameters[:projectIDs] = [@project1.id.to_s]
+ @parameters[:imageIDs] = [@image1.id.to_s]
+ put '/images/link', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @image1.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'shows the error' do
+ expect(@body['error']).to eq 'waahooexception'
+ end
+
+ it 'leaves the images alone' do
+ expect(@image1.projectIDs).to be_empty
+ expect(@image2.projectIDs).to be_empty
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/images/link', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put '/images/link', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put '/images/link', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put '/images/link'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
+
diff --git a/viscoll-api/spec/requests/images/show_images_spec.rb b/viscoll-api/spec/requests/images/show_images_spec.rb
new file mode 100644
index 00000000..02a8d414
--- /dev/null
+++ b/viscoll-api/spec/requests/images/show_images_spec.rb
@@ -0,0 +1,74 @@
+require 'rails_helper'
+
+describe "GET /images/:id", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:codex_project, user: @user, quire_structure: [[1, 2]])
+ @image1 = FactoryGirl.create(:pixel, user: @user)
+ @image2 = FactoryGirl.create(:shiba_inu, user: @user)
+ end
+
+ before :all do
+ imagePath = "#{Rails.root}/public/uploads"
+ File.new(imagePath+'/pixel', 'w')
+ end
+
+ after :all do
+ imagePath = "#{Rails.root}/public/uploads"
+ File.delete(imagePath+'/pixel')
+ end
+
+ context 'and valid authorization' do
+ context 'and valid image' do
+ before do
+ get "/images/#{@image1.id}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'shows the right image' do
+ expect(response.body).to eq(File.open("#{Rails.root}/public/uploads/pixel", 'rb') { |file| file.read })
+ end
+ end
+
+ context 'and missing image' do
+ before do
+ get "/images/#{@image1.id}missing", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+
+ it 'returns the error message' do
+ expect(@body['error']).to eq("image not found with id #{@image1.id.to_str}missing")
+ end
+ end
+
+ context 'and uncaught exception' do
+ before do
+ allow(Image).to receive(:find).and_raise("Exception")
+ get "/images/#{@image1.id}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns the error message' do
+ expect(@body['error']).to eq "Exception"
+ end
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/images/unlink_images_spec.rb b/viscoll-api/spec/requests/images/unlink_images_spec.rb
new file mode 100644
index 00000000..4fa01ebc
--- /dev/null
+++ b/viscoll-api/spec/requests/images/unlink_images_spec.rb
@@ -0,0 +1,259 @@
+require 'rails_helper'
+
+describe "PUT /images/unlink", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project1 = FactoryGirl.create(:codex_project, user: @user, quire_structure: [[2, 2]])
+ @project2 = FactoryGirl.create(:codex_project, user: @user, quire_structure: [[2, 2]])
+ @image1 = FactoryGirl.create(:pixel, user: @user, projectIDs: [@project1.id.to_s, @project2.id.to_s], sideIDs: [@project1.leafs[0].rectoID, @project2.leafs[0].rectoID])
+ @image2 = FactoryGirl.create(:shiba_inu, user: @user, projectIDs: [@project1.id.to_s, @project2.id.to_s], sideIDs: [@project1.leafs[0].versoID, @project2.leafs[0].versoID])
+ @parameters = {
+ "projectIDs": [],
+ "imageIDs": []
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'and valid image and project' do
+ before do
+ @parameters[:projectIDs] = [@project1.id.to_s]
+ @parameters[:imageIDs] = [@image1.id.to_s]
+ put '/images/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @image1.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'breaks the right link' do
+ expect(@image1.projectIDs).not_to include @project1.id.to_s
+ expect(@image1.projectIDs).to include @project2.id.to_s
+ expect(@image1.sideIDs).to eq [@project2.leafs[0].rectoID]
+ expect(Side.find(@project1.leafs[0].rectoID).image).to eq({})
+ end
+ end
+
+ context 'and multiple valid images and multiple projects' do
+ before do
+ @parameters[:projectIDs] = [@project1.id.to_s, @project2.id.to_s]
+ @parameters[:imageIDs] = [@image1.id.to_s, @image2.id.to_s]
+ put '/images/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ # If images have no projectIDs, it will be deleted after unlinking
+ # @image1.reload
+ # @image2.reload
+ @user.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'breaks the specified links' do
+ expect(@user.images).to_not be_empty
+ expect(Side.find(@project1.leafs[0].rectoID).image).to eq({})
+ expect(Side.find(@project1.leafs[0].versoID).image).to eq({})
+ expect(Side.find(@project2.leafs[0].rectoID).image).to eq({})
+ expect(Side.find(@project2.leafs[0].versoID).image).to eq({})
+ end
+ end
+
+ context 'and valid image but missing project' do
+ before do
+ @parameters[:projectIDs] = [@project1.id.to_s, @project2.id.to_s+'missing']
+ @parameters[:imageIDs] = [@image1.id.to_s, @image2.id.to_s]
+ put '/images/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @image1.reload
+ @image2.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+
+ it 'shows the error' do
+ expect(@body['error']).to eq "project not found with id #{@project2.id}missing"
+ end
+
+ it 'leaves the images alone' do
+ expect(@image1.projectIDs).to eq [@project1.id.to_s, @project2.id.to_s]
+ expect(@image2.projectIDs).to eq [@project1.id.to_s, @project2.id.to_s]
+ end
+ end
+
+ context 'and valid image but unauthorized project' do
+ before do
+ @project2.update(user: FactoryGirl.create(:user))
+ @parameters[:projectIDs] = [@project1.id.to_s, @project2.id.to_s]
+ @parameters[:imageIDs] = [@image1.id.to_s, @image2.id.to_s]
+ put '/images/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @image1.reload
+ @image2.reload
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'leaves the images alone' do
+ expect(@image1.projectIDs).to eq [@project1.id.to_s, @project2.id.to_s]
+ expect(@image2.projectIDs).to eq [@project1.id.to_s, @project2.id.to_s]
+ end
+ end
+
+ context 'and missing image but valid project' do
+ before do
+ @parameters[:projectIDs] = [@project1.id.to_s, @project2.id.to_s]
+ @parameters[:imageIDs] = [@image1.id.to_s, @image2.id.to_s+'missing']
+ put '/images/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @image1.reload
+ @image2.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+
+ it 'shows the error' do
+ expect(@body['error']).to eq "image not found with id #{@image2.id}missing"
+ end
+
+ it 'leaves the images alone' do
+ expect(@image1.projectIDs).to eq [@project1.id.to_s, @project2.id.to_s]
+ expect(@image2.projectIDs).to eq [@project1.id.to_s, @project2.id.to_s]
+ end
+ end
+
+ context 'and unauthorized image but valid project' do
+ before do
+ @image2.update(user: FactoryGirl.create(:user))
+ @parameters[:projectIDs] = [@project1.id.to_s, @project2.id.to_s]
+ @parameters[:imageIDs] = [@image1.id.to_s, @image2.id.to_s]
+ put '/images/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @image1.reload
+ @image2.reload
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'leaves the images alone' do
+ expect(@image1.projectIDs).to eq [@project1.id.to_s, @project2.id.to_s]
+ expect(@image2.projectIDs).to eq [@project1.id.to_s, @project2.id.to_s]
+ end
+ end
+
+ context 'and exception in projects' do
+ before do
+ allow(Project).to receive(:find).and_raise('waahooexception')
+ @parameters[:projectIDs] = [@project1.id.to_s]
+ @parameters[:imageIDs] = [@image1.id.to_s]
+ put '/images/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @image1.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'shows the error' do
+ expect(@body['error']).to eq 'waahooexception'
+ end
+
+ it 'leaves the images alone' do
+ expect(@image1.projectIDs).to eq [@project1.id.to_s, @project2.id.to_s]
+ expect(@image2.projectIDs).to eq [@project1.id.to_s, @project2.id.to_s]
+ end
+ end
+
+ context 'and exception in images' do
+ before do
+ allow(Image).to receive(:find).and_raise('waahooexception')
+ @parameters[:projectIDs] = [@project1.id.to_s]
+ @parameters[:imageIDs] = [@image1.id.to_s]
+ put '/images/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @image1.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'shows the error' do
+ expect(@body['error']).to eq 'waahooexception'
+ end
+
+ it 'leaves the images alone' do
+ expect(@image1.projectIDs).to eq [@project1.id.to_s, @project2.id.to_s]
+ expect(@image2.projectIDs).to eq [@project1.id.to_s, @project2.id.to_s]
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/images/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put '/images/unlink', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put '/images/unlink', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put '/images/unlink'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
+
diff --git a/viscoll-api/spec/requests/images/upload_images_spec.rb b/viscoll-api/spec/requests/images/upload_images_spec.rb
new file mode 100644
index 00000000..008b1dbf
--- /dev/null
+++ b/viscoll-api/spec/requests/images/upload_images_spec.rb
@@ -0,0 +1,180 @@
+require 'rails_helper'
+
+describe "POST /images", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:codex_project, user: @user, quire_structure: [[1, 2]])
+ @parameters = {
+ "projectID": @project.id.to_s,
+ "images": [
+ {
+ "filename": "green",
+ "content": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
+ },
+ {
+ "filename": "blue",
+ "content": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
+ }
+ ]
+ }
+ end
+
+ after do
+ Image.where(:projectIDs => @project.id.to_s).each do | image |
+ image.destroy
+ end
+ end
+
+ context 'and valid authorization' do
+ context 'and standard group' do
+ before do
+ post '/images', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'creates two new images connected to the project' do
+ expect(Image.where(filename: 'green.png')).to exist
+ expect(Image.where(filename: 'blue.png')).to exist
+ expect(Image.find_by(filename: 'green.png').projectIDs).to include @project.id.to_s
+ expect(Image.find_by(filename: 'blue.png').projectIDs).to include @project.id.to_s
+ end
+ end
+
+ context 'and duplicated image' do
+ before do
+ @parameters[:images][1][:filename] = 'green'
+ post '/images', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'creates two new images, the second with the _copy(n) suffix' do
+ expect(Image.where(filename: 'green.png')).to exist
+ expect(Image.where(filename: 'green_copy(1).png')).to exist
+ expect(Image.find_by(filename: 'green.png').projectIDs).to include @project.id.to_s
+ expect(Image.find_by(filename: 'green_copy(1).png').projectIDs).to include @project.id.to_s
+ end
+ end
+
+ context 'and missing project' do
+ before do
+ @parameters[:projectID] += 'missing'
+ post '/images', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+
+ it 'returns the error message' do
+ expect(@body['error']).to eq("project not found with id #{@project.id.to_str}missing")
+ end
+ end
+
+ context 'and unauthorized project' do
+ before do
+ @project.update(user: FactoryGirl.create(:user))
+ post '/images', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'and failing image' do
+ before do
+ allow_any_instance_of(Image).to receive(:valid?).and_return(false)
+ post '/images', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and uncaught exception' do
+ before do
+ allow(Project).to receive(:find).and_raise("Exception")
+ post '/images', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns the error message' do
+ expect(@body['error']).to eq "Exception"
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ post '/images', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ post '/images', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ post '/images', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ post '/images'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/images/zip_images_spec.rb b/viscoll-api/spec/requests/images/zip_images_spec.rb
new file mode 100644
index 00000000..e56ce3d4
--- /dev/null
+++ b/viscoll-api/spec/requests/images/zip_images_spec.rb
@@ -0,0 +1,66 @@
+require 'rails_helper'
+
+describe "GET /images/zip/:imageid_:projectid", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ @zipPath = "#{Rails.root}/public/uploads"
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:codex_project, user: @user, quire_structure: [[1, 2]])
+ @image1 = FactoryGirl.create(:pixel, user: @user)
+ @image2 = FactoryGirl.create(:shiba_inu, user: @user)
+ end
+
+ context 'and valid authorization' do
+ context 'and valid image' do
+ before do
+
+ File.open("#{@zipPath}/#{@project.id}_images.zip", 'w+') { |file| file.write('testcontent') }
+ get "/images/zip/#{@project.id}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json'}
+ end
+ after do
+ File.delete("#{@zipPath}/#{@project.id}_images.zip")
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'sends the zip file' do
+ expect(response.body).to eq('testcontent')
+ end
+ end
+
+ context 'and missing image' do
+ before do
+ get "/images/zip/#{@image1.id}missing_#{@project.id}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and uncaught exception' do
+ before do
+ allow(Image).to receive(:find).and_raise("Exception")
+ get "/images/zip/#{@image1.id}_#{@project.id}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns the error message' do
+ expect(@body['error']).to include "Cannot read file"
+ end
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/leafs/leafs_conjoin_spec.rb b/viscoll-api/spec/requests/leafs/leafs_conjoin_spec.rb
new file mode 100644
index 00000000..fe336020
--- /dev/null
+++ b/viscoll-api/spec/requests/leafs/leafs_conjoin_spec.rb
@@ -0,0 +1,209 @@
+require 'rails_helper'
+
+describe "PUT /leafs/conjoin", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ leaf_count = 5
+ @project = FactoryGirl.create(:project, user: @user)
+ @group = FactoryGirl.create(:group, project: @project)
+ @project.add_groupIDs([@group.id.to_s], 0)
+ @leafs = leaf_count.times.collect { FactoryGirl.create(:leaf, project: @project, material: 'Parchment', parentID: @group.id.to_s) }
+ @group.add_members(@leafs.collect { |leaf| leaf.id.to_s }, 0)
+ @parameters = {
+ "leafs": @leafs[0..3].collect { |leaf| leaf.id.to_s }
+ }
+ end
+
+ context 'and valid authorization' do
+ context 'and valid even number of leafs' do
+ before do
+ put '/leafs/conjoin', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @group.reload
+ @leafs.each { |leaf| leaf.reload }
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'updates the affected leafs' do
+ expect(@leafs[0].conjoined_to).to eq @leafs[3].id.to_s
+ expect(@leafs[1].conjoined_to).to eq @leafs[2].id.to_s
+ expect(@leafs[2].conjoined_to).to eq @leafs[1].id.to_s
+ expect(@leafs[3].conjoined_to).to eq @leafs[0].id.to_s
+ end
+ end
+
+ context 'and valid odd number of leafs' do
+ before do
+ @parameters[:leafs] = @leafs[0..4].collect { |leaf| leaf.id.to_s }
+ put '/leafs/conjoin', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @group.reload
+ @leafs.each { |leaf| leaf.reload }
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'updates the affected leafs' do
+ expect(@leafs[0].conjoined_to).to eq @leafs[4].id.to_s
+ expect(@leafs[1].conjoined_to).to eq @leafs[3].id.to_s
+ expect(@leafs[2].conjoined_to).to be_blank
+ expect(@leafs[3].conjoined_to).to eq @leafs[1].id.to_s
+ expect(@leafs[4].conjoined_to).to eq @leafs[0].id.to_s
+ end
+ end
+
+ context 'and valid odd subleaves within even conjoined quire' do
+ before do
+ @project = FactoryGirl.create(:codex_project, user: @user, quire_structure: [[1,8]])
+ @leafs = @project.leafs
+ @parameters[:leafs] = @leafs[0..4].collect { |leaf| leaf.id.to_s }
+ put '/leafs/conjoin', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @leafs.each { |leaf| leaf.reload }
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'updates the affected leafs' do
+ expect(@leafs[0].conjoined_to).to eq @leafs[4].id.to_s
+ expect(@leafs[1].conjoined_to).to eq @leafs[3].id.to_s
+ expect(@leafs[2].conjoined_to).to be_blank
+ expect(@leafs[3].conjoined_to).to eq @leafs[1].id.to_s
+ expect(@leafs[4].conjoined_to).to eq @leafs[0].id.to_s
+ expect(@leafs[5].conjoined_to).to be_blank
+ expect(@leafs[6].conjoined_to).to be_blank
+ expect(@leafs[7].conjoined_to).to be_blank
+ end
+
+ end
+
+ context 'and too few leafs' do
+ before do
+ @parameters[:leafs] = [@leafs[0].id.to_s]
+ put '/leafs/conjoin', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ @project.reload
+ @group.reload
+ @leafs.each { |leaf| leaf.reload }
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'explains the error' do
+ expect(JSON.parse(response.body)['leafs']).to include 'Minimum of 2 leaves required to conjoin'
+ end
+ end
+
+ context 'and missing leaf' do
+ before do
+ @parameters[:leafs][0] += 'missing'
+ put '/leafs/conjoin', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and raised exception' do
+ before do
+ allow_any_instance_of(Leaf).to receive(:save).and_raise('MyException')
+ put '/leafs/conjoin', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and an unauthorized page' do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project.update(user: @user2)
+ put '/leafs/conjoin', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @group.reload
+ @leafs.each { |leaf| leaf.reload }
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should not edit the leaf' do
+ @leafs.each do |leaf|
+ expect(leaf.conjoined_to).to be_blank
+ end
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/leafs/conjoin', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put '/leafs/conjoin', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put '/leafs/conjoin', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ delete "/leafs/#{@leafs[1].id.to_s}"
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/leafs/leafs_create_spec.rb b/viscoll-api/spec/requests/leafs/leafs_create_spec.rb
new file mode 100644
index 00000000..69974d45
--- /dev/null
+++ b/viscoll-api/spec/requests/leafs/leafs_create_spec.rb
@@ -0,0 +1,160 @@
+require 'rails_helper'
+
+describe "POST /leafs", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, user: @user)
+ @group = FactoryGirl.create(:group, project: @project)
+ @project.add_groupIDs([@group.id.to_s], 0)
+ @parameters = {
+ "leaf": {
+ "project_id": @project.id.to_s,
+ "parentID": @group.id.to_s,
+ "order": 1,
+ "material": "Parchment",
+ },
+ "additional": {
+ "memberOrder": 1,
+ "noOfLeafs": 5,
+ "conjoin": true,
+ "oddMemberLeftOut": 2
+ }
+ }
+ end
+
+ it 'should set up properly' do
+ expect(true).to be true
+ end
+
+ context 'and valid authorization' do
+ context 'and standard leaf' do
+ before do
+ post '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @group.reload
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'adds 5 leafs to the project and group' do
+ expect(@project.leafs.length).to eq 5
+ expect(@group.memberIDs).to eq(@project.leafs.collect { |leaf| leaf.id.to_s })
+ end
+ end
+
+ context 'and missing project' do
+ before do
+ @parameters[:leaf][:project_id] += "WAAHOO"
+ post '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and invalid additional arguments' do
+ before do
+ @parameters[:additional][:noOfLeafs] = -1
+ post '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and failing params for the leaf' do
+ before do
+ allow_any_instance_of(Leaf).to receive(:save).and_return(false)
+ post '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and an unauthorized project' do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project.update(user: @user2)
+ post '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @group.reload
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should not add leafs to the project' do
+ expect(@project.leafs).to be_blank
+ expect(@group.memberIDs).to be_blank
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ post '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ post '/leafs', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ post '/leafs', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ post '/leafs'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/leafs/leafs_destroy_multiple_spec.rb b/viscoll-api/spec/requests/leafs/leafs_destroy_multiple_spec.rb
new file mode 100644
index 00000000..256c4987
--- /dev/null
+++ b/viscoll-api/spec/requests/leafs/leafs_destroy_multiple_spec.rb
@@ -0,0 +1,178 @@
+require 'rails_helper'
+
+describe "DELETE /leafs", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ leaf_count = 4
+ @project = FactoryGirl.create(:project, user: @user)
+ @group = FactoryGirl.create(:group, project: @project)
+ @project.add_groupIDs([@group.id.to_s], 0)
+ @leafs = leaf_count.times.collect { FactoryGirl.create(:leaf, project: @project, material: 'Parchment', parentID: @group.id.to_s) }
+ leaf_count.times.each do |i|
+ params = {
+ conjoined_to: @leafs[-i-1].id.to_s
+ }
+ unless i == 0
+ params[:attached_above] = @leafs[i-1].id.to_s
+ end
+ unless i == leaf_count-1
+ params[:attached_below] = @leafs[i+1].id.to_s
+ end
+ @leafs[i].update(params)
+ end
+ @group.add_members(@leafs.collect { |leaf| leaf.id.to_s }, 0)
+ @parameters = {
+ "leafs": [
+ @leafs[1].id.to_s,
+ @leafs[0].id.to_s
+ ]
+ }
+ end
+
+ it 'should set up properly' do
+ expect(true).to be true
+ expect(@leafs[0].conjoined_to).to eq @leafs[3].id.to_s
+ expect(@leafs[1].conjoined_to).to eq @leafs[2].id.to_s
+ expect(@leafs[2].conjoined_to).to eq @leafs[1].id.to_s
+ expect(@leafs[3].conjoined_to).to eq @leafs[0].id.to_s
+ expect(@leafs[1].attached_above).to eq @leafs[0].id.to_s
+ expect(@leafs[2].attached_above).to eq @leafs[1].id.to_s
+ expect(@leafs[3].attached_above).to eq @leafs[2].id.to_s
+ expect(@leafs[0].attached_below).to eq @leafs[1].id.to_s
+ expect(@leafs[1].attached_below).to eq @leafs[2].id.to_s
+ expect(@leafs[2].attached_below).to eq @leafs[3].id.to_s
+ end
+
+ context 'and valid authorization' do
+ context 'and standard leaf' do
+ before do
+ delete '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @group.reload
+ @leafs.each do |leaf|
+ if Leaf.where(id: leaf.id).exists?
+ leaf.reload
+ end
+ end
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'deletes the specified leafs' do
+ expect(Leaf.where(id: @leafs[0].id.to_s).exists?).to be false
+ expect(Leaf.where(id: @leafs[1].id.to_s).exists?).to be false
+ expect(Leaf.where(id: @leafs[2].id.to_s).exists?).to be true
+ expect(Leaf.where(id: @leafs[3].id.to_s).exists?).to be true
+ end
+ end
+
+ context 'and missing page' do
+ before do
+ @parameters[:leafs][0] += 'missing'
+ delete '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and raised exception' do
+ before do
+ allow_any_instance_of(Leaf).to receive(:destroy).and_raise('MyException')
+ delete '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and an unauthorized page' do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project.update(user: @user2)
+ delete '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @group.reload
+ @leafs.each do |leaf|
+ if Leaf.where(id: leaf.id).exists?
+ leaf.reload
+ end
+ end
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should not remove any leafs' do
+ 4.times.each do |i|
+ expect(Leaf.where(id: @leafs[i].id).exists?).to be true
+ end
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ delete '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ delete '/leafs', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ delete '/leafs', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ delete "/leafs/#{@leafs[1].id.to_s}"
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/leafs/leafs_destroy_spec.rb b/viscoll-api/spec/requests/leafs/leafs_destroy_spec.rb
new file mode 100644
index 00000000..46fbe01f
--- /dev/null
+++ b/viscoll-api/spec/requests/leafs/leafs_destroy_spec.rb
@@ -0,0 +1,166 @@
+require 'rails_helper'
+
+describe "DELETE /leafs/:id", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ leaf_count = 4
+ @project = FactoryGirl.create(:project, user: @user)
+ @group = FactoryGirl.create(:group, project: @project)
+ @project.add_groupIDs([@group.id.to_s], 0)
+ @leafs = leaf_count.times.collect { FactoryGirl.create(:leaf, project: @project, material: 'Parchment', parentID: @group.id.to_s) }
+ leaf_count.times.each do |i|
+ params = {
+ conjoined_to: @leafs[-i-1].id.to_s
+ }
+ unless i == 0
+ params[:attached_above] = @leafs[i-1].id.to_s
+ end
+ unless i == leaf_count-1
+ params[:attached_below] = @leafs[i+1].id.to_s
+ end
+ @leafs[i].update(params)
+ end
+ @group.add_members(@leafs.collect { |leaf| leaf.id.to_s }, 0)
+ end
+
+ it 'should set up properly' do
+ expect(true).to be true
+ expect(@leafs[0].conjoined_to).to eq @leafs[3].id.to_s
+ expect(@leafs[1].conjoined_to).to eq @leafs[2].id.to_s
+ expect(@leafs[2].conjoined_to).to eq @leafs[1].id.to_s
+ expect(@leafs[3].conjoined_to).to eq @leafs[0].id.to_s
+ expect(@leafs[1].attached_above).to eq @leafs[0].id.to_s
+ expect(@leafs[2].attached_above).to eq @leafs[1].id.to_s
+ expect(@leafs[3].attached_above).to eq @leafs[2].id.to_s
+ expect(@leafs[0].attached_below).to eq @leafs[1].id.to_s
+ expect(@leafs[1].attached_below).to eq @leafs[2].id.to_s
+ expect(@leafs[2].attached_below).to eq @leafs[3].id.to_s
+ end
+
+ context 'and valid authorization' do
+ context 'and standard leaf' do
+ before do
+ delete "/leafs/#{@leafs[1].id.to_s}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @group.reload
+ @leafs.each { |leaf| leaf.reload unless leaf.id == @leafs[1].id }
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'remove the leaf' do
+ expect(Leaf.where(id: @leafs[1].id).exists?).to be false
+ end
+
+ it 'frees the conjoined leaf' do
+ expect(@leafs[2].conjoined_to).to be_blank
+ end
+
+ it 'frees attached leafs' do
+ expect(@leafs[0].attached_below).to eq 'None'
+ expect(@leafs[2].attached_above).to eq 'None'
+ end
+ end
+
+ context 'and missing page' do
+ before do
+ delete "/leafs/#{@leafs[1].id.to_s}waahoo", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'and raised exception' do
+ before do
+ allow_any_instance_of(Leaf).to receive(:destroy).and_raise('MyException')
+ delete "/leafs/#{@leafs[1].id.to_s}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and an unauthorized page' do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project.update(user: @user2)
+ delete "/leafs/#{@leafs[1].id.to_s}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @group.reload
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should not remove any' do
+ expect(@project.leafs.count).to eq 4
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ delete "/leafs/#{@leafs[1].id.to_s}", headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ delete "/leafs/#{@leafs[1].id.to_s}", headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ delete "/leafs/#{@leafs[1].id.to_s}", headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ delete "/leafs/#{@leafs[1].id.to_s}"
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/leafs/leafs_update_multiple_spec.rb b/viscoll-api/spec/requests/leafs/leafs_update_multiple_spec.rb
new file mode 100644
index 00000000..5a8627c7
--- /dev/null
+++ b/viscoll-api/spec/requests/leafs/leafs_update_multiple_spec.rb
@@ -0,0 +1,198 @@
+require 'rails_helper'
+
+describe "PUT /leafs", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ leaf_count = 4
+ @project = FactoryGirl.create(:project, user: @user)
+ @group = FactoryGirl.create(:group, project: @project)
+ @project.add_groupIDs([@group.id.to_s], 0)
+ @leafs = leaf_count.times.collect { FactoryGirl.create(:leaf, project: @project, material: 'Parchment', parentID: @group.id.to_s) }
+ leaf_count.times.each do |i|
+ params = {
+ conjoined_to: @leafs[-i-1].id.to_s
+ }
+ unless i == 0
+ params[:attached_above] = @leafs[i-1].id.to_s
+ end
+ unless i == leaf_count-1
+ params[:attached_below] = @leafs[i+1].id.to_s
+ end
+ @leafs[i].update(params)
+ end
+ @group.add_members(@leafs.collect { |leaf| leaf.id.to_s }, 0)
+ @parameters = {
+ "leafs": [
+ {
+ "id": @leafs[1].id.to_s,
+ "attributes": {
+ "material": "Paper",
+ "type": "Added",
+ "attached_above": @leafs[0].id.to_s,
+ "attached_below": @leafs[2].id.to_s
+ }
+ }
+ ],
+ "project_id": @project.id.to_s
+ }
+ end
+
+ it 'should set up properly' do
+ expect(true).to be true
+ expect(@leafs[0].conjoined_to).to eq @leafs[3].id.to_s
+ expect(@leafs[1].conjoined_to).to eq @leafs[2].id.to_s
+ expect(@leafs[2].conjoined_to).to eq @leafs[1].id.to_s
+ expect(@leafs[3].conjoined_to).to eq @leafs[0].id.to_s
+ expect(@leafs[1].attached_above).to eq @leafs[0].id.to_s
+ expect(@leafs[2].attached_above).to eq @leafs[1].id.to_s
+ expect(@leafs[3].attached_above).to eq @leafs[2].id.to_s
+ expect(@leafs[0].attached_below).to eq @leafs[1].id.to_s
+ expect(@leafs[1].attached_below).to eq @leafs[2].id.to_s
+ expect(@leafs[2].attached_below).to eq @leafs[3].id.to_s
+ end
+
+ context 'and valid authorization' do
+ context 'and standard leaf' do
+ before do
+ put '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @group.reload
+ @leafs.each { |leaf| leaf.reload }
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'updates the leaf' do
+ expect(@leafs[1].material).to eq 'Paper'
+ expect(@leafs[1].type).to eq 'Added'
+ end
+ end
+
+ context 'and missing project' do
+ before do
+ @parameters[:project_id] += 'missing'
+ put '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and missing page' do
+ before do
+ @parameters[:leafs][0][:id] += 'missing'
+ put '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and failed save' do
+ before do
+ allow_any_instance_of(Leaf).to receive(:update).and_return(false)
+ put '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and raised exception' do
+ before do
+ allow_any_instance_of(Leaf).to receive(:update).and_raise('MyException')
+ put '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and an unauthorized page' do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project.update(user: @user2)
+ put '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @group.reload
+ @leafs.each { |leaf| leaf.reload }
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should not edit the leaf' do
+ expect(@leafs[1].material).not_to eq 'Paper'
+ expect(@leafs[1].type).not_to eq 'Added'
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/leafs', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put '/leafs', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put '/leafs', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ delete "/leafs/#{@leafs[1].id.to_s}"
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/leafs/leafs_update_spec.rb b/viscoll-api/spec/requests/leafs/leafs_update_spec.rb
new file mode 100644
index 00000000..44b0b734
--- /dev/null
+++ b/viscoll-api/spec/requests/leafs/leafs_update_spec.rb
@@ -0,0 +1,149 @@
+require 'rails_helper'
+
+describe "PUT /leafs/:id", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, user: @user)
+ @group = FactoryGirl.create(:group, project: @project)
+ @project.add_groupIDs([@group.id.to_s], 0)
+ @leafs = 3.times.collect { FactoryGirl.create(:leaf, project: @project, material: 'Parchment', parentID: @group.id.to_s) }
+ @group.add_members(@leafs.collect { |leaf| leaf.id.to_s }, 0)
+ @parameters = {
+ "leaf": {
+ "material": "Paper",
+ "type": "Added",
+ "conjoined_to": @leafs[2].id.to_s,
+ "attached_below": "Sewn"
+ }
+ }
+ end
+
+ it 'should set up properly' do
+ expect(true).to be true
+ end
+
+ context 'and valid authorization' do
+ context 'and standard leaf' do
+ before do
+ put "/leafs/#{@leafs[0].id.to_s}", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @group.reload
+ @leafs.each { |leaf| leaf.reload }
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'edit and reconjoin the leaf' do
+ expect(@leafs[0].material).to eq 'Paper'
+ expect(@leafs[0].conjoined_to).to eq @leafs[2].id.to_s
+ expect(@leafs[0].attached_below).to eq 'Sewn'
+ expect(@leafs[1].attached_above).to eq 'Sewn'
+ expect(@leafs[2].conjoined_to).to eq @leafs[0].id.to_s
+ end
+ end
+
+ context 'and missing page' do
+ before do
+ put "/leafs/#{@leafs[0].id.to_s}waahoo", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'and failing params for the leaf' do
+ before do
+ allow_any_instance_of(Leaf).to receive(:update).and_return(false)
+ put "/leafs/#{@leafs[0].id.to_s}", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and an unauthorized page' do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project.update(user: @user2)
+ put "/leafs/#{@leafs[0].id.to_s}", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @group.reload
+ @leafs.each { |leaf| leaf.reload }
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should not edit the leaf' do
+ expect(@leafs[0].material).not_to eq 'Paper'
+ expect(@leafs[0].conjoined_to).not_to eq @leafs[2].id.to_s
+ expect(@leafs[2].conjoined_to).not_to eq @leafs[0].id.to_s
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put "/leafs/#{@leafs[0].id.to_s}", params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put "/leafs/#{@leafs[0].id.to_s}", params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put "/leafs/#{@leafs[0].id.to_s}", params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put "/leafs/#{@leafs[0].id.to_s}"
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/notes/notes_create_spec.rb b/viscoll-api/spec/requests/notes/notes_create_spec.rb
new file mode 100644
index 00000000..3c23e78e
--- /dev/null
+++ b/viscoll-api/spec/requests/notes/notes_create_spec.rb
@@ -0,0 +1,152 @@
+require 'rails_helper'
+
+describe "POST /notes", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {user: @user, noteTypes: ["Ink"]})
+ @parameters = {
+ "note": {
+ "project_id": @project.id.to_str,
+ "title": "some title for note",
+ "type": "Ink",
+ "description": "blue ink"
+ }
+ }
+ end
+
+ context 'and valid authorization' do
+ context 'and standard notes' do
+ before do
+ post '/notes', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'adds a note to the project' do
+ expect(@project.notes.length).to eq 1
+ expect(@project.notes[0].title).to eq "some title for note"
+ end
+ end
+
+ context 'and out-of-context notes' do
+ before do
+ @parameters[:note][:type] = "WAAHOO"
+ post '/notes', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'says what types are allowed' do
+ expect(@body['type']).to include('should be one of ["Ink"]')
+ end
+ end
+
+ context 'and missing project' do
+ before do
+ @parameters[:note][:project_id] += "WAAHOO"
+ post '/notes', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'gives the right error message' do
+ expect(@body['project_id']).to eq "project not found with id #{@parameters[:note][:project_id]}"
+ end
+ end
+
+ context 'and failing params for the note' do
+ before do
+ allow_any_instance_of(Note).to receive(:save).and_return(false)
+ post '/notes', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and an unauthorized project' do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project2 = FactoryGirl.create(:project, { user: @user2, noteTypes: ["Ink"] })
+ @parameters[:note][:project_id] = @project2.id.to_str
+ post '/notes', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should not add notes to the project' do
+ expect(@project2.notes).not_to include an_object_having_attributes({ title: "some title for note" })
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ post '/notes', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ post '/notes', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ post '/notes', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ post '/notes'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/notes/notes_create_type_spec.rb b/viscoll-api/spec/requests/notes/notes_create_type_spec.rb
new file mode 100644
index 00000000..d0f2ce3c
--- /dev/null
+++ b/viscoll-api/spec/requests/notes/notes_create_type_spec.rb
@@ -0,0 +1,147 @@
+require 'rails_helper'
+
+describe "POST /notes/type", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {user: @user, noteTypes: ["Ink"]})
+ @parameters = {
+ "noteType": {
+ "project_id": @project.id.to_str,
+ "type": "Paper"
+ }
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'with valid parameters' do
+ before do
+ post '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ end
+
+ it 'should return 200' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'should add the type to the project' do
+ expect(@project.noteTypes).to include "Ink"
+ expect(@project.noteTypes).to include "Paper"
+ end
+ end
+
+ context 'with missing project' do
+ before do
+ @parameters[:noteType][:project_id] += 'missing'
+ post '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'should return the right error message' do
+ expect(@body['project_id']).to eq "project not found with id #{@project.id}missing"
+ end
+ end
+
+ context 'with duplicated type' do
+ before do
+ @parameters[:noteType][:type] = "Ink"
+ post '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'should return the right error message' do
+ expect(@body['type']).to eq "Ink type already exists in the project"
+ end
+
+ it 'should leave the project alone' do
+ expect(@project.noteTypes).to eq ["Ink"]
+ end
+ end
+
+ context 'with unauthorized project' do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project2 = FactoryGirl.create(:project, {user: @user2, noteTypes: ["Ink"]})
+ @parameters[:noteType][:project_id] = @project2.id.to_str
+ post '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project2.reload
+ end
+
+ it 'should return 403' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should leave the types alone' do
+ expect(@project2.noteTypes).not_to include("Paper")
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ post '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ post '/notes/type', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ post '/notes/type', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ post '/notes/type'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/notes/notes_delete_type_spec.rb b/viscoll-api/spec/requests/notes/notes_delete_type_spec.rb
new file mode 100644
index 00000000..fb8a3481
--- /dev/null
+++ b/viscoll-api/spec/requests/notes/notes_delete_type_spec.rb
@@ -0,0 +1,163 @@
+require 'rails_helper'
+
+describe "DELETE /notes/type", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {user: @user, noteTypes: ["Ink", "Paper"]})
+ @project.notes << FactoryGirl.create(:note, {
+ project_id: @project.id,
+ type: "Ink",
+ description: "Sepia"
+ })
+ @project.notes << FactoryGirl.create(:note, {
+ project_id: @project.id,
+ type: "Paper",
+ description: "Parchment"
+ })
+ @project.save
+ @parameters = {
+ "noteType": {
+ "project_id": @project.id.to_str,
+ "type": "Ink"
+ }
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'with valid parameters' do
+ before do
+ delete '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ end
+
+ it 'should return 200' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'should remove the type from the project' do
+ expect(@project.noteTypes).not_to include "Ink"
+ expect(@project.noteTypes).to include "Paper"
+ end
+
+ it 'should change notes of the type to Unknown' do
+ expect(@project.notes).to include an_object_having_attributes(type: "Unknown")
+ expect(@project.notes).to include an_object_having_attributes(type: "Paper")
+ end
+ end
+
+ context 'with missing project' do
+ before do
+ @parameters[:noteType][:project_id] += 'missing'
+ delete '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'should return the right error message' do
+ expect(@body['project_id']).to eq "project not found with id #{@project.id}missing"
+ end
+ end
+
+ context 'with out-of-context type' do
+ before do
+ @parameters[:noteType][:type] = "Waahoo"
+ delete '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'should return the right error message' do
+ expect(@body['type']).to eq "Waahoo type doesn't exist in the project"
+ end
+
+ it 'should leave the project alone' do
+ expect(@project.noteTypes).to eq ["Ink", "Paper"]
+ end
+ end
+
+ context 'with unauthorized project' do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project.user = @user2
+ @project.save
+ delete '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ end
+
+ it 'should return 403' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should leave the types alone' do
+ expect(@project.noteTypes).to eq ["Ink", "Paper"]
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ delete '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ delete '/notes/type', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ delete '/notes/type', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ delete '/notes/type'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/notes/notes_destroy_spec.rb b/viscoll-api/spec/requests/notes/notes_destroy_spec.rb
new file mode 100644
index 00000000..1fd6c064
--- /dev/null
+++ b/viscoll-api/spec/requests/notes/notes_destroy_spec.rb
@@ -0,0 +1,124 @@
+require 'rails_helper'
+
+describe "DELETE /notes/id", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {
+ user: @user,
+ noteTypes: ["Ink"]
+ })
+ @note = FactoryGirl.create(:note, {
+ type: "Ink",
+ project: @project
+ })
+ @parameters = {}
+ end
+
+ context 'with valid authorization' do
+ context 'and valid note ID' do
+ before do
+ delete '/notes/'+@note.id, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'deletes the note' do
+ expect(Note.where(id: @note.id).exists?).to be false
+ end
+ end
+
+ context 'and invalid note ID' do
+ before do
+ delete '/notes/'+@note.id+'invalid', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context "and someone else's notes" do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project2 = FactoryGirl.create(:project, {
+ user: @user2,
+ noteTypes: ["Hand"]
+ })
+ @note2 = FactoryGirl.create(:note, {
+ type: "Hand",
+ project: @project2
+ })
+ delete '/notes/'+@note2.id, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'leaves the note alone' do
+ expect(Note.where(id: @note2.id).exists?).to be true
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ delete '/notes/'+@note.id, params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ delete '/notes/'+@note.id, params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ delete '/notes/'+@note.id, params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ delete '/notes/'+@note.id
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/notes/notes_link_spec.rb b/viscoll-api/spec/requests/notes/notes_link_spec.rb
new file mode 100644
index 00000000..8d3e4a97
--- /dev/null
+++ b/viscoll-api/spec/requests/notes/notes_link_spec.rb
@@ -0,0 +1,310 @@
+require 'rails_helper'
+
+describe "PUT /notes/id/link", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {user: @user, noteTypes: ["Ink"]})
+ @defaultGroup = FactoryGirl.create(:quire, project: @project)
+ @project.add_groupIDs([@defaultGroup.id.to_s], 0)
+ @note = FactoryGirl.create(:note, {
+ project: @project,
+ title: "some title for note",
+ type: "Ink",
+ description: "blue ink"
+ })
+ @parameters = {
+ objects: [
+ {
+ id: "something",
+ type: "Group"
+ }
+ ]
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'and correct group target' do
+ before do
+ @group = FactoryGirl.create(:group, { project: @project })
+ @project.add_groupIDs([@group.id.to_s], 0)
+ @parameters = {
+ objects: [
+ {
+ id: @group.id.to_str,
+ type: "Group"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/link', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @group.reload
+ end
+
+ it 'should return 200' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'should add the note to the target' do
+ expect(@group.notes.length).to eq 1
+ expect(@group.notes[0].id).to eq @note.id
+ end
+ end
+ context 'and correct leaf target' do
+ before do
+ @leaf = FactoryGirl.create(:leaf, { project: @project, parentID: @defaultGroup.id.to_s })
+ @defaultGroup.add_members([@leaf.id.to_s], 1)
+ @parameters = {
+ objects: [
+ {
+ id: @leaf.id.to_str,
+ type: "Leaf"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/link', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @leaf.reload
+ end
+
+ it 'should return 200' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'should add the note to the target' do
+ expect(@leaf.notes.length).to eq 1
+ expect(@leaf.notes[0].id).to eq @note.id
+ end
+ end
+ context 'and correct side target' do
+ before do
+ @leaf = FactoryGirl.create(:leaf, { project: @project })
+ @defaultGroup.add_members([@leaf.id.to_s], 1)
+ @side = @project.sides[0]
+ @parameters = {
+ objects: [
+ {
+ id: @side.id.to_str,
+ type: "Recto"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/link', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @side.reload
+ end
+
+ it 'should return 200' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'should add the note to the target' do
+ expect(@side.notes.length).to eq 1
+ expect(@side.notes[0].id).to eq @note.id
+ end
+ end
+ context 'and a project belonging to another user' do
+ before :each do
+ @user2 = FactoryGirl.create(:user)
+ @project2 = FactoryGirl.create(:project, { user: @user2, noteTypes: ["Ink"] })
+ end
+ context 'and group target' do
+ before do
+ @group2 = FactoryGirl.create(:group, { project: @project2 })
+ @parameters2 = {
+ objects: [
+ {
+ id: @group2.id.to_str,
+ type: "Group"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/link', params: @parameters2.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @group2.reload
+ end
+
+ it 'should return 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should leave the group alone' do
+ expect(@group2.notes).to be_empty
+ end
+ end
+ context 'and a leaf target' do
+ before do
+ @leaf2 = FactoryGirl.create(:leaf, { project: @project2 })
+ @defaultGroup.add_members([@leaf2.id.to_s], 1)
+ @parameters2 = {
+ objects: [
+ {
+ id: @leaf2.id.to_str,
+ type: "Leaf"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/link', params: @parameters2.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @leaf2.reload
+ end
+
+ it 'should return 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should leave the leaf alone' do
+ expect(@leaf2.notes).to be_empty
+ end
+ end
+ context 'and a side target' do
+ before do
+ @leaf2 = FactoryGirl.create(:leaf, { project: @project2 })
+ @defaultGroup.add_members([@leaf2.id.to_s], 1)
+ @side2 = @project2.sides[0]
+ @parameters2 = {
+ objects: [
+ {
+ id: @side2.id.to_str,
+ type: "Recto"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/link', params: @parameters2.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @side2.reload
+ end
+
+ it 'should return 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should leave the side alone' do
+ expect(@side2.notes).to be_empty
+ end
+ end
+ end
+ context 'and unknown target type' do
+ before do
+ @parameters[:objects][0][:type] = "unknown"
+ put '/notes/'+@note.id+'/link', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 422' do
+ expect(response).to have_http_status :unprocessable_entity
+ end
+
+ it 'should give the right error message' do
+ expect(@body['type']).to eq "object not found with type unknown"
+ end
+ end
+ context 'and missing note' do
+ before do
+ put '/notes/'+@note.id+'missing/link', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 404' do
+ expect(response).to have_http_status :not_found
+ end
+ end
+ context 'and missing target' do
+ before do
+ @group = FactoryGirl.create(:group, { project: @project })
+ @parameters = {
+ objects: [
+ {
+ id: @group.id.to_str+"weird",
+ type: "Group"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/link', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @group.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 422' do
+ expect(response).to have_http_status :unprocessable_entity
+ end
+
+ it 'should give the right error message' do
+ expect(@body['id']).to eq "Group object not found with id #{@group.id.to_str}weird"
+ end
+ end
+ context 'and uncaught exception' do
+ before do
+ allow_any_instance_of(Note).to receive(:save).and_raise("Exception!")
+ @group = FactoryGirl.create(:group, { project: @project })
+ @parameters = {
+ objects: [
+ {
+ id: @group.id.to_str,
+ type: "Group"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/link', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @group.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 422' do
+ expect(response).to have_http_status :unprocessable_entity
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/notes/'+@note.id+'/link', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put '/notes/'+@note.id+'/link', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put '/notes/'+@note.id+'/link', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put '/notes/'+@note.id+'/link'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/notes/notes_unlink_spec.rb b/viscoll-api/spec/requests/notes/notes_unlink_spec.rb
new file mode 100644
index 00000000..d8d8c8cd
--- /dev/null
+++ b/viscoll-api/spec/requests/notes/notes_unlink_spec.rb
@@ -0,0 +1,307 @@
+require 'rails_helper'
+
+describe "PUT /notes/id/unlink", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {user: @user, noteTypes: ["Ink"]})
+ @defaultGroup = FactoryGirl.create(:quire, project: @project)
+ @project.add_groupIDs([@defaultGroup.id.to_s], 0)
+ @note = FactoryGirl.create(:note, {
+ project: @project,
+ title: "some title for note",
+ type: "Ink",
+ description: "blue ink"
+ })
+ @parameters = {
+ objects: [
+ {
+ id: "something",
+ type: "Group"
+ }
+ ]
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'and correct group target' do
+ before do
+ @group = FactoryGirl.create(:group, { project: @project, notes: [@note] })
+ @project.add_groupIDs([@group.id.to_s], 0)
+ @parameters = {
+ objects: [
+ {
+ id: @group.id.to_str,
+ type: "Group"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @group.reload
+ end
+
+ it 'should return 200' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'should remove the note from the target' do
+ expect(@group.notes).to be_empty
+ end
+ end
+ context 'and correct leaf target' do
+ before do
+ @leaf = FactoryGirl.create(:leaf, { project: @project, notes: [@note] })
+ @defaultGroup.add_members([@leaf.id.to_s], 1)
+ @parameters = {
+ objects: [
+ {
+ id: @leaf.id.to_str,
+ type: "Leaf"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @leaf.reload
+ end
+
+ it 'should return 200' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'should remove the note from the target' do
+ expect(@leaf.notes).to be_empty
+ end
+ end
+ context 'and correct side target' do
+ before do
+ @leaf = FactoryGirl.create(:leaf, { project: @project, notes: [@note] })
+ @defaultGroup.add_members([@leaf.id.to_s], 1)
+ @side = @project.sides.find(@leaf.rectoID)
+ @side.notes << @note
+ @side.save
+ @parameters = {
+ objects: [
+ {
+ id: @side.id.to_str,
+ type: "Recto"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @side.reload
+ end
+
+ it 'should return 200' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'should remove the note from the target' do
+ expect(@side.notes).to be_empty
+ end
+ end
+ context 'and a project belonging to another user' do
+ before :each do
+ @user2 = FactoryGirl.create(:user)
+ @project2 = FactoryGirl.create(:project, { user: @user2, noteTypes: ["Ink"] })
+ end
+ context 'and group target' do
+ before do
+ @group2 = FactoryGirl.create(:group, { project: @project2 })
+ @parameters2 = {
+ objects: [
+ {
+ id: @group2.id.to_str,
+ type: "Group"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/unlink', params: @parameters2.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @group2.reload
+ end
+
+ it 'should return 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should leave the group alone' do
+ expect(@group2.notes).to be_empty
+ end
+ end
+ context 'and a leaf target' do
+ before do
+ @leaf2 = FactoryGirl.create(:leaf, { project: @project2 })
+ @defaultGroup.add_members([@leaf2.id.to_s], 1)
+ @parameters2 = {
+ objects: [
+ {
+ id: @leaf2.id.to_str,
+ type: "Leaf"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/unlink', params: @parameters2.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @leaf2.reload
+ end
+
+ it 'should return 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should leave the leaf alone' do
+ expect(@leaf2.notes).to be_empty
+ end
+ end
+ context 'and a side target' do
+ before do
+ @side2 = FactoryGirl.create(:side, { project: @project2 })
+ @parameters2 = {
+ objects: [
+ {
+ id: @side2.id.to_str,
+ type: "Recto"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/unlink', params: @parameters2.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @side2.reload
+ end
+
+ it 'should return 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should leave the side alone' do
+ expect(@side2.notes).to be_empty
+ end
+ end
+ end
+ context 'and unknown target type' do
+ before do
+ @parameters[:objects][0][:type] = "unknown"
+ put '/notes/'+@note.id+'/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 422' do
+ expect(response).to have_http_status :unprocessable_entity
+ end
+
+ it 'should give the right error message' do
+ expect(@body['type']).to eq "object not found with type unknown"
+ end
+ end
+ context 'and missing note' do
+ before do
+ put '/notes/'+@note.id+'missing/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 404' do
+ expect(response).to have_http_status :not_found
+ end
+ end
+ context 'and missing target' do
+ before do
+ @group = FactoryGirl.create(:group, { project: @project })
+ @parameters = {
+ objects: [
+ {
+ id: @group.id.to_str+"weird",
+ type: "Group"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @group.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 422' do
+ expect(response).to have_http_status :unprocessable_entity
+ end
+
+ it 'should give the right error message' do
+ expect(@body['id']).to eq "Group object not found with id #{@group.id.to_str}weird"
+ end
+ end
+ context 'and uncaught exception' do
+ before do
+ allow_any_instance_of(Note).to receive(:save).and_raise("Exception!")
+ @group = FactoryGirl.create(:group, { project: @project })
+ @parameters = {
+ objects: [
+ {
+ id: @group.id.to_str,
+ type: "Group"
+ }
+ ]
+ }
+ put '/notes/'+@note.id+'/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @group.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 422' do
+ expect(response).to have_http_status :unprocessable_entity
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/notes/'+@note.id+'/unlink', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put '/notes/'+@note.id+'/unlink', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put '/notes/'+@note.id+'/unlink', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put '/notes/'+@note.id+'/unlink'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/notes/notes_update_spec.rb b/viscoll-api/spec/requests/notes/notes_update_spec.rb
new file mode 100644
index 00000000..722ea894
--- /dev/null
+++ b/viscoll-api/spec/requests/notes/notes_update_spec.rb
@@ -0,0 +1,168 @@
+require 'rails_helper'
+
+describe "PUT /notes/id", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {
+ user: @user,
+ noteTypes: ["Ink"]
+ })
+ @note = FactoryGirl.create(:note, {
+ type: "Ink",
+ project: @project,
+ description: "vermilion"
+ })
+ @parameters = {
+ "note": {
+ "project_id": @project.id.to_str,
+ "title": "some title for note",
+ "type": "Ink",
+ "description": "sepia"
+ }
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'and valid note ID' do
+ before do
+ put '/notes/'+@note.id, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @note.reload
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'Updates the note' do
+ expect(@note.description).to eq "sepia"
+ end
+ end
+
+ context 'and invalid note ID' do
+ before do
+ put '/notes/'+@note.id+'invalid', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'and failed update' do
+ before do
+ allow_any_instance_of(Note).to receive(:update).and_return(false)
+ put '/notes/'+@note.id, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and out-of-context note type' do
+ before do
+ @parameters[:note][:type] = "waahoo"
+ put '/notes/'+@note.id, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @note.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'shows the available options' do
+ expect(@body['type']).to eq 'should be one of ["Ink"]'
+ end
+
+ it 'leaves the note alone' do
+ expect(@note.description).to eq "vermilion"
+ expect(@note.type).to eq "Ink"
+ end
+ end
+
+ context "and someone else's notes" do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project2 = FactoryGirl.create(:project, {
+ user: @user2,
+ noteTypes: ["Ink"]
+ })
+ @note2 = FactoryGirl.create(:note, {
+ type: "Ink",
+ project: @project2,
+ description: "Prussian blue"
+ })
+ put '/notes/'+@note2.id, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @note2.reload
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'leaves the note alone' do
+ expect(@note2.description).to eq "Prussian blue"
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/notes/'+@note.id, params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put '/notes/'+@note.id, params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put '/notes/'+@note.id, params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put '/notes/'+@note.id
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/notes/notes_update_type_spec.rb b/viscoll-api/spec/requests/notes/notes_update_type_spec.rb
new file mode 100644
index 00000000..71fdf4d9
--- /dev/null
+++ b/viscoll-api/spec/requests/notes/notes_update_type_spec.rb
@@ -0,0 +1,190 @@
+require 'rails_helper'
+
+describe "PUT /notes/type", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {
+ user: @user,
+ noteTypes: ["Ink", "Paper"]
+ })
+ @project.notes << FactoryGirl.create(:note, {
+ project_id: @project.id,
+ type: "Ink",
+ description: "Sepia"
+ })
+ @project.notes << FactoryGirl.create(:note, {
+ project_id: @project.id,
+ type: "Paper",
+ description: "Parchment"
+ })
+ @project.save
+ @parameters = {
+ "noteType": {
+ "project_id": @project.id.to_str,
+ "type": "New Paper",
+ "old_type": "Paper"
+ }
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'with valid parameters' do
+ before do
+ put '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ end
+
+ it 'should return 200' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'should remove the type from the project' do
+ expect(@project.noteTypes).to include "Ink"
+ expect(@project.noteTypes).to include "New Paper"
+ expect(@project.noteTypes).not_to include "Paper"
+ end
+
+ it 'should rename notes with that type' do
+ expect(@project.notes).to include an_object_having_attributes(type: "Ink")
+ expect(@project.notes).to include an_object_having_attributes(type: "New Paper")
+ expect(@project.notes).not_to include an_object_having_attributes(type: "Paper")
+ end
+ end
+
+ context 'with missing project' do
+ before do
+ @parameters[:noteType][:project_id] += 'missing'
+ put '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'should return the right error message' do
+ expect(@body['project_id']).to eq "project not found with id #{@project.id}missing"
+ end
+ end
+
+ context 'with out-of-context type' do
+ before do
+ @parameters[:noteType][:old_type] = "Waahoo"
+ put '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'should return the right error message' do
+ expect(@body['old_type']).to eq "Waahoo type doesn't exist in the project"
+ end
+
+ it 'should leave the project alone' do
+ expect(@project.noteTypes).to eq ["Ink", "Paper"]
+ end
+ end
+
+ context 'with duplicated target type' do
+ before do
+ @parameters[:noteType][:type] = "Ink"
+ put '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'should return the right error message' do
+ expect(@body['type']).to eq "Ink already exists in the project"
+ end
+
+ it 'should leave the project alone' do
+ expect(@project.noteTypes).to eq ["Ink", "Paper"]
+ end
+ end
+
+ context 'with unauthorized project' do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project.user = @user2
+ @project.save
+ put '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ end
+
+ it 'should return 403' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should leave the types alone' do
+ expect(@project.noteTypes).to eq ["Ink", "Paper"]
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/notes/type', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put '/notes/type', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put '/notes/type', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put '/notes/type'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/projects/create_manifest_projects_spec.rb b/viscoll-api/spec/requests/projects/create_manifest_projects_spec.rb
new file mode 100644
index 00000000..899ea3a2
--- /dev/null
+++ b/viscoll-api/spec/requests/projects/create_manifest_projects_spec.rb
@@ -0,0 +1,133 @@
+require 'rails_helper'
+
+describe "POST /projects/:id/manifests", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ stub_request(:get, 'https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest').with(headers: { 'Accept' => '*/*', 'User-Agent' => 'Ruby' }).to_return(status: 200, body: File.read(File.dirname(__FILE__) + '/../../fixtures/uoft_hollar.json'), headers: {})
+ end
+
+ before :each do
+ @parameters = {
+ "manifest": {
+ "url": "https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest"
+ }
+ }
+ @project = FactoryGirl.create(:project, { user: @user })
+ end
+
+ after :each do
+ @project.destroy
+ end
+
+ context 'with valid authorization' do
+ context 'with valid parameters' do
+ before do
+ post "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'adds the manifest' do
+ expect(@project.manifests.any? { |key, manifest| manifest['url'] == "https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest"}).to be true
+ end
+ end
+ context 'with missing project' do
+ before do
+ post "/projects/#{@project.id.to_str}missing/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+ context 'with unauthorized project' do
+ before do
+ @project.user = FactoryGirl.create(:user)
+ @project.save
+ post "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 403' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'leaves the project alone' do
+ expect(@project.manifests.any? { |key, manifest| manifest['url'] == "https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest"}).to be false
+ end
+ end
+ context 'with exception' do
+ before do
+ allow_any_instance_of(Project).to receive(:save).and_raise("WaahooException")
+ post "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 400' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'shows the exception' do
+ expect(@body['errors']).to eq "WaahooException"
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ post "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ post "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ post "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ post "/projects/#{@project.id.to_str}/manifests"
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/projects/create_projects_spec.rb b/viscoll-api/spec/requests/projects/create_projects_spec.rb
new file mode 100644
index 00000000..9fecbc91
--- /dev/null
+++ b/viscoll-api/spec/requests/projects/create_projects_spec.rb
@@ -0,0 +1,250 @@
+require 'rails_helper'
+
+describe "POST /projects", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @parameters = {
+ "project": {
+ "title": "Project Title"
+ },
+ "manuscript": {
+ "shelfmark": "Shelfmark",
+ "uri": "http://www.waahoo.net",
+ "date": "Early 15th Century"
+ },
+ "groups": [
+ {
+ "number": 1,
+ "leaves": 4,
+ "conjoin": true,
+ "oddLeaf": 1
+ },
+ {
+ "number": 2,
+ "leaves": 4,
+ "conjoin": false,
+ "oddLeaf": 1
+ }
+ ]
+ }
+ end
+
+ context 'with correct authorization' do
+ context 'and standard params' do
+ before do
+ post '/projects', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'returns a new project' do
+ expect(Project.find(id: @body["projects"][0]['id'])).not_to be nil
+ end
+ end
+
+ context 'and standard params with no groups' do
+ before do
+ @parameters.delete('groups')
+ post '/projects', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'returns a new project' do
+ expect(Project.find(id: @body["projects"][0]['id'])).not_to be nil
+ end
+ end
+
+ context 'and failing params for the project' do
+ before do
+ allow_any_instance_of(Project).to receive(:save).and_return(false)
+ post '/projects', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+
+ context 'and non-integer leaf count' do
+ before do
+ @parameters[:groups][0][:leaves] = "ULTRAWAAHOO"
+ post '/projects', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'shows the right leaves error' do
+ expect(@body['groups'][0]['leaves']).to include("should be an Integer")
+ end
+ end
+
+ context 'and negative leaf count' do
+ before do
+ @parameters[:groups][0][:leaves] = -583
+ post '/projects', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'shows the right leaves error' do
+ expect(@body['groups'][0]['leaves']).to include("should be greater than 0")
+ end
+ end
+
+ context 'and non-integer odd-leaf' do
+ before do
+ @parameters[:groups][0][:leaves] = 3;
+ @parameters[:groups][0][:oddLeaf] = "ULTRAWAAHOO"
+ post '/projects', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'shows the right odd-leaf error' do
+ expect(@body['groups'][0]['oddLeaf']).to include("should be an Integer")
+ end
+ end
+
+ context 'and negative odd-leaf' do
+ before do
+ @parameters[:groups][0][:leaves] = 3;
+ @parameters[:groups][0][:oddLeaf] = -2
+ post '/projects', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'shows the right odd-leaf error' do
+ expect(@body['groups'][0]['oddLeaf']).to include("should be greater than 0")
+ end
+ end
+
+ context 'and excessive odd-leaf' do
+ before do
+ @parameters[:groups][0][:leaves] = 3;
+ @parameters[:groups][0][:oddLeaf] = 5
+ post '/projects', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'shows the right odd-leaf error' do
+ expect(@body['groups'][0]['oddLeaf']).to include("cannot be greater than leaves")
+ end
+ end
+
+ context 'and non-Boolean conjoin' do
+ before do
+ @parameters[:groups][0][:conjoin] = "ULTRAWAAHOO"
+ post '/projects', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'shows the right leaves error' do
+ expect(@body['groups'][0]['conjoin']).to include("should be a Boolean")
+ end
+ end
+
+ context 'and a failed create' do
+ before do
+ allow_any_instance_of(Project).to receive(:save).and_raise("Exception")
+ post '/projects', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'includes the exception' do
+ expect(@body['errors']).to eq "Exception"
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ post '/projects', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ post '/projects', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ post '/projects', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ post '/projects'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/projects/delete_manifest_projects_spec.rb b/viscoll-api/spec/requests/projects/delete_manifest_projects_spec.rb
new file mode 100644
index 00000000..65780eff
--- /dev/null
+++ b/viscoll-api/spec/requests/projects/delete_manifest_projects_spec.rb
@@ -0,0 +1,160 @@
+require 'rails_helper'
+
+describe "DELETE /projects/:id/manifests", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ stub_request(:get, 'https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest').with(headers: { 'Accept' => '*/*', 'User-Agent' => 'Ruby' }).to_return(status: 200, body: File.read(File.dirname(__FILE__) + '/../../fixtures/uoft_hollar.json'), headers: {})
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {
+ user: @user,
+ manifests: { "59ee3c623b0eb75251207cfe": { id: "59ee3c623b0eb75251207cfe", name: 'ASDF', url: 'https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest', images: [{label: "IMAGE", url: "http://www.example.com/iiif-sample"}]} }
+ })
+ @defaultGroup = FactoryGirl.create(:quire, project: @project)
+ @project[:groupIDs] = [@defaultGroup.id.to_s]
+ @leaf1 = FactoryGirl.create(:leaf, {project: @project})
+ @defaultGroup.add_members([@leaf1.id.to_s], 1)
+ @side1 = @project.sides.find(@leaf1.rectoID)
+ @side1.image = { label: "IMAGE", manifestID: "59ee3c623b0eb75251207cfe", url: "http://www.example.com/iiif-sample" }
+ @side1.save
+ @side2 = @project.sides.find(@leaf1.versoID)
+ @parameters = {
+ "manifest": {
+ "id": "59ee3c623b0eb75251207cfe"
+ }
+ }
+ end
+
+ after :each do
+ @project.destroy
+ end
+
+ context 'with valid authorization' do
+ context 'with valid parameters' do
+ before do
+ delete "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'removes the manifest' do
+ expect(@project.manifests.any? { |key, manifest| manifest['url'] == "https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest"}).to be false
+ end
+ end
+ context 'with missing project' do
+ before do
+ delete "/projects/#{@project.id.to_str}missing/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+ context 'with missing manifest' do
+ before do
+ @parameters[:manifest][:id] += 'missing'
+ delete "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'gives the right error' do
+ expect(@body['error']).to eq "Manifest with id: 59ee3c623b0eb75251207cfemissing not found in project with id: #{@project.id.to_str}."
+ end
+ end
+ context 'with unauthorized project' do
+ before do
+ @project.user = FactoryGirl.create(:user)
+ @project.save
+ delete "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ end
+
+ it 'returns 403' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'leaves the project alone' do
+ expect(@project.manifests.any? { |key, manifest| manifest['url'] == "https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest"}).to be true
+ end
+ end
+ context 'with exception' do
+ before do
+ allow_any_instance_of(Project).to receive(:save).and_raise("WaahooException")
+ delete "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 400' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'shows the exception' do
+ expect(@body['errors']).to eq "WaahooException"
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ delete "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ delete "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ delete "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ delete "/projects/#{@project.id.to_str}/manifests"
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/projects/destroy_projects_spec.rb b/viscoll-api/spec/requests/projects/destroy_projects_spec.rb
new file mode 100644
index 00000000..5f47a313
--- /dev/null
+++ b/viscoll-api/spec/requests/projects/destroy_projects_spec.rb
@@ -0,0 +1,146 @@
+require 'rails_helper'
+
+describe "DELETE /projects/id", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @user2 = FactoryGirl.create(:user)
+ @project1 = FactoryGirl.create(:project, {:user => @user})
+ @project2 = FactoryGirl.create(:project, {:user => @user})
+ @project3 = FactoryGirl.create(:project, {:user => @user2})
+ @deleteParameters = {
+ deleteUnlinkedImages: false,
+ }
+ end
+
+ context 'with correct authorization' do
+ context 'and standard params' do
+ before do
+ delete '/projects/'+@project1.id, params: @deleteParameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'returns the remaining project' do
+ expect(@body["projects"].length).to equal 1
+ expect(@body["projects"][0]['id']).to eq @project2.id.to_str
+ end
+
+ it 'leaves only the undeleted projects' do
+ expect(Project.where(id: @project1.id).exists?).to be false
+ expect(Project.where(id: @project2.id).exists?).to be true
+ expect(Project.where(id: @project3.id).exists?).to be true
+ end
+ end
+
+ context 'and inexistent project' do
+ before do
+ delete '/projects/NONEXISTENT', params: @deleteParameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+
+ it 'should not remove anything' do
+ expect(Project.where(id: @project1.id).exists?).to be true
+ expect(Project.where(id: @project2.id).exists?).to be true
+ expect(Project.where(id: @project3.id).exists?).to be true
+ end
+ end
+
+ context "and somebody else's project" do
+ before do
+ delete '/projects/'+@project3.id, params: @deleteParameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should not remove anything' do
+ expect(Project.where(id: @project1.id).exists?).to be true
+ expect(Project.where(id: @project2.id).exists?).to be true
+ expect(Project.where(id: @project3.id).exists?).to be true
+ end
+ end
+
+ context 'and a failed delete' do
+ before do
+ allow_any_instance_of(Project).to receive(:destroy).and_raise("Exception")
+ delete '/projects/'+@project1.id, params: @deleteParameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'includes the exception' do
+ expect(@body['errors']).to eq "Exception"
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ delete '/projects/'+@project1.id, params: @deleteParameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ delete '/projects/'+@project1.id, params: @deleteParameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ delete '/projects/'+@project1.id, params: @deleteParameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ delete '/projects/'+@project1.id
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/projects/export_projects_spec.rb b/viscoll-api/spec/requests/projects/export_projects_spec.rb
new file mode 100644
index 00000000..8e0deb40
--- /dev/null
+++ b/viscoll-api/spec/requests/projects/export_projects_spec.rb
@@ -0,0 +1,281 @@
+require 'rails_helper'
+
+describe "GET /projects/:id/export/:format", :type => :request do
+ before do
+ stub_request(:get, 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest').with(headers: { 'Accept' => '*/*', 'User-Agent' => 'Ruby' }).to_return(status: 200, body: File.read(File.dirname(__FILE__) + '/../../fixtures/villanova_boston.json'), headers: {})
+ # Set up an account and allow sign-in
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ # Create project
+ @project = FactoryGirl.create(:project,
+ user: @user,
+ 'title' => 'Sample project',
+ 'shelfmark' => 'Ravenna 384.2339',
+ 'metadata' => { date: '18th century' },
+ 'preferences' => { 'showTips' => true },
+ 'noteTypes' => ['Ink', 'Unknown'],
+ 'manifests' => { '12341234': { 'id' => '12341234', 'url' => 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest', 'name' => 'Boston, and Bunker Hill.' } }
+ )
+ # Attach group with 2 leafs - (group with 2 leafs) - 2 conjoined leafs, 1 image
+ @testgroup = FactoryGirl.create(:group, project: @project, nestLevel: 1, title: 'Group 1', direction: 'left-to-right')
+ @upleafs = 2.times.collect { FactoryGirl.create(:leaf, project: @project, parentID: @testgroup.id.to_s, nestLevel: 1) }
+ @testmidgroup = FactoryGirl.create(:group, project: @project, parentID: @testgroup.id.to_s, nestLevel: 2, title: 'Group 2', direction: 'left-to-right')
+ @midleafs = 2.times.collect { FactoryGirl.create(:leaf, project: @project, parentID: @testmidgroup.id.to_s, nestLevel: 2) }
+ @botleafs = 2.times.collect { FactoryGirl.create(:leaf, project: @project, parentID: @testgroup.id.to_s, nestLevel: 1) }
+ @botleafs[1].update(type: 'Endleaf')
+ @project.add_groupIDs([@testgroup.id.to_s, @testmidgroup.id.to_s], 0)
+ @testgroup.add_members([@upleafs[0].id.to_s, @upleafs[1].id.to_s, @testmidgroup.id.to_s, @botleafs[0].id.to_s, @botleafs[1].id.to_s], 0)
+ @testmidgroup.add_members([@midleafs[0].id.to_s, @midleafs[1].id.to_s], 0)
+ @testnote = FactoryGirl.create(:note, project: @project, title: 'Test Note', type: 'Ink', description: 'This is a test', show: true, objects: {Group: [@testgroup.id.to_s], Leaf: [@botleafs[0].id.to_s], Recto: [@botleafs[0].rectoID], Verso: [@botleafs[0].versoID]})
+ @testimage = FactoryGirl.create(:pixel, user: @user, projectIDs: [@project.id.to_s], sideIDs: [@upleafs[0].rectoID], filename: 'pixel.png')
+ Side.find(@upleafs[0].rectoID).update(image: {
+ manifestID: 'DIYImages',
+ label: "Pixel",
+ url: "https://dummy.library.utoronto.ca/images/#{@testimage.id}_pixel.png"
+ })
+ end
+
+ before :each do
+ @format = 'json'
+ end
+
+ before :all do
+ imagePath = "#{Rails.root}/public/uploads"
+ File.new(imagePath+'/pixel', 'w')
+ end
+
+ after :all do
+ imagePath = "#{Rails.root}/public/uploads"
+ if File.file?(imagePath+'/pixel')
+ File.delete(imagePath+'/pixel')
+ end
+ Dir.glob(imagePath+'/*.zip').each { |file| File.delete(file) }
+ end
+
+ context 'with valid authorization' do
+ context 'for JSON export' do
+ before do
+ @format = 'json'
+ get "/projects/#{@project.id}/export/#{@format}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'should have expected content' do
+ export_result = @body['Export']
+ image_result = @body['Images']
+ expect(export_result['project']).to eq({
+ 'title' => 'Sample project',
+ 'shelfmark' => 'Ravenna 384.2339',
+ 'metadata' => { 'date' => '18th century' },
+ 'preferences' => { 'showTips' => true },
+ 'manifests' => { '12341234' => { 'id' => '12341234', 'url' => 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest', 'name' => 'Boston, and Bunker Hill.' } },
+ 'noteTypes' => ['Ink', 'Unknown']
+ })
+ expect(export_result['Groups']).to eq({
+ '1' => {'params'=>{'type'=>"Quire", 'title'=>"Group 1", 'direction'=>"left-to-right", 'nestLevel'=>1}, 'tacketed'=>[], 'sewing'=>[], 'parentOrder'=>nil, 'memberOrders'=>["Leaf_1", "Leaf_2", "Group_2", "Leaf_5", "Leaf_6"]},
+ '2' => {'params'=>{'type'=>"Quire", 'title'=>"Group 2", 'direction'=>"left-to-right", 'nestLevel'=>2}, 'tacketed'=>[], 'sewing'=>[], 'parentOrder'=>1, 'memberOrders'=>["Leaf_3", "Leaf_4"]}
+ })
+ expect(export_result['Leafs']).to eq({
+ '1' => {'params'=>{'material'=>"Paper", 'type'=>"Original", 'attached_above'=>"None", 'attached_below'=>"None", 'stub'=>"None", 'nestLevel'=>1}, 'conjoined_leaf_order'=>nil, 'parentOrder'=>1, 'rectoOrder'=>1, 'versoOrder'=>1},
+ '2' => {'params'=>{'material'=>"Paper", 'type'=>"Original", 'attached_above'=>"None", 'attached_below'=>"None", 'stub'=>"None", 'nestLevel'=>1}, 'conjoined_leaf_order'=>nil, 'parentOrder'=>1, 'rectoOrder'=>2, 'versoOrder'=>2},
+ '3' => {'params'=>{'material'=>"Paper", 'type'=>"Original", 'attached_above'=>"None", 'attached_below'=>"None", 'stub'=>"None", 'nestLevel'=>2}, 'conjoined_leaf_order'=>nil, 'parentOrder'=>2, 'rectoOrder'=>3, 'versoOrder'=>3},
+ '4' => {'params'=>{'material'=>"Paper", 'type'=>"Original", 'attached_above'=>"None", 'attached_below'=>"None", 'stub'=>"None", 'nestLevel'=>2}, 'conjoined_leaf_order'=>nil, 'parentOrder'=>2, 'rectoOrder'=>4, 'versoOrder'=>4},
+ '5' => {'params'=>{'material'=>"Paper", 'type'=>"Original", 'attached_above'=>"None", 'attached_below'=>"None", 'stub'=>"None", 'nestLevel'=>1}, 'conjoined_leaf_order'=>nil, 'parentOrder'=>1, 'rectoOrder'=>5, 'versoOrder'=>5},
+ '6' => {'params'=>{'material'=>"Paper", 'type'=>"Endleaf", 'attached_above'=>"None", 'attached_below'=>"None", 'stub'=>"None", 'nestLevel'=>1}, 'conjoined_leaf_order'=>nil, 'parentOrder'=>1, 'rectoOrder'=>6, 'versoOrder'=>6}
+ })
+ expect(export_result['Rectos']).to eq({
+ '1' => {'params'=>{'folio_number'=>"", 'page_number'=>"", 'texture'=>"None", 'image'=>{'manifestID' => 'DIYImages', 'label' => "Pixel", 'url' => "https://dummy.library.utoronto.ca/images/#{@testimage.id}_pixel.png"}, 'script_direction'=>"None"}, 'parentOrder'=>1},
+ '2' => {'params'=>{'folio_number'=>"", 'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>2},
+ '3' => {'params'=>{'folio_number'=>"", 'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>3},
+ '4' => {'params'=>{'folio_number'=>"", 'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>4},
+ '5' => {'params'=>{'folio_number'=>"", 'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>5},
+ '6' => {'params'=>{'folio_number'=>"", 'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>6}
+ })
+ expect(export_result['Versos']).to eq({
+ '1' => {'params'=>{'folio_number'=>"", 'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>1},
+ '2' => {'params'=>{'folio_number'=>"", 'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>2},
+ '3' => {'params'=>{'folio_number'=>"", 'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>3},
+ '4' => {'params'=>{'folio_number'=>"", 'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>4},
+ '5' => {'params'=>{'folio_number'=>"", 'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>5},
+ '6' => {'params'=>{'folio_number'=>"", 'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>6}
+ })
+ expect(export_result['Notes']).to eq({
+ '1' => {'params'=>{'title'=>"Test Note", 'type'=>"Ink", 'description'=>"This is a test", 'show'=>true}, 'objects'=>{'Group'=>[1], 'Leaf'=>[5], 'Recto'=>[5], 'Verso'=>[5]}}
+ })
+ expect(image_result['exportedImages']).to eq("https://dummy.library.utoronto.ca/api/images/zip/#{@project.id}")
+ end
+ end
+
+ context 'for XML export' do
+ before do
+ @format = 'xml'
+ get "/projects/#{@project.id}/export/#{@format}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'should have expected content' do
+ expect(@body['type']).to eq 'xml'
+ expect(@body['Images']['exportedImages']).to eq("https://dummy.library.utoronto.ca/api/images/zip/#{@project.id}")
+ result = Nokogiri::XML(@body['data'])
+ # Metadata elements
+ expect(result.css("manuscript title").text).to eq 'Sample project'
+ expect(result.css("manuscript shelfmark").text).to eq 'Ravenna 384.2339'
+ expect(result.css("manuscript date").text).to eq '18th century'
+ expect(result.css("taxonomy[xml|id='manuscript_preferences'] term").collect { |t| [t['xml:id'], t.text] }).to include(
+ ['manuscript_preferences_ravenna_384_2339_showTips', 'true']
+ )
+ expect(result.css("taxonomy[xml|id='manifests'] term").collect { |t| [t['xml:id'], t.text] }).to include(
+ ['manifest_12341234', 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest']
+ )
+ # Quires
+ expect(result.css("taxonomy[xml|id='group_type'] term").collect { |t| [t['xml:id'], t.text] }).to include(
+ ['group_type_quire', 'Quire']
+ )
+ expect(result.css("taxonomy[xml|id='group_title'] term").collect { |t| [t['xml:id'], t.text] }).to include(
+ ['group_title_group_1', 'Group 1'],
+ ['group_title_group_2', 'Group 2'],
+ )
+ expect(result.css("taxonomy[xml|id='group_members'] term").collect { |t| [t['xml:id'], t.text] }).to include(
+ ['group_members_ravenna_384_2339-q-1', '#ravenna_384_2339-1-1 #ravenna_384_2339-1-2 #ravenna_384_2339-q-1-2 #ravenna_384_2339-1-3 #ravenna_384_2339-1-4'],
+ ['group_members_ravenna_384_2339-q-1-2', '#ravenna_384_2339-1-2-3 #ravenna_384_2339-1-2-4'],
+ )
+ # Leaves
+ expect(result.css("taxonomy[xml|id='leaf_material'] term").collect { |t| [t['xml:id'], t.text] }).to include(
+ ['leaf_material_paper', 'Paper']
+ )
+ expect(result.css("manuscript leaf").collect { |t| [t['xml:id'], t.css('folioNumber').first.text, t.css('q').first['target'], t.css('q').first['n']] }).to include(
+ ['ravenna_384_2339-1-1', '1', '#ravenna_384_2339-q-1', '1'],
+ ['ravenna_384_2339-1-2', '2', '#ravenna_384_2339-q-1', '1'],
+ ['ravenna_384_2339-1-2-3', '3', '#ravenna_384_2339-q-1-2', '2'],
+ ['ravenna_384_2339-1-2-4', '4', '#ravenna_384_2339-q-1-2', '2'],
+ ['ravenna_384_2339-1-3', '5', '#ravenna_384_2339-q-1', '1'],
+ ['ravenna_384_2339-1-4', '6', '#ravenna_384_2339-q-1', '1']
+ )
+ # Sides and Notes
+
+ expect(result.css("mapping map").collect { |t| [t['target'], t['side'], t.css('term').first['target']]}).to include(
+ ['#ravenna_384_2339-1-1', 'recto', '#side_page_number_EMPTY https://dummy.library.utoronto.ca/images/'+@testimage.id.to_s+'_pixel.png #manifest_DIYImages'],
+ ['#ravenna_384_2339-1-2', 'recto', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-2-3', 'recto', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-2-4', 'recto', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-3', 'recto', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-4', 'recto', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-1', 'verso', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-2', 'verso', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-2-3', 'verso', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-2-4', 'verso', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-3', 'verso', '#side_page_number_EMPTY'],
+ ['#ravenna_384_2339-1-4', 'verso', '#side_page_number_EMPTY']
+ )
+ expect(result.css("mapping map").collect { |t| [t['target'], t.css('term').first['target']]}).to include(
+ ['#ravenna_384_2339-n-1', '#note_title_test_note #note_show'],
+ )
+ end
+ end
+
+ context 'with missing project' do
+ before do
+ get "/projects/#{@project.id}missing/export/#{@format}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+
+ it 'should show error' do
+ expect(@body['error']).to eq "project not found with id #{@project.id}missing"
+ end
+ end
+
+ context 'with unauthorized project' do
+ before do
+ @project.update(user: FactoryGirl.create(:user))
+ get "/projects/#{@project.id}/export/#{@format}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'should return 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'with invalid format' do
+ before do
+ @format = 'waahoo'
+ get "/projects/#{@project.id}/export/#{@format}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'should return 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'should show error' do
+ expect(@body['error']).to eq "Export format must be one of [json, xml]"
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ get "/projects/#{@project.id}/export/#{@format}", headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ get "/projects/#{@project.id}/export/#{@format}", headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ get "/projects/#{@project.id}/export/#{@format}", headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put '/projects/import'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/projects/filter_projects_spec.rb b/viscoll-api/spec/requests/projects/filter_projects_spec.rb
new file mode 100644
index 00000000..e7ed6948
--- /dev/null
+++ b/viscoll-api/spec/requests/projects/filter_projects_spec.rb
@@ -0,0 +1,1018 @@
+require 'rails_helper'
+
+describe "PUT /projects/:id/filter", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ @user2 = FactoryGirl.create(:user, {:password => "user2"})
+ @project1 = FactoryGirl.create(:codex_project, :user => @user, :quire_structure => [[4, 6]])
+ @project2 = FactoryGirl.create(:codex_project, :user => @user2, :quire_structure => [[4, 6]])
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @parameters = {
+ "queries": [
+ {
+ }
+ ]
+ }
+ end
+
+ it 'should be sane' do
+ expect(@project1.groups.count).to eq 4
+ expect(@project1.groups.collect { |g| g.id }.count).to eq 4
+ end
+
+ context 'with correct authorization' do
+ context 'and group-based queries' do
+ context 'equals one' do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "group",
+ "attribute": "title",
+ "condition": "equals",
+ "values": [ @project1.groups[0].title ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Groups']).to include(@project1.groups[0].id.to_s)
+ expect(body['Groups']).not_to include(@project2.groups[0].id.to_s)
+ end
+ end
+
+ context 'equals multiple' do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "group",
+ "attribute": "title",
+ "condition": "equals",
+ "values": [ @project1.groups[0].title, @project2.groups[0].title ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Groups']).to include(@project1.groups[0].id.to_s)
+ expect(body['Groups']).not_to include(@project2.groups[0].id.to_s)
+ end
+ end
+
+ context 'contains one' do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "group",
+ "attribute": "title",
+ "condition": "contains",
+ "values": [ @project1.groups[0].title ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Groups']).to include(@project1.groups[0].id.to_s)
+ expect(body['Groups']).not_to include(@project2.groups[0].id.to_s)
+ end
+ end
+
+ context 'contains multiple' do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "group",
+ "attribute": "title",
+ "condition": "contains",
+ "values": [ @project1.groups[0].title, @project2.groups[0].title ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Groups']).to include(@project1.groups[0].id.to_s)
+ expect(body['Groups']).not_to include(@project2.groups[0].id.to_s)
+ end
+ end
+
+ context 'not equals one' do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "group",
+ "attribute": "title",
+ "condition": "not equals",
+ "values": [ @project1.groups[0].title ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Groups']).not_to include(@project1.groups[0].id.to_s)
+ @project1.groups[1..-1].each do |should_have_group|
+ expect(body['Groups']).to include(should_have_group.id.to_s)
+ end
+ end
+ end
+
+ context 'not equals multiple' do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "group",
+ "attribute": "title",
+ "condition": "not equals",
+ "values": [ @project1.groups[0].title, @project1.groups[1].title ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Groups']).not_to include(@project1.groups[0].id.to_s)
+ expect(body['Groups']).not_to include(@project1.groups[1].id.to_s)
+ @project1.groups[2..-1].each do |should_have_group|
+ expect(body['Groups']).to include(should_have_group.id.to_s)
+ end
+ end
+ end
+
+ context 'not contains one' do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "group",
+ "attribute": "title",
+ "condition": "not contains",
+ "values": [ @project1.groups[0].title ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Groups']).not_to include(@project1.groups[0].id.to_s)
+ @project1.groups[1..-1].each do |should_have_group|
+ expect(body['Groups']).to include(should_have_group.id.to_s)
+ end
+ end
+ end
+
+ context 'not contains multiple' do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "group",
+ "attribute": "title",
+ "condition": "not contains",
+ "values": [ @project1.groups[0].title, @project1.groups[1].title ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Groups']).not_to include(@project1.groups[0].id.to_s)
+ expect(body['Groups']).not_to include(@project1.groups[1].id.to_s)
+ @project1.groups[2..-1].each do |should_have_group|
+ expect(body['Groups']).to include(should_have_group.id.to_s)
+ end
+ end
+ end
+ end
+
+ context 'and leaf-based queries' do
+ context 'equals one' do
+ before do
+ @project1.leafs[5].update(material: 'Copy paper')
+ @project2.leafs[5].update(material: 'Copy paper')
+ @parameters = {
+ "queries": [
+ {
+ "type": "leaf",
+ "attribute": "material",
+ "condition": "equals",
+ "values": [ 'Copy paper' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Leafs']).to eq [@project1.leafs[5].id.to_s]
+ end
+ end
+
+ context 'equals multiple' do
+ before do
+ @project1.leafs[5].update(material: 'Copy paper')
+ @project1.leafs[13].update(material: 'Copy paper')
+ @project1.leafs[16].update(material: 'Plastic')
+ @project2.leafs[5].update(material: 'Copy paper')
+ @project2.leafs[13].update(material: 'Copy paper')
+ @project2.leafs[16].update(material: 'Plastic')
+ @parameters = {
+ "queries": [
+ {
+ "type": "leaf",
+ "attribute": "material",
+ "condition": "equals",
+ "values": [ 'Copy paper', 'Plastic' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Leafs'].length).to eq 3
+ expect(body['Leafs']).to include(@project1.leafs[5].id.to_s)
+ expect(body['Leafs']).to include(@project1.leafs[13].id.to_s)
+ expect(body['Leafs']).to include(@project1.leafs[16].id.to_s)
+ end
+ end
+
+ context 'not equals one' do
+ before do
+ @project1.leafs[5].update(material: 'Copy paper')
+ @project2.leafs[5].update(material: 'Copy paper')
+ @parameters = {
+ "queries": [
+ {
+ "type": "leaf",
+ "attribute": "material",
+ "condition": "not equals",
+ "values": [ 'Copy paper' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Leafs'].count).to eq @project1.leafs.count-1
+ expect(body['Leafs']).not_to include(@project1.leafs[5].id.to_s)
+ expect(body['Leafs']).not_to include(@project2.leafs[5].id.to_s)
+ end
+ end
+
+ context 'not equals multiple' do
+ before do
+ @project1.leafs[5].update(material: 'Copy paper')
+ @project1.leafs[13].update(material: 'Copy paper')
+ @project1.leafs[16].update(material: 'Plastic')
+ @project2.leafs[5].update(material: 'Copy paper')
+ @project2.leafs[13].update(material: 'Copy paper')
+ @project2.leafs[16].update(material: 'Plastic')
+ @parameters = {
+ "queries": [
+ {
+ "type": "leaf",
+ "attribute": "material",
+ "condition": "not equals",
+ "values": [ 'Copy paper', 'Plastic' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Leafs'].count).to eq @project1.leafs.count-3
+ expect(body['Leafs']).not_to include(@project1.leafs[5].id.to_s)
+ expect(body['Leafs']).not_to include(@project1.leafs[13].id.to_s)
+ expect(body['Leafs']).not_to include(@project1.leafs[16].id.to_s)
+ expect(body['Leafs']).not_to include(@project2.leafs[5].id.to_s)
+ expect(body['Leafs']).not_to include(@project2.leafs[13].id.to_s)
+ expect(body['Leafs']).not_to include(@project2.leafs[16].id.to_s)
+ end
+ end
+
+ context 'with legacy conjoined_leaf_order attribute' do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "leaf",
+ "attribute": "conjoined_leaf_order",
+ "condition": "equals",
+ "values": [ @project1.leafs[-1].conjoined_to ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Leafs']).to eq [@project1.leafs[-1].id.to_s]
+ end
+ end
+ end
+
+ context 'and side-based queries' do
+ context 'equals one' do
+ before do
+ @project1.sides[7].update(script_direction: 'Top-To-Bottom')
+ @project2.sides[7].update(script_direction: 'Top-To-Bottom')
+ @parameters = {
+ "queries": [
+ {
+ "type": "side",
+ "attribute": "script_direction",
+ "condition": "equals",
+ "values": [ 'Top-To-Bottom' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Sides']).to eq [@project1.sides[7].id.to_s]
+ end
+ end
+
+ context 'equals multiple' do
+ before do
+ @project1.sides[7].update(script_direction: 'Top-To-Bottom')
+ @project1.sides[10].update(script_direction: 'Left-To-Right')
+ @project2.sides[7].update(script_direction: 'Top-To-Bottom')
+ @parameters = {
+ "queries": [
+ {
+ "type": "side",
+ "attribute": "script_direction",
+ "condition": "equals",
+ "values": [ 'Top-To-Bottom', 'Left-To-Right' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Sides'].count).to eq 2
+ expect(body['Sides']).to include @project1.sides[7].id.to_s
+ expect(body['Sides']).to include @project1.sides[10].id.to_s
+ end
+ end
+
+ context 'not equals one' do
+ before do
+ @project1.sides[7].update(script_direction: 'Top-To-Bottom')
+ @project2.sides[7].update(script_direction: 'Top-To-Bottom')
+ @parameters = {
+ "queries": [
+ {
+ "type": "side",
+ "attribute": "script_direction",
+ "condition": "not equals",
+ "values": [ 'Top-To-Bottom' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Sides'].count).to eq @project1.sides.count-1
+ expect(body['Sides']).not_to include @project1.sides[7].id.to_s
+ end
+ end
+
+ context 'not equals multiple' do
+ before do
+ @project1.sides[7].update(script_direction: 'Top-To-Bottom')
+ @project1.sides[10].update(script_direction: 'Left-To-Right')
+ @project2.sides[7].update(script_direction: 'Top-To-Bottom')
+ @parameters = {
+ "queries": [
+ {
+ "type": "side",
+ "attribute": "script_direction",
+ "condition": "not equals",
+ "values": [ 'Top-To-Bottom', 'Left-To-Right' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Sides'].count).to eq @project1.sides.count-2
+ expect(body['Sides']).not_to include @project1.sides[7].id.to_s
+ expect(body['Sides']).not_to include @project1.sides[10].id.to_s
+ end
+ end
+
+ context 'contains one' do
+ before do
+ @project1.sides[9].update(folio_number: 'FN0')
+ @parameters = {
+ "queries": [
+ {
+ "type": "side",
+ "attribute": "folio_number",
+ "condition": "contains",
+ "values": [ 'FN' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Sides']).to eq [@project1.sides[9].id.to_s]
+ end
+ end
+
+ context 'contains multiple' do
+ before do
+ @project1.sides[6].update(folio_number: 'FN0')
+ @project1.sides[11].update(folio_number: 'QR1')
+ @project2.sides[7].update(folio_number: 'FN0')
+ @parameters = {
+ "queries": [
+ {
+ "type": "side",
+ "attribute": "folio_number",
+ "condition": "contains",
+ "values": [ 'FN', 'QR' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Sides'].count).to eq 2
+ expect(body['Sides']).to include @project1.sides[6].id.to_s
+ expect(body['Sides']).to include @project1.sides[11].id.to_s
+ end
+ end
+
+ context 'not contains one' do
+ before do
+ @project1.sides[9].update(folio_number: 'FN0')
+ @parameters = {
+ "queries": [
+ {
+ "type": "side",
+ "attribute": "folio_number",
+ "condition": "not contains",
+ "values": [ 'FN' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Sides'].count).to eq @project1.sides.count-1
+ expect(body['Sides']).not_to include @project1.sides[9].id.to_s
+ end
+ end
+
+ context 'not contains multiple' do
+ before do
+ @project1.sides[6].update(folio_number: 'FN0')
+ @project1.sides[11].update(folio_number: 'QR1')
+ @project2.sides[7].update(folio_number: 'FN0')
+ @parameters = {
+ "queries": [
+ {
+ "type": "side",
+ "attribute": "folio_number",
+ "condition": "not contains",
+ "values": [ 'FN', 'QR' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Sides'].count).to eq @project1.sides.count-2
+ expect(body['Sides']).not_to include @project1.sides[6].id.to_s
+ expect(body['Sides']).not_to include @project1.sides[11].id.to_s
+ expect(body['Sides']).not_to include @project2.sides[7].id.to_s
+ end
+ end
+ end
+
+ context 'and note-based queries' do
+ before do
+ @note1 = FactoryGirl.create(:note, project_id: @project1.id, attachments: [@project1.groups[1], @project1.leafs[5], @project1.sides[14], @project1.sides[15]], title: "ULTRA WAAHOO")
+ @note2 = FactoryGirl.create(:note, project_id: @project1.id, attachments: [@project1.groups[2], @project1.leafs[7], @project1.sides[2], @project1.sides[3]], title: "XTREME FOOBAR")
+ @note3 = FactoryGirl.create(:note, project_id: @project1.id, attachments: [@project1.groups[3], @project1.leafs[3], @project1.sides[10], @project1.sides[11]], title: "CREEPY WAAHOO")
+ @notebad = FactoryGirl.create(:note, project_id: @project2.id, attachments: [@project2.groups[1], @project2.leafs[5], @project2.sides[14], @project2.sides[15]], title: "ULTRA WAAHOO")
+ end
+
+ context "equals one" do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "note",
+ "attribute": "title",
+ "condition": "equals",
+ "values": [ 'ULTRA WAAHOO' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Notes']).to eq [@note1.id.to_s]
+ end
+ end
+
+ context "equals multiple" do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "note",
+ "attribute": "title",
+ "condition": "equals",
+ "values": [ 'CREEPY WAAHOO', 'XTREME FOOBAR' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Notes'].count).to eq 2
+ expect(body['Notes']).to include @note2.id.to_s
+ expect(body['Notes']).to include @note3.id.to_s
+ end
+ end
+
+ context "not equals one" do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "note",
+ "attribute": "title",
+ "condition": "not equals",
+ "values": [ 'ULTRA WAAHOO' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Notes'].count).to eq @project1.notes.count-1
+ expect(body['Notes']).not_to include @note1.id.to_s
+ expect(body['Notes']).not_to include @notebad.id.to_s
+ end
+ end
+
+ context "not equals multiple" do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "note",
+ "attribute": "title",
+ "condition": "not equals",
+ "values": [ 'ULTRA WAAHOO', 'CREEPY WAAHOO' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Notes'].count).to eq @project1.notes.count-2
+ expect(body['Notes']).not_to include @note1.id.to_s
+ expect(body['Notes']).not_to include @note3.id.to_s
+ expect(body['Notes']).not_to include @notebad.id.to_s
+ end
+ end
+
+ context "contains one" do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "note",
+ "attribute": "title",
+ "condition": "contains",
+ "values": [ 'ULTRA' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Notes']).to eq [@note1.id.to_s]
+ end
+ end
+
+ context "contains multiple" do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "note",
+ "attribute": "title",
+ "condition": "contains",
+ "values": [ 'CREEPY', 'XTREME' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Notes'].count).to eq 2
+ expect(body['Notes']).to include @note2.id.to_s
+ expect(body['Notes']).to include @note3.id.to_s
+ end
+ end
+
+ context "not contains one" do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "note",
+ "attribute": "title",
+ "condition": "not contains",
+ "values": [ 'ULTRA' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Notes'].count).to eq @project1.notes.count-1
+ expect(body['Notes']).not_to include @note1.id.to_s
+ end
+ end
+
+ context "not contains multiple" do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "note",
+ "attribute": "title",
+ "condition": "not contains",
+ "values": [ 'CREEPY', 'XTREME' ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Notes'].count).to eq @project1.notes.count-2
+ expect(body['Notes']).not_to include @note2.id.to_s
+ expect(body['Notes']).not_to include @note3.id.to_s
+ end
+ end
+ end
+
+ context 'and compound conditions' do
+ context 'using AND' do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "group",
+ "attribute": "title",
+ "condition": "contains",
+ "values": [ 'Quire' ],
+ "conjunction": "AND"
+ },
+ {
+ "type": "group",
+ "attribute": "title",
+ "condition": "contains",
+ "values": [ @project1.groups[0].title[5..-1] ]
+ }
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Groups']).to include(@project1.groups[0].id.to_s)
+ expect(body['Groups']).not_to include(@project2.groups[0].id.to_s)
+ end
+ end
+
+ context 'using OR' do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "group",
+ "attribute": "title",
+ "condition": "contains",
+ "values": [ 'Quire' ],
+ "conjunction": "OR"
+ },
+ {
+ "type": "group",
+ "attribute": "title",
+ "condition": "contains",
+ "values": [ 'asdf' ],
+ "conjunction": "OR"
+ },
+ {
+ "type": "group",
+ "attribute": "title",
+ "condition": "contains",
+ "values": [ '4' ]
+ },
+ ]
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'contains the expected entries' do
+ body = JSON.parse(response.body)
+ expect(body['Groups'].count).to eq @project1.groups.count
+ @project1.groups.each do |grp|
+ expect(body['Groups']).to include grp.id.to_s
+ end
+ end
+ end
+ end
+
+ context 'and inexistent params' do
+ before do
+ @parameters = {
+ "queries": []
+ }
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and missing project' do
+ before do
+ put "/projects/#{@project1.id}missing/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'and unauthorized params' do
+ before do
+ put "/projects/#{@project2.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => @authToken+"invalid"}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put "/projects/#{@project1.id}/filter", params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put "/projects/#{@project1.id}/filter"
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/projects/import_projects_spec.rb b/viscoll-api/spec/requests/projects/import_projects_spec.rb
new file mode 100644
index 00000000..45be3d96
--- /dev/null
+++ b/viscoll-api/spec/requests/projects/import_projects_spec.rb
@@ -0,0 +1,143 @@
+require 'rails_helper'
+
+describe "PUT /projects/import", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @parameters = {
+ "importData": nil,
+ "importFormat": nil,
+ "imageData": nil
+ }
+ end
+
+ describe 'JSON imports' do
+ let(:import_data) { File.open(File.dirname(__FILE__) + '/../../fixtures/sample_import_json.json', 'r') { |file| file.read } }
+ before :each do
+ @parameters[:importFormat] = 'json'
+ end
+
+ it 'should import properly' do
+ @parameters[:importData] = import_data
+ expect{ put '/projects/import', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'} }.to change{Project.count}.by(1)
+ expect(response).to have_http_status(:ok)
+ project = Project.last
+ expect(project.title).to eq 'Sample project'
+ expect(project.shelfmark).to eq 'Ravenna 384.2339'
+ expect(project.metadata).to eq({ 'date' => '18th century' })
+ expect(project.preferences).to eq({ 'showTips' => true })
+ expect(project.noteTypes).to eq ['Hand', 'Ink', 'Unknown']
+ expect(project.manifests).to eq({ '12341234' => { 'id' => '12341234', 'url' => 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest', 'name' => 'Boston, and Bunker Hill.' } })
+ expect(project.leafs.count).to eq 6
+ expect(project.sides.count).to eq 12
+ expect(project.notes[0].title).to eq 'Test Note'
+ expect(project.notes[0].type).to eq 'Ink'
+ expect(project.notes[0].description).to eq 'This is a test'
+ expect(project.notes[0].objects).to eq({'Group' => [project.groups[0].id.to_s], 'Leaf' => [project.leafs[4].id.to_s], 'Recto' => [project.leafs[4].rectoID], 'Verso' => [project.leafs[4].versoID]})
+ end
+
+ it 'should show error for invalid JSON' do
+ @parameters[:importData] = import_data + '{}[];;'
+ expect{ put '/projects/import', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'} }.not_to change{Project.count}
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(JSON.parse(response.body)['error']).to eq("Sorry, the imported data cannot be validated. Please check your file for errors and make sure the correct import format is selected above.")
+ end
+ end
+
+ describe 'XML imports' do
+ let(:import_data) { File.open(File.dirname(__FILE__) + '/../../fixtures/sample_import_xml.xml', 'r') { |file| file.read } }
+ before :each do
+ @parameters[:importFormat] = 'xml'
+ end
+
+ it 'should import properly' do
+ @parameters[:importData] = import_data
+ expect{ put '/projects/import', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'} }.to change{Project.count}.by(1)
+ project = Project.last
+ expect(project.title).to eq 'Sample project'
+ expect(project.shelfmark).to eq 'Ravenna 384.2339'
+ expect(project.metadata).to eq({ 'date' => '18th century' })
+ expect(project.preferences).to eq({ 'showTips' => true })
+ expect(project.noteTypes).to include('Ink', 'Unknown')
+ expect(project.manifests).to eq({ '12341234' => { 'id' => '12341234', 'url' => 'https://digital.library.villanova.edu/Item/vudl:99213/Manifest' } })
+ expect(project.leafs.count).to eq 6
+ expect(project.sides.count).to eq 12
+ expect(project.notes[0].title).to eq 'Test Note'
+ expect(project.notes[0].type).to eq 'Ink'
+ expect(project.notes[0].description).to eq 'This is a test'
+ expect(project.notes[0].objects).to eq({'Group' => [project.groups[0].id.to_s], 'Leaf' => [project.leafs[4].id.to_s], 'Recto' => [project.leafs[4].rectoID], 'Verso' => [project.leafs[4].versoID]})
+ end
+
+ it 'should show error for invalid XML' do
+ @parameters[:import_data] = import_data + ' @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'} }.not_to change{Project.count}
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(JSON.parse(response.body)['error']).not_to be_blank
+ end
+ end
+
+ describe 'Invalid situations' do
+ let(:import_data) { File.open(File.dirname(__FILE__) + '/../../fixtures/sample_import_json.json', 'r') { |file| file.read } }
+ before :each do
+ @parameters[:importFormat] = 'json'
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/projects/import', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put '/projects/import', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put '/projects/import', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put '/projects/import'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/projects/index_projects_spec.rb b/viscoll-api/spec/requests/projects/index_projects_spec.rb
new file mode 100644
index 00000000..0e7410fe
--- /dev/null
+++ b/viscoll-api/spec/requests/projects/index_projects_spec.rb
@@ -0,0 +1,86 @@
+require 'rails_helper'
+
+describe "GET /projects", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ context 'with correct authorization' do
+ context 'and standard params' do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project1 = FactoryGirl.create(:project, {:user_id => @user.id})
+ @project2 = FactoryGirl.create(:project, {:user_id => @user.id})
+ @project3 = FactoryGirl.create(:project, {:user_id => @user2.id})
+ get '/projects', params: '', headers: {'Authorization' => @authToken}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it "contains the user's own projects only" do
+ expect(@body["projects"].length).to eq 2
+ expect(@body["projects"][0]['id']).to eq @project2.id.to_str
+ expect(@body["projects"][1]['id']).to eq @project1.id.to_str
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ get '/projects', params: '', headers: {'Authorization' => @authToken+"invalid"}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ get '/projects', params: '', headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ get '/projects', params: '', headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ get '/projects'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/projects/show_projects_spec.rb b/viscoll-api/spec/requests/projects/show_projects_spec.rb
new file mode 100644
index 00000000..44a32c56
--- /dev/null
+++ b/viscoll-api/spec/requests/projects/show_projects_spec.rb
@@ -0,0 +1,103 @@
+require 'rails_helper'
+
+describe "GET /projects/id", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ @user2 = FactoryGirl.create(:user, {:password => "user2"})
+ @project1 = FactoryGirl.create(:project, {:user => @user})
+ @project2 = FactoryGirl.create(:project, {:user => @user2})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ context 'with correct authorization' do
+ context 'and standard params' do
+ before do
+ get '/projects/'+@project1.id, params: '', headers: {'Authorization' => @authToken}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it "contains the user's own projects only" do
+ expect(@body['active']['id']).to eq @project1.id.to_str
+ end
+ end
+
+ context 'and inexistent params' do
+ before do
+ get '/projects/ULTRAWAAHOO', params: '', headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'and unauthorized params' do
+ before do
+ get '/projects/'+@project2._id, params: '', headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ get '/projects/'+@project1.id, params: '', headers: {'Authorization' => @authToken+"invalid"}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ get '/projects/'+@project1.id, params: '', headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ get '/projects/'+@project1.id, params: '', headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ get '/projects/'+@project1.id
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/projects/update_manifest_projects_spec.rb b/viscoll-api/spec/requests/projects/update_manifest_projects_spec.rb
new file mode 100644
index 00000000..ab2fed76
--- /dev/null
+++ b/viscoll-api/spec/requests/projects/update_manifest_projects_spec.rb
@@ -0,0 +1,179 @@
+require 'rails_helper'
+
+describe "PUT /projects/:id/manifests", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ stub_request(:get, 'https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest').with(headers: { 'Accept' => '*/*', 'User-Agent' => 'Ruby' }).to_return(status: 200, body: File.read(File.dirname(__FILE__) + '/../../fixtures/uoft_hollar.json'), headers: {})
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {
+ user: @user,
+ manifests: { "59ee3c623b0eb75251207cfe": { id: "59ee3c623b0eb75251207cfe", name: 'ASDF', url: 'https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest', images: [{label: "IMAGE", url: "http://www.example.com/iiif-sample"}]} }
+ })
+ @defaultGroup = FactoryGirl.create(:quire, project: @project)
+ @project[:groupIDs] = [@defaultGroup.id.to_s]
+ @leaf1 = FactoryGirl.create(:leaf, {project: @project})
+ @defaultGroup.add_members([@leaf1.id.to_s], 1)
+ @side1 = @project.sides.find(@leaf1.rectoID)
+ @side1.image = { label: "IMAGE", manifestID: "59ee3c623b0eb75251207cfe", url: "http://www.example.com/iiif-sample" }
+ @side1.save
+ @side2 = @project.sides.find(@leaf1.versoID)
+ @parameters = {
+ "manifest": {
+ "id": "59ee3c623b0eb75251207cfe",
+ "name": "QWER",
+ "url": 'https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest'
+ }
+ }
+ end
+
+ after :each do
+ @project.destroy
+ end
+
+ context 'with valid authorization' do
+ context 'with valid parameters' do
+ before do
+ put "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'rename the manifest' do
+ expect(@project.manifests.any? { |key, manifest| manifest['name'] == "ASDF"}).to be false
+ expect(@project.manifests.any? { |key, manifest| manifest['name'] == "QWER"}).to be true
+ end
+ end
+ context 'with missing project' do
+ before do
+ put "/projects/#{@project.id.to_str}missing/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+ context 'with missing manifest' do
+ before do
+ @parameters[:manifest][:id] += 'missing'
+ put "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'gives the right error' do
+ expect(@body['error']).to eq "Manifest with id: 59ee3c623b0eb75251207cfemissing not found in project with id: #{@project.id.to_str}."
+ end
+ end
+ context 'with missing id parameter' do
+ before do
+ @parameters[:manifest].delete(:id)
+ put "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'gives the right error' do
+ expect(@body['error']).to eq "Param required: id."
+ end
+ end
+ context 'with unauthorized project' do
+ before do
+ @project.user = FactoryGirl.create(:user)
+ @project.save
+ put "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @project.reload
+ end
+
+ it 'returns 403' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'leaves the project alone' do
+ expect(@project.manifests.any? { |key, manifest| manifest['name'] == "ASDF"}).to be true
+ expect(@project.manifests.any? { |key, manifest| manifest['name'] == "QWER"}).to be false
+ end
+ end
+ context 'with exception' do
+ before do
+ allow_any_instance_of(Project).to receive(:save).and_raise("WaahooException")
+ put "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 400' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'shows the exception' do
+ expect(@body['errors']).to eq "WaahooException"
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put "/projects/#{@project.id.to_str}/manifests", params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put "/projects/#{@project.id.to_str}/manifests"
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/projects/update_projects_spec.rb b/viscoll-api/spec/requests/projects/update_projects_spec.rb
new file mode 100644
index 00000000..bc864c49
--- /dev/null
+++ b/viscoll-api/spec/requests/projects/update_projects_spec.rb
@@ -0,0 +1,174 @@
+require 'rails_helper'
+
+describe "PUT /projects/id", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @user2 = FactoryGirl.create(:user)
+ @project1 = FactoryGirl.create(:project, {:user => @user})
+ @project2 = FactoryGirl.create(:project, {:user => @user})
+ @project3 = FactoryGirl.create(:project, {:user => @user2})
+ @parameters = {
+ "project": {
+ "title": "My modified project",
+ "shelfmark": "MSS 123",
+ "metadata": {
+ "date": "18th century"
+ },
+ "manifests": [
+ {"name": "barrenlands", "url": "https://iiif.library.utoronto.ca/presentation/v2/barrenlands:C10034/manifest"},
+ {"name": "insulin", "url": "https://iiif.library.utoronto.ca/presentation/v2/insulin:E10016/manifest"}
+ ],
+ "noteTypes": [
+ "Ink",
+ "Hand"
+ ],
+ "preferences": {
+ "showTips": false
+ }
+ }
+ }
+ end
+
+ context 'with correct authorization' do
+ context 'and standard params' do
+ before do
+ put '/projects/'+@project1.id, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'returns the changed project' do
+ expect(@body["projects"][0]['id']).to eq @project1.id.to_str
+ end
+
+ it 'changes the right project' do
+ expect(Project.find(id: @project1.id).title).to eq "My modified project"
+ expect(Project.find(id: @project2.id).title).not_to eq "My modified project"
+ expect(Project.find(id: @project3.id).title).not_to eq "My modified project"
+ end
+ end
+
+ context 'and inexistent project' do
+ before do
+ put '/projects/NONEXISTENT', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+
+ it 'should not remove anything' do
+ expect(Project.find(id: @project1.id).title).not_to eq "My modified project"
+ expect(Project.find(id: @project2.id).title).not_to eq "My modified project"
+ expect(Project.find(id: @project3.id).title).not_to eq "My modified project"
+ end
+ end
+
+ context "and somebody else's project" do
+ before do
+ put '/projects/'+@project3.id, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'should not remove anything' do
+ expect(Project.find(id: @project1.id).title).not_to eq "My modified project"
+ expect(Project.find(id: @project2.id).title).not_to eq "My modified project"
+ expect(Project.find(id: @project3.id).title).not_to eq "My modified project"
+ end
+ end
+
+ context 'and a failed save' do
+ before do
+ allow_any_instance_of(Project).to receive(:update).and_return(false)
+ put '/projects/'+@project1.id, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'and an exception' do
+ before do
+ allow_any_instance_of(Project).to receive(:update).and_raise("Exception")
+ put '/projects/'+@project1.id, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'includes the exception' do
+ expect(@body['errors']).to eq "Exception"
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/projects/'+@project1.id, params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put '/projects/'+@project1.id, params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put '/projects/'+@project1.id, params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put '/projects/'+@project1.id
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/sides/sides_generateFolio_spec.rb b/viscoll-api/spec/requests/sides/sides_generateFolio_spec.rb
new file mode 100644
index 00000000..fa2f8826
--- /dev/null
+++ b/viscoll-api/spec/requests/sides/sides_generateFolio_spec.rb
@@ -0,0 +1,45 @@
+require 'rails_helper'
+
+describe "PUT /sides/generateFolio", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {user: @user})
+ @defaultGroup = FactoryGirl.create(:quire, project: @project)
+ @project.add_groupIDs([@defaultGroup.id.to_s], 0)
+ @leaf1 = FactoryGirl.create(:leaf, {project: @project})
+ @leaf2 = FactoryGirl.create(:leaf, {project: @project})
+ @defaultGroup.add_members([@leaf1.id.to_s, @leaf2.id.to_s], 1)
+ @parameters = {
+ rectoIDs: [@leaf1.rectoID, @leaf2.rectoID],
+ versoIDs: [@leaf1.versoID, @leaf2.versoID],
+ startNumber: 9,
+ }
+ end
+
+ context 'generate folio number' do
+ before do
+ put '/sides/generateFolio', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'Updates the side folio numbers' do
+ side1R = @project.sides.find(@leaf1.rectoID)
+ side1V = @project.sides.find(@leaf1.versoID)
+ side2R = @project.sides.find(@leaf2.rectoID)
+ side2V = @project.sides.find(@leaf2.versoID)
+ expect(side1R.folio_number).to eq "9R"
+ expect(side1V.folio_number).to eq "9V"
+ expect(side2R.folio_number).to eq "10R"
+ expect(side2V.folio_number).to eq "10V"
+ end
+ end
+end
\ No newline at end of file
diff --git a/viscoll-api/spec/requests/sides/sides_generatePage_spec.rb b/viscoll-api/spec/requests/sides/sides_generatePage_spec.rb
new file mode 100644
index 00000000..9bfc0937
--- /dev/null
+++ b/viscoll-api/spec/requests/sides/sides_generatePage_spec.rb
@@ -0,0 +1,45 @@
+require 'rails_helper'
+
+describe "PUT /sides/generatePageNumber", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {user: @user})
+ @defaultGroup = FactoryGirl.create(:quire, project: @project)
+ @project.add_groupIDs([@defaultGroup.id.to_s], 0)
+ @leaf1 = FactoryGirl.create(:leaf, {project: @project})
+ @leaf2 = FactoryGirl.create(:leaf, {project: @project})
+ @defaultGroup.add_members([@leaf1.id.to_s, @leaf2.id.to_s], 1)
+ @parameters = {
+ rectoIDs: [@leaf1.rectoID, @leaf2.rectoID],
+ versoIDs: [@leaf1.versoID, @leaf2.versoID],
+ startNumber: 3,
+ }
+ end
+
+ context 'generate page number' do
+ before do
+ put '/sides/generatePageNumber', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'Updates the side page numbers' do
+ side1R = @project.sides.find(@leaf1.rectoID)
+ side1V = @project.sides.find(@leaf1.versoID)
+ side2R = @project.sides.find(@leaf2.rectoID)
+ side2V = @project.sides.find(@leaf2.versoID)
+ expect(side1R.page_number).to eq "3"
+ expect(side1V.page_number).to eq "4"
+ expect(side2R.page_number).to eq "5"
+ expect(side2V.page_number).to eq "6"
+ end
+ end
+end
\ No newline at end of file
diff --git a/viscoll-api/spec/requests/sides/sides_updateMultiple_spec.rb b/viscoll-api/spec/requests/sides/sides_updateMultiple_spec.rb
new file mode 100644
index 00000000..9b7efc63
--- /dev/null
+++ b/viscoll-api/spec/requests/sides/sides_updateMultiple_spec.rb
@@ -0,0 +1,163 @@
+require 'rails_helper'
+
+describe "PUT /sides/id", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {user: @user})
+ @defaultGroup = FactoryGirl.create(:quire, project: @project)
+ @project.add_groupIDs([@defaultGroup.id.to_s], 0)
+ @leaf1 = FactoryGirl.create(:leaf, {project: @project})
+ @defaultGroup.add_members([@leaf1.id.to_s], 1)
+ @side1 = @project.sides.find(@leaf1.rectoID)
+ @side2 = @project.sides.find(@leaf1.versoID)
+ @parameters = {
+ "sides": [
+ {
+ "id": @side1.id.to_str,
+ "attributes": {
+ "texture": "PaperSide1",
+ "script_direction": "LeftSide1"
+ }
+ },
+ {
+ "id": @side2.id.to_str,
+ "attributes": {
+ "texture": "PaperSide2",
+ "script_direction": "LeftSide2"
+ }
+ }
+ ]
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'and valid side ID' do
+ before do
+ put '/sides', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @side1.reload
+ @side2.reload
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'Updates the sides' do
+ expect(@side1.texture).to eq "PaperSide1"
+ expect(@side1.script_direction).to eq "LeftSide1"
+ expect(@side2.texture).to eq "PaperSide2"
+ expect(@side2.script_direction).to eq "LeftSide2"
+ end
+ end
+
+ context 'and invalid side ID' do
+ before do
+ @parameters[:sides][0][:id] = "invalidID"
+ put '/sides', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context "and someone else's sides" do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project2 = FactoryGirl.create(:project, { user: @user2 })
+ @defaultGroup2 = FactoryGirl.create(:quire, project: @project)
+ @leaf2 = FactoryGirl.create(:leaf, {project: @project2})
+ @defaultGroup.add_members([@leaf2.id.to_s], 1)
+ @side3 = @project2.sides.find(@leaf2.rectoID)
+ @side4 = @project2.sides.find(@leaf2.versoID)
+ @parameters = {
+ "sides": [
+ {
+ "id": @side3.id,
+ "attributes": {
+ "texture": "PaperSide1",
+ "script_direction": "LeftSide1"
+ }
+ },
+ {
+ "id": @side4.id,
+ "attributes": {
+ "texture": "PaperSide2",
+ "script_direction": "LeftSide2"
+ }
+ }
+ ]
+ }
+ put '/sides', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @side1.reload
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'leaves the side alone' do
+ expect(@side3.texture).to eq "None"
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/sides', params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put '/sides', params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put '/sides', params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put '/sides'
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/sides/sides_update_spec.rb b/viscoll-api/spec/requests/sides/sides_update_spec.rb
new file mode 100644
index 00000000..1d79af59
--- /dev/null
+++ b/viscoll-api/spec/requests/sides/sides_update_spec.rb
@@ -0,0 +1,147 @@
+require 'rails_helper'
+
+describe "PUT /sides/id", :type => :request do
+ before do
+ @user = FactoryGirl.create(:user, {:password => "user"})
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email => @user.email, :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ before :each do
+ @project = FactoryGirl.create(:project, {user: @user})
+ @defaultGroup = FactoryGirl.create(:quire, project: @project)
+ @project.add_groupIDs([@defaultGroup.id.to_s], 0)
+ @leaf1 = FactoryGirl.create(:leaf, {project: @project})
+ @defaultGroup.add_members([@leaf1.id.to_s], 1)
+ @side1 = @project.sides.find(@leaf1.rectoID)
+ @side2 = @project.sides.find(@leaf1.versoID)
+ @parameters = {
+ "side": {
+ "folio_number": "some folio_number for side",
+ "texture": "Paper",
+ "script_direction": "Left",
+ "image": {
+ "manifestID": "123",
+ "url": "https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3002_0001",
+ "label": "3002_0001"
+ }
+ }
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'and valid side ID' do
+ before do
+ put '/sides/'+@side1.id.to_s, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @side1.reload
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'Updates the side' do
+ expect(@side1.texture).to eq "Paper"
+ expect(@side1.script_direction).to eq "Left"
+ expect(@side1.image[:url]).to eq "https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3002_0001"
+ end
+ end
+
+ context 'and invalid side ID' do
+ before do
+ put '/sides/'+@side1.id.to_s+'invalid', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'and failed update' do
+ before do
+ allow_any_instance_of(Side).to receive(:update).and_return(false)
+ put '/sides/'+@side1.id.to_s, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ end
+
+ it 'returns 422' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ context "and someone else's sides" do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project2 = FactoryGirl.create(:project, { user: @user2 })
+ @defaultGroup2 = FactoryGirl.create(:quire, project: @project)
+ @leaf2 = FactoryGirl.create(:leaf, {project: @project2})
+ @defaultGroup.add_members([@leaf2.id.to_s], 1)
+ @side3 = @project2.sides.find(@leaf2.rectoID)
+ @side4 = @project2.sides.find(@leaf2.versoID)
+ put '/sides/'+@side3.id, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @side1.reload
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'leaves the side alone' do
+ expect(@side3.texture).to eq "None"
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/sides/'+@side1.id, params: @parameters.to_json, headers: {'Authorization' => @authToken+'asdf', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @body = JSON.parse(response.body)
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put '/sides/'+@side1.id, params: @parameters.to_json, headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put '/sides/'+@side1.id, params: @parameters.to_json, headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put '/sides/'+@side1.id
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/users/delete_users_userID_spec.rb b/viscoll-api/spec/requests/users/delete_users_userID_spec.rb
new file mode 100644
index 00000000..9c0fb43e
--- /dev/null
+++ b/viscoll-api/spec/requests/users/delete_users_userID_spec.rb
@@ -0,0 +1,120 @@
+require 'rails_helper'
+
+describe "DELETE /users/userID", :type => :request do
+ before do
+ @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email=> "user@mail.com", :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ context 'with correct authorization' do
+ context 'and valid params' do
+ before do
+ @user2 = User.create(:name => "user2", :email => "user2@mail.com", :password => "user2")
+ @project1 = Project.create(:title => "first project", :user_id => @user.id)
+ @project2 = Project.create(:title => "second project", :user_id => @user.id)
+ @project3 = Project.create(:title => "some other user project", :user_id => @user2.id)
+ delete '/users/'+@user.id.to_s, params: '', headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns a successful no_content response' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'deletes the user from the database' do
+ expect(User.where(id: @user.id).count).to eq(0)
+ end
+
+ it 'deletes all user related objects only' do
+ expect(Project.where(id: @project1.id).count).to eq(0)
+ expect(Project.where(id: @project2.id).count).to eq(0)
+ expect(Project.all.count).to eq(1)
+ end
+ end
+
+ context 'and another user' do
+ before do
+ @user2 = User.create(:name => "user2", :email => "user2@mail.com", :password => "user2")
+ @project1 = Project.create(:title => "first project", :user_id => @user.id)
+ @project2 = Project.create(:title => "second project", :user_id => @user.id)
+ @project3 = Project.create(:title => "some other user project", :user_id => @user2.id)
+ delete '/users/'+@user2.id.to_s, params: '', headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns unauthorized' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'leaves the other user alone' do
+ expect(User.where(id: @user2.id).count).to eq 1
+ end
+ end
+
+ context 'and invalid params' do
+ before do
+ delete '/users/invalidID', params: '', headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns 404 no content found error' do
+ expect(response).to have_http_status(:not_found)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('user not found with id invalidID')
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ delete '/users/'+@user.id.to_s, params: '', headers: {'Authorization' => @authToken+"invalidify"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ delete '/users/'+@user.id.to_s, params: '', headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ delete '/users/'+@user.id.to_s, params: '', headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ delete '/users/'+@user.id.to_s
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/users/get_users_userID_spec.rb b/viscoll-api/spec/requests/users/get_users_userID_spec.rb
new file mode 100644
index 00000000..00425ce9
--- /dev/null
+++ b/viscoll-api/spec/requests/users/get_users_userID_spec.rb
@@ -0,0 +1,103 @@
+require 'rails_helper'
+
+describe "GET /users/userID", :type => :request do
+ before do
+ @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email=> "user@mail.com", :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ context 'with correct authorization' do
+ context 'and valid params' do
+ before do
+ @project1 = Project.create(:title => "first project", :user_id => @user.id)
+ @project2 = Project.create(:title => "second project", :user_id => @user.id)
+ @project3 = Project.create(:title => "some other user project", :user_id => "")
+ get '/users/'+@user.id.to_s, params: '', headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns a successful ok response' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'returns the user object in the response' do
+ expect(JSON.parse(response.body)['id']).to eq(@user.id.to_s)
+ expect(JSON.parse(response.body)['email']).to eq("user@mail.com")
+ expect(JSON.parse(response.body)['name']).to eq("user")
+ end
+
+ it 'returns all the projects with manuscripts of this user' do
+ expect(JSON.parse(response.body)['projects'].size).to eq(2)
+ expect(JSON.parse(response.body)['projects'][0]["title"]).to eq("first project")
+ expect(JSON.parse(response.body)['projects'][1]["title"]).to eq("second project")
+ end
+ end
+
+ context 'and invalid params' do
+ before do
+ get '/users/invalidID', params: '', headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns 404 no content found error' do
+ expect(response).to have_http_status(:not_found)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('user not found with id invalidID')
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ get '/users/'+@user.id.to_s, params: '', headers: {'Authorization' => @authToken+"invalidify"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ get '/users/'+@user.id.to_s, params: '', headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ get '/users/'+@user.id.to_s, params: '', headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ get '/users/'+@user.id.to_s
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/viscoll-api/spec/requests/users/put_users_userID_spec.rb b/viscoll-api/spec/requests/users/put_users_userID_spec.rb
new file mode 100644
index 00000000..f4f4fad3
--- /dev/null
+++ b/viscoll-api/spec/requests/users/put_users_userID_spec.rb
@@ -0,0 +1,265 @@
+require 'rails_helper'
+
+describe "PUT /users/userID", :type => :request do
+ before do
+ @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
+ put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
+ post '/session', params: {:session => { :email=> "user@mail.com", :password => "user" }}
+ @authToken = JSON.parse(response.body)['session']['jwt']
+ end
+
+ context 'with correct authorization' do
+ context 'and valid params' do
+ context 'update email address' do
+ before do
+ put '/users/'+@user.id.to_s, params: {:user => {:email => "newUser@mail.com"}}, headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns a successful ok response' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'returns the same user object in the response with old email address' do
+ expect(JSON.parse(response.body)['email']).to eq("user@mail.com")
+ end
+
+ it 'creates fields for email confirmation in user record' do
+ expect(User.find(@user.id).confirmation_token).not_to eq(nil)
+ expect(User.find(@user.id).unconfirmed_email).to eq("newUser@mail.com")
+ end
+ end
+
+ context 'update name' do
+ before do
+ put '/users/'+@user.id.to_s, params: {:user => {:name => "newUser"}}, headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns a successful ok response' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'returns the updated object in the response with new name' do
+ expect(JSON.parse(response.body)['name']).to eq("newUser")
+ end
+
+ it 'updates the field for name in user record' do
+ expect(User.find(@user.id).name).to eq("newUser")
+ end
+ end
+
+ context 'update email and name' do
+ before do
+ put '/users/'+@user.id.to_s, params: {:user => {:email => "newUser@mail.com", :name => "newUser"}}, headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns a successful ok response' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'returns the updated object in the response with new name and old email' do
+ expect(JSON.parse(response.body)['name']).to eq("newUser")
+ expect(JSON.parse(response.body)['email']).to eq("user@mail.com")
+ end
+
+ it 'updates the field for name and email confirmation in user record' do
+ expect(User.find(@user.id).name).to eq("newUser")
+ expect(User.find(@user.id).confirmation_token).not_to eq(nil)
+ expect(User.find(@user.id).unconfirmed_email).to eq("newUser@mail.com")
+ end
+ end
+
+ context 'update password' do
+ before do
+ put '/users/'+@user.id.to_s, params: {:user => {:current_password => "user", :password => "newUser"}}, headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns a successful ok response' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'returns the updated object in the response' do
+ expect(JSON.parse(response.body)['name']).to eq("user")
+ end
+
+ it 'updates the field for password in user record' do
+ post '/session', params: {:session => { :email=> "user@mail.com", :password => "newUser" }}
+ expect(JSON.parse(response.body)['session']['jwt']).not_to be_empty
+ end
+ end
+
+ context 'update email, name and password' do
+ before do
+ put '/users/'+@user.id.to_s, params: {:user => {:email => "newUser@mail.com", :name => "newUser", :current_password => "user", :password => "newUser"}}, headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns a successful ok response' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'returns the updated object in the response' do
+ expect(JSON.parse(response.body)['email']).to eq("user@mail.com")
+ expect(JSON.parse(response.body)['name']).to eq("newUser")
+ end
+
+ it 'updates the field for password in user record' do
+ post '/session', params: {:session => { :email=> "user@mail.com", :password => "newUser" }}
+ expect(JSON.parse(response.body)['session']['jwt']).not_to be_empty
+ end
+
+ it 'creates fields for email confirmation in user record' do
+ expect(User.find(@user.id).confirmation_token).not_to eq(nil)
+ expect(User.find(@user.id).unconfirmed_email).to eq("newUser@mail.com")
+ end
+
+ it 'updates the field for name and email confirmation in user record' do
+ expect(User.find(@user.id).name).to eq("newUser")
+ expect(User.find(@user.id).confirmation_token).not_to eq(nil)
+ expect(User.find(@user.id).unconfirmed_email).to eq("newUser@mail.com")
+ end
+ end
+
+
+ end
+
+ context 'and invalid params' do
+ context 'with invalid userID' do
+ before do
+ put '/users/invalidID', params: '', headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns 404 no content found error' do
+ expect(response).to have_http_status(:not_found)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('user not found with id invalidID')
+ end
+ end
+
+ context 'with invalid current password' do
+ before do
+ put '/users/'+@user.id.to_s, params: {:user => {:current_password => "userInvalid", :password => "newUser"}}, headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['current_password']).to eq(['invalid'])
+ end
+ end
+
+ context 'with duplicate email address' do
+ before do
+ @user2 = User.create(:name => "newUser", :email => "newUser@mail.com", :password => "newUser")
+ put '/confirmation', params: {:confirmation_token => @user2.confirmation_token}
+ put '/users/'+@user.id.to_s, params: {:user => {:email => "newUser@mail.com"}}, headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['email']).to eq(["is already taken"])
+ end
+ end
+
+ context 'with invalid email address' do
+ before do
+ put '/users/'+@user.id.to_s, params: {:user => {:email => "invalidEmail"}}, headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['email']).to eq(["is not an email"])
+ end
+ end
+
+ context 'with missing current password' do
+ before do
+ put '/users/'+@user.id.to_s, params: {:user => {:password => "newUser"}}, headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['current_password']).to eq(['blank'])
+ end
+ end
+
+ context 'with missing new password' do
+ before do
+ put '/users/'+@user.id.to_s, params: {:user => {:current_password => "userInvalid", :password => ""}}, headers: {'Authorization' => @authToken}
+ end
+
+ it 'returns an unprocessable_entity status' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['password']).to eq(['blank'])
+ end
+ end
+
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/users/'+@user.id.to_s, params: '', headers: {'Authorization' => @authToken+"invalidify"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Signature verification raised')
+ end
+ end
+
+ context 'with empty authorization' do
+ before do
+ put '/users/'+@user.id.to_s, params: '', headers: {'Authorization' => ""}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Nil JSON web token')
+ end
+ end
+
+ context 'invalid authorization' do
+ before do
+ put '/users/'+@user.id.to_s, params: '', headers: {'Authorization' => "123456789"}
+ end
+
+ it 'returns an bad request error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'returns an appropriate error message' do
+ expect(JSON.parse(response.body)['error']).to eq('Authorization Token: Not enough or too many segments')
+ end
+ end
+
+ context 'without authorization' do
+ before do
+ put '/users/'+@user.id.to_s
+ end
+
+ it 'returns an unauthorized action error' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/viscoll-api/spec/spec_helper.rb
similarity index 86%
rename from spec/spec_helper.rb
rename to viscoll-api/spec/spec_helper.rb
index 8f698be4..f5a6e864 100644
--- a/spec/spec_helper.rb
+++ b/viscoll-api/spec/spec_helper.rb
@@ -1,3 +1,19 @@
+# Load and launch SimpleCov at the very top
+require 'simplecov'
+SimpleCov.start('rails') do
+ add_filter '/spec/'
+ add_filter '/config/'
+ add_filter '/mailers/'
+ add_filter 'app/channels/application_cable'
+ add_filter 'app/jobs/application_job.rb'
+ add_filter 'app/controllers/application_controller.rb'
+ add_filter 'app/controllers/concerns/rails_jwt_auth/warden_helper.rb'
+ add_group 'Controllers', 'app/controllers'
+ add_group 'Models', 'app/models'
+ add_group 'Helpers', 'app/helpers'
+end
+# Add webmock
+require 'webmock/rspec'
# This file was generated by the `rails generate rspec:install` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# The generated `.rspec` file contains `--require spec_helper` which will cause
@@ -12,9 +28,6 @@
# the additional setup, and require it from the spec files that actually need
# it.
#
-# The `.rspec` file also contains a few flags that are not defaults but that
-# users commonly want.
-#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
@@ -47,6 +60,11 @@
# triggering implicit auto-inclusion in groups with matching metadata.
config.shared_context_metadata_behavior = :apply_to_host_groups
+ # Clean the test upload directory after the suite
+ config.after(:suite) do
+ FileUtils.rm_rf(Dir["#{Rails.root}/uploads/test-files"])
+ end
+
# The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content.
=begin
@@ -76,7 +94,7 @@
# Use the documentation formatter for detailed output,
# unless a formatter has already been configured
# (e.g. via a command-line flag).
- config.default_formatter = 'doc'
+ config.default_formatter = "doc"
end
# Print the 10 slowest examples and example groups at the
diff --git a/lib/assets/.keep b/viscoll-api/tmp/.keep
similarity index 100%
rename from lib/assets/.keep
rename to viscoll-api/tmp/.keep
diff --git a/viscoll-app/README.md b/viscoll-app/README.md
new file mode 100644
index 00000000..c8ce3927
--- /dev/null
+++ b/viscoll-app/README.md
@@ -0,0 +1,52 @@
+# VisColl (Redux Front-End)
+
+## Introduction
+
+This is the the Redux-driven user interface for Viscoll.
+
+## System Requirements
+
+- `node` (>= 6.11.4)
+- `npm` (>= 3.10.10)
+
+### Additional Requirements for Development:
+
+- [Redux DevTools for Firefox or Chrome](https://github.com/zalmoxisus/redux-devtools-extension) (>= 2.15.1)
+
+## Setup
+
+Run this to install the dependencies:
+```
+npm install
+```
+
+Then run the dev server which brings up a browser window serving the user interface:
+```
+npm start
+```
+
+## Testing
+
+Run this command to test once:
+```
+npm test
+```
+
+Alternatively, run this command to test continually while monitoring for changes:
+```
+npm test -- --watch
+```
+
+## Building
+
+Before building the app, edit line 3 in `viscoll-app/src/store/axiosConfig.js` to contain the correct root endpoint of the VisColl API:
+
+```Javascript
+export let API_URL = '/api';
+
+```
+
+Build the app with:
+```
+npm build
+```
diff --git a/viscoll-app/__test__/actions/frontendBeforeActions/groupActions.spec.js b/viscoll-app/__test__/actions/frontendBeforeActions/groupActions.spec.js
new file mode 100644
index 00000000..c73e3fdc
--- /dev/null
+++ b/viscoll-app/__test__/actions/frontendBeforeActions/groupActions.spec.js
@@ -0,0 +1,311 @@
+import {
+ createGroups,
+ updateGroup,
+ updateGroups,
+ deleteGroup,
+ deleteGroups,
+} from '../../../src/actions/frontend/before/groupActions';
+
+import {projectState001} from '../../testData/projectState001'
+
+import {cloneDeep} from 'lodash';
+
+describe('>>>A C T I O N --- Test group actions', () => {
+ it('+++ actionCreator createGroups', () => {
+ const groupPayload = {
+ payload: {
+ request : {
+ url: `/groups`,
+ method: 'post',
+ data: {
+ "group": {
+ project_id: "5a57825a4cfad13070870dc3",
+ title: "None",
+ type: "Quire",
+ },
+ "additional": {
+ conjoin: false,
+ groupIDs: ["123123", "456456"],
+ leafIDs: ["111", "222"],
+ sideIDs: ["11", "22", "33", "44"],
+ memberOrder: 3,
+ noOfGroups: 2,
+ noOfLeafs: 1,
+ order: 5,
+ }
+ },
+ successMessage: "Successfully added the groups" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+
+ expectedState.project.Groups["Group_123123"] = {
+ id: 'Group_123123',
+ type: 'Quire',
+ title: 'None',
+ tacketed: [],
+ sewing: [],
+ nestLevel: 1,
+ parentID: null,
+ notes: [],
+ memberIDs: ["Leaf_111"],
+ memberType: 'Group',
+ }
+ expectedState.project.Groups["Group_456456"] = {
+ id: 'Group_456456',
+ type: 'Quire',
+ title: 'None',
+ tacketed: [],
+ sewing: [],
+ nestLevel: 1,
+ parentID: null,
+ notes: [],
+ memberIDs: ["Leaf_222"],
+ memberType: 'Group',
+ }
+ expectedState.project.Leafs["Leaf_111"] = {
+ id: 'Leaf_111',
+ attached_above: 'None',
+ attached_below: 'None',
+ conjoined_to: null,
+ material: 'None',
+ stub: 'None',
+ type: 'None',
+ memberType: 'Leaf',
+ nestLevel: 1,
+ notes: [],
+ parentID: "Group_123123",
+ rectoID: "Recto_11",
+ versoID: "Verso_22",
+ }
+ expectedState.project.Leafs["Leaf_222"] = {
+ id: 'Leaf_222',
+ attached_above: 'None',
+ attached_below: 'None',
+ conjoined_to: null,
+ material: 'None',
+ stub: 'None',
+ type: 'None',
+ memberType: 'Leaf',
+ nestLevel: 1,
+ notes: [],
+ parentID: "Group_456456",
+ rectoID: "Recto_33",
+ versoID: "Verso_44",
+ }
+ expectedState.project.Rectos["Recto_11"] = {
+ id: "Recto_11",
+ parentID: "Leaf_111",
+ folio_number: null,
+ script_direction: 'None',
+ texture: 'Hair',
+ image: {},
+ notes: [],
+ memberType: 'Recto',
+ }
+ expectedState.project.Rectos["Recto_33"] = {
+ id: "Recto_33",
+ parentID: "Leaf_222",
+ folio_number: null,
+ script_direction: 'None',
+ texture: 'Hair',
+ image: {},
+ notes: [],
+ memberType: 'Recto',
+ }
+ expectedState.project.Versos["Verso_22"] = {
+ id: "Verso_22",
+ parentID: "Leaf_111",
+ folio_number: null,
+ script_direction: 'None',
+ texture: 'Flesh',
+ image: {},
+ notes: [],
+ memberType: 'Verso',
+ }
+ expectedState.project.Versos["Verso_44"] = {
+ id: "Verso_44",
+ parentID: "Leaf_222",
+ folio_number: null,
+ script_direction: 'None',
+ texture: 'Flesh',
+ image: {},
+ notes: [],
+ memberType: 'Verso',
+ }
+ expectedState.project.groupIDs.push("Group_123123");
+ expectedState.project.groupIDs.push("Group_456456");
+ expectedState.project.leafIDs.push("Leaf_111");
+ expectedState.project.leafIDs.push("Leaf_222");
+ expectedState.project.rectoIDs.push("Recto_11");
+ expectedState.project.rectoIDs.push("Recto_33");
+ expectedState.project.versoIDs.push("Verso_22");
+ expectedState.project.versoIDs.push("Verso_44");
+ expectedState.collationManager.flashItems.groups.push("Group_123123");
+ expectedState.collationManager.flashItems.groups.push("Group_456456");
+ expectedState.collationManager.flashItems.leaves.push("Leaf_111");
+ expectedState.collationManager.flashItems.leaves.push("Leaf_222");
+ const gotState = createGroups(groupPayload, beforeState);
+ expect(gotState).toEqual(expectedState)
+ })
+
+ it('+++ actionCreator updateGroup', () => {
+ const groupPayload = {
+ payload: {
+ request : {
+ url: `/groups/Group_5a57825a4cfad13070870df4`,
+ method: 'put',
+ data: {
+ "group": {
+ title: "New title",
+ type: "Booklet",
+ },
+ },
+ successMessage: "Successfully updated the group" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df4"].type = "Booklet";
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df4"].title = "New title";
+ const gotState = updateGroup(groupPayload, beforeState);
+ expect(gotState).toEqual(expectedState)
+ })
+
+ it('+++ actionCreator updateGroups', () => {
+ const groupPayload = {
+ payload: {
+ request : {
+ url: `/groups`,
+ method: 'put',
+ data: {
+ groups: [
+ {
+ id: "Group_5a57825a4cfad13070870df4",
+ attributes: {
+ title: "New title",
+ type: "Booklet",
+ }
+ },
+ {
+ id: "Group_5a57825a4cfad13070870df5",
+ attributes: {
+ title: "New title 2",
+ type: "Booklet",
+ }
+ }
+ ]
+ },
+ successMessage: "Successfully updated the groups" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df4"].type = "Booklet";
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df4"].title = "New title";
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df5"].type = "Booklet";
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df5"].title = "New title 2";
+ const gotState = updateGroups(groupPayload, beforeState);
+ expect(gotState).toEqual(expectedState)
+ })
+
+ it('+++ actionCreator deleteGroup', () => {
+ const groupPayload = {
+ payload: {
+ request : {
+ url: `/groups/Group_5a57825a4cfad13070870df7`,
+ method: 'delete',
+ successMessage: "Successfully deleted the groups" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+ delete expectedState.project.Groups["Group_5a57825a4cfad13070870df7"];
+ delete expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dd6"];
+ delete expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dd9"];
+ delete expectedState.project.Rectos["Recto_5a57825a4cfad13070870dd7"];
+ delete expectedState.project.Rectos["Recto_5a57825a4cfad13070870dda"];
+ delete expectedState.project.Versos["Verso_5a57825a4cfad13070870dd8"];
+ delete expectedState.project.Versos["Verso_5a57825a4cfad13070870ddb"];
+ expectedState.project.groupIDs.splice(-1);
+ expectedState.project.leafIDs.splice(expectedState.project.leafIDs.indexOf("Leaf_5a57825a4cfad13070870dd6"), 1);
+ expectedState.project.leafIDs.splice(expectedState.project.leafIDs.indexOf("Leaf_5a57825a4cfad13070870dd9"), 1);
+ expectedState.project.rectoIDs.splice(expectedState.project.rectoIDs.indexOf("Recto_5a57825a4cfad13070870dd7"), 1);
+ expectedState.project.rectoIDs.splice(expectedState.project.rectoIDs.indexOf("Recto_5a57825a4cfad13070870dda"), 1);
+ expectedState.project.versoIDs.splice(expectedState.project.versoIDs.indexOf("Verso_5a57825a4cfad13070870dd8"), 1);
+ expectedState.project.versoIDs.splice(expectedState.project.versoIDs.indexOf("Verso_5a57825a4cfad13070870ddb"), 1);
+ expectedState.collationManager.selectedObjects.lastSelected = "";
+ expectedState.collationManager.selectedObjects.members = [];
+ expectedState.collationManager.selectedObjects.type = "";
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df6"].memberIDs.splice(0,1);
+ const gotState = deleteGroup("Group_5a57825a4cfad13070870df7", beforeState);
+ expect(gotState).toEqual(expectedState)
+ })
+
+ it('+++ actionCreator deleteGroups', () => {
+ const groupPayload = {
+ payload: {
+ request : {
+ url: `/groups`,
+ method: 'delete',
+ data: {
+ groups: [
+ "Group_5a57825a4cfad13070870df6",
+ "Group_5a57825a4cfad13070870df7"
+ ]
+ },
+ successMessage: "Successfully deleted the groups" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+
+ delete expectedState.project.Groups["Group_5a57825a4cfad13070870df6"];
+ delete expectedState.project.Leafs["Leaf_5a57825a4cfad13070870ddc"];
+ delete expectedState.project.Leafs["Leaf_5a57825a4cfad13070870ddf"];
+ delete expectedState.project.Rectos["Recto_5a57825a4cfad13070870ddd"];
+ delete expectedState.project.Rectos["Recto_5a57825a4cfad13070870de0"];
+ delete expectedState.project.Versos["Verso_5a57825a4cfad13070870dde"];
+ delete expectedState.project.Versos["Verso_5a57825a4cfad13070870de1"];
+
+ delete expectedState.project.Groups["Group_5a57825a4cfad13070870df7"];
+ delete expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dd6"];
+ delete expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dd9"];
+ delete expectedState.project.Rectos["Recto_5a57825a4cfad13070870dd7"];
+ delete expectedState.project.Rectos["Recto_5a57825a4cfad13070870dda"];
+ delete expectedState.project.Versos["Verso_5a57825a4cfad13070870dd8"];
+ delete expectedState.project.Versos["Verso_5a57825a4cfad13070870ddb"];
+ expectedState.project.groupIDs.splice(expectedState.project.groupIDs.indexOf("Group_5a57825a4cfad13070870df6"), 1);
+ expectedState.project.groupIDs.splice(expectedState.project.groupIDs.indexOf("Group_5a57825a4cfad13070870df7"), 1);
+ expectedState.project.leafIDs.splice(expectedState.project.leafIDs.indexOf("Leaf_5a57825a4cfad13070870ddc"), 1);
+ expectedState.project.leafIDs.splice(expectedState.project.leafIDs.indexOf("Leaf_5a57825a4cfad13070870ddf"), 1);
+ expectedState.project.leafIDs.splice(expectedState.project.leafIDs.indexOf("Leaf_5a57825a4cfad13070870dd6"), 1);
+ expectedState.project.leafIDs.splice(expectedState.project.leafIDs.indexOf("Leaf_5a57825a4cfad13070870dd9"), 1);
+ expectedState.project.rectoIDs.splice(expectedState.project.rectoIDs.indexOf("Recto_5a57825a4cfad13070870ddd"), 1);
+ expectedState.project.rectoIDs.splice(expectedState.project.rectoIDs.indexOf("Recto_5a57825a4cfad13070870de0"), 1);
+ expectedState.project.rectoIDs.splice(expectedState.project.rectoIDs.indexOf("Recto_5a57825a4cfad13070870dd7"), 1);
+ expectedState.project.rectoIDs.splice(expectedState.project.rectoIDs.indexOf("Recto_5a57825a4cfad13070870dda"), 1);
+ expectedState.project.versoIDs.splice(expectedState.project.versoIDs.indexOf("Verso_5a57825a4cfad13070870dde"), 1);
+ expectedState.project.versoIDs.splice(expectedState.project.versoIDs.indexOf("Verso_5a57825a4cfad13070870de1"), 1);
+ expectedState.project.versoIDs.splice(expectedState.project.versoIDs.indexOf("Verso_5a57825a4cfad13070870dd8"), 1);
+ expectedState.project.versoIDs.splice(expectedState.project.versoIDs.indexOf("Verso_5a57825a4cfad13070870ddb"), 1);
+ expectedState.collationManager.selectedObjects.lastSelected = "";
+ expectedState.collationManager.selectedObjects.members = [];
+ expectedState.collationManager.selectedObjects.type = "";
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df5"].memberIDs.splice(0,1);
+ const gotState = deleteGroups(["Group_5a57825a4cfad13070870df6", "Group_5a57825a4cfad13070870df7"], beforeState);
+ expect(gotState).toEqual(expectedState)
+ })
+
+})
\ No newline at end of file
diff --git a/viscoll-app/__test__/actions/frontendBeforeActions/helperActions.spec.js b/viscoll-app/__test__/actions/frontendBeforeActions/helperActions.spec.js
new file mode 100644
index 00000000..5b85c1d0
--- /dev/null
+++ b/viscoll-app/__test__/actions/frontendBeforeActions/helperActions.spec.js
@@ -0,0 +1,48 @@
+import {
+ getLeafMembers,
+} from '../../../src/actions/frontend/before/helperActions';
+
+import {projectState001} from '../../testData/projectState001';
+
+import {cloneDeep} from 'lodash';
+
+describe('>>>A C T I O N --- Test helper actions', () => {
+
+ it('+++ actionCreator getLeafMembers', () => {
+ // Test getLeafMembers of 2nd group (Group_5a57825a4cfad13070870df5)
+ const memberIDs = [
+ 'Group_5a57825a4cfad13070870df6',
+ 'Leaf_5a57825a4cfad13070870de2',
+ 'Leaf_5a57825a4cfad13070870de5',
+ 'Leaf_5a57825a4cfad13070870de8',
+ 'Leaf_5a57825a4cfad13070870deb',
+ 'Leaf_5a57825a4cfad13070870dee',
+ 'Leaf_5a57825a4cfad13070870df1'
+ ];
+ const leafIDs = [];
+ const projectState = cloneDeep(projectState001);
+ const expectedLeafIDs = [
+ 'Leaf_5a57825a4cfad13070870dd6',
+ 'Leaf_5a57825a4cfad13070870dd9',
+ 'Leaf_5a57825a4cfad13070870ddc',
+ 'Leaf_5a57825a4cfad13070870ddf',
+ 'Leaf_5a57825a4cfad13070870de2',
+ 'Leaf_5a57825a4cfad13070870de5',
+ 'Leaf_5a57825a4cfad13070870de8',
+ 'Leaf_5a57825a4cfad13070870deb',
+ 'Leaf_5a57825a4cfad13070870dee',
+ 'Leaf_5a57825a4cfad13070870df1',
+ ]
+ getLeafMembers(memberIDs, projectState, leafIDs);
+ expect(leafIDs).toEqual(expectedLeafIDs);
+ })
+ it('+++ actionCreator getLeafMembers', () => {
+ // Test getLeafMembers of an empty group
+ const memberIDs = [];
+ const leafIDs = [];
+ const projectState = cloneDeep(projectState001);
+ const expectedLeafIDs = []
+ getLeafMembers(memberIDs, projectState, leafIDs);
+ expect(leafIDs).toEqual(expectedLeafIDs);
+ })
+})
\ No newline at end of file
diff --git a/viscoll-app/__test__/actions/frontendBeforeActions/imageActions.spec.js b/viscoll-app/__test__/actions/frontendBeforeActions/imageActions.spec.js
new file mode 100644
index 00000000..dffba831
--- /dev/null
+++ b/viscoll-app/__test__/actions/frontendBeforeActions/imageActions.spec.js
@@ -0,0 +1,112 @@
+import {
+ linkImages,
+ unlinkImages,
+ deleteImages,
+} from '../../../src/actions/frontend/before/imageActions';
+
+import {projectState001} from '../../testData/projectState001'
+import {dashboardState001} from '../../testData/dashboardState001'
+
+import {cloneDeep} from 'lodash';
+
+describe('>>>A C T I O N --- Test image actions', () => {
+ // Test linkImagesFromProject and linkImagesFromDashboard
+ it('+++ actionCreator linkImages', () => {
+ const imagePayload = {
+ payload: {
+ request : {
+ url: `/images/link`,
+ method: 'put',
+ data: {
+ "imageIDs": ['5a5783154cfad16535870e13'],
+ "projectIDs": ['5a57825a4cfad13070870dc3'],
+ },
+ successMessage: "Successfully linked the images",
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeDashState = cloneDeep(dashboardState001);
+ const beforeState = cloneDeep(projectState001);
+ let expectedDashState = cloneDeep(dashboardState001);
+ let expectedState = cloneDeep(projectState001);
+
+ expectedState.project.manifests.DIYImages.images.push({
+ label: '103496018.jpeg',
+ url: 'http://localhost:3001/images/5a5783154cfad16535870e13_103496018.jpeg',
+ manifestID: 'DIYImages'
+ });
+ expectedDashState.images[6].projectIDs.push('5a57825a4cfad13070870dc3');
+
+ const gotState = linkImages(imagePayload, beforeDashState, beforeState);
+ expect(gotState.active).toEqual(expectedState)
+ expect(gotState.dashboard).toEqual(expectedDashState)
+ })
+
+ // Test unlinkImagesFromProject and unlinkImagesFromDashboard
+ it('+++ actionCreator unlinkImages', () => {
+ const imagePayload = {
+ payload: {
+ request : {
+ url: `/images/unlink`,
+ method: 'put',
+ data: {
+ "imageIDs": ['5a5cc9594cfad17bed092f4c', '5a5cc95a4cfad17bed092f4e'],
+ "projectIDs": ['5a57825a4cfad13070870dc3'],
+ },
+ successMessage: "Successfully unlinked the images",
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeDashState = cloneDeep(dashboardState001);
+ const beforeState = cloneDeep(projectState001);
+ let expectedDashState = cloneDeep(dashboardState001);
+ let expectedState = cloneDeep(projectState001);
+
+ expectedState.project.manifests.DIYImages.images.splice(2,1)
+ expectedState.project.manifests.DIYImages.images.splice(3,1)
+ expectedState.project.Versos['Verso_5a57825a4cfad13070870dcc'].image = {};
+
+ expectedDashState.images[2].projectIDs.splice(0,1);
+ expectedDashState.images[4].projectIDs.splice(0,1);
+
+ const gotState = unlinkImages(imagePayload, beforeDashState, beforeState);
+ expect(gotState.active).toEqual(expectedState)
+ expect(gotState.dashboard).toEqual(expectedDashState)
+ })
+
+ // Test deleteImagesFromDashboard
+ it('+++ actionCreator deleteImages', () => {
+ const imagePayload = {
+ payload: {
+ request : {
+ url: `/images`,
+ method: 'delete',
+ data: {
+ "imageIDs": ['5a5cc9594cfad17bed092f4c', '5a5cc95a4cfad17bed092f4e'],
+ },
+ successMessage: "Successfully deleted the images",
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeDashState = cloneDeep(dashboardState001);
+ const beforeState = cloneDeep(projectState001);
+ let expectedDashState = cloneDeep(dashboardState001);
+ let expectedState = cloneDeep(projectState001);
+
+ expectedState.project.manifests.DIYImages.images.splice(2,1)
+ expectedState.project.manifests.DIYImages.images.splice(3,1)
+ expectedState.project.Versos['Verso_5a57825a4cfad13070870dcc'].image = {};
+
+ expectedDashState.images.splice(2,1);
+ expectedDashState.images.splice(3,1);
+
+ const gotState = deleteImages(imagePayload, beforeDashState, beforeState);
+
+ expect(gotState.active).toEqual(expectedState)
+ expect(gotState.dashboard).toEqual(expectedDashState)
+ })
+
+})
\ No newline at end of file
diff --git a/viscoll-app/__test__/actions/frontendBeforeActions/leafActions.spec.js b/viscoll-app/__test__/actions/frontendBeforeActions/leafActions.spec.js
new file mode 100644
index 00000000..c2a7a2c8
--- /dev/null
+++ b/viscoll-app/__test__/actions/frontendBeforeActions/leafActions.spec.js
@@ -0,0 +1,359 @@
+import {
+ createLeaves,
+ updateLeaf,
+ updateLeaves,
+ deleteLeaf,
+ deleteLeaves,
+ autoConjoinLeafs,
+ generateFolioPageNumbers,
+} from '../../../src/actions/frontend/before/leafActions';
+
+import {projectState001} from '../../testData/projectState001'
+
+import {cloneDeep} from 'lodash';
+
+describe('>>>A C T I O N --- Test leaf actions', () => {
+ it('+++ actionCreator createLeaves', () => {
+ const leafPayload = {
+ payload: {
+ request : {
+ url: `/leafs`,
+ method: 'post',
+ data: {
+ "leaf": {
+ project_id: "5a57825a4cfad13070870dc3",
+ parentID: "Group_5a57825a4cfad13070870df5",
+ },
+ "additional": {
+ conjoin: false,
+ leafIDs: ["111"],
+ sideIDs: ["11", "22"],
+ memberOrder: 8,
+ noOfLeafs: 1,
+ order: 17,
+ }
+ },
+ successMessage: "Successfully added the leaves" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df5"].memberIDs.push("Leaf_111");
+ expectedState.project.Leafs["Leaf_111"] = {
+ id: 'Leaf_111',
+ attached_above: 'None',
+ attached_below: 'None',
+ conjoined_to: null,
+ material: 'None',
+ stub: 'None',
+ type: 'None',
+ memberType: 'Leaf',
+ nestLevel: 1,
+ notes: [],
+ parentID: "Group_5a57825a4cfad13070870df5",
+ rectoID: "Recto_11",
+ versoID: "Verso_22",
+ }
+ expectedState.project.Rectos["Recto_11"] = {
+ id: "Recto_11",
+ parentID: "Leaf_111",
+ folio_number: null,
+ script_direction: 'None',
+ texture: 'Hair',
+ image: {},
+ notes: [],
+ memberType: 'Recto',
+ }
+ expectedState.project.Versos["Verso_22"] = {
+ id: "Verso_22",
+ parentID: "Leaf_111",
+ folio_number: null,
+ script_direction: 'None',
+ texture: 'Flesh',
+ image: {},
+ notes: [],
+ memberType: 'Verso',
+ }
+ expectedState.project.leafIDs.push("Leaf_111");
+ expectedState.project.rectoIDs.push("Recto_11");
+ expectedState.project.versoIDs.push("Verso_22");
+ expectedState.collationManager.flashItems.leaves.push("Leaf_111");
+ const gotState = createLeaves(leafPayload, beforeState);
+ expect(gotState).toEqual(expectedState)
+ })
+
+ it('+++ actionCreator updateLeaf', () => {
+ const leafPayload = {
+ payload: {
+ request : {
+ url: `/leafs/Leaf_5a57825a4cfad13070870dc4`,
+ method: 'post',
+ data: {
+ "leaf": {
+ material: "Parchment",
+ }
+ },
+ successMessage: "Successfully updated the leaf" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dc4"].material = "Parchment";
+ const gotState = updateLeaf(leafPayload, beforeState);
+ expect(gotState).toEqual(expectedState);
+ })
+ it('+++ actionCreator updateLeaves', () => {
+ const leafPayload = {
+ payload: {
+ request : {
+ url: `/leafs`,
+ method: 'post',
+ data: {
+ project_id: "5a57825a4cfad13070870dc3",
+ leafs: [
+ {
+ id: "Leaf_5a57825a4cfad13070870dc4",
+ attributes: {
+ material: "Parchment",
+ type: "Added"
+ }
+ },
+ {
+ id: "Leaf_5a57825a4cfad13070870dc7",
+ attributes: {
+ material: "Parchment",
+ type: "Added"
+ }
+ },
+ ]
+ },
+ successMessage: "Successfully updated the leaves" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dc4"].material = "Parchment";
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dc7"].material = "Parchment";
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dc4"].type = "Added";
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dc7"].type = "Added";
+ const gotState = updateLeaves(leafPayload, beforeState);
+ expect(gotState).toEqual(expectedState);
+ })
+
+ it('+++ actionCreator deleteLeaf', () => {
+ const leafPayload = {
+ payload: {
+ request : {
+ url: `/leafs/Leaf_5a57825a4cfad13070870dc4`,
+ method: 'delete',
+ successMessage: "Successfully deleted the leaf" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+
+ delete expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dc4"];
+ delete expectedState.project.Rectos["Recto_5a57825a4cfad13070870dc5"];
+ delete expectedState.project.Versos["Verso_5a57825a4cfad13070870dc6"];
+
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dd3"].conjoined_to = null;
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df4"].memberIDs.splice(0,1);
+ expectedState.project.Notes["5a57825a4cfad13070870df9"].objects.Leaf.splice(0,1);
+ expectedState.project.Notes["5a57825a4cfad13070870df9"].objects.Verso.splice(0,1);
+ expectedState.project.Notes["5a57825a4cfad13070870dfa"].objects.Verso.splice(0,1);
+ expectedState.project.leafIDs.splice(0,1);
+ expectedState.project.rectoIDs.splice(0,1);
+ expectedState.project.versoIDs.splice(0,1);
+
+ expectedState.collationManager.selectedObjects.lastSelected = "";
+ expectedState.collationManager.selectedObjects.members = [];
+ expectedState.collationManager.selectedObjects.type = "";
+
+ const gotState = deleteLeaf("Leaf_5a57825a4cfad13070870dc4", beforeState);
+ expect(gotState).toEqual(expectedState);
+ })
+
+ it('+++ actionCreator deleteLeaves', () => {
+ const leafPayload = {
+ payload: {
+ request : {
+ url: `/leafs`,
+ method: 'delete',
+ data: {
+ leafs: [
+ "Leaf_5a57825a4cfad13070870dc4",
+ "Leaf_5a57825a4cfad13070870df1"
+ ]
+ },
+ successMessage: "Successfully deleted the leaves" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+
+ // Delete first leaf
+ delete expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dc4"];
+ delete expectedState.project.Rectos["Recto_5a57825a4cfad13070870dc5"];
+ delete expectedState.project.Versos["Verso_5a57825a4cfad13070870dc6"];
+
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dd3"].conjoined_to = null;
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df4"].memberIDs.splice(0,1);
+ expectedState.project.Notes["5a57825a4cfad13070870df9"].objects.Leaf.splice(0,1);
+ expectedState.project.Notes["5a57825a4cfad13070870df9"].objects.Verso.splice(0,1);
+ expectedState.project.Notes["5a57825a4cfad13070870dfa"].objects.Verso.splice(0,1);
+ expectedState.project.leafIDs.splice(0,1);
+ expectedState.project.rectoIDs.splice(0,1);
+ expectedState.project.versoIDs.splice(0,1);
+
+ // Delete second leaf
+ delete expectedState.project.Leafs["Leaf_5a57825a4cfad13070870df1"];
+ delete expectedState.project.Rectos["Recto_5a57825a4cfad13070870df2"];
+ delete expectedState.project.Versos["Verso_5a57825a4cfad13070870df3"];
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870de2"].conjoined_to = null;
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dee"].attached_below = "None";
+
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df5"].memberIDs.splice(-1,1);
+ expectedState.project.leafIDs.splice(-1,1);
+ expectedState.project.rectoIDs.splice(-1,1);
+ expectedState.project.versoIDs.splice(-1,1);
+
+ expectedState.collationManager.selectedObjects.lastSelected = "";
+ expectedState.collationManager.selectedObjects.members = [];
+ expectedState.collationManager.selectedObjects.type = "";
+
+ const gotState = deleteLeaves(["Leaf_5a57825a4cfad13070870dc4", "Leaf_5a57825a4cfad13070870df1"], beforeState);
+ expect(gotState).toEqual(expectedState);
+ })
+
+ it('+++ actionCreator autoConjoinLeafs', () => {
+ const leafPayload = {
+ payload: {
+ request : {
+ url: `/leafs/conjoin`,
+ method: 'put',
+ data: {
+ leafs: [
+ "Leaf_5a57825a4cfad13070870dd9",
+ "Leaf_5a57825a4cfad13070870dd6"
+ ]
+ },
+ successMessage: "Successfully conjoined the leaves" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dd9"].conjoined_to = "Leaf_5a57825a4cfad13070870dd6";
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dd6"].conjoined_to = "Leaf_5a57825a4cfad13070870dd9";
+
+ const gotState = autoConjoinLeafs(leafPayload, beforeState, ["Leaf_5a57825a4cfad13070870dd9","Leaf_5a57825a4cfad13070870dd6"]);
+ expect(gotState).toEqual(expectedState);
+ })
+
+ it('+++ actionCreator autoConjoinLeafs for odd number of leaves', () => {
+ const leafPayload = {
+ payload: {
+ request : {
+ url: `/leafs/conjoin`,
+ method: 'put',
+ data: {
+ leafs: [
+ 'Leaf_5a57825a4cfad13070870dc4',
+ 'Leaf_5a57825a4cfad13070870dc7',
+ 'Leaf_5a57825a4cfad13070870dca',
+ ]
+ },
+ successMessage: "Successfully conjoined the leaves" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dc4"].conjoined_to = "Leaf_5a57825a4cfad13070870dca";
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dd3"].conjoined_to = null;
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dc7"].conjoined_to = null;
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dd0"].conjoined_to = null;
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dca"].conjoined_to = "Leaf_5a57825a4cfad13070870dc4";
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dcd"].conjoined_to = null;
+
+ const gotState = autoConjoinLeafs(leafPayload, beforeState, ["Leaf_5a57825a4cfad13070870dc4","Leaf_5a57825a4cfad13070870dc7","Leaf_5a57825a4cfad13070870dca"]);
+ expect(gotState).toEqual(expectedState);
+ })
+
+ it('+++ actionCreator generateFolioPageNumbers', () => {
+ const leafPayload = {
+ payload: {
+ request : {
+ url: `/leafs/generateFolio`,
+ method: 'put',
+ data: {
+ startNumber: 3,
+ rectoIDs: [
+ 'Recto_5a57825a4cfad13070870dc8',
+ 'Recto_5a57825a4cfad13070870dcb',
+ 'Recto_5a57825a4cfad13070870dce',
+ 'Recto_5a57825a4cfad13070870dd1',
+ 'Recto_5a57825a4cfad13070870df2'
+ ],
+ versoIDs: [
+ 'Verso_5a57825a4cfad13070870dc9',
+ 'Verso_5a57825a4cfad13070870dcc',
+ 'Verso_5a57825a4cfad13070870dcf',
+ 'Verso_5a57825a4cfad13070870dd2',
+ 'Verso_5a57825a4cfad13070870df3'
+ ],
+ },
+ successMessage: "Successfully generated the folio numbers" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+
+ let gotState = generateFolioPageNumbers(leafPayload, beforeState, "folio_number");
+ leafPayload.payload.request.data.startNumber = 6;
+ gotState = generateFolioPageNumbers(leafPayload, gotState, "page_number");
+
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dc8"].folio_number = "3R";
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dcb"].folio_number = "4R";
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dce"].folio_number = "5R";
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dd1"].folio_number = "6R";
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870df2"].folio_number = "7R";
+
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870dc9"].folio_number = "3V";
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870dcc"].folio_number = "4V";
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870dcf"].folio_number = "5V";
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870dd2"].folio_number = "6V";
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870df3"].folio_number = "7V";
+
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dc8"].page_number = 6;
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dcb"].page_number = 8;
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dce"].page_number = 10;
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dd1"].page_number = 12;
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870df2"].page_number = 14;
+
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870dc9"].page_number = 7;
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870dcc"].page_number = 9;
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870dcf"].page_number = 11;
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870dd2"].page_number = 13;
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870df3"].page_number = 15;
+
+ expect(gotState).toEqual(expectedState);
+ })
+
+})
\ No newline at end of file
diff --git a/viscoll-app/__test__/actions/frontendBeforeActions/manifestActions.spec.js b/viscoll-app/__test__/actions/frontendBeforeActions/manifestActions.spec.js
new file mode 100644
index 00000000..ca50dd59
--- /dev/null
+++ b/viscoll-app/__test__/actions/frontendBeforeActions/manifestActions.spec.js
@@ -0,0 +1,69 @@
+import {
+ updateManifest,
+ deleteManifest,
+} from '../../../src/actions/frontend/before/manifestActions';
+
+import {projectState001} from '../../testData/projectState001';
+
+import {cloneDeep} from 'lodash';
+
+describe('>>>A C T I O N --- Test manifest actions', () => {
+
+ it('+++ actionCreator updateManifest', () => {
+ const manifestPayload = {
+ payload: {
+ request : {
+ url: `/projects/5a57825a4cfad13070870dc3/manifests`,
+ method: 'put',
+ data: {
+ "manifest": {
+ "id": "5a25b0703b0eb7478b415bd4",
+ "name": "new manifest name",
+ }
+ },
+ successMessage: "Successfully updated the manifest",
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+
+ expectedState.project.manifests["5a25b0703b0eb7478b415bd4"].name = "new manifest name";
+
+ const gotState = updateManifest(manifestPayload, beforeState);
+ expect(gotState).toEqual(expectedState);
+ })
+
+ it('+++ actionCreator deleteManifest', () => {
+ const manifestPayload = {
+ payload: {
+ request : {
+ url: `/projects/5a57825a4cfad13070870dc3/manifests`,
+ method: 'delete',
+ data: {
+ "manifest": {
+ "id": "5a25b0703b0eb7478b415bd4",
+ }
+ },
+ successMessage: "Successfully deleted the manifest",
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+
+ delete expectedState.project.manifests["5a25b0703b0eb7478b415bd4"];
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dc5"].image = {};
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dc8"].image = {};
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dcb"].image = {};
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dce"].image = {};
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dd1"].image = {};
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dd4"].image = {};
+
+ const gotState = deleteManifest(manifestPayload, beforeState);
+ expect(gotState).toEqual(expectedState);
+ })
+
+})
\ No newline at end of file
diff --git a/viscoll-app/__test__/actions/frontendBeforeActions/noteActions.spec.js b/viscoll-app/__test__/actions/frontendBeforeActions/noteActions.spec.js
new file mode 100644
index 00000000..688e596e
--- /dev/null
+++ b/viscoll-app/__test__/actions/frontendBeforeActions/noteActions.spec.js
@@ -0,0 +1,250 @@
+import {
+ createNoteType,
+ updateNoteType,
+ deleteNoteType,
+ createNote,
+ updateNote,
+ linkNote,
+ unlinkNote,
+ deleteNote,
+} from '../../../src/actions/frontend/before/noteActions';
+
+import {projectState001} from '../../testData/projectState001'
+
+import {cloneDeep} from 'lodash';
+
+describe('>>>A C T I O N --- Test note actions', () => {
+
+ it('+++ actionCreator createNoteType', () => {
+ const noteTypePayload = {
+ payload: {
+ request : {
+ url: `/notes/type`,
+ method: 'post',
+ data: {
+ "noteType": {
+ "project_id": "5951303fc9bf3c7b9a573a3f",
+ "type": "Watermark"
+ }
+ },
+ successMessage: "Successfully created the note type" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ const createNoteTypeAction = createNoteType(noteTypePayload, beforeState);
+ let afterState = cloneDeep(projectState001);
+ afterState.project.noteTypes.push("Watermark");
+ expect(createNoteTypeAction).toEqual(afterState);
+ })
+
+ it('+++ actionCreator updateNoteType', () => {
+ const noteTypePayload = {
+ payload: {
+ request: {
+ url: '/notes/type',
+ method: 'put',
+ data: {
+ noteType: {
+ project_id: "5951303fc9bf3c7b9a573a3f",
+ old_type: 'Damage',
+ type: 'Damages',
+ }
+ },
+ successMessage: "Successfully updated the note type",
+ errorMessage: "Oops! Something went wrong",
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001)
+ let expectedState = cloneDeep(projectState001)
+ expectedState.project.noteTypes[3] = 'Damages'
+ expectedState.project.Notes['5a57825a4cfad13070870dfa'].type = 'Damages'
+ let gotState = updateNoteType(noteTypePayload, beforeState)
+ expect(gotState).toEqual(expectedState)
+ })
+
+ it('+++ actionCreator deleteNoteType', () => {
+ const noteTypePayload = {
+ payload: {
+ request: {
+ url: '/notes/type',
+ method: 'delete',
+ data: {
+ noteType: {
+ project_id: "5951303fc9bf3c7b9a573a3f",
+ type: "Hand"
+ }
+ },
+ successMessage: "Successfully deleted the note type",
+ errorMessage: "Oops! Something went wrong",
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001)
+ let expectedState = cloneDeep(projectState001)
+ expectedState.project.noteTypes = ['Unknown', 'Ink', 'Damage']
+ expectedState.project.Notes['5a57825a4cfad13070870df9'].type = 'Unknown'
+ let gotState = deleteNoteType(noteTypePayload, beforeState)
+ expect(gotState).toEqual(expectedState)
+ })
+
+ it('+++ actionCreator createNote', () => {
+ const notePayload = {
+ payload: {
+ request: {
+ url: '/notes',
+ method: 'post',
+ data: {
+ note: {
+ id: "f951303fc9bf3c7b9a573a3f",
+ project_id: "5951303fc9bf3c7b9a573a3f",
+ title: "Example Note",
+ type: "asd",
+ description: "example content",
+ show: true,
+ }
+ },
+ successMessage: "",
+ errorMessage: ""
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001)
+ let expectedState = cloneDeep(beforeState)
+ expectedState.project.Notes["f951303fc9bf3c7b9a573a3f"] = {
+ id: "f951303fc9bf3c7b9a573a3f",
+ title: "Example Note",
+ type: "asd",
+ description: "example content",
+ show: true,
+ objects: { Group: [], Leaf: [], Recto: [], Verso: [] }
+ }
+ let gotState = createNote(notePayload, beforeState)
+ expect(gotState).toEqual(expectedState)
+ })
+
+ it('+++ actionCreator updateNote', () => {
+ const notePayload = {
+ payload: {
+ request: {
+ url: '/notes/5a57825a4cfad13070870df8',
+ method: 'put',
+ data: {
+ note: {
+ description: "Some lot of black ink over here",
+ title: "Black inks",
+ type: "Ink"
+ }
+ },
+ successMessage: "",
+ errorMessage: ""
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001)
+ let expectedState = cloneDeep(beforeState)
+ expectedState.project.Notes["5a57825a4cfad13070870df8"].title = "Black inks"
+ expectedState.project.Notes["5a57825a4cfad13070870df8"].type = "Ink"
+ expectedState.project.Notes["5a57825a4cfad13070870df8"].description = "Some lot of black ink over here"
+ let gotState = updateNote(notePayload, beforeState)
+ expect(gotState).toEqual(expectedState)
+ })
+
+ it('+++ actionCreator linkNote', () => {
+ const notePayload = {
+ payload: {
+ request: {
+ url: '/notes/5a57825a4cfad13070870df8/link',
+ method: 'put',
+ data: {
+ objects: [
+ {
+ type: "Verso",
+ id: "Verso_5a57825a4cfad13070870dc6",
+ },
+ {
+ type: "Leaf",
+ id: "Leaf_5a57825a4cfad13070870dee",
+ },
+ {
+ type: "Group",
+ id: "Group_5a57825a4cfad13070870df6",
+ }
+ ]
+ },
+ successMessage: "",
+ errorMessage: ""
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001)
+ let expectedState = cloneDeep(beforeState)
+ expectedState.project.Notes["5a57825a4cfad13070870df8"].objects.Group.push("Group_5a57825a4cfad13070870df6")
+ expectedState.project.Notes["5a57825a4cfad13070870df8"].objects.Leaf.push("Leaf_5a57825a4cfad13070870dee")
+ expectedState.project.Notes["5a57825a4cfad13070870df8"].objects.Verso.push("Verso_5a57825a4cfad13070870dc6")
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df6"].notes.push("5a57825a4cfad13070870df8")
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870dee"].notes.push("5a57825a4cfad13070870df8")
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870dc6"].notes.push("5a57825a4cfad13070870df8")
+ let gotState = linkNote(notePayload, beforeState)
+ expect(gotState).toEqual(expectedState)
+ })
+
+ it('+++ actionCreator unlinkNote', () => {
+ const notePayload = {
+ payload: {
+ request: {
+ url: '/notes/5a57825a4cfad13070870df8/unlink',
+ method: 'put',
+ data: {
+ objects: [
+ {
+ type: "Group",
+ id: "Group_5a57825a4cfad13070870df5",
+ },
+ {
+ type: "Leaf",
+ id: "Leaf_5a57825a4cfad13070870de8",
+ },
+ ]
+ },
+ successMessage: "",
+ errorMessage: ""
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001)
+ let expectedState = cloneDeep(beforeState)
+ expectedState.project.Notes["5a57825a4cfad13070870df8"].objects.Group.splice(-1,1)
+ expectedState.project.Notes["5a57825a4cfad13070870df8"].objects.Leaf.splice(1,1)
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df5"].notes = []
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870de8"].notes = []
+ let gotState = unlinkNote(notePayload, beforeState)
+ expect(gotState).toEqual(expectedState)
+ })
+
+ it('+++ actionCreator deleteNote', () => {
+ const notePayload = {
+ payload: {
+ request: {
+ url: '/notes/5a57825a4cfad13070870df8',
+ method: 'delete',
+ successMessage: "",
+ errorMessage: ""
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001)
+ let expectedState = cloneDeep(beforeState)
+ delete expectedState.project.Notes["5a57825a4cfad13070870df8"]
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df4"].notes = []
+ expectedState.project.Groups["Group_5a57825a4cfad13070870df5"].notes = []
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870de5"].notes = []
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870de8"].notes = []
+ expectedState.project.Leafs["Leaf_5a57825a4cfad13070870deb"].notes = []
+ let gotState = deleteNote(notePayload, beforeState)
+ expect(gotState).toEqual(expectedState)
+ })
+
+})
diff --git a/viscoll-app/__test__/actions/frontendBeforeActions/projectActions.spec.js b/viscoll-app/__test__/actions/frontendBeforeActions/projectActions.spec.js
new file mode 100644
index 00000000..5a2f46a3
--- /dev/null
+++ b/viscoll-app/__test__/actions/frontendBeforeActions/projectActions.spec.js
@@ -0,0 +1,94 @@
+import {
+ updateProject,
+ deleteProject,
+} from '../../../src/actions/frontend/before/projectActions';
+
+import {dashboardState001} from '../../testData/dashboardState001'
+
+import {cloneDeep} from 'lodash';
+
+describe('>>>A C T I O N --- Test project actions', () => {
+
+ it('+++ actionCreator updateProject', () => {
+ const projectPayload = {
+ payload: {
+ request : {
+ url: `/projects/5a57825a4cfad13070870dc3`,
+ method: 'put',
+ data: {
+ "project": {
+ "title": "my prject 123123",
+ "shelfmark": "mss 568456",
+ "metadata": {
+ "date": "18th century"
+ }
+ }
+ },
+ successMessage: "Successfully linked the images",
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(dashboardState001);
+ let expectedState = cloneDeep(dashboardState001);
+
+ expectedState.projects[0].title = "my prject 123123";
+ expectedState.projects[0].shelfmark = "mss 568456";
+ expectedState.projects[0].metadata.date = "18th century";
+
+ const gotState = updateProject(projectPayload, beforeState);
+ expect(gotState).toEqual(expectedState);
+ })
+
+ it('+++ actionCreator deleteProject delete images', () => {
+ const projectPayload = {
+ payload: {
+ request : {
+ url: `/projects/5a57825a4cfad13070870dc3`,
+ method: 'delete',
+ data: {
+ "deleteUnlinkedImages": true
+ },
+ successMessage: "Successfully linked the images",
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(dashboardState001);
+ let expectedState = cloneDeep(dashboardState001);
+
+ expectedState.projects.splice(0,1);
+ expectedState.images.splice(0, 6);
+
+ const gotState = deleteProject(projectPayload, beforeState);
+ expect(gotState).toEqual(expectedState);
+ })
+
+ it('+++ actionCreator deleteProject do not delete images', () => {
+ const projectPayload = {
+ payload: {
+ request : {
+ url: `/projects/5a57825a4cfad13070870dc3`,
+ method: 'delete',
+ data: {
+ "deleteUnlinkedImages": false
+ },
+ successMessage: "Successfully linked the images",
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(dashboardState001);
+ let expectedState = cloneDeep(dashboardState001);
+
+ expectedState.projects.splice(0,1);
+ for (let i in expectedState.images) {
+ if (expectedState.images[i].projectIDs.includes('5a57825a4cfad13070870dc3')) {
+ expectedState.images[i].projectIDs.splice(0,1);
+ }
+ }
+
+ const gotState = deleteProject(projectPayload, beforeState);
+ expect(gotState).toEqual(expectedState);
+ })
+})
\ No newline at end of file
diff --git a/viscoll-app/__test__/actions/frontendBeforeActions/sideActions.spec.js b/viscoll-app/__test__/actions/frontendBeforeActions/sideActions.spec.js
new file mode 100644
index 00000000..99cb176a
--- /dev/null
+++ b/viscoll-app/__test__/actions/frontendBeforeActions/sideActions.spec.js
@@ -0,0 +1,148 @@
+import {
+ updateSide,
+ updateSides,
+ mapSides,
+ generateFolioNumbers,
+} from '../../../src/actions/frontend/before/sideActions';
+
+import {projectState001} from '../../testData/projectState001'
+import {dashboardState001} from '../../testData/dashboardState001'
+
+import {cloneDeep} from 'lodash';
+
+describe('>>>A C T I O N --- Test side actions', () => {
+ it('+++ actionCreator updateSide', () => {
+ const sidePayload = {
+ payload: {
+ request : {
+ url: `/sides/Recto_5a57825a4cfad13070870dc8`,
+ method: 'put',
+ data: {
+ "side": {
+ texture: "Felt",
+ },
+ },
+ successMessage: "Successfully updated the side" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dc8"].texture = "Felt";
+
+ const gotState = updateSide(sidePayload, beforeState);
+ expect(gotState).toEqual(expectedState)
+ })
+
+ it('+++ actionCreator updateSides', () => {
+ const sidePayload = {
+ payload: {
+ request : {
+ url: `/sides`,
+ method: 'put',
+ data: {
+ "sides": [
+ {
+ id: "Verso_5a57825a4cfad13070870dcc",
+ attributes: { script_direction: "Top-To-Bottom"},
+ },
+ {
+ id: "Verso_5a57825a4cfad13070870dc9",
+ attributes: { script_direction: "Top-To-Bottom"},
+ },
+ {
+ id: "Verso_5a57825a4cfad13070870dc6",
+ attributes: { script_direction: "Right-To-Left"},
+ },
+ ]
+ },
+ successMessage: "Successfully updated the side" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870dcc"].script_direction = "Top-To-Bottom";
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870dc9"].script_direction = "Top-To-Bottom";
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870dc6"].script_direction = "Right-To-Left";
+
+ const gotState = updateSides(sidePayload, beforeState);
+ expect(gotState).toEqual(expectedState)
+ })
+ it('+++ actionCreator mapSides', () => {
+ const sidePayload = {
+ payload: {
+ request : {
+ url: `/sides`,
+ method: 'put',
+ data: {
+ "sides": [
+ {
+ "id": "Recto_5a57825a4cfad13070870ddd",
+ "attributes": {
+ "image": {
+ "manifestID": "DIYImages",
+ "label": "cguk1l0u4aeewdf.jpeg",
+ "url": "http://localhost:3001/images/5a5cc9594cfad17bed092f4a_cguk1l0u4aeewdf.jpeg"
+ }
+ }
+ },
+ {
+ "id": "Verso_5a57825a4cfad13070870de1",
+ "attributes": {
+ "image": {
+ "label": "Hollar_a_3000_0005",
+ "url": "https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0005",
+ "manifestID": "5a25b0703b0eb7478b415bd4"
+ }
+ }
+ },
+ {
+ "id": "Recto_5a57825a4cfad13070870dc5",
+ "attributes": {
+ "image": {}
+ }
+ },
+ {
+ "id": "Verso_5a57825a4cfad13070870dc6",
+ "attributes": {
+ "image": {}
+ }
+ }
+ ]
+ },
+ successMessage: "Successfully updated the side" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+ const beforeDashState = cloneDeep(dashboardState001);
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+ const expectedDashState = cloneDeep(dashboardState001);
+
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870ddd"].image = {
+ "manifestID": "DIYImages",
+ "label": "cguk1l0u4aeewdf.jpeg",
+ "url": "http://localhost:3001/images/5a5cc9594cfad17bed092f4a_cguk1l0u4aeewdf.jpeg"
+ };
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870de1"].image = {
+ "label": "Hollar_a_3000_0005",
+ "url": "https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0005",
+ "manifestID": "5a25b0703b0eb7478b415bd4"
+ };
+ expectedState.project.Rectos["Recto_5a57825a4cfad13070870dc5"].image = {};
+ expectedState.project.Versos["Verso_5a57825a4cfad13070870dc6"].image = {};
+
+ expectedDashState.images[0].sideIDs.splice(0,1);
+ expectedDashState.images[0].sideIDs.push("Recto_5a57825a4cfad13070870ddd");
+
+ const gotState = mapSides(sidePayload, beforeState, beforeDashState);
+ expect(gotState.active).toEqual(expectedState)
+ expect(gotState.dashboard).toEqual(expectedDashState)
+ })
+})
\ No newline at end of file
diff --git a/viscoll-app/__test__/testData/dashboardState001.js b/viscoll-app/__test__/testData/dashboardState001.js
new file mode 100644
index 00000000..ba0e2687
--- /dev/null
+++ b/viscoll-app/__test__/testData/dashboardState001.js
@@ -0,0 +1,78 @@
+export const dashboardState001 = {
+ projects: [
+ {
+ id: '5a57825a4cfad13070870dc3',
+ title: 'my prject',
+ shelfmark: 'mss 568',
+ metadata: {
+ date: '19th century'
+ },
+ created_at: '2018-01-12T19:05:20.803Z',
+ updated_at: '2018-01-12T19:05:21.175Z'
+ },
+ ],
+ images: [
+ {
+ id: '5a5cc9594cfad17bed092f4a',
+ projectIDs: [
+ '5a57825a4cfad13070870dc3'
+ ],
+ sideIDs: ['Verso_5a57825a4cfad13070870dc6'],
+ url: 'http://localhost:3001/images/5a5cc9594cfad17bed092f4a_cguk1l0u4aeewdf.jpeg',
+ label: 'cguk1l0u4aeewdf.jpeg'
+ },
+ {
+ id: '5a5cc9594cfad17bed092f4b',
+ projectIDs: [
+ '5a57825a4cfad13070870dc3'
+ ],
+ sideIDs: ['Verso_5a57825a4cfad13070870dc9'],
+ url: 'http://localhost:3001/images/5a5cc9594cfad17bed092f4b_3c17f2b4127b1a5a8bcfc76ba9de9c9f_chiba_inu_dogs_shiba_inu_funny.jpeg',
+ label: '3c17f2b4127b1a5a8bcfc76ba9de9c9f_chiba_inu_dogs_shiba_inu_funny.jpeg'
+ },
+ {
+ id: '5a5cc9594cfad17bed092f4c',
+ projectIDs: [
+ '5a57825a4cfad13070870dc3'
+ ],
+ sideIDs: ['Verso_5a57825a4cfad13070870dcc'],
+ url: 'http://localhost:3001/images/5a5cc9594cfad17bed092f4c_1_105.jpeg',
+ label: '1_105.jpeg'
+ },
+ {
+ id: '5a5783154cfad13070870e0e',
+ projectIDs: [
+ '5a57825a4cfad13070870dc3'
+ ],
+ sideIDs: [],
+ url: 'http://localhost:3001/images/5a5783154cfad13070870e0e_shiba_inu_taiki.jpeg',
+ label: 'shiba_inu_taiki.jpeg'
+ },
+ {
+ id: '5a5cc95a4cfad17bed092f4e',
+ projectIDs: [
+ '5a57825a4cfad13070870dc3'
+ ],
+ sideIDs: [],
+ url: 'http://localhost:3001/images/5a5cc95a4cfad17bed092f4e_cnrvtp6vaaamulm.png',
+ label: 'cnrvtp6vaaamulm.png'
+ },
+ {
+ id: '5a5783154cfad13070870e13',
+ projectIDs: [
+ '5a57825a4cfad13070870dc3'
+ ],
+ sideIDs: [],
+ url: 'http://localhost:3001/images/5a5783154cfad13070870e13_shiba_inu_3jpg.jpeg',
+ label: 'shiba_inu_3jpg.jpeg'
+ },
+ {
+ id: '5a5783154cfad16535870e13',
+ projectIDs: [],
+ sideIDs: [],
+ url: 'http://localhost:3001/images/5a5783154cfad16535870e13_103496018.jpeg',
+ label: '103496018.jpeg'
+ }
+ ],
+ importStatus: null
+}
\ No newline at end of file
diff --git a/viscoll-app/__test__/testData/membersStructure01.js b/viscoll-app/__test__/testData/membersStructure01.js
new file mode 100644
index 00000000..aa39f4d3
--- /dev/null
+++ b/viscoll-app/__test__/testData/membersStructure01.js
@@ -0,0 +1,308 @@
+/* This has the following structure
+Group 1
+ Leaf 1
+ Leaf 2
+Group 2
+ Group 3
+ Leaf 3
+ Group 4
+ Leaf 4
+ Leaf 5
+*/
+
+
+export const side0_leaf1 = {
+ id: "side0_leaf1_id",
+ member_type: "Side",
+ leaf_id: "leaf1_id",
+ order: 0,
+ folio_number: "None",
+ texture: "None",
+ uri: "None",
+ script_direction: "None",
+ notes: []
+}
+
+export const side1_leaf1 = {
+ id: "side1_leaf1_id",
+ member_type: "Side",
+ leaf_id: "leaf1_id",
+ order: 1,
+ folio_number: "None",
+ texture: "None",
+ uri: "None",
+ script_direction: "None",
+ notes: []
+}
+
+export const side0_leaf2 = {
+ id: "side0_leaf2_id",
+ member_type: "Side",
+ leaf_id: "leaf2_id",
+ order: 0,
+ folio_number: "None",
+ texture: "None",
+ uri: "None",
+ script_direction: "None",
+ notes: []
+}
+
+
+export const side1_leaf2 = {
+ id: "side1_leaf2_id",
+ member_type: "Side",
+ leaf_id: "leaf2_id",
+ order: 1,
+ folio_number: "None",
+ texture: "None",
+ uri: "None",
+ script_direction: "None",
+ notes: []
+}
+
+export const side0_leaf3 = {
+ id: "side0_leaf3_id",
+ member_type: "Side",
+ leaf_id: "leaf3_id",
+ order: 0,
+ folio_number: "None",
+ texture: "None",
+ uri: "None",
+ script_direction: "None",
+ notes: []
+}
+
+export const side1_leaf3 = {
+ id: "side1_leaf3_id",
+ member_type: "Side",
+ leaf_id: "leaf3_id",
+ order: 1,
+ folio_number: "None",
+ texture: "None",
+ uri: "None",
+ script_direction: "None",
+ notes: []
+}
+
+export const side0_leaf4 = {
+ id: "side0_leaf4_id",
+ member_type: "Side",
+ leaf_id: "leaf4_id",
+ order: 0,
+ folio_number: "None",
+ texture: "None",
+ uri: "None",
+ script_direction: "None",
+ notes: []
+}
+
+export const side1_leaf4 = {
+ id: "side1_leaf4_id",
+ member_type: "Side",
+ leaf_id: "leaf4_id",
+ order: 1,
+ folio_number: "None",
+ texture: "None",
+ uri: "None",
+ script_direction: "None",
+ notes: []
+}
+
+export const side0_leaf5 = {
+ id: "side0_leaf5_id",
+ member_type: "Side",
+ leaf_id: "leaf5_id",
+ order: 0,
+ folio_number: "None",
+ texture: "None",
+ uri: "None",
+ script_direction: "None",
+ notes: []
+}
+
+export const side1_leaf5 = {
+ id: "side1_leaf5_id",
+ member_type: "Side",
+ leaf_id: "leaf5_id",
+ order: 1,
+ folio_number: "None",
+ texture: "None",
+ uri: "None",
+ script_direction: "None",
+ notes: []
+}
+
+
+export const leaf1 = {
+ id: "leaf1_id",
+ member_type: "Leaf",
+ member_order: 1,
+ order: 1,
+ material: "Paper",
+ type: "None",
+ conjoined_to: "555",
+ attached_to: {
+ aboveID: "",
+ aboveMethod: "",
+ belowID: "",
+ belowMethod: ""
+ },
+ stub: "None",
+ notes: [],
+ sides: [
+ side0_leaf1,
+ side1_leaf1
+ ]
+}
+
+export const leaf2 = {
+ id: "leaf2_id",
+ member_type: "Leaf",
+ member_order: 2,
+ order: 2,
+ material: "Paper",
+ type: "None",
+ conjoined_to: "555",
+ attached_to: {
+ aboveID: "",
+ aboveMethod: "",
+ belowID: "",
+ belowMethod: ""
+ },
+ stub: "None",
+ notes: [],
+ sides: [
+ side0_leaf2,
+ side1_leaf2
+ ]
+}
+
+export const leaf3 = {
+ id: "leaf3_id",
+ member_type: "Leaf",
+ member_order: 1,
+ order: 3,
+ material: "None",
+ type: "None",
+ conjoined_to: "555",
+ attached_to: {
+ aboveID: "",
+ aboveMethod: "",
+ belowID: "",
+ belowMethod: ""
+ },
+ stub: "None",
+ notes: [],
+ sides: [
+ side0_leaf3,
+ side1_leaf3
+ ]
+}
+
+export const leaf4 = {
+ id: "leaf4_id",
+ member_type: "Leaf",
+ member_order: 1,
+ order: 4,
+ material: "None",
+ type: "None",
+ conjoined_to: "555",
+ attached_to: {
+ aboveID: "",
+ aboveMethod: "",
+ belowID: "",
+ belowMethod: ""
+ },
+ stub: "None",
+ notes: [],
+ sides: [
+ side0_leaf4,
+ side1_leaf4
+ ]
+}
+
+export const leaf5 = {
+ id: "leaf5_id",
+ member_type: "Leaf",
+ member_order: 2,
+ order: 5,
+ material: "None",
+ type: "None",
+ conjoined_to: "555",
+ attached_to: {
+ aboveID: "",
+ aboveMethod: "",
+ belowID: "",
+ belowMethod: ""
+ },
+ stub: "None",
+ notes: [],
+ sides: [
+ side0_leaf5,
+ side1_leaf5
+ ]
+}
+
+export const group1 = {
+ id: "group1_id",
+ member_type: "Group",
+ member_order: 1,
+ order: 1,
+ title: "Default",
+ type: "Quire",
+ notes: [],
+ members: [
+ leaf1,
+ leaf2
+ ]
+}
+
+
+export const group4 = {
+ id: "group4_id",
+ member_type: "Group",
+ member_order: 2,
+ order: 4,
+ title: "Default",
+ type: "Quire",
+ notes: [],
+ members: [
+ leaf4
+ ]
+}
+
+export const group3 = {
+ id: "group3_id",
+ member_type: "Group",
+ member_order: 1,
+ order: 3,
+ title: "Default",
+ type: "Quire",
+ notes: [],
+ members: [
+ leaf3,
+ group4
+ ]
+}
+
+export const group2 = {
+ id: "group2_id",
+ member_type: "Group",
+ member_order: 2,
+ order: 2,
+ title: "Default",
+ type: "Quire",
+ notes: [],
+ members: [
+ group3,
+ leaf5
+ ]
+}
+
+
+
+export const members = [
+ group1,
+ group2
+]
+
diff --git a/viscoll-app/__test__/testData/projectState001.js b/viscoll-app/__test__/testData/projectState001.js
new file mode 100644
index 00000000..339a690f
--- /dev/null
+++ b/viscoll-app/__test__/testData/projectState001.js
@@ -0,0 +1,4737 @@
+export const projectState001 = {
+ project: {
+ id: '5a57825a4cfad13070870dc3',
+ title: 'my prject',
+ shelfmark: 'mss 568',
+ metadata: {
+ date: '19th century'
+ },
+ preferences: {
+ showTips: true
+ },
+ noteTypes: [
+ 'Unknown',
+ 'Ink',
+ 'Hand',
+ 'Damage'
+ ],
+ manifests: {
+ DIYImages: {
+ id: 'DIYImages',
+ images: [
+ {
+ label: 'cguk1l0u4aeewdf.jpeg',
+ url: 'http://localhost:3001/images/5a5cc9594cfad17bed092f4a_cguk1l0u4aeewdf.jpeg',
+ manifestID: 'DIYImages'
+ },
+ {
+ label: '5a5cc9594cfad17bed092f4b_chiba_inu_dogs_shiba_inu_funny.jpeg',
+ url: 'http://localhost:3001/images/5a5783154cfad13070870e0a_5a5cc9594cfad17bed092f4b_chiba_inu_dogs_shiba_inu_funny.jpeg',
+ manifestID: 'DIYImages'
+ },
+ {
+ label: '1_105.jpeg',
+ url: 'http://localhost:3001/images/5a5cc9594cfad17bed092f4c_1_105.jpeg',
+ manifestID: 'DIYImages'
+ },
+ {
+ label: 'shiba_inu_taiki.jpeg',
+ url: 'http://localhost:3001/images/5a5783154cfad13070870e0e_shiba_inu_taiki.jpeg',
+ manifestID: 'DIYImages'
+ },
+ {
+ label: 'cnrvtp6vaaamulm.png',
+ url: 'http://localhost:3001/images/5a5cc95a4cfad17bed092f4e_cnrvtp6vaaamulm.png',
+ manifestID: 'DIYImages'
+ },
+ {
+ label: 'shiba_inu_3jpg.jpeg',
+ url: 'http://localhost:3001/images/5a5783154cfad13070870e13_shiba_inu_3jpg.jpeg',
+ manifestID: 'DIYImages'
+ }
+ ],
+ name: 'Uploaded Images'
+ },
+ '5a25b0703b0eb7478b415bd4': {
+ id: '5a25b0703b0eb7478b415bd4',
+ url: 'https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest',
+ images: [
+ {
+ label: 'Hollar_a_3000_0001',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0001',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0002',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0002',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0003',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0003',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0004',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0004',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0005',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0005',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0006',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0006',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0007',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0007',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0008',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0008',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0009',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0009',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0010',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0010',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0011',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0011',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0012',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0012',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0013',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0013',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0014',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0014',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0015',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0015',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0016',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0016',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0017',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0017',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0018',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0018',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0019',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0019',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0020',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0020',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0021',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0021',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0022',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0022',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0023',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0023',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0024',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0024',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0025',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0025',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0026',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0026',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0027',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0027',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0028',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0028',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0029',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0029',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0030',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0030',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0031',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0031',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0032',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0032',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0033',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0033',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0034',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0034',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0035',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0035',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0036',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0036',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0037',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0037',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0038',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0038',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0039',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0039',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0040',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0040',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0041',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0041',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0042',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0042',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0043',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0043',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0044',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0044',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0045',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0045',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0046',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0046',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0047',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0047',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0048',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0048',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0049',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0049',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0050',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0050',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0051',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0051',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0052',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0052',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0053',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0053',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0054',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0054',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0055',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0055',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0056',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0056',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0057',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0057',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0058',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0058',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0059',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0059',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0060',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0060',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0061',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0061',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0062',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0062',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0063',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0063',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0064',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0064',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0065',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0065',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0066',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0066',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0067',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0067',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0068',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0068',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0069',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0069',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0070',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0070',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0071',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0071',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0072',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0072',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0073',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0073',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0074',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0074',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0075',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0075',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0076',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0076',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0077',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0077',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0078',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0078',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0079',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0079',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0080',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0080',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0081',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0081',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0082',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0082',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0083',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0083',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0084',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0084',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0085',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0085',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0086',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0086',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0087',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0087',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0088',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0088',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0089',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0089',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0090',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0090',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0091',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0091',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0092',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0092',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0093',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0093',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0094',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0094',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0095',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0095',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0096',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0096',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0097',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0097',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0098',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0098',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0099',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0099',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0100',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0100',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0101',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0101',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0102',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0102',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0103',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0103',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0104',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0104',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0105',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0105',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0106',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0106',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0107',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0107',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0108',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0108',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0109',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0109',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0110',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0110',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0111',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0111',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0112',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0112',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0113',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0113',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0114',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0114',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0115',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0115',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0116',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0116',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0117',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0117',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0118',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0118',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0119',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0119',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0120',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0120',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0121',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0121',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0122',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0122',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0123',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0123',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0124',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0124',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0125',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0125',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0126',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0126',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0127',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0127',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0128',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0128',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0129',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0129',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0130',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0130',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0131',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0131',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0132',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0132',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0133',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0133',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0134',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0134',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0135',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0135',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0136',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0136',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0137',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0137',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0138',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0138',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0139',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0139',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0140',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0140',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0141',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0141',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0142',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0142',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0143',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0143',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0144',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0144',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0145',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0145',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0146',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0146',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0147',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0147',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0148',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0148',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0149',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0149',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0150',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0150',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0151',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0151',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0152',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0152',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0153',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0153',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0154',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0154',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0155',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0155',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0156',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0156',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0157',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0157',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0158',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0158',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0159',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0159',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0160',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0160',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0161',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0161',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0162',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0162',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0163',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0163',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0164',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0164',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0165',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0165',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0166',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0166',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0167',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0167',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0168',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0168',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0169',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0169',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0170',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0170',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0171',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0171',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0172',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0172',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0173',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0173',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0174',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0174',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0175',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0175',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0176',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0176',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0177',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0177',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0178',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0178',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0179',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0179',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0180',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0180',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0181',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0181',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0182',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0182',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0183',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0183',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0184',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0184',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0185',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0185',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0186',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0186',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0187',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0187',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0188',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0188',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0189',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0189',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0190',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0190',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0191',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0191',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0192',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0192',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0193',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0193',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0194',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0194',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0195',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0195',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0196',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0196',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0197',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0197',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0198',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0198',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0199',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0199',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0200',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0200',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0201',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0201',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0202',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0202',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0203',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0203',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0204',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0204',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0205',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0205',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0206',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0206',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0207',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0207',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0208',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0208',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0209',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0209',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0210',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0210',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0211',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0211',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0212',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0212',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0213',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0213',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0214',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0214',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0215',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0215',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0216',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0216',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0217',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0217',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0218',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0218',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0219',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0219',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0220',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0220',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0221',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0221',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0222',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0222',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0223',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0223',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0224',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0224',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0225',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0225',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0226',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0226',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0227',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0227',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0228',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0228',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0229',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0229',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0230',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0230',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0231',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0231',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0232',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0232',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0233',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0233',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0234',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0234',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0235',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0235',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0236',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0236',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0237',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0237',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0238',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0238',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0239',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0239',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0240',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0240',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0241',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0241',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0242',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0242',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0243',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0243',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0244',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0244',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0245',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0245',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0246',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0246',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0247',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0247',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0248',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0248',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0249',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0249',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0250',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0250',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0251',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0251',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0252',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0252',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0253',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0253',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0254',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0254',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0255',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0255',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0256',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0256',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0257',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0257',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0258',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0258',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0259',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0259',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0260',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0260',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0261',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0261',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0262',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0262',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0263',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0263',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0264',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0264',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0265',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0265',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0266',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0266',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0267',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0267',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0268',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0268',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0269',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0269',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0270',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0270',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0271',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0271',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0272',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0272',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0273',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0273',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0274',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0274',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0275',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0275',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0276',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0276',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0277',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0277',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0278',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0278',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0279',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0279',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0280',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0280',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0281',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0281',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0282',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0282',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0283',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0283',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0284',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0284',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0285',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0285',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0286',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0286',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0287',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0287',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0288',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0288',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0289',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0289',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0290',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0290',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0291',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0291',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0292',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0292',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0293',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0293',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0294',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0294',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0295',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0295',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0296',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0296',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0297',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0297',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0298',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0298',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0299',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0299',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0300',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0300',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0301',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0301',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0302',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0302',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0303',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0303',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0304',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0304',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0305',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0305',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0306',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0306',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0307',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0307',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0308',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0308',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0309',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0309',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0310',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0310',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0311',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0311',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0312',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0312',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0313',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0313',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0314',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0314',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0315',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0315',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0316',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0316',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0317',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0317',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0318',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0318',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0319',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0319',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0320',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0320',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0321',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0321',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0322',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0322',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0323',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0323',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0324',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0324',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0325',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0325',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0326',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0326',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0327',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0327',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0328',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0328',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0329',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0329',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0330',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0330',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0331',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0331',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0332',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0332',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0333',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0333',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0334',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0334',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0335',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0335',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0336',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0336',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0337',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0337',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0338',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0338',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0339',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0339',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0340',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0340',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0341',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0341',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0342',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0342',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0343',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0343',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0344',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0344',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0345',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0345',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0346',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0346',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0347',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0347',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0348',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0348',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0349',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0349',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0350',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0350',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0351',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0351',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0352',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0352',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0353',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0353',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0354',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0354',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0355',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0355',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0356',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0356',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0357',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0357',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0358',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0358',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0359',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0359',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0360',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0360',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0361',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0361',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0362',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0362',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0363',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0363',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0364',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0364',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0365',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0365',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0366',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0366',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0367',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0367',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0368',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0368',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0369',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0369',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0370',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0370',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0371',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0371',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0372',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0372',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0373',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0373',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0374',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0374',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0375',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0375',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0376',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0376',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0377',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0377',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0378',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0378',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0379',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0379',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0380',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0380',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0381',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0381',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0382',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0382',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0383',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0383',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0384',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0384',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0385',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0385',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0386',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0386',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0387',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0387',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0388',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0388',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0389',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0389',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0390',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0390',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0391',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0391',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0392',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0392',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ }
+ ],
+ name: 'The fables of Aesop / paraphras\'d in verse, and...'
+ },
+ '5a25b0763b0eb7478b415bd5': {
+ id: '5a25b0763b0eb7478b415bd5',
+ url: 'https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3001/manifest',
+ images: [
+ {
+ label: 'Hollar_a_3001_0001',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0001',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0002',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0002',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0003',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0003',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0004',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0004',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0005',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0005',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0006',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0006',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0007',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0007',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0008',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0008',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0009',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0009',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0010',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0010',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0011',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0011',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0012',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0012',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0013',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0013',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0014',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0014',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0015',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0015',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0016',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0016',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0017',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0017',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0018',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0018',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0019',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0019',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0020',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0020',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0021',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0021',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0022',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0022',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0023',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0023',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0024',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0024',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0025',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0025',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0026',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0026',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0027',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0027',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0028',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0028',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0029',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0029',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0030',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0030',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0031',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0031',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0032',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0032',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0033',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0033',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0034',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0034',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0035',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0035',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0036',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0036',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0037',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0037',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0038',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0038',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0039',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0039',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0040',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0040',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0041',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0041',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0042',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0042',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0043',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0043',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0044',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0044',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0045',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0045',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0046',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0046',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0047',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0047',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0048',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0048',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0049',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0049',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0050',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0050',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0051',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0051',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0052',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0052',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0053',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0053',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0054',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0054',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0055',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0055',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0056',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0056',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0057',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0057',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0058',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0058',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0059',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0059',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0060',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0060',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0061',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0061',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0062',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0062',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0063',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0063',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0064',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0064',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0065',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0065',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0066',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0066',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0067',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0067',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0068',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0068',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0069',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0069',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0070',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0070',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0071',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0071',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0072',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0072',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0073',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0073',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0074',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0074',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0075',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0075',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0076',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0076',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0077',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0077',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0078',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0078',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0079',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0079',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0080',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0080',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0081',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0081',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0082',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0082',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0083',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0083',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0084',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0084',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0085',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0085',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0086',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0086',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0087',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0087',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0088',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0088',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0089',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0089',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0090',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0090',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0091',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0091',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0092',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0092',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0093',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0093',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0094',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0094',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0095',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0095',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0096',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0096',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0097',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0097',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0098',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0098',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0099',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0099',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0100',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0100',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0101',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0101',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0102',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0102',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0103',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0103',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0104',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0104',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0105',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0105',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0106',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0106',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0107',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0107',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0108',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0108',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0109',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0109',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0110',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0110',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0111',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0111',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0112',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0112',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0113',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0113',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0114',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0114',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0115',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0115',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0116',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0116',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0117',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0117',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0118',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0118',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0119',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0119',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0120',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0120',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0121',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0121',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0122',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0122',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0123',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0123',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0124',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0124',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0125',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0125',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0126',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0126',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0127',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0127',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0128',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0128',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0129',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0129',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0130',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0130',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0131',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0131',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0132',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0132',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0133',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0133',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0134',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0134',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0135',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0135',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0136',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0136',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0137',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0137',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0138',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0138',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0139',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0139',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0140',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0140',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0141',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0141',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0142',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0142',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0143',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0143',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0144',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0144',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0145',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0145',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0146',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0146',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0147',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0147',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0148',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0148',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0149',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0149',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0150',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0150',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0151',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0151',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0152',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0152',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0153',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0153',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0154',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0154',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0155',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0155',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0156',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0156',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0157',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0157',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0158',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0158',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0159',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0159',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0160',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0160',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0161',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0161',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0162',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0162',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0163',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0163',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0164',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0164',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0165',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0165',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0166',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0166',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0167',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0167',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0168',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0168',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0169',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0169',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0170',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0170',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0171',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0171',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0172',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0172',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0173',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0173',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0174',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0174',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0175',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0175',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0176',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0176',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0177',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0177',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0178',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0178',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0179',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0179',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0180',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0180',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0181',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0181',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0182',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0182',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0183',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0183',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0184',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0184',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0185',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0185',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0186',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0186',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0187',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0187',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0188',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0188',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0189',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0189',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0190',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0190',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0191',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0191',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0192',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0192',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0193',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0193',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0194',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0194',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0195',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0195',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0196',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0196',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0197',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0197',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0198',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0198',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0199',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0199',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0200',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0200',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0201',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0201',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0202',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0202',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0203',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0203',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0204',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0204',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0205',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0205',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0206',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0206',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0207',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0207',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0208',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0208',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0209',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0209',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0210',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0210',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0211',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0211',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0212',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0212',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0213',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0213',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0214',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0214',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0215',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0215',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0216',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0216',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0217',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0217',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0218',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0218',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0219',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0219',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0220',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0220',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0221',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0221',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0222',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0222',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0223',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0223',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0224',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0224',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0225',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0225',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0226',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0226',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0227',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0227',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0228',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0228',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0229',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0229',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0230',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0230',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0231',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0231',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0232',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0232',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0233',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0233',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0234',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0234',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0235',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0235',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0236',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0236',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0237',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0237',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0238',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0238',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0239',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0239',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0240',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0240',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0241',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0241',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0242',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0242',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0243',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0243',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0244',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0244',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0245',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0245',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0246',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0246',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0247',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0247',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0248',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0248',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0249',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0249',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0250',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0250',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0251',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0251',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0252',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0252',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0253',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0253',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0254',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0254',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0255',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0255',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0256',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0256',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0257',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0257',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0258',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0258',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0259',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0259',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0260',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0260',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0261',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0261',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0262',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0262',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0263',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0263',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0264',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0264',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0265',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0265',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0266',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0266',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0267',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0267',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0268',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0268',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0269',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0269',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0270',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0270',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0271',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0271',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0272',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0272',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0273',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0273',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0274',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0274',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0275',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0275',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0276',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0276',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0277',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0277',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0278',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0278',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0279',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0279',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0280',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0280',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0281',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0281',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0282',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0282',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0283',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0283',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0284',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0284',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0285',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0285',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0286',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0286',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0287',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0287',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0288',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0288',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0289',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0289',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0290',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0290',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0291',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0291',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0292',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0292',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0293',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0293',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0294',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0294',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0295',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0295',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0296',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0296',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0297',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0297',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0298',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0298',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0299',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0299',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0300',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0300',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0301',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0301',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0302',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0302',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0303',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0303',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0304',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0304',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0305',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0305',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0306',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0306',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0307',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0307',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0308',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0308',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0309',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0309',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0310',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0310',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0311',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0311',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0312',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0312',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0313',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0313',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0314',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0314',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0315',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0315',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0316',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0316',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0317',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0317',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0318',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0318',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0319',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0319',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0320',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0320',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0321',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0321',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0322',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0322',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0323',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0323',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0324',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0324',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0325',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0325',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0326',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0326',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0327',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0327',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0328',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0328',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ }
+ ],
+ name: 'The history of St. Paul\'s Cathedral in London :...'
+ }
+ },
+ groupIDs: [
+ 'Group_5a57825a4cfad13070870df4',
+ 'Group_5a57825a4cfad13070870df5',
+ 'Group_5a57825a4cfad13070870df6',
+ 'Group_5a57825a4cfad13070870df7'
+ ],
+ leafIDs: [
+ 'Leaf_5a57825a4cfad13070870dc4',
+ 'Leaf_5a57825a4cfad13070870dc7',
+ 'Leaf_5a57825a4cfad13070870dca',
+ 'Leaf_5a57825a4cfad13070870dcd',
+ 'Leaf_5a57825a4cfad13070870dd0',
+ 'Leaf_5a57825a4cfad13070870dd3',
+ 'Leaf_5a57825a4cfad13070870dd6',
+ 'Leaf_5a57825a4cfad13070870dd9',
+ 'Leaf_5a57825a4cfad13070870ddc',
+ 'Leaf_5a57825a4cfad13070870ddf',
+ 'Leaf_5a57825a4cfad13070870de2',
+ 'Leaf_5a57825a4cfad13070870de5',
+ 'Leaf_5a57825a4cfad13070870de8',
+ 'Leaf_5a57825a4cfad13070870deb',
+ 'Leaf_5a57825a4cfad13070870dee',
+ 'Leaf_5a57825a4cfad13070870df1'
+ ],
+ rectoIDs: [
+ 'Recto_5a57825a4cfad13070870dc5',
+ 'Recto_5a57825a4cfad13070870dc8',
+ 'Recto_5a57825a4cfad13070870dcb',
+ 'Recto_5a57825a4cfad13070870dce',
+ 'Recto_5a57825a4cfad13070870dd1',
+ 'Recto_5a57825a4cfad13070870dd4',
+ 'Recto_5a57825a4cfad13070870dd7',
+ 'Recto_5a57825a4cfad13070870dda',
+ 'Recto_5a57825a4cfad13070870ddd',
+ 'Recto_5a57825a4cfad13070870de0',
+ 'Recto_5a57825a4cfad13070870de3',
+ 'Recto_5a57825a4cfad13070870de6',
+ 'Recto_5a57825a4cfad13070870de9',
+ 'Recto_5a57825a4cfad13070870dec',
+ 'Recto_5a57825a4cfad13070870def',
+ 'Recto_5a57825a4cfad13070870df2'
+ ],
+ versoIDs: [
+ 'Verso_5a57825a4cfad13070870dc6',
+ 'Verso_5a57825a4cfad13070870dc9',
+ 'Verso_5a57825a4cfad13070870dcc',
+ 'Verso_5a57825a4cfad13070870dcf',
+ 'Verso_5a57825a4cfad13070870dd2',
+ 'Verso_5a57825a4cfad13070870dd5',
+ 'Verso_5a57825a4cfad13070870dd8',
+ 'Verso_5a57825a4cfad13070870ddb',
+ 'Verso_5a57825a4cfad13070870dde',
+ 'Verso_5a57825a4cfad13070870de1',
+ 'Verso_5a57825a4cfad13070870de4',
+ 'Verso_5a57825a4cfad13070870de7',
+ 'Verso_5a57825a4cfad13070870dea',
+ 'Verso_5a57825a4cfad13070870ded',
+ 'Verso_5a57825a4cfad13070870df0',
+ 'Verso_5a57825a4cfad13070870df3'
+ ],
+ Groups: {
+ Group_5a57825a4cfad13070870df4: {
+ id: 'Group_5a57825a4cfad13070870df4',
+ type: 'Quire',
+ title: 'First Quire',
+ tacketed: [],
+ sewing: [
+ 'Leaf_5a57825a4cfad13070870dc7',
+ 'Leaf_5a57825a4cfad13070870dcd'
+ ],
+ nestLevel: 1,
+ parentID: null,
+ notes: [
+ '5a57825a4cfad13070870df8'
+ ],
+ memberIDs: [
+ 'Leaf_5a57825a4cfad13070870dc4',
+ 'Leaf_5a57825a4cfad13070870dc7',
+ 'Leaf_5a57825a4cfad13070870dca',
+ 'Leaf_5a57825a4cfad13070870dcd',
+ 'Leaf_5a57825a4cfad13070870dd0',
+ 'Leaf_5a57825a4cfad13070870dd3'
+ ],
+ memberType: 'Group'
+ },
+ Group_5a57825a4cfad13070870df5: {
+ id: 'Group_5a57825a4cfad13070870df5',
+ type: 'Quire',
+ title: '2nd Quire',
+ tacketed: [
+ 'Leaf_5a57825a4cfad13070870de5',
+ 'Leaf_5a57825a4cfad13070870dee'
+ ],
+ sewing: [],
+ nestLevel: 1,
+ parentID: null,
+ notes: [
+ '5a57825a4cfad13070870df8'
+ ],
+ memberIDs: [
+ 'Group_5a57825a4cfad13070870df6',
+ 'Leaf_5a57825a4cfad13070870de2',
+ 'Leaf_5a57825a4cfad13070870de5',
+ 'Leaf_5a57825a4cfad13070870de8',
+ 'Leaf_5a57825a4cfad13070870deb',
+ 'Leaf_5a57825a4cfad13070870dee',
+ 'Leaf_5a57825a4cfad13070870df1'
+ ],
+ memberType: 'Group'
+ },
+ Group_5a57825a4cfad13070870df6: {
+ id: 'Group_5a57825a4cfad13070870df6',
+ type: 'Quire',
+ title: '1st Sub Quire of 2',
+ tacketed: [],
+ sewing: [],
+ nestLevel: 2,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ notes: [],
+ memberIDs: [
+ 'Group_5a57825a4cfad13070870df7',
+ 'Leaf_5a57825a4cfad13070870ddc',
+ 'Leaf_5a57825a4cfad13070870ddf'
+ ],
+ memberType: 'Group'
+ },
+ Group_5a57825a4cfad13070870df7: {
+ id: 'Group_5a57825a4cfad13070870df7',
+ type: 'Quire',
+ title: '1st Sub Quire of Sub Quire 2.1',
+ tacketed: [],
+ sewing: [],
+ nestLevel: 3,
+ parentID: 'Group_5a57825a4cfad13070870df6',
+ notes: [],
+ memberIDs: [
+ 'Leaf_5a57825a4cfad13070870dd6',
+ 'Leaf_5a57825a4cfad13070870dd9'
+ ],
+ memberType: 'Group'
+ }
+ },
+ Leafs: {
+ Leaf_5a57825a4cfad13070870dc4: {
+ id: 'Leaf_5a57825a4cfad13070870dc4',
+ material: 'None',
+ type: 'Endleaf',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dd3',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dc5',
+ versoID: 'Verso_5a57825a4cfad13070870dc6',
+ notes: [
+ '5a57825a4cfad13070870df9'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dc7: {
+ id: 'Leaf_5a57825a4cfad13070870dc7',
+ material: 'None',
+ type: 'Missing',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dd0',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dc8',
+ versoID: 'Verso_5a57825a4cfad13070870dc9',
+ notes: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dca: {
+ id: 'Leaf_5a57825a4cfad13070870dca',
+ material: 'Parchment',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dcd',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dcb',
+ versoID: 'Verso_5a57825a4cfad13070870dcc',
+ notes: [
+ '5a57825a4cfad13070870dfa'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dcd: {
+ id: 'Leaf_5a57825a4cfad13070870dcd',
+ material: 'Parchment',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dca',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dce',
+ versoID: 'Verso_5a57825a4cfad13070870dcf',
+ notes: [
+ '5a57825a4cfad13070870dfa'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dd0: {
+ id: 'Leaf_5a57825a4cfad13070870dd0',
+ material: 'Parchment',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dc7',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dd1',
+ versoID: 'Verso_5a57825a4cfad13070870dd2',
+ notes: [
+ '5a57825a4cfad13070870dfa'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dd3: {
+ id: 'Leaf_5a57825a4cfad13070870dd3',
+ material: 'None',
+ type: 'Endleaf',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dc4',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dd4',
+ versoID: 'Verso_5a57825a4cfad13070870dd5',
+ notes: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dd6: {
+ id: 'Leaf_5a57825a4cfad13070870dd6',
+ material: 'None',
+ type: 'None',
+ conjoined_to: null,
+ attached_above: 'None',
+ attached_below: 'Glued (Partial)',
+ stub: 'None',
+ nestLevel: 3,
+ parentID: 'Group_5a57825a4cfad13070870df7',
+ rectoID: 'Recto_5a57825a4cfad13070870dd7',
+ versoID: 'Verso_5a57825a4cfad13070870dd8',
+ notes: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dd9: {
+ id: 'Leaf_5a57825a4cfad13070870dd9',
+ material: 'None',
+ type: 'None',
+ conjoined_to: null,
+ attached_above: 'Glued (Partial)',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 3,
+ parentID: 'Group_5a57825a4cfad13070870df7',
+ rectoID: 'Recto_5a57825a4cfad13070870dda',
+ versoID: 'Verso_5a57825a4cfad13070870ddb',
+ notes: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870ddc: {
+ id: 'Leaf_5a57825a4cfad13070870ddc',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870ddf',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 2,
+ parentID: 'Group_5a57825a4cfad13070870df6',
+ rectoID: 'Recto_5a57825a4cfad13070870ddd',
+ versoID: 'Verso_5a57825a4cfad13070870dde',
+ notes: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870ddf: {
+ id: 'Leaf_5a57825a4cfad13070870ddf',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870ddc',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 2,
+ parentID: 'Group_5a57825a4cfad13070870df6',
+ rectoID: 'Recto_5a57825a4cfad13070870de0',
+ versoID: 'Verso_5a57825a4cfad13070870de1',
+ notes: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870de2: {
+ id: 'Leaf_5a57825a4cfad13070870de2',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870df1',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870de3',
+ versoID: 'Verso_5a57825a4cfad13070870de4',
+ notes: [
+ '5a57825a4cfad13070870df9'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870de5: {
+ id: 'Leaf_5a57825a4cfad13070870de5',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dee',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870de6',
+ versoID: 'Verso_5a57825a4cfad13070870de7',
+ notes: [
+ '5a57825a4cfad13070870df8'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870de8: {
+ id: 'Leaf_5a57825a4cfad13070870de8',
+ material: 'None',
+ type: 'None',
+ conjoined_to: null,
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'Original',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870de9',
+ versoID: 'Verso_5a57825a4cfad13070870dea',
+ notes: [
+ '5a57825a4cfad13070870df8'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870deb: {
+ id: 'Leaf_5a57825a4cfad13070870deb',
+ material: 'None',
+ type: 'None',
+ conjoined_to: null,
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870dec',
+ versoID: 'Verso_5a57825a4cfad13070870ded',
+ notes: [
+ '5a57825a4cfad13070870df8'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dee: {
+ id: 'Leaf_5a57825a4cfad13070870dee',
+ material: 'None',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870de5',
+ attached_above: 'None',
+ attached_below: 'Glued (Complete)',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870def',
+ versoID: 'Verso_5a57825a4cfad13070870df0',
+ notes: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870df1: {
+ id: 'Leaf_5a57825a4cfad13070870df1',
+ material: 'None',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870de2',
+ attached_above: 'Glued (Complete)',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870df2',
+ versoID: 'Verso_5a57825a4cfad13070870df3',
+ notes: [],
+ memberType: 'Leaf'
+ }
+ },
+ Rectos: {
+ Recto_5a57825a4cfad13070870dc5: {
+ id: 'Recto_5a57825a4cfad13070870dc5',
+ parentID: 'Leaf_5a57825a4cfad13070870dc4',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {
+ manifestID: '5a25b0703b0eb7478b415bd4',
+ label: 'Hollar_a_3000_0003',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0003'
+ },
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dc8: {
+ id: 'Recto_5a57825a4cfad13070870dc8',
+ parentID: 'Leaf_5a57825a4cfad13070870dc7',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {
+ manifestID: '5a25b0703b0eb7478b415bd4',
+ label: 'Hollar_a_3000_0002',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0002'
+ },
+ script_direction: 'None',
+ notes: [
+ '5a57825a4cfad13070870dfa'
+ ],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dcb: {
+ id: 'Recto_5a57825a4cfad13070870dcb',
+ parentID: 'Leaf_5a57825a4cfad13070870dca',
+ folio_number: "custom XR",
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {
+ manifestID: '5a25b0703b0eb7478b415bd4',
+ label: 'Hollar_a_3000_0001',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0001'
+ },
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dce: {
+ id: 'Recto_5a57825a4cfad13070870dce',
+ parentID: 'Leaf_5a57825a4cfad13070870dcd',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {
+ manifestID: '5a25b0703b0eb7478b415bd4',
+ label: 'Hollar_a_3000_0007',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0007'
+ },
+ script_direction: 'Left-to-Right',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dd1: {
+ id: 'Recto_5a57825a4cfad13070870dd1',
+ parentID: 'Leaf_5a57825a4cfad13070870dd0',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {
+ manifestID: '5a25b0703b0eb7478b415bd4',
+ label: 'Hollar_a_3000_0009',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0009'
+ },
+ script_direction: 'Left-to-Right',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dd4: {
+ id: 'Recto_5a57825a4cfad13070870dd4',
+ parentID: 'Leaf_5a57825a4cfad13070870dd3',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {
+ manifestID: '5a25b0703b0eb7478b415bd4',
+ label: 'Hollar_a_3000_0011',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0011'
+ },
+ script_direction: 'Left-to-Right',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dd7: {
+ id: 'Recto_5a57825a4cfad13070870dd7',
+ parentID: 'Leaf_5a57825a4cfad13070870dd6',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'Left-to-Right',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dda: {
+ id: 'Recto_5a57825a4cfad13070870dda',
+ parentID: 'Leaf_5a57825a4cfad13070870dd9',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'Left-to-Right',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870ddd: {
+ id: 'Recto_5a57825a4cfad13070870ddd',
+ parentID: 'Leaf_5a57825a4cfad13070870ddc',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'Left-to-Right',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870de0: {
+ id: 'Recto_5a57825a4cfad13070870de0',
+ parentID: 'Leaf_5a57825a4cfad13070870ddf',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870de3: {
+ id: 'Recto_5a57825a4cfad13070870de3',
+ parentID: 'Leaf_5a57825a4cfad13070870de2',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870de6: {
+ id: 'Recto_5a57825a4cfad13070870de6',
+ parentID: 'Leaf_5a57825a4cfad13070870de5',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870de9: {
+ id: 'Recto_5a57825a4cfad13070870de9',
+ parentID: 'Leaf_5a57825a4cfad13070870de8',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dec: {
+ id: 'Recto_5a57825a4cfad13070870dec',
+ parentID: 'Leaf_5a57825a4cfad13070870deb',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870def: {
+ id: 'Recto_5a57825a4cfad13070870def',
+ parentID: 'Leaf_5a57825a4cfad13070870dee',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870df2: {
+ id: 'Recto_5a57825a4cfad13070870df2',
+ parentID: 'Leaf_5a57825a4cfad13070870df1',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ }
+ },
+ Versos: {
+ Verso_5a57825a4cfad13070870dc6: {
+ id: 'Verso_5a57825a4cfad13070870dc6',
+ parentID: 'Leaf_5a57825a4cfad13070870dc4',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {
+ manifestID: 'DIYImages',
+ label: 'cguk1l0u4aeewdf.jpeg',
+ url: 'http://localhost:3001/images/5a5cc9594cfad17bed092f4a_cguk1l0u4aeewdf.jpeg'
+ },
+ script_direction: 'None',
+ notes: [
+ '5a57825a4cfad13070870df9',
+ '5a57825a4cfad13070870dfa'
+ ],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dc9: {
+ id: 'Verso_5a57825a4cfad13070870dc9',
+ parentID: 'Leaf_5a57825a4cfad13070870dc7',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {
+ manifestID: 'DIYImages',
+ label: '5a5cc9594cfad17bed092f4b_chiba_inu_dogs_shiba_inu_funny.jpeg',
+ url: 'http://localhost:3001/images/5a5cc9594cfad17bed092f4b_3c17f2b4127b1a5a8bcfc76ba9de9c9f_chiba_inu_dogs_shiba_inu_funny.jpeg'
+ },
+ script_direction: 'None',
+ notes: [
+ '5a57825a4cfad13070870dfa'
+ ],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dcc: {
+ id: 'Verso_5a57825a4cfad13070870dcc',
+ parentID: 'Leaf_5a57825a4cfad13070870dca',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {
+ manifestID: 'DIYImages',
+ label: '1_105.jpeg',
+ url: 'http://localhost:3001/images/5a5cc9594cfad17bed092f4c_1_105.jpeg'
+ },
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dcf: {
+ id: 'Verso_5a57825a4cfad13070870dcf',
+ parentID: 'Leaf_5a57825a4cfad13070870dcd',
+ folio_number: "custom XV",
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dd2: {
+ id: 'Verso_5a57825a4cfad13070870dd2',
+ parentID: 'Leaf_5a57825a4cfad13070870dd0',
+ folio_number: "custom YV",
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dd5: {
+ id: 'Verso_5a57825a4cfad13070870dd5',
+ parentID: 'Leaf_5a57825a4cfad13070870dd3',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dd8: {
+ id: 'Verso_5a57825a4cfad13070870dd8',
+ parentID: 'Leaf_5a57825a4cfad13070870dd6',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870ddb: {
+ id: 'Verso_5a57825a4cfad13070870ddb',
+ parentID: 'Leaf_5a57825a4cfad13070870dd9',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dde: {
+ id: 'Verso_5a57825a4cfad13070870dde',
+ parentID: 'Leaf_5a57825a4cfad13070870ddc',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870de1: {
+ id: 'Verso_5a57825a4cfad13070870de1',
+ parentID: 'Leaf_5a57825a4cfad13070870ddf',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870de4: {
+ id: 'Verso_5a57825a4cfad13070870de4',
+ parentID: 'Leaf_5a57825a4cfad13070870de2',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870de7: {
+ id: 'Verso_5a57825a4cfad13070870de7',
+ parentID: 'Leaf_5a57825a4cfad13070870de5',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dea: {
+ id: 'Verso_5a57825a4cfad13070870dea',
+ parentID: 'Leaf_5a57825a4cfad13070870de8',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870ded: {
+ id: 'Verso_5a57825a4cfad13070870ded',
+ parentID: 'Leaf_5a57825a4cfad13070870deb',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'Right-To-Left',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870df0: {
+ id: 'Verso_5a57825a4cfad13070870df0',
+ parentID: 'Leaf_5a57825a4cfad13070870dee',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'Right-To-Left',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870df3: {
+ id: 'Verso_5a57825a4cfad13070870df3',
+ parentID: 'Leaf_5a57825a4cfad13070870df1',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'Right-To-Left',
+ notes: [],
+ memberType: 'Verso'
+ }
+ },
+ Notes: {
+ '5a57825a4cfad13070870df8': {
+ id: '5a57825a4cfad13070870df8',
+ title: 'Black ink',
+ type: 'Ink',
+ description: 'Some black ink over here\n',
+ show: true,
+ objects: {
+ Group: [
+ 'Group_5a57825a4cfad13070870df4',
+ 'Group_5a57825a4cfad13070870df5'
+ ],
+ Leaf: [
+ 'Leaf_5a57825a4cfad13070870de5',
+ 'Leaf_5a57825a4cfad13070870de8',
+ 'Leaf_5a57825a4cfad13070870deb'
+ ],
+ Recto: [],
+ Verso: []
+ }
+ },
+ '5a57825a4cfad13070870df9': {
+ id: '5a57825a4cfad13070870df9',
+ title: 'John\'s hand',
+ type: 'Hand',
+ description: 'Look ! ',
+ show: false,
+ objects: {
+ Group: [],
+ Leaf: [
+ 'Leaf_5a57825a4cfad13070870dc4',
+ 'Leaf_5a57825a4cfad13070870de2'
+ ],
+ Recto: [],
+ Verso: [
+ 'Verso_5a57825a4cfad13070870dc6'
+ ]
+ }
+ },
+ '5a57825a4cfad13070870dfa': {
+ id: '5a57825a4cfad13070870dfa',
+ title: 'Fire',
+ type: 'Damage',
+ description: 'Some burnt marks',
+ show: true,
+ objects: {
+ Group: [],
+ Leaf: [
+ 'Leaf_5a57825a4cfad13070870dca',
+ 'Leaf_5a57825a4cfad13070870dcd',
+ 'Leaf_5a57825a4cfad13070870dd0'
+ ],
+ Recto: [
+ 'Recto_5a57825a4cfad13070870dc8'
+ ],
+ Verso: [
+ 'Verso_5a57825a4cfad13070870dc6',
+ 'Verso_5a57825a4cfad13070870dc9'
+ ]
+ }
+ }
+ }
+ },
+ managerMode: 'collationManager',
+ collationManager: {
+ selectedObjects: {
+ type: 'Leaf',
+ members: [
+ 'Leaf_5a57825a4cfad13070870dc4'
+ ],
+ lastSelected: 'Leaf_5a57825a4cfad13070870dc4'
+ },
+ viewMode: 'VISUAL',
+ visibleAttributes: {
+ group: {
+ type: false,
+ title: false
+ },
+ leaf: {
+ type: false,
+ material: false,
+ conjoined_leaf_order: false,
+ attached_below: false,
+ attached_above: false,
+ stub: false
+ },
+ side: {
+ folio_number: false,
+ texture: false,
+ script_direction: false,
+ uri: false
+ }
+ },
+ defaultAttributes: {
+ leaf: [
+ {
+ name: 'type',
+ displayName: 'Type',
+ options: [
+ 'None',
+ 'Original',
+ 'Added',
+ 'Missing',
+ 'Hook',
+ 'Endleaf',
+ 'Replaced'
+ ],
+ isDropdown: true
+ },
+ {
+ name: 'material',
+ displayName: 'Material',
+ options: [
+ 'None',
+ 'Parchment',
+ 'Paper',
+ 'Other'
+ ],
+ isDropdown: true
+ },
+ {
+ name: 'conjoined_to',
+ displayName: 'Conjoined To',
+ isDropdown: true
+ },
+ {
+ name: 'attached_above',
+ displayName: 'Attached Above',
+ options: [
+ 'None',
+ 'Glued (Partial)',
+ 'Glued (Complete)',
+ 'Glued (Drumming)',
+ 'Other'
+ ],
+ isDropdown: true
+ },
+ {
+ name: 'attached_below',
+ displayName: 'Attached Below',
+ options: [
+ 'None',
+ 'Glued (Partial)',
+ 'Glued (Complete)',
+ 'Glued (Drumming)',
+ 'Other'
+ ],
+ isDropdown: true
+ },
+ {
+ name: 'stub',
+ displayName: 'Stub',
+ options: [
+ 'None',
+ 'Original',
+ 'Added'
+ ],
+ isDropdown: true
+ }
+ ],
+ group: [
+ {
+ name: 'type',
+ displayName: 'Type',
+ options: [
+ 'Quire',
+ 'Booklet'
+ ],
+ isDropdown: true
+ },
+ {
+ name: 'title',
+ displayName: 'Title'
+ }
+ ],
+ side: [
+ {
+ name: 'texture',
+ displayName: 'Texture',
+ options: [
+ 'None',
+ 'Hair',
+ 'Flesh',
+ 'Felt',
+ 'Wire'
+ ],
+ isDropdown: true
+ },
+ {
+ name: 'folio_number',
+ displayName: 'Folio Number'
+ },
+ {
+ name: 'script_direction',
+ displayName: 'Script Direction',
+ options: [
+ 'None',
+ 'Left-to-Right',
+ 'Right-To-Left',
+ 'Top-To-Bottom'
+ ],
+ isDropdown: true
+ },
+ {
+ name: 'uri',
+ displayName: 'URI'
+ }
+ ],
+ note: [
+ {
+ name: 'title',
+ displayName: 'Title'
+ },
+ {
+ name: 'type',
+ displayName: 'Type',
+ isDropdown: true
+ },
+ {
+ name: 'description',
+ displayName: 'Description'
+ }
+ ]
+ },
+ filters: {
+ filterPanelOpen: false,
+ Groups: [],
+ Leafs: [],
+ Sides: [],
+ Notes: [],
+ GroupsOfMatchingLeafs: [],
+ LeafsOfMatchingSides: [],
+ GroupsOfMatchingSides: [],
+ GroupsOfMatchingNotes: [],
+ LeafsOfMatchingNotes: [],
+ SidesOfMatchingNotes: [],
+ active: false,
+ hideOthers: false,
+ queries: [
+ {
+ type: null,
+ attribute: '',
+ attributeIndex: '',
+ values: [],
+ condition: '',
+ conjunction: ''
+ }
+ ],
+ selection: ''
+ },
+ flashItems: {
+ leaves: [],
+ groups: []
+ },
+ visualizations: {
+ tacketed: '',
+ sewing: ''
+ }
+ },
+ notesManager: {
+ activeTab: 'MANAGE'
+ },
+ imageManager: {
+ activeTab: 'MANAGE',
+ manageSources: {
+ error: ''
+ }
+ },
+}
\ No newline at end of file
diff --git a/viscoll-app/__test__/testData/state001.js b/viscoll-app/__test__/testData/state001.js
new file mode 100644
index 00000000..890a1eb0
--- /dev/null
+++ b/viscoll-app/__test__/testData/state001.js
@@ -0,0 +1,4737 @@
+export const state001 = {
+ project: {
+ id: '5a57825a4cfad13070870dc3',
+ title: 'my prject',
+ shelfmark: 'mss 568',
+ metadata: {
+ date: '19th century'
+ },
+ preferences: {
+ showTips: true
+ },
+ noteTypes: [
+ 'Unknown',
+ 'Ink',
+ 'Hand',
+ 'Damage'
+ ],
+ manifests: {
+ DIYImages: {
+ id: 'DIYImages',
+ images: [
+ {
+ label: 'cguk1l0u4aeewdf.jpeg',
+ url: 'http://localhost:3001/images/5a5783154cfad13070870e08_cguk1l0u4aeewdf.jpeg',
+ manifestID: 'DIYImages'
+ },
+ {
+ label: '3c17f2b4127b1a5a8bcfc76ba9de9c9f_chiba_inu_dogs_shiba_inu_funny_copy(1).jpeg',
+ url: 'http://localhost:3001/images/5a5783154cfad13070870e0a_3c17f2b4127b1a5a8bcfc76ba9de9c9f_chiba_inu_dogs_shiba_inu_funny_copy(1).jpeg',
+ manifestID: 'DIYImages'
+ },
+ {
+ label: '1_105_copy(1).jpeg',
+ url: 'http://localhost:3001/images/5a5783154cfad13070870e0c_1_105_copy(1).jpeg',
+ manifestID: 'DIYImages'
+ },
+ {
+ label: 'shiba_inu_taiki_copy(1).jpeg',
+ url: 'http://localhost:3001/images/5a5783154cfad13070870e0e_shiba_inu_taiki_copy(1).jpeg',
+ manifestID: 'DIYImages'
+ },
+ {
+ label: 'cnrvtp6vaaamulm_copy(2).png',
+ url: 'http://localhost:3001/images/5a5783154cfad13070870e11_cnrvtp6vaaamulm_copy(2).png',
+ manifestID: 'DIYImages'
+ },
+ {
+ label: 'shiba_inu_3jpg_copy(1).jpeg',
+ url: 'http://localhost:3001/images/5a5783154cfad13070870e13_shiba_inu_3jpg_copy(1).jpeg',
+ manifestID: 'DIYImages'
+ }
+ ],
+ name: 'Uploaded Images'
+ },
+ '5a25b0703b0eb7478b415bd4': {
+ id: '5a25b0703b0eb7478b415bd4',
+ url: 'https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3000/manifest',
+ images: [
+ {
+ label: 'Hollar_a_3000_0001',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0001',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0002',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0002',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0003',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0003',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0004',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0004',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0005',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0005',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0006',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0006',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0007',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0007',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0008',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0008',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0009',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0009',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0010',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0010',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0011',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0011',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0012',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0012',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0013',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0013',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0014',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0014',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0015',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0015',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0016',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0016',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0017',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0017',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0018',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0018',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0019',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0019',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0020',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0020',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0021',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0021',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0022',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0022',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0023',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0023',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0024',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0024',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0025',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0025',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0026',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0026',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0027',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0027',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0028',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0028',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0029',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0029',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0030',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0030',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0031',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0031',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0032',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0032',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0033',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0033',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0034',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0034',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0035',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0035',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0036',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0036',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0037',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0037',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0038',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0038',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0039',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0039',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0040',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0040',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0041',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0041',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0042',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0042',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0043',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0043',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0044',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0044',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0045',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0045',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0046',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0046',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0047',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0047',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0048',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0048',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0049',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0049',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0050',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0050',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0051',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0051',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0052',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0052',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0053',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0053',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0054',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0054',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0055',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0055',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0056',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0056',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0057',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0057',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0058',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0058',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0059',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0059',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0060',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0060',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0061',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0061',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0062',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0062',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0063',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0063',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0064',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0064',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0065',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0065',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0066',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0066',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0067',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0067',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0068',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0068',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0069',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0069',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0070',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0070',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0071',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0071',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0072',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0072',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0073',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0073',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0074',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0074',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0075',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0075',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0076',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0076',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0077',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0077',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0078',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0078',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0079',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0079',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0080',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0080',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0081',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0081',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0082',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0082',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0083',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0083',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0084',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0084',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0085',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0085',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0086',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0086',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0087',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0087',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0088',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0088',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0089',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0089',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0090',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0090',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0091',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0091',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0092',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0092',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0093',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0093',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0094',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0094',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0095',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0095',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0096',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0096',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0097',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0097',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0098',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0098',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0099',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0099',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0100',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0100',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0101',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0101',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0102',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0102',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0103',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0103',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0104',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0104',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0105',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0105',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0106',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0106',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0107',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0107',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0108',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0108',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0109',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0109',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0110',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0110',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0111',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0111',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0112',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0112',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0113',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0113',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0114',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0114',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0115',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0115',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0116',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0116',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0117',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0117',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0118',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0118',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0119',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0119',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0120',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0120',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0121',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0121',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0122',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0122',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0123',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0123',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0124',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0124',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0125',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0125',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0126',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0126',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0127',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0127',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0128',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0128',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0129',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0129',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0130',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0130',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0131',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0131',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0132',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0132',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0133',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0133',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0134',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0134',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0135',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0135',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0136',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0136',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0137',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0137',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0138',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0138',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0139',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0139',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0140',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0140',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0141',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0141',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0142',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0142',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0143',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0143',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0144',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0144',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0145',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0145',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0146',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0146',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0147',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0147',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0148',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0148',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0149',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0149',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0150',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0150',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0151',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0151',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0152',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0152',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0153',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0153',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0154',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0154',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0155',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0155',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0156',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0156',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0157',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0157',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0158',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0158',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0159',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0159',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0160',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0160',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0161',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0161',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0162',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0162',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0163',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0163',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0164',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0164',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0165',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0165',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0166',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0166',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0167',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0167',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0168',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0168',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0169',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0169',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0170',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0170',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0171',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0171',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0172',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0172',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0173',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0173',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0174',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0174',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0175',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0175',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0176',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0176',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0177',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0177',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0178',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0178',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0179',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0179',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0180',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0180',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0181',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0181',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0182',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0182',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0183',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0183',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0184',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0184',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0185',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0185',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0186',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0186',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0187',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0187',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0188',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0188',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0189',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0189',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0190',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0190',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0191',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0191',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0192',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0192',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0193',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0193',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0194',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0194',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0195',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0195',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0196',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0196',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0197',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0197',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0198',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0198',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0199',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0199',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0200',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0200',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0201',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0201',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0202',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0202',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0203',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0203',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0204',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0204',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0205',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0205',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0206',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0206',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0207',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0207',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0208',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0208',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0209',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0209',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0210',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0210',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0211',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0211',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0212',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0212',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0213',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0213',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0214',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0214',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0215',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0215',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0216',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0216',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0217',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0217',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0218',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0218',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0219',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0219',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0220',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0220',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0221',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0221',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0222',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0222',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0223',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0223',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0224',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0224',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0225',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0225',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0226',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0226',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0227',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0227',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0228',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0228',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0229',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0229',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0230',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0230',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0231',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0231',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0232',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0232',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0233',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0233',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0234',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0234',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0235',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0235',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0236',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0236',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0237',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0237',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0238',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0238',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0239',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0239',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0240',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0240',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0241',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0241',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0242',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0242',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0243',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0243',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0244',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0244',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0245',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0245',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0246',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0246',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0247',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0247',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0248',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0248',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0249',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0249',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0250',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0250',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0251',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0251',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0252',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0252',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0253',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0253',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0254',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0254',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0255',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0255',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0256',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0256',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0257',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0257',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0258',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0258',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0259',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0259',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0260',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0260',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0261',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0261',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0262',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0262',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0263',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0263',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0264',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0264',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0265',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0265',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0266',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0266',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0267',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0267',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0268',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0268',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0269',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0269',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0270',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0270',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0271',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0271',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0272',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0272',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0273',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0273',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0274',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0274',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0275',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0275',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0276',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0276',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0277',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0277',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0278',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0278',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0279',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0279',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0280',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0280',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0281',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0281',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0282',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0282',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0283',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0283',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0284',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0284',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0285',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0285',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0286',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0286',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0287',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0287',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0288',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0288',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0289',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0289',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0290',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0290',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0291',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0291',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0292',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0292',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0293',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0293',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0294',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0294',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0295',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0295',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0296',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0296',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0297',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0297',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0298',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0298',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0299',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0299',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0300',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0300',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0301',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0301',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0302',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0302',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0303',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0303',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0304',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0304',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0305',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0305',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0306',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0306',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0307',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0307',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0308',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0308',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0309',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0309',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0310',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0310',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0311',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0311',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0312',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0312',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0313',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0313',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0314',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0314',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0315',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0315',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0316',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0316',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0317',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0317',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0318',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0318',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0319',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0319',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0320',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0320',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0321',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0321',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0322',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0322',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0323',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0323',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0324',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0324',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0325',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0325',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0326',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0326',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0327',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0327',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0328',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0328',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0329',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0329',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0330',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0330',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0331',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0331',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0332',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0332',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0333',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0333',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0334',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0334',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0335',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0335',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0336',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0336',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0337',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0337',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0338',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0338',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0339',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0339',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0340',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0340',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0341',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0341',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0342',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0342',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0343',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0343',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0344',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0344',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0345',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0345',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0346',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0346',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0347',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0347',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0348',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0348',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0349',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0349',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0350',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0350',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0351',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0351',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0352',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0352',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0353',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0353',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0354',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0354',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0355',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0355',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0356',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0356',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0357',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0357',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0358',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0358',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0359',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0359',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0360',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0360',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0361',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0361',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0362',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0362',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0363',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0363',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0364',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0364',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0365',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0365',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0366',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0366',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0367',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0367',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0368',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0368',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0369',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0369',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0370',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0370',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0371',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0371',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0372',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0372',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0373',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0373',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0374',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0374',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0375',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0375',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0376',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0376',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0377',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0377',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0378',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0378',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0379',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0379',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0380',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0380',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0381',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0381',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0382',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0382',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0383',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0383',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0384',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0384',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0385',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0385',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0386',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0386',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0387',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0387',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0388',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0388',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0389',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0389',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0390',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0390',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0391',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0391',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ },
+ {
+ label: 'Hollar_a_3000_0392',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0392',
+ manifestID: '5a25b0703b0eb7478b415bd4'
+ }
+ ],
+ name: 'The fables of Aesop / paraphras\'d in verse, and...'
+ },
+ '5a25b0763b0eb7478b415bd5': {
+ id: '5a25b0763b0eb7478b415bd5',
+ url: 'https://iiif.library.utoronto.ca/presentation/v2/hollar:Hollar_a_3001/manifest',
+ images: [
+ {
+ label: 'Hollar_a_3001_0001',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0001',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0002',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0002',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0003',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0003',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0004',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0004',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0005',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0005',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0006',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0006',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0007',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0007',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0008',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0008',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0009',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0009',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0010',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0010',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0011',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0011',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0012',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0012',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0013',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0013',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0014',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0014',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0015',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0015',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0016',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0016',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0017',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0017',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0018',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0018',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0019',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0019',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0020',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0020',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0021',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0021',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0022',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0022',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0023',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0023',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0024',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0024',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0025',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0025',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0026',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0026',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0027',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0027',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0028',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0028',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0029',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0029',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0030',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0030',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0031',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0031',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0032',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0032',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0033',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0033',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0034',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0034',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0035',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0035',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0036',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0036',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0037',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0037',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0038',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0038',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0039',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0039',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0040',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0040',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0041',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0041',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0042',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0042',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0043',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0043',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0044',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0044',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0045',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0045',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0046',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0046',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0047',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0047',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0048',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0048',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0049',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0049',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0050',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0050',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0051',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0051',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0052',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0052',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0053',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0053',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0054',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0054',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0055',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0055',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0056',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0056',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0057',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0057',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0058',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0058',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0059',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0059',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0060',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0060',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0061',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0061',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0062',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0062',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0063',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0063',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0064',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0064',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0065',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0065',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0066',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0066',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0067',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0067',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0068',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0068',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0069',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0069',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0070',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0070',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0071',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0071',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0072',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0072',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0073',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0073',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0074',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0074',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0075',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0075',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0076',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0076',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0077',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0077',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0078',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0078',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0079',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0079',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0080',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0080',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0081',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0081',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0082',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0082',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0083',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0083',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0084',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0084',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0085',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0085',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0086',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0086',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0087',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0087',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0088',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0088',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0089',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0089',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0090',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0090',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0091',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0091',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0092',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0092',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0093',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0093',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0094',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0094',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0095',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0095',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0096',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0096',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0097',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0097',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0098',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0098',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0099',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0099',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0100',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0100',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0101',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0101',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0102',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0102',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0103',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0103',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0104',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0104',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0105',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0105',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0106',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0106',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0107',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0107',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0108',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0108',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0109',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0109',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0110',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0110',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0111',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0111',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0112',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0112',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0113',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0113',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0114',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0114',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0115',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0115',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0116',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0116',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0117',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0117',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0118',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0118',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0119',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0119',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0120',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0120',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0121',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0121',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0122',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0122',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0123',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0123',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0124',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0124',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0125',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0125',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0126',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0126',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0127',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0127',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0128',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0128',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0129',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0129',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0130',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0130',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0131',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0131',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0132',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0132',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0133',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0133',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0134',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0134',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0135',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0135',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0136',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0136',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0137',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0137',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0138',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0138',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0139',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0139',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0140',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0140',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0141',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0141',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0142',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0142',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0143',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0143',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0144',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0144',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0145',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0145',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0146',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0146',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0147',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0147',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0148',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0148',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0149',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0149',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0150',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0150',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0151',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0151',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0152',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0152',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0153',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0153',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0154',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0154',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0155',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0155',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0156',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0156',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0157',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0157',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0158',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0158',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0159',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0159',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0160',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0160',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0161',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0161',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0162',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0162',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0163',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0163',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0164',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0164',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0165',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0165',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0166',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0166',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0167',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0167',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0168',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0168',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0169',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0169',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0170',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0170',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0171',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0171',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0172',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0172',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0173',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0173',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0174',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0174',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0175',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0175',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0176',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0176',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0177',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0177',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0178',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0178',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0179',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0179',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0180',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0180',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0181',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0181',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0182',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0182',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0183',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0183',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0184',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0184',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0185',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0185',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0186',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0186',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0187',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0187',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0188',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0188',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0189',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0189',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0190',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0190',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0191',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0191',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0192',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0192',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0193',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0193',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0194',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0194',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0195',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0195',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0196',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0196',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0197',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0197',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0198',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0198',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0199',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0199',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0200',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0200',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0201',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0201',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0202',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0202',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0203',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0203',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0204',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0204',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0205',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0205',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0206',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0206',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0207',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0207',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0208',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0208',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0209',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0209',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0210',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0210',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0211',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0211',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0212',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0212',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0213',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0213',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0214',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0214',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0215',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0215',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0216',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0216',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0217',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0217',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0218',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0218',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0219',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0219',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0220',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0220',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0221',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0221',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0222',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0222',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0223',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0223',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0224',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0224',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0225',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0225',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0226',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0226',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0227',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0227',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0228',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0228',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0229',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0229',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0230',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0230',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0231',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0231',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0232',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0232',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0233',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0233',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0234',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0234',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0235',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0235',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0236',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0236',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0237',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0237',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0238',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0238',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0239',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0239',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0240',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0240',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0241',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0241',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0242',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0242',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0243',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0243',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0244',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0244',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0245',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0245',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0246',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0246',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0247',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0247',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0248',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0248',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0249',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0249',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0250',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0250',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0251',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0251',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0252',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0252',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0253',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0253',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0254',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0254',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0255',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0255',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0256',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0256',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0257',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0257',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0258',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0258',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0259',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0259',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0260',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0260',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0261',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0261',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0262',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0262',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0263',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0263',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0264',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0264',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0265',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0265',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0266',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0266',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0267',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0267',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0268',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0268',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0269',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0269',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0270',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0270',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0271',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0271',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0272',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0272',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0273',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0273',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0274',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0274',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0275',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0275',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0276',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0276',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0277',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0277',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0278',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0278',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0279',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0279',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0280',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0280',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0281',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0281',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0282',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0282',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0283',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0283',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0284',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0284',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0285',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0285',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0286',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0286',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0287',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0287',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0288',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0288',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0289',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0289',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0290',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0290',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0291',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0291',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0292',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0292',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0293',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0293',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0294',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0294',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0295',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0295',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0296',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0296',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0297',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0297',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0298',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0298',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0299',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0299',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0300',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0300',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0301',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0301',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0302',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0302',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0303',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0303',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0304',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0304',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0305',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0305',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0306',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0306',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0307',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0307',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0308',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0308',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0309',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0309',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0310',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0310',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0311',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0311',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0312',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0312',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0313',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0313',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0314',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0314',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0315',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0315',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0316',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0316',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0317',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0317',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0318',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0318',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0319',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0319',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0320',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0320',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0321',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0321',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0322',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0322',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0323',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0323',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0324',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0324',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0325',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0325',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0326',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0326',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0327',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0327',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ },
+ {
+ label: 'Hollar_a_3001_0328',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3001_0328',
+ manifestID: '5a25b0763b0eb7478b415bd5'
+ }
+ ],
+ name: 'The history of St. Paul\'s Cathedral in London :...'
+ }
+ },
+ groupIDs: [
+ 'Group_5a57825a4cfad13070870df4',
+ 'Group_5a57825a4cfad13070870df5',
+ 'Group_5a57825a4cfad13070870df6',
+ 'Group_5a57825a4cfad13070870df7'
+ ],
+ leafIDs: [
+ 'Leaf_5a57825a4cfad13070870dc4',
+ 'Leaf_5a57825a4cfad13070870dc7',
+ 'Leaf_5a57825a4cfad13070870dca',
+ 'Leaf_5a57825a4cfad13070870dcd',
+ 'Leaf_5a57825a4cfad13070870dd0',
+ 'Leaf_5a57825a4cfad13070870dd3',
+ 'Leaf_5a57825a4cfad13070870dd6',
+ 'Leaf_5a57825a4cfad13070870dd9',
+ 'Leaf_5a57825a4cfad13070870ddc',
+ 'Leaf_5a57825a4cfad13070870ddf',
+ 'Leaf_5a57825a4cfad13070870de2',
+ 'Leaf_5a57825a4cfad13070870de5',
+ 'Leaf_5a57825a4cfad13070870de8',
+ 'Leaf_5a57825a4cfad13070870deb',
+ 'Leaf_5a57825a4cfad13070870dee',
+ 'Leaf_5a57825a4cfad13070870df1'
+ ],
+ rectoIDs: [
+ 'Recto_5a57825a4cfad13070870dc5',
+ 'Recto_5a57825a4cfad13070870dc8',
+ 'Recto_5a57825a4cfad13070870dcb',
+ 'Recto_5a57825a4cfad13070870dce',
+ 'Recto_5a57825a4cfad13070870dd1',
+ 'Recto_5a57825a4cfad13070870dd4',
+ 'Recto_5a57825a4cfad13070870dd7',
+ 'Recto_5a57825a4cfad13070870dda',
+ 'Recto_5a57825a4cfad13070870ddd',
+ 'Recto_5a57825a4cfad13070870de0',
+ 'Recto_5a57825a4cfad13070870de3',
+ 'Recto_5a57825a4cfad13070870de6',
+ 'Recto_5a57825a4cfad13070870de9',
+ 'Recto_5a57825a4cfad13070870dec',
+ 'Recto_5a57825a4cfad13070870def',
+ 'Recto_5a57825a4cfad13070870df2'
+ ],
+ versoIDs: [
+ 'Verso_5a57825a4cfad13070870dc6',
+ 'Verso_5a57825a4cfad13070870dc9',
+ 'Verso_5a57825a4cfad13070870dcc',
+ 'Verso_5a57825a4cfad13070870dcf',
+ 'Verso_5a57825a4cfad13070870dd2',
+ 'Verso_5a57825a4cfad13070870dd5',
+ 'Verso_5a57825a4cfad13070870dd8',
+ 'Verso_5a57825a4cfad13070870ddb',
+ 'Verso_5a57825a4cfad13070870dde',
+ 'Verso_5a57825a4cfad13070870de1',
+ 'Verso_5a57825a4cfad13070870de4',
+ 'Verso_5a57825a4cfad13070870de7',
+ 'Verso_5a57825a4cfad13070870dea',
+ 'Verso_5a57825a4cfad13070870ded',
+ 'Verso_5a57825a4cfad13070870df0',
+ 'Verso_5a57825a4cfad13070870df3'
+ ],
+ Groups: {
+ Group_5a57825a4cfad13070870df4: {
+ id: 'Group_5a57825a4cfad13070870df4',
+ type: 'Quire',
+ title: 'First Quire',
+ tacketed: [],
+ sewing: [
+ 'Leaf_5a57825a4cfad13070870dc7',
+ 'Leaf_5a57825a4cfad13070870dcd'
+ ],
+ nestLevel: 1,
+ parentID: null,
+ notes: [
+ '5a57825a4cfad13070870df8'
+ ],
+ memberIDs: [
+ 'Leaf_5a57825a4cfad13070870dc4',
+ 'Leaf_5a57825a4cfad13070870dc7',
+ 'Leaf_5a57825a4cfad13070870dca',
+ 'Leaf_5a57825a4cfad13070870dcd',
+ 'Leaf_5a57825a4cfad13070870dd0',
+ 'Leaf_5a57825a4cfad13070870dd3'
+ ],
+ memberType: 'Group'
+ },
+ Group_5a57825a4cfad13070870df5: {
+ id: 'Group_5a57825a4cfad13070870df5',
+ type: 'Quire',
+ title: '2nd Quire',
+ tacketed: [
+ 'Leaf_5a57825a4cfad13070870de5',
+ 'Leaf_5a57825a4cfad13070870dee'
+ ],
+ sewing: [],
+ nestLevel: 1,
+ parentID: null,
+ notes: [
+ '5a57825a4cfad13070870df8'
+ ],
+ memberIDs: [
+ 'Group_5a57825a4cfad13070870df6',
+ 'Leaf_5a57825a4cfad13070870de2',
+ 'Leaf_5a57825a4cfad13070870de5',
+ 'Leaf_5a57825a4cfad13070870de8',
+ 'Leaf_5a57825a4cfad13070870deb',
+ 'Leaf_5a57825a4cfad13070870dee',
+ 'Leaf_5a57825a4cfad13070870df1'
+ ],
+ memberType: 'Group'
+ },
+ Group_5a57825a4cfad13070870df6: {
+ id: 'Group_5a57825a4cfad13070870df6',
+ type: 'Quire',
+ title: '1st Sub Quire of 2',
+ tacketed: [],
+ sewing: [],
+ nestLevel: 2,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ notes: [],
+ memberIDs: [
+ 'Group_5a57825a4cfad13070870df7',
+ 'Leaf_5a57825a4cfad13070870ddc',
+ 'Leaf_5a57825a4cfad13070870ddf'
+ ],
+ memberType: 'Group'
+ },
+ Group_5a57825a4cfad13070870df7: {
+ id: 'Group_5a57825a4cfad13070870df7',
+ type: 'Quire',
+ title: '1st Sub Quire of Sub Quire 2.1',
+ tacketed: [],
+ sewing: [],
+ nestLevel: 3,
+ parentID: 'Group_5a57825a4cfad13070870df6',
+ notes: [],
+ memberIDs: [
+ 'Leaf_5a57825a4cfad13070870dd6',
+ 'Leaf_5a57825a4cfad13070870dd9'
+ ],
+ memberType: 'Group'
+ }
+ },
+ Leafs: {
+ Leaf_5a57825a4cfad13070870dc4: {
+ id: 'Leaf_5a57825a4cfad13070870dc4',
+ material: 'None',
+ type: 'Endleaf',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dd3',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dc5',
+ versoID: 'Verso_5a57825a4cfad13070870dc6',
+ notes: [
+ '5a57825a4cfad13070870df9'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dc7: {
+ id: 'Leaf_5a57825a4cfad13070870dc7',
+ material: 'None',
+ type: 'Missing',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dd0',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dc8',
+ versoID: 'Verso_5a57825a4cfad13070870dc9',
+ notes: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dca: {
+ id: 'Leaf_5a57825a4cfad13070870dca',
+ material: 'Parchment',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dcd',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dcb',
+ versoID: 'Verso_5a57825a4cfad13070870dcc',
+ notes: [
+ '5a57825a4cfad13070870dfa'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dcd: {
+ id: 'Leaf_5a57825a4cfad13070870dcd',
+ material: 'Parchment',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dca',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dce',
+ versoID: 'Verso_5a57825a4cfad13070870dcf',
+ notes: [
+ '5a57825a4cfad13070870dfa'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dd0: {
+ id: 'Leaf_5a57825a4cfad13070870dd0',
+ material: 'Parchment',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dc7',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dd1',
+ versoID: 'Verso_5a57825a4cfad13070870dd2',
+ notes: [
+ '5a57825a4cfad13070870dfa'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dd3: {
+ id: 'Leaf_5a57825a4cfad13070870dd3',
+ material: 'None',
+ type: 'Endleaf',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dc4',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dd4',
+ versoID: 'Verso_5a57825a4cfad13070870dd5',
+ notes: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dd6: {
+ id: 'Leaf_5a57825a4cfad13070870dd6',
+ material: 'None',
+ type: 'None',
+ conjoined_to: null,
+ attached_above: 'None',
+ attached_below: 'Glued (Partial)',
+ stub: 'None',
+ nestLevel: 3,
+ parentID: 'Group_5a57825a4cfad13070870df7',
+ rectoID: 'Recto_5a57825a4cfad13070870dd7',
+ versoID: 'Verso_5a57825a4cfad13070870dd8',
+ notes: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dd9: {
+ id: 'Leaf_5a57825a4cfad13070870dd9',
+ material: 'None',
+ type: 'None',
+ conjoined_to: null,
+ attached_above: 'Glued (Partial)',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 3,
+ parentID: 'Group_5a57825a4cfad13070870df7',
+ rectoID: 'Recto_5a57825a4cfad13070870dda',
+ versoID: 'Verso_5a57825a4cfad13070870ddb',
+ notes: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870ddc: {
+ id: 'Leaf_5a57825a4cfad13070870ddc',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870ddf',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 2,
+ parentID: 'Group_5a57825a4cfad13070870df6',
+ rectoID: 'Recto_5a57825a4cfad13070870ddd',
+ versoID: 'Verso_5a57825a4cfad13070870dde',
+ notes: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870ddf: {
+ id: 'Leaf_5a57825a4cfad13070870ddf',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870ddc',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 2,
+ parentID: 'Group_5a57825a4cfad13070870df6',
+ rectoID: 'Recto_5a57825a4cfad13070870de0',
+ versoID: 'Verso_5a57825a4cfad13070870de1',
+ notes: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870de2: {
+ id: 'Leaf_5a57825a4cfad13070870de2',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870df1',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870de3',
+ versoID: 'Verso_5a57825a4cfad13070870de4',
+ notes: [
+ '5a57825a4cfad13070870df9'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870de5: {
+ id: 'Leaf_5a57825a4cfad13070870de5',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dee',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870de6',
+ versoID: 'Verso_5a57825a4cfad13070870de7',
+ notes: [
+ '5a57825a4cfad13070870df8'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870de8: {
+ id: 'Leaf_5a57825a4cfad13070870de8',
+ material: 'None',
+ type: 'None',
+ conjoined_to: null,
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'Original',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870de9',
+ versoID: 'Verso_5a57825a4cfad13070870dea',
+ notes: [
+ '5a57825a4cfad13070870df8'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870deb: {
+ id: 'Leaf_5a57825a4cfad13070870deb',
+ material: 'None',
+ type: 'None',
+ conjoined_to: null,
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870dec',
+ versoID: 'Verso_5a57825a4cfad13070870ded',
+ notes: [
+ '5a57825a4cfad13070870df8'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dee: {
+ id: 'Leaf_5a57825a4cfad13070870dee',
+ material: 'None',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870de5',
+ attached_above: 'None',
+ attached_below: 'Glued (Complete)',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870def',
+ versoID: 'Verso_5a57825a4cfad13070870df0',
+ notes: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870df1: {
+ id: 'Leaf_5a57825a4cfad13070870df1',
+ material: 'None',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870de2',
+ attached_above: 'Glued (Complete)',
+ attached_below: 'None',
+ stub: 'None',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870df2',
+ versoID: 'Verso_5a57825a4cfad13070870df3',
+ notes: [],
+ memberType: 'Leaf'
+ }
+ },
+ Rectos: {
+ Recto_5a57825a4cfad13070870dc5: {
+ id: 'Recto_5a57825a4cfad13070870dc5',
+ parentID: 'Leaf_5a57825a4cfad13070870dc4',
+ folio_number: '1R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {
+ manifestID: '5a25b0703b0eb7478b415bd4',
+ label: 'Hollar_a_3000_0003',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0003'
+ },
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dc8: {
+ id: 'Recto_5a57825a4cfad13070870dc8',
+ parentID: 'Leaf_5a57825a4cfad13070870dc7',
+ folio_number: '2R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {
+ manifestID: '5a25b0703b0eb7478b415bd4',
+ label: 'Hollar_a_3000_0002',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0002'
+ },
+ script_direction: 'None',
+ notes: [
+ '5a57825a4cfad13070870dfa'
+ ],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dcb: {
+ id: 'Recto_5a57825a4cfad13070870dcb',
+ parentID: 'Leaf_5a57825a4cfad13070870dca',
+ folio_number: '3R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {
+ manifestID: '5a25b0703b0eb7478b415bd4',
+ label: 'Hollar_a_3000_0001',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0001'
+ },
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dce: {
+ id: 'Recto_5a57825a4cfad13070870dce',
+ parentID: 'Leaf_5a57825a4cfad13070870dcd',
+ folio_number: '4R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {
+ manifestID: '5a25b0703b0eb7478b415bd4',
+ label: 'Hollar_a_3000_0007',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0007'
+ },
+ script_direction: 'Left-to-Right',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dd1: {
+ id: 'Recto_5a57825a4cfad13070870dd1',
+ parentID: 'Leaf_5a57825a4cfad13070870dd0',
+ folio_number: '5R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {
+ manifestID: '5a25b0703b0eb7478b415bd4',
+ label: 'Hollar_a_3000_0009',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0009'
+ },
+ script_direction: 'Left-to-Right',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dd4: {
+ id: 'Recto_5a57825a4cfad13070870dd4',
+ parentID: 'Leaf_5a57825a4cfad13070870dd3',
+ folio_number: '6R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {
+ manifestID: '5a25b0703b0eb7478b415bd4',
+ label: 'Hollar_a_3000_0011',
+ url: 'https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3000_0011'
+ },
+ script_direction: 'Left-to-Right',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dd7: {
+ id: 'Recto_5a57825a4cfad13070870dd7',
+ parentID: 'Leaf_5a57825a4cfad13070870dd6',
+ folio_number: '7R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'Left-to-Right',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dda: {
+ id: 'Recto_5a57825a4cfad13070870dda',
+ parentID: 'Leaf_5a57825a4cfad13070870dd9',
+ folio_number: '8R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'Left-to-Right',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870ddd: {
+ id: 'Recto_5a57825a4cfad13070870ddd',
+ parentID: 'Leaf_5a57825a4cfad13070870ddc',
+ folio_number: '9R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'Left-to-Right',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870de0: {
+ id: 'Recto_5a57825a4cfad13070870de0',
+ parentID: 'Leaf_5a57825a4cfad13070870ddf',
+ folio_number: '10R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870de3: {
+ id: 'Recto_5a57825a4cfad13070870de3',
+ parentID: 'Leaf_5a57825a4cfad13070870de2',
+ folio_number: '11R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870de6: {
+ id: 'Recto_5a57825a4cfad13070870de6',
+ parentID: 'Leaf_5a57825a4cfad13070870de5',
+ folio_number: '12R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870de9: {
+ id: 'Recto_5a57825a4cfad13070870de9',
+ parentID: 'Leaf_5a57825a4cfad13070870de8',
+ folio_number: '13R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dec: {
+ id: 'Recto_5a57825a4cfad13070870dec',
+ parentID: 'Leaf_5a57825a4cfad13070870deb',
+ folio_number: '14R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870def: {
+ id: 'Recto_5a57825a4cfad13070870def',
+ parentID: 'Leaf_5a57825a4cfad13070870dee',
+ folio_number: '15R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870df2: {
+ id: 'Recto_5a57825a4cfad13070870df2',
+ parentID: 'Leaf_5a57825a4cfad13070870df1',
+ folio_number: '16R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Recto'
+ }
+ },
+ Versos: {
+ Verso_5a57825a4cfad13070870dc6: {
+ id: 'Verso_5a57825a4cfad13070870dc6',
+ parentID: 'Leaf_5a57825a4cfad13070870dc4',
+ folio_number: '1V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {
+ manifestID: 'DIYImages',
+ label: 'cguk1l0u4aeewdf_copy(1).jpeg',
+ url: 'http://localhost:3001/images/5a5782714cfad13070870dfc_cguk1l0u4aeewdf_copy(1).jpeg'
+ },
+ script_direction: 'None',
+ notes: [
+ '5a57825a4cfad13070870df9',
+ '5a57825a4cfad13070870dfa'
+ ],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dc9: {
+ id: 'Verso_5a57825a4cfad13070870dc9',
+ parentID: 'Leaf_5a57825a4cfad13070870dc7',
+ folio_number: '2V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {
+ manifestID: 'DIYImages',
+ label: '3c17f2b4127b1a5a8bcfc76ba9de9c9f_chiba_inu_dogs_shiba_inu_funny.jpeg',
+ url: 'http://localhost:3001/images/5a5782714cfad13070870dfd_3c17f2b4127b1a5a8bcfc76ba9de9c9f_chiba_inu_dogs_shiba_inu_funny.jpeg'
+ },
+ script_direction: 'None',
+ notes: [
+ '5a57825a4cfad13070870dfa'
+ ],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dcc: {
+ id: 'Verso_5a57825a4cfad13070870dcc',
+ parentID: 'Leaf_5a57825a4cfad13070870dca',
+ folio_number: '3V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {
+ manifestID: 'DIYImages',
+ label: '10_profile_copy(1).jpeg',
+ url: 'http://localhost:3001/images/5a5782714cfad13070870dff_10_profile_copy(1).jpeg'
+ },
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dcf: {
+ id: 'Verso_5a57825a4cfad13070870dcf',
+ parentID: 'Leaf_5a57825a4cfad13070870dcd',
+ folio_number: '4V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dd2: {
+ id: 'Verso_5a57825a4cfad13070870dd2',
+ parentID: 'Leaf_5a57825a4cfad13070870dd0',
+ folio_number: '5V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dd5: {
+ id: 'Verso_5a57825a4cfad13070870dd5',
+ parentID: 'Leaf_5a57825a4cfad13070870dd3',
+ folio_number: '6V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dd8: {
+ id: 'Verso_5a57825a4cfad13070870dd8',
+ parentID: 'Leaf_5a57825a4cfad13070870dd6',
+ folio_number: '7V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870ddb: {
+ id: 'Verso_5a57825a4cfad13070870ddb',
+ parentID: 'Leaf_5a57825a4cfad13070870dd9',
+ folio_number: '8V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dde: {
+ id: 'Verso_5a57825a4cfad13070870dde',
+ parentID: 'Leaf_5a57825a4cfad13070870ddc',
+ folio_number: '9V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870de1: {
+ id: 'Verso_5a57825a4cfad13070870de1',
+ parentID: 'Leaf_5a57825a4cfad13070870ddf',
+ folio_number: '10V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870de4: {
+ id: 'Verso_5a57825a4cfad13070870de4',
+ parentID: 'Leaf_5a57825a4cfad13070870de2',
+ folio_number: '11V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870de7: {
+ id: 'Verso_5a57825a4cfad13070870de7',
+ parentID: 'Leaf_5a57825a4cfad13070870de5',
+ folio_number: '12V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dea: {
+ id: 'Verso_5a57825a4cfad13070870dea',
+ parentID: 'Leaf_5a57825a4cfad13070870de8',
+ folio_number: '13V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870ded: {
+ id: 'Verso_5a57825a4cfad13070870ded',
+ parentID: 'Leaf_5a57825a4cfad13070870deb',
+ folio_number: '14V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'Right-To-Left',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870df0: {
+ id: 'Verso_5a57825a4cfad13070870df0',
+ parentID: 'Leaf_5a57825a4cfad13070870dee',
+ folio_number: '15V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'Right-To-Left',
+ notes: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870df3: {
+ id: 'Verso_5a57825a4cfad13070870df3',
+ parentID: 'Leaf_5a57825a4cfad13070870df1',
+ folio_number: '16V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'Right-To-Left',
+ notes: [],
+ memberType: 'Verso'
+ }
+ },
+ Notes: {
+ '5a57825a4cfad13070870df8': {
+ id: '5a57825a4cfad13070870df8',
+ title: 'Black ink',
+ type: 'Ink',
+ description: 'Some black ink over here\n',
+ show: true,
+ objects: {
+ Group: [
+ 'Group_5a57825a4cfad13070870df4',
+ 'Group_5a57825a4cfad13070870df5'
+ ],
+ Leaf: [
+ 'Leaf_5a57825a4cfad13070870de5',
+ 'Leaf_5a57825a4cfad13070870de8',
+ 'Leaf_5a57825a4cfad13070870deb'
+ ],
+ Recto: [],
+ Verso: []
+ }
+ },
+ '5a57825a4cfad13070870df9': {
+ id: '5a57825a4cfad13070870df9',
+ title: 'John\'s hand',
+ type: 'Hand',
+ description: 'Look ! ',
+ show: false,
+ objects: {
+ Group: [],
+ Leaf: [
+ 'Leaf_5a57825a4cfad13070870dc4',
+ 'Leaf_5a57825a4cfad13070870de2'
+ ],
+ Recto: [],
+ Verso: [
+ 'Verso_5a57825a4cfad13070870dc6'
+ ]
+ }
+ },
+ '5a57825a4cfad13070870dfa': {
+ id: '5a57825a4cfad13070870dfa',
+ title: 'Fire',
+ type: 'Damage',
+ description: 'Some burnt marks',
+ show: true,
+ objects: {
+ Group: [],
+ Leaf: [
+ 'Leaf_5a57825a4cfad13070870dca',
+ 'Leaf_5a57825a4cfad13070870dcd',
+ 'Leaf_5a57825a4cfad13070870dd0'
+ ],
+ Recto: [
+ 'Recto_5a57825a4cfad13070870dc8'
+ ],
+ Verso: [
+ 'Verso_5a57825a4cfad13070870dc6',
+ 'Verso_5a57825a4cfad13070870dc9'
+ ]
+ }
+ }
+ }
+ },
+ managerMode: 'collationManager',
+ collationManager: {
+ selectedObjects: {
+ type: 'Leaf',
+ members: [
+ 'Leaf_5a57825a4cfad13070870dc4'
+ ],
+ lastSelected: 'Leaf_5a57825a4cfad13070870dc4'
+ },
+ viewMode: 'VISUAL',
+ visibleAttributes: {
+ group: {
+ type: false,
+ title: false
+ },
+ leaf: {
+ type: false,
+ material: false,
+ conjoined_leaf_order: false,
+ attached_below: false,
+ attached_above: false,
+ stub: false
+ },
+ side: {
+ folio_number: false,
+ texture: false,
+ script_direction: false,
+ uri: false
+ }
+ },
+ defaultAttributes: {
+ leaf: [
+ {
+ name: 'type',
+ displayName: 'Type',
+ options: [
+ 'None',
+ 'Original',
+ 'Added',
+ 'Missing',
+ 'Hook',
+ 'Endleaf',
+ 'Replaced'
+ ],
+ isDropdown: true
+ },
+ {
+ name: 'material',
+ displayName: 'Material',
+ options: [
+ 'None',
+ 'Parchment',
+ 'Paper',
+ 'Other'
+ ],
+ isDropdown: true
+ },
+ {
+ name: 'conjoined_to',
+ displayName: 'Conjoined To',
+ isDropdown: true
+ },
+ {
+ name: 'attached_above',
+ displayName: 'Attached Above',
+ options: [
+ 'None',
+ 'Glued (Partial)',
+ 'Glued (Complete)',
+ 'Glued (Drumming)',
+ 'Other'
+ ],
+ isDropdown: true
+ },
+ {
+ name: 'attached_below',
+ displayName: 'Attached Below',
+ options: [
+ 'None',
+ 'Glued (Partial)',
+ 'Glued (Complete)',
+ 'Glued (Drumming)',
+ 'Other'
+ ],
+ isDropdown: true
+ },
+ {
+ name: 'stub',
+ displayName: 'Stub',
+ options: [
+ 'None',
+ 'Original',
+ 'Added'
+ ],
+ isDropdown: true
+ }
+ ],
+ group: [
+ {
+ name: 'type',
+ displayName: 'Type',
+ options: [
+ 'Quire',
+ 'Booklet'
+ ],
+ isDropdown: true
+ },
+ {
+ name: 'title',
+ displayName: 'Title'
+ }
+ ],
+ side: [
+ {
+ name: 'texture',
+ displayName: 'Texture',
+ options: [
+ 'None',
+ 'Hair',
+ 'Flesh',
+ 'Felt',
+ 'Wire'
+ ],
+ isDropdown: true
+ },
+ {
+ name: 'folio_number',
+ displayName: 'Folio Number'
+ },
+ {
+ name: 'script_direction',
+ displayName: 'Script Direction',
+ options: [
+ 'None',
+ 'Left-to-Right',
+ 'Right-To-Left',
+ 'Top-To-Bottom'
+ ],
+ isDropdown: true
+ },
+ {
+ name: 'uri',
+ displayName: 'URI'
+ }
+ ],
+ note: [
+ {
+ name: 'title',
+ displayName: 'Title'
+ },
+ {
+ name: 'type',
+ displayName: 'Type',
+ isDropdown: true
+ },
+ {
+ name: 'description',
+ displayName: 'Description'
+ }
+ ]
+ },
+ filters: {
+ filterPanelOpen: false,
+ Groups: [],
+ Leafs: [],
+ Sides: [],
+ Notes: [],
+ GroupsOfMatchingLeafs: [],
+ LeafsOfMatchingSides: [],
+ GroupsOfMatchingSides: [],
+ GroupsOfMatchingNotes: [],
+ LeafsOfMatchingNotes: [],
+ SidesOfMatchingNotes: [],
+ active: false,
+ hideOthers: false,
+ queries: [
+ {
+ type: null,
+ attribute: '',
+ attributeIndex: '',
+ values: [],
+ condition: '',
+ conjunction: ''
+ }
+ ],
+ selection: ''
+ },
+ flashItems: {
+ leaves: [],
+ groups: []
+ },
+ visualizations: {
+ tacketed: '',
+ sewing: ''
+ }
+ },
+ notesManager: {
+ activeTab: 'MANAGE'
+ },
+ imageManager: {
+ activeTab: 'MANAGE',
+ manageSources: {
+ error: ''
+ }
+ },
+}
\ No newline at end of file
diff --git a/viscoll-app/assetsTransformer.js b/viscoll-app/assetsTransformer.js
new file mode 100644
index 00000000..5eadded9
--- /dev/null
+++ b/viscoll-app/assetsTransformer.js
@@ -0,0 +1,7 @@
+const path = require('path');
+
+module.exports = {
+ process(src, filename, config, options) {
+ return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
+ },
+};
diff --git a/viscoll-app/package-lock.json b/viscoll-app/package-lock.json
new file mode 100644
index 00000000..8c7b49b8
--- /dev/null
+++ b/viscoll-app/package-lock.json
@@ -0,0 +1,19531 @@
+{
+ "name": "viscoll-app",
+ "version": "0.1.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
+ "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.8.3"
+ }
+ },
+ "@babel/compat-data": {
+ "version": "7.8.6",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.6.tgz",
+ "integrity": "sha512-CurCIKPTkS25Mb8mz267vU95vy+TyUpnctEX2lV33xWNmHAfjruztgiPBbXZRh3xZZy1CYvGx6XfxyTVS+sk7Q==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.8.5",
+ "invariant": "^2.2.4",
+ "semver": "^5.5.0"
+ }
+ },
+ "@babel/core": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.4.tgz",
+ "integrity": "sha512-0LiLrB2PwrVI+a2/IEskBopDYSd8BCb3rOvH7D5tzoWd696TBEduBvuLVm4Nx6rltrLZqvI3MCalB2K2aVzQjA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.8.3",
+ "@babel/generator": "^7.8.4",
+ "@babel/helpers": "^7.8.4",
+ "@babel/parser": "^7.8.4",
+ "@babel/template": "^7.8.3",
+ "@babel/traverse": "^7.8.4",
+ "@babel/types": "^7.8.3",
+ "convert-source-map": "^1.7.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.1",
+ "json5": "^2.1.0",
+ "lodash": "^4.17.13",
+ "resolve": "^1.3.2",
+ "semver": "^5.4.1",
+ "source-map": "^0.5.0"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.7.tgz",
+ "integrity": "sha512-DQwjiKJqH4C3qGiyQCAExJHoZssn49JTMJgZ8SANGgVFdkupcUhLOdkAeoC6kmHZCPfoDG5M0b6cFlSN5wW7Ew==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.8.7",
+ "jsesc": "^2.5.1",
+ "lodash": "^4.17.13",
+ "source-map": "^0.5.0"
+ },
+ "dependencies": {
+ "jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/helper-annotate-as-pure": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz",
+ "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-builder-binary-assignment-operator-visitor": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz",
+ "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-explode-assignable-expression": "^7.8.3",
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-builder-react-jsx": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.8.3.tgz",
+ "integrity": "sha512-JT8mfnpTkKNCboTqZsQTdGo3l3Ik3l7QIt9hh0O9DYiwVel37VoJpILKM4YFbP2euF32nkQSb+F9cUk9b7DDXQ==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.8.3",
+ "esutils": "^2.0.0"
+ }
+ },
+ "@babel/helper-call-delegate": {
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.8.7.tgz",
+ "integrity": "sha512-doAA5LAKhsFCR0LAFIf+r2RSMmC+m8f/oQ+URnUET/rWeEzC0yTRmAGyWkD4sSu3xwbS7MYQ2u+xlt1V5R56KQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-hoist-variables": "^7.8.3",
+ "@babel/traverse": "^7.8.3",
+ "@babel/types": "^7.8.7"
+ }
+ },
+ "@babel/helper-compilation-targets": {
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz",
+ "integrity": "sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.8.6",
+ "browserslist": "^4.9.1",
+ "invariant": "^2.2.4",
+ "levenary": "^1.1.1",
+ "semver": "^5.5.0"
+ }
+ },
+ "@babel/helper-create-class-features-plugin": {
+ "version": "7.8.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.6.tgz",
+ "integrity": "sha512-klTBDdsr+VFFqaDHm5rR69OpEQtO2Qv8ECxHS1mNhJJvaHArR6a1xTf5K/eZW7eZpJbhCx3NW1Yt/sKsLXLblg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.8.3",
+ "@babel/helper-member-expression-to-functions": "^7.8.3",
+ "@babel/helper-optimise-call-expression": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/helper-replace-supers": "^7.8.6",
+ "@babel/helper-split-export-declaration": "^7.8.3"
+ }
+ },
+ "@babel/helper-create-regexp-features-plugin": {
+ "version": "7.8.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.6.tgz",
+ "integrity": "sha512-bPyujWfsHhV/ztUkwGHz/RPV1T1TDEsSZDsN42JPehndA+p1KKTh3npvTadux0ZhCrytx9tvjpWNowKby3tM6A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.8.3",
+ "@babel/helper-regex": "^7.8.3",
+ "regexpu-core": "^4.6.0"
+ },
+ "dependencies": {
+ "regexpu-core": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz",
+ "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==",
+ "dev": true,
+ "requires": {
+ "regenerate": "^1.4.0",
+ "regenerate-unicode-properties": "^8.1.0",
+ "regjsgen": "^0.5.0",
+ "regjsparser": "^0.6.0",
+ "unicode-match-property-ecmascript": "^1.0.4",
+ "unicode-match-property-value-ecmascript": "^1.1.0"
+ }
+ },
+ "regjsgen": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz",
+ "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==",
+ "dev": true
+ },
+ "regjsparser": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.3.tgz",
+ "integrity": "sha512-8uZvYbnfAtEm9Ab8NTb3hdLwL4g/LQzEYP7Xs27T96abJCCE2d6r3cPZPQEsLKy0vRSGVNG+/zVGtLr86HQduA==",
+ "dev": true,
+ "requires": {
+ "jsesc": "~0.5.0"
+ }
+ }
+ }
+ },
+ "@babel/helper-define-map": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz",
+ "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.8.3",
+ "@babel/types": "^7.8.3",
+ "lodash": "^4.17.13"
+ }
+ },
+ "@babel/helper-explode-assignable-expression": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz",
+ "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==",
+ "dev": true,
+ "requires": {
+ "@babel/traverse": "^7.8.3",
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz",
+ "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.8.3",
+ "@babel/template": "^7.8.3",
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz",
+ "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-hoist-variables": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz",
+ "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-member-expression-to-functions": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz",
+ "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-module-imports": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz",
+ "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-module-transforms": {
+ "version": "7.8.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.8.6.tgz",
+ "integrity": "sha512-RDnGJSR5EFBJjG3deY0NiL0K9TO8SXxS9n/MPsbPK/s9LbQymuLNtlzvDiNS7IpecuL45cMeLVkA+HfmlrnkRg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.8.3",
+ "@babel/helper-replace-supers": "^7.8.6",
+ "@babel/helper-simple-access": "^7.8.3",
+ "@babel/helper-split-export-declaration": "^7.8.3",
+ "@babel/template": "^7.8.6",
+ "@babel/types": "^7.8.6",
+ "lodash": "^4.17.13"
+ }
+ },
+ "@babel/helper-optimise-call-expression": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz",
+ "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-plugin-utils": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz",
+ "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==",
+ "dev": true
+ },
+ "@babel/helper-regex": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz",
+ "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.13"
+ }
+ },
+ "@babel/helper-remap-async-to-generator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz",
+ "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.8.3",
+ "@babel/helper-wrap-function": "^7.8.3",
+ "@babel/template": "^7.8.3",
+ "@babel/traverse": "^7.8.3",
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-replace-supers": {
+ "version": "7.8.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz",
+ "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-member-expression-to-functions": "^7.8.3",
+ "@babel/helper-optimise-call-expression": "^7.8.3",
+ "@babel/traverse": "^7.8.6",
+ "@babel/types": "^7.8.6"
+ }
+ },
+ "@babel/helper-simple-access": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz",
+ "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.8.3",
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz",
+ "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helper-wrap-function": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz",
+ "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.8.3",
+ "@babel/template": "^7.8.3",
+ "@babel/traverse": "^7.8.3",
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/helpers": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.8.4.tgz",
+ "integrity": "sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.8.3",
+ "@babel/traverse": "^7.8.4",
+ "@babel/types": "^7.8.3"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz",
+ "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.0.0",
+ "esutils": "^2.0.2",
+ "js-tokens": "^4.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "@babel/parser": {
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.7.tgz",
+ "integrity": "sha512-9JWls8WilDXFGxs0phaXAZgpxTZhSk/yOYH2hTHC0X1yC7Z78IJfvR1vJ+rmJKq3I35td2XzXzN6ZLYlna+r/A==",
+ "dev": true
+ },
+ "@babel/plugin-proposal-async-generator-functions": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz",
+ "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/helper-remap-async-to-generator": "^7.8.3",
+ "@babel/plugin-syntax-async-generators": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-class-properties": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz",
+ "integrity": "sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-proposal-decorators": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.8.3.tgz",
+ "integrity": "sha512-e3RvdvS4qPJVTe288DlXjwKflpfy1hr0j5dz5WpIYYeP7vQZg2WfAEIp8k5/Lwis/m5REXEteIz6rrcDtXXG7w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-syntax-decorators": "^7.8.3"
+ }
+ },
+ "@babel/plugin-proposal-dynamic-import": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz",
+ "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz",
+ "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-syntax-json-strings": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-numeric-separator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz",
+ "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.8.3"
+ }
+ },
+ "@babel/plugin-proposal-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-8qvuPwU/xxUCt78HocNlv0mXXo0wdh9VT1R04WU8HGOfaOob26pF+9P5/lYjN/q7DHOX1bvX60hnhOvuQUJdbA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-QIoIR9abkVn+seDE3OjA08jWcs3eZ9+wJCKSRgo3WdEU2csFYgdScb+8qHB3+WXsGJD55u+5hWCISI7ejXS+kg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-unicode-property-regex": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.3.tgz",
+ "integrity": "sha512-1/1/rEZv2XGweRwwSkLpY+s60za9OZ1hJs4YDqFHCw0kYWYwL5IFljVY1MYBL+weT1l9pokDO2uhSTLVxzoHkQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-decorators": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.8.3.tgz",
+ "integrity": "sha512-8Hg4dNNT9/LcA1zQlfwuKR8BUc/if7Q7NkTam9sGTcJphLwpf2g4S42uhspQrIrR+dpzE0dtTqBVFoHl8GtnnQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-syntax-dynamic-import": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
+ "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-flow": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.8.3.tgz",
+ "integrity": "sha512-innAx3bUbA0KSYj2E2MNFSn9hiCeowOFLxlsuhXzw8hMQnzkDomUr9QCD7E9VF60NmnG1sNTuuv6Qf4f8INYsg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-jsx": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz",
+ "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-numeric-separator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz",
+ "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-top-level-await": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz",
+ "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-syntax-typescript": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.8.3.tgz",
+ "integrity": "sha512-GO1MQ/SGGGoiEXY0e0bSpHimJvxqB7lktLLIq2pv8xG7WZ8IMEle74jIe1FhprHBWjwjZtXHkycDLZXIWM5Wfg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-arrow-functions": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz",
+ "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-async-to-generator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz",
+ "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/helper-remap-async-to-generator": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-block-scoped-functions": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz",
+ "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-block-scoping": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz",
+ "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "lodash": "^4.17.13"
+ }
+ },
+ "@babel/plugin-transform-classes": {
+ "version": "7.8.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.6.tgz",
+ "integrity": "sha512-k9r8qRay/R6v5aWZkrEclEhKO6mc1CCQr2dLsVHBmOQiMpN6I2bpjX3vgnldUWeEI1GHVNByULVxZ4BdP4Hmdg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.8.3",
+ "@babel/helper-define-map": "^7.8.3",
+ "@babel/helper-function-name": "^7.8.3",
+ "@babel/helper-optimise-call-expression": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/helper-replace-supers": "^7.8.6",
+ "@babel/helper-split-export-declaration": "^7.8.3",
+ "globals": "^11.1.0"
+ },
+ "dependencies": {
+ "globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/plugin-transform-computed-properties": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz",
+ "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-destructuring": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.3.tgz",
+ "integrity": "sha512-H4X646nCkiEcHZUZaRkhE2XVsoz0J/1x3VVujnn96pSoGCtKPA99ZZA+va+gK+92Zycd6OBKCD8tDb/731bhgQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-dotall-regex": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz",
+ "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-duplicate-keys": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz",
+ "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-exponentiation-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz",
+ "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-flow-strip-types": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.8.3.tgz",
+ "integrity": "sha512-g/6WTWG/xbdd2exBBzMfygjX/zw4eyNC4X8pRaq7aRHRoDUCzAIu3kGYIXviOv8BjCuWm8vDBwjHcjiRNgXrPA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-syntax-flow": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-for-of": {
+ "version": "7.8.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.6.tgz",
+ "integrity": "sha512-M0pw4/1/KI5WAxPsdcUL/w2LJ7o89YHN3yLkzNjg7Yl15GlVGgzHyCU+FMeAxevHGsLVmUqbirlUIKTafPmzdw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-function-name": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz",
+ "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-literals": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz",
+ "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-member-expression-literals": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz",
+ "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-modules-amd": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.8.3.tgz",
+ "integrity": "sha512-MadJiU3rLKclzT5kBH4yxdry96odTUwuqrZM+GllFI/VhxfPz+k9MshJM+MwhfkCdxxclSbSBbUGciBngR+kEQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-transforms": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "babel-plugin-dynamic-import-node": "^2.3.0"
+ }
+ },
+ "@babel/plugin-transform-modules-commonjs": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.8.3.tgz",
+ "integrity": "sha512-JpdMEfA15HZ/1gNuB9XEDlZM1h/gF/YOH7zaZzQu2xCFRfwc01NXBMHHSTT6hRjlXJJs5x/bfODM3LiCk94Sxg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-transforms": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/helper-simple-access": "^7.8.3",
+ "babel-plugin-dynamic-import-node": "^2.3.0"
+ }
+ },
+ "@babel/plugin-transform-modules-systemjs": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.8.3.tgz",
+ "integrity": "sha512-8cESMCJjmArMYqa9AO5YuMEkE4ds28tMpZcGZB/jl3n0ZzlsxOAi3mC+SKypTfT8gjMupCnd3YiXCkMjj2jfOg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-hoist-variables": "^7.8.3",
+ "@babel/helper-module-transforms": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "babel-plugin-dynamic-import-node": "^2.3.0"
+ }
+ },
+ "@babel/plugin-transform-modules-umd": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.8.3.tgz",
+ "integrity": "sha512-evhTyWhbwbI3/U6dZAnx/ePoV7H6OUG+OjiJFHmhr9FPn0VShjwC2kdxqIuQ/+1P50TMrneGzMeyMTFOjKSnAw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-transforms": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-named-capturing-groups-regex": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz",
+ "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-new-target": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz",
+ "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-object-super": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz",
+ "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/helper-replace-supers": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-parameters": {
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.7.tgz",
+ "integrity": "sha512-brYWaEPTRimOctz2NDA3jnBbDi7SVN2T4wYuu0aqSzxC3nozFZngGaw29CJ9ZPweB7k+iFmZuoG3IVPIcXmD2g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-call-delegate": "^7.8.7",
+ "@babel/helper-get-function-arity": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-property-literals": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz",
+ "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-react-constant-elements": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.8.3.tgz",
+ "integrity": "sha512-glrzN2U+egwRfkNFtL34xIBYTxbbUF2qJTP8HD3qETBBqzAWSeNB821X0GjU06+dNpq/UyCIjI72FmGE5NNkQQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-react-display-name": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.8.3.tgz",
+ "integrity": "sha512-3Jy/PCw8Fe6uBKtEgz3M82ljt+lTg+xJaM4og+eyu83qLT87ZUSckn0wy7r31jflURWLO83TW6Ylf7lyXj3m5A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-react-jsx": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.8.3.tgz",
+ "integrity": "sha512-r0h+mUiyL595ikykci+fbwm9YzmuOrUBi0b+FDIKmi3fPQyFokWVEMJnRWHJPPQEjyFJyna9WZC6Viv6UHSv1g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-builder-react-jsx": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-syntax-jsx": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-react-jsx-self": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.8.3.tgz",
+ "integrity": "sha512-01OT7s5oa0XTLf2I8XGsL8+KqV9lx3EZV+jxn/L2LQ97CGKila2YMroTkCEIE0HV/FF7CMSRsIAybopdN9NTdg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-syntax-jsx": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-react-jsx-source": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.8.3.tgz",
+ "integrity": "sha512-PLMgdMGuVDtRS/SzjNEQYUT8f4z1xb2BAT54vM1X5efkVuYBf5WyGUMbpmARcfq3NaglIwz08UVQK4HHHbC6ag==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-syntax-jsx": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-regenerator": {
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz",
+ "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==",
+ "dev": true,
+ "requires": {
+ "regenerator-transform": "^0.14.2"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz",
+ "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==",
+ "dev": true,
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.4",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz",
+ "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==",
+ "dev": true
+ },
+ "regenerator-transform": {
+ "version": "0.14.2",
+ "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.2.tgz",
+ "integrity": "sha512-V4+lGplCM/ikqi5/mkkpJ06e9Bujq1NFmNLvsCs56zg3ZbzrnUzAtizZ24TXxtRX/W2jcdScwQCnbL0CICTFkQ==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.8.4",
+ "private": "^0.1.8"
+ }
+ }
+ }
+ },
+ "@babel/plugin-transform-reserved-words": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz",
+ "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-runtime": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.8.3.tgz",
+ "integrity": "sha512-/vqUt5Yh+cgPZXXjmaG9NT8aVfThKk7G4OqkVhrXqwsC5soMn/qTCxs36rZ2QFhpfTJcjw4SNDIZ4RUb8OL4jQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "resolve": "^1.8.1",
+ "semver": "^5.5.1"
+ }
+ },
+ "@babel/plugin-transform-shorthand-properties": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz",
+ "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz",
+ "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-sticky-regex": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz",
+ "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/helper-regex": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-template-literals": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz",
+ "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-typeof-symbol": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz",
+ "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-typescript": {
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.8.7.tgz",
+ "integrity": "sha512-7O0UsPQVNKqpHeHLpfvOG4uXmlw+MOxYvUv6Otc9uH5SYMIxvF6eBdjkWvC3f9G+VXe0RsNExyAQBeTRug/wqQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-syntax-typescript": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-unicode-regex": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz",
+ "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/preset-env": {
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.7.tgz",
+ "integrity": "sha512-BYftCVOdAYJk5ASsznKAUl53EMhfBbr8CJ1X+AJLfGPscQkwJFiaV/Wn9DPH/7fzm2v6iRYJKYHSqyynTGw0nw==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.8.6",
+ "@babel/helper-compilation-targets": "^7.8.7",
+ "@babel/helper-module-imports": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-proposal-async-generator-functions": "^7.8.3",
+ "@babel/plugin-proposal-dynamic-import": "^7.8.3",
+ "@babel/plugin-proposal-json-strings": "^7.8.3",
+ "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-proposal-object-rest-spread": "^7.8.3",
+ "@babel/plugin-proposal-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-proposal-optional-chaining": "^7.8.3",
+ "@babel/plugin-proposal-unicode-property-regex": "^7.8.3",
+ "@babel/plugin-syntax-async-generators": "^7.8.0",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.0",
+ "@babel/plugin-syntax-json-strings": "^7.8.0",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.0",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.0",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.0",
+ "@babel/plugin-syntax-top-level-await": "^7.8.3",
+ "@babel/plugin-transform-arrow-functions": "^7.8.3",
+ "@babel/plugin-transform-async-to-generator": "^7.8.3",
+ "@babel/plugin-transform-block-scoped-functions": "^7.8.3",
+ "@babel/plugin-transform-block-scoping": "^7.8.3",
+ "@babel/plugin-transform-classes": "^7.8.6",
+ "@babel/plugin-transform-computed-properties": "^7.8.3",
+ "@babel/plugin-transform-destructuring": "^7.8.3",
+ "@babel/plugin-transform-dotall-regex": "^7.8.3",
+ "@babel/plugin-transform-duplicate-keys": "^7.8.3",
+ "@babel/plugin-transform-exponentiation-operator": "^7.8.3",
+ "@babel/plugin-transform-for-of": "^7.8.6",
+ "@babel/plugin-transform-function-name": "^7.8.3",
+ "@babel/plugin-transform-literals": "^7.8.3",
+ "@babel/plugin-transform-member-expression-literals": "^7.8.3",
+ "@babel/plugin-transform-modules-amd": "^7.8.3",
+ "@babel/plugin-transform-modules-commonjs": "^7.8.3",
+ "@babel/plugin-transform-modules-systemjs": "^7.8.3",
+ "@babel/plugin-transform-modules-umd": "^7.8.3",
+ "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3",
+ "@babel/plugin-transform-new-target": "^7.8.3",
+ "@babel/plugin-transform-object-super": "^7.8.3",
+ "@babel/plugin-transform-parameters": "^7.8.7",
+ "@babel/plugin-transform-property-literals": "^7.8.3",
+ "@babel/plugin-transform-regenerator": "^7.8.7",
+ "@babel/plugin-transform-reserved-words": "^7.8.3",
+ "@babel/plugin-transform-shorthand-properties": "^7.8.3",
+ "@babel/plugin-transform-spread": "^7.8.3",
+ "@babel/plugin-transform-sticky-regex": "^7.8.3",
+ "@babel/plugin-transform-template-literals": "^7.8.3",
+ "@babel/plugin-transform-typeof-symbol": "^7.8.4",
+ "@babel/plugin-transform-unicode-regex": "^7.8.3",
+ "@babel/types": "^7.8.7",
+ "browserslist": "^4.8.5",
+ "core-js-compat": "^3.6.2",
+ "invariant": "^2.2.2",
+ "levenary": "^1.1.1",
+ "semver": "^5.5.0"
+ }
+ },
+ "@babel/preset-react": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.8.3.tgz",
+ "integrity": "sha512-9hx0CwZg92jGb7iHYQVgi0tOEHP/kM60CtWJQnmbATSPIQQ2xYzfoCI3EdqAhFBeeJwYMdWQuDUHMsuDbH9hyQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-transform-react-display-name": "^7.8.3",
+ "@babel/plugin-transform-react-jsx": "^7.8.3",
+ "@babel/plugin-transform-react-jsx-self": "^7.8.3",
+ "@babel/plugin-transform-react-jsx-source": "^7.8.3"
+ }
+ },
+ "@babel/preset-typescript": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.8.3.tgz",
+ "integrity": "sha512-qee5LgPGui9zQ0jR1TeU5/fP9L+ovoArklEqY12ek8P/wV5ZeM/VYSQYwICeoT6FfpJTekG9Ilay5PhwsOpMHA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-transform-typescript": "^7.8.3"
+ }
+ },
+ "@babel/runtime": {
+ "version": "7.0.0-beta.42",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0-beta.42.tgz",
+ "integrity": "sha512-iOGRzUoONLOtmCvjUsZv3mZzgCT6ljHQY5fr1qG1QIiJQwtM7zbPWGGpa3QWETq+UqwWyJnoi5XZDZRwZDFciQ==",
+ "requires": {
+ "core-js": "^2.5.3",
+ "regenerator-runtime": "^0.11.1"
+ }
+ },
+ "@babel/template": {
+ "version": "7.8.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz",
+ "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.8.3",
+ "@babel/parser": "^7.8.6",
+ "@babel/types": "^7.8.6"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.8.6",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz",
+ "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.8.3",
+ "@babel/generator": "^7.8.6",
+ "@babel/helper-function-name": "^7.8.3",
+ "@babel/helper-split-export-declaration": "^7.8.3",
+ "@babel/parser": "^7.8.6",
+ "@babel/types": "^7.8.6",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0",
+ "lodash": "^4.17.13"
+ },
+ "dependencies": {
+ "globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/types": {
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.7.tgz",
+ "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2",
+ "lodash": "^4.17.13",
+ "to-fast-properties": "^2.0.0"
+ },
+ "dependencies": {
+ "to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
+ "dev": true
+ }
+ }
+ },
+ "@cnakazawa/watch": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz",
+ "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==",
+ "dev": true,
+ "requires": {
+ "exec-sh": "^0.3.2",
+ "minimist": "^1.2.0"
+ }
+ },
+ "@csstools/convert-colors": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz",
+ "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==",
+ "dev": true
+ },
+ "@csstools/normalize.css": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz",
+ "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==",
+ "dev": true
+ },
+ "@hapi/address": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
+ "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==",
+ "dev": true
+ },
+ "@hapi/bourne": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz",
+ "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==",
+ "dev": true
+ },
+ "@hapi/hoek": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz",
+ "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==",
+ "dev": true
+ },
+ "@hapi/joi": {
+ "version": "15.1.1",
+ "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz",
+ "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==",
+ "dev": true,
+ "requires": {
+ "@hapi/address": "2.x.x",
+ "@hapi/bourne": "1.x.x",
+ "@hapi/hoek": "8.x.x",
+ "@hapi/topo": "3.x.x"
+ }
+ },
+ "@hapi/topo": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz",
+ "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==",
+ "dev": true,
+ "requires": {
+ "@hapi/hoek": "^8.3.0"
+ }
+ },
+ "@jest/console": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz",
+ "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==",
+ "dev": true,
+ "requires": {
+ "@jest/source-map": "^24.9.0",
+ "chalk": "^2.0.1",
+ "slash": "^2.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "@jest/core": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.9.0.tgz",
+ "integrity": "sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==",
+ "dev": true,
+ "requires": {
+ "@jest/console": "^24.7.1",
+ "@jest/reporters": "^24.9.0",
+ "@jest/test-result": "^24.9.0",
+ "@jest/transform": "^24.9.0",
+ "@jest/types": "^24.9.0",
+ "ansi-escapes": "^3.0.0",
+ "chalk": "^2.0.1",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.1.15",
+ "jest-changed-files": "^24.9.0",
+ "jest-config": "^24.9.0",
+ "jest-haste-map": "^24.9.0",
+ "jest-message-util": "^24.9.0",
+ "jest-regex-util": "^24.3.0",
+ "jest-resolve": "^24.9.0",
+ "jest-resolve-dependencies": "^24.9.0",
+ "jest-runner": "^24.9.0",
+ "jest-runtime": "^24.9.0",
+ "jest-snapshot": "^24.9.0",
+ "jest-util": "^24.9.0",
+ "jest-validate": "^24.9.0",
+ "jest-watcher": "^24.9.0",
+ "micromatch": "^3.1.10",
+ "p-each-series": "^1.0.0",
+ "realpath-native": "^1.1.0",
+ "rimraf": "^2.5.4",
+ "slash": "^2.0.0",
+ "strip-ansi": "^5.0.0"
+ },
+ "dependencies": {
+ "ansi-escapes": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+ "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "@jest/environment": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.9.0.tgz",
+ "integrity": "sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==",
+ "dev": true,
+ "requires": {
+ "@jest/fake-timers": "^24.9.0",
+ "@jest/transform": "^24.9.0",
+ "@jest/types": "^24.9.0",
+ "jest-mock": "^24.9.0"
+ }
+ },
+ "@jest/fake-timers": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz",
+ "integrity": "sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^24.9.0",
+ "jest-message-util": "^24.9.0",
+ "jest-mock": "^24.9.0"
+ }
+ },
+ "@jest/reporters": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.9.0.tgz",
+ "integrity": "sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==",
+ "dev": true,
+ "requires": {
+ "@jest/environment": "^24.9.0",
+ "@jest/test-result": "^24.9.0",
+ "@jest/transform": "^24.9.0",
+ "@jest/types": "^24.9.0",
+ "chalk": "^2.0.1",
+ "exit": "^0.1.2",
+ "glob": "^7.1.2",
+ "istanbul-lib-coverage": "^2.0.2",
+ "istanbul-lib-instrument": "^3.0.1",
+ "istanbul-lib-report": "^2.0.4",
+ "istanbul-lib-source-maps": "^3.0.1",
+ "istanbul-reports": "^2.2.6",
+ "jest-haste-map": "^24.9.0",
+ "jest-resolve": "^24.9.0",
+ "jest-runtime": "^24.9.0",
+ "jest-util": "^24.9.0",
+ "jest-worker": "^24.6.0",
+ "node-notifier": "^5.4.2",
+ "slash": "^2.0.0",
+ "source-map": "^0.6.0",
+ "string-length": "^2.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "@jest/source-map": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz",
+ "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==",
+ "dev": true,
+ "requires": {
+ "callsites": "^3.0.0",
+ "graceful-fs": "^4.1.15",
+ "source-map": "^0.6.0"
+ },
+ "dependencies": {
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "@jest/test-result": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz",
+ "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==",
+ "dev": true,
+ "requires": {
+ "@jest/console": "^24.9.0",
+ "@jest/types": "^24.9.0",
+ "@types/istanbul-lib-coverage": "^2.0.0"
+ }
+ },
+ "@jest/test-sequencer": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz",
+ "integrity": "sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==",
+ "dev": true,
+ "requires": {
+ "@jest/test-result": "^24.9.0",
+ "jest-haste-map": "^24.9.0",
+ "jest-runner": "^24.9.0",
+ "jest-runtime": "^24.9.0"
+ }
+ },
+ "@jest/transform": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz",
+ "integrity": "sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.1.0",
+ "@jest/types": "^24.9.0",
+ "babel-plugin-istanbul": "^5.1.0",
+ "chalk": "^2.0.1",
+ "convert-source-map": "^1.4.0",
+ "fast-json-stable-stringify": "^2.0.0",
+ "graceful-fs": "^4.1.15",
+ "jest-haste-map": "^24.9.0",
+ "jest-regex-util": "^24.9.0",
+ "jest-util": "^24.9.0",
+ "micromatch": "^3.1.10",
+ "pirates": "^4.0.1",
+ "realpath-native": "^1.1.0",
+ "slash": "^2.0.0",
+ "source-map": "^0.6.1",
+ "write-file-atomic": "2.4.1"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "@jest/types": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz",
+ "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^1.1.1",
+ "@types/yargs": "^13.0.0"
+ }
+ },
+ "@mrmlnc/readdir-enhanced": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
+ "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==",
+ "dev": true,
+ "requires": {
+ "call-me-maybe": "^1.0.1",
+ "glob-to-regexp": "^0.3.0"
+ }
+ },
+ "@nodelib/fs.stat": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
+ "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==",
+ "dev": true
+ },
+ "@svgr/babel-plugin-add-jsx-attribute": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz",
+ "integrity": "sha512-j7KnilGyZzYr/jhcrSYS3FGWMZVaqyCG0vzMCwzvei0coIkczuYMcniK07nI0aHJINciujjH11T72ICW5eL5Ig==",
+ "dev": true
+ },
+ "@svgr/babel-plugin-remove-jsx-attribute": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-4.2.0.tgz",
+ "integrity": "sha512-3XHLtJ+HbRCH4n28S7y/yZoEQnRpl0tvTZQsHqvaeNXPra+6vE5tbRliH3ox1yZYPCxrlqaJT/Mg+75GpDKlvQ==",
+ "dev": true
+ },
+ "@svgr/babel-plugin-remove-jsx-empty-expression": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-4.2.0.tgz",
+ "integrity": "sha512-yTr2iLdf6oEuUE9MsRdvt0NmdpMBAkgK8Bjhl6epb+eQWk6abBaX3d65UZ3E3FWaOwePyUgNyNCMVG61gGCQ7w==",
+ "dev": true
+ },
+ "@svgr/babel-plugin-replace-jsx-attribute-value": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-4.2.0.tgz",
+ "integrity": "sha512-U9m870Kqm0ko8beHawRXLGLvSi/ZMrl89gJ5BNcT452fAjtF2p4uRzXkdzvGJJJYBgx7BmqlDjBN/eCp5AAX2w==",
+ "dev": true
+ },
+ "@svgr/babel-plugin-svg-dynamic-title": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-4.3.3.tgz",
+ "integrity": "sha512-w3Be6xUNdwgParsvxkkeZb545VhXEwjGMwExMVBIdPQJeyMQHqm9Msnb2a1teHBqUYL66qtwfhNkbj1iarCG7w==",
+ "dev": true
+ },
+ "@svgr/babel-plugin-svg-em-dimensions": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-4.2.0.tgz",
+ "integrity": "sha512-C0Uy+BHolCHGOZ8Dnr1zXy/KgpBOkEUYY9kI/HseHVPeMbluaX3CijJr7D4C5uR8zrc1T64nnq/k63ydQuGt4w==",
+ "dev": true
+ },
+ "@svgr/babel-plugin-transform-react-native-svg": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-4.2.0.tgz",
+ "integrity": "sha512-7YvynOpZDpCOUoIVlaaOUU87J4Z6RdD6spYN4eUb5tfPoKGSF9OG2NuhgYnq4jSkAxcpMaXWPf1cePkzmqTPNw==",
+ "dev": true
+ },
+ "@svgr/babel-plugin-transform-svg-component": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-4.2.0.tgz",
+ "integrity": "sha512-hYfYuZhQPCBVotABsXKSCfel2slf/yvJY8heTVX1PCTaq/IgASq1IyxPPKJ0chWREEKewIU/JMSsIGBtK1KKxw==",
+ "dev": true
+ },
+ "@svgr/babel-preset": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-4.3.3.tgz",
+ "integrity": "sha512-6PG80tdz4eAlYUN3g5GZiUjg2FMcp+Wn6rtnz5WJG9ITGEF1pmFdzq02597Hn0OmnQuCVaBYQE1OVFAnwOl+0A==",
+ "dev": true,
+ "requires": {
+ "@svgr/babel-plugin-add-jsx-attribute": "^4.2.0",
+ "@svgr/babel-plugin-remove-jsx-attribute": "^4.2.0",
+ "@svgr/babel-plugin-remove-jsx-empty-expression": "^4.2.0",
+ "@svgr/babel-plugin-replace-jsx-attribute-value": "^4.2.0",
+ "@svgr/babel-plugin-svg-dynamic-title": "^4.3.3",
+ "@svgr/babel-plugin-svg-em-dimensions": "^4.2.0",
+ "@svgr/babel-plugin-transform-react-native-svg": "^4.2.0",
+ "@svgr/babel-plugin-transform-svg-component": "^4.2.0"
+ }
+ },
+ "@svgr/core": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@svgr/core/-/core-4.3.3.tgz",
+ "integrity": "sha512-qNuGF1QON1626UCaZamWt5yedpgOytvLj5BQZe2j1k1B8DUG4OyugZyfEwBeXozCUwhLEpsrgPrE+eCu4fY17w==",
+ "dev": true,
+ "requires": {
+ "@svgr/plugin-jsx": "^4.3.3",
+ "camelcase": "^5.3.1",
+ "cosmiconfig": "^5.2.1"
+ }
+ },
+ "@svgr/hast-util-to-babel-ast": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-4.3.2.tgz",
+ "integrity": "sha512-JioXclZGhFIDL3ddn4Kiq8qEqYM2PyDKV0aYno8+IXTLuYt6TOgHUbUAAFvqtb0Xn37NwP0BTHglejFoYr8RZg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.4.4"
+ }
+ },
+ "@svgr/plugin-jsx": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-4.3.3.tgz",
+ "integrity": "sha512-cLOCSpNWQnDB1/v+SUENHH7a0XY09bfuMKdq9+gYvtuwzC2rU4I0wKGFEp1i24holdQdwodCtDQdFtJiTCWc+w==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.4.5",
+ "@svgr/babel-preset": "^4.3.3",
+ "@svgr/hast-util-to-babel-ast": "^4.3.2",
+ "svg-parser": "^2.0.0"
+ }
+ },
+ "@svgr/plugin-svgo": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-4.3.1.tgz",
+ "integrity": "sha512-PrMtEDUWjX3Ea65JsVCwTIXuSqa3CG9px+DluF1/eo9mlDrgrtFE7NE/DjdhjJgSM9wenlVBzkzneSIUgfUI/w==",
+ "dev": true,
+ "requires": {
+ "cosmiconfig": "^5.2.1",
+ "merge-deep": "^3.0.2",
+ "svgo": "^1.2.2"
+ }
+ },
+ "@svgr/webpack": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-4.3.3.tgz",
+ "integrity": "sha512-bjnWolZ6KVsHhgyCoYRFmbd26p8XVbulCzSG53BDQqAr+JOAderYK7CuYrB3bDjHJuF6LJ7Wrr42+goLRV9qIg==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.4.5",
+ "@babel/plugin-transform-react-constant-elements": "^7.0.0",
+ "@babel/preset-env": "^7.4.5",
+ "@babel/preset-react": "^7.0.0",
+ "@svgr/core": "^4.3.3",
+ "@svgr/plugin-jsx": "^4.3.3",
+ "@svgr/plugin-svgo": "^4.3.1",
+ "loader-utils": "^1.2.3"
+ }
+ },
+ "@types/babel__core": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.6.tgz",
+ "integrity": "sha512-tTnhWszAqvXnhW7m5jQU9PomXSiKXk2sFxpahXvI20SZKu9ylPi8WtIxueZ6ehDWikPT0jeFujMj3X4ZHuf3Tg==",
+ "dev": true,
+ "requires": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "@types/babel__generator": {
+ "version": "7.6.1",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz",
+ "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@types/babel__template": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz",
+ "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==",
+ "dev": true,
+ "requires": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@types/babel__traverse": {
+ "version": "7.0.9",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.9.tgz",
+ "integrity": "sha512-jEFQ8L1tuvPjOI8lnpaf73oCJe+aoxL6ygqSy6c8LcW98zaC+4mzWuQIRCEvKeCOu+lbqdXcg4Uqmm1S8AP1tw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.3.0"
+ }
+ },
+ "@types/color-name": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
+ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
+ "dev": true
+ },
+ "@types/eslint-visitor-keys": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
+ "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==",
+ "dev": true
+ },
+ "@types/events": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
+ "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
+ "dev": true
+ },
+ "@types/glob": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
+ "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
+ "dev": true,
+ "requires": {
+ "@types/events": "*",
+ "@types/minimatch": "*",
+ "@types/node": "*"
+ }
+ },
+ "@types/istanbul-lib-coverage": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz",
+ "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==",
+ "dev": true
+ },
+ "@types/istanbul-lib-report": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
+ "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "@types/istanbul-reports": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz",
+ "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-coverage": "*",
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "@types/json-schema": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz",
+ "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==",
+ "dev": true
+ },
+ "@types/minimatch": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
+ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "13.7.7",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.7.tgz",
+ "integrity": "sha512-Uo4chgKbnPNlxQwoFmYIwctkQVkMMmsAoGGU4JKwLuvBefF0pCq4FybNSnfkfRCpC7ZW7kttcC/TrRtAJsvGtg==",
+ "dev": true
+ },
+ "@types/parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
+ "dev": true
+ },
+ "@types/q": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz",
+ "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==",
+ "dev": true
+ },
+ "@types/stack-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
+ "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==",
+ "dev": true
+ },
+ "@types/yargs": {
+ "version": "13.0.8",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.8.tgz",
+ "integrity": "sha512-XAvHLwG7UQ+8M4caKIH0ZozIOYay5fQkAgyIXegXT9jPtdIGdhga+sUEdAr1CiG46aB+c64xQEYyEzlwWVTNzA==",
+ "dev": true,
+ "requires": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "@types/yargs-parser": {
+ "version": "15.0.0",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz",
+ "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==",
+ "dev": true
+ },
+ "@typescript-eslint/eslint-plugin": {
+ "version": "2.22.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.22.0.tgz",
+ "integrity": "sha512-BvxRLaTDVQ3N+Qq8BivLiE9akQLAOUfxNHIEhedOcg8B2+jY8Rc4/D+iVprvuMX1AdezFYautuGDwr9QxqSxBQ==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/experimental-utils": "2.22.0",
+ "eslint-utils": "^1.4.3",
+ "functional-red-black-tree": "^1.0.1",
+ "regexpp": "^3.0.0",
+ "tsutils": "^3.17.1"
+ }
+ },
+ "@typescript-eslint/experimental-utils": {
+ "version": "2.22.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.22.0.tgz",
+ "integrity": "sha512-sJt1GYBe6yC0dWOQzXlp+tiuGglNhJC9eXZeC8GBVH98Zv9jtatccuhz0OF5kC/DwChqsNfghHx7OlIDQjNYAQ==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.3",
+ "@typescript-eslint/typescript-estree": "2.22.0",
+ "eslint-scope": "^5.0.0"
+ }
+ },
+ "@typescript-eslint/parser": {
+ "version": "2.22.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.22.0.tgz",
+ "integrity": "sha512-FaZKC1X+nvD7qMPqKFUYHz3H0TAioSVFGvG29f796Nc5tBluoqfHgLbSFKsh7mKjRoeTm8J9WX2Wo9EyZWjG7w==",
+ "dev": true,
+ "requires": {
+ "@types/eslint-visitor-keys": "^1.0.0",
+ "@typescript-eslint/experimental-utils": "2.22.0",
+ "@typescript-eslint/typescript-estree": "2.22.0",
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "@typescript-eslint/typescript-estree": {
+ "version": "2.22.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.22.0.tgz",
+ "integrity": "sha512-2HFZW2FQc4MhIBB8WhDm9lVFaBDy6h9jGrJ4V2Uzxe/ON29HCHBTj3GkgcsgMWfsl2U5as+pTOr30Nibaw7qRQ==",
+ "dev": true,
+ "requires": {
+ "debug": "^4.1.1",
+ "eslint-visitor-keys": "^1.1.0",
+ "glob": "^7.1.6",
+ "is-glob": "^4.0.1",
+ "lodash": "^4.17.15",
+ "semver": "^6.3.0",
+ "tsutils": "^3.17.1"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "@webassemblyjs/ast": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
+ "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/helper-module-context": "1.8.5",
+ "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+ "@webassemblyjs/wast-parser": "1.8.5"
+ }
+ },
+ "@webassemblyjs/floating-point-hex-parser": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz",
+ "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-api-error": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz",
+ "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-buffer": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz",
+ "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-code-frame": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz",
+ "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/wast-printer": "1.8.5"
+ }
+ },
+ "@webassemblyjs/helper-fsm": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz",
+ "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-module-context": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz",
+ "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "mamacro": "^0.0.3"
+ }
+ },
+ "@webassemblyjs/helper-wasm-bytecode": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz",
+ "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-wasm-section": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz",
+ "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/helper-buffer": "1.8.5",
+ "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+ "@webassemblyjs/wasm-gen": "1.8.5"
+ }
+ },
+ "@webassemblyjs/ieee754": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz",
+ "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==",
+ "dev": true,
+ "requires": {
+ "@xtuc/ieee754": "^1.2.0"
+ }
+ },
+ "@webassemblyjs/leb128": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz",
+ "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==",
+ "dev": true,
+ "requires": {
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "@webassemblyjs/utf8": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz",
+ "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==",
+ "dev": true
+ },
+ "@webassemblyjs/wasm-edit": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz",
+ "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/helper-buffer": "1.8.5",
+ "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+ "@webassemblyjs/helper-wasm-section": "1.8.5",
+ "@webassemblyjs/wasm-gen": "1.8.5",
+ "@webassemblyjs/wasm-opt": "1.8.5",
+ "@webassemblyjs/wasm-parser": "1.8.5",
+ "@webassemblyjs/wast-printer": "1.8.5"
+ }
+ },
+ "@webassemblyjs/wasm-gen": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz",
+ "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+ "@webassemblyjs/ieee754": "1.8.5",
+ "@webassemblyjs/leb128": "1.8.5",
+ "@webassemblyjs/utf8": "1.8.5"
+ }
+ },
+ "@webassemblyjs/wasm-opt": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz",
+ "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/helper-buffer": "1.8.5",
+ "@webassemblyjs/wasm-gen": "1.8.5",
+ "@webassemblyjs/wasm-parser": "1.8.5"
+ }
+ },
+ "@webassemblyjs/wasm-parser": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz",
+ "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/helper-api-error": "1.8.5",
+ "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+ "@webassemblyjs/ieee754": "1.8.5",
+ "@webassemblyjs/leb128": "1.8.5",
+ "@webassemblyjs/utf8": "1.8.5"
+ }
+ },
+ "@webassemblyjs/wast-parser": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz",
+ "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/floating-point-hex-parser": "1.8.5",
+ "@webassemblyjs/helper-api-error": "1.8.5",
+ "@webassemblyjs/helper-code-frame": "1.8.5",
+ "@webassemblyjs/helper-fsm": "1.8.5",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "@webassemblyjs/wast-printer": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz",
+ "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/wast-parser": "1.8.5",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "@xtuc/ieee754": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+ "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+ "dev": true
+ },
+ "@xtuc/long": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+ "dev": true
+ },
+ "abab": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz",
+ "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==",
+ "dev": true
+ },
+ "accepts": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+ "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+ "dev": true,
+ "requires": {
+ "mime-types": "~2.1.24",
+ "negotiator": "0.6.2"
+ }
+ },
+ "acorn": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
+ "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
+ "dev": true
+ },
+ "acorn-globals": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz",
+ "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==",
+ "dev": true,
+ "requires": {
+ "acorn": "^6.0.1",
+ "acorn-walk": "^6.0.1"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
+ "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
+ "dev": true
+ }
+ }
+ },
+ "acorn-jsx": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
+ "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==",
+ "dev": true
+ },
+ "acorn-walk": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
+ "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==",
+ "dev": true
+ },
+ "address": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz",
+ "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==",
+ "dev": true
+ },
+ "adjust-sourcemap-loader": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-2.0.0.tgz",
+ "integrity": "sha512-4hFsTsn58+YjrU9qKzML2JSSDqKvN8mUGQ0nNIrfPi8hmIONT4L3uUaT6MKdMsZ9AjsU6D2xDkZxCkbQPxChrA==",
+ "dev": true,
+ "requires": {
+ "assert": "1.4.1",
+ "camelcase": "5.0.0",
+ "loader-utils": "1.2.3",
+ "object-path": "0.11.4",
+ "regex-parser": "2.2.10"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz",
+ "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==",
+ "dev": true
+ },
+ "emojis-list": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
+ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
+ "dev": true
+ },
+ "json5": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ },
+ "loader-utils": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
+ "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
+ "dev": true,
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^2.0.0",
+ "json5": "^1.0.1"
+ }
+ }
+ }
+ },
+ "aggregate-error": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz",
+ "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==",
+ "dev": true,
+ "requires": {
+ "clean-stack": "^2.0.0",
+ "indent-string": "^4.0.0"
+ }
+ },
+ "airbnb-prop-types": {
+ "version": "2.13.2",
+ "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.13.2.tgz",
+ "integrity": "sha512-2FN6DlHr6JCSxPPi25EnqGaXC4OC3/B3k1lCd6MMYrZ51/Gf/1qDfaR+JElzWa+Tl7cY2aYOlsYJGFeQyVHIeQ==",
+ "requires": {
+ "array.prototype.find": "^2.0.4",
+ "function.prototype.name": "^1.1.0",
+ "has": "^1.0.3",
+ "is-regex": "^1.0.4",
+ "object-is": "^1.0.1",
+ "object.assign": "^4.1.0",
+ "object.entries": "^1.1.0",
+ "prop-types": "^15.7.2",
+ "prop-types-exact": "^1.2.0",
+ "react-is": "^16.8.6"
+ }
+ },
+ "ajv": {
+ "version": "6.12.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
+ "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ajv-errors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
+ "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==",
+ "dev": true
+ },
+ "ajv-keywords": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz",
+ "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==",
+ "dev": true
+ },
+ "alphanum-sort": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz",
+ "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=",
+ "dev": true
+ },
+ "ansi-colors": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz",
+ "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==",
+ "dev": true
+ },
+ "ansi-escapes": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz",
+ "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.11.0"
+ },
+ "dependencies": {
+ "type-fest": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz",
+ "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==",
+ "dev": true
+ }
+ }
+ },
+ "ansi-html": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",
+ "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true
+ },
+ "anymatch": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
+ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
+ "dev": true,
+ "requires": {
+ "micromatch": "^3.1.4",
+ "normalize-path": "^2.1.1"
+ }
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+ "dev": true
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "aria-query": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz",
+ "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=",
+ "dev": true,
+ "requires": {
+ "ast-types-flow": "0.0.7",
+ "commander": "^2.11.0"
+ }
+ },
+ "arity-n": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz",
+ "integrity": "sha1-2edrEXM+CFacCEeuezmyhgswt0U=",
+ "dev": true
+ },
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+ "dev": true
+ },
+ "arr-flatten": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+ "dev": true
+ },
+ "arr-union": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
+ "dev": true
+ },
+ "array-equal": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
+ "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
+ "dev": true
+ },
+ "array-flatten": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
+ "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==",
+ "dev": true
+ },
+ "array-includes": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz",
+ "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.0",
+ "is-string": "^1.0.5"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.17.4",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz",
+ "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1",
+ "is-callable": "^1.1.5",
+ "is-regex": "^1.0.5",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimleft": "^2.1.1",
+ "string.prototype.trimright": "^2.1.1"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
+ "requires": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ }
+ },
+ "has-symbols": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
+ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
+ "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
+ "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ }
+ }
+ },
+ "array-union": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+ "dev": true,
+ "requires": {
+ "array-uniq": "^1.0.1"
+ }
+ },
+ "array-uniq": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+ "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
+ "dev": true
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+ "dev": true
+ },
+ "array.prototype.find": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.0.4.tgz",
+ "integrity": "sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=",
+ "requires": {
+ "define-properties": "^1.1.2",
+ "es-abstract": "^1.7.0"
+ }
+ },
+ "array.prototype.flat": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz",
+ "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.0-next.1"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.17.4",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz",
+ "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1",
+ "is-callable": "^1.1.5",
+ "is-regex": "^1.0.5",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimleft": "^2.1.1",
+ "string.prototype.trimright": "^2.1.1"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
+ "requires": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ }
+ },
+ "has-symbols": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
+ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
+ "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
+ "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ }
+ }
+ },
+ "arrify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+ "dev": true
+ },
+ "asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
+ },
+ "asn1": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+ "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+ "dev": true,
+ "requires": {
+ "safer-buffer": "~2.1.0"
+ }
+ },
+ "asn1.js": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
+ "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "assert": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz",
+ "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=",
+ "dev": true,
+ "requires": {
+ "util": "0.10.3"
+ }
+ },
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+ "dev": true
+ },
+ "assign-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
+ "dev": true
+ },
+ "ast-types-flow": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
+ "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
+ "dev": true
+ },
+ "astral-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
+ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+ "dev": true
+ },
+ "async": {
+ "version": "2.6.4",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
+ "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.14"
+ }
+ },
+ "async-each": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
+ "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
+ "dev": true
+ },
+ "async-limiter": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
+ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
+ "dev": true
+ },
+ "asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+ "dev": true
+ },
+ "atob": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+ "dev": true
+ },
+ "autoprefixer": {
+ "version": "9.7.4",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.4.tgz",
+ "integrity": "sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.8.3",
+ "caniuse-lite": "^1.0.30001020",
+ "chalk": "^2.4.2",
+ "normalize-range": "^0.1.2",
+ "num2fraction": "^1.2.2",
+ "postcss": "^7.0.26",
+ "postcss-value-parser": "^4.0.2"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "aws-sign2": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
+ "dev": true
+ },
+ "aws4": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
+ "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==",
+ "dev": true
+ },
+ "axios": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
+ "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==",
+ "requires": {
+ "follow-redirects": "1.5.10",
+ "is-buffer": "^2.0.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "follow-redirects": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
+ "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
+ "requires": {
+ "debug": "=3.1.0"
+ }
+ },
+ "is-buffer": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
+ "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw=="
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
+ "axobject-query": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.2.tgz",
+ "integrity": "sha512-ICt34ZmrVt8UQnvPl6TVyDTkmhXmAyAT4Jh5ugfGUX4MOrZ+U/ZY6/sdylRw3qGNr9Ub5AJsaHeDMzNLehRdOQ==",
+ "dev": true
+ },
+ "babel-code-frame": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
+ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
+ "dev": true,
+ "requires": {
+ "chalk": "^1.1.3",
+ "esutils": "^2.0.2",
+ "js-tokens": "^3.0.2"
+ },
+ "dependencies": {
+ "js-tokens": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+ "dev": true
+ }
+ }
+ },
+ "babel-eslint": {
+ "version": "10.0.3",
+ "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz",
+ "integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@babel/parser": "^7.0.0",
+ "@babel/traverse": "^7.0.0",
+ "@babel/types": "^7.0.0",
+ "eslint-visitor-keys": "^1.0.0",
+ "resolve": "^1.12.0"
+ }
+ },
+ "babel-extract-comments": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/babel-extract-comments/-/babel-extract-comments-1.0.0.tgz",
+ "integrity": "sha512-qWWzi4TlddohA91bFwgt6zO/J0X+io7Qp184Fw0m2JYRSTZnJbFR8+07KmzudHCZgOiKRCrjhylwv9Xd8gfhVQ==",
+ "dev": true,
+ "requires": {
+ "babylon": "^6.18.0"
+ }
+ },
+ "babel-helper-bindify-decorators": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz",
+ "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-traverse": "^6.24.1",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helper-builder-binary-assignment-operator-visitor": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz",
+ "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=",
+ "dev": true,
+ "requires": {
+ "babel-helper-explode-assignable-expression": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helper-builder-react-jsx": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz",
+ "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "esutils": "^2.0.2"
+ }
+ },
+ "babel-helper-call-delegate": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz",
+ "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=",
+ "dev": true,
+ "requires": {
+ "babel-helper-hoist-variables": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "babel-traverse": "^6.24.1",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helper-define-map": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz",
+ "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=",
+ "dev": true,
+ "requires": {
+ "babel-helper-function-name": "^6.24.1",
+ "babel-runtime": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "lodash": "^4.17.4"
+ }
+ },
+ "babel-helper-explode-assignable-expression": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz",
+ "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-traverse": "^6.24.1",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helper-explode-class": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz",
+ "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=",
+ "dev": true,
+ "requires": {
+ "babel-helper-bindify-decorators": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "babel-traverse": "^6.24.1",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helper-function-name": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz",
+ "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=",
+ "dev": true,
+ "requires": {
+ "babel-helper-get-function-arity": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1",
+ "babel-traverse": "^6.24.1",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helper-get-function-arity": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz",
+ "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helper-hoist-variables": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz",
+ "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helper-optimise-call-expression": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz",
+ "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helper-regex": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz",
+ "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "lodash": "^4.17.4"
+ }
+ },
+ "babel-helper-remap-async-to-generator": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz",
+ "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=",
+ "dev": true,
+ "requires": {
+ "babel-helper-function-name": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1",
+ "babel-traverse": "^6.24.1",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helper-replace-supers": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz",
+ "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=",
+ "dev": true,
+ "requires": {
+ "babel-helper-optimise-call-expression": "^6.24.1",
+ "babel-messages": "^6.23.0",
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1",
+ "babel-traverse": "^6.24.1",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-jest": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz",
+ "integrity": "sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==",
+ "dev": true,
+ "requires": {
+ "@jest/transform": "^24.9.0",
+ "@jest/types": "^24.9.0",
+ "@types/babel__core": "^7.1.0",
+ "babel-plugin-istanbul": "^5.1.0",
+ "babel-preset-jest": "^24.9.0",
+ "chalk": "^2.4.2",
+ "slash": "^2.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "babel-loader": {
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz",
+ "integrity": "sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==",
+ "dev": true,
+ "requires": {
+ "find-cache-dir": "^2.0.0",
+ "loader-utils": "^1.0.2",
+ "mkdirp": "^0.5.1",
+ "pify": "^4.0.1"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "dev": true
+ }
+ }
+ },
+ "babel-messages": {
+ "version": "6.23.0",
+ "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
+ "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-check-es2015-constants": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz",
+ "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-dynamic-import-node": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz",
+ "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==",
+ "dev": true,
+ "requires": {
+ "object.assign": "^4.1.0"
+ }
+ },
+ "babel-plugin-istanbul": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz",
+ "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "find-up": "^3.0.0",
+ "istanbul-lib-instrument": "^3.3.0",
+ "test-exclude": "^5.2.3"
+ }
+ },
+ "babel-plugin-jest-hoist": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz",
+ "integrity": "sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==",
+ "dev": true,
+ "requires": {
+ "@types/babel__traverse": "^7.0.6"
+ }
+ },
+ "babel-plugin-macros": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
+ "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.7.2",
+ "cosmiconfig": "^6.0.0",
+ "resolve": "^1.12.0"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz",
+ "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==",
+ "dev": true,
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "cosmiconfig": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
+ "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
+ "dev": true,
+ "requires": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.1.0",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.7.2"
+ }
+ },
+ "import-fresh": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
+ "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
+ "dev": true,
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "parse-json": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz",
+ "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-better-errors": "^1.0.1",
+ "lines-and-columns": "^1.1.6"
+ }
+ },
+ "path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true
+ },
+ "regenerator-runtime": {
+ "version": "0.13.4",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz",
+ "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==",
+ "dev": true
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true
+ }
+ }
+ },
+ "babel-plugin-named-asset-import": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.6.tgz",
+ "integrity": "sha512-1aGDUfL1qOOIoqk9QKGIo2lANk+C7ko/fqH0uIyC71x3PEGz0uVP8ISgfEsFuG+FKmjHTvFK/nNM8dowpmUxLA==",
+ "dev": true
+ },
+ "babel-plugin-syntax-async-functions": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
+ "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=",
+ "dev": true
+ },
+ "babel-plugin-syntax-async-generators": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz",
+ "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=",
+ "dev": true
+ },
+ "babel-plugin-syntax-class-properties": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz",
+ "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=",
+ "dev": true
+ },
+ "babel-plugin-syntax-decorators": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz",
+ "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=",
+ "dev": true
+ },
+ "babel-plugin-syntax-dynamic-import": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz",
+ "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=",
+ "dev": true
+ },
+ "babel-plugin-syntax-exponentiation-operator": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz",
+ "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=",
+ "dev": true
+ },
+ "babel-plugin-syntax-flow": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz",
+ "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=",
+ "dev": true
+ },
+ "babel-plugin-syntax-jsx": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
+ "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=",
+ "dev": true
+ },
+ "babel-plugin-syntax-object-rest-spread": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
+ "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=",
+ "dev": true
+ },
+ "babel-plugin-syntax-trailing-function-commas": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz",
+ "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=",
+ "dev": true
+ },
+ "babel-plugin-transform-async-generator-functions": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz",
+ "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=",
+ "dev": true,
+ "requires": {
+ "babel-helper-remap-async-to-generator": "^6.24.1",
+ "babel-plugin-syntax-async-generators": "^6.5.0",
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-async-to-generator": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz",
+ "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=",
+ "dev": true,
+ "requires": {
+ "babel-helper-remap-async-to-generator": "^6.24.1",
+ "babel-plugin-syntax-async-functions": "^6.8.0",
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-class-properties": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz",
+ "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=",
+ "dev": true,
+ "requires": {
+ "babel-helper-function-name": "^6.24.1",
+ "babel-plugin-syntax-class-properties": "^6.8.0",
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-decorators": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz",
+ "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=",
+ "dev": true,
+ "requires": {
+ "babel-helper-explode-class": "^6.24.1",
+ "babel-plugin-syntax-decorators": "^6.13.0",
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-arrow-functions": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz",
+ "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-block-scoped-functions": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz",
+ "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-block-scoping": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz",
+ "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "babel-template": "^6.26.0",
+ "babel-traverse": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "lodash": "^4.17.4"
+ }
+ },
+ "babel-plugin-transform-es2015-classes": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz",
+ "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=",
+ "dev": true,
+ "requires": {
+ "babel-helper-define-map": "^6.24.1",
+ "babel-helper-function-name": "^6.24.1",
+ "babel-helper-optimise-call-expression": "^6.24.1",
+ "babel-helper-replace-supers": "^6.24.1",
+ "babel-messages": "^6.23.0",
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1",
+ "babel-traverse": "^6.24.1",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-computed-properties": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz",
+ "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-destructuring": {
+ "version": "6.23.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz",
+ "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-duplicate-keys": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz",
+ "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-for-of": {
+ "version": "6.23.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz",
+ "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-function-name": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz",
+ "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=",
+ "dev": true,
+ "requires": {
+ "babel-helper-function-name": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-literals": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz",
+ "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-amd": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz",
+ "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=",
+ "dev": true,
+ "requires": {
+ "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-commonjs": {
+ "version": "6.26.2",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz",
+ "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==",
+ "dev": true,
+ "requires": {
+ "babel-plugin-transform-strict-mode": "^6.24.1",
+ "babel-runtime": "^6.26.0",
+ "babel-template": "^6.26.0",
+ "babel-types": "^6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-systemjs": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz",
+ "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=",
+ "dev": true,
+ "requires": {
+ "babel-helper-hoist-variables": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-umd": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz",
+ "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=",
+ "dev": true,
+ "requires": {
+ "babel-plugin-transform-es2015-modules-amd": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-object-super": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz",
+ "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=",
+ "dev": true,
+ "requires": {
+ "babel-helper-replace-supers": "^6.24.1",
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-parameters": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz",
+ "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=",
+ "dev": true,
+ "requires": {
+ "babel-helper-call-delegate": "^6.24.1",
+ "babel-helper-get-function-arity": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1",
+ "babel-traverse": "^6.24.1",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-shorthand-properties": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz",
+ "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-spread": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz",
+ "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-sticky-regex": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz",
+ "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=",
+ "dev": true,
+ "requires": {
+ "babel-helper-regex": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-template-literals": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz",
+ "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-typeof-symbol": {
+ "version": "6.23.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz",
+ "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-unicode-regex": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz",
+ "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=",
+ "dev": true,
+ "requires": {
+ "babel-helper-regex": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "regexpu-core": "^2.0.0"
+ }
+ },
+ "babel-plugin-transform-exponentiation-operator": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz",
+ "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=",
+ "dev": true,
+ "requires": {
+ "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1",
+ "babel-plugin-syntax-exponentiation-operator": "^6.8.0",
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-flow-strip-types": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz",
+ "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=",
+ "dev": true,
+ "requires": {
+ "babel-plugin-syntax-flow": "^6.18.0",
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-object-rest-spread": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz",
+ "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=",
+ "dev": true,
+ "requires": {
+ "babel-plugin-syntax-object-rest-spread": "^6.8.0",
+ "babel-runtime": "^6.26.0"
+ }
+ },
+ "babel-plugin-transform-react-display-name": {
+ "version": "6.25.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz",
+ "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-react-jsx": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz",
+ "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=",
+ "dev": true,
+ "requires": {
+ "babel-helper-builder-react-jsx": "^6.24.1",
+ "babel-plugin-syntax-jsx": "^6.8.0",
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-react-jsx-self": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz",
+ "integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=",
+ "dev": true,
+ "requires": {
+ "babel-plugin-syntax-jsx": "^6.8.0",
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-react-jsx-source": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz",
+ "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=",
+ "dev": true,
+ "requires": {
+ "babel-plugin-syntax-jsx": "^6.8.0",
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-react-remove-prop-types": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz",
+ "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==",
+ "dev": true
+ },
+ "babel-plugin-transform-regenerator": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz",
+ "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=",
+ "dev": true,
+ "requires": {
+ "regenerator-transform": "^0.10.0"
+ }
+ },
+ "babel-plugin-transform-strict-mode": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz",
+ "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-polyfill": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
+ "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "core-js": "^2.5.0",
+ "regenerator-runtime": "^0.10.5"
+ },
+ "dependencies": {
+ "regenerator-runtime": {
+ "version": "0.10.5",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
+ "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg="
+ }
+ }
+ },
+ "babel-preset-es2015": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz",
+ "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=",
+ "dev": true,
+ "requires": {
+ "babel-plugin-check-es2015-constants": "^6.22.0",
+ "babel-plugin-transform-es2015-arrow-functions": "^6.22.0",
+ "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0",
+ "babel-plugin-transform-es2015-block-scoping": "^6.24.1",
+ "babel-plugin-transform-es2015-classes": "^6.24.1",
+ "babel-plugin-transform-es2015-computed-properties": "^6.24.1",
+ "babel-plugin-transform-es2015-destructuring": "^6.22.0",
+ "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1",
+ "babel-plugin-transform-es2015-for-of": "^6.22.0",
+ "babel-plugin-transform-es2015-function-name": "^6.24.1",
+ "babel-plugin-transform-es2015-literals": "^6.22.0",
+ "babel-plugin-transform-es2015-modules-amd": "^6.24.1",
+ "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1",
+ "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1",
+ "babel-plugin-transform-es2015-modules-umd": "^6.24.1",
+ "babel-plugin-transform-es2015-object-super": "^6.24.1",
+ "babel-plugin-transform-es2015-parameters": "^6.24.1",
+ "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1",
+ "babel-plugin-transform-es2015-spread": "^6.22.0",
+ "babel-plugin-transform-es2015-sticky-regex": "^6.24.1",
+ "babel-plugin-transform-es2015-template-literals": "^6.22.0",
+ "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0",
+ "babel-plugin-transform-es2015-unicode-regex": "^6.24.1",
+ "babel-plugin-transform-regenerator": "^6.24.1"
+ }
+ },
+ "babel-preset-flow": {
+ "version": "6.23.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz",
+ "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=",
+ "dev": true,
+ "requires": {
+ "babel-plugin-transform-flow-strip-types": "^6.22.0"
+ }
+ },
+ "babel-preset-jest": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz",
+ "integrity": "sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==",
+ "dev": true,
+ "requires": {
+ "@babel/plugin-syntax-object-rest-spread": "^7.0.0",
+ "babel-plugin-jest-hoist": "^24.9.0"
+ }
+ },
+ "babel-preset-react": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz",
+ "integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=",
+ "dev": true,
+ "requires": {
+ "babel-plugin-syntax-jsx": "^6.3.13",
+ "babel-plugin-transform-react-display-name": "^6.23.0",
+ "babel-plugin-transform-react-jsx": "^6.24.1",
+ "babel-plugin-transform-react-jsx-self": "^6.22.0",
+ "babel-plugin-transform-react-jsx-source": "^6.22.0",
+ "babel-preset-flow": "^6.23.0"
+ }
+ },
+ "babel-preset-react-app": {
+ "version": "9.1.1",
+ "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-9.1.1.tgz",
+ "integrity": "sha512-YkWP2UwY//TLltNlEBRngDOrYhvSLb+CA330G7T9M5UhGEMWe+JK/8IXJc5p2fDTSfSiETf+PY0+PYXFMix81Q==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "7.8.4",
+ "@babel/plugin-proposal-class-properties": "7.8.3",
+ "@babel/plugin-proposal-decorators": "7.8.3",
+ "@babel/plugin-proposal-numeric-separator": "7.8.3",
+ "@babel/plugin-transform-flow-strip-types": "7.8.3",
+ "@babel/plugin-transform-react-display-name": "7.8.3",
+ "@babel/plugin-transform-runtime": "7.8.3",
+ "@babel/preset-env": "7.8.4",
+ "@babel/preset-react": "7.8.3",
+ "@babel/preset-typescript": "7.8.3",
+ "@babel/runtime": "7.8.4",
+ "babel-plugin-macros": "2.8.0",
+ "babel-plugin-transform-react-remove-prop-types": "0.4.24"
+ },
+ "dependencies": {
+ "@babel/preset-env": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.4.tgz",
+ "integrity": "sha512-HihCgpr45AnSOHRbS5cWNTINs0TwaR8BS8xIIH+QwiW8cKL0llV91njQMpeMReEPVs+1Ao0x3RLEBLtt1hOq4w==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.8.4",
+ "@babel/helper-compilation-targets": "^7.8.4",
+ "@babel/helper-module-imports": "^7.8.3",
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-proposal-async-generator-functions": "^7.8.3",
+ "@babel/plugin-proposal-dynamic-import": "^7.8.3",
+ "@babel/plugin-proposal-json-strings": "^7.8.3",
+ "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-proposal-object-rest-spread": "^7.8.3",
+ "@babel/plugin-proposal-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-proposal-optional-chaining": "^7.8.3",
+ "@babel/plugin-proposal-unicode-property-regex": "^7.8.3",
+ "@babel/plugin-syntax-async-generators": "^7.8.0",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.0",
+ "@babel/plugin-syntax-json-strings": "^7.8.0",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.0",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.0",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.0",
+ "@babel/plugin-syntax-top-level-await": "^7.8.3",
+ "@babel/plugin-transform-arrow-functions": "^7.8.3",
+ "@babel/plugin-transform-async-to-generator": "^7.8.3",
+ "@babel/plugin-transform-block-scoped-functions": "^7.8.3",
+ "@babel/plugin-transform-block-scoping": "^7.8.3",
+ "@babel/plugin-transform-classes": "^7.8.3",
+ "@babel/plugin-transform-computed-properties": "^7.8.3",
+ "@babel/plugin-transform-destructuring": "^7.8.3",
+ "@babel/plugin-transform-dotall-regex": "^7.8.3",
+ "@babel/plugin-transform-duplicate-keys": "^7.8.3",
+ "@babel/plugin-transform-exponentiation-operator": "^7.8.3",
+ "@babel/plugin-transform-for-of": "^7.8.4",
+ "@babel/plugin-transform-function-name": "^7.8.3",
+ "@babel/plugin-transform-literals": "^7.8.3",
+ "@babel/plugin-transform-member-expression-literals": "^7.8.3",
+ "@babel/plugin-transform-modules-amd": "^7.8.3",
+ "@babel/plugin-transform-modules-commonjs": "^7.8.3",
+ "@babel/plugin-transform-modules-systemjs": "^7.8.3",
+ "@babel/plugin-transform-modules-umd": "^7.8.3",
+ "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3",
+ "@babel/plugin-transform-new-target": "^7.8.3",
+ "@babel/plugin-transform-object-super": "^7.8.3",
+ "@babel/plugin-transform-parameters": "^7.8.4",
+ "@babel/plugin-transform-property-literals": "^7.8.3",
+ "@babel/plugin-transform-regenerator": "^7.8.3",
+ "@babel/plugin-transform-reserved-words": "^7.8.3",
+ "@babel/plugin-transform-shorthand-properties": "^7.8.3",
+ "@babel/plugin-transform-spread": "^7.8.3",
+ "@babel/plugin-transform-sticky-regex": "^7.8.3",
+ "@babel/plugin-transform-template-literals": "^7.8.3",
+ "@babel/plugin-transform-typeof-symbol": "^7.8.4",
+ "@babel/plugin-transform-unicode-regex": "^7.8.3",
+ "@babel/types": "^7.8.3",
+ "browserslist": "^4.8.5",
+ "core-js-compat": "^3.6.2",
+ "invariant": "^2.2.2",
+ "levenary": "^1.1.1",
+ "semver": "^5.5.0"
+ }
+ },
+ "@babel/runtime": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz",
+ "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==",
+ "dev": true,
+ "requires": {
+ "regenerator-runtime": "^0.13.2"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.3",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
+ "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==",
+ "dev": true
+ }
+ }
+ },
+ "babel-preset-stage-2": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz",
+ "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=",
+ "dev": true,
+ "requires": {
+ "babel-plugin-syntax-dynamic-import": "^6.18.0",
+ "babel-plugin-transform-class-properties": "^6.24.1",
+ "babel-plugin-transform-decorators": "^6.24.1",
+ "babel-preset-stage-3": "^6.24.1"
+ }
+ },
+ "babel-preset-stage-3": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz",
+ "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=",
+ "dev": true,
+ "requires": {
+ "babel-plugin-syntax-trailing-function-commas": "^6.22.0",
+ "babel-plugin-transform-async-generator-functions": "^6.24.1",
+ "babel-plugin-transform-async-to-generator": "^6.24.1",
+ "babel-plugin-transform-exponentiation-operator": "^6.24.1",
+ "babel-plugin-transform-object-rest-spread": "^6.22.0"
+ }
+ },
+ "babel-runtime": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
+ "requires": {
+ "core-js": "^2.4.0",
+ "regenerator-runtime": "^0.11.0"
+ }
+ },
+ "babel-template": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz",
+ "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "babel-traverse": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "babylon": "^6.18.0",
+ "lodash": "^4.17.4"
+ }
+ },
+ "babel-traverse": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz",
+ "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=",
+ "dev": true,
+ "requires": {
+ "babel-code-frame": "^6.26.0",
+ "babel-messages": "^6.23.0",
+ "babel-runtime": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "babylon": "^6.18.0",
+ "debug": "^2.6.8",
+ "globals": "^9.18.0",
+ "invariant": "^2.2.2",
+ "lodash": "^4.17.4"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "babel-types": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
+ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "esutils": "^2.0.2",
+ "lodash": "^4.17.4",
+ "to-fast-properties": "^1.0.3"
+ }
+ },
+ "babylon": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
+ "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
+ "dev": true
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
+ "base": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+ "dev": true,
+ "requires": {
+ "cache-base": "^1.0.1",
+ "class-utils": "^0.3.5",
+ "component-emitter": "^1.2.1",
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.1",
+ "mixin-deep": "^1.2.0",
+ "pascalcase": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true
+ }
+ }
+ },
+ "base64-js": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
+ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
+ "dev": true
+ },
+ "batch": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
+ "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
+ "dev": true
+ },
+ "bcrypt-pbkdf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+ "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+ "dev": true,
+ "requires": {
+ "tweetnacl": "^0.14.3"
+ }
+ },
+ "big.js": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+ "dev": true
+ },
+ "binary-extensions": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
+ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
+ "dev": true
+ },
+ "bindings": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "file-uri-to-path": "1.0.0"
+ }
+ },
+ "bluebird": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+ "dev": true
+ },
+ "bn.js": {
+ "version": "4.11.8",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
+ "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+ "dev": true
+ },
+ "body-parser": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+ "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+ "dev": true,
+ "requires": {
+ "bytes": "3.1.0",
+ "content-type": "~1.0.4",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
+ "on-finished": "~2.3.0",
+ "qs": "6.7.0",
+ "raw-body": "2.4.0",
+ "type-is": "~1.6.17"
+ },
+ "dependencies": {
+ "bytes": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
+ "dev": true
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "qs": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
+ "dev": true
+ }
+ }
+ },
+ "bonjour": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz",
+ "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=",
+ "dev": true,
+ "requires": {
+ "array-flatten": "^2.1.0",
+ "deep-equal": "^1.0.1",
+ "dns-equal": "^1.0.0",
+ "dns-txt": "^2.0.2",
+ "multicast-dns": "^6.0.1",
+ "multicast-dns-service-types": "^1.1.0"
+ }
+ },
+ "boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
+ "dev": true
+ },
+ "bowser": {
+ "version": "1.9.4",
+ "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz",
+ "integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ=="
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "brorand": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+ "dev": true
+ },
+ "browser-process-hrtime": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz",
+ "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==",
+ "dev": true
+ },
+ "browser-resolve": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz",
+ "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==",
+ "dev": true,
+ "requires": {
+ "resolve": "1.1.7"
+ },
+ "dependencies": {
+ "resolve": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
+ "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
+ "dev": true
+ }
+ }
+ },
+ "browserify-aes": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+ "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+ "dev": true,
+ "requires": {
+ "buffer-xor": "^1.0.3",
+ "cipher-base": "^1.0.0",
+ "create-hash": "^1.1.0",
+ "evp_bytestokey": "^1.0.3",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "browserify-cipher": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
+ "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
+ "dev": true,
+ "requires": {
+ "browserify-aes": "^1.0.4",
+ "browserify-des": "^1.0.0",
+ "evp_bytestokey": "^1.0.0"
+ }
+ },
+ "browserify-des": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
+ "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.1",
+ "des.js": "^1.0.0",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "browserify-rsa": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+ "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "randombytes": "^2.0.1"
+ }
+ },
+ "browserify-sign": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
+ "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.1",
+ "browserify-rsa": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "create-hmac": "^1.1.2",
+ "elliptic": "^6.0.0",
+ "inherits": "^2.0.1",
+ "parse-asn1": "^5.0.0"
+ }
+ },
+ "browserify-zlib": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+ "dev": true,
+ "requires": {
+ "pako": "~1.0.5"
+ }
+ },
+ "browserslist": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.9.1.tgz",
+ "integrity": "sha512-Q0DnKq20End3raFulq6Vfp1ecB9fh8yUNV55s8sekaDDeqBaCtWlRHCUdaWyUeSSBJM7IbM6HcsyaeYqgeDhnw==",
+ "dev": true,
+ "requires": {
+ "caniuse-lite": "^1.0.30001030",
+ "electron-to-chromium": "^1.3.363",
+ "node-releases": "^1.1.50"
+ }
+ },
+ "bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "requires": {
+ "node-int64": "^0.4.0"
+ }
+ },
+ "buffer": {
+ "version": "4.9.2",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
+ "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
+ "dev": true,
+ "requires": {
+ "base64-js": "^1.0.2",
+ "ieee754": "^1.1.4",
+ "isarray": "^1.0.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ }
+ }
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+ "dev": true
+ },
+ "buffer-indexof": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz",
+ "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==",
+ "dev": true
+ },
+ "buffer-xor": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+ "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+ "dev": true
+ },
+ "builtin-status-codes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
+ "dev": true
+ },
+ "bytes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
+ "dev": true
+ },
+ "cacache": {
+ "version": "13.0.1",
+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz",
+ "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==",
+ "dev": true,
+ "requires": {
+ "chownr": "^1.1.2",
+ "figgy-pudding": "^3.5.1",
+ "fs-minipass": "^2.0.0",
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.2.2",
+ "infer-owner": "^1.0.4",
+ "lru-cache": "^5.1.1",
+ "minipass": "^3.0.0",
+ "minipass-collect": "^1.0.2",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.2",
+ "mkdirp": "^0.5.1",
+ "move-concurrently": "^1.0.1",
+ "p-map": "^3.0.0",
+ "promise-inflight": "^1.0.1",
+ "rimraf": "^2.7.1",
+ "ssri": "^7.0.0",
+ "unique-filename": "^1.1.1"
+ },
+ "dependencies": {
+ "rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ }
+ }
+ },
+ "cache-base": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+ "dev": true,
+ "requires": {
+ "collection-visit": "^1.0.0",
+ "component-emitter": "^1.2.1",
+ "get-value": "^2.0.6",
+ "has-value": "^1.0.0",
+ "isobject": "^3.0.1",
+ "set-value": "^2.0.0",
+ "to-object-path": "^0.3.0",
+ "union-value": "^1.0.0",
+ "unset-value": "^1.0.0"
+ }
+ },
+ "call-me-maybe": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
+ "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
+ "dev": true
+ },
+ "caller-callsite": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
+ "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
+ "dev": true,
+ "requires": {
+ "callsites": "^2.0.0"
+ }
+ },
+ "caller-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
+ "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
+ "dev": true,
+ "requires": {
+ "caller-callsite": "^2.0.0"
+ }
+ },
+ "callsites": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
+ "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=",
+ "dev": true
+ },
+ "camel-case": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz",
+ "integrity": "sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q==",
+ "dev": true,
+ "requires": {
+ "pascal-case": "^3.1.1",
+ "tslib": "^1.10.0"
+ }
+ },
+ "camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
+ },
+ "caniuse-api": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
+ "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.0.0",
+ "caniuse-lite": "^1.0.0",
+ "lodash.memoize": "^4.1.2",
+ "lodash.uniq": "^4.5.0"
+ }
+ },
+ "caniuse-lite": {
+ "version": "1.0.30001032",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001032.tgz",
+ "integrity": "sha512-8joOm7BwcpEN4BfVHtfh0hBXSAPVYk+eUIcNntGtMkUWy/6AKRCDZINCLe3kB1vHhT2vBxBF85Hh9VlPXi/qjA==",
+ "dev": true
+ },
+ "capture-exit": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz",
+ "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==",
+ "dev": true,
+ "requires": {
+ "rsvp": "^4.8.4"
+ }
+ },
+ "case-sensitive-paths-webpack-plugin": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz",
+ "integrity": "sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ==",
+ "dev": true
+ },
+ "caseless": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
+ "dev": true
+ },
+ "chain-function": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.1.tgz",
+ "integrity": "sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg=="
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ }
+ },
+ "change-emitter": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz",
+ "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
+ },
+ "chardet": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+ "dev": true
+ },
+ "cheerio": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz",
+ "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=",
+ "dev": true,
+ "requires": {
+ "css-select": "~1.2.0",
+ "dom-serializer": "~0.1.0",
+ "entities": "~1.1.1",
+ "htmlparser2": "^3.9.1",
+ "lodash.assignin": "^4.0.9",
+ "lodash.bind": "^4.1.4",
+ "lodash.defaults": "^4.0.1",
+ "lodash.filter": "^4.4.0",
+ "lodash.flatten": "^4.2.0",
+ "lodash.foreach": "^4.3.0",
+ "lodash.map": "^4.4.0",
+ "lodash.merge": "^4.4.0",
+ "lodash.pick": "^4.2.1",
+ "lodash.reduce": "^4.4.0",
+ "lodash.reject": "^4.4.0",
+ "lodash.some": "^4.4.0"
+ }
+ },
+ "chokidar": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz",
+ "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==",
+ "dev": true,
+ "requires": {
+ "anymatch": "~3.1.1",
+ "braces": "~3.0.2",
+ "fsevents": "~2.1.2",
+ "glob-parent": "~5.1.0",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.3.0"
+ },
+ "dependencies": {
+ "anymatch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
+ "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
+ "dev": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ }
+ }
+ },
+ "chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "dev": true
+ },
+ "chrome-trace-event": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz",
+ "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "ci-info": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
+ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
+ "dev": true
+ },
+ "cipher-base": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+ "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "class-utils": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+ "dev": true,
+ "requires": {
+ "arr-union": "^3.1.0",
+ "define-property": "^0.2.5",
+ "isobject": "^3.0.0",
+ "static-extend": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "clean-css": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
+ "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==",
+ "dev": true,
+ "requires": {
+ "source-map": "~0.6.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "clean-stack": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+ "dev": true
+ },
+ "cli-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+ "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+ "dev": true,
+ "requires": {
+ "restore-cursor": "^3.1.0"
+ }
+ },
+ "cli-width": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
+ "dev": true
+ },
+ "clientjs": {
+ "version": "0.1.11",
+ "resolved": "https://registry.npmjs.org/clientjs/-/clientjs-0.1.11.tgz",
+ "integrity": "sha1-Rm4bE8Ipo8u9hIT5hTHc1lj4EFQ="
+ },
+ "cliui": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+ "dev": true,
+ "requires": {
+ "string-width": "^3.1.0",
+ "strip-ansi": "^5.2.0",
+ "wrap-ansi": "^5.1.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "clone-deep": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz",
+ "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=",
+ "dev": true,
+ "requires": {
+ "for-own": "^0.1.3",
+ "is-plain-object": "^2.0.1",
+ "kind-of": "^3.0.2",
+ "lazy-cache": "^1.0.3",
+ "shallow-clone": "^0.1.2"
+ }
+ },
+ "clsx": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.0.4.tgz",
+ "integrity": "sha512-1mQ557MIZTrL/140j+JVdRM6e31/OA4vTYxXgqIIZlndyfjHpyawKZia1Im05Vp9BWmImkcNrNtFYQMyFcgJDg=="
+ },
+ "co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+ "dev": true
+ },
+ "coa": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz",
+ "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==",
+ "dev": true,
+ "requires": {
+ "@types/q": "^1.5.1",
+ "chalk": "^2.4.1",
+ "q": "^1.1.2"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+ "dev": true
+ },
+ "collection-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+ "dev": true,
+ "requires": {
+ "map-visit": "^1.0.0",
+ "object-visit": "^1.0.0"
+ }
+ },
+ "color": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz",
+ "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.1",
+ "color-string": "^1.5.2"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "color-string": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz",
+ "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==",
+ "dev": true,
+ "requires": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "requires": {
+ "delayed-stream": "~1.0.0"
+ }
+ },
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true
+ },
+ "common-tags": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz",
+ "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==",
+ "dev": true
+ },
+ "commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+ "dev": true
+ },
+ "component-emitter": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
+ "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
+ "dev": true
+ },
+ "compose-function": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/compose-function/-/compose-function-3.0.3.tgz",
+ "integrity": "sha1-ntZ18TzFRQHTCVCkhv9qe6OrGF8=",
+ "dev": true,
+ "requires": {
+ "arity-n": "^1.0.4"
+ }
+ },
+ "compressible": {
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
+ "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
+ "dev": true,
+ "requires": {
+ "mime-db": ">= 1.43.0 < 2"
+ }
+ },
+ "compression": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
+ "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
+ "dev": true,
+ "requires": {
+ "accepts": "~1.3.5",
+ "bytes": "3.0.0",
+ "compressible": "~2.0.16",
+ "debug": "2.6.9",
+ "on-headers": "~1.0.2",
+ "safe-buffer": "5.1.2",
+ "vary": "~1.1.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "confusing-browser-globals": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz",
+ "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==",
+ "dev": true
+ },
+ "connect-history-api-fallback": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz",
+ "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==",
+ "dev": true
+ },
+ "console-browserify": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
+ "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==",
+ "dev": true
+ },
+ "constants-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
+ "dev": true
+ },
+ "contains-path": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
+ "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
+ "dev": true
+ },
+ "content-disposition": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+ "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "content-type": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+ "dev": true
+ },
+ "convert-source-map": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
+ "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.1"
+ }
+ },
+ "cookie": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+ "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
+ "dev": true
+ },
+ "cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
+ "dev": true
+ },
+ "copy-concurrently": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
+ "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.1.1",
+ "fs-write-stream-atomic": "^1.0.8",
+ "iferr": "^0.1.5",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.4",
+ "run-queue": "^1.0.0"
+ }
+ },
+ "copy-descriptor": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
+ "dev": true
+ },
+ "copy-to-clipboard": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz",
+ "integrity": "sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w==",
+ "requires": {
+ "toggle-selection": "^1.0.6"
+ }
+ },
+ "core-js": {
+ "version": "2.6.5",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz",
+ "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A=="
+ },
+ "core-js-compat": {
+ "version": "3.6.4",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz",
+ "integrity": "sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.8.3",
+ "semver": "7.0.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
+ "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
+ "dev": true
+ }
+ }
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "cosmiconfig": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
+ "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==",
+ "dev": true,
+ "requires": {
+ "import-fresh": "^2.0.0",
+ "is-directory": "^0.3.1",
+ "js-yaml": "^3.13.1",
+ "parse-json": "^4.0.0"
+ }
+ },
+ "create-ecdh": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
+ "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "elliptic": "^6.0.0"
+ }
+ },
+ "create-hash": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+ "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.1",
+ "inherits": "^2.0.1",
+ "md5.js": "^1.3.4",
+ "ripemd160": "^2.0.1",
+ "sha.js": "^2.4.0"
+ }
+ },
+ "create-hmac": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+ "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.3",
+ "create-hash": "^1.1.0",
+ "inherits": "^2.0.1",
+ "ripemd160": "^2.0.0",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ }
+ },
+ "create-react-class": {
+ "version": "15.6.3",
+ "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz",
+ "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==",
+ "requires": {
+ "fbjs": "^0.8.9",
+ "loose-envify": "^1.3.1",
+ "object-assign": "^4.1.1"
+ }
+ },
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "crypto-browserify": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
+ "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+ "dev": true,
+ "requires": {
+ "browserify-cipher": "^1.0.0",
+ "browserify-sign": "^4.0.0",
+ "create-ecdh": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "create-hmac": "^1.1.0",
+ "diffie-hellman": "^5.0.0",
+ "inherits": "^2.0.1",
+ "pbkdf2": "^3.0.3",
+ "public-encrypt": "^4.0.0",
+ "randombytes": "^2.0.0",
+ "randomfill": "^1.0.3"
+ }
+ },
+ "css": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz",
+ "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "source-map": "^0.6.1",
+ "source-map-resolve": "^0.5.2",
+ "urix": "^0.1.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "css-blank-pseudo": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz",
+ "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.5"
+ }
+ },
+ "css-color-names": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
+ "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=",
+ "dev": true
+ },
+ "css-declaration-sorter": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz",
+ "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.1",
+ "timsort": "^0.3.0"
+ }
+ },
+ "css-has-pseudo": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz",
+ "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.6",
+ "postcss-selector-parser": "^5.0.0-rc.4"
+ },
+ "dependencies": {
+ "cssesc": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz",
+ "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==",
+ "dev": true
+ },
+ "postcss-selector-parser": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz",
+ "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==",
+ "dev": true,
+ "requires": {
+ "cssesc": "^2.0.0",
+ "indexes-of": "^1.0.1",
+ "uniq": "^1.0.1"
+ }
+ }
+ }
+ },
+ "css-in-js-utils": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz",
+ "integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==",
+ "requires": {
+ "hyphenate-style-name": "^1.0.2",
+ "isobject": "^3.0.1"
+ }
+ },
+ "css-loader": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.4.2.tgz",
+ "integrity": "sha512-jYq4zdZT0oS0Iykt+fqnzVLRIeiPWhka+7BqPn+oSIpWJAHak5tmB/WZrJ2a21JhCeFyNnnlroSl8c+MtVndzA==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.3.1",
+ "cssesc": "^3.0.0",
+ "icss-utils": "^4.1.1",
+ "loader-utils": "^1.2.3",
+ "normalize-path": "^3.0.0",
+ "postcss": "^7.0.23",
+ "postcss-modules-extract-imports": "^2.0.0",
+ "postcss-modules-local-by-default": "^3.0.2",
+ "postcss-modules-scope": "^2.1.1",
+ "postcss-modules-values": "^3.0.0",
+ "postcss-value-parser": "^4.0.2",
+ "schema-utils": "^2.6.0"
+ },
+ "dependencies": {
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ }
+ }
+ },
+ "css-prefers-color-scheme": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz",
+ "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.5"
+ }
+ },
+ "css-select": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
+ "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
+ "dev": true,
+ "requires": {
+ "boolbase": "~1.0.0",
+ "css-what": "2.1",
+ "domutils": "1.5.1",
+ "nth-check": "~1.0.1"
+ }
+ },
+ "css-select-base-adapter": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz",
+ "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==",
+ "dev": true
+ },
+ "css-tree": {
+ "version": "1.0.0-alpha.37",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz",
+ "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==",
+ "dev": true,
+ "requires": {
+ "mdn-data": "2.0.4",
+ "source-map": "^0.6.1"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "css-what": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
+ "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==",
+ "dev": true
+ },
+ "cssdb": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz",
+ "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==",
+ "dev": true
+ },
+ "cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true
+ },
+ "cssnano": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz",
+ "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==",
+ "dev": true,
+ "requires": {
+ "cosmiconfig": "^5.0.0",
+ "cssnano-preset-default": "^4.0.7",
+ "is-resolvable": "^1.0.0",
+ "postcss": "^7.0.0"
+ }
+ },
+ "cssnano-preset-default": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz",
+ "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==",
+ "dev": true,
+ "requires": {
+ "css-declaration-sorter": "^4.0.1",
+ "cssnano-util-raw-cache": "^4.0.1",
+ "postcss": "^7.0.0",
+ "postcss-calc": "^7.0.1",
+ "postcss-colormin": "^4.0.3",
+ "postcss-convert-values": "^4.0.1",
+ "postcss-discard-comments": "^4.0.2",
+ "postcss-discard-duplicates": "^4.0.2",
+ "postcss-discard-empty": "^4.0.1",
+ "postcss-discard-overridden": "^4.0.1",
+ "postcss-merge-longhand": "^4.0.11",
+ "postcss-merge-rules": "^4.0.3",
+ "postcss-minify-font-values": "^4.0.2",
+ "postcss-minify-gradients": "^4.0.2",
+ "postcss-minify-params": "^4.0.2",
+ "postcss-minify-selectors": "^4.0.2",
+ "postcss-normalize-charset": "^4.0.1",
+ "postcss-normalize-display-values": "^4.0.2",
+ "postcss-normalize-positions": "^4.0.2",
+ "postcss-normalize-repeat-style": "^4.0.2",
+ "postcss-normalize-string": "^4.0.2",
+ "postcss-normalize-timing-functions": "^4.0.2",
+ "postcss-normalize-unicode": "^4.0.1",
+ "postcss-normalize-url": "^4.0.1",
+ "postcss-normalize-whitespace": "^4.0.2",
+ "postcss-ordered-values": "^4.1.2",
+ "postcss-reduce-initial": "^4.0.3",
+ "postcss-reduce-transforms": "^4.0.2",
+ "postcss-svgo": "^4.0.2",
+ "postcss-unique-selectors": "^4.0.1"
+ }
+ },
+ "cssnano-util-get-arguments": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz",
+ "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=",
+ "dev": true
+ },
+ "cssnano-util-get-match": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz",
+ "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=",
+ "dev": true
+ },
+ "cssnano-util-raw-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz",
+ "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.0"
+ }
+ },
+ "cssnano-util-same-parent": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz",
+ "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==",
+ "dev": true
+ },
+ "csso": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.2.tgz",
+ "integrity": "sha512-kS7/oeNVXkHWxby5tHVxlhjizRCSv8QdU7hB2FpdAibDU8FjTAolhNjKNTiLzXtUrKT6HwClE81yXwEk1309wg==",
+ "dev": true,
+ "requires": {
+ "css-tree": "1.0.0-alpha.37"
+ }
+ },
+ "cssom": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
+ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
+ "dev": true
+ },
+ "cssstyle": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz",
+ "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==",
+ "dev": true,
+ "requires": {
+ "cssom": "0.3.x"
+ }
+ },
+ "cyclist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
+ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
+ "dev": true
+ },
+ "d": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
+ "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
+ "dev": true,
+ "requires": {
+ "es5-ext": "^0.10.50",
+ "type": "^1.0.1"
+ }
+ },
+ "damerau-levenshtein": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz",
+ "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==",
+ "dev": true
+ },
+ "dashdash": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "data-urls": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz",
+ "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==",
+ "dev": true,
+ "requires": {
+ "abab": "^2.0.0",
+ "whatwg-mimetype": "^2.2.0",
+ "whatwg-url": "^7.0.0"
+ },
+ "dependencies": {
+ "whatwg-url": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
+ "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
+ "dev": true,
+ "requires": {
+ "lodash.sortby": "^4.7.0",
+ "tr46": "^1.0.1",
+ "webidl-conversions": "^4.0.2"
+ }
+ }
+ }
+ },
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "dev": true
+ },
+ "decode-uri-component": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
+ "dev": true
+ },
+ "deep-equal": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
+ "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
+ "dev": true,
+ "requires": {
+ "is-arguments": "^1.0.4",
+ "is-date-object": "^1.0.1",
+ "is-regex": "^1.0.4",
+ "object-is": "^1.0.1",
+ "object-keys": "^1.1.1",
+ "regexp.prototype.flags": "^1.2.0"
+ }
+ },
+ "deep-is": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+ "dev": true
+ },
+ "default-gateway": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz",
+ "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==",
+ "dev": true,
+ "requires": {
+ "execa": "^1.0.0",
+ "ip-regex": "^2.1.0"
+ }
+ },
+ "define-properties": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+ "requires": {
+ "object-keys": "^1.0.12"
+ }
+ },
+ "define-property": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.2",
+ "isobject": "^3.0.1"
+ },
+ "dependencies": {
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true
+ }
+ }
+ },
+ "del": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz",
+ "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==",
+ "dev": true,
+ "requires": {
+ "@types/glob": "^7.1.1",
+ "globby": "^6.1.0",
+ "is-path-cwd": "^2.0.0",
+ "is-path-in-cwd": "^2.0.0",
+ "p-map": "^2.0.0",
+ "pify": "^4.0.1",
+ "rimraf": "^2.6.3"
+ },
+ "dependencies": {
+ "globby": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+ "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
+ "dev": true,
+ "requires": {
+ "array-union": "^1.0.1",
+ "glob": "^7.0.3",
+ "object-assign": "^4.0.1",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ }
+ }
+ },
+ "p-map": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+ "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
+ "dev": true
+ },
+ "pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "dev": true
+ }
+ }
+ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+ "dev": true
+ },
+ "depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+ "dev": true
+ },
+ "des.js": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
+ "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "destroy": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
+ "dev": true
+ },
+ "detect-newline": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz",
+ "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=",
+ "dev": true
+ },
+ "detect-node": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
+ "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
+ "dev": true
+ },
+ "detect-port-alt": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz",
+ "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==",
+ "dev": true,
+ "requires": {
+ "address": "^1.0.1",
+ "debug": "^2.6.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "diff-sequences": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz",
+ "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==",
+ "dev": true
+ },
+ "diffie-hellman": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+ "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "miller-rabin": "^4.0.0",
+ "randombytes": "^2.0.0"
+ }
+ },
+ "dir-glob": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz",
+ "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==",
+ "dev": true,
+ "requires": {
+ "arrify": "^1.0.1",
+ "path-type": "^3.0.0"
+ }
+ },
+ "dns-equal": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
+ "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=",
+ "dev": true
+ },
+ "dns-packet": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz",
+ "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==",
+ "dev": true,
+ "requires": {
+ "ip": "^1.1.0",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "dns-txt": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
+ "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=",
+ "dev": true,
+ "requires": {
+ "buffer-indexof": "^1.0.0"
+ }
+ },
+ "doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "dom-converter": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz",
+ "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==",
+ "dev": true,
+ "requires": {
+ "utila": "~0.4"
+ }
+ },
+ "dom-helpers": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
+ "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
+ "requires": {
+ "@babel/runtime": "^7.1.2"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.4.tgz",
+ "integrity": "sha512-w0+uT71b6Yi7i5SE0co4NioIpSYS6lLiXvCzWzGSKvpK5vdQtCbICHMj+gbAKAOtxiV6HsVh/MBdaF9EQ6faSg==",
+ "requires": {
+ "regenerator-runtime": "^0.13.2"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.2",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
+ "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA=="
+ }
+ }
+ },
+ "dom-serializer": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
+ "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==",
+ "dev": true,
+ "requires": {
+ "domelementtype": "^1.3.0",
+ "entities": "^1.1.1"
+ }
+ },
+ "domain-browser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
+ "dev": true
+ },
+ "domelementtype": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
+ "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
+ "dev": true
+ },
+ "domexception": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
+ "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
+ "dev": true,
+ "requires": {
+ "webidl-conversions": "^4.0.2"
+ }
+ },
+ "domhandler": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
+ "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
+ "dev": true,
+ "requires": {
+ "domelementtype": "1"
+ }
+ },
+ "domutils": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
+ "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
+ "dev": true,
+ "requires": {
+ "dom-serializer": "0",
+ "domelementtype": "1"
+ }
+ },
+ "dot-case": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.3.tgz",
+ "integrity": "sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA==",
+ "dev": true,
+ "requires": {
+ "no-case": "^3.0.3",
+ "tslib": "^1.10.0"
+ }
+ },
+ "dot-prop": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz",
+ "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==",
+ "dev": true,
+ "requires": {
+ "is-obj": "^2.0.0"
+ }
+ },
+ "dotenv": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
+ "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==",
+ "dev": true
+ },
+ "dotenv-expand": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
+ "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==",
+ "dev": true
+ },
+ "duplexer": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
+ "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
+ "dev": true
+ },
+ "duplexify": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
+ "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0",
+ "stream-shift": "^1.0.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "ecc-jsbn": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+ "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+ "dev": true,
+ "requires": {
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
+ "dev": true
+ },
+ "electron-to-chromium": {
+ "version": "1.3.368",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.368.tgz",
+ "integrity": "sha512-fqzDipW3p+uDkHUHFPrdW3wINRKcJsbnJwBD7hgaQEQwcuLSvNLw6SeUp5gKDpTbmTl7zri7IZfhsdTUTnygJg==",
+ "dev": true
+ },
+ "elliptic": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
+ "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.4.0",
+ "brorand": "^1.0.1",
+ "hash.js": "^1.0.0",
+ "hmac-drbg": "^1.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.0"
+ }
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "emojis-list": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+ "dev": true
+ },
+ "encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
+ "dev": true
+ },
+ "encoding": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+ "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+ "requires": {
+ "iconv-lite": "~0.4.13"
+ }
+ },
+ "end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dev": true,
+ "requires": {
+ "once": "^1.4.0"
+ }
+ },
+ "enhanced-resolve": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz",
+ "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "memory-fs": "^0.5.0",
+ "tapable": "^1.0.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "memory-fs": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
+ "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==",
+ "dev": true,
+ "requires": {
+ "errno": "^0.1.3",
+ "readable-stream": "^2.0.1"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "entities": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
+ "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
+ "dev": true
+ },
+ "enzyme": {
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-2.9.1.tgz",
+ "integrity": "sha1-B9XOaRJBJA+4F78sSxjW5TAkDfY=",
+ "dev": true,
+ "requires": {
+ "cheerio": "^0.22.0",
+ "function.prototype.name": "^1.0.0",
+ "is-subset": "^0.1.1",
+ "lodash": "^4.17.4",
+ "object-is": "^1.0.1",
+ "object.assign": "^4.0.4",
+ "object.entries": "^1.0.4",
+ "object.values": "^1.0.4",
+ "prop-types": "^15.5.10",
+ "uuid": "^3.0.1"
+ }
+ },
+ "enzyme-adapter-react-16": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.1.tgz",
+ "integrity": "sha512-kC8pAtU2Jk3OJ0EG8Y2813dg9Ol0TXi7UNxHzHiWs30Jo/hj7alc//G1YpKUsPP1oKl9X+Lkx+WlGJpPYA+nvw==",
+ "requires": {
+ "enzyme-adapter-utils": "^1.3.0",
+ "lodash": "^4.17.4",
+ "object.assign": "^4.0.4",
+ "object.values": "^1.0.4",
+ "prop-types": "^15.6.0",
+ "react-reconciler": "^0.7.0",
+ "react-test-renderer": "^16.0.0-0"
+ },
+ "dependencies": {
+ "react-test-renderer": {
+ "version": "16.8.6",
+ "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.6.tgz",
+ "integrity": "sha512-H2srzU5IWYT6cZXof6AhUcx/wEyJddQ8l7cLM/F7gDXYyPr4oq+vCIxJYXVGhId1J706sqziAjuOEjyNkfgoEw==",
+ "requires": {
+ "object-assign": "^4.1.1",
+ "prop-types": "^15.6.2",
+ "react-is": "^16.8.6",
+ "scheduler": "^0.13.6"
+ }
+ }
+ }
+ },
+ "enzyme-adapter-utils": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.11.0.tgz",
+ "integrity": "sha512-0VZeoE9MNx+QjTfsjmO1Mo+lMfunucYB4wt5ficU85WB/LoetTJrbuujmHP3PJx6pSoaAuLA+Mq877x4LoxdNg==",
+ "requires": {
+ "airbnb-prop-types": "^2.12.0",
+ "function.prototype.name": "^1.1.0",
+ "object.assign": "^4.1.0",
+ "object.fromentries": "^2.0.0",
+ "prop-types": "^15.7.2",
+ "semver": "^5.6.0"
+ }
+ },
+ "errno": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
+ "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
+ "dev": true,
+ "requires": {
+ "prr": "~1.0.1"
+ }
+ },
+ "error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "requires": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "es-abstract": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz",
+ "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==",
+ "requires": {
+ "es-to-primitive": "^1.2.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "is-callable": "^1.1.4",
+ "is-regex": "^1.0.4",
+ "object-keys": "^1.0.12"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
+ "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
+ "requires": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ }
+ },
+ "es5-ext": {
+ "version": "0.10.53",
+ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
+ "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==",
+ "dev": true,
+ "requires": {
+ "es6-iterator": "~2.0.3",
+ "es6-symbol": "~3.1.3",
+ "next-tick": "~1.0.0"
+ }
+ },
+ "es6-iterator": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
+ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
+ "dev": true,
+ "requires": {
+ "d": "1",
+ "es5-ext": "^0.10.35",
+ "es6-symbol": "^3.1.1"
+ }
+ },
+ "es6-promise": {
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz",
+ "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q=="
+ },
+ "es6-symbol": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
+ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
+ "dev": true,
+ "requires": {
+ "d": "^1.0.1",
+ "ext": "^1.1.2"
+ }
+ },
+ "escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "escodegen": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz",
+ "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==",
+ "dev": true,
+ "requires": {
+ "esprima": "^4.0.1",
+ "estraverse": "^4.2.0",
+ "esutils": "^2.0.2",
+ "optionator": "^0.8.1",
+ "source-map": "~0.6.1"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "eslint": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz",
+ "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "ajv": "^6.10.0",
+ "chalk": "^2.1.0",
+ "cross-spawn": "^6.0.5",
+ "debug": "^4.0.1",
+ "doctrine": "^3.0.0",
+ "eslint-scope": "^5.0.0",
+ "eslint-utils": "^1.4.3",
+ "eslint-visitor-keys": "^1.1.0",
+ "espree": "^6.1.2",
+ "esquery": "^1.0.1",
+ "esutils": "^2.0.2",
+ "file-entry-cache": "^5.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob-parent": "^5.0.0",
+ "globals": "^12.1.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "inquirer": "^7.0.0",
+ "is-glob": "^4.0.0",
+ "js-yaml": "^3.13.1",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.3.0",
+ "lodash": "^4.17.14",
+ "minimatch": "^3.0.4",
+ "mkdirp": "^0.5.1",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.8.3",
+ "progress": "^2.0.0",
+ "regexpp": "^2.0.1",
+ "semver": "^6.1.2",
+ "strip-ansi": "^5.2.0",
+ "strip-json-comments": "^3.0.1",
+ "table": "^5.2.3",
+ "text-table": "^0.2.0",
+ "v8-compile-cache": "^2.0.3"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "globals": {
+ "version": "12.3.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz",
+ "integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.8.1"
+ }
+ },
+ "import-fresh": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
+ "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
+ "dev": true,
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "regexpp": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
+ "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
+ "dev": true
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "eslint-config-react-app": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-5.2.0.tgz",
+ "integrity": "sha512-WrHjoGpKr1kLLiWDD81tme9jMM0hk5cMxasLSdyno6DdPt+IfLOrDJBVo6jN7tn4y1nzhs43TmUaZWO6Sf0blw==",
+ "dev": true,
+ "requires": {
+ "confusing-browser-globals": "^1.0.9"
+ }
+ },
+ "eslint-import-resolver-node": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz",
+ "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==",
+ "dev": true,
+ "requires": {
+ "debug": "^2.6.9",
+ "resolve": "^1.13.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "eslint-loader": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-3.0.3.tgz",
+ "integrity": "sha512-+YRqB95PnNvxNp1HEjQmvf9KNvCin5HXYYseOXVC2U0KEcw4IkQ2IQEBG46j7+gW39bMzeu0GsUhVbBY3Votpw==",
+ "dev": true,
+ "requires": {
+ "fs-extra": "^8.1.0",
+ "loader-fs-cache": "^1.0.2",
+ "loader-utils": "^1.2.3",
+ "object-hash": "^2.0.1",
+ "schema-utils": "^2.6.1"
+ }
+ },
+ "eslint-module-utils": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz",
+ "integrity": "sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q==",
+ "dev": true,
+ "requires": {
+ "debug": "^2.6.9",
+ "pkg-dir": "^2.0.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+ "dev": true,
+ "requires": {
+ "locate-path": "^2.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+ "dev": true,
+ "requires": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "p-limit": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "dev": true,
+ "requires": {
+ "p-try": "^1.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+ "dev": true,
+ "requires": {
+ "p-limit": "^1.1.0"
+ }
+ },
+ "p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+ "dev": true
+ },
+ "pkg-dir": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
+ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
+ "dev": true,
+ "requires": {
+ "find-up": "^2.1.0"
+ }
+ }
+ }
+ },
+ "eslint-plugin-flowtype": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-4.6.0.tgz",
+ "integrity": "sha512-W5hLjpFfZyZsXfo5anlu7HM970JBDqbEshAJUkeczP6BFCIfJXuiIBQXyberLRtOStT0OGPF8efeTbxlHk4LpQ==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.15"
+ }
+ },
+ "eslint-plugin-import": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.0.tgz",
+ "integrity": "sha512-NK42oA0mUc8Ngn4kONOPsPB1XhbUvNHqF+g307dPV28aknPoiNnKLFd9em4nkswwepdF5ouieqv5Th/63U7YJQ==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.0.3",
+ "array.prototype.flat": "^1.2.1",
+ "contains-path": "^0.1.0",
+ "debug": "^2.6.9",
+ "doctrine": "1.5.0",
+ "eslint-import-resolver-node": "^0.3.2",
+ "eslint-module-utils": "^2.4.1",
+ "has": "^1.0.3",
+ "minimatch": "^3.0.4",
+ "object.values": "^1.1.0",
+ "read-pkg-up": "^2.0.0",
+ "resolve": "^1.12.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "doctrine": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
+ "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2",
+ "isarray": "^1.0.0"
+ }
+ },
+ "find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+ "dev": true,
+ "requires": {
+ "locate-path": "^2.0.0"
+ }
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "load-json-file": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+ "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^2.2.0",
+ "pify": "^2.0.0",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+ "dev": true,
+ "requires": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "p-limit": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "dev": true,
+ "requires": {
+ "p-try": "^1.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+ "dev": true,
+ "requires": {
+ "p-limit": "^1.1.0"
+ }
+ },
+ "p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+ "dev": true
+ },
+ "parse-json": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+ "dev": true,
+ "requires": {
+ "error-ex": "^1.2.0"
+ }
+ },
+ "path-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
+ "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
+ "dev": true,
+ "requires": {
+ "pify": "^2.0.0"
+ }
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ },
+ "read-pkg": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
+ "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
+ "dev": true,
+ "requires": {
+ "load-json-file": "^2.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^2.0.0"
+ }
+ },
+ "read-pkg-up": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
+ "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
+ "dev": true,
+ "requires": {
+ "find-up": "^2.0.0",
+ "read-pkg": "^2.0.0"
+ }
+ }
+ }
+ },
+ "eslint-plugin-jsx-a11y": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz",
+ "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.4.5",
+ "aria-query": "^3.0.0",
+ "array-includes": "^3.0.3",
+ "ast-types-flow": "^0.0.7",
+ "axobject-query": "^2.0.2",
+ "damerau-levenshtein": "^1.0.4",
+ "emoji-regex": "^7.0.2",
+ "has": "^1.0.3",
+ "jsx-ast-utils": "^2.2.1"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz",
+ "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==",
+ "dev": true,
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true
+ },
+ "regenerator-runtime": {
+ "version": "0.13.4",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz",
+ "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-plugin-react": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.18.0.tgz",
+ "integrity": "sha512-p+PGoGeV4SaZRDsXqdj9OWcOrOpZn8gXoGPcIQTzo2IDMbAKhNDnME9myZWqO3Ic4R3YmwAZ1lDjWl2R2hMUVQ==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.1.1",
+ "doctrine": "^2.1.0",
+ "has": "^1.0.3",
+ "jsx-ast-utils": "^2.2.3",
+ "object.entries": "^1.1.1",
+ "object.fromentries": "^2.0.2",
+ "object.values": "^1.1.1",
+ "prop-types": "^15.7.2",
+ "resolve": "^1.14.2"
+ },
+ "dependencies": {
+ "doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "es-abstract": {
+ "version": "1.17.4",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz",
+ "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1",
+ "is-callable": "^1.1.5",
+ "is-regex": "^1.0.5",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimleft": "^2.1.1",
+ "string.prototype.trimright": "^2.1.1"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
+ "requires": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ }
+ },
+ "has-symbols": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
+ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
+ "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
+ "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "object.entries": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz",
+ "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.0-next.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3"
+ }
+ },
+ "object.fromentries": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz",
+ "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.0-next.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3"
+ }
+ },
+ "object.values": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz",
+ "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.0-next.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3"
+ }
+ }
+ }
+ },
+ "eslint-plugin-react-hooks": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz",
+ "integrity": "sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA==",
+ "dev": true
+ },
+ "eslint-scope": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
+ "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.1.0",
+ "estraverse": "^4.1.1"
+ }
+ },
+ "eslint-utils": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
+ "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
+ "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
+ "dev": true
+ },
+ "espree": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.0.tgz",
+ "integrity": "sha512-Xs8airJ7RQolnDIbLtRutmfvSsAe0xqMMAantCN/GMoqf81TFbeI1T7Jpd56qYu1uuh32dOG5W/X9uO+ghPXzA==",
+ "dev": true,
+ "requires": {
+ "acorn": "^7.1.0",
+ "acorn-jsx": "^5.2.0",
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true
+ },
+ "esquery": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.1.0.tgz",
+ "integrity": "sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^4.0.0"
+ }
+ },
+ "esrecurse": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
+ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^4.1.0"
+ }
+ },
+ "estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+ "dev": true
+ },
+ "etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
+ "dev": true
+ },
+ "eventemitter3": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz",
+ "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==",
+ "dev": true
+ },
+ "events": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz",
+ "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==",
+ "dev": true
+ },
+ "eventsource": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz",
+ "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==",
+ "dev": true,
+ "requires": {
+ "original": "^1.0.0"
+ }
+ },
+ "evp_bytestokey": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+ "dev": true,
+ "requires": {
+ "md5.js": "^1.3.4",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "exec-sh": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz",
+ "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==",
+ "dev": true
+ },
+ "execa": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+ "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^6.0.0",
+ "get-stream": "^4.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ }
+ },
+ "exit": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+ "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
+ "dev": true
+ },
+ "expand-brackets": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+ "dev": true,
+ "requires": {
+ "debug": "^2.3.3",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "posix-character-classes": "^0.1.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "expect": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz",
+ "integrity": "sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^24.9.0",
+ "ansi-styles": "^3.2.0",
+ "jest-get-type": "^24.9.0",
+ "jest-matcher-utils": "^24.9.0",
+ "jest-message-util": "^24.9.0",
+ "jest-regex-util": "^24.9.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ }
+ }
+ },
+ "express": {
+ "version": "4.17.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+ "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+ "dev": true,
+ "requires": {
+ "accepts": "~1.3.7",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.19.0",
+ "content-disposition": "0.5.3",
+ "content-type": "~1.0.4",
+ "cookie": "0.4.0",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.1.2",
+ "fresh": "0.5.2",
+ "merge-descriptors": "1.0.1",
+ "methods": "~1.1.2",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "~2.0.5",
+ "qs": "6.7.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.1.2",
+ "send": "0.17.1",
+ "serve-static": "1.14.1",
+ "setprototypeof": "1.1.1",
+ "statuses": "~1.5.0",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "dependencies": {
+ "array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
+ "dev": true
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
+ "dev": true
+ },
+ "qs": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
+ "dev": true
+ }
+ }
+ },
+ "ext": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz",
+ "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==",
+ "dev": true,
+ "requires": {
+ "type": "^2.0.0"
+ },
+ "dependencies": {
+ "type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz",
+ "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==",
+ "dev": true
+ }
+ }
+ },
+ "extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "dev": true
+ },
+ "extend-shallow": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+ "dev": true,
+ "requires": {
+ "assign-symbols": "^1.0.0",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "external-editor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+ "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+ "dev": true,
+ "requires": {
+ "chardet": "^0.7.0",
+ "iconv-lite": "^0.4.24",
+ "tmp": "^0.0.33"
+ }
+ },
+ "extglob": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+ "dev": true,
+ "requires": {
+ "array-unique": "^0.3.2",
+ "define-property": "^1.0.0",
+ "expand-brackets": "^2.1.4",
+ "extend-shallow": "^2.0.1",
+ "fragment-cache": "^0.2.1",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true
+ }
+ }
+ },
+ "extsprintf": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
+ "dev": true
+ },
+ "fast-deep-equal": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
+ "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==",
+ "dev": true
+ },
+ "fast-glob": {
+ "version": "2.2.7",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz",
+ "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==",
+ "dev": true,
+ "requires": {
+ "@mrmlnc/readdir-enhanced": "^2.2.1",
+ "@nodelib/fs.stat": "^1.1.2",
+ "glob-parent": "^3.1.0",
+ "is-glob": "^4.0.0",
+ "merge2": "^1.2.3",
+ "micromatch": "^3.1.10"
+ },
+ "dependencies": {
+ "glob-parent": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+ "dev": true,
+ "requires": {
+ "is-glob": "^3.1.0",
+ "path-dirname": "^1.0.0"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.0"
+ }
+ }
+ }
+ }
+ }
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "faye-websocket": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
+ "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=",
+ "dev": true,
+ "requires": {
+ "websocket-driver": ">=0.5.1"
+ }
+ },
+ "fb-watchman": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz",
+ "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==",
+ "dev": true,
+ "requires": {
+ "bser": "2.1.1"
+ }
+ },
+ "fbjs": {
+ "version": "0.8.17",
+ "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
+ "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
+ "requires": {
+ "core-js": "^1.0.0",
+ "isomorphic-fetch": "^2.1.1",
+ "loose-envify": "^1.0.0",
+ "object-assign": "^4.1.0",
+ "promise": "^7.1.1",
+ "setimmediate": "^1.0.5",
+ "ua-parser-js": "^0.7.18"
+ },
+ "dependencies": {
+ "core-js": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
+ "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
+ }
+ }
+ },
+ "figgy-pudding": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
+ "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==",
+ "dev": true
+ },
+ "figures": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
+ "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
+ "dev": true,
+ "requires": {
+ "escape-string-regexp": "^1.0.5"
+ }
+ },
+ "file-entry-cache": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+ "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+ "dev": true,
+ "requires": {
+ "flat-cache": "^2.0.1"
+ }
+ },
+ "file-loader": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-4.3.0.tgz",
+ "integrity": "sha512-aKrYPYjF1yG3oX0kWRrqrSMfgftm7oJW5M+m4owoldH5C51C0RkIwB++JbRvEW3IU6/ZG5n8UvEcdgwOt2UOWA==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.2.3",
+ "schema-utils": "^2.5.0"
+ }
+ },
+ "file-saver": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz",
+ "integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw=="
+ },
+ "file-uri-to-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+ "dev": true,
+ "optional": true
+ },
+ "filesize": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.0.1.tgz",
+ "integrity": "sha512-u4AYWPgbI5GBhs6id1KdImZWn5yfyFrrQ8OWZdN7ZMfA8Bf4HcO0BGo9bmUIEV8yrp8I1xVfJ/dn90GtFNNJcg==",
+ "dev": true
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "finalhandler": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+ "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.3",
+ "statuses": "~1.5.0",
+ "unpipe": "~1.0.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "find-cache-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
+ "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+ "dev": true,
+ "requires": {
+ "commondir": "^1.0.1",
+ "make-dir": "^2.0.0",
+ "pkg-dir": "^3.0.0"
+ }
+ },
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "flat-cache": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
+ "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+ "dev": true,
+ "requires": {
+ "flatted": "^2.0.0",
+ "rimraf": "2.6.3",
+ "write": "1.0.3"
+ }
+ },
+ "flatted": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
+ "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
+ "dev": true
+ },
+ "flatten": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz",
+ "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==",
+ "dev": true
+ },
+ "flush-write-stream": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
+ "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.3.6"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "follow-redirects": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.10.0.tgz",
+ "integrity": "sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ==",
+ "dev": true,
+ "requires": {
+ "debug": "^3.0.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ }
+ }
+ },
+ "for-in": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
+ "dev": true
+ },
+ "for-own": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
+ "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=",
+ "dev": true,
+ "requires": {
+ "for-in": "^1.0.1"
+ }
+ },
+ "forever-agent": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+ "dev": true
+ },
+ "fork-ts-checker-webpack-plugin": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-3.1.1.tgz",
+ "integrity": "sha512-DuVkPNrM12jR41KM2e+N+styka0EgLkTnXmNcXdgOM37vtGeY+oCBK/Jx0hzSeEU6memFCtWb4htrHPMDfwwUQ==",
+ "dev": true,
+ "requires": {
+ "babel-code-frame": "^6.22.0",
+ "chalk": "^2.4.1",
+ "chokidar": "^3.3.0",
+ "micromatch": "^3.1.10",
+ "minimatch": "^3.0.4",
+ "semver": "^5.6.0",
+ "tapable": "^1.0.0",
+ "worker-rpc": "^0.1.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "form-data": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+ "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+ "dev": true,
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.6",
+ "mime-types": "^2.1.12"
+ }
+ },
+ "forwarded": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+ "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
+ "dev": true
+ },
+ "fragment-cache": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+ "dev": true,
+ "requires": {
+ "map-cache": "^0.2.2"
+ }
+ },
+ "fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
+ "dev": true
+ },
+ "from2": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
+ "fs-minipass": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
+ "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
+ "dev": true,
+ "requires": {
+ "minipass": "^3.0.0"
+ }
+ },
+ "fs-write-stream-atomic": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
+ "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "iferr": "^0.1.5",
+ "imurmurhash": "^0.1.4",
+ "readable-stream": "1 || 2"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "fsevents": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz",
+ "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==",
+ "dev": true,
+ "optional": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ },
+ "function.prototype.name": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.0.tgz",
+ "integrity": "sha512-Bs0VRrTz4ghD8pTmbJQD1mZ8A/mN0ur/jGz+A6FBxPDUPkm1tNfF6bhTYPA7i7aF4lZJVr+OXTNNrnnIl58Wfg==",
+ "requires": {
+ "define-properties": "^1.1.2",
+ "function-bind": "^1.1.1",
+ "is-callable": "^1.1.3"
+ }
+ },
+ "functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+ "dev": true
+ },
+ "fuse.js": {
+ "version": "3.4.5",
+ "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.4.5.tgz",
+ "integrity": "sha512-s9PGTaQIkT69HaeoTVjwGsLfb8V8ScJLx5XGFcKHg0MqLUH/UZ4EKOtqtXX9k7AFqCGxD1aJmYb8Q5VYDibVRQ=="
+ },
+ "gensync": {
+ "version": "1.0.0-beta.1",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
+ "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==",
+ "dev": true
+ },
+ "get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true
+ },
+ "get-own-enumerable-property-symbols": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz",
+ "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==",
+ "dev": true
+ },
+ "get-stream": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+ "dev": true,
+ "requires": {
+ "pump": "^3.0.0"
+ }
+ },
+ "get-value": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
+ "dev": true
+ },
+ "getpass": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
+ "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "glob-to-regexp": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz",
+ "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=",
+ "dev": true
+ },
+ "global-modules": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
+ "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==",
+ "dev": true,
+ "requires": {
+ "global-prefix": "^3.0.0"
+ }
+ },
+ "global-prefix": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
+ "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
+ "dev": true,
+ "requires": {
+ "ini": "^1.3.5",
+ "kind-of": "^6.0.2",
+ "which": "^1.3.1"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true
+ }
+ }
+ },
+ "globals": {
+ "version": "9.18.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
+ "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
+ "dev": true
+ },
+ "globby": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz",
+ "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==",
+ "dev": true,
+ "requires": {
+ "array-union": "^1.0.1",
+ "dir-glob": "2.0.0",
+ "fast-glob": "^2.0.2",
+ "glob": "^7.1.2",
+ "ignore": "^3.3.5",
+ "pify": "^3.0.0",
+ "slash": "^1.0.0"
+ },
+ "dependencies": {
+ "ignore": {
+ "version": "3.3.10",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
+ "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==",
+ "dev": true
+ },
+ "slash": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
+ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
+ "dev": true
+ }
+ }
+ },
+ "graceful-fs": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
+ "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
+ "dev": true
+ },
+ "growly": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
+ "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=",
+ "dev": true
+ },
+ "gzip-size": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz",
+ "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==",
+ "dev": true,
+ "requires": {
+ "duplexer": "^0.1.1",
+ "pify": "^4.0.1"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "dev": true
+ }
+ }
+ },
+ "handle-thing": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz",
+ "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==",
+ "dev": true
+ },
+ "har-schema": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
+ "dev": true
+ },
+ "har-validator": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+ "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.5.5",
+ "har-schema": "^2.0.0"
+ }
+ },
+ "harmony-reflect": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.1.tgz",
+ "integrity": "sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA==",
+ "dev": true
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-ansi": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "has-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
+ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q="
+ },
+ "has-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+ "dev": true,
+ "requires": {
+ "get-value": "^2.0.6",
+ "has-values": "^1.0.0",
+ "isobject": "^3.0.0"
+ }
+ },
+ "has-values": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+ "dev": true,
+ "requires": {
+ "is-number": "^3.0.0",
+ "kind-of": "^4.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "hash-base": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
+ "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "hash.js": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+ "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "minimalistic-assert": "^1.0.1"
+ }
+ },
+ "he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true
+ },
+ "hex-color-regex": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
+ "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==",
+ "dev": true
+ },
+ "history": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz",
+ "integrity": "sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "loose-envify": "^1.2.0",
+ "resolve-pathname": "^2.2.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0",
+ "value-equal": "^0.4.0"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.4.tgz",
+ "integrity": "sha512-w0+uT71b6Yi7i5SE0co4NioIpSYS6lLiXvCzWzGSKvpK5vdQtCbICHMj+gbAKAOtxiV6HsVh/MBdaF9EQ6faSg==",
+ "requires": {
+ "regenerator-runtime": "^0.13.2"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.2",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
+ "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA=="
+ }
+ }
+ },
+ "hmac-drbg": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+ "dev": true,
+ "requires": {
+ "hash.js": "^1.0.3",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.1"
+ }
+ },
+ "hoist-non-react-statics": {
+ "version": "2.5.5",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
+ "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
+ },
+ "hosted-git-info": {
+ "version": "2.8.8",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
+ "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
+ "dev": true
+ },
+ "hpack.js": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
+ "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "obuf": "^1.0.0",
+ "readable-stream": "^2.0.1",
+ "wbuf": "^1.1.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "hsl-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz",
+ "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=",
+ "dev": true
+ },
+ "hsla-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz",
+ "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=",
+ "dev": true
+ },
+ "html-comment-regex": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz",
+ "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==",
+ "dev": true
+ },
+ "html-encoding-sniffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz",
+ "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==",
+ "dev": true,
+ "requires": {
+ "whatwg-encoding": "^1.0.1"
+ }
+ },
+ "html-entities": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz",
+ "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=",
+ "dev": true
+ },
+ "html-escaper": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.0.tgz",
+ "integrity": "sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig==",
+ "dev": true
+ },
+ "html-minifier-terser": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.0.4.tgz",
+ "integrity": "sha512-fHwmKQ+GzhlqdxEtwrqLT7MSuheiA+rif5/dZgbz3GjoMXJzcRzy1L9NXoiiyxrnap+q5guSiv8Tz5lrh9g42g==",
+ "dev": true,
+ "requires": {
+ "camel-case": "^4.1.1",
+ "clean-css": "^4.2.3",
+ "commander": "^4.1.1",
+ "he": "^1.2.0",
+ "param-case": "^3.0.3",
+ "relateurl": "^0.2.7",
+ "terser": "^4.6.3"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true
+ }
+ }
+ },
+ "html-webpack-plugin": {
+ "version": "4.0.0-beta.11",
+ "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.11.tgz",
+ "integrity": "sha512-4Xzepf0qWxf8CGg7/WQM5qBB2Lc/NFI7MhU59eUDTkuQp3skZczH4UA1d6oQyDEIoMDgERVhRyTdtUPZ5s5HBg==",
+ "dev": true,
+ "requires": {
+ "html-minifier-terser": "^5.0.1",
+ "loader-utils": "^1.2.3",
+ "lodash": "^4.17.15",
+ "pretty-error": "^2.1.1",
+ "tapable": "^1.1.3",
+ "util.promisify": "1.0.0"
+ },
+ "dependencies": {
+ "util.promisify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz",
+ "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "object.getownpropertydescriptors": "^2.0.3"
+ }
+ }
+ }
+ },
+ "htmlparser2": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
+ "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
+ "dev": true,
+ "requires": {
+ "domelementtype": "^1.3.1",
+ "domhandler": "^2.3.0",
+ "domutils": "^1.5.1",
+ "entities": "^1.1.1",
+ "inherits": "^2.0.1",
+ "readable-stream": "^3.1.1"
+ }
+ },
+ "http-deceiver": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
+ "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=",
+ "dev": true
+ },
+ "http-errors": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+ "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+ "dev": true,
+ "requires": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.1",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.0"
+ }
+ },
+ "http-parser-js": {
+ "version": "0.4.10",
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz",
+ "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=",
+ "dev": true
+ },
+ "http-proxy": {
+ "version": "1.18.0",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz",
+ "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==",
+ "dev": true,
+ "requires": {
+ "eventemitter3": "^4.0.0",
+ "follow-redirects": "^1.0.0",
+ "requires-port": "^1.0.0"
+ }
+ },
+ "http-proxy-middleware": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz",
+ "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==",
+ "dev": true,
+ "requires": {
+ "http-proxy": "^1.17.0",
+ "is-glob": "^4.0.0",
+ "lodash": "^4.17.11",
+ "micromatch": "^3.1.10"
+ }
+ },
+ "http-signature": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "^1.0.0",
+ "jsprim": "^1.2.2",
+ "sshpk": "^1.7.0"
+ }
+ },
+ "https-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
+ "dev": true
+ },
+ "hyphenate-style-name": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz",
+ "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ=="
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "icss-utils": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz",
+ "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.14"
+ }
+ },
+ "identity-obj-proxy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz",
+ "integrity": "sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ=",
+ "dev": true,
+ "requires": {
+ "harmony-reflect": "^1.4.6"
+ }
+ },
+ "ieee754": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+ "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
+ "dev": true
+ },
+ "iferr": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
+ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
+ "dev": true
+ },
+ "ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "dev": true
+ },
+ "immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
+ },
+ "immer": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz",
+ "integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==",
+ "dev": true
+ },
+ "immutability-helper": {
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/immutability-helper/-/immutability-helper-2.9.1.tgz",
+ "integrity": "sha512-r/RmRG8xO06s/k+PIaif2r5rGc3j4Yhc01jSBfwPCXDLYZwp/yxralI37Df1mwmuzcCsen/E/ITKcTEvc1PQmQ==",
+ "requires": {
+ "invariant": "^2.2.0"
+ }
+ },
+ "import-cwd": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
+ "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=",
+ "dev": true,
+ "requires": {
+ "import-from": "^2.1.0"
+ }
+ },
+ "import-fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
+ "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
+ "dev": true,
+ "requires": {
+ "caller-path": "^2.0.0",
+ "resolve-from": "^3.0.0"
+ }
+ },
+ "import-from": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz",
+ "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=",
+ "dev": true,
+ "requires": {
+ "resolve-from": "^3.0.0"
+ }
+ },
+ "import-local": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz",
+ "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==",
+ "dev": true,
+ "requires": {
+ "pkg-dir": "^3.0.0",
+ "resolve-cwd": "^2.0.0"
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true
+ },
+ "indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true
+ },
+ "indexes-of": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
+ "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=",
+ "dev": true
+ },
+ "infer-owner": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
+ "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ },
+ "ini": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
+ "dev": true
+ },
+ "inline-style-prefixer": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz",
+ "integrity": "sha1-hVG45bTVcyROZqNLBPfTIHaitTQ=",
+ "requires": {
+ "bowser": "^1.7.3",
+ "css-in-js-utils": "^2.0.0"
+ }
+ },
+ "inquirer": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.6.tgz",
+ "integrity": "sha512-7SVO4h+QIdMq6XcqIqrNte3gS5MzCCKZdsq9DO4PJziBFNYzP3PGFbDjgadDb//MCahzgjCxvQ/O2wa7kx9o4w==",
+ "dev": true,
+ "requires": {
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^3.0.0",
+ "cli-cursor": "^3.1.0",
+ "cli-width": "^2.0.0",
+ "external-editor": "^3.0.3",
+ "figures": "^3.0.0",
+ "lodash": "^4.17.15",
+ "mute-stream": "0.0.8",
+ "run-async": "^2.4.0",
+ "rxjs": "^6.5.3",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0",
+ "through": "^2.3.6"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+ "dev": true,
+ "requires": {
+ "@types/color-name": "^1.1.1",
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
+ "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "internal-ip": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz",
+ "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==",
+ "dev": true,
+ "requires": {
+ "default-gateway": "^4.2.0",
+ "ipaddr.js": "^1.9.0"
+ }
+ },
+ "invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "invert-kv": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
+ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
+ "dev": true
+ },
+ "ip": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
+ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
+ "dev": true
+ },
+ "ip-regex": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
+ "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=",
+ "dev": true
+ },
+ "ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "dev": true
+ },
+ "is-absolute-url": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz",
+ "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=",
+ "dev": true
+ },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ },
+ "is-arguments": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz",
+ "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==",
+ "dev": true
+ },
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+ "dev": true
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
+ "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA=="
+ },
+ "is-ci": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
+ "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
+ "dev": true,
+ "requires": {
+ "ci-info": "^2.0.0"
+ }
+ },
+ "is-color-stop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz",
+ "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=",
+ "dev": true,
+ "requires": {
+ "css-color-names": "^0.0.4",
+ "hex-color-regex": "^1.1.0",
+ "hsl-regex": "^1.0.0",
+ "hsla-regex": "^1.0.0",
+ "rgb-regex": "^1.0.1",
+ "rgba-regex": "^1.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ },
+ "is-date-object": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY="
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "is-directory": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
+ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=",
+ "dev": true
+ },
+ "is-docker": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz",
+ "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==",
+ "dev": true
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+ "dev": true
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true
+ },
+ "is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ },
+ "is-obj": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
+ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
+ "dev": true
+ },
+ "is-path-cwd": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
+ "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==",
+ "dev": true
+ },
+ "is-path-in-cwd": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz",
+ "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==",
+ "dev": true,
+ "requires": {
+ "is-path-inside": "^2.1.0"
+ }
+ },
+ "is-path-inside": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz",
+ "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==",
+ "dev": true,
+ "requires": {
+ "path-is-inside": "^1.0.2"
+ }
+ },
+ "is-plain-obj": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+ "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
+ "dev": true
+ },
+ "is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "is-promise": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+ "requires": {
+ "has": "^1.0.1"
+ }
+ },
+ "is-regexp": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
+ "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=",
+ "dev": true
+ },
+ "is-resolvable": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz",
+ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==",
+ "dev": true
+ },
+ "is-root": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz",
+ "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==",
+ "dev": true
+ },
+ "is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+ },
+ "is-string": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz",
+ "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==",
+ "dev": true
+ },
+ "is-subset": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz",
+ "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=",
+ "dev": true
+ },
+ "is-svg": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz",
+ "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==",
+ "dev": true,
+ "requires": {
+ "html-comment-regex": "^1.1.0"
+ }
+ },
+ "is-symbol": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
+ "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
+ "requires": {
+ "has-symbols": "^1.0.0"
+ }
+ },
+ "is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+ "dev": true
+ },
+ "is-windows": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+ "dev": true
+ },
+ "is-wsl": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
+ "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=",
+ "dev": true
+ },
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+ },
+ "isomorphic-fetch": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
+ "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
+ "requires": {
+ "node-fetch": "^1.0.1",
+ "whatwg-fetch": ">=0.10.0"
+ }
+ },
+ "isstream": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+ "dev": true
+ },
+ "istanbul-lib-coverage": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz",
+ "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==",
+ "dev": true
+ },
+ "istanbul-lib-instrument": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz",
+ "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==",
+ "dev": true,
+ "requires": {
+ "@babel/generator": "^7.4.0",
+ "@babel/parser": "^7.4.3",
+ "@babel/template": "^7.4.0",
+ "@babel/traverse": "^7.4.3",
+ "@babel/types": "^7.4.0",
+ "istanbul-lib-coverage": "^2.0.5",
+ "semver": "^6.0.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "istanbul-lib-report": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz",
+ "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==",
+ "dev": true,
+ "requires": {
+ "istanbul-lib-coverage": "^2.0.5",
+ "make-dir": "^2.1.0",
+ "supports-color": "^6.1.0"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+ "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "istanbul-lib-source-maps": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz",
+ "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==",
+ "dev": true,
+ "requires": {
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^2.0.5",
+ "make-dir": "^2.1.0",
+ "rimraf": "^2.6.3",
+ "source-map": "^0.6.1"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "istanbul-reports": {
+ "version": "2.2.7",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz",
+ "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==",
+ "dev": true,
+ "requires": {
+ "html-escaper": "^2.0.0"
+ }
+ },
+ "jest": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-24.9.0.tgz",
+ "integrity": "sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw==",
+ "dev": true,
+ "requires": {
+ "import-local": "^2.0.0",
+ "jest-cli": "^24.9.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "jest-cli": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.9.0.tgz",
+ "integrity": "sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg==",
+ "dev": true,
+ "requires": {
+ "@jest/core": "^24.9.0",
+ "@jest/test-result": "^24.9.0",
+ "@jest/types": "^24.9.0",
+ "chalk": "^2.0.1",
+ "exit": "^0.1.2",
+ "import-local": "^2.0.0",
+ "is-ci": "^2.0.0",
+ "jest-config": "^24.9.0",
+ "jest-util": "^24.9.0",
+ "jest-validate": "^24.9.0",
+ "prompts": "^2.0.1",
+ "realpath-native": "^1.1.0",
+ "yargs": "^13.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "jest-changed-files": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.9.0.tgz",
+ "integrity": "sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^24.9.0",
+ "execa": "^1.0.0",
+ "throat": "^4.0.0"
+ }
+ },
+ "jest-config": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.9.0.tgz",
+ "integrity": "sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.1.0",
+ "@jest/test-sequencer": "^24.9.0",
+ "@jest/types": "^24.9.0",
+ "babel-jest": "^24.9.0",
+ "chalk": "^2.0.1",
+ "glob": "^7.1.1",
+ "jest-environment-jsdom": "^24.9.0",
+ "jest-environment-node": "^24.9.0",
+ "jest-get-type": "^24.9.0",
+ "jest-jasmine2": "^24.9.0",
+ "jest-regex-util": "^24.3.0",
+ "jest-resolve": "^24.9.0",
+ "jest-util": "^24.9.0",
+ "jest-validate": "^24.9.0",
+ "micromatch": "^3.1.10",
+ "pretty-format": "^24.9.0",
+ "realpath-native": "^1.1.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "jest-diff": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz",
+ "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.0.1",
+ "diff-sequences": "^24.9.0",
+ "jest-get-type": "^24.9.0",
+ "pretty-format": "^24.9.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "jest-docblock": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.9.0.tgz",
+ "integrity": "sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA==",
+ "dev": true,
+ "requires": {
+ "detect-newline": "^2.1.0"
+ }
+ },
+ "jest-each": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz",
+ "integrity": "sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^24.9.0",
+ "chalk": "^2.0.1",
+ "jest-get-type": "^24.9.0",
+ "jest-util": "^24.9.0",
+ "pretty-format": "^24.9.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "jest-environment-jsdom": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz",
+ "integrity": "sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==",
+ "dev": true,
+ "requires": {
+ "@jest/environment": "^24.9.0",
+ "@jest/fake-timers": "^24.9.0",
+ "@jest/types": "^24.9.0",
+ "jest-mock": "^24.9.0",
+ "jest-util": "^24.9.0",
+ "jsdom": "^11.5.1"
+ }
+ },
+ "jest-environment-jsdom-fourteen": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/jest-environment-jsdom-fourteen/-/jest-environment-jsdom-fourteen-1.0.1.tgz",
+ "integrity": "sha512-DojMX1sY+at5Ep+O9yME34CdidZnO3/zfPh8UW+918C5fIZET5vCjfkegixmsi7AtdYfkr4bPlIzmWnlvQkP7Q==",
+ "dev": true,
+ "requires": {
+ "@jest/environment": "^24.3.0",
+ "@jest/fake-timers": "^24.3.0",
+ "@jest/types": "^24.3.0",
+ "jest-mock": "^24.0.0",
+ "jest-util": "^24.0.0",
+ "jsdom": "^14.1.0"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
+ "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
+ "dev": true
+ },
+ "jsdom": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-14.1.0.tgz",
+ "integrity": "sha512-O901mfJSuTdwU2w3Sn+74T+RnDVP+FuV5fH8tcPWyqrseRAb0s5xOtPgCFiPOtLcyK7CLIJwPyD83ZqQWvA5ng==",
+ "dev": true,
+ "requires": {
+ "abab": "^2.0.0",
+ "acorn": "^6.0.4",
+ "acorn-globals": "^4.3.0",
+ "array-equal": "^1.0.0",
+ "cssom": "^0.3.4",
+ "cssstyle": "^1.1.1",
+ "data-urls": "^1.1.0",
+ "domexception": "^1.0.1",
+ "escodegen": "^1.11.0",
+ "html-encoding-sniffer": "^1.0.2",
+ "nwsapi": "^2.1.3",
+ "parse5": "5.1.0",
+ "pn": "^1.1.0",
+ "request": "^2.88.0",
+ "request-promise-native": "^1.0.5",
+ "saxes": "^3.1.9",
+ "symbol-tree": "^3.2.2",
+ "tough-cookie": "^2.5.0",
+ "w3c-hr-time": "^1.0.1",
+ "w3c-xmlserializer": "^1.1.2",
+ "webidl-conversions": "^4.0.2",
+ "whatwg-encoding": "^1.0.5",
+ "whatwg-mimetype": "^2.3.0",
+ "whatwg-url": "^7.0.0",
+ "ws": "^6.1.2",
+ "xml-name-validator": "^3.0.0"
+ }
+ },
+ "parse5": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz",
+ "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==",
+ "dev": true
+ },
+ "whatwg-url": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
+ "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
+ "dev": true,
+ "requires": {
+ "lodash.sortby": "^4.7.0",
+ "tr46": "^1.0.1",
+ "webidl-conversions": "^4.0.2"
+ }
+ },
+ "ws": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
+ "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
+ "dev": true,
+ "requires": {
+ "async-limiter": "~1.0.0"
+ }
+ }
+ }
+ },
+ "jest-environment-node": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.9.0.tgz",
+ "integrity": "sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==",
+ "dev": true,
+ "requires": {
+ "@jest/environment": "^24.9.0",
+ "@jest/fake-timers": "^24.9.0",
+ "@jest/types": "^24.9.0",
+ "jest-mock": "^24.9.0",
+ "jest-util": "^24.9.0"
+ }
+ },
+ "jest-get-type": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz",
+ "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==",
+ "dev": true
+ },
+ "jest-haste-map": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz",
+ "integrity": "sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^24.9.0",
+ "anymatch": "^2.0.0",
+ "fb-watchman": "^2.0.0",
+ "fsevents": "^1.2.7",
+ "graceful-fs": "^4.1.15",
+ "invariant": "^2.2.4",
+ "jest-serializer": "^24.9.0",
+ "jest-util": "^24.9.0",
+ "jest-worker": "^24.9.0",
+ "micromatch": "^3.1.10",
+ "sane": "^4.0.3",
+ "walker": "^1.0.7"
+ },
+ "dependencies": {
+ "fsevents": {
+ "version": "1.2.11",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz",
+ "integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "bindings": "^1.5.0",
+ "nan": "^2.12.1",
+ "node-pre-gyp": "*"
+ },
+ "dependencies": {
+ "abbrev": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "are-we-there-yet": {
+ "version": "1.1.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "chownr": {
+ "version": "1.1.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "debug": {
+ "version": "3.2.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "deep-extend": {
+ "version": "0.6.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "detect-libc": {
+ "version": "1.0.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "fs-minipass": {
+ "version": "1.2.7",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.6.0"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
+ }
+ },
+ "glob": {
+ "version": "7.1.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "ignore-walk": {
+ "version": "3.0.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minimatch": "^3.0.4"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "ini": {
+ "version": "1.3.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "minipass": {
+ "version": "2.9.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "^5.1.2",
+ "yallist": "^3.0.0"
+ }
+ },
+ "minizlib": {
+ "version": "1.3.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.9.0"
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "needle": {
+ "version": "2.4.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "debug": "^3.2.6",
+ "iconv-lite": "^0.4.4",
+ "sax": "^1.2.4"
+ }
+ },
+ "node-pre-gyp": {
+ "version": "0.14.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "detect-libc": "^1.0.2",
+ "mkdirp": "^0.5.1",
+ "needle": "^2.2.1",
+ "nopt": "^4.0.1",
+ "npm-packlist": "^1.1.6",
+ "npmlog": "^4.0.2",
+ "rc": "^1.2.7",
+ "rimraf": "^2.6.1",
+ "semver": "^5.3.0",
+ "tar": "^4.4.2"
+ }
+ },
+ "nopt": {
+ "version": "4.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "abbrev": "1",
+ "osenv": "^0.1.4"
+ }
+ },
+ "npm-bundled": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "npm-normalize-package-bin": "^1.0.1"
+ }
+ },
+ "npm-normalize-package-bin": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "npm-packlist": {
+ "version": "1.4.7",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ignore-walk": "^3.0.1",
+ "npm-bundled": "^1.0.1"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "osenv": {
+ "version": "0.1.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.0"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "rc": {
+ "version": "1.2.8",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "rimraf": {
+ "version": "2.7.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "sax": {
+ "version": "1.2.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "semver": {
+ "version": "5.7.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "tar": {
+ "version": "4.4.13",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "chownr": "^1.1.1",
+ "fs-minipass": "^1.2.5",
+ "minipass": "^2.8.6",
+ "minizlib": "^1.2.1",
+ "mkdirp": "^0.5.0",
+ "safe-buffer": "^5.1.2",
+ "yallist": "^3.0.3"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "wide-align": {
+ "version": "1.1.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "yallist": {
+ "version": "3.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ }
+ }
+ }
+ }
+ },
+ "jest-jasmine2": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz",
+ "integrity": "sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==",
+ "dev": true,
+ "requires": {
+ "@babel/traverse": "^7.1.0",
+ "@jest/environment": "^24.9.0",
+ "@jest/test-result": "^24.9.0",
+ "@jest/types": "^24.9.0",
+ "chalk": "^2.0.1",
+ "co": "^4.6.0",
+ "expect": "^24.9.0",
+ "is-generator-fn": "^2.0.0",
+ "jest-each": "^24.9.0",
+ "jest-matcher-utils": "^24.9.0",
+ "jest-message-util": "^24.9.0",
+ "jest-runtime": "^24.9.0",
+ "jest-snapshot": "^24.9.0",
+ "jest-util": "^24.9.0",
+ "pretty-format": "^24.9.0",
+ "throat": "^4.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "jest-leak-detector": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz",
+ "integrity": "sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==",
+ "dev": true,
+ "requires": {
+ "jest-get-type": "^24.9.0",
+ "pretty-format": "^24.9.0"
+ }
+ },
+ "jest-matcher-utils": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz",
+ "integrity": "sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.0.1",
+ "jest-diff": "^24.9.0",
+ "jest-get-type": "^24.9.0",
+ "pretty-format": "^24.9.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "jest-message-util": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz",
+ "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@jest/test-result": "^24.9.0",
+ "@jest/types": "^24.9.0",
+ "@types/stack-utils": "^1.0.1",
+ "chalk": "^2.0.1",
+ "micromatch": "^3.1.10",
+ "slash": "^2.0.0",
+ "stack-utils": "^1.0.1"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "jest-mock": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz",
+ "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^24.9.0"
+ }
+ },
+ "jest-pnp-resolver": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz",
+ "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==",
+ "dev": true
+ },
+ "jest-regex-util": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz",
+ "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==",
+ "dev": true
+ },
+ "jest-resolve": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.9.0.tgz",
+ "integrity": "sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^24.9.0",
+ "browser-resolve": "^1.11.3",
+ "chalk": "^2.0.1",
+ "jest-pnp-resolver": "^1.2.1",
+ "realpath-native": "^1.1.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "jest-resolve-dependencies": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz",
+ "integrity": "sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^24.9.0",
+ "jest-regex-util": "^24.3.0",
+ "jest-snapshot": "^24.9.0"
+ }
+ },
+ "jest-runner": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.9.0.tgz",
+ "integrity": "sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==",
+ "dev": true,
+ "requires": {
+ "@jest/console": "^24.7.1",
+ "@jest/environment": "^24.9.0",
+ "@jest/test-result": "^24.9.0",
+ "@jest/types": "^24.9.0",
+ "chalk": "^2.4.2",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.1.15",
+ "jest-config": "^24.9.0",
+ "jest-docblock": "^24.3.0",
+ "jest-haste-map": "^24.9.0",
+ "jest-jasmine2": "^24.9.0",
+ "jest-leak-detector": "^24.9.0",
+ "jest-message-util": "^24.9.0",
+ "jest-resolve": "^24.9.0",
+ "jest-runtime": "^24.9.0",
+ "jest-util": "^24.9.0",
+ "jest-worker": "^24.6.0",
+ "source-map-support": "^0.5.6",
+ "throat": "^4.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "jest-runtime": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.9.0.tgz",
+ "integrity": "sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==",
+ "dev": true,
+ "requires": {
+ "@jest/console": "^24.7.1",
+ "@jest/environment": "^24.9.0",
+ "@jest/source-map": "^24.3.0",
+ "@jest/transform": "^24.9.0",
+ "@jest/types": "^24.9.0",
+ "@types/yargs": "^13.0.0",
+ "chalk": "^2.0.1",
+ "exit": "^0.1.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.1.15",
+ "jest-config": "^24.9.0",
+ "jest-haste-map": "^24.9.0",
+ "jest-message-util": "^24.9.0",
+ "jest-mock": "^24.9.0",
+ "jest-regex-util": "^24.3.0",
+ "jest-resolve": "^24.9.0",
+ "jest-snapshot": "^24.9.0",
+ "jest-util": "^24.9.0",
+ "jest-validate": "^24.9.0",
+ "realpath-native": "^1.1.0",
+ "slash": "^2.0.0",
+ "strip-bom": "^3.0.0",
+ "yargs": "^13.3.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "jest-serializer": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz",
+ "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==",
+ "dev": true
+ },
+ "jest-snapshot": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.9.0.tgz",
+ "integrity": "sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.0.0",
+ "@jest/types": "^24.9.0",
+ "chalk": "^2.0.1",
+ "expect": "^24.9.0",
+ "jest-diff": "^24.9.0",
+ "jest-get-type": "^24.9.0",
+ "jest-matcher-utils": "^24.9.0",
+ "jest-message-util": "^24.9.0",
+ "jest-resolve": "^24.9.0",
+ "mkdirp": "^0.5.1",
+ "natural-compare": "^1.4.0",
+ "pretty-format": "^24.9.0",
+ "semver": "^6.2.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "jest-util": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz",
+ "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==",
+ "dev": true,
+ "requires": {
+ "@jest/console": "^24.9.0",
+ "@jest/fake-timers": "^24.9.0",
+ "@jest/source-map": "^24.9.0",
+ "@jest/test-result": "^24.9.0",
+ "@jest/types": "^24.9.0",
+ "callsites": "^3.0.0",
+ "chalk": "^2.0.1",
+ "graceful-fs": "^4.1.15",
+ "is-ci": "^2.0.0",
+ "mkdirp": "^0.5.1",
+ "slash": "^2.0.0",
+ "source-map": "^0.6.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "jest-validate": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.9.0.tgz",
+ "integrity": "sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^24.9.0",
+ "camelcase": "^5.3.1",
+ "chalk": "^2.0.1",
+ "jest-get-type": "^24.9.0",
+ "leven": "^3.1.0",
+ "pretty-format": "^24.9.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "jest-watch-typeahead": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-0.4.2.tgz",
+ "integrity": "sha512-f7VpLebTdaXs81rg/oj4Vg/ObZy2QtGzAmGLNsqUS5G5KtSN68tFcIsbvNODfNyQxU78g7D8x77o3bgfBTR+2Q==",
+ "dev": true,
+ "requires": {
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^2.4.1",
+ "jest-regex-util": "^24.9.0",
+ "jest-watcher": "^24.3.0",
+ "slash": "^3.0.0",
+ "string-length": "^3.1.0",
+ "strip-ansi": "^5.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true
+ },
+ "string-length": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-3.1.0.tgz",
+ "integrity": "sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA==",
+ "dev": true,
+ "requires": {
+ "astral-regex": "^1.0.0",
+ "strip-ansi": "^5.2.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "jest-watcher": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.9.0.tgz",
+ "integrity": "sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw==",
+ "dev": true,
+ "requires": {
+ "@jest/test-result": "^24.9.0",
+ "@jest/types": "^24.9.0",
+ "@types/yargs": "^13.0.0",
+ "ansi-escapes": "^3.0.0",
+ "chalk": "^2.0.1",
+ "jest-util": "^24.9.0",
+ "string-length": "^2.0.0"
+ },
+ "dependencies": {
+ "ansi-escapes": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+ "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "jest-worker": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz",
+ "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==",
+ "dev": true,
+ "requires": {
+ "merge-stream": "^2.0.0",
+ "supports-color": "^6.1.0"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+ "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "js-file-download": {
+ "version": "0.4.7",
+ "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.7.tgz",
+ "integrity": "sha512-9AQYwIpgTz3BqKQQ9kZldCXd0BekFmxvUguEVJwVlTe9cON1slRaT+hqctEbjoXbz9Aj+8Qgl3841zYWeCstuA=="
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "js-yaml": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+ "dev": true
+ },
+ "jsdom": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz",
+ "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==",
+ "dev": true,
+ "requires": {
+ "abab": "^2.0.0",
+ "acorn": "^5.5.3",
+ "acorn-globals": "^4.1.0",
+ "array-equal": "^1.0.0",
+ "cssom": ">= 0.3.2 < 0.4.0",
+ "cssstyle": "^1.0.0",
+ "data-urls": "^1.0.0",
+ "domexception": "^1.0.1",
+ "escodegen": "^1.9.1",
+ "html-encoding-sniffer": "^1.0.2",
+ "left-pad": "^1.3.0",
+ "nwsapi": "^2.0.7",
+ "parse5": "4.0.0",
+ "pn": "^1.1.0",
+ "request": "^2.87.0",
+ "request-promise-native": "^1.0.5",
+ "sax": "^1.2.4",
+ "symbol-tree": "^3.2.2",
+ "tough-cookie": "^2.3.4",
+ "w3c-hr-time": "^1.0.1",
+ "webidl-conversions": "^4.0.2",
+ "whatwg-encoding": "^1.0.3",
+ "whatwg-mimetype": "^2.1.0",
+ "whatwg-url": "^6.4.1",
+ "ws": "^5.2.0",
+ "xml-name-validator": "^3.0.0"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "5.7.4",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz",
+ "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==",
+ "dev": true
+ }
+ }
+ },
+ "jsesc": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+ "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+ "dev": true
+ },
+ "json-parse-better-errors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+ "dev": true
+ },
+ "json-schema": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+ "dev": true
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "json-stable-stringify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
+ "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
+ "dev": true,
+ "requires": {
+ "jsonify": "~0.0.0"
+ }
+ },
+ "json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+ "dev": true
+ },
+ "json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+ },
+ "json3": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz",
+ "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==",
+ "dev": true
+ },
+ "json5": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz",
+ "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ },
+ "jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "jsonify": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
+ "dev": true
+ },
+ "jsprim": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+ "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.3.0",
+ "json-schema": "0.2.3",
+ "verror": "1.10.0"
+ }
+ },
+ "jsx-ast-utils": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz",
+ "integrity": "sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.0.3",
+ "object.assign": "^4.1.0"
+ }
+ },
+ "jszip": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.1.tgz",
+ "integrity": "sha512-iCMBbo4eE5rb1VCpm5qXOAaUiRKRUKiItn8ah2YQQx9qymmSAY98eyQfioChEYcVQLh0zxJ3wS4A0mh90AVPvw==",
+ "requires": {
+ "lie": "~3.3.0",
+ "pako": "~1.0.2",
+ "readable-stream": "~2.3.6",
+ "set-immediate-shim": "~1.0.1"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "requires": {
+ "immediate": "~3.0.5"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "keycode": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
+ "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ="
+ },
+ "killable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
+ "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ },
+ "kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "dev": true
+ },
+ "last-call-webpack-plugin": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz",
+ "integrity": "sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.5",
+ "webpack-sources": "^1.1.0"
+ }
+ },
+ "lazy-cache": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
+ "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=",
+ "dev": true
+ },
+ "lcid": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
+ "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
+ "dev": true,
+ "requires": {
+ "invert-kv": "^2.0.0"
+ }
+ },
+ "left-pad": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz",
+ "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==",
+ "dev": true
+ },
+ "leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true
+ },
+ "levenary": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz",
+ "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==",
+ "dev": true,
+ "requires": {
+ "leven": "^3.1.0"
+ }
+ },
+ "levn": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2"
+ }
+ },
+ "lie": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
+ "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
+ "requires": {
+ "immediate": "~3.0.5"
+ }
+ },
+ "linear-layout-vector": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/linear-layout-vector/-/linear-layout-vector-0.0.1.tgz",
+ "integrity": "sha1-OYEU1zA7bsx/1rJzr3uEAdi6nHA="
+ },
+ "lines-and-columns": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
+ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
+ "dev": true
+ },
+ "load-json-file": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
+ "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^4.0.0",
+ "pify": "^3.0.0",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "loader-fs-cache": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz",
+ "integrity": "sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA==",
+ "dev": true,
+ "requires": {
+ "find-cache-dir": "^0.1.1",
+ "mkdirp": "^0.5.1"
+ },
+ "dependencies": {
+ "find-cache-dir": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz",
+ "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=",
+ "dev": true,
+ "requires": {
+ "commondir": "^1.0.1",
+ "mkdirp": "^0.5.1",
+ "pkg-dir": "^1.0.0"
+ }
+ },
+ "find-up": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+ "dev": true,
+ "requires": {
+ "path-exists": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "path-exists": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+ "dev": true,
+ "requires": {
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "pkg-dir": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz",
+ "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=",
+ "dev": true,
+ "requires": {
+ "find-up": "^1.0.0"
+ }
+ }
+ }
+ },
+ "loader-runner": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
+ "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==",
+ "dev": true
+ },
+ "loader-utils": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
+ "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+ "dev": true,
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^1.0.1"
+ },
+ "dependencies": {
+ "json5": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ }
+ }
+ },
+ "localforage": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.7.3.tgz",
+ "integrity": "sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ==",
+ "requires": {
+ "lie": "3.1.1"
+ }
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
+ },
+ "lodash-es": {
+ "version": "4.17.11",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.11.tgz",
+ "integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q=="
+ },
+ "lodash._reinterpolate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
+ "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=",
+ "dev": true
+ },
+ "lodash.assignin": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz",
+ "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=",
+ "dev": true
+ },
+ "lodash.bind": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz",
+ "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=",
+ "dev": true
+ },
+ "lodash.defaults": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
+ "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=",
+ "dev": true
+ },
+ "lodash.filter": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz",
+ "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=",
+ "dev": true
+ },
+ "lodash.flatten": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
+ "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=",
+ "dev": true
+ },
+ "lodash.foreach": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
+ "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=",
+ "dev": true
+ },
+ "lodash.isarray": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
+ "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U="
+ },
+ "lodash.isfinite": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.2.0.tgz",
+ "integrity": "sha1-qmn/uTo36C+rDOGIYmVfkXTO0zk="
+ },
+ "lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=",
+ "dev": true
+ },
+ "lodash.map": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz",
+ "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=",
+ "dev": true
+ },
+ "lodash.memoize": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+ "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
+ "dev": true
+ },
+ "lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
+ },
+ "lodash.pick": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
+ "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=",
+ "dev": true
+ },
+ "lodash.reduce": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz",
+ "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=",
+ "dev": true
+ },
+ "lodash.reject": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz",
+ "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=",
+ "dev": true
+ },
+ "lodash.some": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz",
+ "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=",
+ "dev": true
+ },
+ "lodash.sortby": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
+ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
+ "dev": true
+ },
+ "lodash.template": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
+ "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==",
+ "dev": true,
+ "requires": {
+ "lodash._reinterpolate": "^3.0.0",
+ "lodash.templatesettings": "^4.0.0"
+ }
+ },
+ "lodash.templatesettings": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz",
+ "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==",
+ "dev": true,
+ "requires": {
+ "lodash._reinterpolate": "^3.0.0"
+ }
+ },
+ "lodash.throttle": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
+ "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
+ },
+ "lodash.uniq": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
+ "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=",
+ "dev": true
+ },
+ "loglevel": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz",
+ "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==",
+ "dev": true
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "lower-case": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz",
+ "integrity": "sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.10.0"
+ }
+ },
+ "lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "requires": {
+ "yallist": "^3.0.2"
+ },
+ "dependencies": {
+ "yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ }
+ }
+ },
+ "make-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+ "dev": true,
+ "requires": {
+ "pify": "^4.0.1",
+ "semver": "^5.6.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "dev": true
+ }
+ }
+ },
+ "makeerror": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz",
+ "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=",
+ "dev": true,
+ "requires": {
+ "tmpl": "1.0.x"
+ }
+ },
+ "mamacro": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz",
+ "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==",
+ "dev": true
+ },
+ "map-age-cleaner": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
+ "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
+ "dev": true,
+ "requires": {
+ "p-defer": "^1.0.0"
+ }
+ },
+ "map-cache": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
+ "dev": true
+ },
+ "map-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+ "dev": true,
+ "requires": {
+ "object-visit": "^1.0.0"
+ }
+ },
+ "material-ui": {
+ "version": "0.19.4",
+ "resolved": "https://registry.npmjs.org/material-ui/-/material-ui-0.19.4.tgz",
+ "integrity": "sha1-ypzcqKqLtZTfrF2zjsn/BFoyNYc=",
+ "requires": {
+ "babel-runtime": "^6.23.0",
+ "inline-style-prefixer": "^3.0.2",
+ "keycode": "^2.1.8",
+ "lodash.merge": "^4.6.0",
+ "lodash.throttle": "^4.1.1",
+ "prop-types": "^15.5.7",
+ "react-event-listener": "^0.5.1",
+ "react-transition-group": "^1.2.1",
+ "recompose": "^0.26.0",
+ "simple-assign": "^0.1.0",
+ "warning": "^3.0.0"
+ }
+ },
+ "material-ui-chip-input": {
+ "version": "0.18.8",
+ "resolved": "https://registry.npmjs.org/material-ui-chip-input/-/material-ui-chip-input-0.18.8.tgz",
+ "integrity": "sha512-QrKHmnleXYtVOn7OTPpbe2CJrXf2eIjys2STj9FD1ARSWKJv+bjiby0xNG4qBhDNpmcM8q/XC7tT30wdrisDdQ==",
+ "requires": {
+ "prop-types": "^15.5.7"
+ }
+ },
+ "material-ui-superselectfield": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/material-ui-superselectfield/-/material-ui-superselectfield-1.10.0.tgz",
+ "integrity": "sha512-kCh40oTRJMP1BKL1fgARNhVyfvuyTeDKN9sGjL+gsH9F1BisZedC/CxyMXYQNHsNBZBA4xcK7MYSo4WZ+ENeZA==",
+ "requires": {
+ "react-infinite": "^0.13.0"
+ }
+ },
+ "md5.js": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+ "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+ "dev": true,
+ "requires": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "mdn-data": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz",
+ "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==",
+ "dev": true
+ },
+ "media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+ "dev": true
+ },
+ "mem": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz",
+ "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==",
+ "dev": true,
+ "requires": {
+ "map-age-cleaner": "^0.1.1",
+ "mimic-fn": "^2.0.0",
+ "p-is-promise": "^2.0.0"
+ }
+ },
+ "memory-fs": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
+ "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
+ "dev": true,
+ "requires": {
+ "errno": "^0.1.3",
+ "readable-stream": "^2.0.1"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "merge-deep": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.2.tgz",
+ "integrity": "sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA==",
+ "dev": true,
+ "requires": {
+ "arr-union": "^3.1.0",
+ "clone-deep": "^0.2.4",
+ "kind-of": "^3.0.2"
+ }
+ },
+ "merge-descriptors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
+ "dev": true
+ },
+ "merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
+ "merge2": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz",
+ "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==",
+ "dev": true
+ },
+ "methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
+ "dev": true
+ },
+ "microevent.ts": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz",
+ "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.3.1",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "extglob": "^2.0.4",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^6.0.2",
+ "nanomatch": "^1.2.9",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true
+ }
+ }
+ },
+ "miller-rabin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
+ "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.0.0",
+ "brorand": "^1.0.1"
+ }
+ },
+ "mime": {
+ "version": "2.4.4",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
+ "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==",
+ "dev": true
+ },
+ "mime-db": {
+ "version": "1.43.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
+ "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "2.1.26",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
+ "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
+ "dev": true,
+ "requires": {
+ "mime-db": "1.43.0"
+ }
+ },
+ "mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true
+ },
+ "mini-css-extract-plugin": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz",
+ "integrity": "sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.1.0",
+ "normalize-url": "1.9.1",
+ "schema-utils": "^1.0.0",
+ "webpack-sources": "^1.1.0"
+ },
+ "dependencies": {
+ "schema-utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-errors": "^1.0.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ }
+ }
+ },
+ "minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+ "dev": true
+ },
+ "minimalistic-crypto-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
+ },
+ "minipass": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz",
+ "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==",
+ "dev": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
+ "minipass-collect": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
+ "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
+ "dev": true,
+ "requires": {
+ "minipass": "^3.0.0"
+ }
+ },
+ "minipass-flush": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
+ "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
+ "dev": true,
+ "requires": {
+ "minipass": "^3.0.0"
+ }
+ },
+ "minipass-pipeline": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz",
+ "integrity": "sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA==",
+ "dev": true,
+ "requires": {
+ "minipass": "^3.0.0"
+ }
+ },
+ "mississippi": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
+ "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
+ "dev": true,
+ "requires": {
+ "concat-stream": "^1.5.0",
+ "duplexify": "^3.4.2",
+ "end-of-stream": "^1.1.0",
+ "flush-write-stream": "^1.0.0",
+ "from2": "^2.1.0",
+ "parallel-transform": "^1.1.0",
+ "pump": "^3.0.0",
+ "pumpify": "^1.3.3",
+ "stream-each": "^1.1.0",
+ "through2": "^2.0.0"
+ }
+ },
+ "mixin-deep": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+ "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
+ "dev": true,
+ "requires": {
+ "for-in": "^1.0.2",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "mixin-object": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz",
+ "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=",
+ "dev": true,
+ "requires": {
+ "for-in": "^0.1.3",
+ "is-extendable": "^0.1.1"
+ },
+ "dependencies": {
+ "for-in": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz",
+ "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=",
+ "dev": true
+ }
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
+ "move-concurrently": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
+ "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.1.1",
+ "copy-concurrently": "^1.0.0",
+ "fs-write-stream-atomic": "^1.0.8",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.4",
+ "run-queue": "^1.0.3"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "multicast-dns": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz",
+ "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==",
+ "dev": true,
+ "requires": {
+ "dns-packet": "^1.3.1",
+ "thunky": "^1.0.2"
+ }
+ },
+ "multicast-dns-service-types": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
+ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
+ "dev": true
+ },
+ "mute-stream": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
+ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
+ "dev": true
+ },
+ "nan": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
+ "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==",
+ "dev": true,
+ "optional": true
+ },
+ "nanomatch": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+ "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "fragment-cache": "^0.2.1",
+ "is-windows": "^1.0.2",
+ "kind-of": "^6.0.2",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true
+ }
+ }
+ },
+ "natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+ "dev": true
+ },
+ "negotiator": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
+ "dev": true
+ },
+ "neo-async": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
+ "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
+ "dev": true
+ },
+ "next-tick": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
+ "dev": true
+ },
+ "nice-try": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+ "dev": true
+ },
+ "no-case": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.3.tgz",
+ "integrity": "sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw==",
+ "dev": true,
+ "requires": {
+ "lower-case": "^2.0.1",
+ "tslib": "^1.10.0"
+ }
+ },
+ "node-fetch": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
+ "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
+ "requires": {
+ "encoding": "^0.1.11",
+ "is-stream": "^1.0.1"
+ }
+ },
+ "node-forge": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz",
+ "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==",
+ "dev": true
+ },
+ "node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=",
+ "dev": true
+ },
+ "node-libs-browser": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
+ "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==",
+ "dev": true,
+ "requires": {
+ "assert": "^1.1.1",
+ "browserify-zlib": "^0.2.0",
+ "buffer": "^4.3.0",
+ "console-browserify": "^1.1.0",
+ "constants-browserify": "^1.0.0",
+ "crypto-browserify": "^3.11.0",
+ "domain-browser": "^1.1.1",
+ "events": "^3.0.0",
+ "https-browserify": "^1.0.0",
+ "os-browserify": "^0.3.0",
+ "path-browserify": "0.0.1",
+ "process": "^0.11.10",
+ "punycode": "^1.2.4",
+ "querystring-es3": "^0.2.0",
+ "readable-stream": "^2.3.3",
+ "stream-browserify": "^2.0.1",
+ "stream-http": "^2.7.2",
+ "string_decoder": "^1.0.0",
+ "timers-browserify": "^2.0.4",
+ "tty-browserify": "0.0.0",
+ "url": "^0.11.0",
+ "util": "^0.11.0",
+ "vm-browserify": "^1.0.1"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ },
+ "dependencies": {
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "util": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
+ "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3"
+ }
+ }
+ }
+ },
+ "node-modules-regexp": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz",
+ "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=",
+ "dev": true
+ },
+ "node-notifier": {
+ "version": "5.4.3",
+ "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz",
+ "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==",
+ "dev": true,
+ "requires": {
+ "growly": "^1.3.0",
+ "is-wsl": "^1.1.0",
+ "semver": "^5.5.0",
+ "shellwords": "^0.1.1",
+ "which": "^1.3.0"
+ }
+ },
+ "node-releases": {
+ "version": "1.1.50",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.50.tgz",
+ "integrity": "sha512-lgAmPv9eYZ0bGwUYAKlr8MG6K4CvWliWqnkcT2P8mMAgVrH3lqfBPorFlxiG1pHQnqmavJZ9vbMXUTNyMLbrgQ==",
+ "dev": true,
+ "requires": {
+ "semver": "^6.3.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "normalize-package-data": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+ "dev": true,
+ "requires": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "normalize-path": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+ "dev": true,
+ "requires": {
+ "remove-trailing-separator": "^1.0.1"
+ }
+ },
+ "normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
+ "dev": true
+ },
+ "normalize-url": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz",
+ "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=",
+ "dev": true,
+ "requires": {
+ "object-assign": "^4.0.1",
+ "prepend-http": "^1.0.0",
+ "query-string": "^4.1.0",
+ "sort-keys": "^1.0.0"
+ }
+ },
+ "npm-run-path": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+ "dev": true,
+ "requires": {
+ "path-key": "^2.0.0"
+ }
+ },
+ "nth-check": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
+ "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==",
+ "dev": true,
+ "requires": {
+ "boolbase": "~1.0.0"
+ }
+ },
+ "num2fraction": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
+ "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=",
+ "dev": true
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+ "dev": true
+ },
+ "nwsapi": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz",
+ "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==",
+ "dev": true
+ },
+ "oauth-sign": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
+ "dev": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+ },
+ "object-copy": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+ "dev": true,
+ "requires": {
+ "copy-descriptor": "^0.1.0",
+ "define-property": "^0.2.5",
+ "kind-of": "^3.0.3"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "object-hash": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.3.tgz",
+ "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==",
+ "dev": true
+ },
+ "object-inspect": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
+ "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==",
+ "dev": true
+ },
+ "object-is": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz",
+ "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY="
+ },
+ "object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
+ },
+ "object-path": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.4.tgz",
+ "integrity": "sha1-NwrnUvvzfePqcKhhwju6iRVpGUk=",
+ "dev": true
+ },
+ "object-visit": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.0"
+ }
+ },
+ "object.assign": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
+ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
+ "requires": {
+ "define-properties": "^1.1.2",
+ "function-bind": "^1.1.1",
+ "has-symbols": "^1.0.0",
+ "object-keys": "^1.0.11"
+ }
+ },
+ "object.entries": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz",
+ "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==",
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.12.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3"
+ }
+ },
+ "object.fromentries": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz",
+ "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==",
+ "requires": {
+ "define-properties": "^1.1.2",
+ "es-abstract": "^1.11.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.1"
+ }
+ },
+ "object.getownpropertydescriptors": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz",
+ "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.0-next.1"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.17.4",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz",
+ "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1",
+ "is-callable": "^1.1.5",
+ "is-regex": "^1.0.5",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimleft": "^2.1.1",
+ "string.prototype.trimright": "^2.1.1"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
+ "requires": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ }
+ },
+ "has-symbols": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
+ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
+ "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
+ "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ }
+ }
+ },
+ "object.pick": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "object.values": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz",
+ "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==",
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.12.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3"
+ }
+ },
+ "obuf": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
+ "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
+ "dev": true
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "dev": true,
+ "requires": {
+ "ee-first": "1.1.1"
+ }
+ },
+ "on-headers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+ "dev": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "onetime": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
+ "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
+ "dev": true,
+ "requires": {
+ "mimic-fn": "^2.1.0"
+ }
+ },
+ "open": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-7.0.2.tgz",
+ "integrity": "sha512-70E/pFTPr7nZ9nLDPNTcj3IVqnNvKuP4VsBmoKV9YGTnChe0mlS3C4qM7qKarhZ8rGaHKLfo+vBTHXDp6ZSyLQ==",
+ "dev": true,
+ "requires": {
+ "is-docker": "^2.0.0",
+ "is-wsl": "^2.1.1"
+ },
+ "dependencies": {
+ "is-wsl": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz",
+ "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==",
+ "dev": true
+ }
+ }
+ },
+ "openseadragon": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/openseadragon/-/openseadragon-2.4.0.tgz",
+ "integrity": "sha512-e2fsoEiCDkmwc3vyrv396lkYu4c9CGlpbSX4kYE07eW0ZlEF5DnLbeY2PhqPTskkC1jlsPu2TGZwaZ9VhaAn/w=="
+ },
+ "opn": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz",
+ "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==",
+ "dev": true,
+ "requires": {
+ "is-wsl": "^1.1.0"
+ }
+ },
+ "optimize-css-assets-webpack-plugin": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz",
+ "integrity": "sha512-q9fbvCRS6EYtUKKSwI87qm2IxlyJK5b4dygW1rKUBT6mMDhdG5e5bZT63v6tnJR9F9FB/H5a0HTmtw+laUBxKA==",
+ "dev": true,
+ "requires": {
+ "cssnano": "^4.1.10",
+ "last-call-webpack-plugin": "^3.0.0"
+ }
+ },
+ "optionator": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
+ "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+ "dev": true,
+ "requires": {
+ "deep-is": "~0.1.3",
+ "fast-levenshtein": "~2.0.6",
+ "levn": "~0.3.0",
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2",
+ "word-wrap": "~1.2.3"
+ }
+ },
+ "original": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
+ "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
+ "dev": true,
+ "requires": {
+ "url-parse": "^1.4.3"
+ }
+ },
+ "os-browserify": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
+ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
+ "dev": true
+ },
+ "os-locale": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
+ "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
+ "dev": true,
+ "requires": {
+ "execa": "^1.0.0",
+ "lcid": "^2.0.0",
+ "mem": "^4.0.0"
+ }
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+ "dev": true
+ },
+ "p-defer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
+ "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
+ "dev": true
+ },
+ "p-each-series": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz",
+ "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=",
+ "dev": true,
+ "requires": {
+ "p-reduce": "^1.0.0"
+ }
+ },
+ "p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+ "dev": true
+ },
+ "p-is-promise": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz",
+ "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==",
+ "dev": true
+ },
+ "p-limit": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
+ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-map": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
+ "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
+ "dev": true,
+ "requires": {
+ "aggregate-error": "^3.0.0"
+ }
+ },
+ "p-reduce": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz",
+ "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=",
+ "dev": true
+ },
+ "p-retry": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz",
+ "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==",
+ "dev": true,
+ "requires": {
+ "retry": "^0.12.0"
+ }
+ },
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true
+ },
+ "pako": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz",
+ "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw=="
+ },
+ "paper": {
+ "version": "0.11.8",
+ "resolved": "https://registry.npmjs.org/paper/-/paper-0.11.8.tgz",
+ "integrity": "sha512-3O5o8SntwieIlBmHjWS4hAYESiZ0yb++Outz9Rr5AN/ZFK7jfVGLANjqyIvEKIpGMImf2fQCkW64n8dkYheP5g=="
+ },
+ "parallel-transform": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
+ "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
+ "dev": true,
+ "requires": {
+ "cyclist": "^1.0.1",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.1.5"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "param-case": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.3.tgz",
+ "integrity": "sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA==",
+ "dev": true,
+ "requires": {
+ "dot-case": "^3.0.3",
+ "tslib": "^1.10.0"
+ }
+ },
+ "parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "requires": {
+ "callsites": "^3.0.0"
+ },
+ "dependencies": {
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true
+ }
+ }
+ },
+ "parse-asn1": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
+ "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==",
+ "dev": true,
+ "requires": {
+ "asn1.js": "^4.0.0",
+ "browserify-aes": "^1.0.0",
+ "create-hash": "^1.1.0",
+ "evp_bytestokey": "^1.0.0",
+ "pbkdf2": "^3.0.3",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+ "dev": true,
+ "requires": {
+ "error-ex": "^1.3.1",
+ "json-parse-better-errors": "^1.0.1"
+ }
+ },
+ "parse5": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
+ "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==",
+ "dev": true
+ },
+ "parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "dev": true
+ },
+ "pascal-case": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.1.tgz",
+ "integrity": "sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA==",
+ "dev": true,
+ "requires": {
+ "no-case": "^3.0.3",
+ "tslib": "^1.10.0"
+ }
+ },
+ "pascalcase": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
+ "dev": true
+ },
+ "path-browserify": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
+ "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==",
+ "dev": true
+ },
+ "path-dirname": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
+ "dev": true
+ },
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "dev": true
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-is-inside": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+ "dev": true
+ },
+ "path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+ "dev": true
+ },
+ "path-to-regexp": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
+ "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
+ "requires": {
+ "isarray": "0.0.1"
+ }
+ },
+ "path-type": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+ "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+ "dev": true,
+ "requires": {
+ "pify": "^3.0.0"
+ }
+ },
+ "pbkdf2": {
+ "version": "3.0.17",
+ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
+ "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
+ "dev": true,
+ "requires": {
+ "create-hash": "^1.1.2",
+ "create-hmac": "^1.1.4",
+ "ripemd160": "^2.0.1",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ }
+ },
+ "performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
+ "dev": true
+ },
+ "picomatch": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz",
+ "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==",
+ "dev": true
+ },
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "dev": true
+ },
+ "pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+ "dev": true
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "dev": true,
+ "requires": {
+ "pinkie": "^2.0.0"
+ }
+ },
+ "pirates": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz",
+ "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==",
+ "dev": true,
+ "requires": {
+ "node-modules-regexp": "^1.0.0"
+ }
+ },
+ "pkg-dir": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+ "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+ "dev": true,
+ "requires": {
+ "find-up": "^3.0.0"
+ }
+ },
+ "pkg-up": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
+ "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==",
+ "dev": true,
+ "requires": {
+ "find-up": "^3.0.0"
+ }
+ },
+ "pn": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz",
+ "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
+ "dev": true
+ },
+ "pnp-webpack-plugin": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.0.tgz",
+ "integrity": "sha512-ZcMGn/xF/fCOq+9kWMP9vVVxjIkMCja72oy3lziR7UHy0hHFZ57iVpQ71OtveVbmzeCmphBg8pxNdk/hlK99aQ==",
+ "dev": true,
+ "requires": {
+ "ts-pnp": "^1.1.2"
+ }
+ },
+ "portfinder": {
+ "version": "1.0.25",
+ "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz",
+ "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==",
+ "dev": true,
+ "requires": {
+ "async": "^2.6.2",
+ "debug": "^3.1.1",
+ "mkdirp": "^0.5.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ }
+ }
+ },
+ "posix-character-classes": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
+ "dev": true
+ },
+ "postcss": {
+ "version": "7.0.27",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz",
+ "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.2",
+ "source-map": "^0.6.1",
+ "supports-color": "^6.1.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+ "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "postcss-attribute-case-insensitive": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz",
+ "integrity": "sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2",
+ "postcss-selector-parser": "^6.0.2"
+ }
+ },
+ "postcss-browser-comments": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-3.0.0.tgz",
+ "integrity": "sha512-qfVjLfq7HFd2e0HW4s1dvU8X080OZdG46fFbIBFjW7US7YPDcWfRvdElvwMJr2LI6hMmD+7LnH2HcmXTs+uOig==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7"
+ }
+ },
+ "postcss-calc": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.2.tgz",
+ "integrity": "sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.27",
+ "postcss-selector-parser": "^6.0.2",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
+ "postcss-color-functional-notation": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz",
+ "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2",
+ "postcss-values-parser": "^2.0.0"
+ }
+ },
+ "postcss-color-gray": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz",
+ "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==",
+ "dev": true,
+ "requires": {
+ "@csstools/convert-colors": "^1.4.0",
+ "postcss": "^7.0.5",
+ "postcss-values-parser": "^2.0.0"
+ }
+ },
+ "postcss-color-hex-alpha": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz",
+ "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.14",
+ "postcss-values-parser": "^2.0.1"
+ }
+ },
+ "postcss-color-mod-function": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz",
+ "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==",
+ "dev": true,
+ "requires": {
+ "@csstools/convert-colors": "^1.4.0",
+ "postcss": "^7.0.2",
+ "postcss-values-parser": "^2.0.0"
+ }
+ },
+ "postcss-color-rebeccapurple": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz",
+ "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2",
+ "postcss-values-parser": "^2.0.0"
+ }
+ },
+ "postcss-colormin": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz",
+ "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.0.0",
+ "color": "^3.0.0",
+ "has": "^1.0.0",
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-convert-values": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz",
+ "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-custom-media": {
+ "version": "7.0.8",
+ "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz",
+ "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.14"
+ }
+ },
+ "postcss-custom-properties": {
+ "version": "8.0.11",
+ "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz",
+ "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.17",
+ "postcss-values-parser": "^2.0.1"
+ }
+ },
+ "postcss-custom-selectors": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz",
+ "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2",
+ "postcss-selector-parser": "^5.0.0-rc.3"
+ },
+ "dependencies": {
+ "cssesc": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz",
+ "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==",
+ "dev": true
+ },
+ "postcss-selector-parser": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz",
+ "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==",
+ "dev": true,
+ "requires": {
+ "cssesc": "^2.0.0",
+ "indexes-of": "^1.0.1",
+ "uniq": "^1.0.1"
+ }
+ }
+ }
+ },
+ "postcss-dir-pseudo-class": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz",
+ "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2",
+ "postcss-selector-parser": "^5.0.0-rc.3"
+ },
+ "dependencies": {
+ "cssesc": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz",
+ "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==",
+ "dev": true
+ },
+ "postcss-selector-parser": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz",
+ "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==",
+ "dev": true,
+ "requires": {
+ "cssesc": "^2.0.0",
+ "indexes-of": "^1.0.1",
+ "uniq": "^1.0.1"
+ }
+ }
+ }
+ },
+ "postcss-discard-comments": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz",
+ "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.0"
+ }
+ },
+ "postcss-discard-duplicates": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz",
+ "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.0"
+ }
+ },
+ "postcss-discard-empty": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz",
+ "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.0"
+ }
+ },
+ "postcss-discard-overridden": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz",
+ "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.0"
+ }
+ },
+ "postcss-double-position-gradients": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz",
+ "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.5",
+ "postcss-values-parser": "^2.0.0"
+ }
+ },
+ "postcss-env-function": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz",
+ "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2",
+ "postcss-values-parser": "^2.0.0"
+ }
+ },
+ "postcss-flexbugs-fixes": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.1.0.tgz",
+ "integrity": "sha512-jr1LHxQvStNNAHlgco6PzY308zvLklh7SJVYuWUwyUQncofaAlD2l+P/gxKHOdqWKe7xJSkVLFF/2Tp+JqMSZA==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.0"
+ }
+ },
+ "postcss-focus-visible": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz",
+ "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2"
+ }
+ },
+ "postcss-focus-within": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz",
+ "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2"
+ }
+ },
+ "postcss-font-variant": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz",
+ "integrity": "sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2"
+ }
+ },
+ "postcss-gap-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz",
+ "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2"
+ }
+ },
+ "postcss-image-set-function": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz",
+ "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2",
+ "postcss-values-parser": "^2.0.0"
+ }
+ },
+ "postcss-initial": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz",
+ "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==",
+ "dev": true,
+ "requires": {
+ "lodash.template": "^4.5.0",
+ "postcss": "^7.0.2"
+ }
+ },
+ "postcss-lab-function": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz",
+ "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==",
+ "dev": true,
+ "requires": {
+ "@csstools/convert-colors": "^1.4.0",
+ "postcss": "^7.0.2",
+ "postcss-values-parser": "^2.0.0"
+ }
+ },
+ "postcss-load-config": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz",
+ "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==",
+ "dev": true,
+ "requires": {
+ "cosmiconfig": "^5.0.0",
+ "import-cwd": "^2.0.0"
+ }
+ },
+ "postcss-loader": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz",
+ "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.1.0",
+ "postcss": "^7.0.0",
+ "postcss-load-config": "^2.0.0",
+ "schema-utils": "^1.0.0"
+ },
+ "dependencies": {
+ "schema-utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-errors": "^1.0.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ }
+ }
+ },
+ "postcss-logical": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz",
+ "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2"
+ }
+ },
+ "postcss-media-minmax": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz",
+ "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2"
+ }
+ },
+ "postcss-merge-longhand": {
+ "version": "4.0.11",
+ "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz",
+ "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==",
+ "dev": true,
+ "requires": {
+ "css-color-names": "0.0.4",
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0",
+ "stylehacks": "^4.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-merge-rules": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz",
+ "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.0.0",
+ "caniuse-api": "^3.0.0",
+ "cssnano-util-same-parent": "^4.0.0",
+ "postcss": "^7.0.0",
+ "postcss-selector-parser": "^3.0.0",
+ "vendors": "^1.0.0"
+ },
+ "dependencies": {
+ "postcss-selector-parser": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz",
+ "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==",
+ "dev": true,
+ "requires": {
+ "dot-prop": "^5.2.0",
+ "indexes-of": "^1.0.1",
+ "uniq": "^1.0.1"
+ }
+ }
+ }
+ },
+ "postcss-minify-font-values": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz",
+ "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-minify-gradients": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz",
+ "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==",
+ "dev": true,
+ "requires": {
+ "cssnano-util-get-arguments": "^4.0.0",
+ "is-color-stop": "^1.0.0",
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-minify-params": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz",
+ "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==",
+ "dev": true,
+ "requires": {
+ "alphanum-sort": "^1.0.0",
+ "browserslist": "^4.0.0",
+ "cssnano-util-get-arguments": "^4.0.0",
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0",
+ "uniqs": "^2.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-minify-selectors": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz",
+ "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==",
+ "dev": true,
+ "requires": {
+ "alphanum-sort": "^1.0.0",
+ "has": "^1.0.0",
+ "postcss": "^7.0.0",
+ "postcss-selector-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "postcss-selector-parser": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz",
+ "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==",
+ "dev": true,
+ "requires": {
+ "dot-prop": "^5.2.0",
+ "indexes-of": "^1.0.1",
+ "uniq": "^1.0.1"
+ }
+ }
+ }
+ },
+ "postcss-modules-extract-imports": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz",
+ "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.5"
+ }
+ },
+ "postcss-modules-local-by-default": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz",
+ "integrity": "sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==",
+ "dev": true,
+ "requires": {
+ "icss-utils": "^4.1.1",
+ "postcss": "^7.0.16",
+ "postcss-selector-parser": "^6.0.2",
+ "postcss-value-parser": "^4.0.0"
+ }
+ },
+ "postcss-modules-scope": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.1.1.tgz",
+ "integrity": "sha512-OXRUPecnHCg8b9xWvldG/jUpRIGPNRka0r4D4j0ESUU2/5IOnpsjfPPmDprM3Ih8CgZ8FXjWqaniK5v4rWt3oQ==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.6",
+ "postcss-selector-parser": "^6.0.0"
+ }
+ },
+ "postcss-modules-values": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz",
+ "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==",
+ "dev": true,
+ "requires": {
+ "icss-utils": "^4.0.0",
+ "postcss": "^7.0.6"
+ }
+ },
+ "postcss-nesting": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz",
+ "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2"
+ }
+ },
+ "postcss-normalize": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-8.0.1.tgz",
+ "integrity": "sha512-rt9JMS/m9FHIRroDDBGSMsyW1c0fkvOJPy62ggxSHUldJO7B195TqFMqIf+lY5ezpDcYOV4j86aUp3/XbxzCCQ==",
+ "dev": true,
+ "requires": {
+ "@csstools/normalize.css": "^10.1.0",
+ "browserslist": "^4.6.2",
+ "postcss": "^7.0.17",
+ "postcss-browser-comments": "^3.0.0",
+ "sanitize.css": "^10.0.0"
+ }
+ },
+ "postcss-normalize-charset": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz",
+ "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.0"
+ }
+ },
+ "postcss-normalize-display-values": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz",
+ "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==",
+ "dev": true,
+ "requires": {
+ "cssnano-util-get-match": "^4.0.0",
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-normalize-positions": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz",
+ "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==",
+ "dev": true,
+ "requires": {
+ "cssnano-util-get-arguments": "^4.0.0",
+ "has": "^1.0.0",
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-normalize-repeat-style": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz",
+ "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==",
+ "dev": true,
+ "requires": {
+ "cssnano-util-get-arguments": "^4.0.0",
+ "cssnano-util-get-match": "^4.0.0",
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-normalize-string": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz",
+ "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.0",
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-normalize-timing-functions": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz",
+ "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==",
+ "dev": true,
+ "requires": {
+ "cssnano-util-get-match": "^4.0.0",
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-normalize-unicode": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz",
+ "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.0.0",
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-normalize-url": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz",
+ "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==",
+ "dev": true,
+ "requires": {
+ "is-absolute-url": "^2.0.0",
+ "normalize-url": "^3.0.0",
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "normalize-url": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz",
+ "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==",
+ "dev": true
+ },
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-normalize-whitespace": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz",
+ "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-ordered-values": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz",
+ "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==",
+ "dev": true,
+ "requires": {
+ "cssnano-util-get-arguments": "^4.0.0",
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-overflow-shorthand": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz",
+ "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2"
+ }
+ },
+ "postcss-page-break": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz",
+ "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2"
+ }
+ },
+ "postcss-place": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz",
+ "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2",
+ "postcss-values-parser": "^2.0.0"
+ }
+ },
+ "postcss-preset-env": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz",
+ "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==",
+ "dev": true,
+ "requires": {
+ "autoprefixer": "^9.6.1",
+ "browserslist": "^4.6.4",
+ "caniuse-lite": "^1.0.30000981",
+ "css-blank-pseudo": "^0.1.4",
+ "css-has-pseudo": "^0.10.0",
+ "css-prefers-color-scheme": "^3.1.1",
+ "cssdb": "^4.4.0",
+ "postcss": "^7.0.17",
+ "postcss-attribute-case-insensitive": "^4.0.1",
+ "postcss-color-functional-notation": "^2.0.1",
+ "postcss-color-gray": "^5.0.0",
+ "postcss-color-hex-alpha": "^5.0.3",
+ "postcss-color-mod-function": "^3.0.3",
+ "postcss-color-rebeccapurple": "^4.0.1",
+ "postcss-custom-media": "^7.0.8",
+ "postcss-custom-properties": "^8.0.11",
+ "postcss-custom-selectors": "^5.1.2",
+ "postcss-dir-pseudo-class": "^5.0.0",
+ "postcss-double-position-gradients": "^1.0.0",
+ "postcss-env-function": "^2.0.2",
+ "postcss-focus-visible": "^4.0.0",
+ "postcss-focus-within": "^3.0.0",
+ "postcss-font-variant": "^4.0.0",
+ "postcss-gap-properties": "^2.0.0",
+ "postcss-image-set-function": "^3.0.1",
+ "postcss-initial": "^3.0.0",
+ "postcss-lab-function": "^2.0.1",
+ "postcss-logical": "^3.0.0",
+ "postcss-media-minmax": "^4.0.0",
+ "postcss-nesting": "^7.0.0",
+ "postcss-overflow-shorthand": "^2.0.0",
+ "postcss-page-break": "^2.0.0",
+ "postcss-place": "^4.0.1",
+ "postcss-pseudo-class-any-link": "^6.0.0",
+ "postcss-replace-overflow-wrap": "^3.0.0",
+ "postcss-selector-matches": "^4.0.0",
+ "postcss-selector-not": "^4.0.0"
+ }
+ },
+ "postcss-pseudo-class-any-link": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz",
+ "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2",
+ "postcss-selector-parser": "^5.0.0-rc.3"
+ },
+ "dependencies": {
+ "cssesc": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz",
+ "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==",
+ "dev": true
+ },
+ "postcss-selector-parser": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz",
+ "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==",
+ "dev": true,
+ "requires": {
+ "cssesc": "^2.0.0",
+ "indexes-of": "^1.0.1",
+ "uniq": "^1.0.1"
+ }
+ }
+ }
+ },
+ "postcss-reduce-initial": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz",
+ "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.0.0",
+ "caniuse-api": "^3.0.0",
+ "has": "^1.0.0",
+ "postcss": "^7.0.0"
+ }
+ },
+ "postcss-reduce-transforms": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz",
+ "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==",
+ "dev": true,
+ "requires": {
+ "cssnano-util-get-match": "^4.0.0",
+ "has": "^1.0.0",
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-replace-overflow-wrap": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz",
+ "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.2"
+ }
+ },
+ "postcss-safe-parser": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.1.tgz",
+ "integrity": "sha512-xZsFA3uX8MO3yAda03QrG3/Eg1LN3EPfjjf07vke/46HERLZyHrTsQ9E1r1w1W//fWEhtYNndo2hQplN2cVpCQ==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.0"
+ }
+ },
+ "postcss-selector-matches": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz",
+ "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "postcss": "^7.0.2"
+ }
+ },
+ "postcss-selector-not": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz",
+ "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "postcss": "^7.0.2"
+ }
+ },
+ "postcss-selector-parser": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz",
+ "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==",
+ "dev": true,
+ "requires": {
+ "cssesc": "^3.0.0",
+ "indexes-of": "^1.0.1",
+ "uniq": "^1.0.1"
+ }
+ },
+ "postcss-svgo": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz",
+ "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==",
+ "dev": true,
+ "requires": {
+ "is-svg": "^3.0.0",
+ "postcss": "^7.0.0",
+ "postcss-value-parser": "^3.0.0",
+ "svgo": "^1.0.0"
+ },
+ "dependencies": {
+ "postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-unique-selectors": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz",
+ "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==",
+ "dev": true,
+ "requires": {
+ "alphanum-sort": "^1.0.0",
+ "postcss": "^7.0.0",
+ "uniqs": "^2.0.0"
+ }
+ },
+ "postcss-value-parser": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz",
+ "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==",
+ "dev": true
+ },
+ "postcss-values-parser": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz",
+ "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==",
+ "dev": true,
+ "requires": {
+ "flatten": "^1.0.2",
+ "indexes-of": "^1.0.1",
+ "uniq": "^1.0.1"
+ }
+ },
+ "prelude-ls": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+ "dev": true
+ },
+ "prepend-http": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
+ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
+ "dev": true
+ },
+ "pretty-bytes": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.3.0.tgz",
+ "integrity": "sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg==",
+ "dev": true
+ },
+ "pretty-error": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz",
+ "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=",
+ "dev": true,
+ "requires": {
+ "renderkid": "^2.0.1",
+ "utila": "~0.4"
+ }
+ },
+ "pretty-format": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz",
+ "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^24.9.0",
+ "ansi-regex": "^4.0.0",
+ "ansi-styles": "^3.2.0",
+ "react-is": "^16.8.4"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ }
+ }
+ },
+ "private": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
+ "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==",
+ "dev": true
+ },
+ "process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
+ "dev": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
+ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
+ },
+ "progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true
+ },
+ "promise": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+ "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+ "requires": {
+ "asap": "~2.0.3"
+ }
+ },
+ "promise-inflight": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
+ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
+ "dev": true
+ },
+ "prompts": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.1.tgz",
+ "integrity": "sha512-qIP2lQyCwYbdzcqHIUi2HAxiWixhoM9OdLCWf8txXsapC/X9YdsCoeyRIXE/GP+Q0J37Q7+XN/MFqbUa7IzXNA==",
+ "dev": true,
+ "requires": {
+ "kleur": "^3.0.3",
+ "sisteransi": "^1.0.4"
+ }
+ },
+ "prop-types": {
+ "version": "15.7.2",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
+ "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.8.1"
+ }
+ },
+ "prop-types-exact": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz",
+ "integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==",
+ "requires": {
+ "has": "^1.0.3",
+ "object.assign": "^4.1.0",
+ "reflect.ownkeys": "^0.2.0"
+ }
+ },
+ "proxy-addr": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
+ "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
+ "dev": true,
+ "requires": {
+ "forwarded": "~0.1.2",
+ "ipaddr.js": "1.9.1"
+ }
+ },
+ "prr": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
+ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
+ "dev": true
+ },
+ "psl": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz",
+ "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==",
+ "dev": true
+ },
+ "public-encrypt": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
+ "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "browserify-rsa": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "parse-asn1": "^5.0.0",
+ "randombytes": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "pumpify": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+ "dev": true,
+ "requires": {
+ "duplexify": "^3.6.0",
+ "inherits": "^2.0.3",
+ "pump": "^2.0.0"
+ },
+ "dependencies": {
+ "pump": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ }
+ }
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "dev": true
+ },
+ "q": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
+ "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
+ "dev": true
+ },
+ "qs": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+ "dev": true
+ },
+ "query-string": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
+ "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
+ "dev": true,
+ "requires": {
+ "object-assign": "^4.1.0",
+ "strict-uri-encode": "^1.0.0"
+ }
+ },
+ "querystring": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+ "dev": true
+ },
+ "querystring-es3": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
+ "dev": true
+ },
+ "querystringify": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
+ "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==",
+ "dev": true
+ },
+ "raf": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
+ "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
+ "dev": true,
+ "requires": {
+ "performance-now": "^2.1.0"
+ }
+ },
+ "randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "randomfill": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
+ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.0.5",
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "dev": true
+ },
+ "raw-body": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+ "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+ "dev": true,
+ "requires": {
+ "bytes": "3.1.0",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "dependencies": {
+ "bytes": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
+ "dev": true
+ }
+ }
+ },
+ "react": {
+ "version": "15.6.2",
+ "resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz",
+ "integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=",
+ "requires": {
+ "create-react-class": "^15.6.0",
+ "fbjs": "^0.8.9",
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.0",
+ "prop-types": "^15.5.10"
+ }
+ },
+ "react-addons-test-utils": {
+ "version": "15.6.2",
+ "resolved": "https://registry.npmjs.org/react-addons-test-utils/-/react-addons-test-utils-15.6.2.tgz",
+ "integrity": "sha1-wStu/cIkfBDae4dw0YUICnsEcVY=",
+ "dev": true
+ },
+ "react-app-polyfill": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-1.0.6.tgz",
+ "integrity": "sha512-OfBnObtnGgLGfweORmdZbyEz+3dgVePQBb3zipiaDsMHV1NpWm0rDFYIVXFV/AK+x4VIIfWHhrdMIeoTLyRr2g==",
+ "dev": true,
+ "requires": {
+ "core-js": "^3.5.0",
+ "object-assign": "^4.1.1",
+ "promise": "^8.0.3",
+ "raf": "^3.4.1",
+ "regenerator-runtime": "^0.13.3",
+ "whatwg-fetch": "^3.0.0"
+ },
+ "dependencies": {
+ "core-js": {
+ "version": "3.6.4",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz",
+ "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==",
+ "dev": true
+ },
+ "promise": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz",
+ "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==",
+ "dev": true,
+ "requires": {
+ "asap": "~2.0.6"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.3",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
+ "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==",
+ "dev": true
+ }
+ }
+ },
+ "react-detect-offline": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/react-detect-offline/-/react-detect-offline-1.0.6.tgz",
+ "integrity": "sha512-qcP5SINR1cFXdJwPfx3ia6cOuuImQYC3GnLEIp+4ARe3O+GHwp2i1yUlSDbFVe7Sovjdr8djtDMynIIaYXyBFw=="
+ },
+ "react-dev-utils": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.0.tgz",
+ "integrity": "sha512-MwrvQW2TFjLblhqpDNeqCXHBkz3G5vc7k4wntgutAJZX4ia3o07eGKo6uYGhUOeJ0hfOxcpJFNFk7+4XCc1S8g==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "7.8.3",
+ "address": "1.1.2",
+ "browserslist": "4.8.6",
+ "chalk": "2.4.2",
+ "cross-spawn": "7.0.1",
+ "detect-port-alt": "1.1.6",
+ "escape-string-regexp": "2.0.0",
+ "filesize": "6.0.1",
+ "find-up": "4.1.0",
+ "fork-ts-checker-webpack-plugin": "3.1.1",
+ "global-modules": "2.0.0",
+ "globby": "8.0.2",
+ "gzip-size": "5.1.1",
+ "immer": "1.10.0",
+ "inquirer": "7.0.4",
+ "is-root": "2.1.0",
+ "loader-utils": "1.2.3",
+ "open": "^7.0.2",
+ "pkg-up": "3.1.0",
+ "react-error-overlay": "^6.0.6",
+ "recursive-readdir": "2.2.2",
+ "shell-quote": "1.7.2",
+ "strip-ansi": "6.0.0",
+ "text-table": "0.2.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "browserslist": {
+ "version": "4.8.6",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz",
+ "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==",
+ "dev": true,
+ "requires": {
+ "caniuse-lite": "^1.0.30001023",
+ "electron-to-chromium": "^1.3.341",
+ "node-releases": "^1.1.47"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "dependencies": {
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ }
+ }
+ },
+ "cross-spawn": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz",
+ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==",
+ "dev": true,
+ "requires": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ }
+ },
+ "emojis-list": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
+ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true
+ },
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "inquirer": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz",
+ "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==",
+ "dev": true,
+ "requires": {
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^2.4.2",
+ "cli-cursor": "^3.1.0",
+ "cli-width": "^2.0.0",
+ "external-editor": "^3.0.3",
+ "figures": "^3.0.0",
+ "lodash": "^4.17.15",
+ "mute-stream": "0.0.8",
+ "run-async": "^2.2.0",
+ "rxjs": "^6.5.3",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^5.1.0",
+ "through": "^2.3.6"
+ },
+ "dependencies": {
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "json5": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ },
+ "loader-utils": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
+ "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
+ "dev": true,
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^2.0.0",
+ "json5": "^1.0.1"
+ }
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.2.0"
+ }
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ },
+ "path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true
+ },
+ "shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^3.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ }
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ }
+ }
+ },
+ "react-dom": {
+ "version": "15.6.2",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz",
+ "integrity": "sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=",
+ "requires": {
+ "fbjs": "^0.8.9",
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.0",
+ "prop-types": "^15.5.10"
+ }
+ },
+ "react-error-overlay": {
+ "version": "6.0.6",
+ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.6.tgz",
+ "integrity": "sha512-Yzpno3enVzSrSCnnljmr4b/2KUQSMZaPuqmS26t9k4nW7uwJk6STWmH9heNjPuvqUTO3jOSPkHoKgO4+Dw7uIw==",
+ "dev": true
+ },
+ "react-event-listener": {
+ "version": "0.5.10",
+ "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.5.10.tgz",
+ "integrity": "sha512-YZklRszh9hq3WP3bdNLjFwJcTCVe7qyTf5+LWNaHfZQaZrptsefDK2B5HHpOsEEaMHvjllUPr0+qIFVTSsurow==",
+ "requires": {
+ "@babel/runtime": "7.0.0-beta.42",
+ "fbjs": "^0.8.16",
+ "prop-types": "^15.6.0",
+ "warning": "^3.0.0"
+ }
+ },
+ "react-infinite": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/react-infinite/-/react-infinite-0.13.0.tgz",
+ "integrity": "sha512-sISd4IYKELmOrvCq9i3FaQo4HR+Bn49ufK0eYAWQAisQ87QWJ5tqiQvEzww+JJZryZVMFvBCuiV7RUn/MfeEww==",
+ "requires": {
+ "enzyme-adapter-react-16": "1.1.1",
+ "lodash.isarray": "3.0.4",
+ "lodash.isfinite": "3.2.0",
+ "object-assign": "4.0.1"
+ },
+ "dependencies": {
+ "object-assign": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz",
+ "integrity": "sha1-mVBEVsNZi1ytT8WcJuipuxB/4L0="
+ }
+ }
+ },
+ "react-is": {
+ "version": "16.8.6",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
+ "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
+ },
+ "react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
+ },
+ "react-reconciler": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.7.0.tgz",
+ "integrity": "sha512-50JwZ3yNyMS8fchN+jjWEJOH3Oze7UmhxeoJLn2j6f3NjpfCRbcmih83XTWmzqtar/ivd5f7tvQhvvhism2fgg==",
+ "requires": {
+ "fbjs": "^0.8.16",
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "prop-types": "^15.6.0"
+ }
+ },
+ "react-redux": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.1.tgz",
+ "integrity": "sha512-LE7Ned+cv5qe7tMV5BPYkGQ5Lpg8gzgItK07c67yHvJ8t0iaD9kPFPAli/mYkiyJYrs2pJgExR2ZgsGqlrOApg==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "hoist-non-react-statics": "^3.1.0",
+ "invariant": "^2.2.4",
+ "loose-envify": "^1.1.0",
+ "prop-types": "^15.6.1",
+ "react-is": "^16.6.0",
+ "react-lifecycles-compat": "^3.0.0"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.4.tgz",
+ "integrity": "sha512-w0+uT71b6Yi7i5SE0co4NioIpSYS6lLiXvCzWzGSKvpK5vdQtCbICHMj+gbAKAOtxiV6HsVh/MBdaF9EQ6faSg==",
+ "requires": {
+ "regenerator-runtime": "^0.13.2"
+ }
+ },
+ "hoist-non-react-statics": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz",
+ "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==",
+ "requires": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.2",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
+ "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA=="
+ }
+ }
+ },
+ "react-router": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
+ "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==",
+ "requires": {
+ "history": "^4.7.2",
+ "hoist-non-react-statics": "^2.5.0",
+ "invariant": "^2.2.4",
+ "loose-envify": "^1.3.1",
+ "path-to-regexp": "^1.7.0",
+ "prop-types": "^15.6.1",
+ "warning": "^4.0.1"
+ },
+ "dependencies": {
+ "warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ }
+ }
+ },
+ "react-router-dom": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz",
+ "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==",
+ "requires": {
+ "history": "^4.7.2",
+ "invariant": "^2.2.4",
+ "loose-envify": "^1.3.1",
+ "prop-types": "^15.6.1",
+ "react-router": "^4.3.1",
+ "warning": "^4.0.1"
+ },
+ "dependencies": {
+ "warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ }
+ }
+ },
+ "react-scripts": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.0.tgz",
+ "integrity": "sha512-pBqaAroFoHnFAkuX+uSK9Th1uEh2GYdGY2IG1I9/7HmuEf+ls3lLCk1p2GFYRSrLMz6ieQR/SyN6TLIGK3hKRg==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "7.8.4",
+ "@svgr/webpack": "4.3.3",
+ "@typescript-eslint/eslint-plugin": "^2.10.0",
+ "@typescript-eslint/parser": "^2.10.0",
+ "babel-eslint": "10.0.3",
+ "babel-jest": "^24.9.0",
+ "babel-loader": "8.0.6",
+ "babel-plugin-named-asset-import": "^0.3.6",
+ "babel-preset-react-app": "^9.1.1",
+ "camelcase": "^5.3.1",
+ "case-sensitive-paths-webpack-plugin": "2.3.0",
+ "css-loader": "3.4.2",
+ "dotenv": "8.2.0",
+ "dotenv-expand": "5.1.0",
+ "eslint": "^6.6.0",
+ "eslint-config-react-app": "^5.2.0",
+ "eslint-loader": "3.0.3",
+ "eslint-plugin-flowtype": "4.6.0",
+ "eslint-plugin-import": "2.20.0",
+ "eslint-plugin-jsx-a11y": "6.2.3",
+ "eslint-plugin-react": "7.18.0",
+ "eslint-plugin-react-hooks": "^1.6.1",
+ "file-loader": "4.3.0",
+ "fs-extra": "^8.1.0",
+ "fsevents": "2.1.2",
+ "html-webpack-plugin": "4.0.0-beta.11",
+ "identity-obj-proxy": "3.0.0",
+ "jest": "24.9.0",
+ "jest-environment-jsdom-fourteen": "1.0.1",
+ "jest-resolve": "24.9.0",
+ "jest-watch-typeahead": "0.4.2",
+ "mini-css-extract-plugin": "0.9.0",
+ "optimize-css-assets-webpack-plugin": "5.0.3",
+ "pnp-webpack-plugin": "1.6.0",
+ "postcss-flexbugs-fixes": "4.1.0",
+ "postcss-loader": "3.0.0",
+ "postcss-normalize": "8.0.1",
+ "postcss-preset-env": "6.7.0",
+ "postcss-safe-parser": "4.0.1",
+ "react-app-polyfill": "^1.0.6",
+ "react-dev-utils": "^10.2.0",
+ "resolve": "1.15.0",
+ "resolve-url-loader": "3.1.1",
+ "sass-loader": "8.0.2",
+ "semver": "6.3.0",
+ "style-loader": "0.23.1",
+ "terser-webpack-plugin": "2.3.4",
+ "ts-pnp": "1.1.5",
+ "url-loader": "2.3.0",
+ "webpack": "4.41.5",
+ "webpack-dev-server": "3.10.2",
+ "webpack-manifest-plugin": "2.2.0",
+ "workbox-webpack-plugin": "4.3.1"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "react-tap-event-plugin": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/react-tap-event-plugin/-/react-tap-event-plugin-2.0.1.tgz",
+ "integrity": "sha1-MWvrO8ZVbinshppyk+icgmqQdNI=",
+ "requires": {
+ "fbjs": "^0.8.6"
+ }
+ },
+ "react-test-renderer": {
+ "version": "15.6.2",
+ "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-15.6.2.tgz",
+ "integrity": "sha1-0DM0NPwsQ4CSaWyncNpe1IA376g=",
+ "dev": true,
+ "requires": {
+ "fbjs": "^0.8.9",
+ "object-assign": "^4.1.0"
+ }
+ },
+ "react-tiny-virtual-list": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/react-tiny-virtual-list/-/react-tiny-virtual-list-2.2.0.tgz",
+ "integrity": "sha512-MDiy2xyqfvkWrRiQNdHFdm36lfxmcLLKuYnUqcf9xIubML85cmYCgzBJrDsLNZ3uJQ5LEHH9BnxGKKSm8+C0Bw==",
+ "requires": {
+ "prop-types": "^15.5.7"
+ }
+ },
+ "react-transition-group": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz",
+ "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==",
+ "requires": {
+ "chain-function": "^1.0.0",
+ "dom-helpers": "^3.2.0",
+ "loose-envify": "^1.3.1",
+ "prop-types": "^15.5.6",
+ "warning": "^3.0.0"
+ }
+ },
+ "react-virtualized": {
+ "version": "9.21.1",
+ "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.21.1.tgz",
+ "integrity": "sha512-E53vFjRRMCyUTEKuDLuGH1ld/9TFzjf/fFW816PE4HFXWZorESbSTYtiZz1oAjra0MminaUU1EnvUxoGuEFFPA==",
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "clsx": "^1.0.1",
+ "dom-helpers": "^2.4.0 || ^3.0.0",
+ "linear-layout-vector": "0.0.1",
+ "loose-envify": "^1.3.0",
+ "prop-types": "^15.6.0",
+ "react-lifecycles-compat": "^3.0.4"
+ }
+ },
+ "read-pkg": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
+ "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
+ "dev": true,
+ "requires": {
+ "load-json-file": "^4.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^3.0.0"
+ }
+ },
+ "read-pkg-up": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz",
+ "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==",
+ "dev": true,
+ "requires": {
+ "find-up": "^3.0.0",
+ "read-pkg": "^3.0.0"
+ }
+ },
+ "readable-stream": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz",
+ "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ },
+ "readdirp": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz",
+ "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==",
+ "dev": true,
+ "requires": {
+ "picomatch": "^2.0.7"
+ }
+ },
+ "realpath-native": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz",
+ "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==",
+ "dev": true,
+ "requires": {
+ "util.promisify": "^1.0.0"
+ }
+ },
+ "recompose": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.26.0.tgz",
+ "integrity": "sha512-KwOu6ztO0mN5vy3+zDcc45lgnaUoaQse/a5yLVqtzTK13czSWnFGmXbQVmnoMgDkI5POd1EwIKSbjU1V7xdZog==",
+ "requires": {
+ "change-emitter": "^0.1.2",
+ "fbjs": "^0.8.1",
+ "hoist-non-react-statics": "^2.3.1",
+ "symbol-observable": "^1.0.4"
+ }
+ },
+ "recursive-readdir": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz",
+ "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==",
+ "dev": true,
+ "requires": {
+ "minimatch": "3.0.4"
+ }
+ },
+ "redux": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
+ "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
+ "requires": {
+ "lodash": "^4.2.1",
+ "lodash-es": "^4.2.1",
+ "loose-envify": "^1.1.0",
+ "symbol-observable": "^1.0.3"
+ }
+ },
+ "redux-axios-middleware": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/redux-axios-middleware/-/redux-axios-middleware-4.0.0.tgz",
+ "integrity": "sha1-gZUcPZrc5vg++JL9FWhXHOvkozY="
+ },
+ "redux-devtools-extension": {
+ "version": "2.13.8",
+ "resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz",
+ "integrity": "sha512-8qlpooP2QqPtZHQZRhx3x3OP5skEV1py/zUdMY28WNAocbafxdG2tRD1MWE7sp8obGMNYuLWanhhQ7EQvT1FBg==",
+ "dev": true
+ },
+ "redux-mock-store": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.3.tgz",
+ "integrity": "sha512-ryhkkb/4D4CUGpAV2ln1GOY/uh51aczjcRz9k2L2bPx/Xja3c5pSGJJPyR25GNVRXtKIExScdAgFdiXp68GmJA==",
+ "dev": true,
+ "requires": {
+ "lodash.isplainobject": "^4.0.6"
+ }
+ },
+ "redux-persist": {
+ "version": "4.10.2",
+ "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-4.10.2.tgz",
+ "integrity": "sha512-U+e0ieMGC69Zr72929iJW40dEld7Mflh6mu0eJtVMLGfMq/aJqjxUM1hzyUWMR1VUyAEEdPHuQmeq5ti9krIgg==",
+ "requires": {
+ "json-stringify-safe": "^5.0.1",
+ "lodash": "^4.17.4",
+ "lodash-es": "^4.17.4"
+ }
+ },
+ "reflect.ownkeys": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz",
+ "integrity": "sha1-dJrO7H8/34tj+SegSAnpDFwLNGA="
+ },
+ "regenerate": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
+ "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==",
+ "dev": true
+ },
+ "regenerate-unicode-properties": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz",
+ "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==",
+ "dev": true,
+ "requires": {
+ "regenerate": "^1.4.0"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
+ },
+ "regenerator-transform": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz",
+ "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.18.0",
+ "babel-types": "^6.19.0",
+ "private": "^0.1.6"
+ }
+ },
+ "regex-not": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^3.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "regex-parser": {
+ "version": "2.2.10",
+ "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.10.tgz",
+ "integrity": "sha512-8t6074A68gHfU8Neftl0Le6KTDwfGAj7IyjPIMSfikI2wJUTHDMaIq42bUsfVnj8mhx0R+45rdUXHGpN164avA==",
+ "dev": true
+ },
+ "regexp.prototype.flags": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz",
+ "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.0-next.1"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.17.4",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz",
+ "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1",
+ "is-callable": "^1.1.5",
+ "is-regex": "^1.0.5",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimleft": "^2.1.1",
+ "string.prototype.trimright": "^2.1.1"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
+ "requires": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ }
+ },
+ "has-symbols": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
+ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
+ "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
+ "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ }
+ }
+ },
+ "regexpp": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz",
+ "integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==",
+ "dev": true
+ },
+ "regexpu-core": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz",
+ "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=",
+ "dev": true,
+ "requires": {
+ "regenerate": "^1.2.1",
+ "regjsgen": "^0.2.0",
+ "regjsparser": "^0.1.4"
+ }
+ },
+ "regjsgen": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
+ "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=",
+ "dev": true
+ },
+ "regjsparser": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
+ "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
+ "dev": true,
+ "requires": {
+ "jsesc": "~0.5.0"
+ }
+ },
+ "relateurl": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
+ "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=",
+ "dev": true
+ },
+ "remove-trailing-separator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
+ "dev": true
+ },
+ "renderkid": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz",
+ "integrity": "sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==",
+ "dev": true,
+ "requires": {
+ "css-select": "^1.1.0",
+ "dom-converter": "^0.2",
+ "htmlparser2": "^3.3.0",
+ "strip-ansi": "^3.0.0",
+ "utila": "^0.4.0"
+ }
+ },
+ "repeat-element": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+ "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==",
+ "dev": true
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
+ "dev": true
+ },
+ "request": {
+ "version": "2.88.2",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+ "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+ "dev": true,
+ "requires": {
+ "aws-sign2": "~0.7.0",
+ "aws4": "^1.8.0",
+ "caseless": "~0.12.0",
+ "combined-stream": "~1.0.6",
+ "extend": "~3.0.2",
+ "forever-agent": "~0.6.1",
+ "form-data": "~2.3.2",
+ "har-validator": "~5.1.3",
+ "http-signature": "~1.2.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.19",
+ "oauth-sign": "~0.9.0",
+ "performance-now": "^2.1.0",
+ "qs": "~6.5.2",
+ "safe-buffer": "^5.1.2",
+ "tough-cookie": "~2.5.0",
+ "tunnel-agent": "^0.6.0",
+ "uuid": "^3.3.2"
+ }
+ },
+ "request-promise-core": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz",
+ "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.15"
+ }
+ },
+ "request-promise-native": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz",
+ "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==",
+ "dev": true,
+ "requires": {
+ "request-promise-core": "1.1.3",
+ "stealthy-require": "^1.1.1",
+ "tough-cookie": "^2.3.3"
+ }
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true
+ },
+ "require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true
+ },
+ "requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz",
+ "integrity": "sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ },
+ "resolve-cwd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
+ "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
+ "dev": true,
+ "requires": {
+ "resolve-from": "^3.0.0"
+ }
+ },
+ "resolve-from": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+ "dev": true
+ },
+ "resolve-pathname": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz",
+ "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg=="
+ },
+ "resolve-url": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+ "dev": true
+ },
+ "resolve-url-loader": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.1.tgz",
+ "integrity": "sha512-K1N5xUjj7v0l2j/3Sgs5b8CjrrgtC70SmdCuZiJ8tSyb5J+uk3FoeZ4b7yTnH6j7ngI+Bc5bldHJIa8hYdu2gQ==",
+ "dev": true,
+ "requires": {
+ "adjust-sourcemap-loader": "2.0.0",
+ "camelcase": "5.3.1",
+ "compose-function": "3.0.3",
+ "convert-source-map": "1.7.0",
+ "es6-iterator": "2.0.3",
+ "loader-utils": "1.2.3",
+ "postcss": "7.0.21",
+ "rework": "1.0.1",
+ "rework-visit": "1.0.0",
+ "source-map": "0.6.1"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "emojis-list": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
+ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
+ "dev": true
+ },
+ "json5": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ },
+ "loader-utils": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
+ "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
+ "dev": true,
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^2.0.0",
+ "json5": "^1.0.1"
+ }
+ },
+ "postcss": {
+ "version": "7.0.21",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.21.tgz",
+ "integrity": "sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.2",
+ "source-map": "^0.6.1",
+ "supports-color": "^6.1.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+ "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "restore-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+ "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+ "dev": true,
+ "requires": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "ret": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+ "dev": true
+ },
+ "retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=",
+ "dev": true
+ },
+ "rework": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rework/-/rework-1.0.1.tgz",
+ "integrity": "sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc=",
+ "dev": true,
+ "requires": {
+ "convert-source-map": "^0.3.3",
+ "css": "^2.0.0"
+ },
+ "dependencies": {
+ "convert-source-map": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz",
+ "integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=",
+ "dev": true
+ }
+ }
+ },
+ "rework-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/rework-visit/-/rework-visit-1.0.0.tgz",
+ "integrity": "sha1-mUWygD8hni96ygCtuLyfZA+ELJo=",
+ "dev": true
+ },
+ "rgb-regex": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz",
+ "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=",
+ "dev": true
+ },
+ "rgba-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz",
+ "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "ripemd160": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+ "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+ "dev": true,
+ "requires": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1"
+ }
+ },
+ "rsvp": {
+ "version": "4.8.5",
+ "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
+ "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==",
+ "dev": true
+ },
+ "run-async": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz",
+ "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==",
+ "dev": true,
+ "requires": {
+ "is-promise": "^2.1.0"
+ }
+ },
+ "run-queue": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
+ "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.1.1"
+ }
+ },
+ "rxjs": {
+ "version": "6.5.4",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
+ "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "safe-regex": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+ "dev": true,
+ "requires": {
+ "ret": "~0.1.10"
+ }
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "sane": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz",
+ "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==",
+ "dev": true,
+ "requires": {
+ "@cnakazawa/watch": "^1.0.3",
+ "anymatch": "^2.0.0",
+ "capture-exit": "^2.0.0",
+ "exec-sh": "^0.3.2",
+ "execa": "^1.0.0",
+ "fb-watchman": "^2.0.0",
+ "micromatch": "^3.1.4",
+ "minimist": "^1.1.1",
+ "walker": "~1.0.5"
+ }
+ },
+ "sanitize.css": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-10.0.0.tgz",
+ "integrity": "sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg==",
+ "dev": true
+ },
+ "sass-loader": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.2.tgz",
+ "integrity": "sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==",
+ "dev": true,
+ "requires": {
+ "clone-deep": "^4.0.1",
+ "loader-utils": "^1.2.3",
+ "neo-async": "^2.6.1",
+ "schema-utils": "^2.6.1",
+ "semver": "^6.3.0"
+ },
+ "dependencies": {
+ "clone-deep": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
+ "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4",
+ "kind-of": "^6.0.2",
+ "shallow-clone": "^3.0.0"
+ }
+ },
+ "kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ },
+ "shallow-clone": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
+ "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "sax": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+ "dev": true
+ },
+ "saxes": {
+ "version": "3.1.11",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz",
+ "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==",
+ "dev": true,
+ "requires": {
+ "xmlchars": "^2.1.1"
+ }
+ },
+ "scheduler": {
+ "version": "0.13.6",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz",
+ "integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ },
+ "schema-utils": {
+ "version": "2.6.4",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz",
+ "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.10.2",
+ "ajv-keywords": "^3.4.1"
+ }
+ },
+ "select-hose": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
+ "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=",
+ "dev": true
+ },
+ "selfsigned": {
+ "version": "1.10.7",
+ "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz",
+ "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==",
+ "dev": true,
+ "requires": {
+ "node-forge": "0.9.0"
+ }
+ },
+ "semver": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
+ "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
+ },
+ "send": {
+ "version": "0.17.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+ "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "destroy": "~1.0.4",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "~1.7.2",
+ "mime": "1.6.0",
+ "ms": "2.1.1",
+ "on-finished": "~2.3.0",
+ "range-parser": "~1.2.1",
+ "statuses": "~1.5.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "dev": true
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+ "dev": true
+ }
+ }
+ },
+ "serialize-javascript": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
+ "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
+ "dev": true
+ },
+ "serve-index": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
+ "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
+ "dev": true,
+ "requires": {
+ "accepts": "~1.3.4",
+ "batch": "0.6.1",
+ "debug": "2.6.9",
+ "escape-html": "~1.0.3",
+ "http-errors": "~1.6.2",
+ "mime-types": "~2.1.17",
+ "parseurl": "~1.3.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "http-errors": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+ "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+ "dev": true,
+ "requires": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.0",
+ "statuses": ">= 1.4.0 < 2"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "setprototypeof": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
+ "dev": true
+ }
+ }
+ },
+ "serve-static": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+ "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
+ "dev": true,
+ "requires": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.17.1"
+ }
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "dev": true
+ },
+ "set-immediate-shim": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
+ "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E="
+ },
+ "set-value": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+ "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.3",
+ "split-string": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
+ },
+ "setprototypeof": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
+ "dev": true
+ },
+ "sha.js": {
+ "version": "2.4.11",
+ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "shallow-clone": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz",
+ "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.1",
+ "kind-of": "^2.0.1",
+ "lazy-cache": "^0.2.3",
+ "mixin-object": "^2.0.1"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz",
+ "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.0.2"
+ }
+ },
+ "lazy-cache": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz",
+ "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=",
+ "dev": true
+ }
+ }
+ },
+ "shebang-command": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^1.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+ "dev": true
+ },
+ "shell-quote": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
+ "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==",
+ "dev": true
+ },
+ "shellwords": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
+ "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
+ "dev": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+ "dev": true
+ },
+ "simple-assign": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/simple-assign/-/simple-assign-0.1.0.tgz",
+ "integrity": "sha1-F/0wZqXz13OPUDIbsPFMooHMS6o="
+ },
+ "simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
+ "dev": true,
+ "requires": {
+ "is-arrayish": "^0.3.1"
+ },
+ "dependencies": {
+ "is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
+ "dev": true
+ }
+ }
+ },
+ "sisteransi": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.4.tgz",
+ "integrity": "sha512-/ekMoM4NJ59ivGSfKapeG+FWtrmWvA1p6FBZwXrqojw90vJu8lBmrTxCMuBCydKtkaUe2zt4PlxeTKpjwMbyig==",
+ "dev": true
+ },
+ "slash": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+ "dev": true
+ },
+ "slice-ansi": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
+ "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.0",
+ "astral-regex": "^1.0.0",
+ "is-fullwidth-code-point": "^2.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ }
+ }
+ },
+ "snapdragon": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+ "dev": true,
+ "requires": {
+ "base": "^0.11.1",
+ "debug": "^2.2.0",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "map-cache": "^0.2.2",
+ "source-map": "^0.5.6",
+ "source-map-resolve": "^0.5.0",
+ "use": "^3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "snapdragon-node": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+ "dev": true,
+ "requires": {
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.0",
+ "snapdragon-util": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true
+ }
+ }
+ },
+ "snapdragon-util": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.2.0"
+ }
+ },
+ "sockjs": {
+ "version": "0.3.19",
+ "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz",
+ "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==",
+ "dev": true,
+ "requires": {
+ "faye-websocket": "^0.10.0",
+ "uuid": "^3.0.1"
+ }
+ },
+ "sockjs-client": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz",
+ "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==",
+ "dev": true,
+ "requires": {
+ "debug": "^3.2.5",
+ "eventsource": "^1.0.7",
+ "faye-websocket": "~0.11.1",
+ "inherits": "^2.0.3",
+ "json3": "^3.3.2",
+ "url-parse": "^1.4.3"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "faye-websocket": {
+ "version": "0.11.3",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz",
+ "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==",
+ "dev": true,
+ "requires": {
+ "websocket-driver": ">=0.5.1"
+ }
+ }
+ }
+ },
+ "sort-keys": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
+ "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
+ "dev": true,
+ "requires": {
+ "is-plain-obj": "^1.0.0"
+ }
+ },
+ "source-list-map": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
+ "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ },
+ "source-map-resolve": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
+ "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
+ "dev": true,
+ "requires": {
+ "atob": "^2.1.2",
+ "decode-uri-component": "^0.2.0",
+ "resolve-url": "^0.2.1",
+ "source-map-url": "^0.4.0",
+ "urix": "^0.1.0"
+ }
+ },
+ "source-map-support": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
+ "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "source-map-url": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
+ "dev": true
+ },
+ "spdx-correct": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
+ "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
+ "dev": true,
+ "requires": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-exceptions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
+ "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
+ "dev": true
+ },
+ "spdx-expression-parse": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+ "dev": true,
+ "requires": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-license-ids": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
+ "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
+ "dev": true
+ },
+ "spdy": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.1.tgz",
+ "integrity": "sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==",
+ "dev": true,
+ "requires": {
+ "debug": "^4.1.0",
+ "handle-thing": "^2.0.0",
+ "http-deceiver": "^1.2.7",
+ "select-hose": "^2.0.0",
+ "spdy-transport": "^3.0.0"
+ }
+ },
+ "spdy-transport": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz",
+ "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==",
+ "dev": true,
+ "requires": {
+ "debug": "^4.1.0",
+ "detect-node": "^2.0.4",
+ "hpack.js": "^2.1.6",
+ "obuf": "^1.1.2",
+ "readable-stream": "^3.0.6",
+ "wbuf": "^1.7.3"
+ }
+ },
+ "split-string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^3.0.0"
+ }
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "sshpk": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+ "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+ "dev": true,
+ "requires": {
+ "asn1": "~0.2.3",
+ "assert-plus": "^1.0.0",
+ "bcrypt-pbkdf": "^1.0.0",
+ "dashdash": "^1.12.0",
+ "ecc-jsbn": "~0.1.1",
+ "getpass": "^0.1.1",
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.0.2",
+ "tweetnacl": "~0.14.0"
+ }
+ },
+ "ssri": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz",
+ "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==",
+ "dev": true,
+ "requires": {
+ "figgy-pudding": "^3.5.1",
+ "minipass": "^3.1.1"
+ }
+ },
+ "stable": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
+ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==",
+ "dev": true
+ },
+ "stack-utils": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz",
+ "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==",
+ "dev": true
+ },
+ "static-extend": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+ "dev": true,
+ "requires": {
+ "define-property": "^0.2.5",
+ "object-copy": "^0.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+ "dev": true
+ },
+ "stealthy-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
+ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
+ "dev": true
+ },
+ "stream-browserify": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
+ "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
+ "dev": true,
+ "requires": {
+ "inherits": "~2.0.1",
+ "readable-stream": "^2.0.2"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "stream-each": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
+ "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "stream-shift": "^1.0.0"
+ }
+ },
+ "stream-http": {
+ "version": "2.8.3",
+ "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
+ "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
+ "dev": true,
+ "requires": {
+ "builtin-status-codes": "^3.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.3.6",
+ "to-arraybuffer": "^1.0.0",
+ "xtend": "^4.0.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "stream-shift": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
+ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
+ "dev": true
+ },
+ "strict-uri-encode": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
+ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
+ "dev": true
+ },
+ "string-length": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz",
+ "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=",
+ "dev": true,
+ "requires": {
+ "astral-regex": "^1.0.0",
+ "strip-ansi": "^4.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ }
+ }
+ },
+ "string-width": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+ "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.0"
+ }
+ }
+ }
+ },
+ "string.prototype.trimleft": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz",
+ "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "function-bind": "^1.1.1"
+ }
+ },
+ "string.prototype.trimright": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz",
+ "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "function-bind": "^1.1.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz",
+ "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "stringify-object": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz",
+ "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==",
+ "dev": true,
+ "requires": {
+ "get-own-enumerable-property-symbols": "^3.0.0",
+ "is-obj": "^1.0.1",
+ "is-regexp": "^1.0.0"
+ },
+ "dependencies": {
+ "is-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
+ "dev": true
+ }
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+ "dev": true
+ },
+ "strip-comments": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-1.0.2.tgz",
+ "integrity": "sha512-kL97alc47hoyIQSV165tTt9rG5dn4w1dNnBhOQ3bOU1Nc1hel09jnXANaHJ7vzHLd4Ju8kseDGzlev96pghLFw==",
+ "dev": true,
+ "requires": {
+ "babel-extract-comments": "^1.0.0",
+ "babel-plugin-transform-object-rest-spread": "^6.26.0"
+ }
+ },
+ "strip-eof": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+ "dev": true
+ },
+ "strip-json-comments": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
+ "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
+ "dev": true
+ },
+ "style-loader": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz",
+ "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.1.0",
+ "schema-utils": "^1.0.0"
+ },
+ "dependencies": {
+ "schema-utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-errors": "^1.0.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ }
+ }
+ },
+ "stylehacks": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz",
+ "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.0.0",
+ "postcss": "^7.0.0",
+ "postcss-selector-parser": "^3.0.0"
+ },
+ "dependencies": {
+ "postcss-selector-parser": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz",
+ "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==",
+ "dev": true,
+ "requires": {
+ "dot-prop": "^5.2.0",
+ "indexes-of": "^1.0.1",
+ "uniq": "^1.0.1"
+ }
+ }
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "dev": true
+ },
+ "svg-parser": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
+ "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==",
+ "dev": true
+ },
+ "svgo": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz",
+ "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.1",
+ "coa": "^2.0.2",
+ "css-select": "^2.0.0",
+ "css-select-base-adapter": "^0.1.1",
+ "css-tree": "1.0.0-alpha.37",
+ "csso": "^4.0.2",
+ "js-yaml": "^3.13.1",
+ "mkdirp": "~0.5.1",
+ "object.values": "^1.1.0",
+ "sax": "~1.2.4",
+ "stable": "^0.1.8",
+ "unquote": "~1.1.1",
+ "util.promisify": "~1.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "css-select": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz",
+ "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==",
+ "dev": true,
+ "requires": {
+ "boolbase": "^1.0.0",
+ "css-what": "^3.2.1",
+ "domutils": "^1.7.0",
+ "nth-check": "^1.0.2"
+ }
+ },
+ "css-what": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz",
+ "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==",
+ "dev": true
+ },
+ "domutils": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
+ "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
+ "dev": true,
+ "requires": {
+ "dom-serializer": "0",
+ "domelementtype": "1"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "symbol-observable": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
+ },
+ "symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true
+ },
+ "table": {
+ "version": "5.4.6",
+ "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
+ "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.10.2",
+ "lodash": "^4.17.14",
+ "slice-ansi": "^2.1.0",
+ "string-width": "^3.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "tapable": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
+ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
+ "dev": true
+ },
+ "terser": {
+ "version": "4.6.6",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.6.tgz",
+ "integrity": "sha512-4lYPyeNmstjIIESr/ysHg2vUPRGf2tzF9z2yYwnowXVuVzLEamPN1Gfrz7f8I9uEPuHcbFlW4PLIAsJoxXyJ1g==",
+ "dev": true,
+ "requires": {
+ "commander": "^2.20.0",
+ "source-map": "~0.6.1",
+ "source-map-support": "~0.5.12"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "terser-webpack-plugin": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.4.tgz",
+ "integrity": "sha512-Nv96Nws2R2nrFOpbzF6IxRDpIkkIfmhvOws+IqMvYdFLO7o6wAILWFKONFgaYy8+T4LVz77DQW0f7wOeDEAjrg==",
+ "dev": true,
+ "requires": {
+ "cacache": "^13.0.1",
+ "find-cache-dir": "^3.2.0",
+ "jest-worker": "^25.1.0",
+ "p-limit": "^2.2.2",
+ "schema-utils": "^2.6.4",
+ "serialize-javascript": "^2.1.2",
+ "source-map": "^0.6.1",
+ "terser": "^4.4.3",
+ "webpack-sources": "^1.4.3"
+ },
+ "dependencies": {
+ "find-cache-dir": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.0.tgz",
+ "integrity": "sha512-PtXtQb7IrD8O+h6Cq1dbpJH5NzD8+9keN1zZ0YlpDzl1PwXEJEBj6u1Xa92t1Hwluoozd9TNKul5Hi2iqpsWwg==",
+ "dev": true,
+ "requires": {
+ "commondir": "^1.0.1",
+ "make-dir": "^3.0.2",
+ "pkg-dir": "^4.1.0"
+ }
+ },
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "jest-worker": {
+ "version": "25.1.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.1.0.tgz",
+ "integrity": "sha512-ZHhHtlxOWSxCoNOKHGbiLzXnl42ga9CxDr27H36Qn+15pQZd3R/F24jrmjDelw9j/iHUIWMWs08/u2QN50HHOg==",
+ "dev": true,
+ "requires": {
+ "merge-stream": "^2.0.0",
+ "supports-color": "^7.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "make-dir": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz",
+ "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==",
+ "dev": true,
+ "requires": {
+ "semver": "^6.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.2.0"
+ }
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ },
+ "pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "requires": {
+ "find-up": "^4.0.0"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
+ "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "test-exclude": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz",
+ "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3",
+ "minimatch": "^3.0.4",
+ "read-pkg-up": "^4.0.0",
+ "require-main-filename": "^2.0.0"
+ }
+ },
+ "text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+ "dev": true
+ },
+ "throat": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz",
+ "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=",
+ "dev": true
+ },
+ "through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+ "dev": true
+ },
+ "through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "thunky": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
+ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
+ "dev": true
+ },
+ "timers-browserify": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz",
+ "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==",
+ "dev": true,
+ "requires": {
+ "setimmediate": "^1.0.4"
+ }
+ },
+ "timsort": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
+ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
+ "dev": true
+ },
+ "tiny-invariant": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.4.tgz",
+ "integrity": "sha512-lMhRd/djQJ3MoaHEBrw8e2/uM4rs9YMNk0iOr8rHQ0QdbM7D4l0gFl3szKdeixrlyfm9Zqi4dxHCM2qVG8ND5g=="
+ },
+ "tiny-warning": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.2.tgz",
+ "integrity": "sha512-rru86D9CpQRLvsFG5XFdy0KdLAvjdQDyZCsRcuu60WtzFylDM3eAWSxEVz5kzL2Gp544XiUvPbVKtOA/txLi9Q=="
+ },
+ "tmp": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+ "dev": true,
+ "requires": {
+ "os-tmpdir": "~1.0.2"
+ }
+ },
+ "tmpl": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
+ "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=",
+ "dev": true
+ },
+ "to-arraybuffer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
+ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
+ "dev": true
+ },
+ "to-fast-properties": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
+ "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=",
+ "dev": true
+ },
+ "to-object-path": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ },
+ "to-regex": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+ "dev": true,
+ "requires": {
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "regex-not": "^1.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "to-regex-range": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+ "dev": true,
+ "requires": {
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1"
+ }
+ },
+ "toggle-selection": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
+ "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
+ },
+ "toidentifier": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
+ "dev": true
+ },
+ "tough-cookie": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+ "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+ "dev": true,
+ "requires": {
+ "psl": "^1.1.28",
+ "punycode": "^2.1.1"
+ }
+ },
+ "tr46": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
+ "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "ts-pnp": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.5.tgz",
+ "integrity": "sha512-ti7OGMOUOzo66wLF3liskw6YQIaSsBgc4GOAlWRnIEj8htCxJUxskanMUoJOD6MDCRAXo36goXJZch+nOS0VMA==",
+ "dev": true
+ },
+ "tslib": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
+ "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
+ "dev": true
+ },
+ "tsutils": {
+ "version": "3.17.1",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
+ "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.8.1"
+ }
+ },
+ "tty-browserify": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+ "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
+ "dev": true
+ },
+ "tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "tweetnacl": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+ "dev": true
+ },
+ "type": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
+ "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
+ "dev": true
+ },
+ "type-check": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "~1.1.2"
+ }
+ },
+ "type-fest": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+ "dev": true
+ },
+ "type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dev": true,
+ "requires": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ }
+ },
+ "typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+ "dev": true
+ },
+ "ua-parser-js": {
+ "version": "0.7.19",
+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz",
+ "integrity": "sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ=="
+ },
+ "unicode-canonical-property-names-ecmascript": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
+ "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==",
+ "dev": true
+ },
+ "unicode-match-property-ecmascript": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
+ "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
+ "dev": true,
+ "requires": {
+ "unicode-canonical-property-names-ecmascript": "^1.0.4",
+ "unicode-property-aliases-ecmascript": "^1.0.4"
+ }
+ },
+ "unicode-match-property-value-ecmascript": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz",
+ "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==",
+ "dev": true
+ },
+ "unicode-property-aliases-ecmascript": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz",
+ "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==",
+ "dev": true
+ },
+ "union-value": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+ "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
+ "dev": true,
+ "requires": {
+ "arr-union": "^3.1.0",
+ "get-value": "^2.0.6",
+ "is-extendable": "^0.1.1",
+ "set-value": "^2.0.1"
+ }
+ },
+ "uniq": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
+ "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
+ "dev": true
+ },
+ "uniqs": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz",
+ "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=",
+ "dev": true
+ },
+ "unique-filename": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
+ "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
+ "dev": true,
+ "requires": {
+ "unique-slug": "^2.0.0"
+ }
+ },
+ "unique-slug": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
+ "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
+ "dev": true,
+ "requires": {
+ "imurmurhash": "^0.1.4"
+ }
+ },
+ "universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true
+ },
+ "unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+ "dev": true
+ },
+ "unquote": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz",
+ "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=",
+ "dev": true
+ },
+ "unset-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+ "dev": true,
+ "requires": {
+ "has-value": "^0.3.1",
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "has-value": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+ "dev": true,
+ "requires": {
+ "get-value": "^2.0.3",
+ "has-values": "^0.1.4",
+ "isobject": "^2.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "dev": true,
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ }
+ }
+ },
+ "has-values": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+ "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
+ "dev": true
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ }
+ }
+ },
+ "upath": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
+ "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
+ "dev": true
+ },
+ "uri-js": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "urix": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
+ "dev": true
+ },
+ "url": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+ "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+ "dev": true,
+ "requires": {
+ "punycode": "1.3.2",
+ "querystring": "0.2.0"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+ "dev": true
+ }
+ }
+ },
+ "url-loader": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-2.3.0.tgz",
+ "integrity": "sha512-goSdg8VY+7nPZKUEChZSEtW5gjbS66USIGCeSJ1OVOJ7Yfuh/36YxCwMi5HVEJh6mqUYOoy3NJ0vlOMrWsSHog==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.2.3",
+ "mime": "^2.4.4",
+ "schema-utils": "^2.5.0"
+ }
+ },
+ "url-parse": {
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
+ "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
+ "dev": true,
+ "requires": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
+ "use": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
+ "dev": true
+ },
+ "util": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+ "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.1"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
+ "dev": true
+ }
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ },
+ "util.promisify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz",
+ "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.2",
+ "has-symbols": "^1.0.1",
+ "object.getownpropertydescriptors": "^2.1.0"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.17.4",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz",
+ "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1",
+ "is-callable": "^1.1.5",
+ "is-regex": "^1.0.5",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimleft": "^2.1.1",
+ "string.prototype.trimright": "^2.1.1"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
+ "requires": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ }
+ },
+ "has-symbols": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
+ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
+ "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
+ "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ }
+ }
+ },
+ "utila": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz",
+ "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=",
+ "dev": true
+ },
+ "utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
+ "dev": true
+ },
+ "uuid": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
+ "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
+ "dev": true
+ },
+ "v8-compile-cache": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
+ "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
+ "dev": true
+ },
+ "validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "requires": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "value-equal": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz",
+ "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw=="
+ },
+ "vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
+ "dev": true
+ },
+ "vendors": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz",
+ "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==",
+ "dev": true
+ },
+ "verror": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ }
+ },
+ "vm-browserify": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
+ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
+ "dev": true
+ },
+ "w3c-hr-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",
+ "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=",
+ "dev": true,
+ "requires": {
+ "browser-process-hrtime": "^0.1.2"
+ }
+ },
+ "w3c-xmlserializer": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz",
+ "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==",
+ "dev": true,
+ "requires": {
+ "domexception": "^1.0.1",
+ "webidl-conversions": "^4.0.2",
+ "xml-name-validator": "^3.0.0"
+ }
+ },
+ "walker": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz",
+ "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=",
+ "dev": true,
+ "requires": {
+ "makeerror": "1.0.x"
+ }
+ },
+ "warning": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
+ "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "watchpack": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
+ "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==",
+ "dev": true,
+ "requires": {
+ "chokidar": "^2.0.2",
+ "graceful-fs": "^4.1.2",
+ "neo-async": "^2.5.0"
+ },
+ "dependencies": {
+ "binary-extensions": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
+ "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
+ "dev": true
+ },
+ "chokidar": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
+ "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
+ "dev": true,
+ "requires": {
+ "anymatch": "^2.0.0",
+ "async-each": "^1.0.1",
+ "braces": "^2.3.2",
+ "fsevents": "^1.2.7",
+ "glob-parent": "^3.1.0",
+ "inherits": "^2.0.3",
+ "is-binary-path": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "normalize-path": "^3.0.0",
+ "path-is-absolute": "^1.0.0",
+ "readdirp": "^2.2.1",
+ "upath": "^1.1.1"
+ }
+ },
+ "fsevents": {
+ "version": "1.2.11",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz",
+ "integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "bindings": "^1.5.0",
+ "nan": "^2.12.1",
+ "node-pre-gyp": "*"
+ },
+ "dependencies": {
+ "abbrev": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "are-we-there-yet": {
+ "version": "1.1.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "chownr": {
+ "version": "1.1.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "debug": {
+ "version": "3.2.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "deep-extend": {
+ "version": "0.6.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "detect-libc": {
+ "version": "1.0.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "fs-minipass": {
+ "version": "1.2.7",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.6.0"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
+ }
+ },
+ "glob": {
+ "version": "7.1.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "ignore-walk": {
+ "version": "3.0.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minimatch": "^3.0.4"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "ini": {
+ "version": "1.3.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "minipass": {
+ "version": "2.9.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "^5.1.2",
+ "yallist": "^3.0.0"
+ }
+ },
+ "minizlib": {
+ "version": "1.3.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.9.0"
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "needle": {
+ "version": "2.4.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "debug": "^3.2.6",
+ "iconv-lite": "^0.4.4",
+ "sax": "^1.2.4"
+ }
+ },
+ "node-pre-gyp": {
+ "version": "0.14.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "detect-libc": "^1.0.2",
+ "mkdirp": "^0.5.1",
+ "needle": "^2.2.1",
+ "nopt": "^4.0.1",
+ "npm-packlist": "^1.1.6",
+ "npmlog": "^4.0.2",
+ "rc": "^1.2.7",
+ "rimraf": "^2.6.1",
+ "semver": "^5.3.0",
+ "tar": "^4.4.2"
+ }
+ },
+ "nopt": {
+ "version": "4.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "abbrev": "1",
+ "osenv": "^0.1.4"
+ }
+ },
+ "npm-bundled": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "npm-normalize-package-bin": "^1.0.1"
+ }
+ },
+ "npm-normalize-package-bin": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "npm-packlist": {
+ "version": "1.4.7",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ignore-walk": "^3.0.1",
+ "npm-bundled": "^1.0.1"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "osenv": {
+ "version": "0.1.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.0"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "rc": {
+ "version": "1.2.8",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "rimraf": {
+ "version": "2.7.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "sax": {
+ "version": "1.2.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "semver": {
+ "version": "5.7.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "tar": {
+ "version": "4.4.13",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "chownr": "^1.1.1",
+ "fs-minipass": "^1.2.5",
+ "minipass": "^2.8.6",
+ "minizlib": "^1.2.1",
+ "mkdirp": "^0.5.0",
+ "safe-buffer": "^5.1.2",
+ "yallist": "^3.0.3"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "wide-align": {
+ "version": "1.1.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "yallist": {
+ "version": "3.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "glob-parent": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+ "dev": true,
+ "requires": {
+ "is-glob": "^3.1.0",
+ "path-dirname": "^1.0.0"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.0"
+ }
+ }
+ }
+ },
+ "is-binary-path": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+ "dev": true,
+ "requires": {
+ "binary-extensions": "^1.0.0"
+ }
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "readdirp": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
+ "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.11",
+ "micromatch": "^3.1.10",
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "wbuf": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
+ "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==",
+ "dev": true,
+ "requires": {
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "webidl-conversions": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+ "dev": true
+ },
+ "webpack": {
+ "version": "4.41.5",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.5.tgz",
+ "integrity": "sha512-wp0Co4vpyumnp3KlkmpM5LWuzvZYayDwM2n17EHFr4qxBBbRokC7DJawPJC7TfSFZ9HZ6GsdH40EBj4UV0nmpw==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/helper-module-context": "1.8.5",
+ "@webassemblyjs/wasm-edit": "1.8.5",
+ "@webassemblyjs/wasm-parser": "1.8.5",
+ "acorn": "^6.2.1",
+ "ajv": "^6.10.2",
+ "ajv-keywords": "^3.4.1",
+ "chrome-trace-event": "^1.0.2",
+ "enhanced-resolve": "^4.1.0",
+ "eslint-scope": "^4.0.3",
+ "json-parse-better-errors": "^1.0.2",
+ "loader-runner": "^2.4.0",
+ "loader-utils": "^1.2.3",
+ "memory-fs": "^0.4.1",
+ "micromatch": "^3.1.10",
+ "mkdirp": "^0.5.1",
+ "neo-async": "^2.6.1",
+ "node-libs-browser": "^2.2.1",
+ "schema-utils": "^1.0.0",
+ "tapable": "^1.1.3",
+ "terser-webpack-plugin": "^1.4.3",
+ "watchpack": "^1.6.0",
+ "webpack-sources": "^1.4.1"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
+ "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
+ "dev": true
+ },
+ "cacache": {
+ "version": "12.0.3",
+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz",
+ "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==",
+ "dev": true,
+ "requires": {
+ "bluebird": "^3.5.5",
+ "chownr": "^1.1.1",
+ "figgy-pudding": "^3.5.1",
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.1.15",
+ "infer-owner": "^1.0.3",
+ "lru-cache": "^5.1.1",
+ "mississippi": "^3.0.0",
+ "mkdirp": "^0.5.1",
+ "move-concurrently": "^1.0.1",
+ "promise-inflight": "^1.0.1",
+ "rimraf": "^2.6.3",
+ "ssri": "^6.0.1",
+ "unique-filename": "^1.1.1",
+ "y18n": "^4.0.0"
+ }
+ },
+ "eslint-scope": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
+ "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.1.0",
+ "estraverse": "^4.1.1"
+ }
+ },
+ "schema-utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-errors": "^1.0.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "ssri": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
+ "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
+ "dev": true,
+ "requires": {
+ "figgy-pudding": "^3.5.1"
+ }
+ },
+ "terser-webpack-plugin": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
+ "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
+ "dev": true,
+ "requires": {
+ "cacache": "^12.0.2",
+ "find-cache-dir": "^2.1.0",
+ "is-wsl": "^1.1.0",
+ "schema-utils": "^1.0.0",
+ "serialize-javascript": "^2.1.2",
+ "source-map": "^0.6.1",
+ "terser": "^4.1.2",
+ "webpack-sources": "^1.4.0",
+ "worker-farm": "^1.7.0"
+ }
+ }
+ }
+ },
+ "webpack-dev-middleware": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz",
+ "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==",
+ "dev": true,
+ "requires": {
+ "memory-fs": "^0.4.1",
+ "mime": "^2.4.4",
+ "mkdirp": "^0.5.1",
+ "range-parser": "^1.2.1",
+ "webpack-log": "^2.0.0"
+ }
+ },
+ "webpack-dev-server": {
+ "version": "3.10.2",
+ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.10.2.tgz",
+ "integrity": "sha512-pxZKPYb+n77UN8u9YxXT4IaIrGcNtijh/mi8TXbErHmczw0DtPnMTTjHj+eNjkqLOaAZM/qD7V59j/qJsEiaZA==",
+ "dev": true,
+ "requires": {
+ "ansi-html": "0.0.7",
+ "bonjour": "^3.5.0",
+ "chokidar": "^2.1.8",
+ "compression": "^1.7.4",
+ "connect-history-api-fallback": "^1.6.0",
+ "debug": "^4.1.1",
+ "del": "^4.1.1",
+ "express": "^4.17.1",
+ "html-entities": "^1.2.1",
+ "http-proxy-middleware": "0.19.1",
+ "import-local": "^2.0.0",
+ "internal-ip": "^4.3.0",
+ "ip": "^1.1.5",
+ "is-absolute-url": "^3.0.3",
+ "killable": "^1.0.1",
+ "loglevel": "^1.6.6",
+ "opn": "^5.5.0",
+ "p-retry": "^3.0.1",
+ "portfinder": "^1.0.25",
+ "schema-utils": "^1.0.0",
+ "selfsigned": "^1.10.7",
+ "semver": "^6.3.0",
+ "serve-index": "^1.9.1",
+ "sockjs": "0.3.19",
+ "sockjs-client": "1.4.0",
+ "spdy": "^4.0.1",
+ "strip-ansi": "^3.0.1",
+ "supports-color": "^6.1.0",
+ "url": "^0.11.0",
+ "webpack-dev-middleware": "^3.7.2",
+ "webpack-log": "^2.0.0",
+ "ws": "^6.2.1",
+ "yargs": "12.0.5"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "dev": true
+ },
+ "binary-extensions": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
+ "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
+ "dev": true
+ },
+ "chokidar": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
+ "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
+ "dev": true,
+ "requires": {
+ "anymatch": "^2.0.0",
+ "async-each": "^1.0.1",
+ "braces": "^2.3.2",
+ "fsevents": "^1.2.7",
+ "glob-parent": "^3.1.0",
+ "inherits": "^2.0.3",
+ "is-binary-path": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "normalize-path": "^3.0.0",
+ "path-is-absolute": "^1.0.0",
+ "readdirp": "^2.2.1",
+ "upath": "^1.1.1"
+ }
+ },
+ "cliui": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
+ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^2.1.1",
+ "strip-ansi": "^4.0.0",
+ "wrap-ansi": "^2.0.0"
+ },
+ "dependencies": {
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ }
+ }
+ },
+ "fsevents": {
+ "version": "1.2.11",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz",
+ "integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "bindings": "^1.5.0",
+ "nan": "^2.12.1",
+ "node-pre-gyp": "*"
+ },
+ "dependencies": {
+ "abbrev": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "are-we-there-yet": {
+ "version": "1.1.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "chownr": {
+ "version": "1.1.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "debug": {
+ "version": "3.2.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "deep-extend": {
+ "version": "0.6.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "detect-libc": {
+ "version": "1.0.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "fs-minipass": {
+ "version": "1.2.7",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.6.0"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
+ }
+ },
+ "glob": {
+ "version": "7.1.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "ignore-walk": {
+ "version": "3.0.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minimatch": "^3.0.4"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "ini": {
+ "version": "1.3.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "minipass": {
+ "version": "2.9.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "^5.1.2",
+ "yallist": "^3.0.0"
+ }
+ },
+ "minizlib": {
+ "version": "1.3.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.9.0"
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "needle": {
+ "version": "2.4.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "debug": "^3.2.6",
+ "iconv-lite": "^0.4.4",
+ "sax": "^1.2.4"
+ }
+ },
+ "node-pre-gyp": {
+ "version": "0.14.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "detect-libc": "^1.0.2",
+ "mkdirp": "^0.5.1",
+ "needle": "^2.2.1",
+ "nopt": "^4.0.1",
+ "npm-packlist": "^1.1.6",
+ "npmlog": "^4.0.2",
+ "rc": "^1.2.7",
+ "rimraf": "^2.6.1",
+ "semver": "^5.3.0",
+ "tar": "^4.4.2"
+ }
+ },
+ "nopt": {
+ "version": "4.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "abbrev": "1",
+ "osenv": "^0.1.4"
+ }
+ },
+ "npm-bundled": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "npm-normalize-package-bin": "^1.0.1"
+ }
+ },
+ "npm-normalize-package-bin": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "npm-packlist": {
+ "version": "1.4.7",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ignore-walk": "^3.0.1",
+ "npm-bundled": "^1.0.1"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "osenv": {
+ "version": "0.1.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.0"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "rc": {
+ "version": "1.2.8",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "rimraf": {
+ "version": "2.7.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "sax": {
+ "version": "1.2.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "semver": {
+ "version": "5.7.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "tar": {
+ "version": "4.4.13",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "chownr": "^1.1.1",
+ "fs-minipass": "^1.2.5",
+ "minipass": "^2.8.6",
+ "minizlib": "^1.2.1",
+ "mkdirp": "^0.5.0",
+ "safe-buffer": "^5.1.2",
+ "yallist": "^3.0.3"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "wide-align": {
+ "version": "1.1.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "yallist": {
+ "version": "3.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "get-caller-file": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
+ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
+ "dev": true
+ },
+ "glob-parent": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+ "dev": true,
+ "requires": {
+ "is-glob": "^3.1.0",
+ "path-dirname": "^1.0.0"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.0"
+ }
+ }
+ }
+ },
+ "is-absolute-url": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz",
+ "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==",
+ "dev": true
+ },
+ "is-binary-path": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+ "dev": true,
+ "requires": {
+ "binary-extensions": "^1.0.0"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "readdirp": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
+ "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.11",
+ "micromatch": "^3.1.10",
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "require-main-filename": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
+ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
+ "dev": true
+ },
+ "schema-utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-errors": "^1.0.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "dev": true,
+ "requires": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ },
+ "dependencies": {
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ }
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "supports-color": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+ "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "wrap-ansi": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
+ "dev": true,
+ "requires": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1"
+ },
+ "dependencies": {
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "dev": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "dev": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ }
+ }
+ },
+ "ws": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
+ "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
+ "dev": true,
+ "requires": {
+ "async-limiter": "~1.0.0"
+ }
+ },
+ "yargs": {
+ "version": "12.0.5",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
+ "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
+ "dev": true,
+ "requires": {
+ "cliui": "^4.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^1.0.1",
+ "os-locale": "^3.0.0",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^1.0.1",
+ "set-blocking": "^2.0.0",
+ "string-width": "^2.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^3.2.1 || ^4.0.0",
+ "yargs-parser": "^11.1.1"
+ }
+ },
+ "yargs-parser": {
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
+ "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ }
+ }
+ },
+ "webpack-log": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz",
+ "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "^3.0.0",
+ "uuid": "^3.3.2"
+ }
+ },
+ "webpack-manifest-plugin": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-2.2.0.tgz",
+ "integrity": "sha512-9S6YyKKKh/Oz/eryM1RyLVDVmy3NSPV0JXMRhZ18fJsq+AwGxUY34X54VNwkzYcEmEkDwNxuEOboCZEebJXBAQ==",
+ "dev": true,
+ "requires": {
+ "fs-extra": "^7.0.0",
+ "lodash": ">=3.5 <5",
+ "object.entries": "^1.1.0",
+ "tapable": "^1.0.0"
+ },
+ "dependencies": {
+ "fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ }
+ }
+ },
+ "webpack-sources": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
+ "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
+ "dev": true,
+ "requires": {
+ "source-list-map": "^2.0.0",
+ "source-map": "~0.6.1"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "websocket-driver": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz",
+ "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==",
+ "dev": true,
+ "requires": {
+ "http-parser-js": ">=0.4.0 <0.4.11",
+ "safe-buffer": ">=5.1.0",
+ "websocket-extensions": ">=0.1.1"
+ }
+ },
+ "websocket-extensions": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
+ "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
+ "dev": true
+ },
+ "whatwg-encoding": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz",
+ "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==",
+ "dev": true,
+ "requires": {
+ "iconv-lite": "0.4.24"
+ }
+ },
+ "whatwg-fetch": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
+ "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
+ },
+ "whatwg-mimetype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
+ "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==",
+ "dev": true
+ },
+ "whatwg-url": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz",
+ "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==",
+ "dev": true,
+ "requires": {
+ "lodash.sortby": "^4.7.0",
+ "tr46": "^1.0.1",
+ "webidl-conversions": "^4.0.2"
+ }
+ },
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+ "dev": true
+ },
+ "word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "dev": true
+ },
+ "workbox-background-sync": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz",
+ "integrity": "sha512-1uFkvU8JXi7L7fCHVBEEnc3asPpiAL33kO495UMcD5+arew9IbKW2rV5lpzhoWcm/qhGB89YfO4PmB/0hQwPRg==",
+ "dev": true,
+ "requires": {
+ "workbox-core": "^4.3.1"
+ }
+ },
+ "workbox-broadcast-update": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-4.3.1.tgz",
+ "integrity": "sha512-MTSfgzIljpKLTBPROo4IpKjESD86pPFlZwlvVG32Kb70hW+aob4Jxpblud8EhNb1/L5m43DUM4q7C+W6eQMMbA==",
+ "dev": true,
+ "requires": {
+ "workbox-core": "^4.3.1"
+ }
+ },
+ "workbox-build": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-4.3.1.tgz",
+ "integrity": "sha512-UHdwrN3FrDvicM3AqJS/J07X0KXj67R8Cg0waq1MKEOqzo89ap6zh6LmaLnRAjpB+bDIz+7OlPye9iii9KBnxw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.3.4",
+ "@hapi/joi": "^15.0.0",
+ "common-tags": "^1.8.0",
+ "fs-extra": "^4.0.2",
+ "glob": "^7.1.3",
+ "lodash.template": "^4.4.0",
+ "pretty-bytes": "^5.1.0",
+ "stringify-object": "^3.3.0",
+ "strip-comments": "^1.0.2",
+ "workbox-background-sync": "^4.3.1",
+ "workbox-broadcast-update": "^4.3.1",
+ "workbox-cacheable-response": "^4.3.1",
+ "workbox-core": "^4.3.1",
+ "workbox-expiration": "^4.3.1",
+ "workbox-google-analytics": "^4.3.1",
+ "workbox-navigation-preload": "^4.3.1",
+ "workbox-precaching": "^4.3.1",
+ "workbox-range-requests": "^4.3.1",
+ "workbox-routing": "^4.3.1",
+ "workbox-strategies": "^4.3.1",
+ "workbox-streams": "^4.3.1",
+ "workbox-sw": "^4.3.1",
+ "workbox-window": "^4.3.1"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz",
+ "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==",
+ "dev": true,
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "fs-extra": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
+ "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.4",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz",
+ "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==",
+ "dev": true
+ }
+ }
+ },
+ "workbox-cacheable-response": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-4.3.1.tgz",
+ "integrity": "sha512-Rp5qlzm6z8IOvnQNkCdO9qrDgDpoPNguovs0H8C+wswLuPgSzSp9p2afb5maUt9R1uTIwOXrVQMmPfPypv+npw==",
+ "dev": true,
+ "requires": {
+ "workbox-core": "^4.3.1"
+ }
+ },
+ "workbox-core": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-4.3.1.tgz",
+ "integrity": "sha512-I3C9jlLmMKPxAC1t0ExCq+QoAMd0vAAHULEgRZ7kieCdUd919n53WC0AfvokHNwqRhGn+tIIj7vcb5duCjs2Kg==",
+ "dev": true
+ },
+ "workbox-expiration": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-4.3.1.tgz",
+ "integrity": "sha512-vsJLhgQsQouv9m0rpbXubT5jw0jMQdjpkum0uT+d9tTwhXcEZks7qLfQ9dGSaufTD2eimxbUOJfWLbNQpIDMPw==",
+ "dev": true,
+ "requires": {
+ "workbox-core": "^4.3.1"
+ }
+ },
+ "workbox-google-analytics": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-4.3.1.tgz",
+ "integrity": "sha512-xzCjAoKuOb55CBSwQrbyWBKqp35yg1vw9ohIlU2wTy06ZrYfJ8rKochb1MSGlnoBfXGWss3UPzxR5QL5guIFdg==",
+ "dev": true,
+ "requires": {
+ "workbox-background-sync": "^4.3.1",
+ "workbox-core": "^4.3.1",
+ "workbox-routing": "^4.3.1",
+ "workbox-strategies": "^4.3.1"
+ }
+ },
+ "workbox-navigation-preload": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-4.3.1.tgz",
+ "integrity": "sha512-K076n3oFHYp16/C+F8CwrRqD25GitA6Rkd6+qAmLmMv1QHPI2jfDwYqrytOfKfYq42bYtW8Pr21ejZX7GvALOw==",
+ "dev": true,
+ "requires": {
+ "workbox-core": "^4.3.1"
+ }
+ },
+ "workbox-precaching": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-4.3.1.tgz",
+ "integrity": "sha512-piSg/2csPoIi/vPpp48t1q5JLYjMkmg5gsXBQkh/QYapCdVwwmKlU9mHdmy52KsDGIjVaqEUMFvEzn2LRaigqQ==",
+ "dev": true,
+ "requires": {
+ "workbox-core": "^4.3.1"
+ }
+ },
+ "workbox-range-requests": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-4.3.1.tgz",
+ "integrity": "sha512-S+HhL9+iTFypJZ/yQSl/x2Bf5pWnbXdd3j57xnb0V60FW1LVn9LRZkPtneODklzYuFZv7qK6riZ5BNyc0R0jZA==",
+ "dev": true,
+ "requires": {
+ "workbox-core": "^4.3.1"
+ }
+ },
+ "workbox-routing": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-4.3.1.tgz",
+ "integrity": "sha512-FkbtrODA4Imsi0p7TW9u9MXuQ5P4pVs1sWHK4dJMMChVROsbEltuE79fBoIk/BCztvOJ7yUpErMKa4z3uQLX+g==",
+ "dev": true,
+ "requires": {
+ "workbox-core": "^4.3.1"
+ }
+ },
+ "workbox-strategies": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-4.3.1.tgz",
+ "integrity": "sha512-F/+E57BmVG8dX6dCCopBlkDvvhg/zj6VDs0PigYwSN23L8hseSRwljrceU2WzTvk/+BSYICsWmRq5qHS2UYzhw==",
+ "dev": true,
+ "requires": {
+ "workbox-core": "^4.3.1"
+ }
+ },
+ "workbox-streams": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-4.3.1.tgz",
+ "integrity": "sha512-4Kisis1f/y0ihf4l3u/+ndMkJkIT4/6UOacU3A4BwZSAC9pQ9vSvJpIi/WFGQRH/uPXvuVjF5c2RfIPQFSS2uA==",
+ "dev": true,
+ "requires": {
+ "workbox-core": "^4.3.1"
+ }
+ },
+ "workbox-sw": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-4.3.1.tgz",
+ "integrity": "sha512-0jXdusCL2uC5gM3yYFT6QMBzKfBr2XTk0g5TPAV4y8IZDyVNDyj1a8uSXy3/XrvkVTmQvLN4O5k3JawGReXr9w==",
+ "dev": true
+ },
+ "workbox-webpack-plugin": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-4.3.1.tgz",
+ "integrity": "sha512-gJ9jd8Mb8wHLbRz9ZvGN57IAmknOipD3W4XNE/Lk/4lqs5Htw4WOQgakQy/o/4CoXQlMCYldaqUg+EJ35l9MEQ==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.0.0",
+ "json-stable-stringify": "^1.0.1",
+ "workbox-build": "^4.3.1"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz",
+ "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==",
+ "dev": true,
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.4",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz",
+ "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==",
+ "dev": true
+ }
+ }
+ },
+ "workbox-window": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-4.3.1.tgz",
+ "integrity": "sha512-C5gWKh6I58w3GeSc0wp2Ne+rqVw8qwcmZnQGpjiek8A2wpbxSJb1FdCoQVO+jDJs35bFgo/WETgl1fqgsxN0Hg==",
+ "dev": true,
+ "requires": {
+ "workbox-core": "^4.3.1"
+ }
+ },
+ "worker-farm": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
+ "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==",
+ "dev": true,
+ "requires": {
+ "errno": "~0.1.7"
+ }
+ },
+ "worker-rpc": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz",
+ "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==",
+ "dev": true,
+ "requires": {
+ "microevent.ts": "~0.1.1"
+ }
+ },
+ "wrap-ansi": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.0",
+ "string-width": "^3.0.0",
+ "strip-ansi": "^5.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "write": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
+ "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
+ "dev": true,
+ "requires": {
+ "mkdirp": "^0.5.1"
+ }
+ },
+ "write-file-atomic": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz",
+ "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.11",
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "ws": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
+ "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
+ "dev": true,
+ "requires": {
+ "async-limiter": "~1.0.0"
+ }
+ },
+ "xml-name-validator": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
+ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==",
+ "dev": true
+ },
+ "xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true
+ },
+ "xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "dev": true
+ },
+ "y18n": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+ "dev": true
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "yaml": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.7.2.tgz",
+ "integrity": "sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.6.3"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.8.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz",
+ "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==",
+ "dev": true,
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.4",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz",
+ "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==",
+ "dev": true
+ }
+ }
+ },
+ "yargs": {
+ "version": "13.3.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz",
+ "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==",
+ "dev": true,
+ "requires": {
+ "cliui": "^5.0.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^3.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^13.1.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "yargs-parser": {
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
+ "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ }
+ }
+}
diff --git a/viscoll-app/package.json b/viscoll-app/package.json
new file mode 100644
index 00000000..e7c86488
--- /dev/null
+++ b/viscoll-app/package.json
@@ -0,0 +1,71 @@
+{
+ "name": "viscoll-app",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "axios": "^0.19.0",
+ "babel-polyfill": "^6.26.0",
+ "clientjs": "^0.1.11",
+ "copy-to-clipboard": "^3.2.0",
+ "es6-promise": "^4.1.0",
+ "file-saver": "^2.0.2",
+ "fuse.js": "^3.4.5",
+ "immutability-helper": "^2.4.0",
+ "js-file-download": "^0.4.7",
+ "jszip": "^3.2.1",
+ "localforage": "^1.5.0",
+ "lodash": "^4.17.15",
+ "material-ui": "^0.19.4",
+ "material-ui-chip-input": "^0.18.3",
+ "material-ui-superselectfield": "^1.5.6",
+ "openseadragon": "^2.3.1",
+ "paper": "^0.11.4",
+ "react": "^15.6.1",
+ "react-detect-offline": "^1.0.6",
+ "react-dom": "^15.6.1",
+ "react-redux": "^5.0.5",
+ "react-router-dom": "^4.1.1",
+ "react-tap-event-plugin": "^2.0.1",
+ "react-tiny-virtual-list": "^2.1.4",
+ "react-virtualized": "^9.21.1",
+ "redux": "^3.7.1",
+ "redux-axios-middleware": "^4.0.0",
+ "redux-persist": "^4.8.2"
+ },
+ "devDependencies": {
+ "babel-preset-es2015": "^6.24.1",
+ "babel-preset-react": "^6.24.1",
+ "babel-preset-stage-2": "^6.24.1",
+ "enzyme": "^2.9.1",
+ "react-addons-test-utils": "^15.6.0",
+ "react-scripts": "^3.4.0",
+ "react-test-renderer": "^15.6.1",
+ "redux-devtools-extension": "^2.13.2",
+ "redux-mock-store": "^1.2.3",
+ "regenerator-runtime": "^0.11.0"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "eject": "react-scripts eject"
+ },
+ "babel": {
+ "presets": [
+ "react",
+ "es2015",
+ "stage-2"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ }
+}
diff --git a/viscoll-app/public/favicon.ico b/viscoll-app/public/favicon.ico
new file mode 100644
index 00000000..58182242
Binary files /dev/null and b/viscoll-app/public/favicon.ico differ
diff --git a/viscoll-app/public/index.html b/viscoll-app/public/index.html
new file mode 100644
index 00000000..61e09cd3
--- /dev/null
+++ b/viscoll-app/public/index.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+ Viscoll
+
+
+
+ You need to enable JavaScript to run this app.
+
+
+
+
+
diff --git a/viscoll-app/public/manifest.json b/viscoll-app/public/manifest.json
new file mode 100644
index 00000000..be607e41
--- /dev/null
+++ b/viscoll-app/public/manifest.json
@@ -0,0 +1,15 @@
+{
+ "short_name": "React App",
+ "name": "Create React App Sample",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "192x192",
+ "type": "image/png"
+ }
+ ],
+ "start_url": "./index.html",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/viscoll-app/sass/components/_dialog.scss b/viscoll-app/sass/components/_dialog.scss
new file mode 100644
index 00000000..5a588dff
--- /dev/null
+++ b/viscoll-app/sass/components/_dialog.scss
@@ -0,0 +1,101 @@
+.addDialog {
+
+ .title {
+ color: $black;
+
+ }
+ h3 {
+ border-bottom: 1px solid #ddd;
+ }
+
+ h4 {
+ color: $black;
+ margin-top: 2em;
+ margin-bottom: 0em;
+ font-weight:600;
+ }
+
+ .label {
+ width: 200px;
+ display:inline-block;
+ }
+ .input {
+ width: 200px;
+ display:inline-block;
+ text-align: right;
+ }
+}
+.feedbackDialog {
+ p {
+ color: $black;
+ }
+ .label {
+ color: $black;
+ width: 100px;
+ display:inline-block;
+ vertical-align: top;
+ }
+ .input {
+ width: 250px;
+ display:inline-block;
+ text-align: right;
+ }
+}
+.newProjectDialog {
+ p {
+ color: $black;
+ }
+ h1 {
+ font-weight: normal;
+ text-transform: inherit;
+ }
+ h3 {
+ font-size: 1em;
+ margin-top: 0em;
+ }
+ .section {
+ margin-left: 1em;
+ }
+ .newProjectSelection {
+ button.btnSelection {
+ width: 100%;
+ background: $white;
+ border: 0;
+ @include box-shadow(0px 2px 10px 0px rgba(0,0,0,0.2));
+ margin-bottom: 1em;
+ outline: 0;
+
+ &:focus, &:hover {
+ outline-style: solid;
+ outline-width: 0.5em;
+ outline-color: transparentize($teal, 0.5);
+ cursor: pointer;
+ }
+ }
+ .selectItem {
+ display: flex;
+ padding: 2.5em 0em;
+
+ .icon {
+ width: 50px;
+ padding:0px 15px;
+ }
+ .text {
+ line-height: 2.5em;
+ span:nth-child(1) {
+ text-align: left;
+ font-size: 2.5em;
+ display: block;
+ color: $black;
+ width: 100%;
+ }
+ span:nth-child(2) {
+ font-size: 1.3em;
+ color: lighten($black, 15);
+ width: 100%;
+ display: block;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/components/_textarea.scss b/viscoll-app/sass/components/_textarea.scss
new file mode 100644
index 00000000..a199c3ca
--- /dev/null
+++ b/viscoll-app/sass/components/_textarea.scss
@@ -0,0 +1,8 @@
+textarea {
+ border: 1px solid #e0e0e0;
+ width: 100%;
+ font-size: 1em;
+ &:focus {
+ outline-color: $teal;
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/components/_tooltip.scss b/viscoll-app/sass/components/_tooltip.scss
new file mode 100644
index 00000000..d8c70afd
--- /dev/null
+++ b/viscoll-app/sass/components/_tooltip.scss
@@ -0,0 +1,67 @@
+.tooltip {
+ position: relative;
+ display: inline-block;
+ width: 100%;
+
+ .text {
+ visibility: hidden;
+ width: 210px;
+ font-weight: 300;
+ background: transparentize(darken($black, 15%), 0.1);
+ color: #fff;
+ text-align: center;
+ @include border-radius(6px);
+ padding: 0.5em;
+ margin: 1em;
+ font-size: 0.9em;
+ opacity: 0;
+ @include transition(all, 200ms, ease-in-out);
+
+ /* Position the tooltip */
+ position: absolute;
+ z-index: 100;
+
+ &::after {
+ content: " ";
+ position: absolute;
+ bottom: 100%; /* At the top of the tooltip */
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: transparent transparent transparentize(darken($black, 15%), 0.1) transparent;
+ }
+ }
+
+ &.addDialog {
+ .text {
+ width: 70%;
+ &.active {
+ visibility: visible;
+ opacity: 1;
+ }
+ &::after {
+ left: 20%;
+ }
+ }
+ }
+
+ &.eyeToggle {
+ width: initial;
+ .text {
+ left: -5%;
+ margin-left: 0;
+ width: 100px;
+
+ &::after {
+ bottom: 100%; /* At the top of the tooltip */
+ left: 15%;
+ margin-left: -5px;
+ }
+ &.active {
+ visibility: visible;
+ opacity: 1;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/index.scss b/viscoll-app/sass/index.scss
new file mode 100644
index 00000000..8a5a91ea
--- /dev/null
+++ b/viscoll-app/sass/index.scss
@@ -0,0 +1,24 @@
+@import 'lib/variables';
+@import 'lib/mixins';
+@import 'layout/landing';
+@import 'layout/sidebar';
+@import 'layout/projectPanel';
+@import 'layout/infobox';
+@import 'layout/workspace';
+@import 'layout/tabular';
+@import 'layout/topbar';
+@import 'layout/notes';
+@import 'layout/filter';
+@import 'layout/loading';
+@import 'layout/404';
+@import 'layout/dashboard';
+@import 'layout/imageManager';
+@import 'layout/imageCollection';
+@import 'components/dialog';
+@import 'components/tooltip';
+@import 'components/textarea';
+@import 'typography';
+
+html, body {
+ background: #F2F2F2;
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/layout/_404.scss b/viscoll-app/sass/layout/_404.scss
new file mode 100644
index 00000000..b714f513
--- /dev/null
+++ b/viscoll-app/sass/layout/_404.scss
@@ -0,0 +1,25 @@
+.fourOhFour {
+ width: 100vw;
+ height: 100vh;
+ background: $bg_blue;
+ display: flex;
+ align-items: center;
+ .container {
+ width: 100vw;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ margin-top: -10%;
+ h1 {
+ font-size: 8em;
+ color: $white;
+ padding-bottom: 0;
+ margin-bottom: 10px;
+ }
+ p {
+ color: $white;
+ margin-bottom: 30px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/layout/_dashboard.scss b/viscoll-app/sass/layout/_dashboard.scss
new file mode 100644
index 00000000..6d9d4ce1
--- /dev/null
+++ b/viscoll-app/sass/layout/_dashboard.scss
@@ -0,0 +1,46 @@
+#listView {
+ .header {
+ display: flex;
+ padding: 1em 2em;
+ font-size: 0.8em;
+ div {
+ width: 50%;
+ }
+ }
+ button {
+ width: 100%;
+ display: flex;
+ text-align: left;
+ border-left: 1px solid transparentize($white, 0.5);
+ border-right: 1px solid transparentize($white, 0.5);
+ border-top: 1px solid transparentize($dark_gray, 0.8);
+ border-bottom: 1px solid transparentize($white, 0.5);
+ padding: 1em 2em;
+ background: transparentize($white, 0.5);
+ font-size: 0.9em;
+ color: $black;
+ cursor: pointer;
+ @include transition(background, 100ms, ease-in-out);
+
+ &:hover {
+ background: $white;
+ }
+ &:focus {
+ outline-style: solid;
+ outline-color: transparentize($teal, 0.5);
+ outline-width: 2px;
+ border: 1px solid $teal;
+ }
+ &.selected {
+ background: $teal;
+ }
+
+ &.selected:focus {
+ outline: 0;
+ }
+
+ div {
+ width: 50%;
+ }
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/layout/_filter.scss b/viscoll-app/sass/layout/_filter.scss
new file mode 100644
index 00000000..d6c3896c
--- /dev/null
+++ b/viscoll-app/sass/layout/_filter.scss
@@ -0,0 +1,47 @@
+.filter {
+ width: 100%;
+ max-height: 45%;
+ position: relative;
+ z-index: 2;
+ left: 0;
+ display: flex;
+ justify-content: flex-end;
+ @include transition(opacity, 200ms, linear);
+}
+.filterContainer {
+ border-top:1px solid $gray;
+ position: fixed;
+ width: 82%;
+ max-height: 45%;
+ background: $white;
+ padding-bottom: 10px;
+ overflow: auto;
+ @include transition(top, 450ms, cubic-bezier(0.23, 1, 0.32, 1));
+ @include box-shadow(0px 2px 8px 0px rgba(0,0,0,0.3));
+}
+.filterRow {
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ & + .filterRow {
+ margin-top: -20px;
+ }
+
+ .filterField {
+ width: 230px;
+ margin-left: 10px;
+ &:first-child {
+ margin-left:20px;
+ }
+ &:last-child {
+ width: 160px;
+ text-align: center;
+ }
+ }
+}
+.filterMessage {
+ text-transform: uppercase;
+ font-size: 0.9em;
+ font-weight: 500;
+ color: $dark_gray;
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/layout/_imageCollection.scss b/viscoll-app/sass/layout/_imageCollection.scss
new file mode 100644
index 00000000..80deca51
--- /dev/null
+++ b/viscoll-app/sass/layout/_imageCollection.scss
@@ -0,0 +1,7 @@
+.imageFilter {
+ @include box-shadow(0px 2px 10px 0px rgba(0,0,0,0.1));
+ background: $white;
+ margin: 0em 0em 0.5em 0em;
+ display: flex;
+ justify-content: space-between;
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/layout/_imageManager.scss b/viscoll-app/sass/layout/_imageManager.scss
new file mode 100644
index 00000000..09528a89
--- /dev/null
+++ b/viscoll-app/sass/layout/_imageManager.scss
@@ -0,0 +1,229 @@
+.imageManager {
+ .form {
+ .row {
+ display: flex;
+ .label {
+ padding-top:1em;
+ min-width: 120px;
+ }
+ .input {
+ flex-grow: 1;
+ }
+ }
+ }
+ .manageManifests {
+ padding: 1em 2em 0em 2em;
+ h2 {
+ font-size: 1.1em;
+ padding: 0em 0em 0em 0em;
+ margin:0em;
+ color: $black;
+ }
+ .manifestCard {
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ padding: 1.5em 1.5em 1em 1.5em;
+
+ span {
+ font-size: 1em;
+ font-weight: normal;
+ color: transparentize($black, 0.2);
+ }
+ &>div {
+ flex-grow: 1;
+ }
+ .thumbnails {
+ text-align: right;
+ }
+ }
+ .addImages {
+ display: flex;
+ &>div {
+ width: 50%;
+ // border: 1px solid transparentize($dark_gray, 0.8);
+ padding: 1.5em 1.5em 1em 1.5em;
+ background: $white;
+ @include box-shadow(0px 2px 10px 0px rgba(0,0,0,0.1));
+
+
+ &:first-child {
+ margin-right: 0.5em;
+ }
+ &:nth-child(2) {
+ margin-left: 0.5em;
+ }
+ }
+ }
+ }
+ .imageMapper {
+ .moveableItem {
+ height: 50px;
+ background: $white;
+ border-width: 0px 1px 0px 1px;
+ border-style: solid;
+ border-color: $gray;
+ cursor: pointer;
+ @include border-radius(3px);
+ padding-left: 0.5em;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .text {
+ color: $black;
+ padding-left: 0.5em;
+ @media screen and (max-width: $xsmall) {
+ font-size: 0.9em;
+ }
+ &>span {
+ font-size: 0.8em;
+ }
+ }
+ .thumbnail {
+ opacity: 0.5;
+ @include transition(all, 200ms, ease-in-out);
+
+ &:hover {
+ opacity: 1;
+ cursor: pointer;
+ }
+ }
+ }
+
+ .middleBar {
+ display: flex;
+ justify-content: space-around;
+ background: $white;
+ margin: 0.5em 1em;
+ padding: 0.2em 0em;
+ @media screen and (max-width: $small) {
+ margin: 0.5em 0em;
+ }
+ }
+
+ .panelBar {
+ background: $white;
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ border-bottom: 2px solid $gray;
+ height: 40px;
+ .title {
+ padding-left: 1em;
+ text-transform: uppercase;
+ color: $black;
+ font-weight: bold;
+ }
+ .action {
+ padding-right: 0.5em;
+ }
+ }
+ .topPanel {
+ flex-grow: 2;
+ display: flex;
+ flex-direction: column;
+ width: 97%;
+ margin-top: 1em;
+ margin-left: 1em;
+ background: darken($gray, 3);
+ color: $black;
+ &>div {
+ // display: block;
+ width: 100%;
+ height: 100%;
+ margin: 0em;
+ // padding: 0.5em;
+ }
+ .boards {
+ display: flex;
+ width: 100%;
+ overflow-y: auto;
+ &>div {
+ width: 50%;
+ }
+ }
+ .binText {
+ text-transform: uppercase;
+ text-align: center;
+ display:flex;
+ align-items: center;
+ justify-content: center;
+ width: 100% !important;
+ height: 38vh;
+ }
+ }
+ .bottomPanel {
+ flex-grow: 2;
+ display: flex;
+ justify-content: space-between;
+ width: 97%;
+ // height: 42vh;
+ margin-left: 1em;
+ .backlog {
+ width: 49%;
+ padding: 0em;
+
+ .scrollable {
+ overflow-y: auto;
+ }
+ }
+ .sideBacklog {
+ @extend .backlog;
+ .scrollable {
+ text-align: left;
+ height: 32vh;
+ }
+ }
+ .imageBacklog {
+ @extend .backlog;
+ height: 37vh;
+ .manifestSelection {
+ // background: teal;
+ height: 40px;
+ padding: 0em 1em;
+ display: flex;
+ justify-content: space-evenly;
+ border-bottom: 2px solid $gray;
+ background: $white;
+ width: 100%;
+ .title {
+ width:70px;
+ padding-top: 10px;
+ padding-right: 10px;
+ color: $black;
+ }
+ .form {
+ flex-grow: 1;
+ // background: green;
+ }
+ }
+ .scrollable {
+ height: 27vh;
+ }
+ }
+ }
+ .mainToolbar {
+ // position: fixed;
+ // bottom: 0;
+ margin-top: 0.5em;
+ width: 100%;
+ height: 50px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background: $white;
+ @include box-shadow(0px 2px 2px 0px rgba(0,0,0,0.05));
+ .message {
+ padding-left: 1em;
+ text-transform: uppercase;
+ color: $black;
+ font-size: 0.9em;
+ }
+ .actions {
+ padding-right:1em;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/layout/_infobox.scss b/viscoll-app/sass/layout/_infobox.scss
new file mode 100644
index 00000000..51030c98
--- /dev/null
+++ b/viscoll-app/sass/layout/_infobox.scss
@@ -0,0 +1,51 @@
+.infoBox {
+ position: fixed;
+ display: inline-block;
+ width: 22%;
+ vertical-align:top;
+ right: 0;
+ top: 56px;
+ background: white;
+ max-height: 90%;
+ overflow-y: auto;
+ margin: 2% 2% 0% 0%;
+ @include box-shadow(0px 2px 10px 0px rgba(0,0,0,0.1));
+
+ .inner {
+ padding: 10px 20px 15px 20px;
+ @media screen and (max-width: $small) {
+ padding: 5px 15px 7px 15px;
+ }
+ @media screen and (max-width: $xsmall) {
+ padding: 5px 10px 7px 10px;
+ }
+
+ .row {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: center;
+ }
+ .label {
+ width: 35%;
+ }
+ .input {
+ width: 55%;
+ @media screen and (max-width: $xsmall) {
+ width: 50%;
+ }
+ }
+ }
+ button.image {
+ border: 0;
+ background: none;
+ padding: 0;
+ margin: 0;
+ font-size: 1em;
+ & + button {
+ margin-left: 0.5em;
+ }
+ overflow: none;
+ max-width: 40%;
+ }
+}
diff --git a/viscoll-app/sass/layout/_landing.scss b/viscoll-app/sass/layout/_landing.scss
new file mode 100644
index 00000000..da1c9412
--- /dev/null
+++ b/viscoll-app/sass/layout/_landing.scss
@@ -0,0 +1,77 @@
+.landing {
+ width: 100vw;
+ height: 100vh;
+ background: $bg_blue2;
+ text-align: center;
+
+ .container {
+ margin: 0 auto;
+ width: 80vw;
+ height: 90vh;
+ display: flex;
+ align-items: center;
+ align-content: center;
+ }
+
+ img {
+ width: 100%;
+ }
+
+ .panelLogo {
+ width: 55%;
+ }
+
+ .panelLogin {
+ width: 40%;
+ padding-left: 5%;
+ }
+
+ .panelBottom {
+ display: table;
+ width: 100%;
+ height:10vh;
+ background: $teal;
+
+ div {
+ display: table-cell;
+ vertical-align: middle;
+ }
+
+ span:first-child {
+ font-weight: bold;
+ }
+
+ span {
+ color: #2b4352;
+ font-size: 1.1em;
+ display: block;
+ margin-bottom: .5em;
+ }
+ }
+
+ hr {
+ border: 1px solid $teal;
+ }
+
+ .spacingBottom {
+ margin-bottom: 1.5em;
+ }
+
+ .spacingTop {
+ margin-top: 1.5em;
+ }
+
+ p {
+ color: $teal;
+ }
+
+ a {
+ color: $teal;
+ cursor: pointer;
+ text-decoration: underline;
+
+ &:hover {
+ color: lighten($teal, 20%);
+ }
+ }
+}
diff --git a/viscoll-app/sass/layout/_loading.scss b/viscoll-app/sass/layout/_loading.scss
new file mode 100644
index 00000000..abab09bd
--- /dev/null
+++ b/viscoll-app/sass/layout/_loading.scss
@@ -0,0 +1,21 @@
+.appLoading {
+ width: 100vw;
+ height: 100vh;
+ background: $bg_blue;
+ display: flex;
+ align-items: center;
+ .container {
+ width: 100vw;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ margin-top: -5%;
+ }
+ .logo {
+ width: 30%;
+ max-width: 300px;
+ margin-bottom: 1em;
+ }
+
+}
diff --git a/viscoll-app/sass/layout/_notes.scss b/viscoll-app/sass/layout/_notes.scss
new file mode 100644
index 00000000..3f935066
--- /dev/null
+++ b/viscoll-app/sass/layout/_notes.scss
@@ -0,0 +1,138 @@
+.notesManager {
+ height: 100%;
+ .container {
+ padding: 1em 2em 0em 2em;
+ }
+
+ .browse {
+ height: 100%;
+ .notesList {
+ .item {
+ margin: 0.5em 0em 0em 0em;
+ overflow: hidden;
+ @include transition(background, 200ms, ease-in-out);
+ width: 90%;
+ font-size: 1em;
+ background: $gray;
+ border: 0px;
+ @include box-shadow(0px 0px 5px 0px rgba(0,0,0,0.2));
+ &:hover {
+ cursor: pointer;
+ background: $white;
+ }
+ .title {
+ padding-top: 10px;
+ font-weight: 500;
+ color: $black;
+ }
+ .type {
+ padding-top: 5px;
+ padding-bottom: 10px;
+ color: $dark_gray;
+ font-style: italic;
+ font-size: 0.9em;
+ }
+ &.active {
+ background: $teal;
+ }
+ }
+ position: fixed;
+ top:56px;
+ left:18%;
+ width: 300px;
+ height: calc(100% - 56px);
+ overflow-y: auto;
+ text-align: center;
+ background: darken($gray, 5%);
+ }
+ .details {
+ position: relative;
+ width: calc(100% - 320px);
+ left: 300px;
+ }
+ }
+ .noteType {
+ padding: 1em 2em;
+ .items {
+ display: flex;
+ flex-wrap: wrap;
+ }
+ .item {
+ width: 220px;
+ margin-right: 1em;
+ }
+ .create {
+ display: flex;
+ margin-bottom: 2em;
+ .input {
+ margin-right: 1em;
+ }
+ }
+ }
+}
+.notesInfobox {
+ display: flex;
+ flex-wrap: wrap;
+}
+.noteSearch {
+ height: 56px;
+ @media screen and (max-width: 890px) {
+ width: 170px;
+ }
+
+ .searchTextbox {
+ padding-top: 5px;
+ }
+}
+.searchOptions {
+ visibility: hidden;
+ opacity: 0;
+ background: $white;
+ width: 200px;
+ @include border-radius(0px 0px 6px 6px);
+ @include transition(all, 200ms, ease-in-out);
+
+ @media screen and (max-width: 890px) {
+ width: 140px;
+ }
+
+ padding: 0em 1em;
+ &.active{
+ visibility: visible;
+ opacity: 1;
+ }
+}
+.noteForm {
+ width: 100%;
+ margin-left: 2%;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: flex-start;
+
+ .label {
+ padding-top:1em;
+ width: 20%;
+ @media screen and (max-width: $xsmall) {
+ font-size: 0.8em;
+ }
+ }
+ .input {
+ width: 80%;
+ @media screen and (max-width: $xsmall) {
+ padding-left: 5%;
+ width: 75%;
+ }
+ .textOnly {
+ margin-top: 1em;
+ color: $black;
+ }
+ }
+ .buttons {
+ text-align: right;
+ width: 100%;
+ padding-top: 2em;
+ }
+ .objectAttachments {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/layout/_projectPanel.scss b/viscoll-app/sass/layout/_projectPanel.scss
new file mode 100644
index 00000000..dd3b71b7
--- /dev/null
+++ b/viscoll-app/sass/layout/_projectPanel.scss
@@ -0,0 +1,12 @@
+.projectPanelInfo {
+ padding: 1em;
+ line-height: 2em;
+
+ .info {
+ padding-top: 1em;
+ font-size: 0.9em;
+ span {
+ color: transparentize($black, 0.2);
+ }
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/layout/_sidebar.scss b/viscoll-app/sass/layout/_sidebar.scss
new file mode 100644
index 00000000..c7a87440
--- /dev/null
+++ b/viscoll-app/sass/layout/_sidebar.scss
@@ -0,0 +1,152 @@
+.sidebar {
+ position: fixed;
+ display: block;
+ top: 55px;
+ width: 18%;
+ height: calc(100% - 55px);
+ background: $bg_blue;
+ opacity: 1;
+ @include transition(all, 200ms, ease-in-out);
+ overflow-y: auto;
+ z-index: 2200;
+
+ &.lowerZIndex {
+ z-index: inherit;
+ }
+
+ &.hidden {
+ opacity: 0;
+ }
+ hr {
+ border: 1px solid $teal;
+ margin: 0;
+ }
+ h1 {
+ text-transform: uppercase;
+ color: $white;
+ font-size: 1em;
+ font-weight: 600;
+ }
+ h2 {
+ color: $white;
+ font-size: 0.8em;
+ font-weight: lighter;
+ padding: 1em 0em;
+ margin: 0em;
+ text-transform: uppercase;
+ &:nth-child(1) {
+ padding-top: 0em;
+ }
+ @media screen and (max-width: $xsmall) {
+ font-size: 0.7em;
+ }
+ }
+ .panel {
+ .header {
+ padding: 0.5em 1em;
+ display: flex;
+ justify-content: space-between;
+ @media screen and (max-width: $xsmall) {
+ font-size: 0.85em;
+ }
+ }
+ .content {
+ padding: 1em;
+ background: transparentize($fg_blue, 0.8);
+ &.hidden {
+ display: none;
+ }
+ }
+ &:last-child {
+ margin-bottom: 50px;
+ }
+ }
+ .selectMode {
+ padding: 1em 1em 1em 1em;
+ text-align: center;
+
+ span {
+ font-size: 13px;
+ color: $teal;
+ text-transform: uppercase;
+ }
+ .tip {
+ font-size: 0.8em;
+ color: $gray;
+ text-align: left;
+ line-height: 1.5em;
+ }
+ .close {
+ text-align: right;
+ margin-right: -10px;
+ margin-top: -10px;
+ }
+ background: darken($bg_blue, 3%);
+ }
+
+ .navigation {
+ text-align: center;
+ margin: 0px;
+ text-transform: uppercase;
+ @include transition(all, 100ms, ease-in-out);
+ width: 100%;
+ border: 0;
+ background: none;
+ color: $white;
+ font-size: 1em;
+ &:hover {
+ background: transparentize($teal,0.90);
+ font-weight: bold;
+ }
+ &.active {
+ background: $teal;
+ font-weight: bold;
+ color: $black;
+ }
+ }
+ .manager {
+ @extend .navigation;
+ padding: 0.5em 0em;
+ @media screen and (max-width: $xsmall) {
+ font-size: 0.8em;
+ }
+ }
+ .dashboard {
+ @extend .navigation;
+ text-transform: none;
+ padding: 0.75em 0em;
+ &:hover {
+ background: transparentize($white,0.95);
+ }
+ &.active {
+ background: transparentize($white, 0.9);
+ font-weight: bold;
+ color: $white;
+ }
+ @media screen and (max-width: $xsmall) {
+ font-size: 0.8em;
+ }
+ }
+
+ .export {
+ line-height: 45px;
+ }
+}
+
+.feedback {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ width: 18%;
+ z-index:10000;
+ text-align: center;
+}
+
+.editIcon {
+ @include transition(all, 200ms, ease-in-out);
+ background: #BABABA !important;
+ &:hover {
+ background: #A5A5A5 !important;
+ cursor: pointer;
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/layout/_tabular.scss b/viscoll-app/sass/layout/_tabular.scss
new file mode 100644
index 00000000..ee25668e
--- /dev/null
+++ b/viscoll-app/sass/layout/_tabular.scss
@@ -0,0 +1,227 @@
+.groupContainer {
+ input {
+ opacity: 0;
+ width: 1px;
+ height: 1px;
+ margin-top: -10px;
+ float:right;
+
+ }
+ @include box-shadow(0px 2px 10px 0px rgba(0,0,0,0.1));
+ @include transition(border, 150ms, ease-in-out);
+ background: $white;
+ margin-bottom: 1em;
+ cursor: pointer;
+ border: 2px solid $white;
+
+ &.focus {
+ border: 2px solid $teal;
+ }
+ &.active {
+ background: $teal;
+ }
+
+ .groupMembers {
+ padding: 0em 1em 1em 1em;
+
+ &.hidden {
+ display: none;
+ }
+ }
+}
+
+.itemContainer {
+ display: flex;
+ align-items: stretch;
+ &.group {
+ justify-content: space-between;
+
+ .groupSection {
+ display: flex;
+ }
+
+ .toggleButton {
+ float: right;
+ }
+ }
+}
+.leafContainer {
+ @include box-shadow(0px 2px 10px 0px rgba(0,0,0,0.1));
+ margin-bottom: 0.2em;
+ cursor: pointer;
+ background: $white;
+
+ .leafSection {
+ display: flex;
+ flex-grow: 1;
+ @include transition(border, 100ms, ease-in-out);
+ border: 2px solid $white;
+
+ &.active {
+ background: $teal;
+ }
+
+ &.focus {
+ border: 2px solid $teal;
+ }
+ }
+}
+
+.itemName {
+ width: 70px;
+ display: flex;
+ align-items: center;
+ font-weight: 500;
+ padding-left: 20px;
+ min-height: 45px;
+ @media screen and (max-width:$xsmall) {
+ font-size: 0.9em;
+ }
+}
+.itemAttributes {
+ flex-grow: 4;
+ display: flex;
+}
+.attribute {
+ display: flex;
+ align-items: center;
+ max-width: 100px;
+ padding: 0.5em 0.8em;
+ color: transparentize($black, 0.4);
+ font-weight: 400;
+ border-left: 1px solid transparentize($black, 0.95);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ @media screen and (max-width:$xsmall) {
+ font-size: 0.9em;
+ }
+
+ span {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-size: 11px;
+ @media screen and (max-width: $xsmall) {
+ font-size: 10px;
+ }
+ }
+
+ span:nth-child(1) {
+ color: transparentize($black, 0.6);
+ display: block;
+ }
+
+ &.active, &:hover {
+ color: $black;
+ }
+
+ &.active {
+ &.small {
+ color: $black;
+ }
+ span:nth-child(1) {
+ color: transparentize($black, 0.3);
+ }
+ }
+}
+
+.sideSection {
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ border-left: 1px solid transparentize($black, 0.85);
+ overflow: hidden;
+
+ .side {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ border: 2px solid $white;
+ cursor: pointer;
+
+ &.active {
+ background: $teal;
+ }
+ &.focus {
+ border: 2px solid $teal;
+ color: transparentize($black, 0);
+ }
+
+ .name {
+ width: 40px;
+ display: inline-block;
+ padding: 7px 10px 0px 10px;
+ vertical-align: top;
+ font-weight: normal;
+ border-right: 1px solid transparentize($black, 0.95);
+ }
+ .attribute {
+ display: inline-block;
+ vertical-align: top;
+ padding: 0px 10px;
+ padding-top: 2px;
+ border-right: 1px solid transparentize($black, 0.95);
+ color: transparentize($black, 0.4);
+ @include transition(color, 200ms, ease-in-out);
+ font-size: 13px;
+
+ span {
+ font-weight: normal;
+ color: transparentize($black, 0.4);
+ }
+ &:hover {
+ color: transparentize($black, 0);
+ }
+ &.active {
+ color: transparentize($black, 0);
+ }
+ }
+ &:first-child {
+ border-bottom: 2px solid transparentize($black, 0.90);
+ &.focus {
+ border-bottom: 2px solid $teal;
+ }
+ }
+ }
+}
+
+.sideToggle {
+ width: 20px;
+ height: 49px;
+ border-left: 1px solid transparentize($black, 0.85);
+ cursor: pointer;
+ .side {
+ display: block;
+ width: 16px;
+ height: 18px;
+ padding-top: 3px;
+ text-align: center;
+ color: transparentize($black, 0.4);
+ border: 2px solid $white;
+
+ &:first-child {
+ border-bottom: 1px solid transparentize($black, 0.85);
+ }
+ &.active {
+ background: $teal;
+ }
+ &.focus {
+ border: 2px solid $teal;
+ color: transparentize($black, 0);
+ }
+ }
+}
+
+.flash {
+ animation-name: flashify;
+ animation-duration: 3s;
+}
+
+@include keyframes(flashify) {
+ 0% { border: 2px solid rgba(255, 255, 255, 1); }
+ 35% { border: 2px solid rgba(78, 214, 203, 1); }
+ // 50% { border: 2px solid rgba(255,255,255, 1); }
+ 80% { border: 2px solid rgba(78, 214, 203, 1); }
+ 100% { border: 2px solid rgba(255, 255, 255, 1); }
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/layout/_topbar.scss b/viscoll-app/sass/layout/_topbar.scss
new file mode 100644
index 00000000..74424f3c
--- /dev/null
+++ b/viscoll-app/sass/layout/_topbar.scss
@@ -0,0 +1,32 @@
+.topbar {
+ position: fixed;
+ left:0px;
+ width: 100%;
+ z-index: 2200;
+ @include box-shadow(0px 1px 2px 1px rgba(0,0,0,0.05));
+
+ &.lowerZIndex {
+ z-index: 1500;
+ }
+
+ .logo {
+ float:left;
+ width: 18%;
+ height: 55px;
+ text-align: center;
+ position: relative;
+ background: $bg_blue;
+ img {
+ width: 60%;
+ margin: 0;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ -ms-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+ @media screen and (max-width: $xsmall) {
+ width: 85%;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/layout/_workspace.scss b/viscoll-app/sass/layout/_workspace.scss
new file mode 100644
index 00000000..66a111ac
--- /dev/null
+++ b/viscoll-app/sass/layout/_workspace.scss
@@ -0,0 +1,33 @@
+.workspace {
+ position: absolute;
+ left: 18%;
+ top: 56px;
+}
+.projectWorkspace {
+ @extend .workspace;
+ width: 54%;
+ margin: 2%;
+ .viewingMode {
+ display: flex;
+ &>div:first-child {
+ margin-top: 4px;
+ }
+ &>div:nth-child(2) {
+ margin-top: 14px;
+
+ }
+ }
+}
+.dashboardWorkspace {
+ @extend .workspace;
+ width: 82%;
+ @include transition(all, 450ms, cubic-bezier(0.23, 1, 0.32, 1));
+
+ &.projectPanelOpen {
+ width: 70%;
+ }
+}
+.notesWorkspace, .imageWorkspace {
+ @extend .workspace;
+ width: 82%;
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/lib/_mixins.scss b/viscoll-app/sass/lib/_mixins.scss
new file mode 100644
index 00000000..470684c1
--- /dev/null
+++ b/viscoll-app/sass/lib/_mixins.scss
@@ -0,0 +1,53 @@
+@mixin border-radius($radius) {
+ -webkit-border-radius: $radius;
+ -moz-border-radius: $radius;
+ -ms-border-radius: $radius;
+ border-radius: $radius;
+}
+
+@mixin box-shadow($shadow...) {
+ -webkit-box-shadow: $shadow;
+ box-shadow: $shadow;
+}
+
+@mixin transition($target, $duration, $transitionType) {
+ -webkit-transition: $target $duration $transitionType;
+ -ms-transition: $target $duration $transitionType;
+ transition: $target $duration $transitionType;
+}
+
+@mixin breakpoint($point) {
+ @media(nth($point, 1): nth($point, 2)) { @content; }
+}
+
+@mixin centerize($x_percent, $y_percent) {
+ position: relative;
+ top: $y_percent;
+ left: $x_percent;
+ transform: translateY(-$y_percent) translateX(-$x_percent);
+}
+
+@mixin user-select($value) {
+-webkit-touch-callout: $value; /* iOS Safari */
+ -webkit-user-select: $value; /* Safari */
+ -khtml-user-select: $value; /* Konqueror HTML */
+ -moz-user-select: $value; /* Firefox */
+ -ms-user-select: $value; /* Internet Explorer/Edge */
+ user-select: $value; /* Non-prefixed version, currently
+ supported by Chrome and Opera */
+}
+
+@mixin keyframes($name) {
+ @-webkit-keyframes #{$name} {
+ @content;
+ }
+ @-moz-keyframes #{$name} {
+ @content;
+ }
+ @-ms-keyframes #{$name} {
+ @content;
+ }
+ @keyframes #{$name} {
+ @content;
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/sass/lib/_variables.scss b/viscoll-app/sass/lib/_variables.scss
new file mode 100644
index 00000000..e45ef30e
--- /dev/null
+++ b/viscoll-app/sass/lib/_variables.scss
@@ -0,0 +1,17 @@
+// Palette
+$fg_blue: #526C91;
+$bg_blue: #3A4B55;
+$bg_blue2: #2B4352;
+$teal: #4ED6CB;
+$white: #FFFFFF;
+$gray: #F2F2F2;
+$dark_gray: #727272;
+$black: #4e4e4e;
+$error: #bd4a4a;
+$success: #34A251;
+$warning: #E37A05;
+
+// Breakpoints
+$medium: 1200px;
+$small: 1024px;
+$xsmall: 768px;
diff --git a/viscoll-app/sass/typography.scss b/viscoll-app/sass/typography.scss
new file mode 100644
index 00000000..f395be97
--- /dev/null
+++ b/viscoll-app/sass/typography.scss
@@ -0,0 +1,21 @@
+h1 {
+ font-size: 1.6em;
+ font-weight: bold;
+ color: $black;
+ @media screen and (max-width: $xsmall) {
+ font-size: 1.3em;
+ }
+}
+h2 {
+ color: $dark_gray;
+ padding-top: 0.8em;
+ font-size: 1.15em;
+ @media screen and (max-width: $xsmall) {
+ font-size: 1em;
+ }
+}
+h3 {
+ @media screen and (max-width: $xsmall) {
+ font-size: 1em;
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/backend/filterActions.js b/viscoll-app/src/actions/backend/filterActions.js
new file mode 100644
index 00000000..b39642a1
--- /dev/null
+++ b/viscoll-app/src/actions/backend/filterActions.js
@@ -0,0 +1,110 @@
+
+export function filterProject(projectID, queries) {
+ /**
+ "queries": [
+ {
+ "type": "Leaf",
+ "attribute": "material",
+ "condition": "equals",
+ "values": ["paper"],
+ "conjunction": "AND"
+ }
+ ]
+ */
+ return {
+ types: ['SHOW_LOADING','FILTER_PROJECT_SUCCESS','FILTER_PROJECT_FAILED'],
+ payload: {
+ request : {
+ url: `/projects/${projectID}/filter`,
+ method: 'put',
+ data: queries,
+ successMessage: "Successfully filtered the project" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+export function reapplyFilterProject(projectID, filters) {
+ const { queries, active } = filters;
+ if (!active)
+ return {type: "NO_FILTER_CHANGE"}
+ let index = 0;
+ let haveErrors = false;
+ for (let query of queries) {
+ if (query.type === null)
+ haveErrors = true
+ if (query.attribute === "")
+ haveErrors = true
+ if (query.values.length === 0)
+ haveErrors = true
+ if (query.condition === "")
+ haveErrors = true
+ if (index !== queries.length-1)
+ if (query.conjunction === "")
+ haveErrors = true
+ index += 1;
+ }
+ if (!haveErrors){
+ return {
+ types: ['NO_LOADING','FILTER_PROJECT_SUCCESS','FILTER_PROJECT_FAILED'],
+ payload: {
+ request : {
+ url: `/projects/${projectID}/filter`,
+ method: 'put',
+ data: {queries},
+ successMessage: "" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+ }
+ return { type: "NO_FILTER_CHANGE" }
+}
+
+
+export function resetFilters(queries) {
+ return {
+ type: 'RESET_FILTERS',
+ payload: queries,
+ };
+}
+
+
+export function toggleFilterDisplay() {
+ return {
+ type: 'TOGGLE_FILTER_DISPLAY'
+ };
+}
+
+
+export function updateFilterQuery(newQueries) {
+ return {
+ type: 'UPDATE_FILTER_QUERY',
+ payload: newQueries
+ };
+}
+
+
+export function updateFilterSelection(selection, matchingFilterObjects, allObjects) {
+ let type = selection.split("_")[0];
+ const select = selection.split("_")[1];
+ let selectedObjects = { type: type? type.slice(0,-1) : type, members: [], lastSelected: "" };
+ if (select==="all"){
+ selectedObjects.members = allObjects[type.slice(0, type.length-1)+"IDs"];
+ } else if (select==="matching") {
+ selectedObjects.members = allObjects[type.slice(0, type.length-1)+"IDs"].filter((id) => {
+ if (type==="Rectos" || type==="Versos")
+ return matchingFilterObjects.Sides.includes(id)
+ else
+ return matchingFilterObjects[type].includes(id)
+ })
+ }
+ return {
+ type: 'UPDATE_FILTER_SELECTION',
+ payload: {
+ selection,
+ selectedObjects
+ }
+ };
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/backend/groupActions.js b/viscoll-app/src/actions/backend/groupActions.js
new file mode 100644
index 00000000..a74d0daf
--- /dev/null
+++ b/viscoll-app/src/actions/backend/groupActions.js
@@ -0,0 +1,79 @@
+
+export function addGroups(group, additional) {
+ return {
+ types: ['CREATE_GROUPS_FRONTEND','CREATE_GROUPS_SUCCESS_BACKEND','CREATE_GROUPS_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/groups`,
+ method: 'post',
+ data: {group, additional},
+ successMessage: "Successfully added the groups" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+export function updateGroup(groupID, group) {
+ return {
+ types: ['UPDATE_GROUP_FRONTEND','UPDATE_GROUP_SUCCESS_BACKEND','UPDATE_GROUP_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/groups/${groupID}`,
+ method: 'put',
+ data: {group},
+ successMessage: "Successfully updated the group" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+export function updateGroups(groups) {
+ return {
+ types: ['UPDATE_GROUPS_FRONTEND','UPDATE_GROUPS_SUCCESS_BACKEND','UPDATE_GROUPS_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/groups`,
+ method: 'put',
+ data: {groups},
+ successMessage: "Successfully updated the groups" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+export function deleteGroup(groupID) {
+ return {
+ types: ['DELETE_GROUP_FRONTEND','DELETE_GROUP_SUCCESS_BACKEND','DELETE_GROUP_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/groups/${groupID}`,
+ method: 'delete',
+ successMessage: "Successfully deleted the group" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+export function deleteGroups(groups, projectID) {
+ return {
+ types: ['DELETE_GROUPS_FRONTEND','DELETE_GROUPS_SUCCESS_BACKEND','DELETE_GROUPS_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/groups`,
+ method: 'delete',
+ data: {...groups, projectID},
+ successMessage: "Successfully deleted the groups" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
diff --git a/viscoll-app/src/actions/backend/imageActions.js b/viscoll-app/src/actions/backend/imageActions.js
new file mode 100644
index 00000000..ba26353c
--- /dev/null
+++ b/viscoll-app/src/actions/backend/imageActions.js
@@ -0,0 +1,98 @@
+
+/**
+ *
+ * @param {list} projectIDs [ 5a26d22f3b0eb7594c9dec23, ... ]
+ * @param {imageIDs} imageIDs [ 5a26d22f3b0eb7594c9dec23, ... ]
+ */
+export function linkImages(projectIDs, imageIDs) {
+ return {
+ types: ['LINK_IMAGES_FRONTEND','LINK_IMAGES_SUCCESS_BACKEND','LINK_IMAGE_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/images/link`,
+ data: {projectIDs, imageIDs},
+ method: 'put',
+ successMessage: projectIDs.length>1 ? "You have successfully linked the projects to this image" : "You have successfully linked the project to this image" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+/**
+ *
+ * @param {list} projectIDs [ 5a26d22f3b0eb7594c9dec23, ... ]
+ * @param {imageIDs} imageIDs [ 5a26d22f3b0eb7594c9dec23, ... ]
+ */
+export function unlinkImages(projectIDs, imageIDs) {
+ return {
+ types: ['UNLINK_IMAGES_FRONTEND','UNLINK_IMAGES_SUCCESS_BACKEND','UNLINK_IMAGES_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/images/unlink`,
+ data: {projectIDs, imageIDs},
+ method: 'put',
+ successMessage: projectIDs.length>1 ? "You have successfully unlinked the projects to this image" : "You have successfully unlinked this project to this image" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+/**
+ *
+ * @param {list} imageIDs
+ */
+export function deleteImages(imageIDs) {
+ return {
+ types: ['DELETE_IMAGES_FRONTEND','DELETE_IMAGES_SUCCESS_BACKEND','DELETE_IMAGES_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/images/`,
+ method: 'delete',
+ data: {imageIDs},
+ successMessage: imageIDs.length>1?"You have successfully deleted the images":"You have successfully deleted the image",
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+/**
+ *
+ * @param {list} images [ { filename:"", contents: data(base64) } , ... ]
+ */
+export function uploadImages(images, projectID) {
+ return {
+ types: ['SHOW_LOADING','UPLOAD_IMAGES_SUCCESS_BACKEND','UPLOAD_IMAGES_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/images`,
+ method: 'post',
+ data: {projectID, images},
+ successMessage: images.length>1? "You have successfully uploaded your images" : "You have successfully uploaded your image" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+
+export function mapSidesToImages(sideMappings) {
+ // sideMappings = [{id: 112, image: {}}, ...]
+ return {
+ types: ['MAP_SIDES_FRONTEND','MAP_SIDES_SUCCESS_BACKEND','MAP_SIDES_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/sides`,
+ method: 'put',
+ data: {sides: sideMappings},
+ successMessage: "Successfully updated the sides" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/backend/interactionActions.js b/viscoll-app/src/actions/backend/interactionActions.js
new file mode 100644
index 00000000..b9410874
--- /dev/null
+++ b/viscoll-app/src/actions/backend/interactionActions.js
@@ -0,0 +1,165 @@
+export function changeViewMode(viewMode) {
+ return {
+ type: "CHANGE_VIEW_MODE",
+ payload: viewMode
+ };
+}
+
+export function changeManagerMode(managerMode) {
+ return {
+ type: "CHANGE_MANAGER_MODE",
+ payload: managerMode
+ };
+}
+
+export function changeNotesTab(newTab) {
+ return {
+ type: "CHANGE_NOTES_TAB",
+ payload: newTab
+ };
+}
+
+export function changeImageTab(newTab) {
+ return {
+ type: "CHANGE_IMAGES_TAB",
+ payload: newTab
+ };
+}
+
+export function toggleFilterPanel(value) {
+ return {
+ type: "TOGGLE_FILTER_PANEL",
+ payload: value
+ };
+}
+
+export function handleObjectPress(selectedObjects, object, event) {
+ selectedObjects = {...selectedObjects, members: [...selectedObjects.members]};
+ selectedObjects.type = object.memberType;
+ selectedObjects.members = [object.id];
+
+ return {
+ type: "TOGGLE_SELECTED_OBJECTS",
+ payload: selectedObjects
+ };
+
+}
+
+export function handleObjectClick(selectedObjects, object, event, objects) {
+ selectedObjects = {...selectedObjects, members: [...selectedObjects.members]};
+ if (event.ctrlKey || event.metaKey || (event.modifiers!==undefined && event.modifiers.command)) {
+ // Toggle this object without clearing active objects unless type is different
+ if (selectedObjects.type !== object.memberType) {
+ selectedObjects.members = [];
+ selectedObjects.type = object.memberType;
+ }
+ const index = selectedObjects.members.indexOf(object.id);
+ if (index !== -1) {
+ selectedObjects.members.splice(index, 1);
+ }
+ else {
+ selectedObjects.members.push(object.id)
+ }
+ }
+ if (event.button === 0 || event.modifiers!==undefined) {
+ let notCtrl=event.ctrlKey !== undefined && !event.ctrlKey && !event.shiftKey;
+ let notCmd=event.metaKey !== undefined && !event.metaKey && !event.shiftKey;
+ let notCanvasCmd=event.modifiers !== undefined && !event.modifiers.command && !event.modifiers.shift;
+ if ((notCtrl&¬Cmd)||notCanvasCmd) {
+ // Clear all and toggle only this object
+ if (selectedObjects.members.includes(object.id)) {
+ selectedObjects.members = [];
+ }
+ else {
+ selectedObjects.members = [object.id];
+ }
+ }
+ if (event.shiftKey || (event.modifiers!==undefined && event.modifiers.shift)) {
+ window.getSelection().removeAllRanges();
+ // Object type changed, clear all active selected objects
+ if (selectedObjects.type !== object.memberType) {
+ selectedObjects.members = [object.id];
+ } else {
+ // Select all similar type objects within this object and last selected object
+ const orderOfCurrentElement = objects[object.memberType+"s"].indexOf(object.id)
+ const orderOfLastElement = objects[object.memberType+"s"].indexOf(selectedObjects.lastSelected)
+ let indexes = [orderOfLastElement, orderOfCurrentElement];
+ indexes.sort((a, b) => {return a-b});
+ const currentSelected = [...selectedObjects.members];
+ selectedObjects.members = objects[object.memberType+"s"].slice(indexes[0], indexes[1]+1);
+ for (let id of currentSelected){
+ if (!selectedObjects.members.includes(id))
+ selectedObjects.members.push(id);
+ }
+ }
+ }
+ }
+ // Sort the selected members by ascending order
+ selectedObjects.members.sort((a, b)=>objects[object.memberType+"s"].indexOf(a) > objects[object.memberType+"s"].indexOf(b) ? 1 : -1);
+
+ if (selectedObjects.members.length === 0) {
+ selectedObjects.type = "";
+ } else {
+ selectedObjects.type = object.memberType;
+ selectedObjects.lastSelected = object.id;
+ }
+
+ return {
+ type: "TOGGLE_SELECTED_OBJECTS",
+ payload: selectedObjects
+ };
+}
+
+/**
+ * Switch selectedObjects between types: Leaf, Recto, Verso
+ * @param {object} selectedObjects currently selected objects
+ * @param {string} newType new type to switch to (leaf, recto or verso)
+ * @param {object} Leafs all Leaf, Recto & Verso objects of this project
+ */
+export function changeInfoBoxTab(newType, selectedObjects, objects) {
+ let newSelectedObjects = {type: newType, members: [], lastSelected: ""};
+ if (selectedObjects.type==="newType")
+ return { type: "NO_TAB_CHANGE" }
+
+ const object = objects[selectedObjects.type+"s"];
+
+ switch(newType) {
+ case "Leaf":
+ for (let id of selectedObjects.members){
+ newSelectedObjects.members.push(object[id].parentID)
+ }
+ break;
+ case "Recto":
+ for (let id of selectedObjects.members){
+ if (selectedObjects.type==="Leaf")
+ newSelectedObjects.members.push(object[id].rectoID)
+ else
+ newSelectedObjects.members.push(objects.Leafs[object[id].parentID].rectoID)
+ }
+ break;
+ case "Verso":
+ for (let id of selectedObjects.members){
+ if (selectedObjects.type==="Leaf")
+ newSelectedObjects.members.push(object[id].versoID)
+ else
+ newSelectedObjects.members.push(objects.Leafs[object[id].parentID].versoID)
+ }
+ break;
+ default:
+ break;
+ }
+
+ newSelectedObjects.lastSelected = newSelectedObjects.members[newSelectedObjects.members.length-1]
+
+ return {
+ type: "TOGGLE_SELECTED_OBJECTS",
+ payload: newSelectedObjects,
+ };
+}
+
+export function toggleVisualizationDrawing(request) {
+ return {
+ type: 'TOGGLE_VISUALIZATION_DRAWING',
+ payload: request
+ };
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/backend/leafActions.js b/viscoll-app/src/actions/backend/leafActions.js
new file mode 100644
index 00000000..11c043e8
--- /dev/null
+++ b/viscoll-app/src/actions/backend/leafActions.js
@@ -0,0 +1,141 @@
+
+export function addLeafs(leaf, additional) {
+ let successMessage = "Successfully added the leaf";
+ if (additional.noOfLeafs>1) successMessage = "Successfully added the leaves";
+
+ return {
+ types: ['CREATE_LEAVES_FRONTEND','CREATE_LEAVES_SUCCESS_BACKEND','CREATE_LEAVES_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/leafs`,
+ method: 'post',
+ data: { leaf, additional },
+ successMessage,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+export function updateLeaf(leafID, leaf) {
+ return {
+ types: ['UPDATE_LEAF_FRONTEND','UPDATE_LEAF_SUCCESS_BACKEND','UPDATE_LEAF_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/leafs/${leafID}`,
+ method: 'put',
+ data: {leaf},
+ successMessage: "Successfully updated the leaf" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+export function updateLeafs(leafs, project_id) {
+ return {
+ types: ['UPDATE_LEAVES_FRONTEND','UPDATE_LEAVES_SUCCESS_BACKEND','UPDATE_LEAVES_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/leafs`,
+ method: 'put',
+ data: {leafs, project_id},
+ successMessage: "Successfully updated the leaves" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+
+/**
+ leafs: [
+ "59ea025c3b0eb7168e9591de",
+ "59ea025c3b0eb7168e9591de",
+ "59ea025c3b0eb7168e9591de",
+ "59ea025c3b0eb7168e9591de"
+ ]
+ */
+export function autoConjoinLeafs(leafs) {
+ return {
+ types: ['AUTOCONJOIN_LEAFS_FRONTEND','AUTOCONJOIN_LEAFS_SUCCESS_BACKEND','AUTOCONJOIN_LEAFS_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/leafs/conjoin`,
+ method: 'put',
+ data: {leafs},
+ successMessage: "Successfully conjoined the leaves" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+
+
+export function deleteLeaf(leafID) {
+ return {
+ types: ['DELETE_LEAF_FRONTEND','DELETE_LEAF_SUCCESS_BACKEND','DELETE_LEAF_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/leafs/${leafID}`,
+ method: 'delete',
+ successMessage: "Successfully deleted the leaf" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+export function deleteLeafs(leafs) {
+ return {
+ types: ['DELETE_LEAVES_FRONTEND','DELETE_LEAFS_SUCCESS_BACKEND','DELETE_LEAFS_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/leafs`,
+ method: 'delete',
+ data: leafs,
+ successMessage: "Successfully deleted the leaves" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+export function generateFolioNumbers(startNumber, rectoIDs, versoIDs) {
+ return {
+ types: ['GENERATE_FOLIO_NUMBERS_FRONTEND','GENERATE_FOLIO_NUMBERS_SUCCESS_BACKEND','GENERATE_FOLIO_NUMBERS_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/sides/generateFolio`,
+ method: 'put',
+ data: {startNumber, rectoIDs, versoIDs},
+ successMessage: "Successfully generated the folio numbers" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+export function generatePageNumbers(startNumber, rectoIDs, versoIDs) {
+ return {
+ types: ['GENERATE_PAGE_NUMBERS_FRONTEND','GENERATE_PAGE_NUMBERS_SUCCESS_BACKEND','GENERATE_PAGE_NUMBERS_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/sides/generatePageNumber`,
+ method: 'put',
+ data: {startNumber, rectoIDs, versoIDs},
+ successMessage: "Successfully generated the page numbers" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/backend/manifestActions.js b/viscoll-app/src/actions/backend/manifestActions.js
new file mode 100644
index 00000000..c81e0e02
--- /dev/null
+++ b/viscoll-app/src/actions/backend/manifestActions.js
@@ -0,0 +1,70 @@
+
+
+
+export function createManifest(projectID, manifest) {
+ return {
+ types: ['SHOW_LOADING','CREATE_MANIFEST_SUCCESS','CREATE_MANIFEST_FAILED'],
+ payload: {
+ request : {
+ url: `/projects/${projectID}/manifests`,
+ method: 'post',
+ data: manifest,
+ successMessage: "You have successfully created the manifest" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+
+export function updateManifest(projectID, manifest) {
+ return {
+ types: ['UPDATE_MANIFEST_FRONTEND','UPDATE_MANIFEST_SUCCESS_BACKEND','UPDATE_MANIFEST_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/projects/${projectID}/manifests`,
+ method: 'put',
+ data: manifest,
+ successMessage: "You have successfully updated the manifest" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+
+export function deleteManifest(projectID, manifest) {
+ return {
+ types: ['DELETE_MANIFEST_FRONTEND','DELETE_MANIFEST_SUCCESS_BACKEND','DELETE_MANIFEST_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/projects/${projectID}/manifests`,
+ method: 'delete',
+ data: manifest,
+ successMessage: "You have successfully deleted the manifest" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+export function cancelCreateManifest(){
+ return {type: "CANCEL_CREATE_MANIFEST"}
+}
+
+
+export function cloneProjectImagesMapping(projectID) {
+ return {
+ types: ['NO_LOADING','CLONE_PROJECT_MAPPING_SUCCESS','CLONE_PROJECT_MAPPING_FAILED'],
+ payload: {
+ request : {
+ url: `/projects/${projectID}/cloneImageMapping`,
+ method: 'get',
+ successMessage: "" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/backend/noteActions.js b/viscoll-app/src/actions/backend/noteActions.js
new file mode 100644
index 00000000..d84232ef
--- /dev/null
+++ b/viscoll-app/src/actions/backend/noteActions.js
@@ -0,0 +1,183 @@
+
+export function addNote(note) {
+ /**
+ note: {
+ "project_id": "5951303fc9bf3c7b9a573a3f",
+ "id": "595130sadsadsa9bf3c7b9a573a3f"
+ "title": "some title for note",
+ "type": "Ink",
+ "description": "blue ink",
+ "show": "true"
+ }
+ */
+ return {
+ types: ['CREATE_NOTE_FRONTEND','CREATE_NOTE_SUCCESS_BACKEND','CREATE_NOTE_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/notes`,
+ method: 'post',
+ data: {note},
+ successMessage: "Successfully created the note" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+export function updateNote(noteID, note) {
+ /**
+ note: {
+ "title": "some title for note",
+ "type": "Ink",
+ "description": "blue ink"
+ }
+ */
+ return {
+ types: ['UPDATE_NOTE_FRONTEND','UPDATE_NOTE_SUCCESS_BACKEND','UPDATE_NOTE_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/notes/${noteID}`,
+ method: 'put',
+ data: {note},
+ successMessage: "Successfully updated the note" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+
+
+export function deleteNote(noteID) {
+ return {
+ types: ['DELETE_NOTE_FRONTEND','DELETE_NOTE_SUCCESS_BACKEND','DELETE_NOTE_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/notes/${noteID}`,
+ method: 'delete',
+ successMessage: "Successfully deleted the note" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+
+export function linkNote(noteID, objects) {
+ /**
+ objects: [
+ {
+ "id": "5951303fc9bf3c7b9a573a3f",
+ "type": "Group"
+ },
+ ]
+ */
+ return {
+ types: ['LINK_NOTE_FRONTEND','LINK_NOTE_SUCCESS_BACKEND','LINK_NOTE_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/notes/${noteID}/link`,
+ method: 'put',
+ data: {objects},
+ successMessage: "Successfully linked the note" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+export function unlinkNote(noteID, objects) {
+ /**
+ objects: [
+ {
+ "id": "5951303fc9bf3c7b9a573a3f",
+ "type": "Group"
+ },
+ ]
+ */
+ return {
+ types: ['UNLINK_NOTE_FRONTEND','UNLINK_NOTE_SUCCESS_BACKEND','UNLINK_NOTE_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/notes/${noteID}/unlink`,
+ method: 'put',
+ data: {objects},
+ successMessage: "Successfully unlinked the note" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+
+export function createNoteType(noteType) {
+ /**
+ "noteType": {
+ "project_id": "5951303fc9bf3c7b9a573a3f",
+ "type": "Ink"
+ }
+ */
+ return {
+ types: ['CREATE_NOTETYPE_FRONTEND','CREATE_NOTETYPE_SUCCESS_BACKEND','CREATE_NOTETYPE_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/notes/type`,
+ method: 'post',
+ data: {noteType},
+ successMessage: "Successfully created the note type" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+
+export function updateNoteType(noteType) {
+ /**
+ "noteType": {
+ "project_id": "5951303fc9bf3c7b9a573a3f",
+ "type": "Ink",
+ "old_type": "Inkss"
+ }
+ */
+ return {
+ types: ['UPDATE_NOTETYPE_FRONTEND','UPDATE_NOTETYPE_SUCCESS_BACKEND','UPDATE_NOTETYPE_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/notes/type`,
+ method: 'put',
+ data: {noteType},
+ successMessage: "Successfully updated the note type" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+
+export function deleteNoteType(noteType) {
+ /**
+ "noteType": {
+ "project_id": "5951303fc9bf3c7b9a573a3f",
+ "type": "Ink"
+ }
+ */
+ return {
+ types: ['DELETE_NOTETYPE_FRONTEND','DELETE_NOTETYPE_SUCCESS_BACKEND','DELETE_NOTETYPE_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/notes/type`,
+ method: 'delete',
+ data: {noteType},
+ successMessage: "Successfully deleted the note type" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/backend/projectActions.js b/viscoll-app/src/actions/backend/projectActions.js
new file mode 100644
index 00000000..031b4d84
--- /dev/null
+++ b/viscoll-app/src/actions/backend/projectActions.js
@@ -0,0 +1,168 @@
+export function loadProject(projectID, showLoading='SHOW_LOADING') {
+ return {
+ types: [showLoading,'LOAD_PROJECT_SUCCESS','LOAD_PROJECT_FAILED'],
+ payload: {
+ request : {
+ url: `/projects/${projectID}`,
+ method: 'get',
+ successMessage: "" ,
+ errorMessage: "Ooops! Something went wrong",
+ },
+ }
+ };
+}
+
+export function loadProjectViewOnly(projectID, showLoading = 'SHOW_LOADING') {
+ return {
+ types: [showLoading, 'LOAD_PROJECT_VIEW_ONLY_SUCCESS', 'LOAD_PROJECT_VIEW_ONLY_FAILED'],
+ payload: {
+ request: {
+ url: `/projects/${projectID}/viewOnly`,
+ method: 'get',
+ successMessage: "",
+ errorMessage: "Ooops! Something went wrong",
+ },
+ }
+ };
+}
+
+
+export function createProject(newProject) {
+ return {
+ types: ['SHOW_LOADING','CREATE_PROJECT_SUCCESS','CREATE_PROJECT_FAILED'],
+ payload: {
+ request : {
+ url: `/projects`,
+ method: 'post',
+ data: newProject,
+ successMessage: "Successfully created the project" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+export function updateProject(projectID, project) {
+ return {
+ types: ['UPDATE_PROJECT_FRONTEND','UPDATE_PROJECT_SUCCESS_BACKEND','UPDATE_PROJECT_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/projects/${projectID}`,
+ method: 'put',
+ data: {project},
+ successMessage: "Successfully updated the project",
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+export function updatePreferences(projectID, project) {
+ return {
+ types: ['UPDATE_PREFERENCES_FRONTEND','','UPDATE_PROJECT_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/projects/${projectID}`,
+ method: 'put',
+ data: {project},
+ successMessage: "Successfully updated the project",
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+export function deleteProject(projectID, deleteUnlinkedImages) {
+ return {
+ types: ['DELETE_PROJECT_FRONTEND','DELETE_PROJECT_SUCCESS_BACKEND','DELETE_PROJECT_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/projects/${projectID}`,
+ method: 'delete',
+ data: {deleteUnlinkedImages},
+ successMessage: "Successfully deleted the project" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+
+export function loadProjects() {
+ return {
+ types: ['NO_LOADING','LOAD_PROJECTS_SUCCESS','LOAD_PROJECTS_FAILED'],
+ payload: {
+ request : {
+ url: `/projects`,
+ method: 'get',
+ successMessage: "" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+
+/**
+ *
+ * @param {object} data {importData:"", importFormat:"", imageData:""}
+ */
+export function importProject(data) {
+ return {
+ types: ['SHOW_LOADING','IMPORT_PROJECT_SUCCESS','IMPORT_PROJECT_FAILED'],
+ payload: {
+ request : {
+ url: `/projects/import`,
+ method: 'put',
+ data: data,
+ successMessage: "Successfully imported the project",
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+
+export function cloneProject(projectID) {
+ return {
+ types: ['SHOW_LOADING','CLONE_PROJECT_SUCCESS','CLONE_PROJECT_FAILED'],
+ payload: {
+ request : {
+ url: `/projects/${projectID}/clone`,
+ method: 'get',
+ successMessage: "Successfully cloned the project" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+
+export function exportProject(projectID, format) {
+ return {
+ types: ['SHOW_LOADING','EXPORT_SUCCESS','EXPORT_FAILED'],
+ payload: {
+ request : {
+ url: `/projects/${projectID}/export/${format}`,
+ method: 'get',
+ successMessage: "" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+
+export function exportProjectBeforeFeedback(projectID, format) {
+ return {
+ types: ['NO_LOADING','EXPORT_SUCCESS','EXPORT_FAILED'],
+ payload: {
+ request : {
+ url: `/projects/${projectID}/export/${format}`,
+ method: 'get',
+ successMessage: "You have successfully sent a feedback!" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/backend/sideActions.js b/viscoll-app/src/actions/backend/sideActions.js
new file mode 100644
index 00000000..134015b7
--- /dev/null
+++ b/viscoll-app/src/actions/backend/sideActions.js
@@ -0,0 +1,31 @@
+export function updateSide(sideID, side) {
+ return {
+ types: ['UPDATE_SIDE_FRONTEND','UPDATE_SIDE_SUCCESS_BACKEND','UPDATE_SIDE_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/sides/${sideID}`,
+ method: 'put',
+ data: {side},
+ successMessage: "Successfully updated the side" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
+
+export function updateSides(sides) {
+ return {
+ types: ['UPDATE_SIDES_FRONTEND','UPDATE_SIDES_SUCCESS_BACKEND','UPDATE_SIDES_FAILED_BACKEND'],
+ payload: {
+ request : {
+ url: `/sides`,
+ method: 'put',
+ data: {sides},
+ successMessage: "Successfully updated the sides" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ },
+ isUndoable: true,
+ };
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/backend/userActions.js b/viscoll-app/src/actions/backend/userActions.js
new file mode 100644
index 00000000..10edcd03
--- /dev/null
+++ b/viscoll-app/src/actions/backend/userActions.js
@@ -0,0 +1,153 @@
+
+export function login(session) {
+ return {
+ types: ['NO_LOADING','LOGIN_SUCCESS','LOGIN_FAILED'],
+ payload: {
+ request : {
+ url: `/session`,
+ method: 'post',
+ data: { session },
+ successMessage: "You have successfully logged in" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+export function register(user) {
+ return {
+ types: ['NO_LOADING','REGISTER_SUCCESS','REGISTER_FAILED'],
+ payload: {
+ request : {
+ url: `/registration`,
+ method: 'post',
+ data: {user},
+ successMessage: "You have successfully registered" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+export function confirm(confirmation_token) {
+ return {
+ types: ['NO_LOADING','CONFIRM_SUCCESS','CONFIRM_FAILED'],
+ payload: {
+ request : {
+ url: `/confirmation`,
+ method: 'put',
+ data: { confirmation_token },
+ successMessage: "You have successfully confirmed your account" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+export function resendConfirmation(email) {
+ return {
+ type: 'RESEND_CONFIRMATION',
+ payload: {
+ request : {
+ url: `/confirmation`,
+ method: 'post',
+ data: {
+ confirmation: {
+ email: email
+ }
+ },
+ successMessage: "You have successfully resent the confirmation email" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+}
+
+export function logout() {
+ return {
+ types: ['NO_LOADING','LOGOUT_SUCCESS','LOGOUT_FAILED'],
+ payload: {
+ request : {
+ url: `/session`,
+ method: 'delete',
+ successMessage: "You have successfully logged out" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+export function resetPasswordRequest(email) {
+ return {
+ types: ['NO_LOADING','REQUEST_RESET_SUCCESS','REQUEST_RESET_FAILED'],
+ payload: {
+ request : {
+ url: `/password`,
+ method: 'post',
+ data: {password: { email }},
+ successMessage: "You have successfully requested to reset password" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+export function resetPassword(reset_password_token, password) {
+ return {
+ types: ['NO_LOADING','RESET_SUCCESS','RESET_FAILED'],
+ payload: {
+ request : {
+ url: `/password`,
+ method: 'put',
+ data: {reset_password_token, password},
+ successMessage: "You have successfully reset your password" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+export function updateProfile(user, userID) {
+ return {
+ types: ['SHOW_LOADING','UPDATE_PROFILE_SUCCESS','UPDATE_PROFILE_FAILED'],
+ payload: {
+ request : {
+ url: `/users/${userID}`,
+ method: 'put',
+ data: user,
+ successMessage: "You have successfully updated your account" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+export function deleteProfile(userID) {
+ return {
+ types: ['SHOW_LOADING','DELETE_PROFILE_SUCCESS','DELETE_PROFILE_FAILED'],
+ payload: {
+ request : {
+ url: `/users/${userID}`,
+ method: 'delete',
+ successMessage: "You have successfully deleted your account" ,
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ };
+}
+
+export function sendFeedback(title, message, browserInformation, project) {
+ return {
+ types: ['NO_LOADING', 'SEND_FEEDBACK_SUCCESS', 'SEND_FEEDBACK_FAILED'],
+ payload: {
+ request: {
+ url: `/feedback`,
+ method: 'post',
+ data: {title, message, browserInformation, project},
+ successMessage: "You have successfully sent a feedback!",
+ errorMessage: "Ooops! Something went wrong"
+ }
+ }
+ }
+}
+
diff --git a/viscoll-app/src/actions/frontend/after/imageActions.js b/viscoll-app/src/actions/frontend/after/imageActions.js
new file mode 100644
index 00000000..d460e568
--- /dev/null
+++ b/viscoll-app/src/actions/frontend/after/imageActions.js
@@ -0,0 +1,14 @@
+export function updateImagesAfterUpload(action, dashboard, active) {
+ const newDIYImages = action.payload.images
+ dashboard.images = [...dashboard.images, ...newDIYImages]
+ if (active.project.id !== ""){
+ // Update the active project's DIYManifest images list
+ active.project.manifests.DIYImages.images = [
+ ...active.project.manifests.DIYImages.images,
+ ...newDIYImages.map(image => {
+ return { label: image.label, url: image.url, manifestID: "DIYImages" }
+ })
+ ]
+ }
+ return {dashboard, active}
+}
diff --git a/viscoll-app/src/actions/frontend/before/groupActions.js b/viscoll-app/src/actions/frontend/before/groupActions.js
new file mode 100644
index 00000000..4b7690e0
--- /dev/null
+++ b/viscoll-app/src/actions/frontend/before/groupActions.js
@@ -0,0 +1,126 @@
+import { createLeaves, deleteLeaf } from './leafActions';
+import { getLeafMembers } from './helperActions';
+
+export function createGroups(action, state) {
+ const parentGroupID = action.payload.request.data.additional.parentGroupID
+ const parentGroup = parentGroupID ? state.project.Groups[parentGroupID] : null
+ const noOfGroups = action.payload.request.data.additional.noOfGroups
+ const noOfLeaves = action.payload.request.data.additional.noOfLeafs
+ const memberOrder = action.payload.request.data.additional.memberOrder
+ const globalOrder = action.payload.request.data.additional.order
+ const autoConjoin = action.payload.request.data.additional.conjoin
+ const oddMemberLeftOut = action.payload.request.data.additional.oddMemberLeftOut
+ const groupIDs = action.payload.request.data.additional.groupIDs
+ const leafIDs = action.payload.request.data.additional.leafIDs
+ const sideIDs = action.payload.request.data.additional.sideIDs
+ const title = action.payload.request.data.group.title
+ const type = action.payload.request.data.group.type
+ const direction = action.payload.request.data.group.direction
+ const tacketed = action.payload.request.data.group.tacketed
+ const sewing = action.payload.request.data.group.sewing
+ let newlyAddedGroupIDs = []
+ for (let count = 0; count < noOfGroups; count++) {
+ // Create new Group with give groupID
+ state.project.Groups["Group_" + groupIDs[count]] = {
+ id: "Group_" + groupIDs[count],
+ title: title? title : "None",
+ type: type? type : "Quire",
+ direction: direction,
+ tacketed: tacketed? tacketed : [],
+ sewing: sewing? sewing : [],
+ nestLevel: parentGroup ? parentGroup.nestLevel+1 : 1,
+ parentID: parentGroup ? parentGroup.id : null,
+ notes: [],
+ memberType: "Group",
+ memberIDs: leafIDs.slice(count*noOfLeaves, count*noOfLeaves+noOfLeaves).map(leafID => "Leaf_"+leafID)
+ }
+ newlyAddedGroupIDs.push("Group_" + groupIDs[count])
+ }
+ // Add newlyAddedGroupIDs to the parentGroup's memberIDs list if parentGroup exist
+ if (parentGroup) state.project.Groups[parentGroupID].memberIDs.splice(memberOrder-1, 0, ...newlyAddedGroupIDs)
+ // Add newlyAddedGroupIDs to the active project's groupIDs list
+ state.project.groupIDs.splice(globalOrder-1, 0, ...newlyAddedGroupIDs)
+ // Populate leafIDs recursively and replace the active project's leafIDs list
+ let updatedLeafIDs = [];
+ for (let groupID of state.project.groupIDs) {
+ const group = state.project.Groups[groupID];
+ if (group.nestLevel===1) getLeafMembers(group.memberIDs, state, updatedLeafIDs)
+ }
+ state.project.leafIDs = updatedLeafIDs;
+ // Create new leaves for each new Group
+ let groupCount = 0
+ for (let groupID of newlyAddedGroupIDs){
+ const action = {payload: {request: {data: {
+ leaf: { parentID: groupID },
+ additional: {
+ noOfLeafs: noOfLeaves,
+ conjoin: autoConjoin,
+ oddMemberLeftOut: oddMemberLeftOut,
+ leafIDs: leafIDs.slice(groupCount*noOfLeaves, groupCount*noOfLeaves+noOfLeaves),
+ sideIDs: sideIDs.slice(groupCount*2*noOfLeaves, groupCount*2*noOfLeaves+2*noOfLeaves)
+ }
+ }}}}
+ state = createLeaves(action, state, true)
+ groupCount += 1
+ }
+ // Generate the list of new Groups and Leaves to flash
+ state.collationManager.flashItems.leaves = leafIDs.map((leafID)=>"Leaf_"+leafID);
+ state.collationManager.flashItems.groups = [...newlyAddedGroupIDs]
+ return state
+}
+
+export function updateGroup(action, state) {
+ const updatedGroupID = action.payload.request.url.split("/").pop();
+ const updatedGroup = action.payload.request.data.group
+ // Update the group with id updatedGroupID
+ state.project.Groups[updatedGroupID] = { ...state.project.Groups[updatedGroupID], ...updatedGroup }
+ return state
+}
+
+export function updateGroups(action, state) {
+ const updatedGroups = action.payload.request.data.groups
+ for (let updatedGroup of updatedGroups) {
+ // Update the group of id updatedGroup.id with attributes updatedGroup.attributes
+ state.project.Groups[updatedGroup.id] = { ...state.project.Groups[updatedGroup.id], ...updatedGroup.attributes }
+ }
+ return state
+}
+
+export function deleteGroup(deletedGroupID, state) {
+ const deletedGroup = state.project.Groups[deletedGroupID]
+ // Remove deletedGroupID from groupIDs list
+ let deletedGroupIDIndex = state.project.groupIDs.indexOf(deletedGroupID)
+ state.project.groupIDs.splice(deletedGroupIDIndex, 1)
+ // Unlink all Notes of deletedGroupID
+ for (let noteID in state.project.Notes) {
+ deletedGroupIDIndex = state.project.Notes[noteID].objects.Group.indexOf(deletedGroupID)
+ if (deletedGroupIDIndex !== -1)
+ state.project.Notes[noteID].objects.Group.splice(deletedGroupIDIndex, 1)
+ }
+ // Remove deletedGroupID from deletedGroupParent's memberIDs list if exists
+ if (deletedGroup.parentID ) {
+ const deletedGroupParent = state.project.Groups[deletedGroup.parentID]
+ deletedGroupIDIndex = deletedGroupParent.memberIDs.indexOf(deletedGroupID)
+ state.project.Groups[deletedGroup.parentID].memberIDs.splice(deletedGroupIDIndex, 1)
+ }
+ // Remove all Group members of deletedGroup
+ for (let memberID of [...deletedGroup.memberIDs]){
+ if (memberID.charAt(0)==="G")
+ deleteGroup(memberID, state)
+ else
+ deleteLeaf(memberID, state)
+ }
+ // Reset selectedObjects to empty list
+ state.collationManager.selectedObjects = { type: "", members: [], lastSelected: "" }
+ // Remove the deletedGroupID from Groups
+ delete state.project.Groups[deletedGroupID]
+ return state
+}
+
+export function deleteGroups(deletedGroupIDs, state) {
+ for (let deletedGroupID of deletedGroupIDs) {
+ if (state.project.Groups.hasOwnProperty(deletedGroupID))
+ deleteGroup(deletedGroupID, state)
+ }
+ return state
+}
diff --git a/viscoll-app/src/actions/frontend/before/helperActions.js b/viscoll-app/src/actions/frontend/before/helperActions.js
new file mode 100644
index 00000000..73512b56
--- /dev/null
+++ b/viscoll-app/src/actions/frontend/before/helperActions.js
@@ -0,0 +1,9 @@
+export function getLeafMembers(memberIDs, state, leafIDs=[]) {
+ for (let memberID of memberIDs){
+ if (memberID.charAt(0)==="G"){
+ getLeafMembers(state.project.Groups[memberID].memberIDs, state, leafIDs)
+ } else {
+ leafIDs.push(memberID)
+ }
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/frontend/before/imageActions.js b/viscoll-app/src/actions/frontend/before/imageActions.js
new file mode 100644
index 00000000..886f8b88
--- /dev/null
+++ b/viscoll-app/src/actions/frontend/before/imageActions.js
@@ -0,0 +1,93 @@
+export function linkImages(action, dashboard, active) {
+ if (active.project.id!=="")
+ linkImagesFromProject(action, dashboard, active)
+ linkImagesFromDashboard(action, dashboard)
+ return {dashboard, active}
+}
+
+export function unlinkImages(action, dashboard, active) {
+ if (active.project.id !== "")
+ unlinkImagesFromProject(action, dashboard, active)
+ unlinkImagesFromDashboard(action, dashboard)
+ return { dashboard, active }
+}
+
+export function deleteImages(action, dashboard, active) {
+ if (active.project.id !== "")
+ unlinkImagesFromProject(action, dashboard, active)
+ deleteImagesFromDashboard(action, dashboard)
+ return { dashboard, active }
+}
+
+export function linkImagesFromProject(action, dashboard, active) {
+ const imageIDs = action.payload.request.data.imageIDs
+ for (let imageID of imageIDs) {
+ // Add image of imageID to the list of DIYImages in active project
+ const imageIndex = dashboard.images.findIndex(image => image.id === imageID)
+ const image = dashboard.images[imageIndex]
+ active.project.manifests.DIYImages.images.push({
+ label: image.label,
+ url: image.url,
+ manifestID: "DIYImages"
+ })
+ }
+}
+
+function findByLabel (DIYImage) {
+ return DIYImage.label === this.label;
+}
+
+export function unlinkImagesFromProject(action, dashboard, active) {
+ const imageIDs = action.payload.request.data.imageIDs;
+ for (let imageID of imageIDs) {
+ // Remove image of imageID from the list of DIYImages in active project
+ let imageIndex = dashboard.images.findIndex(image => image.id === imageID)
+ const image = dashboard.images[imageIndex]
+ imageIndex = active.project.manifests.DIYImages.images.findIndex(findByLabel, {label:image.label})
+ active.project.manifests.DIYImages.images.splice(imageIndex, 1)
+ // Unlink all sides of this project if it was mapped to this image
+ for (let sideID of image.sideIDs) {
+ if (active.project.Rectos.hasOwnProperty(sideID))
+ active.project.Rectos[sideID].image = {}
+ if (active.project.Versos.hasOwnProperty(sideID))
+ active.project.Versos[sideID].image = {}
+ }
+ }
+}
+
+export function linkImagesFromDashboard(action, dashboard) {
+ const projectIDs = action.payload.request.data.projectIDs
+ const imageIDs = action.payload.request.data.imageIDs
+ for (let projectID of projectIDs){
+ // Add projectID to the list of projectIDs for each image of imageIDs
+ for (let imageID of imageIDs) {
+ let imageIndex = dashboard.images.findIndex(image => image.id === imageID)
+ let projectIDIndex = dashboard.images[imageIndex].projectIDs.indexOf(projectID)
+ if (projectIDIndex === -1)
+ dashboard.images[imageIndex].projectIDs.push(projectID)
+ }
+ }
+}
+
+export function unlinkImagesFromDashboard(action, dashboard) {
+ const projectIDs = action.payload.request.data.projectIDs
+ const imageIDs = action.payload.request.data.imageIDs
+ for (let projectID of projectIDs) {
+ // Remove projectID from the list of projectIDs for each image of imageIDs
+ for (let imageID of imageIDs) {
+ let imageIndex = dashboard.images.findIndex(image => image.id === imageID)
+ let projectIDIndex = dashboard.images[imageIndex].projectIDs.indexOf(projectID)
+ if (projectIDIndex !== -1)
+ dashboard.images[imageIndex].projectIDs.splice(projectIDIndex, 1)
+ }
+ }
+}
+
+export function deleteImagesFromDashboard(action, dashboard) {
+ const imageIDs = action.payload.request.data.imageIDs
+ // Remove imageID from dashboard.images for each image of imageIDs
+ for (let imageID of imageIDs) {
+ let imageIndex = dashboard.images.findIndex(image => image.id === imageID)
+ dashboard.images.splice(imageIndex, 1)
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/frontend/before/leafActions.js b/viscoll-app/src/actions/frontend/before/leafActions.js
new file mode 100644
index 00000000..a1c0eca3
--- /dev/null
+++ b/viscoll-app/src/actions/frontend/before/leafActions.js
@@ -0,0 +1,264 @@
+import { getLeafMembers } from './helperActions';
+
+export function autoConjoinLeafs(action, state, leaves, oddMemberLeftOut=false) {
+ // Remove the existing conjoined_to of each leaf if it is already conjoined_to another leaf
+ for (let leafID of leaves){
+ const leafConjoinedToID = state.project.Leafs[leafID].conjoined_to
+ if (leafConjoinedToID) {
+ state.project.Leafs[leafConjoinedToID].conjoined_to = null
+ }
+ }
+ // Remove the oddLeafID from leaves list
+ if (leaves.length%2===1){
+ let oddLeafID;
+ if (oddMemberLeftOut)
+ oddLeafID = leaves[oddMemberLeftOut-1]
+ else
+ oddLeafID = leaves[Math.floor(leaves.length/2)]
+ const oddLeafIDIndex = leaves.indexOf(oddLeafID)
+ leaves.splice(oddLeafIDIndex, 1)
+ state.project.Leafs[oddLeafID].conjoined_to = null
+ }
+ for (let i = 0; i < leaves.length; i++) {
+ if (leaves.length / 2 === i)
+ break
+ else {
+ const leafOneID = leaves[i]
+ const leafTwoID = leaves[leaves.length - i - 1]
+ state.project.Leafs[leafOneID].conjoined_to = leafTwoID
+ state.project.Leafs[leafTwoID].conjoined_to = leafOneID
+ }
+ }
+ return state
+}
+
+export function createLeaves(action, state, fromGroupCreation=false) {
+ const parentGroupID = action.payload.request.data.leaf.parentID
+ const parentGroup = state.project.Groups[parentGroupID]
+ const noOfLeaves = action.payload.request.data.additional.noOfLeafs
+ const memberOrder = action.payload.request.data.additional.memberOrder
+ const globalOrder = action.payload.request.data.additional.order
+ const autoConjoin = action.payload.request.data.additional.conjoin
+ const oddMemberLeftOut = action.payload.request.data.additional.oddMemberLeftOut
+ const leafIDs = action.payload.request.data.additional.leafIDs
+ const sideIDs = action.payload.request.data.additional.sideIDs
+ let newlyAddedLeafIDs = []
+ let sideCount = 0
+ for (let count = 0; count < noOfLeaves; count++) {
+ // Create new Leaf with give leafID
+ state.project.Leafs["Leaf_" + leafIDs[count]] = {
+ id: "Leaf_" + leafIDs[count],
+ material: action.payload.request.data.leaf.material? action.payload.request.data.leaf.material : "None",
+ type: action.payload.request.data.leaf.type? action.payload.request.data.leaf.type : "None",
+ conjoined_to: null,
+ attached_above: "None",
+ attached_below: "None",
+ stub: action.payload.request.data.leaf.stub? action.payload.request.data.leaf.stub : "None",
+ nestLevel: parentGroup.nestLevel,
+ parentID: parentGroupID,
+ rectoID: "Recto_" + sideIDs[sideCount],
+ versoID: "Verso_" + sideIDs[sideCount+1],
+ notes: [],
+ memberType: "Leaf"
+ }
+ newlyAddedLeafIDs.push("Leaf_" + leafIDs[count])
+ // Create new Recto with given rectoID
+ state.project.Rectos["Recto_" + sideIDs[sideCount]] = {
+ id: "Recto_" + sideIDs[sideCount],
+ parentID: "Leaf_" + leafIDs[count],
+ folio_number: null,
+ texture: "Hair",
+ image: {},
+ script_direction: "None",
+ notes: [],
+ memberType: "Recto"
+ }
+ state.project.rectoIDs.push("Recto_" + sideIDs[sideCount]);
+ // Create new Verso with given rectoID
+ state.project.Versos["Verso_" + sideIDs[sideCount+1]] = {
+ id: "Verso_" + sideIDs[sideCount+1],
+ parentID: "Leaf_" + leafIDs[count],
+ folio_number: null,
+ texture: "Flesh",
+ image: {},
+ script_direction: "None",
+ notes: [],
+ memberType: "Verso"
+ }
+ state.project.versoIDs.push("Verso_" + sideIDs[sideCount+1]);
+ sideCount += 2
+ }
+ if (!fromGroupCreation) {
+ // Add newlyAddedLeafIDs to the parentGroup's memberIDs list
+ state.project.Groups[parentGroupID].memberIDs.splice(memberOrder-1, 0, ...newlyAddedLeafIDs)
+ // Add newlyAddedLeafIDs to the active project's leafIDs list
+ if (globalOrder)
+ state.project.leafIDs.splice(globalOrder-1, 0, ...newlyAddedLeafIDs)
+ else {
+ // Populate leafIDs recursively and replace the active project's leafIDs list
+ let updatedLeafIDs = [];
+ for (let groupID of state.project.groupIDs) {
+ const group = state.project.Groups[groupID];
+ if (group.nestLevel===1) getLeafMembers(group.memberIDs, state, updatedLeafIDs)
+ }
+ state.project.leafIDs = updatedLeafIDs;
+ }
+ // AutoConjoin newlyAddedLeaves if necessary
+ }
+ if (autoConjoin) autoConjoinLeafs(action, state, newlyAddedLeafIDs, oddMemberLeftOut)
+ // Generate the list of new Leaves to flash
+ state.collationManager.flashItems.leaves = [...newlyAddedLeafIDs]
+ return state
+}
+
+export function updateLeaf(action, state) {
+ const updatedLeafID = action.payload.request.url.split("/").pop();
+ const updatedLeaf = action.payload.request.data.leaf;
+ // Do side effects if necessary
+ if (action.payload.request.data.leaf.hasOwnProperty("conjoined_to")) {
+ state = handleConjoin(state, updatedLeafID, action.payload.request.data.leaf.conjoined_to);
+ } else if (action.payload.request.data.leaf.hasOwnProperty("attached_above")) {
+ state = handleAttachAbove(state, updatedLeafID, action.payload.request.data.leaf.attached_above);
+ } else if (action.payload.request.data.leaf.hasOwnProperty("attached_below")) {
+ state = handleAttachBelow(state, updatedLeafID, action.payload.request.data.leaf.attached_below);
+ } else if (action.payload.request.data.leaf.hasOwnProperty("material") && action.payload.request.data.leaf.material==="Paper") {
+ state = handlePaperUpdate(state, updatedLeafID);
+ }
+ // Update the leaf with id updatedLeafID
+ state.project.Leafs[updatedLeafID] = { ...state.project.Leafs[updatedLeafID], ...updatedLeaf }
+ return state
+}
+
+function handleConjoin(state, leafID, conjoinedToID) {
+ const leaf = state.project.Leafs[leafID];
+ if (leaf.conjoined_to) {
+ state.project.Leafs[leaf.conjoined_to].conjoined_to = null;
+ }
+ if (conjoinedToID) {
+ const conjoinedToLeaf = state.project.Leafs[conjoinedToID];
+ if (conjoinedToLeaf.conjoined_to) {
+ state.project.Leafs[conjoinedToLeaf.conjoined_to].conjoined_to = null;
+ }
+ state.project.Leafs[conjoinedToID].conjoined_to = leafID;
+ }
+ return state;
+}
+
+function handleAttachAbove(state, leafID, attachType) {
+ const aboveLeafID = state.project.leafIDs[state.project.leafIDs.indexOf(leafID) - 1];
+ state.project.Leafs[aboveLeafID].attached_below = attachType;
+ return state;
+}
+
+function handleAttachBelow(state, leafID, attachType) {
+ const belowLeafID = state.project.leafIDs[state.project.leafIDs.indexOf(leafID) + 1];
+ state.project.Leafs[belowLeafID].attached_above = attachType;
+ return state;
+}
+
+function handlePaperUpdate(state, leafID) {
+ const leaf = state.project.Leafs[leafID];
+ state.project.Rectos[leaf.rectoID].texture = "None";
+ state.project.Versos[leaf.versoID].texture = "None";
+ return state;
+}
+
+export function updateLeaves(action, state) {
+ const updatedLeaves = action.payload.request.data.leafs
+ for (let updatedLeaf of updatedLeaves) {
+ // Update the leaf of id updatedLeaf.id with attributes updatedLeaf.attributes
+ state.project.Leafs[updatedLeaf.id] = { ...state.project.Leafs[updatedLeaf.id], ...updatedLeaf.attributes };
+ if (updatedLeaf.attributes.hasOwnProperty("material") && updatedLeaf.attributes.material==="Paper") {
+ state = handlePaperUpdate(state, updatedLeaf.id);
+ }
+ }
+ return state
+}
+
+export function deleteLeaf(deletedLeafID, state) {
+ const deletedLeaf = state.project.Leafs[deletedLeafID]
+ const deletedLeafParent = state.project.Groups[deletedLeaf.parentID]
+ const deletedLeafMemberIndex = deletedLeafParent.memberIDs.indexOf(deletedLeafID)
+ // Detach deletedLeaf's conjoined leaf if exists
+ if (deletedLeaf.conjoined_to !== null)
+ state.project.Leafs[deletedLeaf.conjoined_to].conjoined_to = null
+ // Detach deletedLeaf's attached_above leaf if exists
+ if (deletedLeaf.attached_above !== "None"){
+ const attachedAboveLeafID = deletedLeafParent.memberIDs[deletedLeafMemberIndex-1]
+ if (attachedAboveLeafID){ // deletedLeaf could be the first leaf in Group
+ state.project.Leafs[attachedAboveLeafID].attached_below = "None"
+ }
+ }
+ // Detach deletedLeaf's attached_below leaf if exists
+ if (deletedLeaf.attached_below !== "None") {
+ const attachedBelowLeafID = deletedLeafParent.memberIDs[deletedLeafMemberIndex+1]
+ if (attachedBelowLeafID) // deletedLeaf could be the last leaf in Group
+ state.project.Leafs[attachedBelowLeafID].attached_above = "None"
+ }
+ // Remove deletedLeafID from leafIDs list
+ let deletedLeafIDIndex = state.project.leafIDs.indexOf(deletedLeafID)
+ state.project.leafIDs.splice(deletedLeafIDIndex, 1)
+ // Remove deletedLeafID from deletedLeafParent's memberIDs list
+ deletedLeafIDIndex = deletedLeafParent.memberIDs.indexOf(deletedLeafID)
+ state.project.Groups[deletedLeaf.parentID].memberIDs.splice(deletedLeafIDIndex, 1)
+ // Update deletedLeafParent's tacketed if deletedLeafID is present
+ deletedLeafIDIndex = deletedLeafParent.tacketed.indexOf(deletedLeafID)
+ if (deletedLeafIDIndex !== -1)
+ state.project.Groups[deletedLeaf.parentID].tacketed.splice(deletedLeafIDIndex, 1)
+ // Update deletedLeafParent's sewing if deletedLeafID is present
+ deletedLeafIDIndex = deletedLeafParent.sewing.indexOf(deletedLeafID)
+ if (deletedLeafIDIndex !== -1)
+ state.project.Groups[deletedLeaf.parentID].sewing.splice(deletedLeafIDIndex, 1)
+ // Unlink all Notes of deletedLeafID. Also unlink Notes in Recto and Verso of deletedLeafID
+ for (let noteID in state.project.Notes) {
+ deletedLeafIDIndex = state.project.Notes[noteID].objects.Leaf.indexOf(deletedLeafID)
+ let rectoIDIndex = state.project.Notes[noteID].objects.Recto.indexOf(deletedLeaf.rectoID)
+ let versoIDIndex = state.project.Notes[noteID].objects.Verso.indexOf(deletedLeaf.versoID)
+ if (deletedLeafIDIndex !== -1)
+ state.project.Notes[noteID].objects.Leaf.splice(deletedLeafIDIndex, 1)
+ if (rectoIDIndex !== -1)
+ state.project.Notes[noteID].objects.Recto.splice(rectoIDIndex, 1)
+ if (versoIDIndex !== -1)
+ state.project.Notes[noteID].objects.Verso.splice(versoIDIndex, 1)
+ }
+ // Remove deletedLeaf's Recto and Verso IDs from Rectos, rectoIDs, Versos, versoIDs
+ delete state.project.Rectos[deletedLeaf.rectoID]
+ delete state.project.Versos[deletedLeaf.versoID]
+ const rectoIDIndex = state.project.rectoIDs.indexOf(deletedLeaf.rectoID)
+ state.project.rectoIDs.splice(rectoIDIndex, 1)
+ const versoIDIndex = state.project.versoIDs.indexOf(deletedLeaf.versoID)
+ state.project.versoIDs.splice(versoIDIndex, 1)
+ // Reset selectedObjects to empty list
+ state.collationManager.selectedObjects = {type: "", members: [], lastSelected: ""}
+ // Remove the deletedLeafID from Leafs
+ delete state.project.Leafs[deletedLeafID]
+ return state
+}
+
+export function deleteLeaves(deletedLeafIDs, state) {
+ for (let deletedLeafID of deletedLeafIDs) {
+ deleteLeaf(deletedLeafID, state)
+ }
+ return state
+}
+
+export function generateFolioPageNumbers(action, state, folioOrPage) {
+ let numberCount = action.payload.request.data.startNumber;
+ let rectoIDs = action.payload.request.data.rectoIDs;
+ let versoIDs = action.payload.request.data.versoIDs;
+ for (const index in rectoIDs) {
+ const recto = state.project.Rectos[rectoIDs[index]];
+ const verso = state.project.Versos[versoIDs[index]];
+ if (folioOrPage==="folio_number") {
+ recto[folioOrPage] = numberCount + recto.id[0];
+ verso[folioOrPage] = numberCount + verso.id[0];
+
+ } else {
+ recto[folioOrPage] = numberCount;
+ numberCount++;
+ verso[folioOrPage] = numberCount;
+ }
+ numberCount++;
+ }
+ return state
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/frontend/before/manifestActions.js b/viscoll-app/src/actions/frontend/before/manifestActions.js
new file mode 100644
index 00000000..dfe061dd
--- /dev/null
+++ b/viscoll-app/src/actions/frontend/before/manifestActions.js
@@ -0,0 +1,24 @@
+export function updateManifest(action, state) {
+ const updatedManifest = action.payload.request.data.manifest
+ // Only manifest name is allowed to be updated
+ state.project.manifests[updatedManifest.id].name = updatedManifest.name
+ return state
+}
+
+export function deleteManifest(action, state) {
+ const deletedManifest = action.payload.request.data.manifest
+ // Delete the manifest with id deletedManifest.id from the active project's manifests
+ delete state.project.manifests[deletedManifest.id]
+ // Update all sides that have an image mapped from deletedManifest
+ for (let rectoID of [...Object.keys(state.project.Rectos)]){
+ const rectoSide = state.project.Rectos[rectoID]
+ if (rectoSide.image.hasOwnProperty('manifestID') && rectoSide.image.manifestID===deletedManifest.id)
+ state.project.Rectos[rectoID].image = {}
+ }
+ for (let versoID of [...Object.keys(state.project.Versos)]) {
+ const versoSide = state.project.Versos[versoID]
+ if (versoSide.image.hasOwnProperty('manifestID') && versoSide.image.manifestID === deletedManifest.id)
+ state.project.Versos[versoID].image = {}
+ }
+ return state
+}
diff --git a/viscoll-app/src/actions/frontend/before/noteActions.js b/viscoll-app/src/actions/frontend/before/noteActions.js
new file mode 100644
index 00000000..ca481c47
--- /dev/null
+++ b/viscoll-app/src/actions/frontend/before/noteActions.js
@@ -0,0 +1,109 @@
+export function createNoteType(action, state) {
+ const newNoteType = action.payload.request.data.noteType.type
+ state.project.noteTypes.push(newNoteType)
+ return state
+}
+
+export function updateNoteType(action, state) {
+ const updatedNoteType = action.payload.request.data.noteType.type
+ const oldNoteType = action.payload.request.data.noteType.old_type
+ // Rename the noteType of each Note that had oldNoteType
+ for (let noteID in state.project.Notes){
+ if (state.project.Notes[noteID].type === oldNoteType)
+ state.project.Notes[noteID].type = updatedNoteType
+ }
+ // Rename the noteType in the noteTypes array
+ const oldNoteTypeIndex = state.project.noteTypes.indexOf(oldNoteType)
+ state.project.noteTypes[oldNoteTypeIndex] = updatedNoteType
+ return state
+}
+
+export function deleteNoteType(action, state) {
+ const deletedNoteType = action.payload.request.data.noteType.type
+ // Rename the noteType of each Note that had deleteNoteType to Unknown
+ for (let noteID in state.project.Notes) {
+ if (state.project.Notes[noteID].type === deletedNoteType)
+ state.project.Notes[noteID].type = "Unknown"
+ }
+ // Delete the noteType from the noteTypes array
+ const deletedNoteTypeIndex = state.project.noteTypes.indexOf(deletedNoteType)
+ state.project.noteTypes.splice(deletedNoteTypeIndex, 1)
+ return state
+}
+
+
+export function createNote(action, state) {
+ const newNote = action.payload.request.data.note
+ // Add new note to Notes
+ state.project.Notes[newNote.id] = {
+ id: newNote.id,
+ title: newNote.title,
+ type: newNote.type,
+ description: newNote.description,
+ show: newNote.show,
+ objects: { Group: [], Leaf: [], Recto: [], Verso: [] }
+ }
+ return state
+}
+
+export function updateNote(action, state) {
+ const updatedNoteID = action.payload.request.url.split("/").pop();
+ const updatedNote = action.payload.request.data.note
+ // Update the note with id updatedNoteID
+ state.project.Notes[updatedNoteID] = { ...state.project.Notes[updatedNoteID], ...updatedNote }
+ return state
+}
+
+export function linkNote(action, state) {
+ const linkedNoteID = action.payload.request.url.split("/").slice(-2)[0];
+ const linkedObjects = action.payload.request.data.objects
+ // Update each object with linkedNoteID
+ for (let object of linkedObjects){
+ if (object.type==="Side")
+ object.type = object.id.charAt(0) === "R" ? "Recto" : "Verso"
+ state.project[`${object.type}s`][object.id].notes.push(linkedNoteID)
+ // Update the objects property of note with linkedNoteID
+ state.project.Notes[linkedNoteID].objects[object.type].push(object.id)
+ }
+ return state
+}
+
+export function unlinkNote(action, state) {
+ const unlinkedNoteID = action.payload.request.url.split("/").slice(-2)[0];
+ const unlinkedObjects = action.payload.request.data.objects
+ // Update each object by removing unlinkedNoteID
+ for (let object of unlinkedObjects) {
+ if (object.type === "Side")
+ object.type = object.id.charAt(0) === "R" ? "Recto" : "Verso"
+ const unlinkedNoteIDIndex = state.project[`${object.type}s`][object.id].notes.indexOf(unlinkedNoteID)
+ state.project[`${object.type}s`][object.id].notes.splice(unlinkedNoteIDIndex, 1)
+ // Update the objects property of note with unlinkedNoteID
+ const unlinkedObjectIDIndex = state.project.Notes[unlinkedNoteID].objects[object.type].indexOf(object.id)
+ state.project.Notes[unlinkedNoteID].objects[object.type].splice(unlinkedObjectIDIndex, 1)
+ }
+ return state
+}
+
+export function deleteNote(action, state) {
+ const deletedNoteID = action.payload.request.url.split("/").pop();
+ // Delete the reference on all Groups,Leaves,Sides that this deletedNote had
+ for (let groupID of state.project.Notes[deletedNoteID].objects.Group) {
+ const deletedNoteIDIndex = state.project.Groups[groupID].notes.indexOf(deletedNoteID)
+ state.project.Groups[groupID].notes.splice(deletedNoteIDIndex, 1)
+ }
+ for (let leafID of state.project.Notes[deletedNoteID].objects.Leaf) {
+ const deletedNoteIDIndex = state.project.Leafs[leafID].notes.indexOf(deletedNoteID)
+ state.project.Leafs[leafID].notes.splice(deletedNoteIDIndex, 1)
+ }
+ for (let rectoID of state.project.Notes[deletedNoteID].objects.Recto) {
+ const deletedNoteIDIndex = state.project.Rectos[rectoID].notes.indexOf(deletedNoteID)
+ state.project.Rectos[rectoID].notes.splice(deletedNoteIDIndex, 1)
+ }
+ for (let versoID of state.project.Notes[deletedNoteID].objects.Verso) {
+ const deletedNoteIDIndex = state.project.Versos[versoID].notes.indexOf(deletedNoteID)
+ state.project.Versos[versoID].notes.splice(deletedNoteIDIndex, 1)
+ }
+ // Delete the note with id deletedNoteID in Notes
+ delete state.project.Notes[deletedNoteID]
+ return state
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/frontend/before/projectActions.js b/viscoll-app/src/actions/frontend/before/projectActions.js
new file mode 100644
index 00000000..e0c7cd93
--- /dev/null
+++ b/viscoll-app/src/actions/frontend/before/projectActions.js
@@ -0,0 +1,36 @@
+export function updateProject(action, state){
+ const updatedProject = action.payload.request.data.project;
+ const updatedProjectID = action.payload.request.url.split("/").pop();
+ const projectIndex = state.projects.findIndex(project => project.id === updatedProjectID)
+ state.projects[projectIndex] = {...state.projects[projectIndex], ...updatedProject};
+ return state
+}
+
+export function updatePreferences(action, state) {
+ const preferences = action.payload.request.data.project.preferences;
+ state.project.preferences = {
+ group:{...state.project.preferences.group, ...preferences.group},
+ leaf:{...state.project.preferences.leaf, ...preferences.leaf},
+ side:{...state.project.preferences.side,...preferences.side}
+ };
+ return state;
+}
+
+export function deleteProject(action, state) {
+ const deletedProjectID = action.payload.request.url.split("/").pop();
+ const deletedProjectIndex = state.projects.findIndex(project => project.id === deletedProjectID);
+ state.projects.splice(deletedProjectIndex, 1)
+ // Remove deletedProjectID from all images. If image has no projects linked, delete the image.
+ for (let image of [...state.images]){
+ const projectIDIndex = image.projectIDs.indexOf(deletedProjectID)
+ const imageIndex = state.images.findIndex(DIYImage => DIYImage.id === image.id)
+ if (projectIDIndex !== -1) {
+ state.images[imageIndex].projectIDs.splice(projectIDIndex, 1)
+ }
+ // Remove the image if its not linked to any other projects
+ if (projectIDIndex!==-1 && image.projectIDs.length===0 && action.payload.request.data.deleteUnlinkedImages) {
+ state.images.splice(imageIndex, 1)
+ }
+ }
+ return state
+}
diff --git a/viscoll-app/src/actions/frontend/before/sideActions.js b/viscoll-app/src/actions/frontend/before/sideActions.js
new file mode 100644
index 00000000..17e2a7e6
--- /dev/null
+++ b/viscoll-app/src/actions/frontend/before/sideActions.js
@@ -0,0 +1,71 @@
+export function updateSide(action, state) {
+ const updatedSideID = action.payload.request.url.split("/").pop();
+ const updatedSide = action.payload.request.data.side
+ // Update the side with id updatedSideID
+ if (updatedSideID.charAt(0)==="R")
+ state.project.Rectos[updatedSideID] = { ...state.project.Rectos[updatedSideID], ...updatedSide }
+ else
+ state.project.Versos[updatedSideID] = { ...state.project.Versos[updatedSideID], ...updatedSide }
+ return state
+}
+
+export function updateSides(action, state) {
+ const updatedSides = action.payload.request.data.sides
+ for (let updatedSide of updatedSides) {
+ // Update the side of id updatedSide.id with attributes updatedSide.attributes
+ if (updatedSide.id.charAt(0) === "R")
+ state.project.Rectos[updatedSide.id] = { ...state.project.Rectos[updatedSide.id], ...updatedSide.attributes }
+ else
+ state.project.Versos[updatedSide.id] = { ...state.project.Versos[updatedSide.id], ...updatedSide.attributes }
+ }
+ return state
+}
+
+export function mapSides(action, active, dashboard) {
+ // SPEICAL CASE FOR DIY IMAGE MAPPING
+ const mappedSides = action.payload.request.data.sides
+ for (let mappedSide of mappedSides){
+ const mappedSideID = mappedSide.id
+ const mappedSideImage = mappedSide.attributes.image
+ const sideNameKey = mappedSideID.charAt(0) === "R" ? "Rectos" : "Versos"
+ const currentSideImage = active.project[sideNameKey][mappedSideID].image
+ let imageLinkedID = false
+ // If an Image was linked, check if it is a DIY Image and link mappedSideID to the Image
+ if (mappedSideImage.hasOwnProperty('manifestID') && mappedSideImage.manifestID==='DIYImages'){
+ imageLinkedID = mappedSideImage.url.split("/").pop().split("_")[0]
+ let imageLinkedIDIndex = dashboard.images.findIndex(image => image.id===imageLinkedID)
+ if (imageLinkedIDIndex>=0) {
+ let mappedSideIDIndex = dashboard.images[imageLinkedIDIndex].sideIDs.indexOf(mappedSideID)
+ // Link mappedSideID to this image
+ if (mappedSideIDIndex===-1)
+ dashboard.images[imageLinkedIDIndex].sideIDs.push(mappedSideID)
+ }
+ }
+ // Check if this mappedSideID is now already linked to another DIY Image and unlink this mappedSideID from that Image
+ if (mappedSideImage.hasOwnProperty('manifestID') && currentSideImage.hasOwnProperty('manifestID') && currentSideImage.manifestID === 'DIYImages') {
+ let imageUnlinkedID = currentSideImage.url.split("/").pop().split("_")[0]
+ if (!imageLinkedID || imageLinkedID !== imageUnlinkedID) {
+ let imageUnlinkedIDIndex = dashboard.images.findIndex(image => image.id === imageUnlinkedID)
+ let mappedSideIDIndex = dashboard.images[imageUnlinkedIDIndex].sideIDs.indexOf(mappedSideID)
+ if (mappedSideIDIndex !== -1)
+ dashboard.images[imageUnlinkedIDIndex].sideIDs.splice(mappedSideIDIndex, 1)
+ }
+ }
+ // If an Image was unlinked, check if it was a DIY Image and unlink mappedSideID from the Image
+ if (!mappedSideImage.hasOwnProperty('manifestID') && currentSideImage.hasOwnProperty('manifestID') && currentSideImage.manifestID==='DIYImages'){
+ let imageID = currentSideImage.url.split("/").pop().split("_")[0]
+ let imageIndex = dashboard.images.findIndex(image => image.id === imageID)
+ if (imageIndex>=0) {
+ let mappedSideIDIndex = dashboard.images[imageIndex].sideIDs.indexOf(mappedSideID)
+ if (mappedSideIDIndex !== -1)
+ dashboard.images[imageIndex].sideIDs.splice(mappedSideIDIndex, 1)
+ }
+ }
+ }
+ updateSides(action, active) // this will handle updating the 'image' field of all mapped Sides
+ return { dashboard, active }
+}
+
+
+
+
diff --git a/viscoll-app/src/actions/undoRedo/groupHelper.js b/viscoll-app/src/actions/undoRedo/groupHelper.js
new file mode 100644
index 00000000..4985a6d6
--- /dev/null
+++ b/viscoll-app/src/actions/undoRedo/groupHelper.js
@@ -0,0 +1,133 @@
+import {
+ addGroups,
+ updateGroup,
+ updateGroups,
+ deleteGroups,
+} from '../backend/groupActions';
+
+import {
+ helperUndoDeleteLeaves
+} from './leafHelper';
+
+import {
+ linkNote,
+} from '../backend/noteActions';
+
+export function undoCreateGroups(action, state) {
+ const groupIDs = action.payload.request.data.additional.groupIDs.map((id)=>{return ("Group_"+id)});
+ const deleteRequest = deleteGroups({groups: groupIDs}, state.project.id);
+ return [deleteRequest];
+}
+
+export function undoUpdateGroups(action, state) {
+ const requestData = action.payload.request.data.groups;
+ let groups = [];
+ for (const request of requestData) {
+ const group = state.project.Groups[request.id];
+ let item = {
+ id: request.id,
+ attributes: {},
+ }
+ for (let attribute in request.attributes) {
+ if (!request.attributes.hasOwnProperty(attribute)) continue;
+ item.attributes[attribute] = group[attribute];
+ }
+ groups.push(item)
+ }
+ const historyAction = updateGroups(groups);
+ return [historyAction];
+}
+
+export function undoUpdateGroup(action, state) {
+ const groupID = action.payload.request.url.split("/").pop();
+ let attribute = Object.keys(action.payload.request.data.group)[0];
+
+ let group = {
+ [attribute]: state.project.Groups[groupID][attribute],
+ }
+ const historyAction = updateGroup(groupID, group);
+ return [historyAction];
+}
+
+export function undoDeleteGroup(action, state) {
+ const groupID = action.payload.request.url.split("/").pop();
+ const historyActions = helperUndoDeleteGroup(groupID, state);
+ return historyActions;
+}
+
+export function undoDeleteGroups(action, state) {
+ const groupIDs = action.payload.request.data.groups;
+ let historyActions = [];
+ for (const groupID of groupIDs) {
+ const group = state.project.Groups[groupID];
+ if (!(group.parentID && groupIDs.includes(group.parentID))) {
+ historyActions = historyActions.concat(helperUndoDeleteGroup(groupID, state));
+ }
+ }
+ return historyActions;
+}
+
+function helperUndoDeleteGroup(groupID, state) {
+ const group = state.project.Groups[groupID];
+ let historyActions = [];
+ // Create emtpy group
+ const groupInfo = {
+ project_id: state.project.id,
+ title: group.title,
+ type: group.type,
+ tacketed: group.tacketed,
+ sewing: group.sewing,
+ }
+ const additional = {
+ noOfGroups: 1,
+ leafIDs: [],
+ sideIDs: [],
+ groupIDs: [groupID.split("_")[1]],
+ order: state.project.groupIDs.indexOf(groupID) + 1,
+ }
+ if (group.parentID) {
+ additional["memberOrder"] = state.project.Groups[group.parentID].memberIDs.indexOf(groupID) + 1;
+ additional["parentGroupID"] = group.parentID;
+ } else {
+ additional["memberOrder"] = additional.order;
+ }
+ historyActions.push(addGroups(groupInfo, additional));
+
+ // Populate members
+ const groupedMembers = helperSeparateMembers(group.memberIDs);
+ for (let members of groupedMembers) {
+ if (members[0][0]==="L") {
+ historyActions = historyActions.concat(helperUndoDeleteLeaves(members, state));
+ } else {
+ // Recurse!
+ historyActions = historyActions.concat(helperUndoDeleteGroup(members[0], state));
+ }
+ }
+ // Link notes to group
+ if (group.notes.length>0) {
+ const objects = [{ id: groupID, type: "Group" }];
+ for (const noteID of group.notes) {
+ const noteRequest = linkNote(noteID, objects);
+ historyActions.push(noteRequest);
+ }
+ }
+ return historyActions;
+}
+
+/**
+ * Separate members into groups of leaves and groups
+ */
+function helperSeparateMembers(memberIDs) {
+ let result = memberIDs.length>0? [[memberIDs[0]]] : [];
+ for (let i=1; i{return ("Leaf_"+id)});
+ const deleteRequest = deleteLeafs({leafs: leafIDs});
+ return [deleteRequest];
+}
+
+export function undoUpdateLeaf(action, state) {
+ const historyActions = [];
+ const leafID = action.payload.request.url.split("/").pop();
+ const attribute = Object.keys(action.payload.request.data.leaf)[0];
+
+ const leaf = {
+ [attribute]: state.project.Leafs[leafID][attribute],
+ }
+ historyActions.push(updateLeaf(leafID, leaf));
+ if (attribute==="material" && (action.payload.request.data.leaf.material==="Paper" || action.payload.request.data.leaf.material==="Parchment")) {
+ historyActions.push(updateSides([
+ {
+ id: state.project.Leafs[leafID].rectoID,
+ attributes: {texture: state.project.Rectos[state.project.Leafs[leafID].rectoID].texture},
+ },
+ {
+ id: state.project.Leafs[leafID].versoID,
+ attributes: {texture: state.project.Versos[state.project.Leafs[leafID].versoID].texture},
+ }
+ ]))
+ }
+ return historyActions;
+}
+
+export function undoUpdateLeaves(action, state) {
+ const requestData = action.payload.request.data.leafs;
+ const leafs = [];
+ const historyActions = [];
+
+ for (const request of requestData) {
+ const leaf = state.project.Leafs[request.id];
+ const item = {
+ id: request.id,
+ attributes: {},
+ }
+ for (const attribute in request.attributes) {
+ if (!request.attributes.hasOwnProperty(attribute)) continue;
+ item.attributes[attribute] = leaf[attribute];
+ if (attribute==="material" && (request.attributes[attribute]==="Paper"||request.attributes[attribute]==="Parchment")) {
+ historyActions.push(updateSides([
+ {
+ id: state.project.Leafs[leaf.id].rectoID,
+ attributes: {texture: state.project.Rectos[state.project.Leafs[leaf.id].rectoID].texture},
+ },
+ {
+ id: state.project.Leafs[leaf.id].versoID,
+ attributes: {texture: state.project.Versos[state.project.Leafs[leaf.id].versoID].texture},
+ }
+ ]))
+ }
+ }
+ leafs.push(item)
+ }
+ historyActions.push(updateLeafs(leafs, state.project.id));
+ return historyActions;
+}
+
+export function undoDeleteLeaves(action, state) {
+ const historyActions = helperUndoDeleteLeaves(action.payload.request.data.leafs, state);
+ return historyActions;
+}
+
+export function undoDeleteLeaf(action, state) {
+ const leafID = action.payload.request.url.split("/").pop();
+ const historyActions = helperUndoDeleteLeaves([leafID], state);
+ return historyActions;
+}
+
+/**
+ * Params
+ * leafIDs list of leaf IDs that may not belong to the same parent nor are sequential
+ * state active tree state
+ * Returns [ [leafID, leafID,..], ... ]
+ */
+function helperSeparateLeavesByGroup(leafIDs, state) {
+ const leafNeighbours = [[leafIDs[0]]];
+ for (let i=1; i[state.project.Leafs[leafID].rectoID.split("_")[1], state.project.Leafs[leafID].versoID.split("_")[1]])).reduce((a,b)=>a.concat(b));
+
+ const leaf = {
+ project_id: state.project.id,
+ parentID: parentID,
+ nestLevel: state.project.Groups[parentID].nestLevel,
+ }
+ const additional = {
+ conjoin: false,
+ leafIDs: leafIDs.map((id)=>id.split("_")[1]),
+ memberOrder: state.project.Groups[parentID].memberIDs.indexOf(leafIDs[0]) + 1,
+ noOfLeafs: leafIDs.length,
+ order: state.project.leafIDs.indexOf(leafIDs[0]) + 1,
+ sideIDs: sideIDs,
+ }
+ const createRequest = addLeafs(leaf, additional);
+ historyActions.push(createRequest);
+ }
+
+ // Update leaves attributes
+ const leafs = [];
+ for (const leafID of leafIDs) {
+ const leaf = state.project.Leafs[leafID];
+ let attributes = {};
+ attributes['type'] = leaf.type;
+ attributes['material'] = leaf.material;
+ attributes['stub'] = leaf.stub;
+ if (leaf.conjoined_to) {
+ attributes['conjoined_to'] = leaf.conjoined_to;
+ // Update the conjoin partner leaf too
+ leafs.push({id:leaf.conjoined_to, attributes:{conjoined_to:leafID}});
+ }
+ if (leaf.attached_above!=="None") {
+ attributes['attached_above'] = leaf.attached_above;
+ // Update the above leaf too
+ const leafAboveID = state.project.leafIDs[state.project.leafIDs.indexOf(leafID)-1];
+ if (!leafIDs.includes(leafAboveID)) leafs.push({id:leafAboveID, attributes:{attached_below:leaf.attached_above}});
+ }
+ if (leaf.attached_below!=="None") {
+ attributes['attached_below'] = leaf.attached_below;
+ // Update the below leaf too
+ const leafBelowID = state.project.leafIDs[state.project.leafIDs.indexOf(leafID)+1];
+ if (!leafIDs.includes(leafBelowID))leafs.push({id:leafBelowID, attributes:{attached_above:leaf.attached_below}});
+ }
+ leafs.push({
+ id: leafID,
+ attributes,
+ });
+ }
+ const updateLeafsRequest = updateLeafs(leafs, state.project.id);
+ historyActions.push(updateLeafsRequest);
+
+ // Update side attributes
+ const sides = [];
+ for (const leafID of leafIDs) {
+ const leaf = state.project.Leafs[leafID];
+ const recto = state.project.Rectos[leaf.rectoID];
+ const verso = state.project.Versos[leaf.versoID];
+ sides.push({
+ id: recto.id,
+ attributes: {
+ texture: recto.texture,
+ folio_number: recto.folio_number? recto.folio_number : "",
+ script_direction: recto.script_direction,
+ }
+ })
+ sides.push({
+ id: verso.id,
+ attributes: {
+ texture: verso.texture,
+ folio_number: verso.folio_number? verso.folio_number : "",
+ script_direction: verso.script_direction,
+ }
+ });
+ }
+ const sideRequest = updateSides(sides);
+ historyActions.push(sideRequest);
+
+ // Link notes
+ for (const leafID of leafIDs) {
+ const leaf = state.project.Leafs[leafID];
+ const recto = state.project.Rectos[leaf.rectoID];
+ const verso = state.project.Versos[leaf.versoID];
+
+ if (leaf.notes.length>0) {
+ const objects = [{ id: leafID, type: "Leaf" }];
+ for (const noteID of leaf.notes) {
+ const noteRequest = linkNote(noteID, objects);
+ historyActions.push(noteRequest);
+ }
+ }
+ if (recto.notes.length>0) {
+ const objects = [{ id: recto.id, type: "Side" }];
+ for (const noteID of recto.notes) {
+ const noteRequest = linkNote(noteID, objects);
+ historyActions.push(noteRequest);
+ }
+ }
+ if (verso.notes.length>0) {
+ const objects = [{ id: verso.id, type: "Side" }];
+ for (const noteID of verso.notes) {
+ const noteRequest = linkNote(noteID, objects);
+ historyActions.push(noteRequest);
+ }
+ }
+ }
+ // Update parent group if leaves were part of sewing/tacket
+ const groupsRequest = [];
+ for (const leafID of leafIDs) {
+ const leaf = state.project.Leafs[leafID];
+ const group = state.project.Groups[leaf.parentID];
+ if (group.sewing.length>0 && (group.sewing[0]===leafID || (group.sewing[1] && group.sewing[1]===leafID))) {
+ groupsRequest.push({
+ id: group.id,
+ attributes: {
+ sewing: group.sewing,
+ }
+ });
+ }
+ if (group.tacketed.length>0 && (group.tacketed[0]===leafID || (group.tacketed[1] && group.tacketed[1]===leafID))) {
+ groupsRequest.push({
+ id: group.id,
+ attributes: {
+ tacketed: group.tacketed,
+ }
+ });
+ }
+ }
+ if (groupsRequest.length>0) historyActions.push(updateGroups(groupsRequest))
+ return historyActions;
+}
+
+export function undoAutoconjoin(action, state) {
+ const leafIDs = action.payload.request.data.leafs;
+ const leafs = [];
+ for (const leafID of leafIDs) {
+ leafs.push({
+ id: leafID,
+ attributes: {conjoined_to: state.project.Leafs[leafID].conjoined_to}
+ })
+ }
+ const historyAction = updateLeafs(leafs, state.project.id);
+ return [historyAction];
+}
+
+export function undoFolioPageNumbers(action, state, folioOrPage) {
+ const sideIDs = action.payload.request.data.rectoIDs.concat(action.payload.request.data.versoIDs);
+ const sides = [];
+ for (const sideID of sideIDs) {
+ const sideType = sideID.split("_")[0] + "s";
+ const item = {
+ id: sideID,
+ attributes: {
+ [folioOrPage]: state.project[sideType][sideID][folioOrPage],
+ }
+ }
+ sides.push(item);
+ }
+ const historyActions = updateSides(sides);
+ return [historyActions];
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/undoRedo/manifestHelper.js b/viscoll-app/src/actions/undoRedo/manifestHelper.js
new file mode 100644
index 00000000..10a7b462
--- /dev/null
+++ b/viscoll-app/src/actions/undoRedo/manifestHelper.js
@@ -0,0 +1,61 @@
+import {
+ createManifest,
+ updateManifest,
+} from '../backend/manifestActions';
+
+import {
+ updateSides,
+} from '../backend/sideActions';
+
+export function undoUpdateManifest(action, state) {
+ const manifest = {
+ id: action.payload.request.data.manifest.id,
+ name: state.project.manifests[action.payload.request.data.manifest.id].name,
+ }
+ const historyAction = updateManifest(state.project.id, {manifest});
+ return [historyAction];
+}
+
+export function undoDeleteManifest(action, state) {
+ const historyActions = [];
+ const manifestID = action.payload.request.data.manifest.id;
+ // Create manifest
+ const manifest = {
+ id: manifestID,
+ url: state.project.manifests[manifestID].url,
+ }
+ const createAction = createManifest(state.project.id, {manifest});
+ historyActions.push(createAction);
+
+ // Relink sides linked to images in this manifest
+ const sides = [];
+ for (const rectoID of state.project.rectoIDs) {
+ const recto = state.project.Rectos[rectoID];
+ if (recto.image.manifestID && recto.image.manifestID === manifestID) {
+ const item = {
+ id: recto.id,
+ attributes: {
+ image: {...recto.image},
+ }
+ }
+ sides.push(item);
+ }
+ }
+ for (const versoID of state.project.versoIDs) {
+ const verso = state.project.Versos[versoID];
+ if (verso.image.manifestID && verso.image.manifestID === manifestID) {
+ const item = {
+ id: verso.id,
+ attributes: {
+ image: {...verso.image},
+ }
+ };
+ sides.push(item);
+ }
+ }
+ if (sides.length>0) {
+ const mapAction = updateSides(sides);
+ historyActions.push(mapAction);
+ }
+ return historyActions;
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/undoRedo/noteHelper.js b/viscoll-app/src/actions/undoRedo/noteHelper.js
new file mode 100644
index 00000000..2441fb97
--- /dev/null
+++ b/viscoll-app/src/actions/undoRedo/noteHelper.js
@@ -0,0 +1,96 @@
+import {
+ deleteNoteType,
+ updateNoteType,
+ createNoteType,
+ updateNote,
+ unlinkNote,
+ linkNote,
+ addNote,
+} from '../backend/noteActions';
+
+export function undoUpdateNoteType(action, state) {
+ const noteType = {
+ project_id: state.project.id,
+ old_type: action.payload.request.data.noteType.type,
+ type: action.payload.request.data.noteType.old_type,
+ }
+ const historyAction = updateNoteType(noteType);
+ return [historyAction];
+}
+
+export function undoCreateNoteType(action, state) {
+ const noteType = {
+ project_id: state.project.id,
+ type: action.payload.request.data.noteType.type,
+ }
+ const historyAction = deleteNoteType(noteType);
+ return [historyAction];
+}
+
+export function undoDeleteNoteType(action, state) {
+ const historyActions = [];
+ // Create note type
+ const type = action.payload.request.data.noteType.type;
+ const noteType = {
+ project_id: state.project.id,
+ type,
+ }
+ historyActions.push(createNoteType(noteType));
+ // Update notes that had this note type
+ for (const key in state.project.Notes) {
+ if (!state.project.Notes.hasOwnProperty(key)) continue;
+ if (state.project.Notes[key].type === type) {
+ historyActions.push(updateNote(key, {type}));
+ }
+ }
+ return historyActions;
+}
+
+export function undoLinkNote(action, state) {
+ const urlSplit = action.payload.request.url.split("/");
+ const noteID = urlSplit[urlSplit.length-2];
+ const historyAction = unlinkNote(noteID, action.payload.request.data.objects);
+ return [historyAction];
+}
+
+export function undoUnlinkNote(action, state) {
+ const urlSplit = action.payload.request.url.split("/");
+ const noteID = urlSplit[urlSplit.length-2];
+ const historyAction = linkNote(noteID, action.payload.request.data.objects);
+ return [historyAction];
+}
+
+export function undoDeleteNote(action, state) {
+ const historyActions = [];
+ const noteID = action.payload.request.url.split("/").pop();
+ const note = state.project.Notes[noteID];
+
+ // Create note
+ const noteData = {
+ project_id: state.project.id,
+ id: noteID,
+ title: note.title,
+ type: note.type,
+ description: note.description,
+ show: note.show,
+ }
+ historyActions.push(addNote(noteData));
+
+ // Relink leaves, groups, sides
+ const objects = [];
+ for (const id of note.objects.Group) {
+ objects.push({id, type:"Group"});
+ }
+ for (const id of note.objects.Leaf) {
+ objects.push({id, type:"Leaf"});
+ }
+ for (const id of note.objects.Recto) {
+ objects.push({id, type:"Recto"});
+ }
+ for (const id of note.objects.Verso) {
+ objects.push({id, type:"Verso"});
+ }
+ historyActions.push(linkNote(noteID, objects));
+
+ return historyActions;
+}
\ No newline at end of file
diff --git a/viscoll-app/src/actions/undoRedo/sideHelper.js b/viscoll-app/src/actions/undoRedo/sideHelper.js
new file mode 100644
index 00000000..b836bf15
--- /dev/null
+++ b/viscoll-app/src/actions/undoRedo/sideHelper.js
@@ -0,0 +1,36 @@
+import {
+ updateSide,
+ updateSides,
+} from '../backend/sideActions';
+
+export function undoUpdateSide(action, state) {
+ const sideID = action.payload.request.url.split("/").pop();
+ const attribute = Object.keys(action.payload.request.data.side)[0];
+ const sideType = sideID.split("_")[0] + "s";
+ const side = {
+ [attribute]: state.project[sideType][sideID][attribute],
+ };
+ const historyAction = updateSide(sideID, side);
+ return [historyAction];
+}
+
+export function undoUpdateSides(action, state) {
+ const requests = action.payload.request.data.sides;
+ const sides = [];
+
+ for (const request of requests) {
+ const sideType = request.id.split("_")[0] + "s";
+ const side = state.project[sideType][request.id];
+ const item = {
+ id: request.id,
+ attributes: {},
+ }
+ for (const attribute in request.attributes) {
+ if (!request.attributes.hasOwnProperty(attribute)) continue;
+ item.attributes[attribute] = side[attribute];
+ }
+ sides.push(item);
+ }
+ const historyActions = updateSides(sides);
+ return [historyActions];
+}
\ No newline at end of file
diff --git a/viscoll-app/src/assets/blank_page.png b/viscoll-app/src/assets/blank_page.png
new file mode 100644
index 00000000..6958b3a6
Binary files /dev/null and b/viscoll-app/src/assets/blank_page.png differ
diff --git a/viscoll-app/src/assets/collation.png b/viscoll-app/src/assets/collation.png
new file mode 100644
index 00000000..706fcfc6
Binary files /dev/null and b/viscoll-app/src/assets/collation.png differ
diff --git a/viscoll-app/src/assets/logo_white.png b/viscoll-app/src/assets/logo_white.png
new file mode 100644
index 00000000..6b756189
Binary files /dev/null and b/viscoll-app/src/assets/logo_white.png differ
diff --git a/viscoll-app/src/assets/logo_white.svg b/viscoll-app/src/assets/logo_white.svg
new file mode 100644
index 00000000..be04257f
--- /dev/null
+++ b/viscoll-app/src/assets/logo_white.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/viscoll-app/src/assets/viscoll_loading.gif b/viscoll-app/src/assets/viscoll_loading.gif
new file mode 100644
index 00000000..13d6923b
Binary files /dev/null and b/viscoll-app/src/assets/viscoll_loading.gif differ
diff --git a/viscoll-app/src/assets/visualMode/PaperGroup.js b/viscoll-app/src/assets/visualMode/PaperGroup.js
new file mode 100644
index 00000000..b306c475
--- /dev/null
+++ b/viscoll-app/src/assets/visualMode/PaperGroup.js
@@ -0,0 +1,129 @@
+import paper from 'paper';
+
+PaperGroup.prototype = {
+ constructor: PaperGroup,
+ draw: function() {
+ // Create rectangle shapes
+ const rightIndent = (this.direction === "left-to-right") ? 0 : (this.spacing*(this.nestLevel-1));
+ let rectangle = new paper.Rectangle(
+ new paper.Point(this.x+10, this.y),
+ new paper.Size(this.width-this.x-rightIndent-20, this.groupHeight)
+ );
+ if (this.viewingMode) {
+ rectangle = new paper.Rectangle(
+ new paper.Point(this.x+10, this.y),
+ new paper.Size(this.width-rightIndent-this.x, this.groupHeight)
+ );
+ }
+ let highlightRectangle = rectangle.clone();
+ highlightRectangle.set(
+ new paper.Point(this.x, this.y-10),
+ new paper.Size(this.width-rightIndent-this.x, this.groupHeight+20)
+ );
+
+ // Create path from rectangle
+ this.path = new paper.Path.Rectangle(rectangle);
+ if (this.isActive) {
+ this.path.fillColor = this.groupColorActive;
+ } else {
+ this.path.fillColor = this.groupColor;
+ }
+ if (this.group.nestLevel%2===0) {
+ this.path.fillColor.brightness -= 0.05;
+ }
+ this.path.name = "group " + this.groupOrder;
+
+ // Create highlight path from rectangle
+ this.highlight = new paper.Path.Rectangle(highlightRectangle);
+ this.highlight.fillColor = new paper.Color(112/255.0, 229/255.0, 220/255.0, 1);
+ this.highlight.opacity = 0;
+ this.highlight.name = "group " + this.groupOrder + " highlight";
+ this.highlight.insertBelow(this.path);
+
+ this.filterHighlight = new paper.Path.Rectangle(highlightRectangle);
+ this.filterHighlight.fillColor = this.filterColor;
+ this.filterHighlight.opacity = 0;
+ this.filterHighlight.insertBelow(this.path);
+
+ // Set highlight path to be at the back
+ paper.project.activeLayer.insertChild(0, this.highlight);
+ paper.project.activeLayer.insertChild(0, this.filterHighlight);
+ },
+ setMouseEventHandlers: function() {
+ // Set mouse event handlers
+ let that = this;
+ this.path.onClick = function(event) {
+ that.handleObjectClick(that.group, event);
+ };
+ this.path.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ this.path.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ },
+ removeMouseEventHandlers: function() {
+ this.path.onClick = function(event) {};
+ this.path.onMouseEnter = function(event) {};
+ this.path.onMouseLeave = function(event) {};
+ },
+ activate: function() {
+ this.path.fillColor = this.groupColorActive;
+ this.isActive = true;
+ this.highlight.opacity = 0.75;
+ this.highlight.fillColor = "#ffffff";
+ },
+ deactivate: function() {
+ this.path.fillColor = this.groupColor;
+ if (this.group.nestLevel%2===0) {
+ this.path.fillColor.brightness -= 0.05;
+ }
+ this.isActive = false;
+ this.highlight.opacity = 0;
+ this.highlight.fillColor = new paper.Color(112/255.0, 229/255.0, 220/255.0, 1);
+ },
+ setVisibility: function(visibleAttributes) {
+ this.visibleAttributes = visibleAttributes;
+ let groupText = this.group.type + " " + this.groupOrder;
+ if (this.visibleAttributes && this.visibleAttributes.title) groupText = groupText + ": " + this.group.title;
+ this.text.set({
+ content: groupText,
+ });
+ if(this.direction === "right-to-left"){
+ this.text.set({
+ point: [this.width-this.text.bounds.width-this.text.point.x-(this.spacing*(this.nestLevel-1)), this.text.point.y],
+ })
+ }
+ },
+}
+// Constructor for group
+function PaperGroup(args) {
+ this.manager = args.manager;
+ this.group = args.group;
+ this.groupOrder = args.groupIDs.indexOf(args.group.id)+1
+ this.direction = args.direction;
+ this.y = args.y;
+ this.x = args.x;
+ this.nestLevel = args.nestLevel;
+ this.spacing = args.spacing;
+ this.width = args.width;
+ this.groupHeight = args.groupHeight;
+ this.isActive = args.isActive;
+ this.highlight = new paper.Path();
+ this.filterHighlight = new paper.Path();
+ this.path = new paper.Path();
+ this.groupColor = args.groupColor;
+ this.groupColorActive = args.groupColorActive;
+ this.textColor = args.textColor;
+ this.filterColor = args.filterColor;
+ this.handleObjectClick = args.handleObjectClick;
+ this.visibleAttributes = {};
+ this.viewingMode = args.viewingMode;
+ this.text = new paper.PointText({
+ point: [this.x+args.spacing*0.6, this.y+args.spacing*0.6],
+ fillColor: this.textColor,
+ fontSize: args.spacing*0.48,
+ });
+ this.setVisibility(args.visibleAttributes);
+}
+export default PaperGroup;
diff --git a/viscoll-app/src/assets/visualMode/PaperLeaf.js b/viscoll-app/src/assets/visualMode/PaperLeaf.js
new file mode 100644
index 00000000..bbabbc80
--- /dev/null
+++ b/viscoll-app/src/assets/visualMode/PaperLeaf.js
@@ -0,0 +1,631 @@
+import paper from 'paper';
+PaperLeaf.prototype = {
+ constructor: PaperLeaf,
+ draw: function() {
+ // Call this function only after ALL leaves have been instantiated
+ // This is because we need all leaves present in order
+ // to compute their indentations relative to each other
+ this.setIndent();
+ // Draw horizontal part
+ let x1, x2
+ if(!this.groupDirection || this.groupDirection === "left-to-right"){
+ x1 = this.multiplier<1? 10 + this.indent*this.spacing*this.multiplier : this.indent*this.spacing*this.multiplier;
+ x2 = this.width;
+ } else {
+ x1 = this.groupWidth - (this.multiplier < 1 ? 10 + this.indent*this.spacing*this.multiplier : this.indent*this.spacing*this.multiplier);
+ x2 = this.groupWidth - this.width;
+ this.oldPointX = x2;
+ }
+ this.dirMultiplier = (!this.groupDirection || this.groupDirection === "left-to-right") ? 1 : -1;
+
+ this.path.add(new paper.Point(x1, this.y));
+ if (this.leaf.stub !== "None" && (!this.groupDirection || this.groupDirection === "left-to-right")) {
+ x2 = this.width*0.15+x1;
+ } else if (this.leaf.stub !== "None") {
+ x2 = (x1-this.width*0.15)-x2;
+ }
+
+ this.path.add(new paper.Point(x2, this.y));
+ // Draw vertical part
+ if (this.isConjoined()) {
+ var conjoinY=this.y_conjoin_center(this.conjoined_to);
+ this.path.insert(0, new paper.Point(this.path.segments[0].point.x, conjoinY));
+ }
+ this.curveMe();
+
+ this.path.name = "leaf " + this.order;
+
+ // Create highlight path
+ this.highlight = this.path.clone();
+ this.highlight.dashArray = [0, 0];
+ this.highlight.segments[this.highlight.segments.length-1].point.x = this.highlight.segments[this.highlight.segments.length-1].point.x + 5;
+ if (this.leaf.conjoined_to === "None") {
+ this.highlight.segments[0].point.x = this.highlight.segments[0].point.x - 5;
+ }
+ this.highlight.strokeColor = this.strokeColorActive;
+ this.highlight.strokeWidth = this.path.strokeWidth*2;
+ this.highlight.opacity = 0;
+ this.highlight.name = "leaf " + this.order + " highlight";
+ this.highlight.insertBelow(this.path);
+
+ if (this.isActive) {
+ this.highlight.opacity = 0.35;
+ this.highlight.strokeWidth = this.path.strokeWidth*2;
+ this.highlight.strokeColor = "#ffffff";
+ }
+
+ this.filterHighlight = this.path.clone();
+ this.filterHighlight.dashArray = [0, 0];
+ this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x = this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x + 5;
+ if (this.leaf.conjoined_to === "None") {
+ this.filterHighlight.segments[0].point.x = this.filterHighlight.segments[0].point.x - 5;
+ }
+ this.filterHighlight.strokeColor = this.strokeColorFilter;
+ this.filterHighlight.strokeWidth = this.path.strokeWidth*2;
+ this.filterHighlight.opacity = 0;
+ this.filterHighlight.insertBelow(this.path);
+
+ // this.path.fullySelected = true;
+ this.showAttributes();
+ this.createAttachments();
+
+ const leafNotesToShow = this.leaf.notes.filter((noteID)=>{return this.Notes[noteID].show}).reverse();
+ const rectoNotesToShow = this.recto.notes.filter((noteID)=>{return this.Notes[noteID].show}).reverse();
+ const versoNotesToShow = this.verso.notes.filter((noteID)=>{return this.Notes[noteID].show});
+
+ let textX = 0;
+ let textY = this.y;
+ let fontSize = this.spacing*0.50;
+ let numChars = this.path.bounds.width/fontSize*2.4;
+
+ if (this.isConjoined()) {
+ // This leaf is conjoined
+ textX = this.path.segments[1].point.x;
+ } else {
+ // Separate leaf
+ textX = this.path.segments[0].point.x+this.dirMultiplier*10;
+ }
+ if (this.leaf.attached_above.includes("Partial")) {
+ // This leaf has a partial glue attachment
+ // Place text to the right of attachment drawing
+ if(!this.groupDirection || this.groupDirection === "left-to-right"){
+ textX = this.attachment.bounds.right+5;
+ } else {
+ textX = this.attachment.bounds.left - 5;
+ }
+ } else if (this.leaf.attached_above.includes("Glued")) {
+ // Other type of glueing exists
+ if(!this.groupDirection || this.groupDirection === "left-to-right"){
+ textY -= this.spacing*0.7;
+ } else {
+ textY += this.spacing*0.7;
+ }
+ }
+ let that = this;
+ let clickListener = function(note) {
+ return function(event) {
+ that.openNoteDialog(note);
+ }
+ }
+ // Draw recto note text
+ for (let noteIndex = 0; noteIndex < rectoNotesToShow.length; noteIndex++) {
+ const note = this.Notes[rectoNotesToShow[noteIndex]];
+ const noteTitle = this.recto.folio_number? "â–¼ " + this.recto.folio_number + " : " + note.title.substr(0,numChars) : "â–¼ R : " + note.title.substr(0,numChars) ;
+ let textNote = new paper.PointText({
+ content: noteTitle,
+ point: [textX, textY - noteIndex*(this.spacing*0.7) - this.spacing*0.3],
+ fillColor: this.strokeColor,
+ fontSize: fontSize,
+ });
+ const textPointX = (!this.groupDirection || this.groupDirection === "left-to-right") ? textX : textX-textNote.bounds.width;
+ textNote.set({point: [textPointX, textY - noteIndex * (this.spacing * 0.7) - this.spacing * 0.3]});
+ textNote.onClick = !this.viewingMode && clickListener(note);
+ this.textNotes.addChild(textNote);
+ }
+ // Draw leaf note text
+ for (let noteIndex = 0; noteIndex < leafNotesToShow.length; noteIndex++) {
+ const note = this.Notes[leafNotesToShow[noteIndex]];
+
+ let textNote = new paper.PointText({
+ content: "â–¼ L" + this.order + " : " + note.title.substr(0,numChars),
+ fillColor: this.strokeColor,
+ fontSize: fontSize,
+ });
+ const textPointX = (!this.groupDirection || this.groupDirection === "left-to-right") ? textX : textX - textNote.bounds.width;
+ textNote.set({point: [textPointX, textY - rectoNotesToShow.length*(this.spacing*0.7) - noteIndex*(this.spacing*0.7) - this.spacing*0.3]});
+ textNote.onClick = !this.viewingMode && clickListener(note);
+ this.textNotes.addChild(textNote);
+ }
+ // Draw verso note text
+ for (let noteIndex = 0; noteIndex < versoNotesToShow.length; noteIndex++) {
+ const note = this.Notes[versoNotesToShow[noteIndex]];
+ const noteTitle = this.verso.folio_number? "â–² " + this.verso.folio_number + " : " + note.title.substr(0,numChars) : "â–² V : " + note.title.substr(0,numChars);
+ let textNote = new paper.PointText({
+ content: noteTitle,
+ point: [textX, this.y + noteIndex*(this.spacing*0.7) + this.spacing*0.8],
+ fillColor: this.strokeColor,
+ fontSize: fontSize,
+ });
+ const textPointX = (!this.groupDirection || this.groupDirection === "left-to-right") ? textX : textX-textNote.bounds.width;
+ textNote.set({point: [textPointX, this.y + noteIndex*(this.spacing*0.7) + this.spacing*0.8]})
+ textNote.onClick = !this.viewingMode && clickListener(note);
+ this.textNotes.addChild(textNote);
+ }
+ this.textNotes.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ this.textNotes.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ return this;
+ },
+ setMouseEventHandlers: function() {
+ // Set mouse event handlers
+ let that = this;
+ this.path.onClick = function(event) {
+ that.handleObjectClick(that.leaf, event);
+ };
+ this.textLeafOrder.onClick = function(event) {
+ that.handleObjectClick(that.leaf, event);
+ };
+ this.textRecto.onClick = function(event) {
+ that.handleObjectClick(that.leaf, event);
+ };
+ this.textVerso.onClick = function(event) {
+ that.handleObjectClick(that.leaf, event);
+ };
+ this.path.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ this.path.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ this.textLeafOrder.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ this.textLeafOrder.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ this.textRecto.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ this.textRecto.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ this.textVerso.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ this.textVerso.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ },
+ removeMouseEventHandlers: function() {
+ // Set mouse event handlers
+ this.path.onClick = function(event) {};
+ this.textLeafOrder.onClick = function(event) {};
+ this.textRecto.onClick = function(event) {};
+ this.textVerso.onClick = function(event) {};
+ this.path.onMouseEnter = function(event) {};
+ this.path.onMouseLeave = function(event) {};
+ this.textLeafOrder.onMouseEnter = function(event) {};
+ this.textLeafOrder.onMouseLeave = function(event) {};
+ this.textRecto.onMouseEnter = function(event) {};
+ this.textRecto.onMouseLeave = function(event) {};
+ this.textVerso.onMouseEnter = function(event) {};
+ this.textVerso.onMouseLeave = function(event) {};
+ },
+ createAttachments: function() {
+ if (this.order>1 && this.leaf.attached_above.includes("Glued")) {
+ this.createGlue();
+ } else if (this.order>1 && this.leaf.attached_above==="Other") {
+ this.createOtherAttachment();
+ }
+ if (this.order>1 && this.leaf.conjoin_type==="Sewn") {
+ this.createSewn();
+ }
+ },
+ createGlue: function() {
+ let x = this.path.segments[0].point.x;
+ if (this.isConjoined() && this.conjoined_to (this.path.segments[this.path.segments.length-1].point.x-10)) {
+ let glueLine = new paper.Path();
+ glueLine.add(new paper.Point(x, this.y-this.spacing*0.3));
+ glueLine.add(new paper.Point(x-10, this.y-this.spacing*0.7));
+ glueLine.strokeColor = "#707070";
+ glueLine.strokeWidth = 2;
+ this.attachment.addChild(glueLine);
+ x -= 5;
+ }
+ } else {
+ // Complete glue
+ while (x <= (this.path.segments[this.path.segments.length-1].point.x-10)) {
+ let glueLine = new paper.Path();
+ glueLine.add(new paper.Point(x, this.y-this.spacing*0.3));
+ glueLine.add(new paper.Point(x+10, this.y-this.spacing*0.7));
+ glueLine.strokeColor = "#707070";
+ glueLine.strokeWidth = 2;
+ this.attachment.addChild(glueLine);
+ x += 5;
+ }
+ }
+ },
+ // Sewing of conjoined leaves
+ createSewn: function() {
+ if (this.isConjoined() && this.conjoined_to0;
+ },
+ conjoinedLeaf: function() {
+ return this.manager.getLeaf(this.conjoined_to);
+ },
+ y_conjoin_center: function(conjoin_order) {
+ var y1 = this.path.segments[1].point.y;
+ var y2 = (this.manager.getLeaf(conjoin_order)).getY();
+ return y1+((y2-y1)/2.0);
+ },
+ y_nonconjoin_center: function() {
+ var y = this.y
+ var y_center = this.y_centerQuire();
+ if (y===y_center) {
+ return y_center;
+ } else if (y= this.manager.numLeaves()) {
+ return 0;
+ } else {
+ return this.manager.getLeaf(this.order+1).y;
+ }
+ },
+ curveMe: function() {
+ var path_height = Math.abs(this.path.segments[0].point.y - this.path.segments[1].point.y);
+ var midpoint = this.path.segments[1].point;
+ if (this.isConjoined() ) {
+ var numLeavesInside = Math.abs(this.conjoined_to - this.order);
+ // Remove the middle point and insert a new one with handles
+ this.path.removeSegment(1);
+ // // Calculate new point's location and radius
+ var new_x = midpoint.x + this.dirMultiplier*20;
+ var radius = new_x - this.path.segments[0].point.x;
+ var s1 = new paper.Segment(new paper.Point(new_x, midpoint.y), new paper.Point(-radius,0), null);
+ this.path.insert(1, s1);
+
+ if (numLeavesInside > 5) {
+ // Modify first point's handle so that the curve isn't too curvy
+ var direction = this.path.segments[0].point.y > this.path.segments[1].point.y? -1 : 1;
+ var oldPoint = this.path.segments[0].point;
+ this.path.removeSegment(0);
+ var s0 = new paper.Segment(new paper.Point(oldPoint.x, oldPoint.y), null, new paper.Point(0,direction*(path_height)));
+ this.path.insert(0, s0);
+ }
+ }
+ },
+ setIndent: function() {
+ this.indent = this.leaf.nestLevel;
+ if (this.isConjoined() && this.conjoinedLeaf().indent != null) {
+ // Leaf is conjoined and conjoiner has indent, so copy that conjoiner's indent
+ this.indent = this.conjoinedLeaf().indent;
+ } else if (this.isBelowAConjoined()) {
+ this.indent = (this.prevPaperLeaf().indent+1);
+ } else if (this.order>1 && this.parentOrder === this.prevPaperLeaf().parentOrder){
+ // Leaf is a sibling of previous leaf, so copy sibling's indent
+ this.indent = this.prevPaperLeaf().indent;
+ }
+ },
+ isBelowAConjoined: function() {
+ return (this.order>1 && this.prevPaperLeaf().isConjoined() && this.prevPaperLeaf().conjoined_to > this.order);
+ },
+ y_centerQuire: function () {
+ var last_leaf = this.manager.getLastLeaf().getY();
+ return (last_leaf+ this.spacing)/2.0 ;
+ },
+ getY: function() {
+ return this.y;
+ },
+ activate: function() {
+ this.path.strokeColor = this.strokeColorActive;
+ this.highlight.opacity = 0.35;
+ this.highlight.strokeWidth = this.path.strokeWidth*2;
+ this.highlight.strokeColor = "#ffffff";
+ },
+ deactivate: function() {
+ this.highlight.opacity = 0;
+ this.highlight.strokeWidth = this.path.strokeWidth;
+ this.highlight.strokeColor = this.strokeColorActive;
+
+ if (this.leaf.type==="Added") {
+ this.path.strokeColor = this.strokeColorAdded;
+ } else if (this.leaf.type==="Endleaf") {
+ this.path.strokeColor = "#727272";
+ } else {
+ this.path.strokeColor = this.strokeColor;
+ }
+ },
+ setVisibility: function(visibleAttributes) {
+ this.visibleAttributes = visibleAttributes;
+ this.showAttributes();
+ },
+ showAttributes: function() {
+ const rectoValues = [];
+ const versoValues = [];
+ if (this.visibleAttributes.side) {
+ if (this.visibleAttributes.side.folio_number) {
+ if (this.recto.folio_number) rectoValues.push(this.recto.folio_number)
+ if (this.verso.folio_number) versoValues.push(this.verso.folio_number)
+ }
+ if (this.visibleAttributes.side.page_number) {
+ if (this.recto.page_number) rectoValues.push(this.recto.page_number)
+ if (this.verso.page_number) versoValues.push(this.verso.page_number)
+ }
+ if (this.visibleAttributes.side.texture) {
+ rectoValues.push(this.recto.texture)
+ versoValues.push(this.verso.texture)
+ }
+ }
+ let rectoContent = "";
+ let versoContent = "";
+ for (let i=0; i {
+ if (this.visibleAttributes.side[key]) return acc+1;
+ return acc;
+ }
+ const visibleAttributeCount = this.visibleAttributes.side? Object.keys(this.visibleAttributes.side).reduce(reducer,0) : 0;
+
+ if (visibleAttributeCount===3) {
+ const newPointX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*3 : this.oldPointX + this.spacing * 3;
+ const newHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*2.8 : this.oldPointX + this.spacing * 2.8;
+ const newfilterHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*2.8 : this.oldPointX + this.spacing * 2.8;
+ const textLeafOrderX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*2.8 : this.oldPointX - this.textLeafOrder.bounds.width + this.spacing * 2.8;
+
+ if (this.leaf.stub === "None") {
+ // Reduce leaf width so we can fit attribute text
+ this.path.segments[this.path.segments.length-1].point.x = newPointX;
+ this.highlight.segments[this.highlight.segments.length-1].point.x = newHighlightX;
+ this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x = newfilterHighlightX;
+ }
+ this.textLeafOrder.set({point: [textLeafOrderX, this.textLeafOrder.point.y]});
+ this.textRecto.set({content: rectoContent});
+ this.textVerso.set({content: versoContent});
+
+ const textRectoX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.textLeafOrder.bounds.right+this.spacing*0.4 : this.textLeafOrder.bounds.left - this.textRecto.bounds.width - this.spacing * 0.4;
+ const textVersoX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.textLeafOrder.bounds.right+this.spacing*0.4 :this.textLeafOrder.bounds.left - this.textVerso.bounds.width - this.spacing * 0.4;
+
+ this.textRecto.set({ point: [textRectoX, this.textRecto.point.y], });
+ this.textVerso.set({ point: [textVersoX, this.textVerso.point.y], });
+ } else if (visibleAttributeCount===2) {
+ const newPointX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*2 : this.oldPointX + this.spacing * 2;
+ const newHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*1.8 : this.oldPointX + this.spacing * 1.8;
+ const newfilterHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*1.8 : this.oldPointX + this.spacing * 1.8;
+ const textLeafOrderX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*1.8 : this.oldPointX- this.textLeafOrder.bounds.width + this.spacing * 1.8;
+
+ if (this.leaf.stub === "None") {
+ // Reduce leaf width so we can fit attribute text
+ this.path.segments[this.path.segments.length-1].point.x = newPointX;
+ this.highlight.segments[this.highlight.segments.length-1].point.x = newHighlightX;
+ this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x = newfilterHighlightX;
+ }
+ this.textLeafOrder.set({point: [textLeafOrderX, this.textLeafOrder.point.y]});
+ this.textRecto.set({content: rectoContent});
+ this.textVerso.set({content: versoContent});
+
+ const textRectoX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.textLeafOrder.bounds.right+this.spacing*0.2 : this.textLeafOrder.bounds.left - this.textRecto.bounds.width - this.spacing * 0.2;
+ const textVersoX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.textLeafOrder.bounds.right+this.spacing*0.2 : this.textLeafOrder.bounds.left - this.textVerso.bounds.width - this.spacing * 0.2;
+
+ this.textRecto.set({ point: [textRectoX, this.textRecto.point.y], });
+ this.textVerso.set({ point: [textVersoX, this.textVerso.point.y], });
+ }
+ else if (visibleAttributeCount===1) {
+ const newPointX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing : this.oldPointX + this.spacing;
+ const newHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*0.8 : this.oldPointX + this.spacing * 0.8;
+ const newfilterHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*0.8 : this.oldPointX + this.spacing * 0.8;
+ const textLeafOrderX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*0.8 : this.oldPointX- this.textLeafOrder.bounds.width + this.spacing * 0.8;
+
+ if (this.leaf.stub === "None") {
+ // Reduce leaf width so we can fit folio number text
+ this.path.segments[this.path.segments.length-1].point.x = newPointX;
+ this.highlight.segments[this.highlight.segments.length-1].point.x = newHighlightX;
+ this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x = newfilterHighlightX;
+ }
+ this.textLeafOrder.set({point: [textLeafOrderX, this.textLeafOrder.point.y]});
+ this.textRecto.set({content: rectoContent});
+ this.textVerso.set({content: versoContent});
+
+ const textRectoX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.textLeafOrder.bounds.right+this.spacing*0.2 : this.textLeafOrder.bounds.left - this.textRecto.bounds.width - this.spacing * 0.2;
+ const textVersoX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.textLeafOrder.bounds.right+this.spacing*0.2 : this.textLeafOrder.bounds.left - this.textVerso.bounds.width - this.spacing * 0.2;
+
+ this.textRecto.set({ point: [textRectoX, this.textRecto.point.y], });
+ this.textVerso.set({ point: [textVersoX, this.textVerso.point.y], });
+ } else {
+ const newPointX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width : this.oldPointX;
+ const newHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width + 5 : this.oldPointX - 5;
+ const newfilterHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width + 5 : this.oldPointX - 5;
+ const textLeafOrderX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width+10 : this.oldPointX - this.textLeafOrder.bounds.width - 5;
+
+ // Reset leaf
+ if (this.leaf.stub === "None") {
+ this.path.segments[this.path.segments.length-1].point.x = newPointX;
+ this.highlight.segments[this.highlight.segments.length-1].point.x = newHighlightX;
+ this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x = newfilterHighlightX;
+ }
+ this.textLeafOrder.set({point: [textLeafOrderX, this.textLeafOrder.point.y]});
+ }
+ },
+}
+// Constructor for leaf
+function PaperLeaf(args) {
+ this.manager = args.manager;
+ this.Notes = args.Notes;
+ this.leafIDs = args.leafIDs;
+ this.leaf = args.leaf;
+ this.recto = args.recto;
+ this.verso = args.verso;
+ this.order = args.leafIDs.indexOf(args.leaf.id) + 1;
+ this.parentOrder = args.groupIDs.indexOf(this.leaf.parentID)+1;
+ this.conjoined_to = this.leaf.conjoined_to===null ? "None" : this.leafIDs.indexOf(this.leaf.conjoined_to)+1;
+ this.indent = null;
+ this.origin = args.origin;
+ this.viewingMode = args.viewingMode;
+ this.width = this.viewingMode? args.width*0.88 : args.width*0.92;
+ this.groupDirection = args.Groups[this.leaf.parentID].direction;
+ this.groupWidth = args.width;
+ this.spacing = args.spacing;
+ this.y = args.y;
+ this.strokeWidth = args.strokeWidth;
+ this.strokeColor = args.strokeColor;
+ this.strokeColorActive = args.strokeColorActive;
+ this.strokeColorGroupActive = args.strokeColorGroupActive;
+ this.strokeColorFilter = args.strokeColorFilter;
+ this.strokeColorAdded = args.strokeColorAdded;
+ this.highlight = new paper.Path();
+ this.filterHighlight = new paper.Path();
+ this.path = new paper.Path();
+ this.isActive = args.isActive;
+ this.handleObjectClick = args.handleObjectClick;
+ this.multiplier = args.multiplier;
+ this.attachment = new paper.Group();
+ this.textNotes = new paper.Group();
+ this.openNoteDialog = args.openNoteDialog;
+
+ this.textLeafOrder = new paper.PointText({
+ point: [this.width, this.y+5],
+ content: "L"+ this.order,
+ fillColor: this.strokeColor,
+ fontSize: this.spacing*0.48,
+ });
+ this.textRecto = new paper.PointText({
+ point: [this.width+this.spacing, this.y-3],
+ fillColor: this.strokeColor,
+ fontSize: this.spacing*0.37,
+ });
+ this.textVerso = new paper.PointText({
+ point: [this.width+this.spacing, this.y+this.spacing*0.37-3],
+ fillColor: this.strokeColor,
+ fontSize: this.spacing*0.37,
+ });
+
+ this.visibleAttributes = args.visibleAttributes;
+ // Set path properties
+ if (this.leaf.type==="Added") {
+ this.path.strokeColor = args.strokeColorAdded;
+ } else if (this.leaf.type==="Endleaf") {
+ this.path.strokeColor = "#919191";
+ } else {
+ this.path.strokeColor = args.strokeColor;
+ }
+ if (this.leaf.type==='Missing') {
+ // If leaf is missing, make stroke dashed
+ this.path.dashArray = [20, 10];
+ }
+ if (this.leaf.type==='Replaced') {
+ this.path.strokeColor = "#9d6464";
+ }
+ if (this.isActive) {
+ this.path.strokeColor = args.strokeColorActive;
+ }
+ this.path.strokeWidth = this.strokeWidth;
+ if (this.leaf.material === "Parchment") {
+ this.path.strokeWidth = this.path.strokeWidth*1.20;
+ } else if (this.leaf.material === "Paper") {
+ this.path.strokeWidth = this.path.strokeWidth*0.80;
+ }
+}
+
+
+export default PaperLeaf;
diff --git a/viscoll-app/src/assets/visualMode/PaperManager.js b/viscoll-app/src/assets/visualMode/PaperManager.js
new file mode 100644
index 00000000..decb812d
--- /dev/null
+++ b/viscoll-app/src/assets/visualMode/PaperManager.js
@@ -0,0 +1,714 @@
+import paper from 'paper';
+import PaperLeaf from "./PaperLeaf.js";
+import PaperGroup from "./PaperGroup.js";
+import { getMemberOrder } from '../../helpers/getMemberOrder';
+
+PaperManager.prototype = {
+ constructor: PaperManager,
+ createGroup: function(group) {
+ let g = new PaperGroup({
+ manager: this,
+ group: group,
+ groupIDs: this.groupIDs,
+ direction: group.direction,
+ y: this.groupYs[this.groupIDs.indexOf(group.id)],
+ x: (group.direction === "left-to-right") ? (group.nestLevel-1)*(this.spacing) : 0,
+ width: this.width,
+ groupHeight: this.getGroupHeight(group),
+ isActive: this.activeGroups.includes(group.id),
+ groupColor: this.groupColor,
+ groupColorActive: this.groupColorActive,
+ filterColor: this.strokeColorFilter,
+ handleObjectClick: this.handleObjectClick,
+ textColor: this.groupTextColor,
+ visibleAttributes: this.visibleAttributes.group,
+ viewingMode: this.viewingMode,
+ spacing: this.spacing,
+ nestLevel: group.nestLevel,
+ });
+ g.draw();
+ g.setMouseEventHandlers();
+
+ // Add this group to collections
+ this.groupGroups.addChild(g.filterHighlight);
+ this.groupGroups.addChild(g.highlight);
+ this.groupGroups.addChild(g.path);
+ this.groupGroups.addChild(g.text);
+ this.paperGroups.push(g);
+
+ // Add this group to list of items to flash if it's in the flashItems list
+ if (this.flashItems.groups.includes(group.id)) {
+ this.flashGroups.push(g);
+ }
+
+ },
+ createLeaf: function(leaf) {
+ let l = new PaperLeaf({
+ manager: this,
+ origin: this.origin,
+ width: this.width,
+ spacing: this.spacing,
+ strokeWidth: this.strokeWidth * Math.min(1.0, this.multipliers[this.leafIDs.indexOf(leaf.id)+1]),
+ strokeColor: this.strokeColor,
+ strokeColorActive: this.strokeColorActive,
+ strokeColorGroupActive: this.strokeColorGroupActive,
+ strokeColorAdded: this.strokeColorAdded,
+ leaf: leaf,
+ recto: this.Rectos[leaf.rectoID],
+ verso: this.Versos[leaf.versoID],
+ groupIDs: this.groupIDs,
+ leafIDs: this.leafIDs,
+ Groups: this.Groups,
+ Notes: this.Notes,
+ y: this.leafYs[this.leafIDs.indexOf(leaf.id)],
+ isActive: this.activeLeafs.includes(leaf.id) || this.activeRectos.includes(leaf.rectoID) || this.activeVersos.includes(leaf.versoID),
+ customSpacings: this.customSpacings,
+ handleObjectClick: this.handleObjectClick,
+ multiplier: this.multipliers[this.leafIDs.indexOf(leaf.id)+1],
+ strokeColorFilter: this.strokeColorFilter,
+ visibleAttributes: this.visibleAttributes,
+ viewingMode: this.viewingMode,
+ openNoteDialog: this.openNoteDialog,
+ });
+ this.paperLeaves.push(l);
+ this.groupLeaves.addChild(l.path);
+ this.groupLeaves.addChild(l.textLeafOrder);
+ this.groupLeaves.addChild(l.textRecto);
+ this.groupLeaves.addChild(l.textVerso);
+ this.groupLeaves.addChild(l.attachment);
+ this.groupLeaves.addChild(l.textNotes);
+ if (this.flashItems.leaves.includes(leaf.id)) {
+ this.flashLeaves.push(l);
+ }
+ return l;
+ },
+ draw: function() {
+ // Clear existing drawn elements
+ this.leafYs = [];
+ this.groupYs = [];
+ this.paperLeaves = [];
+ this.paperGroups = [];
+ this.flashGroups = [];
+ this.flashLeaves = [];
+ this.groupLeaves.removeChildren();
+ this.groupGroups.removeChildren();
+ this.groupContainer.removeChildren();
+ this.groupTacket.removeChildren();
+
+ // Calculate y positions of groups and leaves
+ let currentY = 0;
+ let prevRootGroupID = null;
+ for (let i in this.groupIDs) {
+ const groupID = this.groupIDs[i];
+ const group = this.Groups[groupID];
+ if (group.nestLevel === 1) {
+ if (i>0 && prevRootGroupID) {
+ const prevGroupsLastMember = this.getLastMember(prevRootGroupID)
+ const nestLevel = prevGroupsLastMember? prevGroupsLastMember.nestLevel +1 : 1;
+ currentY = currentY + this.spacing*(nestLevel);
+ }
+ this.groupYs.push(currentY);
+ currentY = this.calculateYs(group.memberIDs, currentY, this.spacing);
+
+ if (group.memberIDs.length===0) {
+ currentY = currentY + this.spacing;
+ }
+ prevRootGroupID = groupID;
+ }
+ }
+
+ // Create background Rectangle for each group
+ for (let groupID of this.groupIDs) {
+ const group = this.Groups[groupID];
+ this.createGroup(group);
+ }
+
+ // Create all the leaves
+ for (let leafID of this.leafIDs) {
+ this.createLeaf(this.Leafs[leafID]);
+ }
+ // Draw all leaves and set mouse event handlers
+ this.paperLeaves.forEach((leaf)=> {
+ leaf.draw();
+ leaf.setMouseEventHandlers();
+ });
+
+ // Show filter
+ this.showFilter();
+
+ // Draw other visualizations
+ this.drawTackets();
+ this.drawSewing();
+
+ this.groupContainer.addChild(this.groupGroups);
+ this.groupContainer.addChild(this.groupLeaves);
+ this.groupContainer.addChild(this.groupTacket);
+ this.groupContainer.addChild(this.groupTacketGuide);
+ // Reposition the drawing
+ this.groupContainer.position.y += 10;
+ this.fitCanvas();
+ },
+ activateTacketTool: function(groupID, type="tacketed") {
+ // Remove existing tacket
+ this.groupTacket.removeChildren();
+ this.groupTacketGuide.removeChildren();
+ this.groupTacketGuideLine.removeChildren();
+
+ this.tacketToolIsActive = true;
+ if (this.tool) this.tool.remove();
+ this.tool = new paper.Tool();
+ this.tool.minDistance=5;
+ let targets = [];
+
+ // Remove hover cursor effect on groups and leaves
+ this.paperGroups.forEach((group)=>group.removeMouseEventHandlers());
+ this.paperLeaves.forEach((leaf)=>leaf.removeMouseEventHandlers());
+ document.body.style.cursor = "crosshair";
+
+ this.drawTacketGuide(groupID, type);
+
+ this.tool.onMouseDown = (event) => {
+ this.tacketLineDrag = new paper.Path();
+ this.tacketLineDrag.strokeColor = this.strokeColorTacket;
+ this.tacketLineDrag.strokeWidth = 5;
+ this.tacketLineDrag.add(event.point);
+ this.groupTacketGuide.addChild(this.tacketLineDrag);
+ }
+ this.tool.onMouseUp = (event) => {
+ // Remove line from canvas
+ this.groupTacketGuide.removeChildren();
+ // Reset colour of leaves
+ this.paperLeaves.forEach((leaf)=> {
+ leaf.deactivate();
+ });
+ this.toggleVisualizationDrawing({type: type, value: ""});
+ if (targets.length>0) {
+ let targetLeaf1 = targets[0];
+ let targetLeaf2 = targets[targets.length/2];
+ this.addVisualization(targetLeaf1.leaf.parentID, type, [targetLeaf1.leaf.id, targetLeaf2.leaf.id]);
+
+ } else {
+ // Redraw old visualization
+ if (type==="tacketed") {
+ this.drawTackets();
+ } else {
+ this.drawSewing();
+ }
+ }
+ }
+ this.tool.onMouseDrag = (event) => {
+ // Update line
+ if (!this.tacketLineDrag.segments[1]) {
+ this.tacketLineDrag.add(event.point);
+ } else {
+ this.tacketLineDrag.segments[1].point = event.point;
+ }
+ targets = [];
+ // Highlight leaves that intersect the line
+ const targetGroup = this.paperGroups.find((member)=>{return (member.group.id===groupID)});
+ targetGroup.group.memberIDs.forEach((memberID)=> {
+ if (memberID.charAt(0)==="L") {
+ const leaf = this.getLeaf(this.leafIDs.indexOf(this.Leafs[memberID].id)+1);
+ if (leaf.isConjoined() && (this.tacketLineDrag.getIntersections(leaf.path).length>0 ||
+ this.tacketLineDrag.getIntersections(leaf.conjoinedLeaf().path).length>0)) {
+ leaf.path.strokeColor = "#ffffff";
+ leaf.conjoinedLeaf().path.strokeColor = "#ffffff";
+ // Add leaf to list of targets to tacket
+ targets.push(leaf);
+ } else {
+ leaf.deactivate();
+ }
+ }
+
+ });
+ }
+ this.tool.activate();
+ },
+ drawTacketGuide: function(groupID, type) {
+ const direction = this.Groups[groupID].direction
+ const dirMultiplier = (!direction || direction === "left-to-right") ? 1 : -1;
+ const targetGroup = this.paperGroups.find((member)=>{return (member.group.id===groupID)});
+ const guideY = targetGroup.path.bounds.height/2;
+ const guideX = (!direction || direction === "left-to-right") ? targetGroup.path.bounds.left : targetGroup.path.bounds.right;
+ let guideLine = new paper.Path();
+ guideLine.strokeColor = "#ffffff";
+ guideLine.strokeWidth = 5;
+ guideLine.dashArray = [10,10];
+ guideLine.add(new paper.Point(guideX, targetGroup.path.bounds.y + guideY+ (this.strokeWidth/2)));
+ guideLine.add(new paper.Point(guideX+dirMultiplier*targetGroup.path.bounds.width/3, targetGroup.path.bounds.y + guideY+(this.strokeWidth/2)));
+ let guideLineArrow = new paper.Path();
+ guideLineArrow.strokeColor = "#ffffff";
+ guideLineArrow.strokeWidth = 3;
+ guideLineArrow.add(guideLine.segments[1].point.x-dirMultiplier*10, guideLine.segments[1].point.y-10);
+ guideLineArrow.add(guideLine.segments[1].point.x, guideLine.segments[1].point.y);
+ guideLineArrow.add(guideLine.segments[1].point.x-dirMultiplier*10, guideLine.segments[1].point.y+10);
+
+ let guideLineX1 = new paper.Path();
+ guideLineX1.strokeColor = "#ffffff";
+ guideLineX1.strokeWidth = 3;
+ guideLineX1.add(new paper.Point(guideX-dirMultiplier*10, guideLine.segments[0].point.y-10));
+ guideLineX1.add(new paper.Point(guideX+dirMultiplier*10, guideLine.segments[0].point.y+10));
+
+ let guideLineX2 = new paper.Path();
+ guideLineX2.strokeColor = "#ffffff";
+ guideLineX2.strokeWidth = 3;
+ guideLineX2.add(new paper.Point(guideX-dirMultiplier*10, guideLine.segments[0].point.y+10));
+ guideLineX2.add(new paper.Point(guideX+dirMultiplier*10, guideLine.segments[0].point.y-10));
+
+ const drawType = type==="tacketed"? "TACKET" : "SEWING";
+ let guideText = new paper.PointText({
+ content: "DRAW " + drawType + " LINE",
+ fillColor: "#000000",
+ fontSize: 12,
+ });
+ const guideTextX = (!direction || direction === "left-to-right") ? guideX+20 : guideX-guideText.bounds.width-20;
+ guideText.set({point: [guideTextX, targetGroup.path.bounds.y+guideY-20],})
+
+ const guideTextRectangleX = (!direction || direction === "left-to-right") ? guideX+15 : guideX-guideText.bounds.width-35;
+ const guideTextRectangleWidth = (!direction || direction === "left-to-right") ? guideText.bounds.width+10 : guideText.bounds.width+30;
+
+ let guideTextRectangle = new paper.Rectangle(
+ new paper.Point(guideTextRectangleX, targetGroup.path.bounds.y+guideY-35),
+ new paper.Size(guideTextRectangleWidth, guideText.bounds.height+5)
+ );
+ let guideTextBackground = new paper.Path.Rectangle(guideTextRectangle);
+ guideTextBackground.fillColor = "rgba(255,255,255,0.75)";
+ this.groupTacketGuideLine.addChild(guideLine);
+ this.groupTacketGuideLine.addChild(guideLineArrow);
+ this.tacketToolOriginalPosition = this.groupTacketGuideLine.position.x;
+ this.groupTacketGuide.addChild(this.groupTacketGuideLine);
+ this.groupTacketGuide.addChild(guideTextBackground);
+ this.groupTacketGuide.addChild(guideText);
+ this.groupTacketGuide.addChild(guideLineX1);
+ this.groupTacketGuide.addChild(guideLineX2);
+ },
+ deactivateTacketTool: function() {
+ this.tacketToolIsActive = false;
+ this.groupTacketGuide.removeChildren();
+ if (this.tool) {
+ this.tool.remove();
+ }
+ document.body.style.cursor = "default";
+ this.paperGroups.forEach((group)=>group.setMouseEventHandlers());
+ this.paperLeaves.forEach((leaf)=>leaf.setMouseEventHandlers());
+ },
+ drawSewing: function() {
+ this.paperGroups.forEach((group)=> {
+ const dirMultiplier = (!group.direction || group.direction === "left-to-right") ? 1 : -1
+ if (group.group.sewing!==null && group.group.sewing.length>0) {
+ const leafID1 = group.group.sewing[0];
+ const leafID2 = group.group.sewing.length>1? group.group.sewing[1] : undefined;
+
+ if (leafID1!==undefined && this.leafIDs.indexOf(leafID1)>=0) {
+ let startX, startY, endX, endY;
+ let paperLeaf1, paperLeaf2;
+
+ paperLeaf1 = this.getLeaf(this.leafIDs.indexOf(leafID1)+1);
+ if (leafID2!==undefined) {
+ paperLeaf2 = this.getLeaf(this.leafIDs.indexOf(leafID2)+1);
+ startX = paperLeaf1.path.segments[0].point.x-dirMultiplier*this.strokeWidth;
+ startY = paperLeaf2.path.segments[0].point.y;
+ endX = paperLeaf2.path.segments[0].point.x;
+ } else {
+ startX = 15;
+ startY = paperLeaf1.path.segments[0].point.y;
+ endX = paperLeaf1.path.segments[0].point.x;
+ }
+ if (group.group.tacketed!==null && group.group.tacketed.length>0) {
+ startY -= this.spacing*0.15;
+ }
+ endY = startY;
+ let sewingPath = new paper.Path();
+ sewingPath.name = "tacket1";
+ sewingPath.strokeColor = this.strokeColorTacket;
+ sewingPath.strokeWidth = 3;
+ sewingPath.add(new paper.Point(startX, startY));
+ sewingPath.add(new paper.Point(endX+dirMultiplier*this.strokeWidth, endY));
+ const that = this;
+ // Add listeners
+ sewingPath.onClick = function(event) {
+ that.handleObjectClick(group.group, event);
+ }
+ sewingPath.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ sewingPath.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ this.groupTacket.addChild(sewingPath);
+ }
+ }
+ });
+ },
+ drawTackets: function() {
+ this.paperGroups.forEach((group)=> {
+ const dirMultiplier = (!group.direction || group.direction === "left-to-right") ? 1 : -1
+ if (group.group.tacketed!==null && group.group.tacketed.length>0) {
+ const leafID1 = group.group.tacketed[0];
+ const leafID2 =group.group.tacketed[1];
+ if (leafID1!==undefined && this.leafIDs.indexOf(leafID1)>=0) {
+ let startX, startY, endX, endY;
+ let paperLeaf1, paperLeaf2;
+
+ paperLeaf1 = this.getLeaf(this.leafIDs.indexOf(leafID1)+1);
+ if (leafID2!==undefined) {
+ paperLeaf2 = this.getLeaf(this.leafIDs.indexOf(leafID2)+1);
+ startX = paperLeaf1.path.segments[0].point.x-dirMultiplier*this.strokeWidth;
+ startY = paperLeaf2.path.segments[0].point.y;
+ endX = paperLeaf2.path.segments[0].point.x;
+ } else {
+ startX = 15;
+ startY = paperLeaf1.path.segments[0].point.y;
+ endX = paperLeaf1.path.segments[0].point.x;
+ }
+ if (group.group.tacketed!==null && group.group.tacketed.length>0) {
+ startY -= this.spacing*0.2;
+ }
+ if (group.group.sewing!==null && group.group.sewing.length>0) {
+ startY += this.spacing*0.25;
+ }
+ endY = startY;
+ let tacketPath1 = new paper.Path();
+ tacketPath1.name = "tacket1";
+ tacketPath1.strokeColor = this.strokeColorTacket;
+ tacketPath1.strokeWidth = 3;
+ tacketPath1.add(new paper.Point(startX, startY-2));
+ tacketPath1.add(new paper.Point(endX+dirMultiplier*this.strokeWidth, endY-2));
+ tacketPath1.add(new paper.Point(tacketPath1.segments[1].point.x+dirMultiplier*5, tacketPath1.segments[1].point.y-3));
+ let tacketPath2 = new paper.Path();
+ tacketPath2.name = "tacket2";
+ tacketPath2.strokeColor = this.strokeColorTacket;
+ tacketPath2.strokeWidth = 3;
+ tacketPath2.add(new paper.Point(startX, startY+2));
+ tacketPath2.add(new paper.Point(endX+dirMultiplier*this.strokeWidth, endY+2));
+ tacketPath2.add(new paper.Point(tacketPath2.segments[1].point.x+dirMultiplier*5, tacketPath2.segments[1].point.y+3));
+ const that = this;
+ // Add listeners
+ tacketPath1.onClick = function(event) {
+ that.handleObjectClick(group.group, event);
+ }
+ tacketPath2.onClick = function(event) {
+ that.handleObjectClick(group.group, event);
+ }
+ tacketPath1.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ tacketPath1.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ tacketPath2.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ tacketPath2.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ this.groupTacket.addChild(tacketPath1);
+ this.groupTacket.addChild(tacketPath2);
+ }
+ }
+ });
+ },
+ getYOfFirstMember: function(groupID) {
+ let group = this.Groups[groupID];
+ if (group.memberIDs.length===0) {
+ let y = this.groupYs[this.groupIDs.indexOf(group.id)];
+ return y;
+ }
+ let firstMemberID = group.memberIDs[0];
+ let firstMember = this[firstMemberID.split("_")[0]+"s"][firstMemberID];
+ if (firstMemberID.memberType==="Group") {
+ return this.getYOfFirstMember(firstMemberID);
+ } else {
+ let firstLeafY = this.leafYs[this.leafIDs.indexOf(firstMember.id)];
+ return firstLeafY;
+ }
+ },
+ getYOfLastMember: function(groupID) {
+ const group = this.Groups[groupID];
+ const lastMember = this.getLastMember(groupID);
+ if (lastMember && lastMember.memberType==="Group") {
+ let y = this.groupYs[this.groupIDs.indexOf(lastMember.id)];
+ return y+((lastMember.nestLevel-group.nestLevel)*this.spacing);
+ } else if (lastMember && lastMember.memberType==="Leaf") {
+ let lastLeafY = this.leafYs[this.leafIDs.indexOf(lastMember.id)] + this.strokeWidth + this.spacing/2.0;
+ return lastLeafY+((lastMember.nestLevel-group.nestLevel-1)*this.spacing);
+ } else {
+ return 0;
+ }
+ },
+ getLastMember: function(groupID) {
+ let lastMemberIDs = this.Groups[groupID].memberIDs;
+ if (lastMemberIDs.length===0) return null;
+ let lastMemberID = lastMemberIDs[lastMemberIDs.length-1];
+ if (lastMemberID.charAt(0)==="L") {
+ return this.Leafs[lastMemberID];
+ } else {
+ let lastMember = this.Groups[lastMemberID];
+ // Check if this group has members
+ let innerLastMember = this.getLastMember(lastMemberID);
+ if (innerLastMember) lastMember = innerLastMember;
+ return lastMember;
+ }
+ },
+ getGroupHeight: function(group) {
+ if (group.memberIDs.length>0) {
+ let height = this.getYOfLastMember(group.id) - this.groupYs[this.groupIDs.indexOf(group.id)];
+ return height+this.spacing;
+ } else {
+ return this.spacing;
+ }
+ },
+ numLeaves: function() {
+ return this.paperLeaves.length;
+ },
+ getLeaf: function(leaf_order) {
+ return this.paperLeaves[leaf_order-1];
+ },
+ getLastLeaf: function() {
+ return this.paperLeaves[this.numLeaves()-1];
+ },
+ calculateYs: function(members, currentY, spacing) {
+ if (members.length<1) {
+ return currentY;
+ }
+ let multiplier = 1;
+ if (members.length>70) {
+ multiplier = 0.5;
+ } else if (members.length>45) {
+ multiplier = 0.6;
+ } else if (members.length>35 || this.viewingMode) {
+ multiplier = 0.8;
+ }
+ members.forEach((memberID, i)=> {
+ let memberObject = this[memberID.split("_")[0]+"s"][memberID];
+ let notesToShowAbove = memberObject.notes.filter((noteID)=>{return this.Notes[noteID].show}).length;
+ let notesToShowBelow = 0;
+ let glueSpacing = 0;
+ if (memberObject.memberType==="Leaf") {
+ // Find if it has side notes
+ notesToShowAbove += this.Rectos[memberObject.rectoID].notes.filter((noteID)=>{return this.Notes[noteID].show}).length;
+ notesToShowBelow += this.Versos[memberObject.versoID].notes.filter((noteID)=>{return this.Notes[noteID].show}).length;
+ // Find if leaf has glue that's not a partial glue
+ glueSpacing = (notesToShowAbove>0 && memberObject.attached_above.includes("Glued") && !memberObject.attached_above.includes("Partial"))? 1 : 0;
+ }
+
+ if (memberObject.memberType === "Leaf" && getMemberOrder(memberObject, this.Groups, this.groupIDs)===1 && notesToShowAbove>0) {
+ // First leaf in the group with a note
+ this.multipliers[this.leafIDs.indexOf(memberObject.id)+1] = multiplier;
+ currentY = currentY + spacing*(notesToShowAbove+1);
+ this.leafYs.push(currentY);
+ currentY = currentY + spacing*notesToShowBelow*0.8;
+ if (i===(members.length-1)) {
+ // Last member of group
+ currentY = currentY + (memberObject.nestLevel)*spacing;
+ }
+ } else if (memberObject.memberType==="Leaf" && this.leafIDs.indexOf(memberObject.id)+1 > 0) {
+ this.multipliers[this.leafIDs.indexOf(memberObject.id)+1] = multiplier;
+ currentY = currentY + spacing*(Math.max(1,notesToShowAbove)) + spacing*glueSpacing;
+ if (i > 0 && members[i-1].includes("Group") && this.Groups[members[i-1]].memberIDs.length) {
+ // Previous sibling is a group with children
+ // Find difference of nest level between current leaf and previous group's last member
+ const previousMember = this.getLastMember(members[i-1]);
+ currentY = currentY + (previousMember.nestLevel - memberObject.nestLevel)*spacing;
+ }
+ this.leafYs.push(currentY);
+ currentY = currentY + spacing*notesToShowBelow*0.8;
+ } else if (memberObject.memberType==="Group") {
+ currentY = currentY + spacing;
+ if (i > 0 && members[i-1].includes("Group") && this.Groups[members[i-1]].memberIDs.length>0) {
+ // Previous sibling is a group with children
+ const previousMember = this.getLastMember(members[i-1]);
+ currentY = currentY + (previousMember.nestLevel - memberObject.nestLevel + 1)*spacing;
+ } else if (i > 0 && members[i-1].includes("Group")&& this.Groups[members[i-1]].memberIDs.length===0) {
+ // Previous sibling is a group without children
+ currentY = currentY + spacing;
+ }
+ this.groupYs.push(currentY);
+
+ // Recursify!!!
+ currentY = this.calculateYs(memberObject.memberIDs, currentY, spacing);
+ }
+ });
+ return currentY;
+ },
+ fitCanvas: function() {
+ // Resize canvas so that nothing is cut off
+ this.canvas.height = this.groupGroups.bounds.bottom+10;
+ },
+ setWidth: function(value) {
+ this.width = value;
+ },
+ setProject: function(project) {
+ this.groupIDs = project.groupIDs;
+ this.leafIDs = project.leafIDs;
+ this.Groups = project.Groups;
+ this.Leafs = project.Leafs;
+ this.Rectos = project.Rectos;
+ this.Versos = project.Versos;
+ this.Notes = project.Notes;
+ },
+ setActiveGroups: function(value) {
+ this.activeGroups = value;
+ if (this.paperGroups.length>0) {
+ this.paperGroups.forEach((group)=>{
+ group.deactivate();
+ });
+ this.paperGroups.forEach((paperGroup)=> {
+ if (this.activeGroups.includes(paperGroup.group.id)) {
+ paperGroup.activate();
+ }
+ });
+ }
+ },
+ setActiveLeafs: function(value) {
+ this.activeLeafs = value;
+ if (this.paperLeaves.length>0) {
+ this.paperLeaves.forEach((leaf)=>{
+ leaf.deactivate();
+ });
+ this.paperLeaves.forEach((paperLeaf)=> {
+ if (this.activeLeafs.includes(paperLeaf.leaf.id)) {
+ paperLeaf.activate();
+ }
+ });
+ }
+ },
+ setActiveRectos: function(value) {
+ this.activeRectos = value;
+ if (this.paperLeaves.length>0) {
+ this.paperLeaves.forEach((leaf)=>{
+ leaf.deactivate();
+ });
+ this.paperLeaves.forEach((paperLeaf)=> {
+ if (this.activeRectos.includes(paperLeaf.leaf.rectoID)) {
+ paperLeaf.activate();
+ }
+ });
+ }
+ },
+ setActiveVersos: function(value) {
+ this.activeVersos = value;
+ if (this.paperLeaves.length>0) {
+ this.paperLeaves.forEach((leaf)=>{
+ leaf.deactivate();
+ });
+ this.paperLeaves.forEach((paperLeaf)=> {
+ if (this.activeVersos.includes(paperLeaf.leaf.versoID)) {
+ paperLeaf.activate();
+ }
+ });
+ }
+ },
+ setFlashItems: function(value) {
+ this.flashItems = value;
+ },
+ setFilter: function(filters) {
+ this.filters = filters;
+ this.showFilter();
+ },
+ showFilter: function() {
+ this.paperLeaves.forEach((leaf)=>{
+ leaf.filterHighlight.opacity = 0;
+ if (this.filters.Leafs.includes(leaf.leaf.id)
+ || this.filters.Sides.includes(leaf.leaf.rectoID)
+ || this.filters.Sides.includes(leaf.leaf.versoID)) {
+ leaf.filterHighlight.opacity = 1;
+ }
+ });
+ this.paperGroups.forEach((group)=>{
+ group.filterHighlight.opacity = 0;
+ if (this.filters.Groups.includes(group.group.id)) {
+ group.filterHighlight.opacity = 1;
+ }
+ });
+ },
+ setVisibility: function(visibleAttributes) {
+ this.visibleAttributes = visibleAttributes;
+ this.paperGroups.forEach((group)=>group.setVisibility(visibleAttributes.group));
+ this.paperLeaves.forEach((leaf)=>leaf.setVisibility(visibleAttributes));
+ },
+ setScale: function(spacing, strokeWidth) {
+ this.spacing = this.width*spacing;
+ this.strokeWidth = this.width*strokeWidth;
+ },
+}
+function PaperManager(args) {
+ this.canvas = document.getElementById(args.canvasID);
+ paper.setup(this.canvas);
+ this.tool = null;
+ this.groupIDs = args.groupIDs;
+ this.leafIDs = args.leafIDs;
+ this.Groups = args.Groups;
+ this.Leafs = args.Leafs;
+ this.Rectos = args.Rectos;
+ this.Versos = args.Versos;
+ this.Notes = args.Notes;
+ this.origin = args.origin;
+ this.width = paper.view.viewSize.width;
+ this.spacing = this.width*args.spacing;
+ this.strokeWidth = this.width*args.strokeWidth;
+ this.strokeColor = args.strokeColor;
+ this.strokeColorActive = args.strokeColorActive;
+ this.strokeColorAdded = args.strokeColorAdded;
+ this.strokeColorGroupActive = args.strokeColorGroupActive;
+ this.strokeColorTacket = args.strokeColorTacket;
+ this.groupColor = args.groupColor;
+ this.groupColorActive = args.groupColorActive;
+ this.groupTextColor = args.groupTextColor;
+ this.handleObjectClick = args.handleObjectClick;
+ this.groupLeaves = new paper.Group();// Groups of leaf paths
+ this.groupGroups = new paper.Group();// Group of group paths
+ this.groupContainer = new paper.Group();
+ this.activeGroups = args.activeGroups;
+ this.activeLeafs = args.activeLeafs;
+ this.activeRectos = args.activeRectos;
+ this.activeVersos = args.activeVersos;
+ this.paperLeaves = [];
+ this.paperGroups = [];
+ this.leafYs = [];
+ this.groupYs = [];
+ this.multipliers = {};
+ this.flashItems = args.flashItems;
+ this.flashLeaves = [];
+ this.flashGroups = [];
+ this.filters = args.filters;
+ this.strokeColorFilter = args.strokeColorFilter;
+ this.visibleAttributes = args.visibleAttributes;
+ this.viewingMode = args.viewingMode;
+ this.tacketLineDrag = new paper.Path();
+ this.groupTacketGuide = new paper.Group();
+ this.groupTacketGuideLine = new paper.Group();
+ this.groupTacket = new paper.Group();
+ this.toggleVisualizationDrawing = args.toggleVisualizationDrawing;
+ this.addVisualization = args.addVisualization;
+ this.tacketToolIsActive = false;
+ this.tacketToolOriginalPosition = 0;
+ this.slideForward = true;
+ this.openNoteDialog = args.openNoteDialog;
+ this.leafIDs = args.leafIDs;
+
+ let that = this;
+ // Flash newly added items
+ paper.view.onFrame = function(event) {
+ for (let i=0; i that.tacketToolOriginalPosition+25) {
+ that.slideForward = false;
+ }
+ } else {
+ that.groupTacketGuideLine.position.x -= 0.5;
+ if (that.groupTacketGuideLine.position.x < that.tacketToolOriginalPosition) {
+ that.slideForward = true;
+ }
+ }
+ }
+ }
+}
+export default PaperManager;
diff --git a/viscoll-app/src/assets/visualMode/export/PaperGroup.js b/viscoll-app/src/assets/visualMode/export/PaperGroup.js
new file mode 100644
index 00000000..bea8153e
--- /dev/null
+++ b/viscoll-app/src/assets/visualMode/export/PaperGroup.js
@@ -0,0 +1,129 @@
+import paper from 'paper';
+
+PaperGroup.prototype = {
+ constructor: PaperGroup,
+ draw: function() {
+ // Create rectangle shapes
+ const rightIndent = (this.direction === "left-to-right") ? 0 : (this.spacing*(this.nestLevel-1));
+ let rectangle = new paper.Rectangle(
+ new paper.Point(this.x+10, this.y),
+ new paper.Size(this.width-this.x-rightIndent-20, this.groupHeight)
+ );
+ if (this.viewingMode) {
+ rectangle = new paper.Rectangle(
+ new paper.Point(this.x+10, this.y),
+ new paper.Size(this.width-rightIndent-this.x, this.groupHeight)
+ );
+ }
+ let highlightRectangle = rectangle.clone();
+ highlightRectangle.set(
+ new paper.Point(this.x, this.y-10),
+ new paper.Size(this.width-rightIndent-this.x, this.groupHeight+20)
+ );
+
+ // Create path from rectangle
+ this.path = new paper.Path.Rectangle(rectangle);
+ if (this.isActive) {
+ this.path.fillColor = this.groupColorActive;
+ } else {
+ this.path.fillColor = this.groupColor;
+ }
+ if (this.group.nestLevel%2===0) {
+ this.path.fillColor.brightness -= 0.05;
+ }
+ this.path.name = "group " + this.groupOrder;
+
+ // Create highlight path from rectangle
+ this.highlight = new paper.Path.Rectangle(highlightRectangle);
+ this.highlight.fillColor = new paper.Color(112/255.0, 229/255.0, 220/255.0, 1);
+ this.highlight.opacity = 0;
+ this.highlight.name = "group " + this.groupOrder + " highlight";
+ this.highlight.insertBelow(this.path);
+
+ this.filterHighlight = new paper.Path.Rectangle(highlightRectangle);
+ this.filterHighlight.fillColor = this.filterColor;
+ this.filterHighlight.opacity = 0;
+ this.filterHighlight.insertBelow(this.path);
+
+ // Set highlight path to be at the back
+ paper.project.activeLayer.insertChild(0, this.highlight);
+ paper.project.activeLayer.insertChild(0, this.filterHighlight);
+ },
+ setMouseEventHandlers: function() {
+ // Set mouse event handlers
+ let that = this;
+ this.path.onClick = function(event) {
+ that.handleObjectClick(that.group, event);
+ };
+ this.path.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ this.path.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ },
+ removeMouseEventHandlers: function() {
+ this.path.onClick = function(event) {};
+ this.path.onMouseEnter = function(event) {};
+ this.path.onMouseLeave = function(event) {};
+ },
+ activate: function() {
+ this.path.fillColor = this.groupColorActive;
+ this.isActive = true;
+ this.highlight.opacity = 0.75;
+ this.highlight.fillColor = "#ffffff";
+ },
+ deactivate: function() {
+ this.path.fillColor = this.groupColor;
+ if (this.group.nestLevel%2===0) {
+ this.path.fillColor.brightness -= 0.05;
+ }
+ this.isActive = false;
+ this.highlight.opacity = 0;
+ this.highlight.fillColor = new paper.Color(112/255.0, 229/255.0, 220/255.0, 1);
+ },
+ setVisibility: function(visibleAttributes) {
+ this.visibleAttributes = visibleAttributes;
+ let groupText = this.group.type + " " + this.groupOrder;
+ if (this.visibleAttributes && this.visibleAttributes.title) groupText = groupText + ": " + this.group.title;
+ this.text.set({
+ content: groupText,
+ });
+ if(this.direction === "right-to-left"){
+ this.text.set({
+ point: [this.width-this.text.bounds.width-this.text.point.x-(this.spacing*(this.nestLevel-1)), this.text.point.y],
+ })
+ }
+ },
+}
+// Constructor for group
+function PaperGroup(args) {
+ this.manager = args.manager;
+ this.group = args.group;
+ this.groupOrder = args.allGroupIDs.indexOf(args.group.id)+1
+ this.direction = args.direction;
+ this.y = args.y;
+ this.x = args.x;
+ this.nestLevel = args.nestLevel;
+ this.spacing = args.spacing;
+ this.width = args.width;
+ this.groupHeight = args.groupHeight;
+ this.isActive = args.isActive;
+ this.highlight = new paper.Path();
+ this.filterHighlight = new paper.Path();
+ this.path = new paper.Path();
+ this.groupColor = args.groupColor;
+ this.groupColorActive = args.groupColorActive;
+ this.textColor = args.textColor;
+ this.filterColor = args.filterColor;
+ this.handleObjectClick = args.handleObjectClick;
+ this.visibleAttributes = {};
+ this.viewingMode = args.viewingMode;
+ this.text = new paper.PointText({
+ point: [this.x+args.spacing*0.6, this.y+args.spacing*0.6],
+ fillColor: this.textColor,
+ fontSize: args.spacing*0.48,
+ });
+ this.setVisibility(args.visibleAttributes);
+}
+export default PaperGroup;
diff --git a/viscoll-app/src/assets/visualMode/export/PaperLeaf.js b/viscoll-app/src/assets/visualMode/export/PaperLeaf.js
new file mode 100644
index 00000000..6542ee1b
--- /dev/null
+++ b/viscoll-app/src/assets/visualMode/export/PaperLeaf.js
@@ -0,0 +1,643 @@
+import paper from 'paper';
+PaperLeaf.prototype = {
+ constructor: PaperLeaf,
+ draw: function() {
+ // Call this function only after ALL leaves have been instantiated
+ // This is because we need all leaves present in order
+ // to compute their indentations relative to each other
+ this.setIndent();
+ // Draw horizontal part
+ let x1, x2
+
+ if(!this.groupDirection || this.groupDirection === "left-to-right"){
+ x1 = this.multiplier<1? 10 + this.indent*this.spacing*this.multiplier : this.indent*this.spacing*this.multiplier;
+ x2 = this.width;
+ } else {
+ x1 = this.groupWidth - (this.multiplier < 1 ? 10 + this.indent*this.spacing*this.multiplier : this.indent*this.spacing*this.multiplier);
+ x2 = this.groupWidth - this.width;
+ this.oldPointX = x2;
+ }
+ this.dirMultiplier = (!this.groupDirection || this.groupDirection === "left-to-right") ? 1 : -1;
+
+ this.path.add(new paper.Point(x1, this.y));
+ if (this.leaf.stub !== "None" && (!this.groupDirection || this.groupDirection === "left-to-right")) {
+ x2 = this.width*0.15+x1;
+ } else if (this.leaf.stub !== "None") {
+ x2 = (x1-this.width*0.15)-x2;
+ }
+
+ this.path.add(new paper.Point(x2, this.y));
+ // Draw vertical part
+ if (this.isConjoined()) {
+ var conjoinY=this.y_conjoin_center(this.conjoined_to);
+ this.path.insert(0, new paper.Point(this.path.segments[0].point.x, conjoinY));
+ }
+ this.curveMe();
+
+ this.path.name = "leaf " + this.order;
+
+ // Create highlight path
+ this.highlight = this.path.clone();
+ this.highlight.dashArray = [0, 0];
+ this.highlight.segments[this.highlight.segments.length-1].point.x = this.highlight.segments[this.highlight.segments.length-1].point.x + 5;
+ if (this.leaf.conjoined_to === "None") {
+ this.highlight.segments[0].point.x = this.highlight.segments[0].point.x - 5;
+ }
+ this.highlight.strokeColor = this.strokeColorActive;
+ this.highlight.strokeWidth = this.path.strokeWidth*2;
+ this.highlight.opacity = 0;
+ this.highlight.name = "leaf " + this.order + " highlight";
+ this.highlight.insertBelow(this.path);
+
+ if (this.isActive) {
+ this.highlight.opacity = 0.35;
+ this.highlight.strokeWidth = this.path.strokeWidth*2;
+ this.highlight.strokeColor = "#ffffff";
+ }
+
+ this.filterHighlight = this.path.clone();
+ this.filterHighlight.dashArray = [0, 0];
+ this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x = this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x + 5;
+ if (this.leaf.conjoined_to === "None") {
+ this.filterHighlight.segments[0].point.x = this.filterHighlight.segments[0].point.x - 5;
+ }
+ this.filterHighlight.strokeColor = this.strokeColorFilter;
+ this.filterHighlight.strokeWidth = this.path.strokeWidth*2;
+ this.filterHighlight.opacity = 0;
+ this.filterHighlight.insertBelow(this.path);
+
+ // this.path.fullySelected = true;
+ this.showAttributes();
+ this.createAttachments();
+
+ const leafNotesToShow = this.leaf.notes.filter((noteID)=>{return this.Notes[noteID].show}).reverse();
+ const rectoNotesToShow = this.recto.notes.filter((noteID)=>{return this.Notes[noteID].show}).reverse();
+ const versoNotesToShow = this.verso.notes.filter((noteID)=>{return this.Notes[noteID].show});
+
+ let textX = 0;
+ let textY = this.y;
+ let fontSize = this.spacing*0.50;
+ let numChars = this.path.bounds.width/fontSize*2.4;
+
+ if (this.isConjoined()) {
+ // This leaf is conjoined
+ textX = this.path.segments[1].point.x;
+ } else {
+ // Separate leaf
+ textX = this.path.segments[0].point.x+this.dirMultiplier*10;
+ }
+ if (this.leaf.attached_above.includes("Partial")) {
+ // This leaf has a partial glue attachment
+ // Place text to the right of attachment drawing
+ if(!this.groupDirection || this.groupDirection === "left-to-right"){
+ textX = this.attachment.bounds.right+5;
+ } else {
+ textX = this.attachment.bounds.left - 5;
+ }
+ } else if (this.leaf.attached_above.includes("Glued")) {
+ // Other type of glueing exists
+ if(!this.groupDirection || this.groupDirection === "left-to-right"){
+ textY -= this.spacing*0.7;
+ } else {
+ textY += this.spacing*0.7;
+ }
+ }
+ let that = this;
+ let clickListener = function(note) {
+ return function(event) {
+ that.openNoteDialog(note);
+ }
+ }
+ if (this.showNotes) {
+ // Draw recto note text
+ for (let noteIndex = 0; noteIndex < rectoNotesToShow.length; noteIndex++) {
+ const note = this.Notes[rectoNotesToShow[noteIndex]];
+ const noteTitle = this.recto.folio_number? "â–¼ " + this.recto.folio_number + " : " + note.title.substr(0,numChars) : "â–¼ R : " + note.title.substr(0,numChars) ;
+ let textNote = new paper.PointText({
+ content: noteTitle,
+ point: [textX, textY - noteIndex*(this.spacing*0.7) - this.spacing*0.3],
+ fillColor: this.strokeColor,
+ fontSize: fontSize,
+ });
+ const textPointX = (!this.groupDirection || this.groupDirection === "left-to-right") ? textX : textX-textNote.bounds.width;
+ textNote.set({point: [textPointX, textY - noteIndex * (this.spacing * 0.7) - this.spacing * 0.3]});
+ textNote.onClick = !this.viewingMode && clickListener(note);
+ this.textNotes.addChild(textNote);
+ }
+ // Draw leaf note text
+ for (let noteIndex = 0; noteIndex < leafNotesToShow.length; noteIndex++) {
+ const note = this.Notes[leafNotesToShow[noteIndex]];
+
+ let textNote = new paper.PointText({
+ content: "â–¼ L" + this.order + " : " + note.title.substr(0,numChars),
+ fillColor: this.strokeColor,
+ fontSize: fontSize,
+ });
+ const textPointX = (!this.groupDirection || this.groupDirection === "left-to-right") ? textX : textX - textNote.bounds.width;
+ textNote.set({point: [textPointX, textY - rectoNotesToShow.length*(this.spacing*0.7) - noteIndex*(this.spacing*0.7) - this.spacing*0.3]});
+ textNote.onClick = !this.viewingMode && clickListener(note);
+ this.textNotes.addChild(textNote);
+ }
+ // Draw verso note text
+ for (let noteIndex = 0; noteIndex < versoNotesToShow.length; noteIndex++) {
+ const note = this.Notes[versoNotesToShow[noteIndex]];
+ const noteTitle = this.verso.folio_number? "â–² " + this.verso.folio_number + " : " + note.title.substr(0,numChars) : "â–² V : " + note.title.substr(0,numChars);
+ let textNote = new paper.PointText({
+ content: noteTitle,
+ point: [textX, this.y + noteIndex*(this.spacing*0.7) + this.spacing*0.8],
+ fillColor: this.strokeColor,
+ fontSize: fontSize,
+ });
+ const textPointX = (!this.groupDirection || this.groupDirection === "left-to-right") ? textX : textX-textNote.bounds.width;
+ textNote.set({point: [textPointX, this.y + noteIndex*(this.spacing*0.7) + this.spacing*0.8]})
+ textNote.onClick = !this.viewingMode && clickListener(note);
+ this.textNotes.addChild(textNote);
+ }
+ this.textNotes.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ this.textNotes.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ }
+
+
+ return this;
+ },
+ setMouseEventHandlers: function() {
+ // Set mouse event handlers
+ let that = this;
+ this.path.onClick = function(event) {
+ that.handleObjectClick(that.leaf, event);
+ };
+ this.textLeafOrder.onClick = function(event) {
+ that.handleObjectClick(that.leaf, event);
+ };
+ this.textRecto.onClick = function(event) {
+ that.handleObjectClick(that.leaf, event);
+ };
+ this.textVerso.onClick = function(event) {
+ that.handleObjectClick(that.leaf, event);
+ };
+ this.path.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ this.path.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ this.textLeafOrder.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ this.textLeafOrder.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ this.textRecto.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ this.textRecto.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ this.textVerso.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ this.textVerso.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ },
+ removeMouseEventHandlers: function() {
+ // Set mouse event handlers
+ this.path.onClick = function(event) {};
+ this.textLeafOrder.onClick = function(event) {};
+ this.textRecto.onClick = function(event) {};
+ this.textVerso.onClick = function(event) {};
+ this.path.onMouseEnter = function(event) {};
+ this.path.onMouseLeave = function(event) {};
+ this.textLeafOrder.onMouseEnter = function(event) {};
+ this.textLeafOrder.onMouseLeave = function(event) {};
+ this.textRecto.onMouseEnter = function(event) {};
+ this.textRecto.onMouseLeave = function(event) {};
+ this.textVerso.onMouseEnter = function(event) {};
+ this.textVerso.onMouseLeave = function(event) {};
+ },
+ createAttachments: function() {
+ if (this.order>1 && this.leaf.attached_above.includes("Glued")) {
+ this.createGlue();
+ } else if (this.order>1 && this.leaf.attached_above==="Other") {
+ this.createOtherAttachment();
+ }
+ if (this.order>1 && this.leaf.conjoin_type==="Sewn") {
+ this.createSewn();
+ }
+ },
+ createGlue: function() {
+ let x = this.path.segments[0].point.x;
+ if (this.isConjoined() && this.conjoined_to (this.path.segments[this.path.segments.length-1].point.x-10)) {
+ let glueLine = new paper.Path();
+ glueLine.add(new paper.Point(x, this.y-this.spacing*0.3));
+ glueLine.add(new paper.Point(x-10, this.y-this.spacing*0.7));
+ glueLine.strokeColor = "#707070";
+ glueLine.strokeWidth = 2;
+ this.attachment.addChild(glueLine);
+ x -= 5;
+ }
+ } else {
+ // Complete glue
+ while (x <= (this.path.segments[this.path.segments.length-1].point.x-10)) {
+ let glueLine = new paper.Path();
+ glueLine.add(new paper.Point(x, this.y-this.spacing*0.3));
+ glueLine.add(new paper.Point(x+10, this.y-this.spacing*0.7));
+ glueLine.strokeColor = "#707070";
+ glueLine.strokeWidth = 2;
+ this.attachment.addChild(glueLine);
+ x += 5;
+ }
+ }
+ },
+ // Sewing of conjoined leaves
+ createSewn: function() {
+ if (this.isConjoined() && this.conjoined_to0;
+ },
+ conjoinedLeaf: function() {
+ return this.manager.getLeaf(this.conjoined_to);
+ },
+ y_conjoin_center: function(conjoin_order) {
+ var y1 = this.path.segments[1].point.y;
+ var y2 = (this.manager.getLeaf(conjoin_order)).getY();
+ return y1+((y2-y1)/2.0);
+ },
+ y_nonconjoin_center: function() {
+ var y = this.y
+ var y_center = this.y_centerQuire();
+ if (y===y_center) {
+ return y_center;
+ } else if (y= this.manager.numLeaves()) {
+ return 0;
+ } else {
+ return this.manager.getLeaf(this.order+1).y;
+ }
+ },
+ curveMe: function() {
+ var path_height = Math.abs(this.path.segments[0].point.y - this.path.segments[1].point.y);
+ var midpoint = this.path.segments[1].point;
+ if (this.isConjoined() ) {
+ var numLeavesInside = Math.abs(this.conjoined_to - this.order);
+ // Remove the middle point and insert a new one with handles
+ this.path.removeSegment(1);
+ // // Calculate new point's location and radius
+ var new_x = midpoint.x + this.dirMultiplier*20;
+ var radius = new_x - this.path.segments[0].point.x;
+ var s1 = new paper.Segment(new paper.Point(new_x, midpoint.y), new paper.Point(-radius,0), null);
+ this.path.insert(1, s1);
+
+ if (numLeavesInside > 5) {
+ // Modify first point's handle so that the curve isn't too curvy
+ var direction = this.path.segments[0].point.y > this.path.segments[1].point.y? -1 : 1;
+ var oldPoint = this.path.segments[0].point;
+ this.path.removeSegment(0);
+ var s0 = new paper.Segment(new paper.Point(oldPoint.x, oldPoint.y), null, new paper.Point(0,direction*(path_height)));
+ this.path.insert(0, s0);
+ }
+ }
+ },
+ setIndent: function() {
+ this.indent = this.leaf.nestLevel;
+ if (this.isConjoined() && this.conjoinedLeaf().indent != null) {
+ // Leaf is conjoined and conjoiner has indent, so copy that conjoiner's indent
+ this.indent = this.conjoinedLeaf().indent;
+ } else if (this.isBelowAConjoined()) {
+ this.indent = (this.prevPaperLeaf().indent+1);
+ } else if (this.localOrder>1 && this.parentOrder === this.prevPaperLeaf().parentOrder){
+ // Leaf is a sibling of previous leaf, so copy sibling's indent
+ this.indent = this.prevPaperLeaf().indent;
+ }
+ },
+ isBelowAConjoined: function() {
+ let previousLeafID = this.allLeafIDs[this.allLeafIDs.indexOf(this.leaf.id)-1];
+ if (!previousLeafID) return false;
+ let previousLeaf = this.Leafs[previousLeafID];
+ return (this.order>1 && previousLeaf.conjoined_to!==null && (this.allLeafIDs.indexOf(previousLeaf.conjoined_to)+1)>this.order)
+ },
+ y_centerQuire: function () {
+ var last_leaf = this.manager.getLastLeaf().getY();
+ return (last_leaf+ this.spacing)/2.0 ;
+ },
+ getY: function() {
+ return this.y;
+ },
+ activate: function() {
+ this.path.strokeColor = this.strokeColorActive;
+ this.highlight.opacity = 0.35;
+ this.highlight.strokeWidth = this.path.strokeWidth*2;
+ this.highlight.strokeColor = "#ffffff";
+ },
+ deactivate: function() {
+ this.highlight.opacity = 0;
+ this.highlight.strokeWidth = this.path.strokeWidth;
+ this.highlight.strokeColor = this.strokeColorActive;
+
+ if (this.leaf.type==="Added") {
+ this.path.strokeColor = this.strokeColorAdded;
+ } else if (this.leaf.type==="Endleaf") {
+ this.path.strokeColor = "#727272";
+ } else {
+ this.path.strokeColor = this.strokeColor;
+ }
+ },
+ setVisibility: function(visibleAttributes) {
+ this.visibleAttributes = visibleAttributes;
+ this.showAttributes();
+ },
+ showAttributes: function() {
+ const rectoValues = [];
+ const versoValues = [];
+ if (this.visibleAttributes.side) {
+ if (this.visibleAttributes.side.folio_number) {
+ if (this.recto.folio_number) rectoValues.push(this.recto.folio_number)
+ if (this.verso.folio_number) versoValues.push(this.verso.folio_number)
+ }
+ if (this.visibleAttributes.side.page_number) {
+ if (this.recto.page_number) rectoValues.push(this.recto.page_number)
+ if (this.verso.page_number) versoValues.push(this.verso.page_number)
+ }
+ if (this.visibleAttributes.side.texture) {
+ rectoValues.push(this.recto.texture)
+ versoValues.push(this.verso.texture)
+ }
+ }
+ let rectoContent = "";
+ let versoContent = "";
+ for (let i=0; i {
+ if (this.visibleAttributes.side[key]) return acc+1;
+ return acc;
+ }
+ const visibleAttributeCount = this.visibleAttributes.side? Object.keys(this.visibleAttributes.side).reduce(reducer,0) : 0;
+
+ if (visibleAttributeCount===3) {
+ const newPointX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*3 : this.oldPointX + this.spacing * 3;
+ const newHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*2.8 : this.oldPointX + this.spacing * 2.8;
+ const newfilterHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*2.8 : this.oldPointX + this.spacing * 2.8;
+ const textLeafOrderX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*2.8 : this.oldPointX - this.textLeafOrder.bounds.width + this.spacing * 2.8;
+
+ if (this.leaf.stub === "None") {
+ // Reduce leaf width so we can fit attribute text
+ this.path.segments[this.path.segments.length-1].point.x = newPointX;
+ this.highlight.segments[this.highlight.segments.length-1].point.x = newHighlightX;
+ this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x = newfilterHighlightX;
+ }
+ this.textLeafOrder.set({point: [textLeafOrderX, this.textLeafOrder.point.y]});
+ this.textRecto.set({content: rectoContent});
+ this.textVerso.set({content: versoContent});
+
+ const textRectoX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.textLeafOrder.bounds.right+this.spacing*0.4 : this.textLeafOrder.bounds.left - this.textRecto.bounds.width - this.spacing * 0.4;
+ const textVersoX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.textLeafOrder.bounds.right+this.spacing*0.4 :this.textLeafOrder.bounds.left - this.textVerso.bounds.width - this.spacing * 0.4;
+
+ this.textRecto.set({ point: [textRectoX, this.textRecto.point.y], });
+ this.textVerso.set({ point: [textVersoX, this.textVerso.point.y], });
+ } else if (visibleAttributeCount===2) {
+ const newPointX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*2 : this.oldPointX + this.spacing * 2;
+ const newHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*1.8 : this.oldPointX + this.spacing * 1.8;
+ const newfilterHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*1.8 : this.oldPointX + this.spacing * 1.8;
+ const textLeafOrderX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*1.8 : this.oldPointX- this.textLeafOrder.bounds.width + this.spacing * 1.8;
+
+ if (this.leaf.stub === "None") {
+ // Reduce leaf width so we can fit attribute text
+ this.path.segments[this.path.segments.length-1].point.x = newPointX;
+ this.highlight.segments[this.highlight.segments.length-1].point.x = newHighlightX;
+ this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x = newfilterHighlightX;
+ }
+ this.textLeafOrder.set({point: [textLeafOrderX, this.textLeafOrder.point.y]});
+ this.textRecto.set({content: rectoContent});
+ this.textVerso.set({content: versoContent});
+
+ const textRectoX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.textLeafOrder.bounds.right+this.spacing*0.2 : this.textLeafOrder.bounds.left - this.textRecto.bounds.width - this.spacing * 0.2;
+ const textVersoX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.textLeafOrder.bounds.right+this.spacing*0.2 : this.textLeafOrder.bounds.left - this.textVerso.bounds.width - this.spacing * 0.2;
+
+ this.textRecto.set({ point: [textRectoX, this.textRecto.point.y], });
+ this.textVerso.set({ point: [textVersoX, this.textVerso.point.y], });
+ }
+ else if (visibleAttributeCount===1) {
+ const newPointX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing : this.oldPointX + this.spacing;
+ const newHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*0.8 : this.oldPointX + this.spacing * 0.8;
+ const newfilterHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*0.8 : this.oldPointX + this.spacing * 0.8;
+ const textLeafOrderX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width-this.spacing*0.8 : this.oldPointX- this.textLeafOrder.bounds.width + this.spacing * 0.8;
+
+ if (this.leaf.stub === "None") {
+ // Reduce leaf width so we can fit folio number text
+ this.path.segments[this.path.segments.length-1].point.x = newPointX;
+ this.highlight.segments[this.highlight.segments.length-1].point.x = newHighlightX;
+ this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x = newfilterHighlightX;
+ }
+ this.textLeafOrder.set({point: [textLeafOrderX, this.textLeafOrder.point.y]});
+ this.textRecto.set({content: rectoContent});
+ this.textVerso.set({content: versoContent});
+
+ const textRectoX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.textLeafOrder.bounds.right+this.spacing*0.2 : this.textLeafOrder.bounds.left - this.textRecto.bounds.width - this.spacing * 0.2;
+ const textVersoX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.textLeafOrder.bounds.right+this.spacing*0.2 : this.textLeafOrder.bounds.left - this.textVerso.bounds.width - this.spacing * 0.2;
+
+ this.textRecto.set({ point: [textRectoX, this.textRecto.point.y], });
+ this.textVerso.set({ point: [textVersoX, this.textVerso.point.y], });
+ } else {
+ const newPointX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width : this.oldPointX;
+ const newHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width + 5 : this.oldPointX - 5;
+ const newfilterHighlightX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width + 5 : this.oldPointX - 5;
+ const textLeafOrderX = (!this.groupDirection || this.groupDirection === "left-to-right") ? this.width+10 : this.oldPointX - this.textLeafOrder.bounds.width - 5;
+
+ // Reset leaf
+ if (this.leaf.stub === "None") {
+ this.path.segments[this.path.segments.length-1].point.x = newPointX;
+ this.highlight.segments[this.highlight.segments.length-1].point.x = newHighlightX;
+ this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x = newfilterHighlightX;
+ }
+ this.textLeafOrder.set({point: [textLeafOrderX, this.textLeafOrder.point.y]});
+ }
+ },
+}
+// Constructor for leaf
+function PaperLeaf(args) {
+ this.manager = args.manager;
+ this.Notes = args.Notes;
+ this.leafIDs = args.leafIDs;
+ this.allLeafIDs = args.allLeafIDs;
+ this.Leafs = args.Leafs;
+ this.leaf = args.leaf;
+ this.recto = args.recto;
+ this.verso = args.verso;
+ this.order = args.allLeafIDs.indexOf(args.leaf.id) + 1;
+ this.localOrder = args.leafIDs.indexOf(args.leaf.id) + 1;
+ this.parentOrder = args.groupIDs.indexOf(this.leaf.parentID)+1;
+ this.conjoined_to = this.leaf.conjoined_to===null ? "None" : this.leafIDs.indexOf(this.leaf.conjoined_to)+1;
+ this.indent = null;
+ this.origin = args.origin;
+ this.viewingMode = args.viewingMode;
+ this.width = this.viewingMode? args.width*0.88 : args.width*0.92;
+ this.groupDirection = args.Groups[this.leaf.parentID].direction;
+ this.groupWidth = args.width;
+ this.spacing = args.spacing;
+ this.y = args.y;
+ this.strokeWidth = args.strokeWidth;
+ this.strokeColor = args.strokeColor;
+ this.strokeColorActive = args.strokeColorActive;
+ this.strokeColorGroupActive = args.strokeColorGroupActive;
+ this.strokeColorFilter = args.strokeColorFilter;
+ this.strokeColorAdded = args.strokeColorAdded;
+ this.highlight = new paper.Path();
+ this.filterHighlight = new paper.Path();
+ this.path = new paper.Path();
+ this.isActive = args.isActive;
+ this.handleObjectClick = args.handleObjectClick;
+ this.multiplier = args.multiplier;
+ this.attachment = new paper.Group();
+ this.textNotes = new paper.Group();
+ this.openNoteDialog = args.openNoteDialog;
+ this.showNotes = args.showNotes;
+
+ this.textLeafOrder = new paper.PointText({
+ point: [this.width, this.y+5],
+ content: "L"+ this.order,
+ fillColor: this.strokeColor,
+ fontSize: this.spacing*0.48,
+ });
+ this.textRecto = new paper.PointText({
+ point: [this.width+this.spacing, this.y-3],
+ fillColor: this.strokeColor,
+ fontSize: this.spacing*0.37,
+ });
+ this.textVerso = new paper.PointText({
+ point: [this.width+this.spacing, this.y+this.spacing*0.37-3],
+ fillColor: this.strokeColor,
+ fontSize: this.spacing*0.37,
+ });
+
+ this.visibleAttributes = args.visibleAttributes;
+ // Set path properties
+ if (this.leaf.type==="Added") {
+ this.path.strokeColor = args.strokeColorAdded;
+ } else if (this.leaf.type==="Endleaf") {
+ this.path.strokeColor = "#919191";
+ } else {
+ this.path.strokeColor = args.strokeColor;
+ }
+ if (this.leaf.type==='Missing') {
+ // If leaf is missing, make stroke dashed
+ this.path.dashArray = [20, 10];
+ }
+ if (this.leaf.type==='Replaced') {
+ this.path.strokeColor = "#9d6464";
+ }
+ if (this.isActive) {
+ this.path.strokeColor = args.strokeColorActive;
+ }
+ this.path.strokeWidth = this.strokeWidth;
+ if (this.leaf.material === "Parchment") {
+ this.path.strokeWidth = this.path.strokeWidth*1.20;
+ } else if (this.leaf.material === "Paper") {
+ this.path.strokeWidth = this.path.strokeWidth*0.80;
+ }
+}
+
+
+export default PaperLeaf;
diff --git a/viscoll-app/src/assets/visualMode/export/PaperManager.js b/viscoll-app/src/assets/visualMode/export/PaperManager.js
new file mode 100644
index 00000000..e7193e0e
--- /dev/null
+++ b/viscoll-app/src/assets/visualMode/export/PaperManager.js
@@ -0,0 +1,721 @@
+import paper from 'paper';
+import PaperLeaf from "./PaperLeaf.js";
+import PaperGroup from "./PaperGroup.js";
+import { getMemberOrder } from '../../../helpers/getMemberOrder';
+
+PaperManager.prototype = {
+ constructor: PaperManager,
+ createGroup: function(group) {
+ let g = new PaperGroup({
+ manager: this,
+ group: group,
+ allGroupIDs: this.allGroupIDs,
+ direction: group.direction,
+ y: this.groupYs[this.groupIDs.indexOf(group.id)],
+ x: (group.direction === "left-to-right") ? (group.nestLevel-1)*(this.spacing) : 0,
+ width: this.width,
+ groupHeight: this.getGroupHeight(group),
+ isActive: this.activeGroups.includes(group.id),
+ groupColor: this.groupColor,
+ groupColorActive: this.groupColorActive,
+ filterColor: this.strokeColorFilter,
+ handleObjectClick: this.handleObjectClick,
+ textColor: this.groupTextColor,
+ visibleAttributes: this.visibleAttributes.group,
+ viewingMode: this.viewingMode,
+ spacing: this.spacing,
+ nestLevel: group.nestLevel,
+ });
+ g.draw();
+ g.setMouseEventHandlers();
+
+ // Add this group to collections
+ this.groupGroups.addChild(g.filterHighlight);
+ this.groupGroups.addChild(g.highlight);
+ this.groupGroups.addChild(g.path);
+ this.groupGroups.addChild(g.text);
+ this.paperGroups.push(g);
+
+ // Add this group to list of items to flash if it's in the flashItems list
+ if (this.flashItems.groups.includes(group.id)) {
+ this.flashGroups.push(g);
+ }
+
+ },
+ createLeaf: function(leaf) {
+ let l = new PaperLeaf({
+ manager: this,
+ origin: this.origin,
+ width: this.width,
+ spacing: this.spacing,
+ strokeWidth: this.strokeWidth * Math.min(1.0, this.multipliers[this.leafIDs.indexOf(leaf.id)+1]),
+ strokeColor: this.strokeColor,
+ strokeColorActive: this.strokeColorActive,
+ strokeColorGroupActive: this.strokeColorGroupActive,
+ strokeColorAdded: this.strokeColorAdded,
+ leaf: leaf,
+ recto: this.Rectos[leaf.rectoID],
+ verso: this.Versos[leaf.versoID],
+ groupIDs: this.groupIDs,
+ leafIDs: this.leafIDs,
+ allLeafIDs: this.allLeafIDs,
+ Groups: this.Groups,
+ Leafs: this.Leafs,
+ Notes: this.Notes,
+ y: this.leafYs[this.leafIDs.indexOf(leaf.id)],
+ isActive: this.activeLeafs.includes(leaf.id) || this.activeRectos.includes(leaf.rectoID) || this.activeVersos.includes(leaf.versoID),
+ customSpacings: this.customSpacings,
+ handleObjectClick: this.handleObjectClick,
+ multiplier: this.multipliers[this.leafIDs.indexOf(leaf.id)+1],
+ strokeColorFilter: this.strokeColorFilter,
+ visibleAttributes: this.visibleAttributes,
+ viewingMode: this.viewingMode,
+ openNoteDialog: this.openNoteDialog,
+ showNotes: this.showNotes,
+ });
+ this.paperLeaves.push(l);
+ this.groupLeaves.addChild(l.path);
+ this.groupLeaves.addChild(l.textLeafOrder);
+ this.groupLeaves.addChild(l.textRecto);
+ this.groupLeaves.addChild(l.textVerso);
+ this.groupLeaves.addChild(l.attachment);
+ this.groupLeaves.addChild(l.textNotes);
+ if (this.flashItems.leaves.includes(leaf.id)) {
+ this.flashLeaves.push(l);
+ }
+ return l;
+ },
+ draw: function() {
+ // Clear existing drawn elements
+ this.leafYs = [];
+ this.groupYs = [];
+ this.paperLeaves = [];
+ this.paperGroups = [];
+ this.flashGroups = [];
+ this.flashLeaves = [];
+ this.groupLeaves.removeChildren();
+ this.groupGroups.removeChildren();
+ this.groupContainer.removeChildren();
+ this.groupTacket.removeChildren();
+
+ // Calculate y positions of groups and leaves
+ let currentY = 0;
+ let prevRootGroupID = null;
+ for (let i in this.groupIDs) {
+ const groupID = this.groupIDs[i];
+ const group = this.Groups[groupID];
+ if (group.nestLevel === 1) {
+ currentY = 0;
+ if (i>0 && prevRootGroupID) {
+ const prevGroupsLastMember = this.getLastMember(prevRootGroupID)
+ const nestLevel = prevGroupsLastMember? prevGroupsLastMember.nestLevel +1 : 1;
+ currentY = currentY + this.spacing*(nestLevel);
+ }
+ this.groupYs.push(currentY);
+ currentY = this.calculateYs(group.memberIDs, currentY, this.spacing);
+
+ if (group.memberIDs.length===0) {
+ currentY = currentY + this.spacing;
+ }
+ prevRootGroupID = groupID;
+ }
+ }
+
+ // Create background Rectangle for each group
+ for (let groupID of this.groupIDs) {
+ const group = this.Groups[groupID];
+ this.createGroup(group);
+ }
+
+ // Create all the leaves
+ for (let leafID of this.leafIDs) {
+ this.createLeaf(this.Leafs[leafID]);
+ }
+ // Draw all leaves and set mouse event handlers
+ this.paperLeaves.forEach((leaf)=> {
+ leaf.draw();
+ leaf.setMouseEventHandlers();
+ });
+
+ // Show filter
+ this.showFilter();
+
+ // Draw other visualizations
+ this.drawTackets();
+ this.drawSewing();
+
+ this.groupContainer.addChild(this.groupGroups);
+ this.groupContainer.addChild(this.groupLeaves);
+ this.groupContainer.addChild(this.groupTacket);
+ this.groupContainer.addChild(this.groupTacketGuide);
+ // Reposition the drawing
+ this.groupContainer.position.y += 10;
+ this.fitCanvas();
+ },
+ activateTacketTool: function(groupID, type="tacketed") {
+ // Remove existing tacket
+ this.groupTacket.removeChildren();
+ this.groupTacketGuide.removeChildren();
+ this.groupTacketGuideLine.removeChildren();
+
+ this.tacketToolIsActive = true;
+ if (this.tool) this.tool.remove();
+ this.tool = new paper.Tool();
+ this.tool.minDistance=5;
+ let targets = [];
+
+ // Remove hover cursor effect on groups and leaves
+ this.paperGroups.forEach((group)=>group.removeMouseEventHandlers());
+ this.paperLeaves.forEach((leaf)=>leaf.removeMouseEventHandlers());
+ document.body.style.cursor = "crosshair";
+
+ this.drawTacketGuide(groupID, type);
+
+ this.tool.onMouseDown = (event) => {
+ this.tacketLineDrag = new paper.Path();
+ this.tacketLineDrag.strokeColor = this.strokeColorTacket;
+ this.tacketLineDrag.strokeWidth = 5;
+ this.tacketLineDrag.add(event.point);
+ this.groupTacketGuide.addChild(this.tacketLineDrag);
+ }
+ this.tool.onMouseUp = (event) => {
+ // Remove line from canvas
+ this.groupTacketGuide.removeChildren();
+ // Reset colour of leaves
+ this.paperLeaves.forEach((leaf)=> {
+ leaf.deactivate();
+ });
+ this.toggleVisualizationDrawing({type: type, value: ""});
+ if (targets.length>0) {
+ let targetLeaf1 = targets[0];
+ let targetLeaf2 = targets[targets.length/2];
+ this.addVisualization(targetLeaf1.leaf.parentID, type, [targetLeaf1.leaf.id, targetLeaf2.leaf.id]);
+
+ } else {
+ // Redraw old visualization
+ if (type==="tacketed") {
+ this.drawTackets();
+ } else {
+ this.drawSewing();
+ }
+ }
+ }
+ this.tool.onMouseDrag = (event) => {
+ // Update line
+ if (!this.tacketLineDrag.segments[1]) {
+ this.tacketLineDrag.add(event.point);
+ } else {
+ this.tacketLineDrag.segments[1].point = event.point;
+ }
+ targets = [];
+ // Highlight leaves that intersect the line
+ const targetGroup = this.paperGroups.find((member)=>{return (member.group.id===groupID)});
+ targetGroup.group.memberIDs.forEach((memberID)=> {
+ if (memberID.charAt(0)==="L") {
+ const leaf = this.getLeaf(this.leafIDs.indexOf(this.Leafs[memberID].id)+1);
+ if (leaf.isConjoined() && (this.tacketLineDrag.getIntersections(leaf.path).length>0 ||
+ this.tacketLineDrag.getIntersections(leaf.conjoinedLeaf().path).length>0)) {
+ leaf.path.strokeColor = "#ffffff";
+ leaf.conjoinedLeaf().path.strokeColor = "#ffffff";
+ // Add leaf to list of targets to tacket
+ targets.push(leaf);
+ } else {
+ leaf.deactivate();
+ }
+ }
+
+ });
+ }
+ this.tool.activate();
+ },
+ drawTacketGuide: function(groupID, type) {
+ const direction = this.Groups[groupID].direction
+ const dirMultiplier = (!direction || direction === "left-to-right") ? 1 : -1;
+ const targetGroup = this.paperGroups.find((member)=>{return (member.group.id===groupID)});
+ const guideY = targetGroup.path.bounds.height/2;
+ const guideX = (!direction || direction === "left-to-right") ? targetGroup.path.bounds.left : targetGroup.path.bounds.right;
+ let guideLine = new paper.Path();
+ guideLine.strokeColor = "#ffffff";
+ guideLine.strokeWidth = 5;
+ guideLine.dashArray = [10,10];
+ guideLine.add(new paper.Point(guideX, targetGroup.path.bounds.y + guideY+ (this.strokeWidth/2)));
+ guideLine.add(new paper.Point(guideX+dirMultiplier*targetGroup.path.bounds.width/3, targetGroup.path.bounds.y + guideY+(this.strokeWidth/2)));
+ let guideLineArrow = new paper.Path();
+ guideLineArrow.strokeColor = "#ffffff";
+ guideLineArrow.strokeWidth = 3;
+ guideLineArrow.add(guideLine.segments[1].point.x-dirMultiplier*10, guideLine.segments[1].point.y-10);
+ guideLineArrow.add(guideLine.segments[1].point.x, guideLine.segments[1].point.y);
+ guideLineArrow.add(guideLine.segments[1].point.x-dirMultiplier*10, guideLine.segments[1].point.y+10);
+
+ let guideLineX1 = new paper.Path();
+ guideLineX1.strokeColor = "#ffffff";
+ guideLineX1.strokeWidth = 3;
+ guideLineX1.add(new paper.Point(guideX-dirMultiplier*10, guideLine.segments[0].point.y-10));
+ guideLineX1.add(new paper.Point(guideX+dirMultiplier*10, guideLine.segments[0].point.y+10));
+
+ let guideLineX2 = new paper.Path();
+ guideLineX2.strokeColor = "#ffffff";
+ guideLineX2.strokeWidth = 3;
+ guideLineX2.add(new paper.Point(guideX-dirMultiplier*10, guideLine.segments[0].point.y+10));
+ guideLineX2.add(new paper.Point(guideX+dirMultiplier*10, guideLine.segments[0].point.y-10));
+
+ const drawType = type==="tacketed"? "TACKET" : "SEWING";
+ let guideText = new paper.PointText({
+ content: "DRAW " + drawType + " LINE",
+ fillColor: "#000000",
+ fontSize: 12,
+ });
+ const guideTextX = (!direction || direction === "left-to-right") ? guideX+20 : guideX-guideText.bounds.width-20;
+ guideText.set({point: [guideTextX, targetGroup.path.bounds.y+guideY-20],})
+
+ const guideTextRectangleX = (!direction || direction === "left-to-right") ? guideX+15 : guideX-guideText.bounds.width-35;
+ const guideTextRectangleWidth = (!direction || direction === "left-to-right") ? guideText.bounds.width+10 : guideText.bounds.width+30;
+
+ let guideTextRectangle = new paper.Rectangle(
+ new paper.Point(guideTextRectangleX, targetGroup.path.bounds.y+guideY-35),
+ new paper.Size(guideTextRectangleWidth, guideText.bounds.height+5)
+ );
+ let guideTextBackground = new paper.Path.Rectangle(guideTextRectangle);
+ guideTextBackground.fillColor = "rgba(255,255,255,0.75)";
+ this.groupTacketGuideLine.addChild(guideLine);
+ this.groupTacketGuideLine.addChild(guideLineArrow);
+ this.tacketToolOriginalPosition = this.groupTacketGuideLine.position.x;
+ this.groupTacketGuide.addChild(this.groupTacketGuideLine);
+ this.groupTacketGuide.addChild(guideTextBackground);
+ this.groupTacketGuide.addChild(guideText);
+ this.groupTacketGuide.addChild(guideLineX1);
+ this.groupTacketGuide.addChild(guideLineX2);
+ },
+ deactivateTacketTool: function() {
+ this.tacketToolIsActive = false;
+ this.groupTacketGuide.removeChildren();
+ if (this.tool) {
+ this.tool.remove();
+ }
+ document.body.style.cursor = "default";
+ this.paperGroups.forEach((group)=>group.setMouseEventHandlers());
+ this.paperLeaves.forEach((leaf)=>leaf.setMouseEventHandlers());
+ },
+ drawSewing: function() {
+ this.paperGroups.forEach((group)=> {
+ const dirMultiplier = (!group.direction || group.direction === "left-to-right") ? 1 : -1
+ if (group.group.sewing!==null && group.group.sewing.length>0) {
+ const leafID1 = group.group.sewing[0];
+ const leafID2 = group.group.sewing.length>1? group.group.sewing[1] : undefined;
+
+ if (leafID1!==undefined && this.leafIDs.indexOf(leafID1)>=0) {
+ let startX, startY, endX, endY;
+ let paperLeaf1, paperLeaf2;
+
+ paperLeaf1 = this.getLeaf(this.leafIDs.indexOf(leafID1)+1);
+ if (leafID2!==undefined) {
+ paperLeaf2 = this.getLeaf(this.leafIDs.indexOf(leafID2)+1);
+ startX = paperLeaf1.path.segments[0].point.x-dirMultiplier*this.strokeWidth;
+ startY = paperLeaf2.path.segments[0].point.y;
+ endX = paperLeaf2.path.segments[0].point.x;
+ } else {
+ startX = 15;
+ startY = paperLeaf1.path.segments[0].point.y;
+ endX = paperLeaf1.path.segments[0].point.x;
+ }
+ if (group.group.tacketed!==null && group.group.tacketed.length>0) {
+ startY -= this.spacing*0.15;
+ }
+ endY = startY;
+ let sewingPath = new paper.Path();
+ sewingPath.name = "tacket1";
+ sewingPath.strokeColor = this.strokeColorTacket;
+ sewingPath.strokeWidth = 3;
+ sewingPath.add(new paper.Point(startX, startY));
+ sewingPath.add(new paper.Point(endX+dirMultiplier*this.strokeWidth, endY));
+ const that = this;
+ // Add listeners
+ sewingPath.onClick = function(event) {
+ that.handleObjectClick(group.group, event);
+ }
+ sewingPath.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ sewingPath.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ this.groupTacket.addChild(sewingPath);
+ }
+ }
+ });
+ },
+ drawTackets: function() {
+ this.paperGroups.forEach((group)=> {
+ const dirMultiplier = (!group.direction || group.direction === "left-to-right") ? 1 : -1
+ if (group.group.tacketed!==null && group.group.tacketed.length>0) {
+ const leafID1 = group.group.tacketed[0];
+ const leafID2 =group.group.tacketed[1];
+ if (leafID1!==undefined && this.leafIDs.indexOf(leafID1)>=0) {
+ let startX, startY, endX, endY;
+ let paperLeaf1, paperLeaf2;
+
+ paperLeaf1 = this.getLeaf(this.leafIDs.indexOf(leafID1)+1);
+ if (leafID2!==undefined) {
+ paperLeaf2 = this.getLeaf(this.leafIDs.indexOf(leafID2)+1);
+ startX = paperLeaf1.path.segments[0].point.x-dirMultiplier*this.strokeWidth;
+ startY = paperLeaf2.path.segments[0].point.y;
+ endX = paperLeaf2.path.segments[0].point.x;
+ } else {
+ startX = 15;
+ startY = paperLeaf1.path.segments[0].point.y;
+ endX = paperLeaf1.path.segments[0].point.x;
+ }
+ if (group.group.tacketed!==null && group.group.tacketed.length>0) {
+ startY -= this.spacing*0.2;
+ }
+ if (group.group.sewing!==null && group.group.sewing.length>0) {
+ startY += this.spacing*0.25;
+ }
+ endY = startY;
+ let tacketPath1 = new paper.Path();
+ tacketPath1.name = "tacket1";
+ tacketPath1.strokeColor = this.strokeColorTacket;
+ tacketPath1.strokeWidth = 3;
+ tacketPath1.add(new paper.Point(startX, startY-2));
+ tacketPath1.add(new paper.Point(endX+dirMultiplier*this.strokeWidth, endY-2));
+ tacketPath1.add(new paper.Point(tacketPath1.segments[1].point.x+dirMultiplier*5, tacketPath1.segments[1].point.y-3));
+ let tacketPath2 = new paper.Path();
+ tacketPath2.name = "tacket2";
+ tacketPath2.strokeColor = this.strokeColorTacket;
+ tacketPath2.strokeWidth = 3;
+ tacketPath2.add(new paper.Point(startX, startY+2));
+ tacketPath2.add(new paper.Point(endX+dirMultiplier*this.strokeWidth, endY+2));
+ tacketPath2.add(new paper.Point(tacketPath2.segments[1].point.x+dirMultiplier*5, tacketPath2.segments[1].point.y+3));
+ const that = this;
+ // Add listeners
+ tacketPath1.onClick = function(event) {
+ that.handleObjectClick(group.group, event);
+ }
+ tacketPath2.onClick = function(event) {
+ that.handleObjectClick(group.group, event);
+ }
+ tacketPath1.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ tacketPath1.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ tacketPath2.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ tacketPath2.onMouseLeave = function(event) {
+ document.body.style.cursor = "default";
+ }
+ this.groupTacket.addChild(tacketPath1);
+ this.groupTacket.addChild(tacketPath2);
+ }
+ }
+ });
+ },
+ getYOfFirstMember: function(groupID) {
+ let group = this.Groups[groupID];
+ if (group.memberIDs.length===0) {
+ let y = this.groupYs[this.groupIDs.indexOf(group.id)];
+ return y;
+ }
+ let firstMemberID = group.memberIDs[0];
+ let firstMember = this[firstMemberID.split("_")[0]+"s"][firstMemberID];
+ if (firstMemberID.memberType==="Group") {
+ return this.getYOfFirstMember(firstMemberID);
+ } else {
+ let firstLeafY = this.leafYs[this.leafIDs.indexOf(firstMember.id)];
+ return firstLeafY;
+ }
+ },
+ getYOfLastMember: function(groupID) {
+ const group = this.Groups[groupID];
+ const lastMember = this.getLastMember(groupID);
+ if (lastMember && lastMember.memberType==="Group") {
+ let y = this.groupYs[this.groupIDs.indexOf(lastMember.id)];
+ return y+((lastMember.nestLevel-group.nestLevel)*this.spacing);
+ } else if (lastMember && lastMember.memberType==="Leaf") {
+ let lastLeafY = this.leafYs[this.leafIDs.indexOf(lastMember.id)] + this.strokeWidth + this.spacing/2.0;
+ return lastLeafY+((lastMember.nestLevel-group.nestLevel-1)*this.spacing);
+ } else {
+ return 0;
+ }
+ },
+ getLastMember: function(groupID) {
+ let lastMemberIDs = this.Groups[groupID].memberIDs;
+ if (lastMemberIDs.length===0) return null;
+ let lastMemberID = lastMemberIDs[lastMemberIDs.length-1];
+ if (lastMemberID.charAt(0)==="L") {
+ return this.Leafs[lastMemberID];
+ } else {
+ let lastMember = this.Groups[lastMemberID];
+ // Check if this group has members
+ let innerLastMember = this.getLastMember(lastMemberID);
+ if (innerLastMember) lastMember = innerLastMember;
+ return lastMember;
+ }
+ },
+ getGroupHeight: function(group) {
+ if (group.memberIDs.length>0) {
+ let height = this.getYOfLastMember(group.id) - this.groupYs[this.groupIDs.indexOf(group.id)];
+ return height+this.spacing;
+ } else {
+ return this.spacing;
+ }
+ },
+ numLeaves: function() {
+ return this.paperLeaves.length;
+ },
+ getLeaf: function(leaf_order) {
+ return this.paperLeaves[leaf_order-1];
+ },
+ getLastLeaf: function() {
+ return this.paperLeaves[this.numLeaves()-1];
+ },
+ calculateYs: function(members, currentY, spacing) {
+ if (members.length<1) {
+ return currentY;
+ }
+ let multiplier = 1;
+ if (members.length>70) {
+ multiplier = 0.5;
+ } else if (members.length>45) {
+ multiplier = 0.6;
+ } else if (members.length>35 || this.viewingMode) {
+ multiplier = 0.8;
+ }
+ members.forEach((memberID, i)=> {
+ let memberObject = this[memberID.split("_")[0]+"s"][memberID];
+ let notesToShowAbove = memberObject.notes.filter((noteID)=>{return this.Notes[noteID].show&&this.showNotes}).length;
+ let notesToShowBelow = 0;
+ let glueSpacing = 0;
+ if (memberObject.memberType==="Leaf") {
+ // Find if it has side notes
+ notesToShowAbove += this.Rectos[memberObject.rectoID].notes.filter((noteID)=>{return this.Notes[noteID].show&&this.showNotes}).length;
+ notesToShowBelow += this.Versos[memberObject.versoID].notes.filter((noteID)=>{return this.Notes[noteID].show&&this.showNotes}).length;
+ // Find if leaf has glue that's not a partial glue
+ glueSpacing = (notesToShowAbove>0 && memberObject.attached_above.includes("Glued") && !memberObject.attached_above.includes("Partial"))? 1 : 0;
+ }
+
+ if (memberObject.memberType === "Leaf" && getMemberOrder(memberObject, this.Groups, this.groupIDs)===1 && notesToShowAbove>0) {
+ // First leaf in the group with a note
+ this.multipliers[this.leafIDs.indexOf(memberObject.id)+1] = multiplier;
+ currentY = currentY + spacing*(notesToShowAbove+1);
+ this.leafYs.push(currentY);
+ currentY = currentY + spacing*notesToShowBelow*0.8;
+ if (i===(members.length-1)) {
+ // Last member of group
+ currentY = currentY + (memberObject.nestLevel)*spacing;
+ }
+ } else if (memberObject.memberType==="Leaf" && this.leafIDs.indexOf(memberObject.id)+1 > 0) {
+ this.multipliers[this.leafIDs.indexOf(memberObject.id)+1] = multiplier;
+ currentY = currentY + spacing*(Math.max(1,notesToShowAbove)) + spacing*glueSpacing;
+ if (i > 0 && members[i-1].includes("Group") && this.Groups[members[i-1]].memberIDs.length) {
+ // Previous sibling is a group with children
+ // Find difference of nest level between current leaf and previous group's last member
+ const previousMember = this.getLastMember(members[i-1]);
+ currentY = currentY + (previousMember.nestLevel - memberObject.nestLevel)*spacing;
+ }
+ this.leafYs.push(currentY);
+ currentY = currentY + spacing*notesToShowBelow*0.8;
+ } else if (memberObject.memberType==="Group") {
+ currentY = currentY + spacing;
+ if (i > 0 && members[i-1].includes("Group") && this.Groups[members[i-1]].memberIDs.length>0) {
+ // Previous sibling is a group with children
+ const previousMember = this.getLastMember(members[i-1]);
+ currentY = currentY + (previousMember.nestLevel - memberObject.nestLevel + 1)*spacing;
+ } else if (i > 0 && members[i-1].includes("Group")&& this.Groups[members[i-1]].memberIDs.length===0) {
+ // Previous sibling is a group without children
+ currentY = currentY + spacing;
+ }
+ this.groupYs.push(currentY);
+
+ // Recursify!!!
+ currentY = this.calculateYs(memberObject.memberIDs, currentY, spacing);
+ }
+ });
+ return currentY;
+ },
+ fitCanvas: function() {
+ // Resize canvas so that nothing is cut off
+ this.canvas.height = this.groupGroups.bounds.bottom+10;
+ },
+ setWidth: function(value) {
+ this.width = value;
+ },
+ setProject: function(project) {
+ this.groupIDs = project.groupIDs;
+ this.leafIDs = project.leafIDs;
+ this.Groups = project.Groups;
+ this.Leafs = project.Leafs;
+ this.Rectos = project.Rectos;
+ this.Versos = project.Versos;
+ this.Notes = project.Notes;
+ },
+ setActiveGroups: function(value) {
+ this.activeGroups = value;
+ if (this.paperGroups.length>0) {
+ this.paperGroups.forEach((group)=>{
+ group.deactivate();
+ });
+ this.paperGroups.forEach((paperGroup)=> {
+ if (this.activeGroups.includes(paperGroup.group.id)) {
+ paperGroup.activate();
+ }
+ });
+ }
+ },
+ setActiveLeafs: function(value) {
+ this.activeLeafs = value;
+ if (this.paperLeaves.length>0) {
+ this.paperLeaves.forEach((leaf)=>{
+ leaf.deactivate();
+ });
+ this.paperLeaves.forEach((paperLeaf)=> {
+ if (this.activeLeafs.includes(paperLeaf.leaf.id)) {
+ paperLeaf.activate();
+ }
+ });
+ }
+ },
+ setActiveRectos: function(value) {
+ this.activeRectos = value;
+ if (this.paperLeaves.length>0) {
+ this.paperLeaves.forEach((leaf)=>{
+ leaf.deactivate();
+ });
+ this.paperLeaves.forEach((paperLeaf)=> {
+ if (this.activeRectos.includes(paperLeaf.leaf.rectoID)) {
+ paperLeaf.activate();
+ }
+ });
+ }
+ },
+ setActiveVersos: function(value) {
+ this.activeVersos = value;
+ if (this.paperLeaves.length>0) {
+ this.paperLeaves.forEach((leaf)=>{
+ leaf.deactivate();
+ });
+ this.paperLeaves.forEach((paperLeaf)=> {
+ if (this.activeVersos.includes(paperLeaf.leaf.versoID)) {
+ paperLeaf.activate();
+ }
+ });
+ }
+ },
+ setFlashItems: function(value) {
+ this.flashItems = value;
+ },
+ setFilter: function(filters) {
+ this.filters = filters;
+ this.showFilter();
+ },
+ showFilter: function() {
+ this.paperLeaves.forEach((leaf)=>{
+ leaf.filterHighlight.opacity = 0;
+ if (this.filters.Leafs.includes(leaf.leaf.id)
+ || this.filters.Sides.includes(leaf.leaf.rectoID)
+ || this.filters.Sides.includes(leaf.leaf.versoID)) {
+ leaf.filterHighlight.opacity = 1;
+ }
+ });
+ this.paperGroups.forEach((group)=>{
+ group.filterHighlight.opacity = 0;
+ if (this.filters.Groups.includes(group.group.id)) {
+ group.filterHighlight.opacity = 1;
+ }
+ });
+ },
+ setVisibility: function(visibleAttributes) {
+ this.visibleAttributes = visibleAttributes;
+ this.paperGroups.forEach((group)=>group.setVisibility(visibleAttributes.group));
+ this.paperLeaves.forEach((leaf)=>leaf.setVisibility(visibleAttributes));
+ },
+ setScale: function(spacing, strokeWidth) {
+ this.spacing = this.width*spacing;
+ this.strokeWidth = this.width*strokeWidth;
+ },
+}
+function PaperManager(args) {
+ this.canvas = document.getElementById(args.canvasID);
+ paper.setup(this.canvas);
+ this.tool = null;
+ this.allGroupIDs = args.allGroupIDs;
+ this.allLeafIDs = args.allLeafIDs;
+ this.groupIDs = args.groupIDs;
+ this.leafIDs = args.leafIDs;
+ this.Groups = args.Groups;
+ this.Leafs = args.Leafs;
+ this.Rectos = args.Rectos;
+ this.Versos = args.Versos;
+ this.Notes = args.Notes;
+ this.origin = args.origin;
+ this.width = paper.view.viewSize.width;
+ this.spacing = this.width*args.spacing;
+ this.strokeWidth = this.width*args.strokeWidth;
+ this.strokeColor = args.strokeColor;
+ this.strokeColorActive = args.strokeColorActive;
+ this.strokeColorAdded = args.strokeColorAdded;
+ this.strokeColorGroupActive = args.strokeColorGroupActive;
+ this.strokeColorTacket = args.strokeColorTacket;
+ this.groupColor = args.groupColor;
+ this.groupColorActive = args.groupColorActive;
+ this.groupTextColor = args.groupTextColor;
+ this.handleObjectClick = args.handleObjectClick;
+ this.groupLeaves = new paper.Group();// Groups of leaf paths
+ this.groupGroups = new paper.Group();// Group of group paths
+ this.groupContainer = new paper.Group();
+ this.activeGroups = args.activeGroups;
+ this.activeLeafs = args.activeLeafs;
+ this.activeRectos = args.activeRectos;
+ this.activeVersos = args.activeVersos;
+ this.paperLeaves = [];
+ this.paperGroups = [];
+ this.leafYs = [];
+ this.groupYs = [];
+ this.multipliers = {};
+ this.flashItems = args.flashItems;
+ this.flashLeaves = [];
+ this.flashGroups = [];
+ this.filters = args.filters;
+ this.strokeColorFilter = args.strokeColorFilter;
+ this.visibleAttributes = args.visibleAttributes;
+ this.viewingMode = args.viewingMode;
+ this.tacketLineDrag = new paper.Path();
+ this.groupTacketGuide = new paper.Group();
+ this.groupTacketGuideLine = new paper.Group();
+ this.groupTacket = new paper.Group();
+ this.toggleVisualizationDrawing = args.toggleVisualizationDrawing;
+ this.addVisualization = args.addVisualization;
+ this.tacketToolIsActive = false;
+ this.tacketToolOriginalPosition = 0;
+ this.slideForward = true;
+ this.openNoteDialog = args.openNoteDialog;
+ this.leafIDs = args.leafIDs;
+ this.showNotes = args.showNotes;
+
+ let that = this;
+ // Flash newly added items
+ paper.view.onFrame = function(event) {
+ for (let i=0; i that.tacketToolOriginalPosition+25) {
+ that.slideForward = false;
+ }
+ } else {
+ that.groupTacketGuideLine.position.x -= 0.5;
+ if (that.groupTacketGuideLine.position.x < that.tacketToolOriginalPosition) {
+ that.slideForward = true;
+ }
+ }
+ }
+ }
+}
+export default PaperManager;
diff --git a/viscoll-app/src/components/authentication/Login.js b/viscoll-app/src/components/authentication/Login.js
new file mode 100644
index 00000000..0807d42c
--- /dev/null
+++ b/viscoll-app/src/components/authentication/Login.js
@@ -0,0 +1,100 @@
+import React, { Component } from 'react';
+import ResendConfirmation from './ResendConfirmation';
+import TextField from 'material-ui/TextField';
+import RaisedButton from 'material-ui/RaisedButton';
+import FlatButton from 'material-ui/FlatButton';
+import { btnLg, btnAuthCancel } from '../../styles/button';
+import {floatFieldDark} from '../../styles/textfield';
+
+/**
+ * Contains the login form that is used by the landing page component called `Landing`.
+ */
+class Login extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ email: "",
+ password: "",
+ error: "",
+ };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.user.errors.login.errorMessage!==undefined) {
+ this.setState({error: nextProps.user.errors.login.errorMessage[0]});
+ }
+ }
+
+ componentDidUpdate() {
+ if (this.props.user.authenticated) {
+ this.props.history.push("/dashboard");
+ }
+ }
+
+ /**
+ * Update state when user inputs new value in a text field
+ */
+ onInputChange(v, type) {
+ this.setState({[type]: v});
+ }
+ /**
+ * Submit login information and clear any message
+ */
+ submit = (e) => {
+ if (e) e.preventDefault();
+ this.setState({ error: ""});
+ this.props.action.loginUser({email: this.state.email, password: this.state.password});
+ }
+
+ /**
+ * Cancel button. Resets local Login state.
+ */
+ cancel = () => {
+ this.setState({email:"",password:"",error:""});
+ this.props.tapCancel();
+ }
+
+ render() {
+ let submitButton =
+ let content = ;
+ if (this.state.error && this.state.error.includes("unconfirmed")) {
+ content =
+ }
+ return (
+ content
+ );
+ }
+}
+
+export default Login;
diff --git a/viscoll-app/src/components/authentication/Register.js b/viscoll-app/src/components/authentication/Register.js
new file mode 100644
index 00000000..1aab0a82
--- /dev/null
+++ b/viscoll-app/src/components/authentication/Register.js
@@ -0,0 +1,115 @@
+import React, { Component } from 'react';
+import TextField from 'material-ui/TextField';
+import RaisedButton from 'material-ui/RaisedButton';
+import FlatButton from 'material-ui/FlatButton';
+import { btnLg, btnAuthCancel } from '../../styles/button';
+import {floatFieldDark} from '../../styles/textfield';
+
+/**
+ * Contains the registration form that is used by the landing page component called `Landing`.
+ */
+class Register extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ name: "",
+ email: "",
+ password: "",
+ };
+ this.submit = this.submit.bind(this);
+ }
+ /**
+ * Update state when user inputs new value in a text field
+ */
+ onInputChange = (v, type) => {
+ this.setState({[type]: v});
+ }
+ /**
+ * Submit registration information
+ */
+ submit = (e) => {
+ if (e) e.preventDefault();
+ this.props.action.registerUser({...this.state});
+ }
+
+ render() {
+ let emailError = "";
+ let passwordError = "";
+ let registerSuccess = false;
+ try {
+ emailError = this.props.userState.errors.register.email;
+ passwordError = this.props.userState.errors.register.password;
+ registerSuccess = this.props.userState.registerSuccess;
+ } catch (e) {}
+
+ let registerForm = (
+
+ );
+
+ const successMessage = (
+
+
+ Registration successful! You will be notified by email once your account has been approved.
+
+
+
this.props.tapCancel()}
+ label="Okay"
+ />
+
+ );
+
+ if (registerSuccess) {
+ return successMessage;
+ } else {
+ return registerForm;
+ }
+ }
+}
+export default Register;
diff --git a/viscoll-app/src/components/authentication/ResendConfirmation.js b/viscoll-app/src/components/authentication/ResendConfirmation.js
new file mode 100644
index 00000000..68bdef57
--- /dev/null
+++ b/viscoll-app/src/components/authentication/ResendConfirmation.js
@@ -0,0 +1,76 @@
+import React, { Component } from 'react';
+import TextField from 'material-ui/TextField';
+import RaisedButton from 'material-ui/RaisedButton';
+import FlatButton from 'material-ui/FlatButton';
+import { btnLg, btnAuthCancel } from '../../styles/button';
+import {floatFieldDark} from '../../styles/textfield';
+
+/**
+ * Resend confirmation form.
+ */
+class ResendConfirmation extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ message: props.message,
+ email: props.email,
+ submitted: false,
+ };
+ }
+
+ /**
+ * Update state when user inputs new value in a text field
+ */
+ onChange(v, type) {
+ this.setState({[type]: v});
+ }
+
+ /**
+ * Send confirmation email
+ */
+ resendConfirmation = () => {
+ this.props.action.resendConfirmation(this.state.email);
+ this.setState({submitted:true, message: "An email confirmation has been resent to " + this.state.email});
+ }
+
+ render() {
+ let form = this.state.submitted? "": ;
+
+ let cancel = this.props.tapCancel()}
+ label="Go back"
+ {...btnAuthCancel}
+ />;
+
+ if (this.state.submitted) {
+ cancel = this.props.tapCancel()}
+ />;
+ }
+
+ return (
+
+
{this.state.message}
+ {form}
+
+ {cancel}
+
+
);
+ }
+}
+export default ResendConfirmation;
\ No newline at end of file
diff --git a/viscoll-app/src/components/authentication/ResetPassword.js b/viscoll-app/src/components/authentication/ResetPassword.js
new file mode 100644
index 00000000..50e09f0a
--- /dev/null
+++ b/viscoll-app/src/components/authentication/ResetPassword.js
@@ -0,0 +1,69 @@
+import React, { Component } from 'react';
+import TextField from 'material-ui/TextField';
+import RaisedButton from 'material-ui/RaisedButton';
+import { btnLg } from '../../styles/button';
+import {floatFieldDark} from '../../styles/textfield';
+/**
+ * Contains the form to update password when user forgets password.
+ */
+class ResetPassword extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ password: "",
+ passwordConfirm: "",
+ resetMessage: ""
+ };
+ }
+
+ /**
+ * Update state when user inputs new value in a text field
+ */
+ onInputChange = (v, type) => {
+ this.setState({ [type]: v });
+ }
+
+ /**
+ * Validate password input and submit password change
+ */
+ submit = (e) => {
+ if (e) e.preventDefault();
+ let resetMessage = ""
+ if (!this.state.password || !this.state.passwordConfirm) {
+ resetMessage = "Error: Both Password & Password Confirmation must be filled";
+ } else if (this.state.password !== this.state.passwordConfirm) {
+ resetMessage = "Error: Both Password & Password Confirmation must match";
+ }
+ else {
+ const reset_password_token = this.props.reset_password_token;
+ const password = {
+ password: this.state.password,
+ password_confirmation: this.state.passwordConfirm
+ };
+ this.props.resetPassword(reset_password_token, password);
+ this.props.handleResetPasswordSuccess();
+ }
+ this.setState({ resetMessage })
+ };
+
+ render() {
+ return (
+
+ );
+ }
+}
+export default ResetPassword;
diff --git a/viscoll-app/src/components/authentication/ResetPasswordRequest.js b/viscoll-app/src/components/authentication/ResetPasswordRequest.js
new file mode 100644
index 00000000..811dfbb9
--- /dev/null
+++ b/viscoll-app/src/components/authentication/ResetPasswordRequest.js
@@ -0,0 +1,90 @@
+import React, { Component } from 'react';
+import TextField from 'material-ui/TextField';
+import RaisedButton from 'material-ui/RaisedButton';
+import FlatButton from 'material-ui/FlatButton';
+import { btnLg, btnAuthCancel } from '../../styles/button';
+import {floatFieldDark} from '../../styles/textfield';
+/**
+ * Contains the reset password request form that is used by the landing page
+ * component called `Landing`. User inputs their email address and the app
+ * will email them a link to change their password. The component that handles the
+ * password change is `ResetPassword`.
+ */
+class ResetPasswordRequest extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ email: "",
+ resetMessage: "",
+ requested: false,
+ };
+ }
+
+ /**
+ * Update state when user inputs new value in a text field
+ */
+ onInputChange(v, type) {
+ this.setState({[type]: v});
+ }
+
+ /**
+ * Validate email address and submit password reset request
+ */
+ resetPasswordRequest = (e) => {
+ if (e) e.preventDefault();
+ let re = /[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}/igm;
+ let resetMessage = ""
+ if (!this.state.email) {
+ resetMessage = "Please enter your email address to reset the password.";
+ } else if (!re.test(this.state.email)) {
+ resetMessage = "Please enter a valid email address.";
+ }
+ else {
+ this.props.action.resetPasswordRequest(this.state.email);
+ resetMessage = "If this email address exists in our system, we've sent a password reset link to it."
+ this.setState({requested:true});
+ }
+ this.setState({ resetMessage })
+ };
+
+ render() {
+ let cancelButton = this.props.tapCancel()}
+ label="Go back"
+ {...btnAuthCancel}
+ />;
+ let submit =
+
+ this.resetPasswordRequest(null)}
+ />
+
;
+ if (this.state.requested) {
+ cancelButton = this.props.tapCancel()}
+ label="Okay"
+ {...btnLg}
+ />;
+ submit = "";
+ }
+ return (
+
+ )
+ }
+}
+export default ResetPasswordRequest;
diff --git a/viscoll-app/src/components/collationManager/ExportMode.js b/viscoll-app/src/components/collationManager/ExportMode.js
new file mode 100644
index 00000000..d3de2d95
--- /dev/null
+++ b/viscoll-app/src/components/collationManager/ExportMode.js
@@ -0,0 +1,157 @@
+import React from 'react';
+import PaperManager from "../../assets/visualMode/export/PaperManager.js";
+
+/** Contains the collation drawing in a canvas element */
+export default class ExportMode extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ paperManagers: [],
+ };
+ }
+
+ getChildrenByType = (type, memberIDs) => {
+ let ids = [];
+ for (let id of memberIDs) {
+ if (id.includes(type)) ids.push(id);
+ if (id.includes('Group')) {
+ let itemMembers = this.props.project.Groups[id].memberIDs;
+ ids = [...ids, ...this.getChildrenByType(type, itemMembers)];
+ }
+ }
+ return ids;
+ }
+
+ componentDidMount() {
+ this.props.toggleVisualizationDrawing({type:"tacketed", value: ""});
+ this.props.toggleVisualizationDrawing({type:"sewing", value: ""});
+ window.addEventListener("resize", this.drawOnCanvas);
+ this.updatePM();
+ }
+
+ componentWillUnmount() {
+ this.props.toggleVisualizationDrawing({type:"tacketed", value: ""});
+ this.props.toggleVisualizationDrawing({type:"sewing", value: ""});
+ // this.state.paperManager.deactivateTacketTool();
+ window.removeEventListener("resize", this.drawOnCanvas);
+ }
+
+ shouldComponentUpdate(nextProps) {
+ return (this.props.project.Groups!==nextProps.project.Groups ||
+ this.props.project.Sides!==nextProps.project.Sides ||
+ this.props.project.Rectos!==nextProps.project.Rectos ||
+ this.props.project.Versos!==nextProps.project.Versos ||
+ this.props.project.Notes!==nextProps.project.Notes ||
+ this.props.collationManager.selectedObjects!==nextProps.collationManager.selectedObjects ||
+ this.props.collationManager.flashItems !== nextProps.collationManager.flashItems ||
+ this.props.collationManager.filters !== nextProps.collationManager.filters ||
+ this.props.project.preferences !== nextProps.project.preferences ||
+ this.props.tacketed !== nextProps.tacketed ||
+ this.props.sewing !== nextProps.sewing ||
+ this.props.showNotes !== nextProps.showNotes
+ );
+ }
+
+ componentDidUpdate() {
+ this.updatePM();
+ }
+
+ updatePM = () => {
+ let pm = [];
+ for (let [i, [groupID, group]] of Object.entries(this.props.project.Groups).entries()) {
+ if (group.nestLevel > 1) continue;
+ // Filter leaf ids for this group only
+ let memberLeafIDs = this.getChildrenByType('Leaf', group.memberIDs);
+ let memberGroupIDs = this.getChildrenByType('Group', group.memberIDs);
+ let memberGroups = {};
+ memberGroups[groupID] = this.props.project.Groups[groupID];
+ for (let id of memberGroupIDs) {
+ memberGroups[id] = this.props.project.Groups[id];
+ }
+ pm.push(
+ new PaperManager({
+ canvasID: 'canvas'+i,
+ origin: 0,
+ spacing: 0.04,
+ strokeWidth: 0.015,
+ strokeColor: 'rgb(82,108,145)',
+ strokeColorActive: 'rgb(78,214,203)',
+ strokeColorGroupActive: 'rgb(82,108,145)',
+ strokeColorFilter: '#95fff6',
+ strokeColorAdded: "#5F95D6",
+ groupColor: '#e7e7e7',
+ groupColorActive: 'rgb(78,214,203)',
+ groupTextColor: "#727272",
+ strokeColorTacket: "#4e4e4e",
+ handleObjectClick: this.props.handleObjectClick,
+ groupIDs: [this.props.project.groupIDs[i], ...memberGroupIDs],
+ leafIDs: memberLeafIDs,
+ allLeafIDs: this.props.project.leafIDs,
+ allGroupIDs: this.props.project.groupIDs,
+ Groups: memberGroups,
+ Leafs: this.props.project.Leafs,
+ Rectos: this.props.project.Rectos,
+ Versos: this.props.project.Versos,
+ Notes: this.props.project.Notes,
+ activeGroups: this.props.collationManager.selectedObjects.type==="Group"? this.props.collationManager.selectedObjects.members : [],
+ activeLeafs: this.props.collationManager.selectedObjects.type==="Leaf"? this.props.collationManager.selectedObjects.members : [],
+ activeRectos: this.props.collationManager.selectedObjects.type==="Recto"? this.props.collationManager.selectedObjects.members : [],
+ activeVersos: this.props.collationManager.selectedObjects.type==="Verso"? this.props.collationManager.selectedObjects.members : [],
+ flashItems: this.props.collationManager.flashItems,
+ filters: this.props.collationManager.filters,
+ visibleAttributes: this.props.project.preferences,
+ toggleVisualizationDrawing: this.props.toggleVisualizationDrawing,
+ addVisualization: this.addVisualization,
+ openNoteDialog: this.props.openNoteDialog,
+ showNotes: this.props.showNotes
+ })
+ )
+ }
+ this.setState({paperManagers:pm}, ()=>{this.drawOnCanvas();});
+ }
+
+ addVisualization = (groupID, type, leafIDs) => {
+ let updatedGroup = {
+ [type]: leafIDs,
+ }
+ this.props.updateGroup(groupID, updatedGroup);
+ }
+
+ /**
+ * Update canvas size based on current window size
+ */
+ updateCanvasSize = () => {
+ // Resize the canvas
+ let maxWidth = window.innerWidth-window.innerWidth*0.46;
+ document.getElementById("myCanvas").width=maxWidth;
+ }
+
+ /**
+ * Draw canvas
+ */
+ drawOnCanvas = () => {
+ // Create leaves through manager
+ for (let i = 0; i < this.state.paperManagers.length; i++) {
+ this.state.paperManagers[i].draw();
+ }
+ }
+
+ render() {
+ let canvases = [];
+ let canvasAttr = {
+ 'data-paper-hidpi': 'off',
+ 'height': '500',
+ 'width': window.innerWidth-window.innerWidth*0.46,
+ };
+ for (let [i, [, group]] of Object.entries(this.props.project.Groups).entries()) {
+ if (group.nestLevel === 1) {
+ canvases.push( )
+ }
+ }
+ return (
+
+ {canvases}
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/collationManager/TabularMode.js b/viscoll-app/src/components/collationManager/TabularMode.js
new file mode 100644
index 00000000..32e3cd55
--- /dev/null
+++ b/viscoll-app/src/components/collationManager/TabularMode.js
@@ -0,0 +1,67 @@
+import React from 'react';
+import update from 'immutability-helper';
+import Group from './tabularMode/Group';
+
+
+/** Tabular mode - shows collation as table rows */
+export default class TabularMode extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ focusGroupID: [],
+ focusLeafID: null,
+ }
+ }
+
+ toggleFocusGroup = (id) => {
+ let newList = [];
+ if (id!==null) {
+ // Push to array
+ newList = update(this.state.focusGroupID, {$push: [id]});
+ } else {
+ // Pop the array
+ newList = update(this.state.focusGroupID, {$splice: [[this.state.focusGroupID.length-1, 1]]});
+ }
+ this.setState({focusGroupID: newList});
+ }
+
+ toggleFocusLeaf = (id) => {
+ this.setState({focusLeafID: id, focusGroupID: [this.state.focusGroupID[this.state.focusGroupID.length-1]]});
+ }
+
+ render() {
+ let group_components = [];
+ for (let groupID of this.props.project.groupIDs){
+ const group = this.props.project.Groups[groupID]
+ if (group.nestLevel === 1)
+ group_components.push(
+ 0? this.state.focusGroupID[this.state.focusGroupID.length-1] : null}
+ focusLeafID={this.state.focusLeafID}
+ handleObjectPress={this.props.handleObjectPress}
+ tabIndex={this.props.tabIndex}
+ leafIDs={this.props.leafIDs}
+ />
+ );
+ }
+
+ let emptyResults = false;
+ const activeFiltersLength = this.props.collationManager.filters.Groups.length + this.props.collationManager.filters.Leafs.length + this.props.collationManager.filters.Sides.length + this.props.collationManager.filters.Notes.length;
+ if (activeFiltersLength===0)
+ emptyResults = true && this.props.collationManager.filters.hideOthers && this.props.collationManager.filters.active
+
+ return (
+
+ {emptyResults ?
No objects match the query : group_components}
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/collationManager/ViewingMode.js b/viscoll-app/src/components/collationManager/ViewingMode.js
new file mode 100644
index 00000000..20d608fd
--- /dev/null
+++ b/viscoll-app/src/components/collationManager/ViewingMode.js
@@ -0,0 +1,145 @@
+import React from 'react';
+import PaperManager from "../../assets/visualMode/PaperManager.js";
+import ImageViewer from "../global/ImageViewer";
+
+/** Contains the collation drawing in a canvas element */
+export default class ViewingMode extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ paperManager: {},
+ };
+ }
+
+ componentDidMount() {
+ window.addEventListener("resize", this.drawOnCanvas);
+ this.setState({
+ paperManager: new PaperManager({
+ canvasID: 'myCanvas',
+ origin: 0,
+ spacing: 0.04,
+ strokeWidth: 0.015,
+ strokeColor: 'rgb(82,108,145)',
+ strokeColorActive: 'rgb(78,214,203)',
+ strokeColorGroupActive: 'rgb(82,108,145)',
+ strokeColorFilter: '#95fff6',
+ strokeColorAdded: "#5F95D6",
+ groupColor: '#e7e7e7',
+ groupColorActive: 'rgb(78,214,203)',
+ groupTextColor: "#727272",
+ strokeColorTacket: "#4e4e4e",
+ handleObjectClick: this.props.handleObjectClick,
+ groupIDs: this.props.project.groupIDs,
+ leafIDs: this.props.project.leafIDs,
+ Groups: this.props.project.Groups,
+ Leafs: this.props.project.Leafs,
+ Rectos: this.props.project.Rectos,
+ Versos: this.props.project.Versos,
+ Notes: this.props.project.Notes,
+ activeGroups: this.props.collationManager.selectedObjects.type==="Group"? this.props.collationManager.selectedObjects.members : [],
+ activeLeafs: this.props.collationManager.selectedObjects.type==="Leaf"? this.props.collationManager.selectedObjects.members : [],
+ activeRectos: this.props.collationManager.selectedObjects.type==="Recto"? this.props.collationManager.selectedObjects.members : [],
+ activeVersos: this.props.collationManager.selectedObjects.type==="Verso"? this.props.collationManager.selectedObjects.members : [],
+ flashItems: this.props.collationManager.flashItems,
+ filters: this.props.collationManager.filters,
+ visibleAttributes: this.props.project.preferences,
+ toggleTacket: this.props.toggleTacket,
+ addTacket: this.addTacket,
+ viewingMode: true,
+ })
+ }, ()=>{this.drawOnCanvas();});
+ }
+
+ shouldComponentUpdate(nextProps, nextState) {
+ return (
+ this.props.collationManager.selectedObjects!==nextProps.collationManager.selectedObjects ||
+ this.props.collationManager.filters !== nextProps.collationManager.filters ||
+ this.props.project.preferences !== nextProps.project.preferences ||
+ this.props.project.Notes!==nextProps.project.Notes ||
+ this.state.viewingMode !== nextState.viewingMode ||
+ this.props.imageViewerEnabled !== nextProps.imageViewerEnabled
+ );
+ }
+
+ componentWillUpdate(nextProps, nextState) {
+ if (Object.keys(this.state.paperManager).length>0) {
+ this.state.paperManager.setProject(nextProps.project);
+ this.state.paperManager.setActiveGroups(nextProps.collationManager.selectedObjects.type==="Group"? nextProps.collationManager.selectedObjects.members : []);
+ this.state.paperManager.setActiveLeafs(nextProps.collationManager.selectedObjects.type==="Leaf"? nextProps.collationManager.selectedObjects.members : []);
+ this.state.paperManager.setActiveRectos(nextProps.collationManager.selectedObjects.type==="Recto"? nextProps.collationManager.selectedObjects.members : []);
+ this.state.paperManager.setActiveVersos(nextProps.collationManager.selectedObjects.type==="Verso"? nextProps.collationManager.selectedObjects.members : []);
+ this.state.paperManager.setFilter(nextProps.collationManager.filters);
+ this.state.paperManager.setVisibility(nextProps.project.preferences);
+ }
+ }
+
+ componentDidUpdate() {
+ this.drawOnCanvas();
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener("resize", this.drawOnCanvas);
+ }
+
+ /**
+ * Draw canvas
+ */
+ drawOnCanvas = () => {
+ // Create leaves through manager
+ this.updateCanvasSize();
+ this.state.paperManager.draw();
+ }
+ /**
+ * Update canvas size based on current window size
+ */
+ updateCanvasSize = () => {
+ // Resize the canvas
+ let maxWidth = window.innerWidth-window.innerWidth*0.46;
+ if (this.props.imageViewerEnabled) {
+ maxWidth = window.innerWidth-window.innerWidth*0.75;
+ }
+ document.getElementById("myCanvas").width=maxWidth;
+ this.state.paperManager.setWidth(maxWidth);
+ if (this.props.imageViewerEnabled) {
+ this.state.paperManager.setScale(0.06, 0.027);
+ } else {
+ this.state.paperManager.setScale(0.04, 0.015);
+ }
+ }
+
+ render() {
+ let canvasAttr = {
+ 'data-paper-hidpi': 'off',
+ 'height': "99999999px",
+ 'width': this.props.imageViewerEnabled? window.innerWidth-window.innerWidth*0.75: window.innerWidth-window.innerWidth*0.46,
+ };
+
+ let leafID, leaf, recto, verso, isRectoDIY, isVersoDIY, rectoURL, versoURL;
+ if (this.props.selectedObjects.type==="Leaf"){
+ leafID = this.props.selectedObjects.members[0];
+ leaf = this.props.project.Leafs[leafID];
+ recto = this.props.project.Rectos[leaf.rectoID];
+ verso = this.props.project.Versos[leaf.versoID];
+ } else if (this.props.selectedObjects.type==="Recto") {
+ recto = this.props.project.Rectos[this.props.selectedObjects.members[0]];
+ } else if (this.props.selectedObjects.type==="Verso") {
+ verso = this.props.project.Versos[this.props.selectedObjects.members[0]];
+ }
+ isRectoDIY = recto!==undefined && recto.image.manifestID!==undefined && recto.image.manifestID.includes("DIY");
+ isVersoDIY = verso!==undefined && verso.image.manifestID!==undefined && verso.image.manifestID.includes("DIY");
+ rectoURL = recto!==undefined && recto.image.url!==undefined? recto.image.url : null;
+ versoURL = verso!==undefined && verso.image.url!==undefined? verso.image.url : null;
+ return (
+
+
+
+
+
+ {this.props.imageViewerEnabled?
+
+ :""
+ }
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/collationManager/VisualMode.js b/viscoll-app/src/components/collationManager/VisualMode.js
new file mode 100644
index 00000000..03d58a7d
--- /dev/null
+++ b/viscoll-app/src/components/collationManager/VisualMode.js
@@ -0,0 +1,134 @@
+import React from 'react';
+import PaperManager from "../../assets/visualMode/PaperManager.js";
+
+/** Contains the collation drawing in a canvas element */
+export default class VisualMode extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ paperManager: {},
+ };
+ }
+
+ componentDidMount() {
+ this.props.toggleVisualizationDrawing({type:"tacketed", value: ""});
+ this.props.toggleVisualizationDrawing({type:"sewing", value: ""});
+ window.addEventListener("resize", this.drawOnCanvas);
+ this.setState({
+ paperManager: new PaperManager({
+ canvasID: 'myCanvas',
+ origin: 0,
+ spacing: 0.04,
+ strokeWidth: 0.015,
+ strokeColor: 'rgb(82,108,145)',
+ strokeColorActive: 'rgb(78,214,203)',
+ strokeColorGroupActive: 'rgb(82,108,145)',
+ strokeColorFilter: '#95fff6',
+ strokeColorAdded: "#5F95D6",
+ groupColor: '#e7e7e7',
+ groupColorActive: 'rgb(78,214,203)',
+ groupTextColor: "#727272",
+ strokeColorTacket: "#4e4e4e",
+ handleObjectClick: this.props.handleObjectClick,
+ groupIDs: this.props.project.groupIDs,
+ leafIDs: this.props.project.leafIDs,
+ Groups: this.props.project.Groups,
+ Leafs: this.props.project.Leafs,
+ Rectos: this.props.project.Rectos,
+ Versos: this.props.project.Versos,
+ Notes: this.props.project.Notes,
+ activeGroups: this.props.collationManager.selectedObjects.type==="Group"? this.props.collationManager.selectedObjects.members : [],
+ activeLeafs: this.props.collationManager.selectedObjects.type==="Leaf"? this.props.collationManager.selectedObjects.members : [],
+ activeRectos: this.props.collationManager.selectedObjects.type==="Recto"? this.props.collationManager.selectedObjects.members : [],
+ activeVersos: this.props.collationManager.selectedObjects.type==="Verso"? this.props.collationManager.selectedObjects.members : [],
+ flashItems: this.props.collationManager.flashItems,
+ filters: this.props.collationManager.filters,
+ visibleAttributes: this.props.project.preferences,
+ toggleVisualizationDrawing: this.props.toggleVisualizationDrawing,
+ addVisualization: this.addVisualization,
+ openNoteDialog: this.props.openNoteDialog,
+ })
+ }, ()=>{this.drawOnCanvas();});
+ }
+ componentWillUnmount() {
+ this.props.toggleVisualizationDrawing({type:"tacketed", value: ""});
+ this.props.toggleVisualizationDrawing({type:"sewing", value: ""});
+ this.state.paperManager.deactivateTacketTool();
+ window.removeEventListener("resize", this.drawOnCanvas);
+ }
+
+ shouldComponentUpdate(nextProps) {
+ return (this.props.project.Groups!==nextProps.project.Groups ||
+ this.props.project.Sides!==nextProps.project.Sides ||
+ this.props.project.Rectos!==nextProps.project.Rectos ||
+ this.props.project.Versos!==nextProps.project.Versos ||
+ this.props.project.Notes!==nextProps.project.Notes ||
+ this.props.collationManager.selectedObjects!==nextProps.collationManager.selectedObjects ||
+ this.props.collationManager.flashItems !== nextProps.collationManager.flashItems ||
+ this.props.collationManager.filters !== nextProps.collationManager.filters ||
+ this.props.project.preferences !== nextProps.project.preferences ||
+ this.props.tacketed !== nextProps.tacketed ||
+ this.props.sewing !== nextProps.sewing
+ );
+ }
+
+ componentWillUpdate(nextProps) {
+ if (Object.keys(this.state.paperManager).length>0) {
+ this.state.paperManager.setProject(nextProps.project);
+ this.state.paperManager.setFlashItems(nextProps.collationManager.flashItems);
+ this.state.paperManager.setActiveGroups(nextProps.collationManager.selectedObjects.type==="Group"? nextProps.collationManager.selectedObjects.members : []);
+ this.state.paperManager.setActiveLeafs(nextProps.collationManager.selectedObjects.type==="Leaf"? nextProps.collationManager.selectedObjects.members : []);
+ this.state.paperManager.setActiveRectos(nextProps.collationManager.selectedObjects.type==="Recto"? nextProps.collationManager.selectedObjects.members : []);
+ this.state.paperManager.setActiveVersos(nextProps.collationManager.selectedObjects.type==="Verso"? nextProps.collationManager.selectedObjects.members : []);
+ this.state.paperManager.setFilter(nextProps.collationManager.filters);
+ this.state.paperManager.setVisibility(nextProps.project.preferences);
+ this.drawOnCanvas();
+ if (nextProps.tacketed!=="") {
+ this.state.paperManager.activateTacketTool(nextProps.tacketed);
+ } else if (nextProps.sewing!=="") {
+ this.state.paperManager.activateTacketTool(nextProps.sewing, "sewing");
+ } else {
+ this.state.paperManager.deactivateTacketTool();
+ }
+ }
+ }
+
+ addVisualization = (groupID, type, leafIDs) => {
+ let updatedGroup = {
+ [type]: leafIDs,
+ }
+ this.props.updateGroup(groupID, updatedGroup);
+ }
+
+ /**
+ * Update canvas size based on current window size
+ */
+ updateCanvasSize = () => {
+ // Resize the canvas
+ let maxWidth = window.innerWidth-window.innerWidth*0.46;
+ document.getElementById("myCanvas").width=maxWidth;
+ this.state.paperManager.setWidth(maxWidth);
+ }
+
+ /**
+ * Draw canvas
+ */
+ drawOnCanvas = () => {
+ // Create leaves through manager
+ this.updateCanvasSize();
+ this.state.paperManager.draw();
+ }
+
+ render() {
+ let canvasAttr = {
+ 'data-paper-hidpi': 'off',
+ 'height': "99999999px",
+ 'width': window.innerWidth-window.innerWidth*0.46,
+ };
+ return (
+
+
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/collationManager/dialog/NoteDialog.js b/viscoll-app/src/components/collationManager/dialog/NoteDialog.js
new file mode 100644
index 00000000..b86ecfcb
--- /dev/null
+++ b/viscoll-app/src/components/collationManager/dialog/NoteDialog.js
@@ -0,0 +1,122 @@
+import React from 'react';
+import EditNoteForm from '../../notesManager/EditNoteForm';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+
+/** Note dialog */
+export default class NoteDialog extends React.Component {
+ getLinkedGroups = () => {
+ const groupsWithCurrentNote = Object.keys(this.props.Groups).filter((groupID) => {
+ return (this.props.Groups[groupID].notes.includes(this.props.activeNote.id))
+ });
+ return groupsWithCurrentNote.map((value) => {
+ const label = `Group ${this.props.groupIDs.indexOf(value)+1}`;
+ return {label, value};
+ });
+ }
+
+ getLinkedLeaves = () => {
+ const leafsWithCurrentNote = Object.keys(this.props.Leafs).filter((leafID) => {
+ return (this.props.Leafs[leafID].notes.includes(this.props.activeNote.id))
+ });
+ return leafsWithCurrentNote.map((value)=>{
+ const label = `Leaf ${this.props.leafIDs.indexOf(value)+1}`;
+ return {label, value};
+ });
+ }
+
+ getLinkedSides = () => {
+ const rectosWithCurrentNote = Object.keys(this.props.Rectos).filter((rectoID) => {
+ return (this.props.Rectos[rectoID].notes.includes(this.props.activeNote.id))
+ });
+ const versosWithCurrentNote = Object.keys(this.props.Versos).filter((versoID) => {
+ return (this.props.Versos[versoID].notes.includes(this.props.activeNote.id))
+ });
+ const sidesWithCurrentNote = [];
+ for (let value of rectosWithCurrentNote){
+ const leafOrder = this.props.leafIDs.indexOf(this.props.Rectos[value].parentID) + 1;
+ const folioNumber = this.props.Rectos[value].folio_number && this.props.Rectos[value].folio_number!==""? `(${this.props.Rectos[value].folio_number})`:"";
+ const pageNumber = this.props.Rectos[value].page_number && this.props.Rectos[value].page_number!==""? `(${this.props.Rectos[value].page_number})`:"";
+ const label = `L${leafOrder} Recto ${folioNumber} ${pageNumber}`;
+ sidesWithCurrentNote.push({label, value})
+ }
+ for (let value of versosWithCurrentNote){
+ const leafOrder = this.props.leafIDs.indexOf(this.props.Versos[value].parentID) + 1;
+ const folioNumber = this.props.Versos[value].folio_number && this.props.Versos[value].folio_number!==""? `(${this.props.Versos[value].folio_number})`:"";
+ const pageNumber = this.props.Versos[value].page_number && this.props.Versos[value].page_number!==""? `(${this.props.Versos[value].page_number})`:"";
+ const label = `L${leafOrder} Verso ${folioNumber} ${pageNumber}`;
+ sidesWithCurrentNote.push({label, value})
+ }
+ return sidesWithCurrentNote;
+ }
+
+ getRectosAndVersos = () => {
+ const size = Object.keys(this.props.Rectos).length;
+ let result = {};
+ for (let i=0; i ,
+ ];
+ return (
+
+ {this.props.open?
+ : ""}
+
+ );
+ }
+
+}
+
+
+
+
diff --git a/viscoll-app/src/components/collationManager/tabularMode/Group.js b/viscoll-app/src/components/collationManager/tabularMode/Group.js
new file mode 100644
index 00000000..4170039d
--- /dev/null
+++ b/viscoll-app/src/components/collationManager/tabularMode/Group.js
@@ -0,0 +1,154 @@
+import React from 'react';
+import Leaf from './Leaf';
+import IconButton from 'material-ui/IconButton';
+import ExpandMore from 'material-ui/svg-icons/navigation/expand-more';
+import ExpandLess from 'material-ui/svg-icons/navigation/expand-less';
+
+/** Group element in the tabular edit mode. Recursively mounts nested groups and leaves. */
+export default class Group extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ open: true,
+ }
+ }
+
+ handleChange = (type, value) => {
+ this.setState({[type]:value});
+ }
+
+ findItemByName = (attributeName) => {
+ return this.props.collationManager.defaultAttributes.group.find((item)=>item.name===attributeName);
+ }
+
+ render() {
+
+ const isActive = this.props.collationManager.selectedObjects.members.includes(this.props.activeGroup.id);
+ const isFiltered = this.props.collationManager.filters.Groups.includes(this.props.activeGroup.id);
+ const groupsOfMatchingElements = this.props.collationManager.filters.GroupsOfMatchingLeafs + this.props.collationManager.filters.GroupsOfMatchingSides + this.props.collationManager.filters.GroupsOfMatchingNotes;
+ const isAffectedFiltered = groupsOfMatchingElements.includes(this.props.activeGroup.id) && !isFiltered;
+ const hideOthers = this.props.collationManager.filters.hideOthers;
+ const isFilterActive = this.props.collationManager.filters.active;
+ // Populate all the members of this Group.
+ let groupMembers = [];
+ this.props.activeGroup.memberIDs.forEach((memberID, index) => {
+ if (memberID.charAt(0)==="L"){
+ let current_leaf = this.props.project.Leafs[memberID];
+ groupMembers.push(
+
+ );
+ } else {
+ let current_group = this.props.project.Groups[memberID];
+ groupMembers.push(
+
+ );
+ }
+ });
+
+ let attributes = [];
+ if (this.props.project.preferences.group) {
+ for (var attributeName of Object.keys(this.props.project.preferences.group)) {
+ if (this.props.project.preferences.group[attributeName]) {
+ const item = this.findItemByName(attributeName);
+ attributes.push(
+
+
+ {item.displayName}
+ {this.props.activeGroup[attributeName]}
+
+
+ );
+ }
+ }
+ }
+
+ let activeGroupStyle = {};
+ if (isFiltered && !hideOthers) {
+ activeGroupStyle["borderColor"] = "#0f7fdb";
+ }
+ if (isAffectedFiltered && hideOthers && isFilterActive){
+ activeGroupStyle["backgroundColor"] = "#d9dbdb";
+ activeGroupStyle["borderColor"] = "#d9dbdb";
+ }
+ let groupContainerClasses = "groupContainer ";
+ if (this.props.collationManager.flashItems.groups.includes(this.props.activeGroup.id)) groupContainerClasses += "flash ";
+ if (isActive) groupContainerClasses += "active ";
+ if (this.props.focusLeafID===null && this.props.focusGroupID === this.props.activeGroup.id) groupContainerClasses += "focus ";
+
+ let groupComponent = this.props.toggleFocusGroup(this.props.activeGroup.id)}
+ onMouseLeave={()=>this.props.toggleFocusGroup(null)}
+ onClick={(event) =>this.props.handleObjectClick(this.props.activeGroup, event)}
+ >
+
+
+
+ Group {this.props.activeGroupOrder}
+ {if(e.key===" "){this.props.handleObjectPress(this.props.activeGroup, e)}}}
+ onClick={(e)=>{this.props.handleObjectPress(this.props.activeGroup, e);}}
+ tabIndex={this.props.tabIndex}
+ />
+
+ {attributes}
+
+
+ {e.stopPropagation();e.preventDefault();this.handleChange("open", !this.state.open)}}
+ aria-label={this.state.open?"Collapse group " + this.props.activeGroupOrder : "Expand group " + this.props.activeGroupOrder }
+ tabIndex={this.props.tabIndex}
+ tooltip={this.state.open?"Collapse group" : "Expand group"}
+ >
+ {this.state.open? : }
+
+
+
+
+ {groupMembers}
+
+
+
+ if (!isFiltered && hideOthers && isFilterActive && !isAffectedFiltered)
+ groupComponent =
;
+
+ return (
+ groupComponent
+ );
+ }
+ }
+
+
+
\ No newline at end of file
diff --git a/viscoll-app/src/components/collationManager/tabularMode/Leaf.js b/viscoll-app/src/components/collationManager/tabularMode/Leaf.js
new file mode 100644
index 00000000..2d1e5a04
--- /dev/null
+++ b/viscoll-app/src/components/collationManager/tabularMode/Leaf.js
@@ -0,0 +1,150 @@
+import React from 'react';
+import Side from './Side';
+
+/** Leaf element of collation used in tabular edit mode*/
+const Leaf = (props) => {
+ const { activeLeaf, project } = props;
+ const {
+ selectedObjects,
+ filters,
+ defaultAttributes,
+ flashItems,
+ } = props.collationManager;
+ const isActive = selectedObjects.members.includes(activeLeaf.id);
+ const isFiltered = filters.Leafs.includes(activeLeaf.id);
+ const leafsOfMatchingElements = filters.LeafsOfMatchingSides + filters.LeafsOfMatchingNotes;
+ const isAffectedFiltered = leafsOfMatchingElements.includes(activeLeaf.id) && !isFiltered;
+ const hideOthers = filters.hideOthers;
+ const isFilterActive = filters.active;
+ let leafAttributes = [];
+ let sideAttributesActive = false;
+
+ // Determine if any side attributes are active (visibility toggled)
+ for (let sideAttribute of defaultAttributes.side) {
+ let attributeName = sideAttribute.name;
+ if (project.preferences.side && project.preferences.side[attributeName]) {
+ sideAttributesActive = true;
+ break;
+ }
+ }
+
+ // Render any visible leaf attributes
+ for (let leafAttribute of defaultAttributes.leaf) {
+ let attributeName = leafAttribute.name;
+ if (project.preferences.leaf && project.preferences.leaf[attributeName]) {
+ let divStyle = "attribute ";
+ if (isActive) divStyle += "active ";
+ if (attributeName==="conjoined_to"){
+ leafAttributes.push(
+
+
+ {leafAttribute.displayName}
+ {props.leafIDs.indexOf(activeLeaf[attributeName])!==-1 ? `Leaf ${props.leafIDs.indexOf(activeLeaf[attributeName])+1}` : "None"}
+
+
+ );
+ } else{
+ leafAttributes.push(
+
+
+ {leafAttribute.displayName}
+ {activeLeaf[attributeName]}
+
+
+ );
+ }
+ }
+ }
+
+
+
+ let activeLeafStyle = {};
+ if (isFiltered && !hideOthers) {
+ activeLeafStyle["borderColor"] = "#0f7fdb";
+ }
+ if (isAffectedFiltered && hideOthers && isFilterActive){
+ activeLeafStyle["backgroundColor"] = "#d9dbdb";
+ activeLeafStyle["borderColor"] = "#d9dbdb";
+ }
+
+ let sideComponents;
+ let sideClassName = "sideToggle";
+ if (sideAttributesActive) {
+ sideClassName = "sideSection";
+ }
+ const rectoSide = props.Rectos[activeLeaf.rectoID];
+ const versoSide = props.Versos[activeLeaf.versoID];
+ sideComponents = (
+
+
+
+
+ );
+
+
+ let sectionStyle = "leafSection ";
+ if (isActive) sectionStyle += "active ";
+ if (props.focusLeafID === activeLeaf.id) sectionStyle += "focus";
+
+ let leafComponent =
+
+
{props.handleObjectClick(activeLeaf, event);event.stopPropagation()}}
+ style={{ ...activeLeafStyle }}
+ onMouseEnter={()=>props.toggleFocusLeaf(activeLeaf.id)}
+ onMouseLeave={()=>props.toggleFocusLeaf(null)}
+ >
+
+ Leaf {props.activeLeafOrder}
+ {if(e.key===" "){props.handleObjectPress(activeLeaf, e)}}}
+ onClick={(e)=>{props.handleObjectPress(activeLeaf, e);e.stopPropagation();}}
+ tabIndex={props.tabIndex}
+ />
+
+ {leafAttributes.length>0?
+
+ {leafAttributes}
+
+ :""}
+
+ {sideComponents}
+
+
+
+ if (!isFiltered && hideOthers && isFilterActive && !isAffectedFiltered)
+ leafComponent =
;
+
+ return (
+ leafComponent
+ );
+}
+export default Leaf;
diff --git a/viscoll-app/src/components/collationManager/tabularMode/Side.js b/viscoll-app/src/components/collationManager/tabularMode/Side.js
new file mode 100644
index 00000000..30541896
--- /dev/null
+++ b/viscoll-app/src/components/collationManager/tabularMode/Side.js
@@ -0,0 +1,100 @@
+import React from 'react';
+
+/** Side element of collation used in tabular edit mode */
+const Side = (props) => {
+ const { activeSide, project } = props;
+ const {
+ selectedObjects,
+ filters,
+ defaultAttributes,
+ } = props.collationManager;
+ const isActive = selectedObjects.members.includes(activeSide.id);
+ const sidesOfMatchingElements = filters.SidesOfMatchingNotes;
+ const isFiltered = filters.Sides.includes(activeSide.id);
+ const isAffectedFiltered = sidesOfMatchingElements.includes(activeSide.id) && !isFiltered;
+ const hideOthers = filters.hideOthers;
+ const isFilterActive = filters.active;
+
+ let sideAttributes = [];
+
+ for (let attribute of defaultAttributes.side) {
+ if (project.preferences.side && project.preferences.side[attribute.name]) {
+ sideAttributes.push(
+
+ {attribute.displayName}: {activeSide[attribute.name]}
+
+ );
+ }
+ }
+
+ let activeSideStyle = {};
+
+ if (isFiltered && !hideOthers) {
+ activeSideStyle["borderColor"] = "#0f7fdb";
+ }
+
+ if (isAffectedFiltered && hideOthers && isFilterActive){
+ activeSideStyle["backgroundColor"] = "#d9dbdb";
+ activeSideStyle["borderColor"] = "#d9dbdb";
+ }
+
+ const activeSideName = activeSide.id.split("_")[0];
+
+ let classNames = "side ";
+ if (props.focusLeafID===props.activeSide.id) classNames += "focus ";
+ if (isActive) classNames += "active ";
+
+ let sideComponent = (
+ {props.handleObjectClick(activeSide, event); event.stopPropagation();}}
+ style={{ ...activeSideStyle }}
+ onMouseEnter={()=>props.toggleFocusLeaf(activeSide.id)}
+ onMouseLeave={()=>props.toggleFocusLeaf(null)}
+ >
+ {activeSideName.charAt(0)}
+ {if(e.key===" "){props.handleObjectPress(activeSide, e)}}}
+ onClick={(e)=>{props.handleObjectPress(activeSide, e);e.stopPropagation();}}
+ tabIndex={props.tabIndex}
+ />
+
+ );
+
+ if (sideAttributes.length>0) {
+ sideComponent = (
+ {props.handleObjectClick(activeSide, event); event.stopPropagation();}}
+ style={{ ...activeSideStyle }}
+ onMouseEnter={()=>props.toggleFocusLeaf(activeSide.id)}
+ onMouseLeave={()=>props.toggleFocusLeaf(null)}
+ >
+
+ {activeSideName}
+ {if(e.key===" "){props.handleObjectPress(activeSide, e)}}}
+ onClick={(e)=>{props.handleObjectPress(activeSide, e);e.stopPropagation();}}
+ tabIndex={props.tabIndex}
+ />
+
+ {sideAttributes}
+
+ );
+ }
+
+ return (
+ sideComponent
+ );
+}
+export default Side;
diff --git a/viscoll-app/src/components/dashboard/CloneProject.js b/viscoll-app/src/components/dashboard/CloneProject.js
new file mode 100644
index 00000000..494bfee4
--- /dev/null
+++ b/viscoll-app/src/components/dashboard/CloneProject.js
@@ -0,0 +1,77 @@
+import React from 'react';
+import RaisedButton from 'material-ui/RaisedButton';
+import FlatButton from 'material-ui/FlatButton';
+import SelectField from '../global/SelectField';
+
+/** New Project dialog - clone existing project */
+export default class CloneProject extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ projectIndex: -1
+ }
+ }
+
+ onChange = (projectIndex) => {
+ this.setState({ projectIndex });
+ }
+
+ submit = (event) => {
+ if (event) event.preventDefault();
+ this.props.cloneProject(this.props.allProjects[this.state.projectIndex].id);
+ this.props.reset();
+ this.props.close();
+ }
+
+ render(){
+ if (this.props.allProjects.length>0) {
+ const data = this.props.allProjects.map((project, index)=>{
+ return (
+ {text: project.title, value:index}
+ );
+ });
+ return (
+
+
Clone Existing Collation
+
+
+ );
+ } else {
+ return
+
Clone Existing Collation
+
You do not have any projects to clone.
+
+ this.props.previousStep()}
+ />
+
+
+ }
+ }
+}
diff --git a/viscoll-app/src/components/dashboard/EditProjectForm.js b/viscoll-app/src/components/dashboard/EditProjectForm.js
new file mode 100644
index 00000000..68c00cc1
--- /dev/null
+++ b/viscoll-app/src/components/dashboard/EditProjectForm.js
@@ -0,0 +1,367 @@
+import React from 'react';
+import IconButton from 'material-ui/IconButton';
+import CloseIcon from 'material-ui/svg-icons/navigation/close';
+import RaisedButton from 'material-ui/RaisedButton';
+import TextField from 'material-ui/TextField';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import IconSubmit from 'material-ui/svg-icons/action/done';
+import IconClear from 'material-ui/svg-icons/content/clear';
+import Checkbox from 'material-ui/Checkbox';
+
+/** Form to edit project information on the project panel in the dashboard */
+class EditProjectForm extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ unsavedDialog: false,
+ deleteDialog: false,
+ deleteUnlinkedImages: false,
+ editing: {
+ title: false,
+ shelfmark: false,
+ date: false,
+ },
+ errors: {
+ title: "",
+ shelfmark: "",
+ date: "",
+ },
+ };
+ }
+
+ /**
+ * Update project pane if a new project was selected in the dashboard
+ */
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.selectedProject) {
+ let title = nextProps.selectedProject.title;
+ let shelfmark = nextProps.selectedProject.shelfmark;
+ let date = nextProps.selectedProject.metadata.date;
+ if (this.props.selectedProject && this.props.selectedProject.id === nextProps.selectedProject.id) {
+ // Do not update the fields if they are currently editing and did not submit
+ if (this.state.title !== title && this.state.editing.title) title = this.state.title;
+ if (this.state.shelfmark !== shelfmark && this.state.editing.shelfmark) shelfmark = this.state.shelfmark;
+ if (this.state.date !== date && this.state.editing.date) date = this.state.date;
+ } else {
+ // Switched project selection - reset editing states
+ this.setState({
+ editing: {
+ title: false,
+ shelfmark: false,
+ date: false,
+ },
+ });
+ }
+ this.setState({
+ title: title,
+ shelfmark: shelfmark,
+ date: date,
+ errors: {
+ title: "",
+ shelfmark: "",
+ date: "",
+ },
+ selectedProject: nextProps.selectedProject,
+ allProjects: nextProps.allProjects,
+ })
+ }
+ }
+
+ /**
+ * Validate user input and display appropriate error message
+ */
+ checkValidationError = (type) => {
+ const errors = {title:"", shelfmark:"", date:""};
+ const allProjectsExceptCurrent = [...this.state.allProjects];
+ const selectedProjectIndex = this.state.allProjects.findIndex((project)=>project.id===this.props.selectedProject.id);
+ allProjectsExceptCurrent.splice(selectedProjectIndex, 1);
+ allProjectsExceptCurrent.forEach(project => {
+ if (type==="title"){
+ if (project.title === this.state.title)
+ errors.title = "Project title should be unique";
+ if (!this.state.title)
+ errors.title = "Project title is required";
+ } else {
+ if (project.shelfmark === this.state.shelfmark)
+ errors.shelfmark = "Manuscript shelfmark should be unique";
+ if (!this.state.shelfmark)
+ errors.shelfmark = "Manuscript shelfmark is required";
+ }
+ });
+ return errors;
+ }
+
+ /**
+ * Return true if any errors exist in the project form
+ */
+ ifErrorsExist = () => {
+ if (this.state.errors.title)
+ return true;
+ if (this.state.errors.shelfmark)
+ return true;
+ return false;
+ }
+
+ /**
+ * Update state when user inputs new value in a text field
+ */
+ onInputChange = (event, newValue, type) => {
+ this.setState({[type]: newValue, editing: {...this.state.editing, [type]:true}}, () => {
+ this.setState({errors: this.checkValidationError(type)})
+ });
+ }
+
+ /**
+ * Toggle delete confirmation dialog
+ */
+ handleDeleteDialogToggle = (deleteDialog=false) => {
+ this.props.togglePopUp(deleteDialog);
+ this.setState({ deleteDialog });
+ };
+
+ /**
+ * Toggle unsaved changes dialog
+ */
+ handleUnsavedDialogToggle = (unsavedDialog=false) => {
+ this.setState({ unsavedDialog });
+ };
+
+ /**
+ * Submit project update of a specific input field
+ */
+ handleProjectUpdate = (event, field) => {
+ if (event) event.preventDefault();
+ const projectID = this.props.selectedProject.id;
+ const project = {
+ title: this.state.title,
+ shelfmark: this.state.shelfmark,
+ metadata: {
+ date: this.state.date
+ }
+ };
+ const user = {
+ id: this.props.user.id,
+ token: this.props.user.token
+ };
+ this.setState({editing: {...this.state.editing, [field]: false }});
+ this.props.updateProject(projectID, project, user);
+ }
+ /**
+ * Submit project delete
+ */
+ handleProjectDelete = () => {
+ this.props.closeProjectPanel();
+ this.setState({deleteDialog: false});
+ const projectID = this.props.selectedProject.id;
+ this.props.deleteProject(projectID, this.state.deleteUnlinkedImages);
+ };
+
+ /**
+ * Close project panel
+ */
+ handleProjectPanelClose = (ignoreChanges=false) => {
+ // Check for any unsaved changes before closing and show the warning dialog.
+ if (!ignoreChanges && this.isEditing())
+ this.setState({ unsavedDialog: true });
+ else {
+ this.setState({ unsavedDialog: false });
+ this.props.closeProjectPanel();
+ }
+ }
+
+ /**
+ * Return true if any input fields have been changed and not saved
+ */
+ isEditing = () => {
+ return (this.state.editing.title||this.state.editing.shelfmark||this.state.editing.uri||this.state.editing.date);
+ }
+
+ /**
+ * Reset text field to original values
+ */
+ handleProjectCancelUpdate = (field) => {
+ this.setState({
+ [field]: this.props.selectedProject[field],
+ editing: {...this.state.editing, [field]: false },
+ errors: {...this.state.errors, [field]: ""},
+ });
+ }
+
+ /**
+ * Return a generated HTML of submit and cancel buttons for a specific input name
+ */
+ submitButtons = (field) => {
+ if (this.state.editing[field]) {
+ return (
+
+ }
+ style={{minWidth:"60px",marginLeft:"5px"}}
+ name="submit"
+ type="submit"
+ disabled={this.ifErrorsExist()}
+ onClick={() => this.handleProjectUpdate(null, field)}
+ />
+ }
+ style={{minWidth:"60px",marginLeft:"5px"}}
+ onClick={() => this.handleProjectCancelUpdate(field)}
+ />
+
+ )
+ } else {
+ return "";
+ }
+ }
+
+ render() {
+ const selectedProject = this.props.selectedProject;
+ if (!selectedProject)
+ return
;
+
+ let projectPanelData = (
+
+
+
+
+
+ Created at: {new Date(selectedProject.created_at).toLocaleString('en-US')}
+ Last modified: {new Date(selectedProject.updated_at).toLocaleString('en-US')}
+
+
+
+ );
+
+ const deleteActions = [
+ this.handleDeleteDialogToggle()}
+ />,
+ this.handleProjectDelete()}
+ />,
+ ];
+
+ const unsaveActions = [
+ this.handleUnsavedDialogToggle()}
+ />,
+ this.handleProjectPanelClose(true)}
+ />,
+ ];
+
+
+ return (
+
+
+ this.handleProjectPanelClose()}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+
+ {projectPanelData}
+
+
+
+ this.props.history.push(`/project/${this.props.selectedProject.id}`)}
+ style={{width:"49%",float:"left",marginRight:"2%"}}
+ tabIndex={this.props.tabIndex}
+ />
+ this.handleDeleteDialogToggle(true)}
+ labelColor="#b53c3c"
+ style={{width:"49%"}}
+ tabIndex={this.props.tabIndex}
+ />
+
+
+ this.setState({deleteUnlinkedImages: !this.state.deleteUnlinkedImages})}
+ />
+
+
+
+
+
+ );
+ }
+}
+export default EditProjectForm;
diff --git a/viscoll-app/src/components/dashboard/ImageCollections.js b/viscoll-app/src/components/dashboard/ImageCollections.js
new file mode 100644
index 00000000..ee2455bd
--- /dev/null
+++ b/viscoll-app/src/components/dashboard/ImageCollections.js
@@ -0,0 +1,306 @@
+import React, {Component} from 'react';
+import { Grid } from 'react-virtualized';
+import Checkbox from 'material-ui/Checkbox';
+import RaisedButton from 'material-ui/RaisedButton';
+import FlatButton from 'material-ui/FlatButton';
+import ChipInput from 'material-ui-chip-input'
+import Popover from 'material-ui/Popover';
+import Menu from 'material-ui/Menu';
+import MenuItem from 'material-ui/MenuItem';
+import IconFilter from 'material-ui/svg-icons/content/filter-list';
+import ArrowDropRight from 'material-ui/svg-icons/navigation-arrow-drop-right';
+import RemoveImageConfirmation from '../imageManager/RemoveImageConfirmation';
+import UploadImages from '../imageManager/UploadImages';
+import { btnBase } from '../../styles/button';
+
+/** Image collection page in dashboard section */
+class ImageCollection extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ columnCount: 3,
+ selectedImages: props.images? props.images.map((image)=>false):[],
+ filterOpen: false,
+ filter: {value:"all", text:"Show all images"},
+ removeConfirmationOpen: "",
+ windowWidth: window.innerWidth,
+ gridWidth: window.innerWidth*0.50,
+ gridHeight: window.innerHeight-150,
+ columnWidth: window.innerWidth*0.50*0.33,
+ };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (this.state.selectedImages.length===0||nextProps.images.length!==this.props.images.length) {
+ this.setState({selectedImages:nextProps.images.map((image)=>false)});
+ }
+ }
+
+ componentDidMount() {
+ window.addEventListener("resize", this.windowResize);
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener("resize", this.windowResize);
+ }
+
+ toggleConfirmation = (value) => {
+ this.setState({removeConfirmationOpen:value});
+ if (value.length>0) {
+ this.props.togglePopUp(true);
+ } else {
+ this.props.togglePopUp(false);
+ }
+ }
+
+ windowResize = () => {
+ this.setState({
+ windowWidth: window.innerWidth,
+ gridWidth: window.innerWidth*0.50,
+ gridHeight: window.innerHeight-150,
+ columnWidth: window.innerWidth*0.50*0.33,
+ });
+ }
+
+ handleFilterClick = (e) => {
+ e.preventDefault(); // Prevent ghost click
+ this.setState({
+ filterOpen: true,
+ anchorEl: e.currentTarget,
+ });
+ }
+ handleFilterClose = () => {
+ this.setState({
+ filterOpen: false,
+ });
+ }
+ handleFilterChoice = (value, text) => {
+ this.setState({
+ filter: {value, text},
+ filterOpen: false,
+ selectedImages: this.props.images.map((image)=>false),
+ })
+ }
+
+ toggleCheckbox = (index) => {
+ let newArray = Object.assign([], this.state.selectedImages);
+ newArray[index] = !newArray[index];
+ this.setState({selectedImages: newArray}, ()=>this.forceUpdate());
+ }
+
+ cellRenderer = ({ columnIndex, key, rowIndex, style }, imagesToRender) => {
+ const index = this.state.columnCount*rowIndex+columnIndex;
+ if (indeximage.id===img.id);
+ return (
+
+
+
+
+
{this.toggleCheckbox(globalIndex)}}
+ labelStyle={{overflow:"hidden", textOverflow: "ellipsis", wordWrap:"break-word", width:this.state.windowWidth*0.50*0.25-50}}
+ />
+
+ )
+ }
+ }
+
+ getActiveImages = () => {
+ let ids=[];
+ for (let i=0; i {
+ return this.props.projects.find((project)=>project.id===projectID);
+ }
+
+ /**
+ * Returns items in common
+ */
+ intersect = (list1, list2) => {
+ if (list1.length >= list2.length)
+ return list1.filter((id1)=>{return list2.includes(id1)});
+ else
+ return list2.filter((id1)=>{return list1.includes(id1)});
+ }
+
+ handleAddChip = (chip) => {
+ // Link project to selected images
+ this.props.action.linkImages([chip.id],this.getActiveImages().map((img)=>img.id));
+ }
+
+ handleDeleteChip = (chip, index) => {
+ // Unlink project from selected images
+ this.props.action.unlinkImages([chip],this.getActiveImages().map((img)=>img.id));
+ }
+
+ selectAll = () => {
+ let selectedImages = [];
+ if (this.state.filter.value === "all") {
+ selectedImages = this.props.images.map(()=>true);
+ } else if (this.state.filter.value === "orphans") {
+ selectedImages = this.props.images.map((img)=>img.projectIDs.length===0);
+ } else {
+ // Filter is a project ID
+ selectedImages = this.props.images.map((image)=>{if (image.projectIDs.includes(this.state.filter.value)) { return true } else { return false }});
+ }
+ this.setState({selectedImages});
+ }
+
+ render() {
+ if (this.props.images) {
+ let imagesToRender = this.props.images;
+ if (this.state.filter.value.includes("orphans")) {
+ imagesToRender = imagesToRender.filter((img)=>img.projectIDs.length===0);
+ } else if (this.state.filter.value!=="all") {
+ imagesToRender = imagesToRender.filter((img)=>img.projectIDs.includes(this.state.filter.value));
+ }
+
+ // Generate info panel
+ let infoPanel =
Select one or more images to edit
+ const numSelected = this.state.selectedImages.filter((x)=>x).length;
+ const activeImages = this.getActiveImages();
+ if (numSelected>0) {
+ let projectInfo = "";
+ // More than one image selected
+ // Find all the projects in common
+ let projectDataSource = this.props.projects.map((project)=>{return {id:project.id,title:project.title}});
+ let commonProjectIDs = activeImages[0].projectIDs;
+ let commonProjectDataSource = [];
+ for (let img of activeImages) {
+ commonProjectIDs = this.intersect(commonProjectIDs, img.projectIDs);
+ }
+ commonProjectIDs.forEach((id)=>commonProjectDataSource.push({id:id, title:this.getProject(id).title}));
+ projectInfo =
+
{numSelected>1?"Projects in common":"Projects"}
+ this.handleAddChip(chip)}
+ onRequestDelete={(chip, index) => this.handleDeleteChip(chip, index)}
+ dataSource={projectDataSource}
+ dataSourceConfig={{text:'title', value:'id'}}
+ openOnFocus={true}
+ fullWidth
+ hintText={"Choose project.."}
+ />
+
+ infoPanel =
+
{numSelected} image{numSelected>1?"s":""} selected
+ {projectInfo}
+ {this.toggleConfirmation("delete")}}
+ backgroundColor="#b53c3c"
+ labelColor="#ffffff"
+ fullWidth
+ />
+
+ }
+ // Generate file upload panel
+ const uploadPanel =
+
Upload images
+
+
+ // Generate filter panel
+ const filterPanel =
+
+
}
+ {...btnBase()}
+ />
+
+
+ this.handleFilterChoice("all", "Show all images")} primaryText="Show all images" />
+ this.handleFilterChoice("orphans", "Show orphaned images")} primaryText="Show orphaned images" />
+ }
+ menuItems={this.props.projects.map((project)=>this.handleFilterChoice(project.id, project.title)} />)}
+ />
+
+
+
+
+ !x)===-1}
+ />
+ this.setState({selectedImages:this.props.images.map((image)=>false)})}
+ labelStyle={this.state.windowWidth<=768?{fontSize:"0.6em"}:{}}
+ disabled={this.state.selectedImages.findIndex((x)=>x)===-1}
+ />
+
+
+
+ return
+
+
+ {filterPanel}
+ this.cellRenderer(data, imagesToRender)}
+ columnCount={this.state.columnCount}
+ columnWidth={this.state.columnWidth}
+ height={this.state.gridHeight}
+ rowCount={imagesToRender.length%3===0? imagesToRender.length/3 : Math.floor(imagesToRender.length/3)+1}
+ rowHeight={200}
+ width={this.state.gridWidth}
+ id="grid"
+ />
+
+
+ {numSelected>0?infoPanel:uploadPanel}
+
+
+
0}
+ toggleConfirmation={this.toggleConfirmation}
+ deleteImages={this.props.action.deleteImages}
+ unlinkImages={this.props.action.unlinkImages}
+ imgs={activeImages.map((img)=>{return {id:img.id, label:img.label}})}
+ actionType={"delete"}
+ numToRemove={numSelected}
+ collectionsMode={true}
+ />
+
+ } else {
+ return
+ }
+ }
+}
+export default ImageCollection;
\ No newline at end of file
diff --git a/viscoll-app/src/components/dashboard/ImportProject.js b/viscoll-app/src/components/dashboard/ImportProject.js
new file mode 100644
index 00000000..f16b0559
--- /dev/null
+++ b/viscoll-app/src/components/dashboard/ImportProject.js
@@ -0,0 +1,163 @@
+import React from 'react';
+import FlatButton from 'material-ui/FlatButton';
+import RaisedButton from 'material-ui/RaisedButton';
+import {RadioButton, RadioButtonGroup} from 'material-ui/RadioButton';
+
+/** New Project dialog - import existing project */
+export default class ImportProject extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ importData: "",
+ importFormat: "json",
+ imageData:"",
+ }
+ }
+
+ isDisabled = () => {
+ if (this.state.importData)
+ return (this.state.importData.length===0);
+ else
+ return true
+ }
+
+ submit = (event) => {
+ if (event) event.preventDefault();
+ if (!this.isDisabled()) {
+ this.props.importProject({importData: this.state.importData, importFormat: this.state.importFormat, imageData: this.state.imageData});
+ }
+ }
+
+ onChange = (value, type) => {
+ this.setState({ [type]: value });
+ }
+
+ componentWillReceiveProps = (nextProps) => {
+ if (nextProps.importStatus==="SUCCESS") {
+ nextProps.reset();
+ nextProps.close();
+ }
+ }
+
+ checkIfFileTypeIsInvalid = (file) => {
+ const allowedFileTypes = ["json", "xml"];
+ return !allowedFileTypes.includes(file.type)
+ }
+
+
+ handleFileSelected = (files) => {
+ let file = files[0];
+ let importFormat = file.type.split("/")[1];
+ let reader = new FileReader();
+ reader.readAsText(file);
+ reader.onloadend = ()=>this.setState({importData: reader.result, importFormat})
+ }
+
+ handleImageFile = (files) => {
+ let file = files[0];
+ let reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onloadend = ()=> {this.setState({imageData: reader.result})}
+ }
+
+ render() {
+ let xmlMessage = "";
+ if (this.state.importFormat==="xml")
+ xmlMessage = Note : If the XML file was not originally created by this application,
+ some attributes and mappings may not be successfully imported.
+ However, the collation structure will always be importable from any XML file that follows the VisColl schema.
+ return (
+
+
+
Import
+
+
Import collation
+
Upload your exported collation file or directly paste the content of the file in the textbox below.
+
+
+ );
+ };
+}
diff --git a/viscoll-app/src/components/dashboard/ListView.js b/viscoll-app/src/components/dashboard/ListView.js
new file mode 100644
index 00000000..45613d81
--- /dev/null
+++ b/viscoll-app/src/components/dashboard/ListView.js
@@ -0,0 +1,37 @@
+import React from 'react';
+
+/**
+ * List the projects in a table format
+ */
+const ListView = ({selectedProjectID, selectProject, allProjects=[], doubleClick, tabIndex}) => {
+ const viewDate = {year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'}
+ const ariaDate = {year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit'}
+ const projectsList = allProjects.map((project, i) => {
+ return (
+ selectProject(i)}
+ onDoubleClick={()=>doubleClick(project.id)}
+ className={selectedProjectID===project.id? "selected":""}
+ tabIndex={tabIndex}
+ >
+ {project.title}
+ {new Date(project.updated_at).toLocaleString('en-US',viewDate)}
+
+ );
+ });
+ return (
+
+ );
+};
+export default ListView;
diff --git a/viscoll-app/src/components/dashboard/NewProjectChoice.js b/viscoll-app/src/components/dashboard/NewProjectChoice.js
new file mode 100644
index 00000000..13486072
--- /dev/null
+++ b/viscoll-app/src/components/dashboard/NewProjectChoice.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import RaisedButton from 'material-ui/RaisedButton';
+import {btnMd} from '../../styles/button';
+
+/** New Project dialog - select between starting with an empty project or pre-poulating it */
+const NewProjectChoice = (props) => {
+ return
+
+
+ OR
+
+
+
+}
+export default NewProjectChoice;
\ No newline at end of file
diff --git a/viscoll-app/src/components/dashboard/NewProjectContainer.js b/viscoll-app/src/components/dashboard/NewProjectContainer.js
new file mode 100644
index 00000000..a3aa30c4
--- /dev/null
+++ b/viscoll-app/src/components/dashboard/NewProjectContainer.js
@@ -0,0 +1,343 @@
+import React from 'react';
+import Dialog from 'material-ui/Dialog';
+import NewProjectSelection from './NewProjectSelection';
+import ProjectDetails from './ProjectDetails';
+import ProjectStructure from './ProjectStructure';
+import ImportProject from './ImportProject';
+import CloneProject from './CloneProject';
+import NewProjectChoice from './NewProjectChoice';
+import ProjectOptions from './ProjectOptions';
+
+/** New Project dialog wrapper */
+export default class NewProjectContainer extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ projectType: "",
+ step: 1,
+ title: "",
+ shelfmark: "",
+ date: "",
+ direction: "left-to-right",
+ quireNo: 2,
+ leafNo: 10,
+ conjoined: true,
+ startFolioPageNumber: 1,
+ generateFolioPageNumber: null,
+ startingTexture: "Hair",
+ collationGroups: [],
+ errors: {
+ title: "",
+ shelfmark: "",
+ date: "",
+ },
+ };
+ }
+
+ set = (name, value) => {
+ this.setState({[name]: value}, () => {
+ this.setState({errors:{...this.state.errors, ...this.checkValidationError(name)}});
+ });
+ }
+
+ reset = () => {
+ this.setState({
+ projectType:"",
+ step:1,
+ title: "",
+ shelfmark: "",
+ date: "",
+ direction: "left-to-right",
+ quireNo: 1,
+ leafNo: 10,
+ conjoined: true,
+ startFolioPageNumber: 1,
+ generateFolioPageNumber: null,
+ collationGroups: [],
+ errors: {
+ title: "",
+ shelfmark: "",
+ date: "",
+ },
+ });
+ }
+
+
+
+ /**
+ * Validate user input and display appropriate error message
+ * @param {string} type text field name
+ * @public
+ */
+ checkValidationError = (type) => {
+ const errors = {};
+ this.props.allProjects.forEach(project => {
+ if (type==="title") {
+ if (project.title === this.state.title) {
+ errors.title = "Project title should be unique";
+ } else if (!this.state.title) {
+ errors.title = "Project title is required";
+ }
+ } else if (type==="shelfmark") {
+ if (project.shelfmark === this.state.shelfmark) {
+ errors.shelfmark = "Manuscript shelfmark should be unique";
+ } else if (!this.state.shelfmark) {
+ errors.shelfmark = "Manuscript shelfmark is required";
+ }
+ }
+ });
+ if (Object.keys(errors).length===0) {
+ errors[type] = "";
+ }
+ return errors;
+ }
+ /**
+ * Return true if any errors exist in the project form
+ * @public
+ */
+ doErrorsExist = () => {
+ if (this.state.errors.title)
+ return true;
+ if (this.state.errors.shelfmark)
+ return true;
+ for (let group in this.state.collationGroups) {
+ if (!this.state.collationGroups[group].leaves && this.state.step===2) return true;
+ }
+ if (!this.state.title && this.state.step===1)
+ return true;
+ if (!this.state.shelfmark && this.state.step === 1)
+ return true;
+ return false;
+ }
+
+ /**
+ * Remove a group
+ * @param {number} groupNo group number
+ * @public
+ */
+ handleRemoveCollationGroupRow = (groupNo) => {
+ let newCollationGroups = [];
+ this.state.collationGroups.forEach((group) => {
+ if (group.number < groupNo) {
+ newCollationGroups.push(group);
+ } else if (group.number > groupNo) {
+ newCollationGroups.push({...group, number: group.number-1})
+ }
+ });
+ this.setState({ collationGroups: newCollationGroups });
+ }
+
+ /**
+ * Add a new group
+ * @public
+ */
+ handleAddNewCollationGroupRow = () => {
+ let newCollationGroups = [].concat(this.state.collationGroups);
+ for (let i=0; i {
+ let newCollationGroups = [];
+ this.state.collationGroups.forEach((group, i) => {
+ if (updatedGroup.number === i+1) {
+ updatedGroup = {...group};
+ if (field==="oddLeaf") {
+ updatedGroup[field] = value;
+ } else if (field==="leaves"){
+ updatedGroup[field] = parseInt(event.target.value, 10);
+ if (updatedGroup[field] < updatedGroup["oddLeaf"]) {
+ updatedGroup["oddLeaf"] = 1;
+ }
+ if (updatedGroup[field]===1) {
+ updatedGroup["conjoin"]=false;
+ }
+ }
+ newCollationGroups.push(updatedGroup);
+ } else {
+ newCollationGroups.push(group);
+ }
+ });
+ this.setState({ collationGroups: newCollationGroups });
+ }
+
+ /**
+ * Toggle the conjoin option for a specific group
+ * @param {object} updatedGroup
+ * @public
+ */
+ handleToggleConjoin = (updatedGroup) => {
+ let newCollationGroups = [];
+ this.state.collationGroups.forEach((group, i) => {
+ if (updatedGroup.number === i+1) {
+ updatedGroup = {...group};
+ updatedGroup.conjoin = !updatedGroup.conjoin;
+ newCollationGroups.push(updatedGroup);
+ } else {
+ newCollationGroups.push(group);
+ }
+ });
+ this.setState({ collationGroups: newCollationGroups });
+ }
+
+ handleToggleDirection = (updatedGroup) => {
+ let newCollationGroups = [];
+ this.state.collationGroups.forEach((group, i) => {
+ if (updatedGroup.number === i+1) {
+ if (updatedGroup.direction === "right-to-left") {
+ updatedGroup = {...group};
+ updatedGroup.direction = "left-to-right";
+ } else {
+ updatedGroup = {...group};
+ updatedGroup.direction = "right-to-left";
+ }
+ newCollationGroups.push(updatedGroup);
+ } else {
+ newCollationGroups.push(group);
+ }
+ });
+ this.setState({ collationGroups: newCollationGroups });
+ }
+
+ finish = () => {
+ const user = {
+ id: this.props.user.id,
+ token: this.props.user.token
+ };
+ let request = {
+ project: {
+ title: this.state.title,
+ shelfmark: this.state.shelfmark,
+ metadata: {
+ date: this.state.date
+ },
+ preferences: {
+ showTips: true,
+ side: this.state.generateFolioPageNumber!==null?{[this.state.generateFolioPageNumber]:true}:{},
+ }
+ },
+ groups: [],
+ folioNumber: this.state.generateFolioPageNumber==="folio_number"? this.state.startFolioPageNumber : null,
+ pageNumber: this.state.generateFolioPageNumber==="page_number"? this.state.startFolioPageNumber : null,
+ startingTexture: this.state.startingTexture,
+ }
+ this.state.collationGroups.forEach((group)=>{return request.groups.push(group)});
+ this.props.createProject(request, user);
+ this.reset();
+ this.props.close();
+ }
+
+ handleRequestClose = () => {
+ if (this.state.step===1) {
+ this.reset();
+ this.props.close();
+ }
+ }
+
+ render() {
+ let content = this.set("projectType",type)}
+ />;
+ if (this.state.projectType==="new") {
+ if (this.state.step===1) {
+ content = this.set("step", 2)}
+ previousStep={this.reset}
+ doErrorsExist={this.doErrorsExist}
+ />;
+ } else if (this.state.step===2) {
+ content = this.set("step", 1)}
+ nextStep={()=>this.set("step",3)}
+ finish={this.finish}
+ />
+ } else if (this.state.step===3) {
+ content = this.setState({
+ step: 1,
+ quireNo: 2,
+ leafNo: 10,
+ direction: "left-to-right",
+ conjoined: true,
+ collationGroups: []})}
+ nextStep={()=>this.set("step",4)}
+ set={this.set}
+ quireNo={this.state.quireNo}
+ leafNo={this.state.leafNo}
+ direction={this.state.direction}
+ conjoined={this.state.conjoined}
+ collationGroups={this.state.collationGroups}
+ handleToggleConjoin={this.handleToggleConjoin}
+ handleToggleDirection={this.handleToggleDirection}
+ onInputChangeCollationGroupsRows={this.onInputChangeCollationGroupsRows}
+ addCollationRows={this.handleAddNewCollationGroupRow}
+ handleRemoveCollationGroupRow={this.handleRemoveCollationGroupRow}
+ />;
+ } else {
+ content = this.set("step", 3)}
+ finish={this.finish}
+ set={this.set}
+ />
+ }
+ } else if (this.state.projectType==="import") {
+ content = (
+
+ );
+ }
+ else if (this.state.projectType==="clone") {
+ content = (
+
+ );
+ }
+ if (this.props.open) {
+ return (
+
+ this.handleRequestClose()}
+ className="newProjectDialog"
+ autoScrollBodyContent
+ >
+ {content}
+
+
+ );
+ } else {
+ return
;
+ }
+
+ }
+}
diff --git a/viscoll-app/src/components/dashboard/NewProjectSelection.js b/viscoll-app/src/components/dashboard/NewProjectSelection.js
new file mode 100644
index 00000000..f793208b
--- /dev/null
+++ b/viscoll-app/src/components/dashboard/NewProjectSelection.js
@@ -0,0 +1,72 @@
+import React from 'react';
+import AddIcon from 'material-ui/svg-icons/content/add';
+import CopyIcon from 'material-ui/svg-icons/content/content-copy';
+import ImportIcon from 'material-ui/svg-icons/action/system-update-alt';
+
+/** New Project dialog - select between creating new, importing and cloning project */
+const NewProjectSelection = (props) => {
+ return (
+
+
props.setProjectType("new")}
+ tabIndex="1"
+ >
+
+
+
+ Create new
+ Create a new collation from scratch
+
+
+
+
+
props.setProjectType("import")}
+ tabIndex="2"
+ >
+
+
+
+
+
+ Import
+ Import a collation from VisColl XML or JSON
+
+
+
+
+
props.setProjectType("clone")}
+ tabIndex="3"
+ >
+
+
+
+
+
+ Clone
+ Clone one of your existing collations
+
+
+
+
+ );
+}
+export default NewProjectSelection;
\ No newline at end of file
diff --git a/viscoll-app/src/components/dashboard/ProjectDetails.js b/viscoll-app/src/components/dashboard/ProjectDetails.js
new file mode 100644
index 00000000..937e9c77
--- /dev/null
+++ b/viscoll-app/src/components/dashboard/ProjectDetails.js
@@ -0,0 +1,67 @@
+import React from 'react';
+import {floatFieldLight} from '../../styles/textfield';
+import TextField from 'material-ui/TextField';
+import RaisedButton from 'material-ui/RaisedButton';
+import FlatButton from 'material-ui/FlatButton';
+
+/** New Project dialog - panel with a form to fill out project details */
+const ProjectDetails = (props) => {
+ let submit = (event) => {
+ if (event) event.preventDefault();
+ if(!props.doErrorsExist()) props.nextStep()
+ }
+ return (
+
+
Object Details
+
+
+ 0}
+ onChange={(event, newValue) => props.set("title", newValue)}
+ fullWidth
+ />
+ 0}
+ onChange={(event, newValue) => props.set("shelfmark", newValue)}
+ fullWidth
+ />
+ 0}
+ onChange={(event, newValue) => props.set("date", newValue)}
+ fullWidth
+ />
+
+
+ props.previousStep()}
+ aria-label="Back"
+ />
+
+
+
+
+ );
+}
+export default ProjectDetails;
diff --git a/viscoll-app/src/components/dashboard/ProjectOptions.js b/viscoll-app/src/components/dashboard/ProjectOptions.js
new file mode 100644
index 00000000..e8386fea
--- /dev/null
+++ b/viscoll-app/src/components/dashboard/ProjectOptions.js
@@ -0,0 +1,93 @@
+import React from 'react';
+import TextField from 'material-ui/TextField';
+import RaisedButton from 'material-ui/RaisedButton';
+import FlatButton from 'material-ui/FlatButton';
+import {RadioButton, RadioButtonGroup} from 'material-ui/RadioButton';
+import SelectField from '../global/SelectField';
+import IconButton from 'material-ui/IconButton';
+import IconHelp from 'material-ui/svg-icons/action/help';
+
+/** New Project dialog - panel with additional options for project creation */
+const ProjectOptions = (props) => {
+ return (
+
+
Project Options
+
Generate page/folio numbers
+
+ props.set("generateFolioPageNumber", v)}
+ style={{width:240}}
+ >
+
+
+
+
+
+ {props.generateFolioPageNumber?
+
+
+ Starting number
+
+
+ {props.set("startFolioPageNumber", parseInt(newValue, 10))}}
+ style={{width:50}}
+ type="number"
+ />
+
+
+ :""}
+
+
Select starting texture
+
+
+
+
+
+ {
+ if (props.startingTexture==="Hair"){
+ props.set("startingTexture", "Flesh")
+ }else{
+ props.set("startingTexture", "Hair")
+ }}}
+ width={250}
+ />
+
+
+
+ props.previousStep()}
+ />
+ props.finish()}
+ />
+
+
+ )
+}
+export default ProjectOptions;
\ No newline at end of file
diff --git a/viscoll-app/src/components/dashboard/ProjectStructure.js b/viscoll-app/src/components/dashboard/ProjectStructure.js
new file mode 100644
index 00000000..d3d23010
--- /dev/null
+++ b/viscoll-app/src/components/dashboard/ProjectStructure.js
@@ -0,0 +1,204 @@
+import React from 'react';
+import TextField from 'material-ui/TextField';
+import RaisedButton from 'material-ui/RaisedButton';
+import FlatButton from 'material-ui/FlatButton';
+import Checkbox from 'material-ui/Checkbox';
+import {
+ Table,
+ TableBody,
+ TableHeader,
+ TableHeaderColumn,
+ TableRow,
+ TableRowColumn,
+} from 'material-ui/Table';
+import IconClear from 'material-ui/svg-icons/content/clear';
+import IconHelp from 'material-ui/svg-icons/action/help';
+import IconButton from 'material-ui/IconButton';
+import SelectField from '../global/SelectField';
+
+/** New Project dialog - panel to create initial collation structure */
+const ProjectStructure = (props) => {
+
+ /**
+ * Return a list of MenuItem's for the unconjoined drop down menu
+ */
+ let menuItems = (selectedValue, unconjoinLeafsList, isDisabled) => {
+ if (isDisabled) {
+ return ([{
+ key: selectedValue+"blank",
+ value: 0,
+ text: "",
+ }])
+ }
+ return unconjoinLeafsList.map((val) => {
+ return {
+ key:val,
+ value:val+1,
+ text:"Leaf "+(val+1),
+ }
+ }
+ );
+ }
+
+ const collationGroupsRows = [];
+ props.collationGroups.forEach((group) => {
+ const unconjoinLeafsList = !group.leaves? [] : Array.from(Array(group.leaves).keys());
+ collationGroupsRows.push(
+
+ {group.number}
+
+ props.onInputChangeCollationGroupsRows(e, group, "leaves")}
+ style={{width:50}}
+ />
+
+
+ {props.handleToggleDirection(group)}}
+ checked={group.direction === "right-to-left"}
+ style={{marginLeft:8}}
+ />
+
+
+ props.handleToggleConjoin(group)}
+ checked={group.conjoin}
+ disabled={group.leaves<=1}
+ style={{marginLeft:8}}
+ />
+
+
+ {props.onInputChangeCollationGroupsRows(null, group, "oddLeaf", value)}}
+ data={menuItems(group.oddLeaf, unconjoinLeafsList, (!group.conjoin || group.leaves%2 === 0))}
+ value={group.oddLeaf}
+ disabled={(!group.conjoin || group.leaves%2 === 0)}
+ maxHeight={200}
+ >
+
+
+
+ props.handleRemoveCollationGroupRow(group.number)}
+ >
+
+
+
+
+ );
+ });
+
+ return (
+
+
+
+
+
+
+
Structure
+
+ Pre-populate your collation with quires and leaves by using the formula below.
+ Generate the items by clicking the "Add" button. You can add multiple times.
+
+
+
+ # of Quires
+
+
+ {props.set("quireNo", parseInt(newValue, 10))}}
+ style={{width:50}}
+ type="number"
+ />
+
+
+ ×
+ # of Leaves
+
+
+ {props.set("leafNo", parseInt(newValue, 10))}}
+ style={{width:50}}
+ type="number"
+ min="1"
+ />
+
+
+ {if(props.direction === "right-to-left"){return props.set("direction", "left-to-right")}else if(props.direction === "left-to-right"){return props.set("direction", "right-to-left")}}}
+ />
+
+
+ props.set("conjoined", !props.conjoined)}
+ />
+
+
+ props.addCollationRows()}
+ primary
+ />
+
+
+ {collationGroupsRows.length>0?
+
+
+
+
+ Quire no.
+ Number of leaves
+ Right-to-Left
+ Conjoin
+ Unconjoined leaf
+
+
+
+ {collationGroupsRows}
+
+
+
: ""
+ }
+
+
+ props.previousStep()}
+ />
+ {props.collationGroups.length>0?
+ props.nextStep()}
+ />:""}
+
+
+ );
+}
+export default ProjectStructure;
diff --git a/viscoll-app/src/components/export/Export.js b/viscoll-app/src/components/export/Export.js
new file mode 100644
index 00000000..abdc74ff
--- /dev/null
+++ b/viscoll-app/src/components/export/Export.js
@@ -0,0 +1,142 @@
+import React from 'react';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import fileDownload from 'js-file-download';
+import copy from 'copy-to-clipboard';
+import IconCopy from 'material-ui/svg-icons/content/content-copy';
+import IconDownload from 'material-ui/svg-icons/file/file-download';
+import IconButton from 'material-ui/IconButton';
+import JSZip from 'jszip';
+import saveAs from 'file-saver';
+import TextField from 'material-ui/TextField';
+import Checkbox from 'material-ui/Checkbox';
+
+/** Dialog to export collation to JSON, XML or PNG */
+const Export = (props) => {
+
+ const filename = props.projectTitle.replace(/\s/g, "_");
+
+ const isValidExport = props.exportCols > 0 && props.exportCols <= props.numRootGroups;
+
+ const actions = [
+ }
+ style={props.exportedType==="png"?{marginRight:10}:{display:"none"}}
+ onClick={()=>props.downloadImage()}
+ disabled={!isValidExport}
+ />,
+ }
+ style={props.exportedImages&&props.exportedType!=="png"?{marginRight:10}:{display:"none"}}
+ onClick={()=>{downloadZip();fileDownload(props.exportedData, `${filename}.${props.exportedType}`)}}
+ />,
+ }
+ style={props.exportedImages||props.exportedType==="png"||props.exportedType==="share"?{display:"none"}:{marginRight:10}}
+ onClick={()=>fileDownload(props.exportedData, `${filename}.${props.exportedType}`)}
+ />,
+ props.handleExportToggle(false)}
+ keyboardFocused
+ />,
+ ];
+
+ const downloadZip = () => {
+ fetch(props.exportedImages)
+ .then(function (response) {
+ if (response.status === 200 || response.status === 0) {
+ return Promise.resolve(response.blob());
+ } else {
+ return Promise.reject(new Error(response.statusText));
+ }
+ })
+ .then(JSZip.loadAsync)
+ .then(function (zip) {
+ zip.generateAsync({type:"blob"}).then(function (blob) {
+ saveAs(blob, `${props.projectID}_images.zip`);
+ }, function (err) {
+ console.log("error saving zip file!");
+ });
+ })
+ }
+
+ const exportedData = props.exportedType!=="png"?
+
+
{
+ copy(props.exportedType==="share"? window.location.href + "/viewOnly" : props.exportedData);
+ props.showCopyToClipboardNotification();
+ }}
+ >
+
+
+
+ {props.exportedType==="share"?
+ window.location.href + "/viewOnly"
+ :
+ props.exportedData}
+
+
+ :
+
+
props.setExport("exportCols", newValue)}
+ style={{width: 180}}
+ errorText={isValidExport? '': `Must be between 1 and ${props.numRootGroups}`}
+ aria-invalid={!isValidExport}
+ min={1}
+ max={props.numRootGroups}
+ />
+
+ props.setExport('exportNotes', !props.exportNotes)}
+ />
+
+
+
+
+
+ ;
+
+ return (
+ props.handleExportToggle(false)}
+ contentStyle={{maxWidth: 1000}}
+ >
+ {props.label==="XML"?
+
+ Note: custom folio numbers and page numbers will be lost when exporting to XML format.
+ If you wish to preserve all collation data, please choose JSON export.
+
+ :""}
+ {props.label==="Share this project"?
+
+ The URL below shows the view-only mode of your project.
+
+ :""}
+ {exportedData}
+
+ );
+}
+
+
+export default Export;
+
diff --git a/viscoll-app/src/components/filter/FilterRow.js b/viscoll-app/src/components/filter/FilterRow.js
new file mode 100644
index 00000000..fe398d70
--- /dev/null
+++ b/viscoll-app/src/components/filter/FilterRow.js
@@ -0,0 +1,201 @@
+import React, {Component} from 'react';
+import {floatFieldLight} from '../../styles/textfield';
+import MenuItem from 'material-ui/MenuItem';
+import TextField from 'material-ui/TextField';
+import IconClear from 'material-ui/svg-icons/content/clear';
+import ContentAdd from 'material-ui/svg-icons/content/add';
+import IconButton from 'material-ui/IconButton';
+import FloatingActionButton from 'material-ui/FloatingActionButton';
+import SelectField from '../global/SelectField';
+
+/** A row of filter query */
+class FilterRow extends Component {
+
+ renderAttributeMenuItems = () => {
+ if (this.props.type) {
+ return this.props.defaultAttributes[this.props.type].map(this.mapAttributeMenuItems);
+ } else {
+ return [];
+ }
+ }
+
+ mapNoteAttributeMenuItems = (noteType, index) => {
+ return { key:noteType+index, value:noteType, text:noteType }
+ }
+
+ mapAttributeMenuItems = (item, index) => {
+ return { key:item.name+index, value:item.name, text:item.displayName}
+ }
+
+ renderValueItems = (item, index) => {
+ return -1} />;
+ }
+
+ mapConditionItems = (item) => {
+ return { key:item, value:item, text:item}
+ }
+
+ filterConditionItems = (item) => {
+ let isDropdown = false;
+ try {
+ isDropdown = (this.props.defaultAttributes[this.props.type][this.props.attributeIndex]['isDropdown']);
+ } catch (e) { }
+ return ((!isDropdown && item && !item.includes("equal"))|| (isDropdown && item && !item.includes("contain")));
+ }
+
+ renderValueField = () => {
+ let input = ;
+ if (this.props.attributeIndex!=="") {
+ try {
+ if (this.props.defaultAttributes[this.props.type] && this.props.defaultAttributes[this.props.type][this.props.attributeIndex]['options']!==undefined) {
+ input = {return{text:x,value:x}})}
+ tabIndex={this.props.tabIndex}
+ errorText={(this.props.type!==null && this.props.values.length===0)?"Required":""}
+ onChange={(v,i)=>this.props.onChange(this.props.queryIndex,"values",i,[v])}
+ />
+ }
+ else if (this.props.defaultAttributes[this.props.type] && this.props.defaultAttributes[this.props.type][this.props.attributeIndex]['name']==="conjoined_to"){
+ input =
+ (
+ this.props.onChange(this.props.queryIndex,"values",i,[v])}
+ />
+ );
+ }
+ else if (this.props.defaultAttributes[this.props.type] && this.props.defaultAttributes[this.props.type][this.props.attributeIndex]['name']==="type"){
+ let dataSource = this.props.noteTypes.map((noteType) => {
+ return {text: noteType, value: noteType}
+ })
+ input =
+ (
+ this.props.onChange(this.props.queryIndex,"values",i,[v])}
+ />
+ );
+ }
+ else {
+ input =
+ this.props.onChange(this.props.queryIndex,"values",0,[v])}
+ tabIndex={this.props.tabIndex}
+ {...floatFieldLight}
+ />;
+ }
+ } catch (e) {}
+ }
+ return input;
+ }
+
+ renderRow = () => {
+ let row =
+
+
+ {let queryIndex = this.props.queryIndex; this.props.onChange(queryIndex,"type",i,v);}}
+ disabled={this.props.disableNewRow}
+ tabIndex={this.props.tabIndex}
+ {...floatFieldLight}
+ data={[{value:"leaf",text:"Leaf"},{value:"group",text:"Group"},{value:"side",text:"Side"},{value:"note",text:"Note"}]}
+ />
+
+
+ {let queryIndex = this.props.queryIndex; this.props.clearFilterRowOnAttribute(queryIndex, v, i); this.props.onChange(this.props.queryIndex,"attribute",i,v)}}
+ errorText={(this.props.type!=="" && this.props.type!==null && this.props.attribute==="")?"Required":""}
+ disabled={this.props.disableNewRow||!this.props.type}
+ tabIndex={this.props.tabIndex}
+ data={this.renderAttributeMenuItems()}
+ {...floatFieldLight}
+ >
+
+
+
+ this.props.onChange(this.props.queryIndex,"condition",i,v)}
+ errorText={(this.props.type!=="" && this.props.type!==null && this.props.condition==="")?"Required":""}
+ disabled={this.props.disableNewRow}
+ tabIndex={this.props.tabIndex}
+ data={['equals', 'contains', 'not equals', 'not contains'].filter((item)=>this.filterConditionItems(item)).map(this.mapConditionItems)}
+ {...floatFieldLight}
+ >
+
+
+
+ {this.renderValueField()}
+
+
+ this.props.onChange(this.props.queryIndex,"conjunction",i,v)}
+ disabled={this.props.lastRow}
+ errorText={(!this.props.lastRow && this.props.conjunction==="")?"Required":""}
+ tabIndex={this.props.tabIndex}
+ data={[{value:"AND", text:"AND"},{value:"OR", text:"OR"}]}
+ {...floatFieldLight}
+ >
+
+
+
+ this.props.removeRow(this.props.queryIndex)}
+ style={(this.props.queryIndex===0 && this.props.queriesLength===1)? {display:"none"}: {}}
+ >
+
+
+ this.props.addRow()}
+ style={(this.props.queryIndex===this.props.queriesLength-1)? {marginLeft:10} : {opacity:0,pointerEvents:'none',marginLeft:10}}
+ secondary
+ disabled={this.props.disableAddRow}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+
+
+ return row;
+ }
+
+ render() {
+ return this.renderRow();
+ }
+
+}
+export default FilterRow;
diff --git a/viscoll-app/src/components/global/AppLoadingScreen.js b/viscoll-app/src/components/global/AppLoadingScreen.js
new file mode 100644
index 00000000..f78f2253
--- /dev/null
+++ b/viscoll-app/src/components/global/AppLoadingScreen.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import logoImg from '../../assets/logo_white.png';
+import CircularProgress from 'material-ui/CircularProgress';
+
+/** Stateless functional component for the app loading screen */
+const AppLoadingScreen = (props) => {
+ const logo = ;
+ if (props.loading) {
+ return ;
+ } else {
+ return
+ }
+}
+export default AppLoadingScreen;
diff --git a/viscoll-app/src/components/global/ImageViewer.js b/viscoll-app/src/components/global/ImageViewer.js
new file mode 100644
index 00000000..70f8e2b8
--- /dev/null
+++ b/viscoll-app/src/components/global/ImageViewer.js
@@ -0,0 +1,108 @@
+import React from 'react';
+import OpenSeadragon from 'openseadragon';
+import UUID from 'uuid';
+import BlankPage from '../../assets/blank_page.png';
+
+/** Image viewing component (OpenSeaDragon) */
+export default class ImageViewer extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ suffixedID: 'openseadragon-' + UUID.v4(),
+ osd: null
+ }
+ }
+
+ componentDidMount() {
+ var tilesSources = [];
+ if (this.props.rectoURL && !this.props.isRectoDIY) {
+ tilesSources.push(this.props.rectoURL + "/info.json");
+ } else if (this.props.rectoURL && this.props.isRectoDIY) {
+ tilesSources.push({type: "image", url: this.props.rectoURL});
+ }
+ if (this.props.versoURL && !this.props.isVersoDIY) {
+ tilesSources.push(this.props.versoURL + "/info.json");
+ } else if (this.props.versoURL && this.props.isVersoDIY) {
+ tilesSources.push({type: "image", url: this.props.versoURL});
+ }
+ if (!this.props.rectoURL && !this.props.versoURL) tilesSources = [{type: "image", url: BlankPage}];
+ this.setState({
+ osd: OpenSeadragon({
+ id: this.state.suffixedID,
+ prefixUrl: 'https://cdnjs.cloudflare.com/ajax/libs/openseadragon/2.3.1/images/',
+ showNavigationControl: true,
+ showFullPageControl: false,
+ showRotationControl: true,
+ showNavigator: true,
+ collectionMode: true,
+ collectionRows: 1,
+ collectionTileMargin: 1,
+ crossOriginPolicy: 'Anonymous',
+ navImages: {
+ zoomIn: {
+ REST: "zoomin_rest.png",
+ GROUP: "zoomin_grouphover.png",
+ HOVER: "zoomin_hover.png",
+ DOWN: "zoomin_pressed.png"
+ },
+ zoomOut: {
+ REST: "zoomout_rest.png",
+ GROUP: "zoomout_grouphover.png",
+ HOVER: "zoomout_hover.png",
+ DOWN: "zoomout_pressed.png"
+ },
+ home: {
+ REST: "home_rest.png",
+ GROUP: "home_grouphover.png",
+ HOVER: "home_hover.png",
+ DOWN: "home_pressed.png"
+ },
+ rotateleft: {
+ REST: "rotateleft_rest.png",
+ GROUP: "rotateleft_grouphover.png",
+ HOVER: "rotateleft_hover.png",
+ DOWN: "rotateleft_pressed.png"
+ },
+ rotateright: {
+ REST: "rotateright_rest.png",
+ GROUP: "rotateright_grouphover.png",
+ HOVER: "rotateright_hover.png",
+ DOWN: "rotateright_pressed.png"
+ }
+ },
+ tileSources: tilesSources
+ })
+ });
+ }
+
+ componentWillUpdate(nextProps) {
+ this.addTiles(nextProps.isRectoDIY, nextProps.isVersoDIY, nextProps.rectoURL, nextProps.versoURL);
+ }
+
+ addTiles(rDIY, vDIY, r, v) {
+ if (this.state.osd) {
+ var tilesSources = [];
+ if (r && !rDIY) {
+ tilesSources.push(r + "/info.json");
+ } else if (r && rDIY) {
+ tilesSources.push({type: "image", url: r});
+ }
+ if (v && !vDIY) {
+ tilesSources.push(v + "/info.json");
+ } else if (v && vDIY) {
+ tilesSources.push({type: "image", url: v});
+ }
+ if (!r && !v) tilesSources = [{type: "image", url: BlankPage}];
+ this.state.osd.open(tilesSources);
+ }
+ }
+
+ render() {
+ let style = {width: "100%", height: "500px", background: this.props.backgroundColor? this.props.backgroundColor:"black"};
+ if (this.props.fixed) style = {position: "fixed", width:"42.5%",height:"82%", left: "30%", background: 'black', padding: 5};
+ return (
+
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/global/LoadingScreen.js b/viscoll-app/src/components/global/LoadingScreen.js
new file mode 100644
index 00000000..0481be2d
--- /dev/null
+++ b/viscoll-app/src/components/global/LoadingScreen.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import Dialog from 'material-ui/Dialog';
+import loadingImg from '../../assets/viscoll_loading.gif';
+
+/** Stateless functional component for the loading screen */
+const LoadingScreen = (props) => {
+ const logo = ;
+ return (
+
+ {logo}
+
+ );
+}
+export default LoadingScreen;
diff --git a/viscoll-app/src/components/global/ManagersPanel.js b/viscoll-app/src/components/global/ManagersPanel.js
new file mode 100644
index 00000000..79a91ddd
--- /dev/null
+++ b/viscoll-app/src/components/global/ManagersPanel.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import Panel from './Panel';
+
+/** Stateless functional component for the Managers panel in sidebar of project edit page */
+const ManagersPanel = (props) => {
+ return (
+
+ props.changeManagerMode("collationManager")}
+ tabIndex={props.popUpActive?-1:0}
+ aria-label="Collation Manager"
+ >
+ Collation
+
+ props.changeManagerMode("notesManager")}
+ tabIndex={props.popUpActive?-1:0}
+ aria-label="Notes Manager"
+ >
+ Notes
+
+ props.changeManagerMode("imageManager")}
+ tabIndex={props.popUpActive?-1:0}
+ aria-label="Image Manager"
+ >
+ Images
+
+
+ );
+}
+export default ManagersPanel;
\ No newline at end of file
diff --git a/viscoll-app/src/components/global/NetworkErrorScreen.js b/viscoll-app/src/components/global/NetworkErrorScreen.js
new file mode 100644
index 00000000..1d4d0bba
--- /dev/null
+++ b/viscoll-app/src/components/global/NetworkErrorScreen.js
@@ -0,0 +1,47 @@
+import React from 'react';
+import Dialog from 'material-ui/Dialog';
+import {API_URL} from '../../store/axiosConfig';
+
+/** Dialog to show disconnection from API */
+export default class NetworkErrorScreen extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ isOffline: false,
+ }
+ }
+
+ componentWillMount() {
+ let that = this;
+ let apiRequest = () => {
+ fetch(API_URL)
+ .then(() => {
+ if (that.state.isOffline) that.setState({isOffline:false});
+ }).catch(e=> {
+ if (!that.state.isOffline) that.setState({isOffline:true});
+ });
+ }
+ setInterval(apiRequest, 5000);
+ }
+
+ render() {
+
+ const imgSrc = "data:image/gif;base64,R0lGODlh+gD6APf/AIqctVFrkJnY1tLZ46XZ16Sxxfb4+krVyllylnqOqu7x9EbUyEZiio3h20pljExnjeLy81rDueHm7FzJvPT2+Pj5+uTo7ljNyFSdqrLb27G9zfL091Jljkhjivr7/HfKxMrR3a66y1bFumd+nvn6+0LQxmbLxlrQyo/a0dzx8XvT0N3i6e/7+qjk4bXA0HGHpNLt7WrTzvDz9tbc5fv8/YGUr1x1l+j19vH5+ff8/E7YzJ2swcXO2mV8nZChuEPNxLzg4IWYsn6RrU7XzMzr6+ns8UpeiXPSz2yCobLj48Lk5EJdhaXf3rvG1Kq2yUzMxpmpvjzNw3OIpuz29kfOxc7W4ITKyFVxlWF4mkRgiIKVr/39/ubq75WlvGqAoMPp6dfd5tns7Zvd3JSkusHK2Mbn6GPPy5HTzrnE00ZiiFRuknaKqLrF007Ryb3H1evu8kpkjKGvw0TSx/P19/3+/mHCu9jv8FvIvFTBt26Do+v490vWy9Tb5LHf3tvh6Ozv82nFvvb6+4DW0/P8+0PUyM3U30lki01njr/J18vl5mXc0o/b2bTo5rjC0j1ag4zXz9DX4YnY1d/k62HMv0rQx+73+E7ez/P6+r/o6EXczUnSyLnn5svT3mLLv8jv7W7d1E9qkLHs6MLL2EzYzG+FpMfP201ojVjHumJ6m0ViiYXa1o6guGR7nZenvc/08U7Wy/b7+1Zvk/X5+nDGwEfKw0HKwkLTx2HKvkdjiuL590d3lkDMxP///09pj/7///Dy9U9qj/7+/k5pjv7+//b4+U5pj/b3+U5oju/x9WHLvk7Xy0VhiWHLv+jr8PT1+GLRwV/Lvu3w9EvYy1F/m+rt8kNgiOrt8UvYzEZhiu3w82mAoE/XzE5oj09pjtnf5/Hz9tPa409ojorq4F93mWh/n05ijEvXy0Nfh/P5+Y2et3CptEViiu3v86i1yKa0x8jQ3LS/z1vPvrTA0M/o6FDVy0hrj1HOx0dpjk3Dtfj7/F7KvWzVz4jc2FrAuFJskf///yH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpkMWUyYmU2NS00MmU1LTQ1OWYtODQ0ZC03NWEzNGJjNDc5NWEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NkE3N0I3NTQzRTRGMTFFN0FGQjhDMDgyNjAyQjEzMTEiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RDBERTZBRkMzRTRFMTFFN0FGQjhDMDgyNjAyQjEzMTEiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpkMWUyYmU2NS00MmU1LTQ1OWYtODQ0ZC03NWEzNGJjNDc5NWEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6ZDFlMmJlNjUtNDJlNS00NTlmLTg0NGQtNzVhMzRiYzQ3OTVhIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAAIfkECRQA/wAsAAAAAPoA+gBACP8A/wkcSLCgwYMIEypcyLChw4cQI0qcSLGixYsYM2rcyLGjx48gQ4ocSbKkyZMoU6pcybKly5cwY8qc6ZGGBTRBEDgw5MCfz59AgwodSrSo0aP+OvyiyTSmBCG4QCGdSrWq1as/lTbdGlLCOGFYhwYwxWCEOwlbuKpdO/KXDalYAyzrsqGjAWR8nEAJMs5fMVwMAgseTLiw4cOIB2NzpICt44M0WAG7yiAERQpxGAQIy7mzVa2P2a5gUDWAmmgRBzjY7LnXgywv4nibwyu0bZkhek59AAXiAENYDY0xdru4Y15SqBpi89APrqqHfNR+GG0NcM/Yr4I2vvKN7qPCCjj/9CUE7tEHXRwOk2Ieay8H8OPLn0+/vv378hlzXylkarFWDtGAwFSmiMcQDTZUBUwz+zUYkijXGRXAKg6RwNpRDqDGUBwPIHXIDg6GGBIjcrxi4okommhOJBVeaJQDyDRERodIBUCMiDhytMUFOqTo4yu2eOKQB7FMJYwTDlkQIVIPaJFWjlBShEmJP6ZYgh0OBTMCVQ+4AxEP32UnJlbbRSkSBFRWeaIOT3jwkBtLHlUgRWDk0YGLY+bpU5lmjnQJJcqoiaIyF9AA0YxYVRNCMH02ehIsT/Qo6Inb7AKBRG50gF0Ax3RwRToD/MKoo6RaRAMz10yaog4lKFGRJFKE/6nnrJ8tVWpMEPxgjqo+bnOAGZd2RIICPLQzRnIPCPbAssw26+yz0EYrrbNLNHYrVzQsYoukvFbpqyYxYILDdNeWyxUvMJywALfdtuvuu/CqSQgL5t5KAwxiXCCHHOawG++/AP84b70EF2zwwQgnrPDCDDfs8MMQRyzxxBRXbPHFGGes8cYcQ2lNCDZERevInPHZsbDunIPnrMAAA4oDgzkw7cw01xztEjGebJExSJji2SEddPGGzg7zsgONVgFjAxgW+bICFKA80B7JI5tM9D/k+VzVOpZFNAw8KlMtdlVWc5wb2UVANMc4K1O1DAAWXL2fJJpOZQgnDxmAlRpxT//0RhyoZCFML2OTaavGFfjTdlAB9PAkQxY8N1UA5DzOkAvYFE5y2RKTEMviQDmASIBUBcCK5QlJUHfp/rgxqtwtKVljLCQ4FIesQ/WSTkMVTIYUMGsMAztT8tRzxfHIJ3/8PXg3ZAzuQvWiDbkKpYN0UQxIMHxTZaSppjkn0OHQDtAHBYwPDrVTflCgCEH99i/B4L2aVzpkzDGTI/H6QhasX9QxAYiDBVAHv5J8wRa8ssUmHrICydVICvtjSDB8ECfNZYdzFfPFEXalKh2YQXwOKUIFJRSLoWHEAFwAQSuCsIYRFKNlMIyhDGdIwxrasGVZOJzOUlCCQKlqGyXARET/ZuA/o/QiACEwQAEdJIYFtGsbcmDCRCoghOtZ8Ip70uESfaECDj5xAZGAxUVIUAhS4MKKWMQOBpcokFx58V1DOAAVxIADNlpsGEmgwhsDdiJlmGMUhCgBuI4gBlfYIQU3qIQvFsmLRjrykZCMpCQnSclJ+uJ9duSILFpwgQWYw4d8DKUo5UWvTLJlCzTIgSpzoIcyuPKVsIylLGdJy1rWUgmGMqUud8nLXvryl8AMpjCHScxiGvOYyEymMpfJzGY685nQtFcFuPAONoQgHXkYARYSlMapLEOL0VSIMUQhBFMYYmrd1NMal+kLPyRAZOns5jqHSQMXqIFw8cwnUObJ/0tfgA10YeHUMg5BinTwQAJzGAYmw7mSYbTDgZ0hixSqkEuGrsUtvguLKV7ANIvaxgAjyKhVHpCAulyEF79AQwKOITOR6tMo/DwZJ1KBFQesonYTGcYMSNEBdL40LDHV2DtIYxUGNGEixhBCEX/amaBazBmfU9ALCLiQDYwAn2NyzSFg9oAAIOCrYA2rWMdK1rKaVazhAOfJ0LDUn3QpNWDpDDBwEQQ/gNCjGTnb5EZAVYTQIAFYtco53IFTvHrEDW31hyFE8RBeaMGlSKnGUS3Ciw1IgAdQSIcQEsDZznr2s6ANrWhHy1lSzOFkFVADVRwAgodYIAtWcYAbJMIOH/8wAH9MLYpTGYbYqRwDQA5xTlUcwAWIIIKouaXKbhU2BsgOxQF/cIjeqGKK3TlkADRNruE2BoXAEiUANqjoQkSxOqOAogYOmQNEqVIMBtQABEo07EAgMULGxcJNDWlEfYOyjOIyZAOJVVwPviHfhGyhL0hhgB8cUoi2HsKkC9HrUYABwQIvhL41WoMvGrKFHkxFwQ3hARqHYggCW3gh8NivW11gv6WCYhziTYj1kOIAPpyYIaEghA52zOMe75gQrmgxUl4cY4RwCCmmiMONF7KJ+f1oFEfYMEO2gIqppEISzhuxUECRgCUnJAd7+CEVbuCQUrQVGBRoiICqogZreZn/IKFw4qQ8KLyGOKGtD1DrQUZjlWIgYMFvpgOPVGUOfiz0IE0ob1HC0ZshecGnXIKPIUxxiEpb+tKYzrSmN81pnG2MDvYApZrobL9iUBdEDxmGFnCrXeXqmWLBGLSq5EBmh4BgvUdx80PAgIUA/3S5C2uBnOccvubg2ijF6NtEtjCAIDzAEKZuNbAXlo9I8cqDd50yKQAqlADEwr9v5ggEenjtXUwBIiQgh3OP0otYgCPcGoHALrbBqyEsQBB1fogojl2VB8QiBBCGN0RoYAZ/CeoaJuirQgbgD0iHBRiHSNZzbCCEIIzh4hjPuMY3zvGOezzjACBOAVMQBYOPulUU/1mBF3zd6qNM22JlKIHJT94Hi1QgBP7Qcstd/urt4cAeo4DXKNpABI2QYAZdsEEHlrVzobycY2UoebyGYA4zwODQApfJFC6QKoDp4ABPEEMlsr4VO5xgjwBThg7kQIkjYCIFNMA62UsSjDxKY5STUsYQlHGAA+zh74APvOAHT/jCG97wtihlOAPBBCqsC++QjzyKBnZiOkCACfZYwOMlz3l3UX7uAsnBDZLABDPQQhOa9+SOhzCEzovy86CPvexnT/va2/72uM+97nfP+977/vfAD77wh0/84hv/+MhPvvKXz/zmO//50I++9KffJ16g0gMkyL72t8/97nv/++APf/8FpCx7GjTjHU5YAxZMsRP4RLvpSem5fHlhjHcAQCeGWDf8nS7/cHoAEkHQDT21fyXTf8zkC5IQBIagcwRYKxbFC+4ETw04Jk+nSxsAAAzgcBPoGRXIRhKABCy3gdphgL4kAeQQglPhMg6wDL2ABDXgA24ABivABc3wC98gAziYgzq4gzzYgz74gz4oKshEATXAgFchUKiwAzPwC/kmfU2wDNw2OYYwDnGAFtRXECSgBUbIJIbQCpIQQVcoEFalgUchFz5gQmF4EBjFGYegBk0AhhtBAwZAAd+gAMhwh3iYh3q4h3zYh36Yh+wAh2xkDONAhkQBCjYwABthDN7gDgn/EAAMIDNMJ20kSDRb8AL6VxRyAQ+C+BBz0ARrwAAPkIkt14EUowHI1W9eEHAREQwzsAZLJ4JDYYoRgwyKcxW44A5ypxArYB2GuIG0+DC3QxksJhG+UAo5J4uutkRzEFVVgQsaMBEzgAXvp4xTEYwLQwa+9gBxsIsGUQEJsIXWyH/wEwSs9juoEF8P4RXexRm9YE4PIAVQoAFgEA0bYAwekI/6uI/82I/++I8AyY8k4I0Y02FR+BNZ0DwPMQBaI1cMgAQa8AedOHeeUxqooHAHwQUqNmG4ADe9ZwC/6BMP0DUV0gPtuBuo4A3AB5JVYQhp8xAS1m/jgGXBV5FUcQUi/+c8RXIVhuAEBOkQlRSUQjmUj6QzwUAOVeE4DwEC4vgTm9FRE0EDklAAa1AMy+AAwNALWrmVXNmVXvmVYBmWXJkFOdMxAECKP6GUtuNrqfBuEsEF6TBpB5lO2FguvTUVMDYeCRCSioUkELEFaIAN5wh/dXkr0ZBYxXBaDeELe1k6CKCODVEFUiOLhUkqvLAGVMEAKwCUj0hdXgKULpCKIliZjgIJokkUhoAGD3Fk3gSVDEEGmTOO8bcxwdADURgASNCECsEDilYUhqBrCrEBg2mNpNknhXCaQ8EANMkQc9BWAfAAwJkQiICCxYCVWqABPDAA2rmd3Nmd3vmd4Bme2v9JURoTDNtWI0IAlFrwi7gwAw4BBU35E7ggBBLwk88kARvpE8rpEJLQVsfglwzRBfHpVQwSbi6wVAEgBQTJC0KAlgEwAtmWEE7QlF5lYuFmnh9WCOkljsuwmQzRn6tVjAJHAr+IDeC2EGyFFL2goAxxmWjpDyBGdt6Qn5QzkQSRHEhxDCSpENnQm0PxALM1d6VAozVAkB7ATUfBAMqmECGwhRRmn4YFJ1NhXQzRO7thoQohoDm6o1kHD3cSAGAapmIaAFmgoc6zVC1IkDN2FKlQoHMXCj/QBpQgp3Q6p3PaBj8gJGeqokhAkGOwhaYgomSXY6oSJNI1nD/KigjRpEj/pgWxxwjDpiajoAJDMiAJ5qYKwQ78FhSG4JZkNyWqMgpmYKMCwQukMBUOwFgSJAUv2gHaQ3Z24GQ+sgf08JMHqqJrQJB+kFgMoIhZRweAoiolAAMOwQVttQyvuiFNCQqkgJHyNQwxMHMoYg6LkCV5kIK52hC8kAAvKpIJIAPwhgkIlHdtUGQIUQVtZQg8wJl86RPY0Ao5eWI5oAm8Igd6yhCM+aLLsKQL4Q75+aMOwApxgAgSQAEGcLAIm7AKu7AM27AOawDGQKpFEwl3l3f2YK4H0QxN+QCK2RAWsAyymRWVyDAQEKlqsgCM8BDwkFhoBhE+gKjAOLILwwuCgHY//0IFevAQ60kVx/CSDrEFqzaOxdknNyCrPoJtWSIFc2kKjRARvEAGy9CuvyazDCNsvHIA1TokCDY5pBChvNMKDiC18TS0fSJo0ooiC7BAAYIEfGkIQSoRWwAC2oAL1UiJJ3MD5DZrZdBYNdCtilNCFyED76AFanCVxSAMwuC3m0O1DUMEJcAryiAHRaeyvuZtaKgRvBAMFLC5nNu5nvu5oBu6oru5cyCxE8MEhNAtcvAFEFEEm/pdAeCrxCcIQXe1UtRYAHAIWEFSWPp7vNBF3WIOgkB+DbEBzkgZWqAhvgu8kGsPYwcRt9YZD4AAjQCZu9dEoqYm20AItwsRbICcI/8FCukwA4WFe0lgsoKiA84rEe/wulZhTriQB63QCFzwC8aAseEGAVFAb92yAIsApQKBDD1Qt3piCjZzwAhMLdF5NR5wAbXLK0CUBBPhC987l0LLuBbDBOibvig3ER7gDpoRsrqFwRZTCZTQeu3CKkBQEcHAA7EQnw1Itgijwe+ydkxAvBJhDCFwBWErwjKMMLAga190Aud2EcGwAjsQCzKjjD+cMDegR/AyBCUgBl5bER7wB/KwCrGwdMtyklPrSxCgCSj8LqPwA0xQxRoxDAYwB7/wBm/wDmjQBHI8x3Rcx3Z8x3icx3PsAuXLS3YAxfHCKpHwvGm4EDfwBF03dWD/JwboUMgKgQMmsAD8+y9fRwWLcAO66cj/wAt9IHWhNApsdwRlgAM4nIY4cAQHYLP/ogzXEEgncARfYAeKBMDFNwUxIMacpwzKsAfmYA62UAI/sAtUoAiKsA8xcMzInMzKvMzM3MzO3MwnMAjPNAUqYAtj7HrYHEqw50we0AdPsADXnM3i3C7bHE04wATffLbjvM6vUM4WRQcwcAQlsHnsXM/uLF/3IgbfTM/13Hn3vGSWlwSC8ARyoHk6EM79/C//THZ0kAODUAZJ0AL8cAFPQAWOl3oYndEavdEc3dEejdFRoHiaPNIkXdImfdIondIqvdIs3dIu/dIwHdMyPdM0B13TNl0wAQEAIfkECRQA/wAsAAAAAPoA+gBACP8A/wkcSLCgwYMIEypcyLChw4cQI0qcSLGixYsYM2rcyLGjx48gQ4ocSbKkyZMoU6pcybKly5cwY8qc6ZGGBTRBEDgw5MCfz59AgwodSrSo0aP+OvyiyTSmBCG4QCGdSrWq1as/lTbdGlLCOGFYhwYwxWCEOwlbuKpdO/KXDalYAyzrsqGjAWR8nEAJMs5fMVwMAgseTLiw4cOIB2NzpICt44M0WAG7yiAERQpxGAQIy7mzVa2P2a5gUDWAmmgRBzjY7LnXgywv4nibwyu0bZkhek59AAXiAENYDY0xdru4Y15SqBpi89APrqqHfNR+GG0NcM/Yr4I2vvKN7qPCCjj/9CUE7tEHXRwOk2Ieay8H8OPLn0+/vv378hlzXylkarFWDtGAwFSmiMcQDTZUBUwz+zUYkijXGRXAKg6RwNpRDqDGUBwPIHXIDg6GGFITERYFSnoNwRIBM7e06OKLt3SCxw0NkdEhUgEQI+KOHAWDxFQMQOKQBxEkA+ORt+hDgEMWlHieFmnxKCVFIJCGFAMWOLQFIJMgeeQESz7Ew3fZlYnVdlOK1IyTRSFAw0MZnOIlkvoIQBEYeXRwoZl8ZrVUmiYZ4M+eRrESpUNA3DGnl8ngQQAdgEZ6UgUIEGpUKkVI1IcInSzqaYzQnFKHFURMAamkqFq0BSmTWWUIJxWF/zHLHV1+auutuOYK4yk4pCpTEQG0V9UDebzhkSxTACEACh/0g4cIIpwywbTUVmvttdhmq621IvTqK1c0FPBcdoc8kAAnxH2rrmh5kNnnu/D6hOa6kdKwgjs97NRqvPwONS+9AAcs8MAEF2zwwQgnrPDCDDfs8MMQRyzxxBRXbPHFGDtoTQg2RNXvx1T9mzFHJLhzjqV9AgMMKA4M5sADMMcs88w012zzzTcvgczIFxmDhCmeHdJBF8bynDAvO9xoFTA2gGGRLytAAcoDwoLMp8hGkwd0VetYFtEw8Jxs9dhFYX1xblV1kOlDc4yD8lTLAJCl0ftJ0oFysDok6FVqzP8t0RtxoJKFML2QnfafFlcwKI49HLqQBePiSI7jC7mAjeHvmt0wCbG8DZQDiARIVQCGNiTB3aX540YwdMPUJI6xkOBQHO4O1Us6DVWwr1HArDFM603Bw6ZQDIDgEDEYJK/88surM91C6ShdFAMSAL/VDMMHFQASrDfEhKKeJmPF8wq1U/tQoAhBvvUwrZB9UFg6JMuKn0Izy6kMWXD+UccEEIcFlGOfSQphpaMYggwPCYMIcHU/iATDB+/DnHYQdzFe1KBqRPHdQyCAh07dKhkRgEBGDMAFELQiCGsYQTFUxsIWuvCFMIyhDFWWBQqOjAuRQ8o6qhCReeCBRbq6BTT/IkAAdAgwRE7YH1EY4IKJGOADd/BgEKdIxSq2iFdHLAgvACA9qjgACh64SD7KMAsRTACIVkwjFbGYxYUAC4NU6VAIDNBGifECEWoo3LuC5YBl9IIUQSgAHyTRDAVQYBi+4IUiF8nIRjrykZCMpCTXV0eOVKARPTAEHCUIMs1VUibBoIEHSGAAA7xhAAMAhypXycpWuvKVsIzlK6vwpk/a8pa4zKUud8nLXvryl8AMpjCHScxiGvOYyEymMpfJTF/RoAJceAcbQpCOPIwACwniJFKWYcNmKsQYohCCKTSpTX558pi+8EMCPFZOCZ7zlzRwQR7bSU8/FdMXYfNcWAJw/4xlHIIU6eCBBOYwDEp6cyXDaEcOOUMWKdDyoI5xy+6uYooXOA2itjHACCYaxwTU5SK8+AUaEnCMl3G0nv7qpgA5kQqsOGAVspvIMGZAig5sEqWHa+M7CkgVBjRhIsYQghJx6pl3VswZnVPQCwK4kA2MQI9mcs0hWvaAACDgqljNqla3ytWuelWr4VBpxtAw1KA8wB2pAUtngIGLIPgBfxjFCNqmEoARMBUhNEgAVK1yDnfENK4ecUNZf2IIUTyEF1o4qVGq8VOL8GIDEuABFNIhhARY9rKYzaxmN8vZzlqWFHMYWQXU4EXjMSkLVnGAGyTCDh8w4BhELZtYIybYqf8cA0AOcU5VHMAFiCCCp7E9ilEVNgbFBsUBf9BbVUyBO4cMoKXBneDFoLBXogTABrVkiChQdxRQ1MAhc1iofxhQAxDQEbADgUQEfRKAWISxIY1Yr0+W0VuGbGCwPwlAD76B3oRsoS9X8oNDCoHfQ3x0IXM9CjCk0L3+JkS9OFqDLxqyhR4AScAM4UEXiWII/jp4IcJDygOa2BBjDBYU48iuQqKHFAfw4cMMCfFRiueQHCiqEzjOsY5vkQwT3LUgHEKKKeIA44XwQL4BCEKA6lCrRYkgBSXeMPoSUOSEVKC6RMkQouTkqU5EoBINEVBV1NCYKhdEA4MNQAIMehACgM//UxGYgkNGY5ViIADDVQ6GNvTpk16gqCFA4LKn9HGGh3jACzc9D3wMYYpDOPrRkI60pCdN6Urr7GL/5fOgpMBmg+CgSLYitAODANvohmy2DxtGHuLIjocoQdDh6weNIgIGLOCXqMNdWCNuzb0ELhBX/YAyRbYwgCA8wBDFMHWuF+aBAVHlBQ1miAdmYSRceVmEZu5IEeT7E1N42CEVAAQ0gqiPCMwj2tmuyBuAa5QHAKDTCAGCCKodRGZMoB8EAHO6JbIqTf8EGC+Aq0MS0Y9xqzFG+piWCPDQjzo84hFniLjEJ07xilv84hifuBVgkUUuVOMqhjCtRGQVxYOb/OS3/2BjGyEBXVettiKyIEA/JtBklNv8UyqvJAVsYNyiMG0GGoHFPM5QBzOekd43N3nObzkAsYXlEC/A87658o0eJFrECNAABaa+FQmgItlmCsDdtPAOLvyY6ybxBR6x/LFeuP3tcI+73OdO97rbvRdZ2NlBKyDPW5u6M8s+Ji+aEYJxvOzv2Qk8YGlQhFK0Qwo2eEB8wI54e6L98pjPvOY3z/nOe/7zoA+96EdP+tKb/vSoT73qV8/61rv+9bCPvexnT/va2/72uM+90XixBVGS4PfAD77wh0/84hv/+BWYsOZp0Ix3OGENWDDFTuBDecQrfpm8MMY7AKATQ/S88kC5fv8xPQCJIHTDpuDnjPh/6QtJBMEQUk7/mVBtTF6ok53yN9P6b7kBADDg6vkXFvtXRxKABH4XgAJIf7wkAeRwgArCMn6EBDXgA24ABivABc3wC98gAxzYgR74gSAYgiI4giL4C+j2SxRQA/G3T/2ECjswA7/wO7r3D02wDP52FAFgCOMQB2gxgwVBAlqwgnFkCK0gCSfog//gVABoXcvgA0WDhAchUZxxCGrQBEeoETRgABTwDQqADF74hWAYhmI4hmRYhmDIDleYRcYwDksYFKBgAwOwEcbgDe6QAAHAAC8DM8qmgK2zBS/wfWKxDPCQhuDVBGvAAA8AiME1gBGjAez/JmJecGASEQwzsAYdIITyx4gOgwyLYxW44A7wphArYB1tmH6ayDC0QxkkFhG+UAr+gIkIGH58eDFzkFRVgQsaMBEzgAXVF4tTcYoIQwYH+ABxEIoGUQEJAIu+mFICRGoKggrn9RBewXbuMU4PIAVQoAFgEA0bYAwe8I3gGI7iOI7kWI7mKI4kYIwUU2E36A9ZkDcPMQBbs1YMgAQa8AeEyHWcUxqocHYGwQXcRhRsJTekZwBt+ABeUyE9QI3ngQrecHoGWRWGsDYOkWDDMg6SkHr7SBVXkC4lFgvB4QTq6BCTVJImeZI8EwzkUBWN8xAgoIz55Q8XNRE0IAkFsAbF/7AMDgAMd9eTPumTecczAKCI/tCSs3OAqQAOE8EF6cBo7ehOs8gwtTUVKTYeCdCGhuAEEbEFaIANpWaKUakw0XBrxRBaDeELVzk6CBCNDVEFVBOLwPgtvLAGPbUCJGmHVGEKaEWSLvCImRiWCAMJfhkUhoAGDxFkcDOTDEEGl7OM8gKYBhMMPaBp2yODGcZdBlRm9vWVjpkUkFkwBAQkGdkQc4BfAfAAmrkQiOCAPlEMO6kFGsADqDSbtFmbtnmbuJmbs/lQFRMMpECZQkCSWgCAuAB0DQEFMPkTuCAEEjCSyyQB3MYAo8kQkoBfx6CVDdEFyWlVDJJtLpBmnNYQvP8gBIBYVwKHEE4Ak1b1bWbmm0BSCOCljMtgl9R5aw6wiulGAgCIDfXFEGSFFL0Qngsxl4rIAFKXbt6AZOSQjwORHEhxDAmpENmAmUXxAC/HdaWAZDWgjh6QTTPmNwoRArC4YM4JWG7Abc3FELqzG+yZENr5oBE6dTJmFNgAnyVmBByQozq6ozlaDiNgmSsGi6nQnWg3o9MjcguRA/HwDEzapE7apCrAoP8wBrBoCvgpo/LFAO9QY/SzKI0iZwwhokKmBZgHIThCIQ2RD/2ARoyCB9i2EOwgXkNhCEqJdlWAZGtGYbNQc15yB2XQEMEgBYrYAdXDddCJIzZwngjRB7D/5iX68AHq6Ae3xgBxOHVidiX0yRAQ8GZz0ihhMDswCQqk4I/o1R9IUQwgoqd8SicfoHwDmgBE+YoJIAPpxglIhl0O8Wq2cgpKcJel6A/Y0Aoe+WEVEACahgtCAqhWYHBe6qYP4Q4BaVYOwApxgAgSQAGllK3auq3c2q3e+q2lZAxSqjA+wJBAgQWkKhAQEAG20ij61hAWsAydaXkV0wz4BTpwcgpI16Zm6RA+wJm+GJe+kg6K6G0kuaztGkIQsQVaALABKLCpkg2m+QIjSQcfsKpeMgEZEBG8QAbL8Kv1BLGpAg/4ZQqpmqZMdiuTMAsqBm6t4AAgq00iiyoqqRxb/xoge3ornXAKQDBsIKANuNCLi/iZB5MNcjoUDlCpDTEMVqAPuAJCb0oRMvAOWqAGOlkMwiAMsWpORHsw4NCYSGEID5mvuQK1HcELwUABaru2bNu2bvu2cBu3ajsH4+owaKYcyeoQHKQrIDQPrTcG5goUh5CLD+ELTasrnTABHwCmqRcEN8hcJVoJESBFuMIMImAFs3Z6q0CUVuUMEaEEvxZEyXBvGWBEpZdEu9UIHJsBIsCmfDsBEWAFMCALoreapWEDW/e5eICxfAsNd4AHH3AGGXADU4ADf4V2XCCvw7KXETEF4nZzycAM9nYH21K91nu91CICLHBENDCZVsEACP80iX2AB/uadOa7q95yRHfrKkgKEflAAOR7vvLrKUvHPnNQKSDXvg4EBDNHufM7v/UrQCSLFbhYogaBDjJ3B9BQvv97cgEsQCSABFvrEw+ABJI4bGEgAP0gLbzbwFP0wEf0C7EwwYOSBSFQtwwRCHpAAGcQARFAvXegD5PADAzswUgCwllUBGrwlPllCoMYEryADpVQCXqgB0qQAUicxEq8xEzcxE78xEpMAC17SxKgBiTMXssABZ4LhQzxBggQsz/xAFnHllxsEMYgBclZof4QB39gwLbHBqnAw9bVAWoAAFUwrGVsDMa2R4awDCMAAAJFAW7Mer+QjB+jMsUgGDuOOQLkIAWe9ciQHMmbBVrL9A1BgAtyPK/M2Ew04AY6ocmA17W9ZAzw8MmgnFP9tQVgABVpvIwzq0tbcC82AB+nLItotwXNQAYAYANwwBOd+crI5AEUwAeioAHpgASoUCkwgx/M3MzOfB+OoHdlPM3UXM3WfM3YnM3avM3c3M3e/M3gHM7iPM7kXM7mfDEBAQAh+QQJFAD/ACwAAAAA+gD6AEAI/wD/CRxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDihxJsqTJkyhTqlzJsqXLlzBjypzZcZiBKu28ADNkqJe/n0CDCh1KtKjRo0j9dfhFsylMXt7yOEhKtarVq1iDLnXKNSSIWKCyEg1g6kGCUr94dV3LduSfAGGzBlhWoALHLXP8aNgBYJy/BwwCCx5MuLDhw4gLY3MUra3jg8awBMDKwA3FX0I6TBbLubPVrY/bgphaNQCCDRF5kPbc64EDIY3+kAhNe2aIB1UfxIH4DldWQ3G21B7ueFieqobIPJzhu6qpHRH99MDtuTpW0MRXFll91IHlhsMSxP9Fegh6QxpIxmft5cDBg/fw48ufT7++/fdLFGRfKYRqMSgO0RALVaa4cx4CVqnBzn4MhiSKIUkF4INDJGyGlAONMRQHdUc91+CHIbkBYVLpUGhhdxkulMQCr7To4ostDvFDJSDWyFEwxyXFQBUBDpiUMAU4pEcJQ8BopIvKXDCFjUxWxAkDVDEggUO+iEfVA+Y5hAmRR3bp5ZdghhkmISw0WdIbI1JlzENgQOkcgBPdEEMJoxQp5p144kmmmSdVoB5SNtj1EAjNXWXIDsLxqahJHthwIlJZgCPRO9xxBoxrCcjzxmyLdmoRL1qYQtl3E31TQwd/WqeqZ9h5+tIvNqT/emUP3ni0BQUruBAHAKioYUpiwAYrbGGMubqWLxrg8qhYARjiRQjIqGXstGxZIISyq2arLVKtUqvoFlzA8wIohlS67blFdevtuuy26+678MYr77z01mvvvfjmq+++/Pbr778AByzwwAwOI0E6/piL7sJUqUswR3MAwCG6AQADTDGCuQfMCOR07PHHIIcs8sgkjzwOag9b9Asqslr1QADuzJFyvaBOXBUwWLxh0RbgJMDAAy0zvKrDM2+Rx7JHMYCGRDTskKbQUHPL1MwC+SCMVccgAxEXT18FTBY7UED1fmBgg9wAD1EQ9Fhq6CxRMBKMUYwDwKwdNVFE76uAwkMF/+BFMA5JUihSAZACeEO8AFDM3ejmjW8FaiAtlAOiOESMTxH+3ZAEHVw1mR9jwyRB12PF4oFDcfAdFDCrNESMqEkVk4C0oc8ED+lDMQCCQ8aoDlQv2tCukDiZWGL88chbkkkmudTuFBi4CxUAEsOg7vtPwJTYkBgshmmOGXQ4P5Mk0QvFgAUOGYA54Zo3VEkJyuSpjDklHAFDIOKrVIWbSBlCKkOCK00eDueQYLSgBDrIkwIXeKc9EYwXQbAbUAaYNskVZTLUyMgwIJACJgjiCJ8IoQhHSMISmvCEKBShIgYxtjdcLygPYENEmlE+pIAiC1BwRv4a5IIXBsUBIZgIDf/iUEPGQc1xoXOHD4ViFgNcZAszSAAu6GZEbSFxh/+AlQSNQpYdoAyL/CqEDdanLVAIgwG4sEEC0tEOMEiCC9GQAQWC4Ys62vGOeMyjHvfIRz4KD4wdoUMUcbHFKjZuaoBsSzC2QAMSkMAAkIykJCdJyUpa8pKYNEb1EsnJTnryk6AMpShHScpSmvKUqEylKlfJyla68pWwjKUsPbUFDxSBE24IAQCQwAob/OQ+wAymMO2Tn1kWkAshIEd7DHlERBrzHx5wwwgMUUhmnuuKpaxAO85RTWtGDZue9IA7BqcqUBxjGQEQgju8UQQa/PGZLPHADsgplgccQwveSBQ811L/hFgAgzPNSoDb9vmYOdjgn1lxABQEtbM/OIEUy3iPN6/jzB26gZ5JwUUBCBiRLYCgB6iaaLbAKTA2LBEolZmIAkhxUpGKhaT+egNGjfKAVbxzIRLAAhmt84BDOOBnakAAKYJA1KIa9ahITapSl2pUIYgtdPA46QOCCJFSFBEpLyvAQAmKER8UsnAcXQgN8rBTqvQiAGjYJFc70oglGmJ3DuHFGhBaGn+A4SI0aIY33JEOIUihZIANrGDJcbKHIWOJDoDEQ1bQOaswAG0zXINmXJoUmNKrrVQJR5YYwhyrCGOrCwkGPPhH2c9U1F8+oOtRHqC1hhhDtR3qwkPekYrS/3bGsvHqQln7hgUaOAQRjUWKKVrhkF9cdSjFwEU6JHC6tQqkEFc1TXMZEgIfLqMIDfnFTMeiDR069yBb8KWOQNeQ0VClG95dSHWpAowE+OK7CqlCdGfXkC30gCqpkERDyGCzojhAZvBVyO2S8gAX8O6FoBiHPhUCACNw4MEQjvCDjTClAAv4qtgoxIGTkmDfMmQR3fvSASJhYYXwILpauKlB7Itf/TIEB7VIIJi+F9YSC6QCux0KNprhEPP+KL0K8YA9ZAymIVABAjYuiAZeGIA1qNgg603KdR1iB/jhSQdUIEKNnRsMJFgQKAHQwkPIENyjcEO2DqGDGczBwFeMwv8cC4iznOdM5zrb+c54ljMholCmgNFBMhF6wZML8lrnjAEiw1DBAezU5kY7ukUODJgvXlAVKbFpu0MRhjUkEoYTLCB+jw51A/s8sCYscQQLXogkyixlyE5kCxCIBBUWYI49MFrUoY70wGjgl6poI9UKqRJsCeePGSS5I9E47k8cAFqxvmBxWOkFMBqx5WNHhAJflh4SwgcRECi7KA/oRRysMWhrH4QXNRi2UYBBjuk+RAKoyDFnTGGKn04lFkjQgr73ze9++/vfAA84vxPwVPFRA9NEwcX/IPKLF7TUtlXBLcC4QNqq4EIDO+PECEIKcc5IXGAVGIG6CfcA5WSEDtH/CMELItrfjgPl4w8rQsU48wBUDKDc5naJMfLQ8qu8LA6tzTlNfkGOnjOrA6aQghNWYAycC10kIBiH0RkGjF5Y/epYz7rWt871rnM9C0GfJQ3cgIWHu/ylp+WqMdAglamf3bRPHwgdDDADNgCgB2pwQLkcAO23vzztcQ+84AdP+MIb/vCIT7ziF8/4xjv+8ZCPvOQnT/nKW/7ymM+85jfP+c57/vOgD73oR1+SRTbykZhMvepXb0lNCn7udb973vfed78rBfDfBZe4yGX2s8NclWtvu+1ZhftYClKK3Rz+UX4PyrGXXfnXLH4qrYUt6B8SllF3O7qq7vXue//7Wgd7/yvFKG/PmBGNamSjG+EoRzr28f3wj78end5JomvfKgFAutKZTv/K8yJZ2UYVzfIs0UJ6BLFz97da/gB0BngQWtQZXfRFDVgQMjdyBNYDNwcSW2AAc/AL0WANRRCCIjiCJFiCJniCKDiCzQBsYPSAWfEAtKIRwRANZNAKZeceEuV70hc6IWeBXFRyGIEMIZAHEeWDOshJSvSCCeBEFEED70AOHGd9eLODKUNxWXFxFMELA+AFUSiFRsF8+QIqsONYC1dAaPAyXgh3+XNwWKFwEyEKhxCAaSgUYGgvPXQVQCQRziAVQnMpw/SHgAgfxVQ76GaEq9NuETEADiCHSdEa2P+AAOngDgPADhtgAB7Qf/vUKIzoD5GSGqz2gtWQAG7wDYhHAZ6zbRChiJ3xMk5QcInnQi4jQw8RGZv4QwmAPo6XbFbBbBBRAGb3AFKwIJDnJ1YRKA+xAWJRDY2wEbzQjM74jNAYjdI4jdT4jL6AifbCa1bxaw+BCAn4EwEQC1xQEc4AAumABVnAAMfAfeDXju5odeL3QBFUFRTkEEFgiEGhBrgIEcEwAC+QBbXncnXILqZWFajmEMMgBdVkCAYGESTQCgyQfN40kN6CJlaxJg0RDKTAiAFADiyYEDxQfVJIkdMyaZVWYQyRkIxoCvDwEFsQBHAwhz9BksayP1ThPw//MY9UkQYr8BCYJZMzSYX18mcBGACC5hCi8IngJoEKYQEIB3006SlPEiUouRBzgFik2BDt0HtzgwCr4AYDsAHEMJZkWZZmeZZomZZqSQyuBzBdVpRihjhCsEXLUJUJwQtzSRkAgF0WRj5Rso8LAQ4+dAxUtRB4WU0B0AP6kWRLFiFOBh45Qjj1uBAFkIABgApAVmI4EiU80hDNYF0uthB+sESPlXM4RhU75hA/eRS98JihJQX4yAChaW3QEyHUgziUlhSU0xB/oJRDYQiSInQnFiEp1hAegCA6Mo7UpX3A4Jo5JyJUoT0MUQHHcCVMiRBdoH0PsIxPN2BIkWEbhhS9/4AETmcFGHCe6Jme6ZkCceedSQNXDNE7jUieDdEALDIEOpCf+qmfQ2AOTNCeV1VgDkGd1rk9IeYlexAD7yV0DxIhE2KcyIkUDKCcC6EHciAmhOAJTydfEUJfDMELuXkhlcMQdLAPbAYmylACMCB0oxMhpqOaL9SaTmcHB/olcvAFORdeUUJeDLEdVLEMs6kQ3HNrXqIDT7Ak1iYEAfgfCEkKAogE1VYQvnAEJyomo2APSJZk0BUhCOBuCiGYVEGYVHIEo5AnOiAHR5AC2OhKkBOAuymX+FiXD8E9C9SfC0ALgsAESQABg9CnfvqngBqogjqohNqnCwowuhUhveUQzv+QgIaQlQ2RAjGGa5SqQLrmLz6qm2WYEEmZG5D6oStCpJU6qkZyqaiFj6yVk4y4DD35EMGgApogqqRKqqbaL3sjgO2TkmtQSKaAcRDBC5iwC2U6q8Raq/2ymkdxDJsVWuNSGgcZEfkgACVgDrJKrI1mrPxiHMhhcuCxq1ZhCC05EcFABDGwC9JQrdaqJ6QmMIdVFYkFEQBwNQkCmBOBA0QgCCewC7ZgDuZwAHtwAAAbsAI7sARbsAZ7sABrC+sqMGVzNhBBBi0VAPrYEbwwDMNQjRibsRrbjDODrEfxVhAxB2KRBfJQebeRG7vRizGJFQ9ACs3WeF5VGoYDEcaACrX/CBRmYZeMZzVYE3YNwQe990ug0A6ZmXhR5TKFOVu+6TKhyAZCaW4+JoCnIRGKeLNFcSnYcAUAUACQ8AbfYAwkEKUWJlM+Z1MSQQF8yDCgEIhsO0yD6DxGU4tKQxE8UAwS6YVRKS8mNSoUEQxNgIZAuXxPKzC0yLcVwQdI0IWBG5SJdFFXuFEZx4V3C3F5SzNa8I1AgTMvKxG/oAGk8DP4SLmDmzIGFbqTs1AbEQy/wANQgAquAR9vV7n48hZ3Oxd1IRJbUAEUIAPI8Aa++7vAG7zCO7zEW7zBaw0fCUj9ZLrSYwgCNYEMsTKT+0swA2DQexDy9JQEZgr4lLwN+BXTlRsUZGEWaLGmmSdO2nsV5oRO6sRO7mSAFCAxQlMxF5MxD7Axg5W/+msy17lK2sRNi6sqskuIURG0AXx7BBVN0xS+aTjAgGQwCGPA1ufAnhQMyKRMEnyEOWcTOKETPFF+tkfBrFRLt5RLu9RLv9S2KvyHb3u9LvzCMBzDMjzDNFzDNnzDOJzDOrzDPNzDPvzDQBzE7BIQACH5BAkUAP8ALAAAAAD6APoAQAj/AP8JHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuXMGPKnNlxmIEq7bwAM2Sol7+fQIMKHUq0qNGjSP11+EWzKUxe3vI4SEq1qtWrWIMudco1JIhYoLISDWDqQYJSv3h1Xct25J8AYbMGWFagAsctc/xo2AFgnL8HDAILHky4sOHDiAtjcxStreODxrAEwMrADcVfQjpMFsu5s9Wtj9uCmFo1AIINEXmQ9tzrgQMhjf6QCE17ZogHVR/EgfgOV1ZDcbbUHu54WJ6qhsg8nOG7qqkdEYlcOKDslfXr2LNr3869u/fthFgQ/29ZZPVRB5YbDksQF+kh6A1pnDD3vbsOcwvy69/Pv7///wDuZ4t446kkBFXFQOEQDbFQZYo7DXnwhA71vTIEFTcUqGFIohiSVAA+OETCZkg50BhDiyxQ3wGRbOhiSG54mFQ6IpJ43okLiYJbUg7M8eKPHAVzXFIMVLFgg0kJU4BD38hIVQAjfAPklBVxwgBVDEjgkC/sUfUAfA5VsYxnZJapFFNUkvSGk0kZ8xAYVzqn4ER/CIGLjWbmqRWaaZZUQXtJ2WDXQyA0d5UhOwjX56ImeWADnkdlAY5E75jHGTCuJSDPG7Mx6qlFvGhhCmXpTfRNDR0AqueqWIH2KUy/2P+gam49eOPRFhSs4EIcAKCihimJBSvssIUx9upavmhwZ5kBGOJFCMiodey0bFlgJ6SsZqunq9QyugUX8LwAiiGWamvunt2mq+667Lbr7rvwxivvvPTWa++9+Oar77789uvvvwAHDOQwEqTjT7nnJmwUtwJ7NAcAO5obADDAFCOYAw8AMwI5HHfs8ccghyzyyCKPg1rDFv2CyqxXPRCAOz6iLG+oEVsFDBZvWLQFOAkw8ADLCpvJsMwCbZEHtkYxgIZENOzAZtBQDzW0wD4IY9UxyEDExdM2Z7EDBUQXCAY2yA3wEAVAFxWAGjlLtEUKglCxgDR7VFfh3Xh3N2DACiD/PFQAXgTjkCSGIhUAKYI3tIUKewyR9+OQ5x3evxWogXRQDojiEDE+fRh4Q2GUYHeFQ1ACgy9hvyQB13/H4oFDcfgdFDCrNIRDLRR+Z44ZqKdOEzysC8UACA4ZIztQvWgj7UJBGMHB89BHz0E5S2jpe1NgBB9UAEgMA/vxPwFDY0MhgP8TKGt4f71MkmgfFAMWOGRA54Z/3tAGNVfVCwNa+DHo+iipQpyQYohSMYRwpclD4hzCCzakImqrmhq/eBGEtAlFgWe73Fj8QY2MbCEaEtBAF4KQgBeY8IQoTKEKV8jCFpqQHCeT2RvMB5QHsCEizXDfUUCRBSg4A4AbcgEN/4HigBBMhAZx0CEEzyXB67ljiEExiwEusoUZJAAXDgDGErfFJyAmJFYWNNxzYujFfBXCBvRbFSiEwQBc2CAB6WgHGCTBhWjIgALB8IUe98jHPvrxj4AMZCCXV0aP0MGKuAjjFs3VxELSJBhboAEJSGCASlrykpjMpCY3yclOGkN9jgylKEdJylKa8pSoTKUqV8nKVrrylbCMpSxnScta2vKWntqCB4rACTeEAABIYIUNfvKAYhrzmMhMpjKXycxmLkEBuHRIMLgQAnI4AIqLFEsjZ+kBN4zAEIrMpra2ucoKtOMc4RQn1MhJSg+4o3BmAsUxlhEAIbjDG0WgASGj6f8SD+wAnmJ5wDG04A1F8ZMtRYiFFsXSrAS07aChmYMNFooVB0DhfxXZwh+cQIplFFOdV2EnytwA0KTgogALjMgWQNCDVIE0TyIFGBuw6Y/KTEQBpKDpS0PaxbC9oaRGecAq9skQCWAhjWV6wCEc4DM1IIAUQYiqVKdK1apa9apYnaoQwJY6eEDxAUaESCmUeBSXFeChEM2ID9J5uJQuhAZ5QCpVehEANIAyrR1pxBANQTwGroGiT/IHGEBFgxS4QgxHiME+FMHYxjr2sZCNrGQn29hBNAwZQ3QAJB6ygg5chQFmiwgEYiAHHYwucqiVHIEApleqhANMDGGOVYSB1oX/0IEJtshdaneL2skBzAeADWrWGmKM4BrFFF14yCZK4DjeOje1vvVXF+T6NyzQwCGI8GxSTNEKh+hBdHnbhjlKoIIU5ACvBCmEEk3zOvLRcBlFaMgUftDc+ijjApVAb0K2MEwi+cEho6FKN37IEDGoqD57iIFb9WuQKqw3AUQ9yBZ6QJVUSKIhSTiwd4bwg/wyeCHAS8oDXFA884FiHAZViDgyYYkWu/jFLc5ELj7MkBAjBRuFKHFSTnxdhsQhf0R5Do0XwoP1aiHCBplwhS/MEAOMasdSQPKQK0DdoWCjGQCmoTAI/FYEWAUU8R2yQTRgvgCsQcoFKR9V4OsQC5AV/ygB6AU40AzRYCBBgz8JgBYeQgbtIoUbyXXIFsTVmV5c0wE8SbSiF83oRjv60YaAgyOg+S86SOZDL6DzQIrrnDFExAfH2ClnYiovX7ygKll6E1CJIgxrSEQCSNCpOkk9ryYMcQQpXogk/Lzm0E6EF82Agj8MUYx0QpDW86KBX6qijVwrhEvGPcpkZiDmj0TjzQer7VtfUIys9AIYjVhwtSlCATxvDwl0iAgIsE2UB/QiDtbQ9LgPwosaRLsowCBHeyEiAVRUmTOmMAVTpxILJGjh4AhPuMIXzvCGOzzhCeDq+qix6qLgwoAQ+cULZC3qpCDbX1wYoFVwoQGdcWIELv/teGc+DrAKjODe0n6AcjJCh2iE4AUeBbLK0VXKIkyMMw9AxQDkPW+XGCMPOm9ZAOIw3KI75RfkSDpDO2AKKThhBcYgutNFAoJxSP1cwOiF2MdO9rKb/exoTzvas9B0XNLADVjg+M5b1VP0GgMNUvn63Hm69YLQwQAzYAMAeqAGRF+z23sXCsv7zvjGO/7xkI+85CdP+cpb/vKYz7zmN8/5znv+86APvehHT/rSm/70qE+96lfP+ta7Pl6QlCQlO0n72tt+k598/N8DP/jCk8sBiE88UBZPy2+Fa1xyVznxXXn3vAufTMtH5SGvaOzne7zutXx73K0/TuzH0lrL4j7/I73fyq7rXVthV7v6189+s7Ndlmf8t2fW2MY3xnGOdbxjHgXJ//77/49a50hQd35WEQBUZ3VYF4CgxwvKYm4f4izQooCkd3QEWFb+wHSvpxBg1BlksQNklIEH4XMwhxQP0ANDBxJbYABz8AvRYA1F8IIwGIMyOIM0WIM2GIPN4GxltIFZUYK2khHBEA1k0ApxhzEftXfRty8uN4JqI3MYgQwhkAcexYRzl4T58kQ9mABTRBE08A7kkHLiVxRWaC8hlxUkRxG8MABeAIZheBRjOC+h8mRWYVMSEQxo4DJtyHcARHFYcXETIQqH4IB5qHjk1zBCdBVFJBHOIBUJgynN//SIkBiJxfRM11NvVAgU+bZvDjEADiCISNEa2IAA6eAOA8AOG2AAHiCB0eQonigpqcFrAVUNCeAGUkJ5FHAV3JNuD8GJneEyTiBxljdDVmFDEBEZnog5CRA/m3dtVuEA2qYQBaBTDyAF7OB5f2IVgvIQGyAW1dAIGsEL4Ph/4jiOe6SK8aJsVtFsD4EIFZhnscAFaFgJX6ACJ7ALtmAO+LgHB7CP/NiP/viPABmQArmPeyMwFJROGOQQQXCJQKEGyggRW1AGZlAC5lBfz3WR1hFdAGNrVYFrDjEMUmBshkBiEBEIkUCRp4WRKvkKGukva2IVbtIQwUAKghgA5KCDCP/BC5uwC6Owkj6ZHS3JL6aGatbDECApiKYADw9BA0ewABb5kz8ZlPsiQFRRQA9RQVWRBivAQC0gB9sAlWB5HVKZL5ZmbgGQaQ4hCrAYVB+YEHawC7oVlmA5lvhiJVhSlAsxB5lViwWmYXgjXgvwBKrQAkowCDlwmIiZmIq5mIzZmI55mLrYL3ZmlnvWELwgBOG0DHiZEL4QA0/5HTpAXhAgZu2DJQ+5EOBAQ8cQVgvRmZ9pHxeQIeNGZh9yZuoxJIaTkAshAPRRITqAX0UnJFhiJA3RDO/FZAthB37pHbbwBVtHZVRxZQ7RWp9om7a1D73pHcpQAnbQd9nzId1jmaf/xiOawxB6IAcVQgiewHhF9iFHFiFeRiTw2JcItg+9s3UxQhXjwxAVEGoi1pYH0QDLuR3mwASNZ2NHgWM69olIIG9WgAEQGqESKqEpcKBKNDwLehS90KAN0QWHsF0k2XcIGlQhuhD96SUAahC3kRTAYJ1b1yEfEiLwiSXzuRDRUHFAYQib1XcO9iEQJp5UkTnq8VdY8l9btzof4jrTaT694KIKIQlDxAA55nT8VaQOUR5rhpwL4QQVGADjwJfjJgTmliAfSQpPggTiZhChwpD+4KUdNG7q9SEIoIkKkZpUsZoPoQXVV1NacJoMVjnmJqSWKQSXqJkPEQLshjwOoAZB/yAPpSABcxCpkjqplFqplnqpmDoHG5Cm+DJdH2JdDuEMFWgIYLoQOTSIRPGGr4KlPIJxCqGWuVGqC+EGyfdSqvoqwOUlbcc8grgMW/kQwbAKidpxt/opffMk9mOUa5BOplByEcEJDrCntlqI+EKdSHEMsLUQwUBoT+KREeEBcdABbApSxfopxoEcMzekIqmUv+YNCZAFcoiE1IovmFUVmgURAGA1VuGQF2EAA9AFKMcAplAMwlCwBnuwCJuwCruwDHuw57Cr/zI2ZQMRZIBNa+OnGQGOGruxHNuxHvuxINuxMmOtBNRXDjEHYpEF8iB6K+oluwERBQAHPUgKz5h5a/9VGohTjKhwjDWUAJtps/p6pxC7EHwgdz/TDlyGeV41jKz5EO+wlljxALLIBvPadwFWGqchEZzIs/j2ANhwBQBQAJDwBt9gDCTAqUP2Uy0zVBJBAYx4LqAgiXI7t8dEietjNMeoNBTBA8WGqnoIQDNFKhQRDE2Ah36LFOX6LsYouBXBB0jAhoc7fFU7UjhKFCeFtgyxBZywhtK6c4kbLzSTFTdTsxHxCxpACj4zrjv1ufIiUar7ExaFURcRDL/AA1CACq5hTInHuvPyFp07F3UhEltQARQgA8jwBsibvMq7vMzbvM77vMtrDThZSAn1unlmCA4Fgg+hMp1bQy8TM9qpqxD+VLkiZgoENb3h+xXdKxRkYRZoYY6k507kexXyRE/2hE/6pL0UADEJMzEVczEZszEkM8AEXMAgYzK2ZE7oFLkwNbmrBBVvy8Cj5sCu1E3ftL6oyrteRDAGU6vcp8GhNE3VdE0SLDUUDFE2gRM6wRPy93wg/Eq6xEu+BEzCREx0e8M4bLfhu8M83MM+/MNAHMRCPMREXMRGfMRInMRKvMRM3MROHE0BAQAh+QQJFAD/ACwAAAAA+gD6AEAI/wD/CRxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDihxJsqTJkyhTqlzJsqXLlzBjypzZcZiBKu28ADNkqJe/n0CDCh1KtKjRo0j9dfhFsylMXt7yOEhKtarVq1iDLnXKNSSIWKCyEg1g6kGCUr94dV3LduSfAGGzBlhWoAJHGpXCEDjzCFA/PHhECB5MuLDhw4gTG8bHoq3jg8awBMDKwA3FKR9EJLvFubPnz6BDix5NujTpUzgeOwYxtWoABBsiAhHRyTRpZvru4PmQ4UYg1cBnhnhQ9UEciLNr2+bcSYSALcGjOx6Wp6ohMg/niVh+S98ZXxD99P8gLra8+Z9bpbMs0hqpA8sNg32AZvuOAIc0kMQV28uBgwcABijggAQWaOCBAC6hgHorCUFVMVDg1w8zpk1AQEM0IGCVGuww6GFIohiSVAA+OARLBBSS1gkeNzQUB3lHmbLDhzSG5IaISaXjEAmTJeVANA2JAuNRDsxR45EcBVNdUgxUgV8sVAlTgEPf4EhVACN8g+SWFXHCAFUMSOCQLwnsd9QDMz5UxTLntemmUkxxSdIbViZlzENgfFmVKRFO9IcQuPT45qBDpSdnSRWYiZQNdj0EAi5ZGbIDdIdWapIHNgiaVBbgSPROe+YB84ADCcjzBgmWpnoRL1qYQhl8E33/U0MHihJq61WGqvrSLzbUStUDPXjj0RYUrOBCHACgooYpDDTr7LPQRivttNRK6wiQunbliwaBuhmAIV6EgIxa2ZbLlgWAanrrurfmam6lW3ABzwugGAIqu/gK5e67/Pbr778AByzwwAQXbPDBCCes8MIMN+zwwxBHLPHEFHs4jATp+HNvvhwftW/FHM0BwJDrBgAMMMU4+x8wI5Dj8sswxyzzzDTXTPM4sYFs0S+o+GrVAwG4Y6TOBbNKMlXAYPGGRVsk8gEeE+izGXdUV101akQftEUe6iLFABoSkSDAKcpZbfbZaHOGddb/+CCMVccgA1EKeExitj54CJAa29KB/4GNdQM8hEMEU9uWTAQQTBSMBGMU4wAwPnfscZwSK7DxWF4E41AY29k2ySx0OMQLAMVIju/HC1egRtdDOSCKQ5cQbloygITOkAQdXDWZH3y/JEGdRQUQiwcOMXGHadBYQa5CxLiaVDEJLN97TPAATxQDIDhkDAbcd++99+pIn1A6Rw8V5vRNgWH9UAEgMYxDcVwuFDA6NhSC/EKBssb76Mckyfrms4BDDOCTEWXOIRsoH1J6wQAt+KFR/TtJFfSEFEPAiiGSgNSV8qC5h/CCDakwXbsoNzFeBCFyQuHgQyjAOqNMhhoZ2UI0JKCBLgQhAS/IoQ53yMMe+vCHQMwhOf9yRrQ34G8oD2BDRJoBwKSAIgtQcEYEPeSCI7YuBBOhQRyaKELJoW567rAiUcxigItsYQYJwMXjumirL05RILxCIVLIsgMivnFhhbBBAW8FCmEwABc2SEA62gEGSXAhGjKgQDB8wchGOvKRkIykJCc5SfHdcSN0QCMu5MjGfLnxkjIJxhZoQAISGOCUqEylKlfJyla68pXG4B8oZ0nLWtrylrjMpS53ycte+vKXwAymMIdJzGIa85jITOZatuCBInDCDSEAABJYYYOfIOia2MzmgRSkzPhwIQTk8E8nO/bJYXrADSMwBCfHeToSDrMC7TjHOtkpwnLW0gPu0CChQHH/jGUEQAju8EYRaGDJbq7EAzvQp3kecAwteINSBl1LEWIBjPJ8KwFLi6hq5mCDimbFAVCAYEVocAMCPG0CE5hEitLGUtOs7Y1uUGhVcFGADkpkC0oAxCno09Kepu2l6GODGIVSmYncYBZ3sJtPl9pToLLtDTJFygNWUVCFhAEQPG1pMpKB0lOIAA/9qMMHUEDWspr1rGhNq1rXatZHwKJ38BiqNbGIHNqcLRl3iAABbiBLjV7EB/MMAClsyhAazCKr3NFHBDJgO792pBFyNUT2HLKF+VDtcPO4CA2a4Q13pEMIUrCZaEdLWnLgDGTIkKsDIPEQznFnRYmQSDPW0IEW/9KTKPZEGGSpEo40NQQGnZsd4h4SDHhQ8LZZya3BfOBRqcqtIeiQXYW+45B3hBC5bVIuwbqwRxdigQYOAcIpbDMBAVS1IL/gYlGKgYt0SIB4jhVIIdTrj9fAlyEEOF5pVpQ4hvwiqi7UhhTjq7VqMol3DRGvcCthv6ECIwHgIXBCqkDfAEQPQ3VQKmlEkIKGkEGBQimShBdSvaQ8wAUOyYF+S2MCiCqEfEwS4IgVUmKkYKMQ2gMxUAIwDvAy5EVJkdGMFcKDCmvhvATZQg+okgpJNMQAzkMKKKSAZAlXoLtGwUYzHMKaKA14IRmyCiiKMGSDaECMAVhDlQdyP6osg//MDbEAfYMSgF6AY83dDAYSbLtjLTyEDLlLCje68JAt0Ms8/fEPTxbN6EY7+tGQjrQh4OCIBUmMDpIZ0Qvw/A9jNBcpphhDRHxwDOyaR7sE88ULqnI+h4ABwEURhjUkIgEkyNXUcAJZE+Q6AhcvRBKBrsoyAkcRXjQDCv4wRDHm2UVUG4wG47CKNnytEDJ9ekT+mEGZPRKNOf/EARnFzwtKh5VeAKMRhN12RVholfY1lsveLsoDehEHa3Ba3QThRQ2ufRRgkOO+D5EAKrDcJlOYwgEMmEoskKCFhjv84RCPuMQnTvGHJ4ACEaQGrI2CiwtC5BcvuDWuceVO9HHhuDP/1QDTODECWo38PM6mWAVGwO8RPQA7GaFDNELwgmUA6OWTu2URTFaeB6BiAPfG90qMkQcd/yoAcXiu0pvyC3I4XXcdMIUUnLACYyR96h8BwTiuvi5g9OLsaE+72tfO9ra7ve1ZkHoyaeAGLIgc6KcuuV+NgQapkB3vVIm5RulggBmwAQA9UIMD7OUAcgNeK3oHu+QnT/nKW/7ymM+85jfP+c57/vOgD73oR0/60pv+9KhPvepXz/rWu/71sI+97GdP+4iJkpSmfKXud8/7Vsay8oQ3POIVz3jHPx49kSdwvOZVr7s/XvDB5Lvfj5/d5B8zk2lkNvWTAv1b0t3u22dX//dnia5uhb+dxhT7321l9re7//3wX3vch5lHgr+pj38M5CALechELpKSABiAAhhJX/dGVbd+I5J1W9d1BUh6vMAtfOYa4CIuDYh6TIeAY+QPUVd7ChFH5kFHdsSBBzF0NfcrPYB0IOEBOFAJUzAFevCCMBiDMjiDNFiDNjiD73ZJHpgVwCIsMTQFQHAGgIAHp6AP+jABzJCEW8VUP7U3lzRzJXgUAXBzFzEMU2BSUKMPZcOEXHgLThVBYcSDCVBGFEEDSjALIgANK9WFbNgZXzg9J5cVuKByijMPszBebZiHofGGbMMqUcZqHucQdNAHdaOHhriHTjg9GocVHScRvP8ABHhQOIc4iW6YiHxTRVfhAHQFEZWAVJLYU8mQhJOQG3dQiqZ4iqiYiqq4iqxoiiLQGL2jb1E4FP4GcA2RCHigDy3VCUeIB3VwBmcAA1NQCTgQCH21bZgSgUHBKbJhV3c1AbsBBFNwjJVHAbqDBDm4EIkQXFXTCROgV+jweUb0M0okOFhlNXj1AR0Wet1mFeDmQQSAh681AbPQIqOXKFbBKA9RCdK1HMmABxmQbhXBCwRZkAZ5kAiZkAq5kAbpCxU4MNAmbdSWEECwYoYzXBThDCCQDliQBQxwDO0XfyI5kmc3fxVjQvOkQg3hC1agi8vBDBgJEcEwAC+QBcaHd+P/9y+7VhW95hDygVgqcgoZEBEk0AoMoH23lZP9QidWcScNQQezoGGlQTsTmRA8YH7nh3wUo2qsJibxYVm2oQ9DSVlBAAdZWSjWpzATRBUWJDpWAJSisSJh8BC7dZZoOTGYpowBsGnhJY+lgQcMFmcbd5ZKaS5eAiZeyRCDc5FT4BDt4Hz+4DgIsApuMAAbQAyYmZmauZmc2Zme+ZnE8Hu2t2cj4mfx8Za2gQfruBC8IARIyQAAAGcj9j9gImMMkQh+ORr6cCEM0ZrMFgA9YGlldmYjomZPOQtbKBq0Q40HUQB/FwCo8GVlpiRg4iQNAQEWqZx4YAcN4QdyxQDEhm9X/0YVWuYQfZCboaEPH7BmwSAFswgUDOBkU6c+I+I+xymVonEHZdAQfxBsR2EInQJ2RVaaeJYPEzKVqmk/VwcMxgl2N0IV9cMQJ/KJoPGPjckQXXB1D9AIk1djR3FjKRYPzzCiJFqiJKoCAnkQMOY1XNCh9IU92mME5cABNFqjNsoB5dADzGkQGRpkKCZ5HmoUJ+YQFVBqJhaCCDEcScGgD5lMITIiJdIQHqAhTNKiDBENgwkUhsBaYEdhI3JhvblqPvI6DDEMa/CeP8EACKZ0vzMiw+MQdXkUvdCgv/adOKZ0W2BgXrOmC8EebiafDOEEzzkOWoJvQqCMEOIQw0AKV/+CBClqEKyCpjs2DjC0bfM1IghgiwkBDkN1DJvIEFrwmlpgm1a2OmMqOkIgqcuQmAwRAvEWFP2hBkEgD6UgAXNwq7iaq7q6q7zaq746BxvwqAvDXSPyXQ7hDH9nCIXaEExkl0VRmOXipz4SiAkhCv55JsvaEG4AmSMHreXCXL8idwsRBMr4E8uwAjK5Cq/6ct6aLZZzJQfUEGY6T6ZAhxDBCQ6AlNjVrtkSp0dxDL7FEMFwaFfSkxHhAXHQAZJqavyqK9RhHTgnr2vAbIYAD8XmDQmQBX+Ik2m5MKlVFasFEQDwNhtCqhNhAAPQBS3HAKZQDMLwsjAbszI7szRbszb/G7PnIK4S4zeAAxFkIHIBoAYmmxEMWbRGe7QGSTT++p+T5RBzIBZZIA+op6S/chwQUQBmiRUPQArhNnqA5RqDBRHGgArlikQJwKqg5zZwo7MLwQfc6g8PAArtIJ2dF1c/86nVda08WA0JwAYdC3Zd5hqwIRED4ABlixSigg1XAAAFAAlv8A3GQALCOmNQdRVTVYAUIBUcAwra1LmeKyDchD5bc7j+8DUUwQPL5qxY0bACI1SvQhHB0ARAo7qB97cVExmkW7rU+hB8gAQuR7u4ZbsgE1NyWFMr5wW/C7y5dklGkxVJ07UV8QsaQAoM8AAL263CSzQcdb1BAVIidRHB6/ALPAAFqDAqAfJ82Zs1b6Gv9UUX39sRW1ABFCADyPAG9nu/+Ju/+ru//Nu/+WsNVXlHE8W9dGYIGCWCD8Ez7AsUQCM0CMwQCJWlv2IKDhXAD/wVCywUZGEWaNGkqIdPEowV/ORPACVQBIXAFDAyHGMyKKMy1tsypRXDMjwzp2VM8CRPyjsorDs9UKG5OZx3GnVO6ZTBqrvDl3QxGfO2WWnEtRQM3xROSgx4TKxLNoETOsET9nd+UyxMzORM0CRN1GRNnzvGZBy6D3zGaJzGarzGbNzGbvzGcBzHcjzHdFzHdnzHeJzHeqxMAQEAIfkEBRQA/wAsAAAAAPoA+gBACP8A/wkcSLCgwYMIEypcyLChw4cQI0qcSLGixYsYM2rcyLGjx48gQ4ocSbKkyZMoU6pcybKly5cwY8qc6ZFGihaCaC3YqUOHsldAgwodSrSo0aNIjxJiQbMpTF52jsgxl7Sq1atYsypl6rQrSDv2qGotas5ciRNi7NDwyrbtyClPpI19NaSEqikdDSDj4wRKkHH+iuFiQLiw4cOIEytebBibIwVuIx/McUEu1m22xPiaSCEOgwD+QoseTbq06dOoU6tO3eGX5Mh2bGEdQulGxAEOQK9O3etBlhdxvM3h9bq4TCYLrh6IRNzhAEO7SRsaY8y49cjBzOiwuiDJQz+4ovv/O+SjucNoa6CLX88+euvrLW/IsXpAjENfQkDtftDF4TAp+rUXWi8OFGjggQgmqOCCDB74GHwq0XHEdkntwY95CtGAwG6mFNAQDTaIB0wzEJYYEiPzJaWDCg6RoJtqDkTTUBwPrHbIDibmGFIT6qUGSn8NubibA8g0REaNqwVAjI5MchQMErsxAIlDHsSymzBOOGRBj6s9oMUWTYZZEQgMRGmBQ8GMEN0D7kDEgwMCxikna66JSVIzXKaGwFoOuZFnah1SBEYeHbw456GqvWenSQb4Y2hqrID50JHtVRNCMItmelIFCDyaWipFSORGB4cGcEwHV6QzwC+YauqqRVuQ/wIMe4ZwUpEkUsCJ6K68+qPoqzAVEUCA7D2QxxsekaAAD+2MIYU/DxT2wLTUVmvttdhmq621S0AGrFc0FBAeooc8kAAn1X2rrlsr5KFrr/DGa9qv62pKwwru9OCAIbPK6y+d9QYs8MAEF2zwwQgnrPDCDDfs8MMQRyzxxBRXbPHFGGfcpC8QMPGEHKP8NNfIJM+1lMYiBSJGFKOUjJQyyuxhzigL2FJCCT/krPPOPPfs889AB71LFFyhXFElZhxA8jYHlMDPDRgabbAviyit1Sj2EGGRLytAAcoDxP77L71SDzRMDFZbhZlmEQ0Dzzmeii03amSjjNxVQ/wAAURzjP8T927LAHBm2fDZkWJVcmDyUKPsqTG4RG/EgUoWwvQyd3t1TwyLJlaZcwGfDFkwbpLkSNqQC9hcPnadFedACXeMOERDdAFE2pAEpIoHmhutEt6S4VbRk4NDcbyLWi/pNFRBv6oBs8YwvjcVSgn0tFH99dZnX4InDhlj/Gm9aBM1QukgmRoDEkTv1Ax/lhYAEr0vtMP3pgHjg0Pt0G8aKEKMr35LK2hfaRjwuIUY4xi7eV/8FGIB/anmGAGIgwVM97+TFKJMqzEEGR6ygtGpJgBSWOBCguEDAapOTpmjGC9qELbUPO8hRTChaQIQC2RhxABcAEErgrCGERQDGEAMohD/h0jEIhrxiMDIAuuMxgUPqmYdVYjIDBy4ml4EIAQGqKCJnEBF0zDABROpgBDMd8IyiiaF0eMFAMgYHQdAwQMXIUEhSIELNprxUGjU4kCE1cL11AiLepwYLxChBsvFa1gOWEYvSBGEAvBBEs1QAAWG4QteWPKSmMykJjfJyU560n+B1EgFGtEDQ/TxjqrLYyhlEgwaeIAEBjDAGwYwAHDY8pa4zKUud8nLXu6yCqBbpTCHScxiGvOYyEymMpfJzGY685nQjKY0p0nNalrzmtjUVDA8kAMIJKEFLeDHBS7whCdQ4ZzoTKc618nOdrrznUTL5n1wwIgjUGEqFHKZPvd5/zJ5DiQYMIhBCaQhsn0a9KBD6Wc1PcAEKhAUoRCNKFEU6sxgMCEK+dynMsyyADOoABN2mAIdLOnPl9BBDLbI6FiU0TQzfCGYJe3KDeIysiHswQxE2ExMJYOOC4glK0M4gAkqgRFe/AINCTiGAx7APFSKR5VlU0IJtpGVbSxAEIGgyDBmQIoOnNKpc4KqxjYx1cvYog8TMYYQughWXonVYjigxBCwcg0TULAhGxiBIXnVm0M4gAEPCAACBkvYwhr2sIhNrGING44lSq0PyVGOGECJkAEIY07AwEUQ/ECHnWrkblbRwQXumqEE7HU953AHCTz7kSREtiqE2MRDeKGFpv/uphpNuAgvNiABHkAhHUJIgHCHS9ziGve4yE2ucEkxB5Rd4nVWIYTitJSF9TjADRJhhw8YgMC27uatDHMtfS7kEPCIxwFcgAgiMOhdzDl2Yr6IhGWSIgfbNIRxHEqec1LRXjy+V2KLmC9S2gDThIgid6oBRQ0cMgcn7qYYDKgBCLLIWoFg4nBJKTBCGiFD0SwjvQzZAFtHE4AefKPCCKGDPQp6FDlorSGFGPF4NtCQEMgYGCFEsUIuXJVRmEGEB9lCD6Lkh4bwwI6mMcSJdayQUBCiKgeIXUO8txpQjEPDBinfahzAByYvRAMdCEcAxEzmMZv5HCDo3oitjOWC0Gj/NaaIg5cVwoMOOyoIDtkCKnaTCklMGcmlAUUC5oyQCpwWNTFySCluTIEPbSg6avAWoQeigREHIAGULQgX9/Pfg6yAvQ9GQJEJHQxt/G00vQBSQ5qA4NSEAwoP8YAXvrqfAhnCFIfIta53zete+/rXwF5CkS62Bb8lSQqZJogxihEdU+DoIcEIQnf7696MDSMPa2LHQ0Dg4NRI+iFgwIKMqe2rTk+sEeOG33e6jZpiFDAiWxhAEB5gCGaT+4zmnpgHHr2bFwAZIbE69QxjAeJJbySG4jHFkltEDtvuphexAIfBM/IGUKvmAQBItkFEwe41xSIENJ44vEkhcNIA4wWd/72NP2jdHmCUiwHhsYEQgjCGmtv85jjPuc53zvObAyBd/+NCNWiV5omswAvjvvdT8102SPCXVtitSAVCAC2lo5DpvqOADRy+GmDYYAYaIcEMumCDDkzL6qcBr+8GALc4HeIFoxa5V77RA5bvBwEaaLTcmyIBVNh7VwEglRbewQXS7h0lviDkoefWi8Y7/vGQj7zkJ0/5yvciC8OWZwVcoIakoz1OaqcmL5oRgnEs9fOICj1raVCEUrRDCjZ4gIH+jvrQqP7wuM+97nfP+977/vfAD77wh0/84hv/+MhPvvKXz/zmO//50I++9KdP/epb//rYz772i7MFV+bg++APv//4x0/+8pv//DmAhU5xzwsPeLMFZniCJnayE2n0pCdDyL/+98///vv//wAYgBQ1Z8FwCZsgCE+wAPgkUQxYFAPIWrDwBSqgCbZgDizWgBg4UUUTU8FgBypQAgugUhk4gg64gdg0DAE1FSS4gtFlgtRUCR/YMiw4g7DlgtBkB2awAHNFgjATgD74g0AIgA/YTHZwAjoYUXtAMyVACYoQAyqwCa5gBymQAjdQhVZ4hViYhVq4hVzYhTeQcs6EA0dwADuoTxtVAvawCDBwA5WkfbzQBxilTzogB/aQFm12fYFwBEc4MixVAotgB2C4fQUxBRdwDSSjA3bBhoKYEDMlg1r/wTRU0AeB2BE0YAAU8A0KgAyauImc2Ime+ImgGIqcyA7/pkU4YA+OmBWj0AZloHHd4w3ukAABwABLdXZWd3sYQwMmYIhaMQS7wASTKBFz0ARrAFhcV3u4aDFMYAtUdRkHcAF4QRHBMANrYHa1911YZzQ3IFdVlRnrFxErkB52d42kkYwRwwtVU1UlwASuWBC+UApVR47VVkGVwI1XgRlM8I0OMQNYQHvyyB7m6DBJkDbcsQjtOBAVkACA9o9Lpz68cAQ/VRWegwMRIQHjsHgC0gumYAgPIAVQoAFgEA0bYAweUJImeZIomZIquZIsiZIkcJASQwMXUIZJsQ0lMF0P/zEApnAowMAASKABf1CKewcLVCCCRiFaw/MQXGBnqJFZgkN8l0AFEYkU9QERJNADGNklqOANxxeVU2kUNrk3D2Fj7fEA4+BnyEeURkkUykAJFNk9VkIrTgCTC/FJdnmXeGk0dHACazkUynABwZgQILCQM+QPYFARNCAJBbAGxbAMDgAMlheZkimZmIcyvCAIX1kUQwCYD1E865EKEicRXJAOt1ZybRWQBCNeVaEM9hCYB+ELCTCOomEIWQIRW4AG2DBt14iaAnMDJWAVeUNUDQGbsukoCEBhDlEFYMOQtpeNDJMdfSkUcmAHDsELsthsbVKdLmBxDMmb9fIFsgFb3v9BPIQ5GstwmA5BBqnDnOXonAqjYjRpFENwAoZ3EDzQaqlhCN+mEBugm+yJbxgDnlZRAjDAYDIWAA+wnwmBCJ4nGsXwmFqgATxASxRaoRZ6oRiaoRpKocB0MXRwAdEJFDoQA9DDELQlm7gAdg0BBeVZGrggBBJAl9RkB79ZFQTqEJIgY8dQmwzRBS0qGoJFIpPGCE+WFOZgBvVZELwgBMdIYiPgmpr2o8a5cIS2BSBaFbbAPQ0xBz+6DCvQEDnaRmAkcjlAkEahDCWQAg6BBiPWC8hmomvQpKPBAHE3cUSAYUZhDiegjwnxLKtxDCHQENmAn6fxAFEnd5uAp0UxCiT/2hAeECKrQUA11qI4JqMlhSJVsSIOsTz7QaUJ4aN/GqiH52Q64IOE4ApqVkVI4IpaphqpIKR7hwa6MA20Wqu2Squ6EEVT1qar2hBj0KKmMKZ7Bw92xgDv4BAG4J+FGnILEQLAqgW4Jwp2FgCrQCX8dj6wqhDs0HGjYQihKXdVMK2Y1hC8QApDIgoNEQxSIKdnlD5yJwHTagNQWhAu0KZr4Ip+MG4MMABypyFR8qUNwQUytgzuyhBvthugQApJimJC8GDPxhDBgG1dd6/kmgDsOhrmIgMGxwnxeocEUQUyZgg8UJ2xyR7Y0ApAp2MVEACmiQtTMpwWCzjvphDuwJSl/yF7rBAHiCABFBBLPvuzQBu0Qju0RBtLxiCUDuMDWUkaWLCwBNEMUvoAzaUly/CfpeGd6tIMMuYAiPAQ8DBuwKB3DuEDysqcWKsu6XCx/qBwD6EFxXkMofIQW6AFZSuPZ/st2XCgL9CO6mqaoWEKjRARvEAGy1Cc5Ha33/K1HPKwDOEBgEE7pDCvhdYKDmC43oW4wBIM5BAdhnCssoMEhmsIhwpvIKANuOCPSoe5wJIN3DoaDsCv1VkDahsaNGRDFSED76AFauCYxSAMwjC7YqO6wAIO65lBXOm1nle7HcELwUABzvu80Bu90ju91Fu9zjsHSCsxlca5L+sQRdC67v8TALC7fGOwtKRxCBoAEWp0CGWZAJ5qfEHgt2ubDjC5AbEgv6TBAFogI8i3CrMrWM4QEdwmJw+AAI2AnMO3aW0UuBHBBtxZLKCQDjOwWsHHoLpjA2L7EO8Avs1mCLiQB63QCFzwC8bgsV7GBVUrHmwyEcjQA6jbK6awLTI8wzRcLd1SQTTQA/jrDwywQRLhCw68w/8pvAWzvethCEUnER7gDp9htatBxAUzB51CdBURDDwQC1L6j1BsMIrLHrigAZY6EMYQAldQuU4MoKtEAkgAvKLxAEjArFW8AjsQC6fHnluMML8QC2xMu1lwKRvhAX8gD6sQC2Y3LeZ7mu5JOEX/oAZCTLumAA/ZWxHDYABz8Atv8AbvgAZNsMmc3Mme/MmgHMqizMkuQMHGJAFqsMdAugxQEMCL2BBvgACWe7N4h8CvfBDGIAVZjBo1Egd/EMbTxwap0Mgz1AFqAABVkLK3/A/GMG+HZAjLMAIAwAM8C8zJ9wsKKTdABGGE8ZgjQA5SoFziPM7kfFzMdU3fEAS4QMxnPC+JrEw04AYI0KDt3J47ZQzwMM/1DJDvHE1bAAZCUEf7nCj9XE1bgC82UCADPRp3jExb0AxkAAA2AAeGQM+HW9A65gEUwAeioAHpgASo0CnT0iAkXdImzSCOkHnLvNIs3dIu/dIwHdMyPdM0E13TNn3TOJ3TOr3TPN3TPn0xAQEAOw==";
+ const logo = ;
+
+ return (
+
+
+ {logo}
+
+ Cannot connect to VisColl. Trying to re-connect..
+
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/src/components/global/Notification.js b/viscoll-app/src/components/global/Notification.js
new file mode 100644
index 00000000..d425d03c
--- /dev/null
+++ b/viscoll-app/src/components/global/Notification.js
@@ -0,0 +1,15 @@
+import React from 'react';
+import Snackbar from 'material-ui/Snackbar';
+
+/** Stateless functional component for snackbar notification */
+const Notification = (props) => {
+ return (
+
+ );
+}
+export default Notification;
diff --git a/viscoll-app/src/components/global/PageNotFound.js b/viscoll-app/src/components/global/PageNotFound.js
new file mode 100644
index 00000000..d3fb9138
--- /dev/null
+++ b/viscoll-app/src/components/global/PageNotFound.js
@@ -0,0 +1,20 @@
+import React, { Component } from 'react';
+import RaisedButton from 'material-ui/RaisedButton';
+
+/** 404 page */
+export default class PageNotFound extends Component {
+ render() {
+ return
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/src/components/global/Panel.js b/viscoll-app/src/components/global/Panel.js
new file mode 100644
index 00000000..6de1e475
--- /dev/null
+++ b/viscoll-app/src/components/global/Panel.js
@@ -0,0 +1,44 @@
+import React, {Component} from 'react';
+import IconButton from 'material-ui/IconButton';
+import ExpandMore from 'material-ui/svg-icons/navigation/expand-more';
+import ExpandLess from 'material-ui/svg-icons/navigation/expand-less';
+
+/** Expandable panel component for the project sidebar. Panel examples: Filter, export.. */
+export default class Panel extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ open: props.defaultOpen,
+ }
+ }
+
+ handleChange = (type, value) => {
+ this.setState({[type]: value});
+ }
+
+ render() {
+ let paddingStyle = this.props.noPadding?{padding:0}:{};
+ return (
+
+
+
{this.props.title}
+ {e.preventDefault(); e.stopPropagation(); this.handleChange("open", !this.state.open)}}
+ aria-label={this.state.open?"Hide " + this.props.title + " panel" : "Expand " + this.props.title + " panel"}
+ iconStyle={{color:"white"}}
+ tooltip={this.state.open?"Hide panel":"Expand panel"}
+ style={{padding:0,width:"inherit",height:"inherit"}}
+ tooltipPosition="bottom-left"
+ tabIndex={this.props.tabIndex}
+ >
+ {this.state.open? : }
+
+
+
+ {this.props.children}
+
+
+ )
+ }
+
+}
diff --git a/viscoll-app/src/components/global/SelectField.js b/viscoll-app/src/components/global/SelectField.js
new file mode 100644
index 00000000..c3da173a
--- /dev/null
+++ b/viscoll-app/src/components/global/SelectField.js
@@ -0,0 +1,86 @@
+import React from 'react';
+import AutoComplete from 'material-ui/AutoComplete';
+import {floatFieldLight} from '../../styles/textfield';
+
+/** Custom select field */
+export default class SelectField extends React.Component {
+
+ constructor(props) {
+ super(props);
+ let searchText = "";
+ if (props.value!==undefined) {
+ const targetData = props.data.find((data)=>data.value===props.value);
+ if (targetData) searchText = targetData.text;
+ }
+ this.state = {
+ searchText: searchText.toString(),
+ prevSearchText: searchText.toString(),
+ filteredDropDown: [],
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.value && nextProps.value!==this.props.value) {
+ const targetData = nextProps.data.find((data)=>data.value===nextProps.value);
+ if (targetData) this.setState({searchText: targetData.text, prevSearchText: targetData.text});
+ } else if (nextProps.value==="") {
+ this.setState({searchText:"", prevSearchText:""});
+ }
+ }
+
+ onMenuClose = () => {
+ const targetIndex = this.props.data.findIndex((data)=>data.text===this.state.searchText);
+ if (targetIndex>=0) {
+ const target = this.props.data[targetIndex];
+ if (this.state.searchText!==this.state.prevSearchText) {
+ const searchTextExists = target !==undefined;
+ if (searchTextExists) {
+ // User entered a valid value
+ this.setState({prevSearchText: this.state.searchText});
+ // Return selected value to caller component
+ this.props.onChange(target.value, targetIndex);
+ } else {
+ // Reset text field to have the previous valid search text
+ this.setState({searchText: this.state.prevSearchText});
+ }
+ }
+ }
+
+ // Unfocus the input field
+ document.querySelector("#"+this.props.id).blur();
+ }
+
+ filter = (searchText, key) => {
+ if (searchText===this.state.prevSearchText || searchText.length===0) {
+ return AutoComplete.noFilter(searchText, key);
+ } else {
+ return AutoComplete.caseInsensitiveFilter(searchText, key);
+ }
+ }
+
+ render() {
+ let style=this.props.style? this.props.style : {};
+ if (this.props.width) style["width"] = this.props.width.width;
+ return {this.setState({searchText:v})}}
+ onClose={this.onMenuClose}
+ fullWidth
+ style={style}
+ textFieldStyle={{fontSize:window.innerWidth<=1024?"12px":null}}
+ disabled={this.props.disabled}
+ errorText={this.props.errorText}
+ {...floatFieldLight}
+ menuProps={{maxHeight:this.props.maxHeight?this.props.maxHeight:300}}
+ />
+ }
+
+}
\ No newline at end of file
diff --git a/viscoll-app/src/components/global/ServerErrorScreen.js b/viscoll-app/src/components/global/ServerErrorScreen.js
new file mode 100644
index 00000000..a65323dc
--- /dev/null
+++ b/viscoll-app/src/components/global/ServerErrorScreen.js
@@ -0,0 +1,48 @@
+import React, {Component} from 'react';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import { connect } from "react-redux";
+
+/** Dialog for server error */
+class ServerErrorScreen extends Component {
+
+ render() {
+
+ const actions = [
+ this.props.logout()}
+ />,
+ ];
+
+ return (
+
+ Something has gone wrong likely having to do with internets and tubes.
+ Re-login into your account to get back to business.
+
+ );
+ }
+}
+
+const mapStateToProps = (state) => {
+ return {
+ serverError: state.global.serverError
+ };
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ logout: () => {
+ dispatch({type: "LOGOUT_SUCCESS"})
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(ServerErrorScreen);
diff --git a/viscoll-app/src/components/imageManager/AddManifest.js b/viscoll-app/src/components/imageManager/AddManifest.js
new file mode 100644
index 00000000..600b4a1e
--- /dev/null
+++ b/viscoll-app/src/components/imageManager/AddManifest.js
@@ -0,0 +1,133 @@
+import React, {Component} from 'react';
+import RaisedButton from 'material-ui/RaisedButton';
+import TextField from 'material-ui/TextField';
+import {floatFieldLight} from '../../styles/textfield';
+import UploadImages from './UploadImages';
+import { btnBase } from '../../styles/button';
+
+/** Form to submit a new manifest */
+export default class AddManifest extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ url: "",
+ urlError: "",
+ };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.createManifestError!==""){
+ if (this.state.urlError==="")
+ this.setState({urlError: nextProps.createManifestError});
+ } else {
+ this.setState({url: "", urlError: ""});
+ }
+ }
+
+ onChange = (type, value) => {
+ this.setState({[type]: value}, ()=>{this.runValidation()})
+ }
+
+ onSubmit = (e) => {
+ e.preventDefault();
+ const manifest = {url: this.state.url}
+ this.props.action.createManifest({manifest});
+ }
+
+ onCancel = (e) => {
+ this.setState({url: "", urlError: ""})
+ this.props.action.cancelCreateManifest();
+ }
+
+ runValidation = () => {
+ // Check if manifest url already exists
+ for (const manifestID in this.props.manifests){
+ const manifest = this.props.manifests[manifestID];
+ if (manifest.url===this.state.url){
+ this.setState({urlError: `Manifest with url: ${manifest.url} already exists.`});
+ return;
+ }
+ }
+ // Check if url is a valid JSON
+ fetch(this.state.url)
+ .then(response => {
+ const contentType = response.headers.get("content-type");
+ if(contentType && contentType.indexOf("json") !== -1) {
+ // No validation errors
+ if (response.url!==this.state.url) {
+ // Original URL was a redirect to a valid JSON url, so update our state
+ this.setState({url: response.url, urlError: ""});
+ }
+ this.setState({urlError: ""});
+ } else {
+ this.setState({urlError: "Invalid URL: the URL does not resolve to a JSON file."})
+ }
+ })
+ .catch(()=> {
+ this.setState({urlError: "Invalid URL: the URL does not resolve to a JSON file."})
+ })
+ }
+
+ isValid = () => {
+ return (this.state.urlError==="" && this.state.url!=="")
+ }
+
+ render() {
+ return (
+
+
Add new images
+
+
+
Upload images
+
+
+
+
Import images from a IIIF mancifest
+
this.onSubmit(e)}>
+
+
+ this.onChange("url", v)}
+ tabIndex={this.props.tabIndex}
+ {...floatFieldLight}
+ />
+
+
+
+ this.onCancel(e)}
+ {...btnBase()}
+ style={{...btnBase().style, marginRight: 5}}
+ tabIndex={this.props.tabIndex}
+ />
+ this.onSubmit(e)}
+ tabIndex={this.props.tabIndex}
+ {...btnBase()}
+ />
+
+
+
+
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/imageManager/DeleteManifest.js b/viscoll-app/src/components/imageManager/DeleteManifest.js
new file mode 100644
index 00000000..e5a05bde
--- /dev/null
+++ b/viscoll-app/src/components/imageManager/DeleteManifest.js
@@ -0,0 +1,41 @@
+import React, {Component} from 'react';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import RaisedButton from 'material-ui/RaisedButton';
+
+/** Confirmation dialog to delete manifest */
+export default class DeleteManifest extends Component {
+ render() {
+ const actions = [
+ ,
+ {this.props.handleClose(); this.props.deleteManifest()}}
+ backgroundColor="#b53c3c"
+ labelColor="#ffffff"
+ />,
+ ];
+
+ if (this.props.open) {
+ return (
+
+
+
+
+ );
+ } else {
+ return
;
+ }
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/src/components/imageManager/EditManifest.js b/viscoll-app/src/components/imageManager/EditManifest.js
new file mode 100644
index 00000000..47e34098
--- /dev/null
+++ b/viscoll-app/src/components/imageManager/EditManifest.js
@@ -0,0 +1,266 @@
+import React, {Component} from 'react';
+import RemoveImageConfirmation from './RemoveImageConfirmation';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import RaisedButton from 'material-ui/RaisedButton';
+import TextField from 'material-ui/TextField';
+import IconButton from 'material-ui/IconButton';
+import IconDelete from 'material-ui/svg-icons/action/delete-forever';
+import IconHide from 'material-ui/svg-icons/action/visibility-off';
+import IconAdd from 'material-ui/svg-icons/content/add';
+import VirtualList from 'react-tiny-virtual-list';
+import Menu from 'material-ui/Menu';
+import MenuItem from 'material-ui/MenuItem';
+import Popover from 'material-ui/Popover';
+import IconFilter from 'material-ui/svg-icons/content/filter-list';
+
+/** Dialog to edit manifest name */
+export default class EditManifest extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ name: props.manifest? props.manifest.name:"",
+ nameError: "",
+ confirmationOpen: "",
+ activeImg: "",
+ filter:{value:"this", text:"Viewing images in this project"},
+ filterOpen: false,
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.manifest) {
+ this.setState({name: nextProps.manifest.name})
+ }
+ }
+
+ toggleConfirmation = (value) => {
+ this.setState({confirmationOpen:value});
+ }
+
+ onChange = (type, value) => {
+ this.setState({[type]: value}, ()=>{this.runValidation()})
+ }
+
+ onSubmit = (e) => {
+ e.preventDefault();
+ const manifest = {id: this.props.manifest.id, name: this.state.name.trim()}
+ this.setState({name: ""}, ()=>{
+ this.props.action.updateManifest({manifest});
+ this.props.handleClose();
+ })
+ }
+
+ onCancel = (e) => {
+ this.setState({name: ""})
+ }
+
+ runValidation = () => {
+ for (const manifestID in this.props.manifests){
+ const manifest = this.props.manifests[manifestID];
+ if (manifestID!==this.props.manifest.id && manifest.name===this.state.name.trim()){
+ this.setState({nameError: `Manifest with name: ${manifest.name} already exists.`});
+ return;
+ } else if (this.state.name.length>51) {
+ this.setState({nameError: `Manifest name must be under 50 characters.`});
+ return;
+ }
+ }
+ // No validation errors
+ this.setState({nameError: ""});
+ }
+
+ isValid = () => {
+ return (this.state.nameError==="" && this.state.name!=="" && this.props.manifest.name!==this.state.name.trim())
+ }
+
+ handleFilterClick = (e) => {
+ e.preventDefault(); // Prevent ghost click
+ this.setState({
+ filterOpen: true,
+ anchorEl: e.currentTarget,
+ });
+ }
+
+ handleFilterChoice = (value, text) => {
+ this.setState({
+ filter: {value, text},
+ filterOpen: false,
+ })
+ }
+
+ renderImageTile = (index, style, images) => {
+ const img = images[index];
+ let projectCountText = "";
+ if (img.projectIDs) {
+ const inCurrentProject = img.projectIDs.includes(this.props.projectID);
+ if (img.projectIDs.length===1 && inCurrentProject) {
+ projectCountText = "In current project";
+ } else if (img.projectIDs.length===1) {
+ projectCountText = "In 1 project";
+ } else {
+ if (inCurrentProject) {
+ const plural = (img.projectIDs.length-1)>1?"s":"";
+ projectCountText = "In current project and " + (img.projectIDs.length-1) + " other project" + plural;
+ } else {
+ projectCountText = "In " + img.projectIDs.length + " projects";
+ }
+ }
+ }
+ if (img) {
+ return
+
+
+
+
+
+
{img.label}
+ {projectCountText}
+
+
+ projectID===this.props.projectID)>=0?"":"Link image to project"}
+ iconStyle={{color:"8b8b8b"}}
+ style={(img.manifestID===this.props.manifest.id)||(img.projectIDs && img.projectIDs.findIndex((projectID)=>projectID===this.props.projectID)>=0)?{display:"none"}:{width:"40px",padding:0}}
+ onClick={()=>this.props.action.linkImages([img.id])}
+ >
+
+
+ {this.setState({confirmationOpen:"hide", activeImg:img})}}
+ >
+
+
+ {
+ this.setState({confirmationOpen:"delete", activeImg:img})
+ }}
+ style={{width:"40px",padding:0}}
+ >
+
+
+
+
+
+ } else {
+ return
+ }
+ }
+
+ render() {
+ const editName = this.onSubmit(e)}>
+
+
Manifest name
+
+ this.onChange("name", v)}
+ fullWidth
+ autoFocus
+ />
+
+
+
+
+ if (this.props.open) {
+ const actions = [
+ ,
+ ,
+ ];
+ let images = this.props.manifest.images;
+ if (this.state.filter.value==="all") {
+ images = this.props.images;
+ }
+
+ return (
+
+
+ {this.props.manifest.id.includes("DIY")? "Edit images":"Edit manifest"}
+
+ {this.props.manifest.id.includes("DIY")?
+
+
+
}
+ />
+ this.setState({filterOpen: false})}
+ >
+
+ this.handleFilterChoice("this", "Viewing images in this project")} primaryText="View images in this project" />
+ this.handleFilterChoice("all", "Viewing all images")} primaryText="View all images" />
+
+
+
+
this.renderImageTile(index, style, images)}
+ overscanCount={10}
+ estimatedItemSize={400}
+ />
+
+ :
+ editName
+ }
+
+
+
0}
+ toggleConfirmation={this.toggleConfirmation}
+ deleteImages={this.props.action.deleteImages}
+ unlinkImages={this.props.action.unlinkImages}
+ imgs={this.state.activeImg?[{label:this.state.activeImg.label, id:this.state.activeImg.url.split("/").pop().split("_")[0]}]:[{id:"",label:""}]}
+ actionType={this.state.confirmationOpen}
+ numToRemove={1}
+ />
+
+ );
+ } else {
+ return
;
+ }
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/src/components/imageManager/ManageManifests.js b/viscoll-app/src/components/imageManager/ManageManifests.js
new file mode 100644
index 00000000..3ea406f6
--- /dev/null
+++ b/viscoll-app/src/components/imageManager/ManageManifests.js
@@ -0,0 +1,136 @@
+import React, {Component} from 'react';
+import FlatButton from 'material-ui/FlatButton';
+import {Card, CardActions} from 'material-ui/Card';
+import AddManifest from './AddManifest';
+import EditManifest from './EditManifest';
+import DeleteManifest from './DeleteManifest';
+import ImageViewer from "../global/ImageViewer";
+import Dialog from 'material-ui/Dialog';
+
+/** Manage Images page */
+export default class ManageManifests extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ editOpen: false,
+ deleteOpen: false,
+ activeManifestID: "",
+ imageModalOpen: false,
+ activeImage: {manifestID:"",}
+ };
+ }
+ handleOpen = (name, manifestID) => {
+ this.setState({[name]: true, activeManifestID:manifestID});
+ this.props.togglePopUp(true);
+ };
+
+ handleClose = (name) => {
+ this.setState({[name]: false});
+ this.props.togglePopUp(false);
+ };
+
+ toggleImageModal = (imageModalOpen, activeImage) => {
+ this.setState({imageModalOpen, activeImage});
+ this.props.togglePopUp(imageModalOpen);
+ }
+
+ renderManifestCard = (manifestID) => {
+ const manifest = this.props.manifests[manifestID]
+ return (
+
+
+
+
+
{manifest.name}
+ {manifest.url}
+
+
+ {manifest.images.slice(0,4).map((img) => (
+
this.toggleImageModal(true, img)}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+ ))}
+
+
+
+ this.handleOpen("editOpen", manifestID)}
+ tabIndex={this.props.tabIndex}
+ />
+ this.handleOpen("deleteOpen", manifestID)}
+ tabIndex={this.props.tabIndex}
+ disabled={manifest.id.includes("DIY")}
+ />
+
+
+
+
+ )
+ }
+
+ render() {
+ return (
+
+
+
this.handleClose("editOpen")}
+ manifest={this.props.manifests[this.state.activeManifestID]}
+ manifests={this.props.manifests}
+ images={this.props.images}
+ projectID={this.props.projectID}
+ action={{
+ updateManifest: this.props.action.updateManifest,
+ linkImages: this.props.action.linkImages,
+ unlinkImages: this.props.action.unlinkImages,
+ deleteImages: this.props.action.deleteImages,
+ }}
+ />
+ this.handleClose("deleteOpen")}
+ deleteManifest={()=>this.props.action.deleteManifest({manifest: {id: this.state.activeManifestID}})}
+ />
+
+ Image collections
+ {this.props.manifests? Object.keys(this.props.manifests).map(this.renderManifestCard) : "" }
+ this.toggleImageModal(false)}
+ contentStyle={{background: "none", boxShadow: "inherit"}}
+ bodyStyle={{padding:0}}
+ >
+ {this.state.activeImage?
+ :""}
+
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/imageManager/MapImages.js b/viscoll-app/src/components/imageManager/MapImages.js
new file mode 100644
index 00000000..176bec03
--- /dev/null
+++ b/viscoll-app/src/components/imageManager/MapImages.js
@@ -0,0 +1,444 @@
+import React, {Component} from 'react';
+import SideBacklog from './mapImages/SideBacklog';
+import ImageBacklog from './mapImages/ImageBacklog';
+import MapBoard from './mapImages/MapBoard';
+import RaisedButton from 'material-ui/RaisedButton';
+import ImageViewer from "../global/ImageViewer";
+import Dialog from 'material-ui/Dialog';
+import SelectField from '../global/SelectField';
+import { btnBase } from '../../styles/button';
+
+/** Map Images page */
+export default class MapImages extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ imageMapBoard: [], // [{manifestID: "", label: "", url: ""}, ...]
+ sideMapBoard: [], // [sideID, ...]
+ imageBacklog: [], // [{manifestID: "", label: "", url: ""}, ...]
+ sideBacklog: [], // [sideID, ...]
+ activeManifest: this.props.manifests[Object.keys(this.props.manifests)[0]], // {id: "", url: "", images: []}
+ initialMapping: {imageMapBoard: [], sideMapBoard: [], imageBacklog: {}, sideBacklog: []},
+ selectedObjects: {type: "", members: [], lastSelected: null},
+ imageModalOpen: false,
+ activeImage: null // {url:"", isDIY: true/false}
+ }
+ }
+
+ componentWillMount() {
+ this.updateBoards(this.props);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ let members = [];
+ if (nextProps.selectAll!=="")
+ members = [...this.state[nextProps.selectAll]];
+ const selectedObjects = {type: nextProps.selectAll, members, lastSelected: members[-1]};
+ this.setState({ selectedObjects });
+ if (nextProps.Rectos!==this.props.Rectos||nextProps.Versos!==this.props.Versos) this.updateBoards(nextProps);
+ }
+
+ updateBoards = (props) => {
+ // Update initial map board with already existing mappings
+ const { Rectos, rectoIDs, Versos, versoIDs } = props;
+ let imageBacklog = [];
+ for (let manifest of Object.entries(props.manifests)) {
+ imageBacklog = imageBacklog.concat(manifest[1].images);
+ }
+ let sideBacklog = [...rectoIDs].map((rectoID, index) => [rectoID, versoIDs[index]]).reduce((a,b)=> a.concat(b), []);
+ let imageMapBoard = [];
+ let sideMapBoard = [];
+ // Add sides to board or backlog depending if they're linked to images
+ for (const sideID of sideBacklog) {
+ const side = sideID.charAt(0)==="R" ? Rectos[sideID] : Versos[sideID];
+ if (side.image.label){
+ sideMapBoard.push(sideID);
+ imageMapBoard.push(side.image);
+ }
+ }
+ // Remove items from backlog which already exists in the intial map board
+ imageBacklog = imageBacklog.filter(backlogImage => !imageMapBoard.find(mapImage => mapImage.url===backlogImage.url && mapImage.manifestID===backlogImage.manifestID));
+ sideBacklog = sideBacklog.filter(sideID => !sideMapBoard.includes(sideID));
+ this.setState({imageBacklog, sideBacklog, sideMapBoard, imageMapBoard, initialMapping:{imageBacklog, sideBacklog, sideMapBoard, imageMapBoard}});
+ }
+
+ handleManifestChange = activeManifestID => this.setState({activeManifest: this.props.manifests[activeManifestID]});
+
+ toggleImageModal = (imageModalOpen, url, isDIY) => {this.props.togglePopUp(imageModalOpen); this.setState({imageModalOpen, activeImage: {url, isDIY}})};
+
+ resetChanges = () => {
+ const { imageBacklog, sideBacklog, sideMapBoard, imageMapBoard } = this.state.initialMapping;
+ const selectedObjects = {type: "", members: [], lastSelected: null};
+ const activeManifest = this.props.manifests[Object.keys(this.props.manifests)[0]];
+ this.setState({ imageBacklog, sideBacklog, sideMapBoard, imageMapBoard, selectedObjects, activeManifest });
+ }
+
+ handleObjectClick = (type, object, event) => {
+ let selectedObjects = {...this.state.selectedObjects, members: [...this.state.selectedObjects.members]};
+ if (event.ctrlKey || event.metaKey || (event.modifiers!==undefined && event.modifiers.command)) {
+ // Toggle this object without clearing active objects unless type is different
+ if (selectedObjects.type !== type) {
+ selectedObjects.members = [];
+ selectedObjects.type = type;
+ }
+ let index;
+ if (type.includes("image"))
+ index = selectedObjects.members.findIndex(member => member.url===object.url && member.manifestID===object.manifestID);
+ else
+ index = selectedObjects.members.indexOf(object);
+ (index!==-1) ? selectedObjects.members.splice(index, 1) : selectedObjects.members.push(object);
+ }
+ if (event.button === 0 || event.modifiers!==undefined) {
+ let notCtrl=event.ctrlKey !== undefined && !event.ctrlKey && !event.shiftKey;
+ let notCmd=event.metaKey !== undefined && !event.metaKey && !event.shiftKey;
+ let notCanvasCmd=event.modifiers !== undefined && !event.modifiers.command && !event.modifiers.shift;
+ if ((notCtrl&¬Cmd)||notCanvasCmd) {
+ // Clear all and toggle only this object
+ if (selectedObjects.members.includes(object)) {
+ selectedObjects.members = [];
+ }
+ else {
+ selectedObjects.members = [object];
+ }
+ }
+ if (event.shiftKey || (event.modifiers!==undefined && event.modifiers.shift)) {
+ window.getSelection().removeAllRanges();
+ // Object type changed, clear all active selected objects
+ if (selectedObjects.type !== type) {
+ selectedObjects.members = [object];
+ } else {
+ // Select all similar type objects within this object and last selected object
+ let allMembers = [...this.state[type]];
+ let indexOfCurrentElement, indexOfLastElement;
+ if (type.includes("image")){
+ indexOfCurrentElement = allMembers.findIndex(member => member.url===object.url && member.manifestID===object.manifestID);
+ indexOfLastElement = allMembers.findIndex(member => member.url===selectedObjects.lastSelected.url && member.manifestID===selectedObjects.lastSelected.manifestID);
+ }
+ else {
+ indexOfCurrentElement = allMembers.indexOf(object);
+ indexOfLastElement = allMembers.indexOf(selectedObjects.lastSelected);
+ }
+ let indexes = [indexOfLastElement, indexOfCurrentElement];
+ indexes.sort((a, b) => {return a-b});
+ const currentSelected = [...selectedObjects.members];
+ selectedObjects.members = allMembers.slice(indexes[0], indexes[1]+1);
+ for (let object of currentSelected){
+ if (!selectedObjects.members.includes(object))
+ selectedObjects.members.push(object);
+ }
+ }
+ }
+ }
+ if (selectedObjects.members.length === 0) {
+ selectedObjects.type = "";
+ } else {
+ selectedObjects.type = type;
+ selectedObjects.lastSelected = object;
+ }
+ this.setState({ selectedObjects });
+ }
+
+ moveItemUpOrDown = (item, mapBoardType, position) => {
+ let newMapBoard = [...this.state[mapBoardType]];
+ let indexOfItem;
+ if (mapBoardType==="imageMapBoard"){
+ indexOfItem = newMapBoard.findIndex(image => image.url===item.url && image.manifestID===item.manifestID);
+ } else {
+ indexOfItem = newMapBoard.indexOf(item);
+ }
+ const indexOfSwappingItem = position==="down" ? indexOfItem+1 : indexOfItem-1;
+ const swappedItem = newMapBoard[indexOfSwappingItem];
+ newMapBoard[indexOfItem] = swappedItem;
+ newMapBoard[indexOfSwappingItem] = item;
+ this.setState({ [mapBoardType]: newMapBoard });
+ }
+
+ moveItemsToMap = (items=this.state.selectedObjects.members, mapBoardType, backlogBoardType) => {
+ let newMapBoard = [...this.state[mapBoardType], ...items];
+ let newBacklogBoard;
+ if (mapBoardType==="imageMapBoard"){
+ newBacklogBoard = [...this.state[backlogBoardType]].filter(image => !items.find(item => item.url===image.url && item.manifestID===image.manifestID));
+ } else {
+ newBacklogBoard = [...this.state[backlogBoardType]].filter(item => !items.includes(item));
+ }
+ let selectedObjects = {...this.state.selectedObjects, members: [...this.state.selectedObjects.members]};
+ selectedObjects.type = mapBoardType;
+ this.setState({ [mapBoardType]: newMapBoard, [backlogBoardType]: newBacklogBoard, selectedObjects });
+ }
+
+ moveItemsToBacklog = (items=this.state.selectedObjects.members, mapBoardType, backlogBoardType) => {
+ let newBacklogBoard = [...this.state[backlogBoardType], ...items];
+ let newMapBoard;
+ if (mapBoardType==="imageMapBoard"){
+ newMapBoard = [...this.state[mapBoardType]].filter(image => !items.find(item => item.url===image.url && item.manifestID===image.manifestID));
+ newBacklogBoard.sort((a, b)=>this.state.initialMapping[backlogBoardType].findIndex(image => image.url===a.url && image.manifestID===a.manifestID) > this.state.initialMapping[backlogBoardType].findIndex(image => image.url===b.url && image.manifestID===b.manifestID) ? 1 : -1);
+ // newBacklogBoard.sort((a, b)=>a.label>b.label ? 1 : -1);
+ } else {
+ newMapBoard = [...this.state[mapBoardType]].filter(item => !items.includes(item));
+ newBacklogBoard.sort((a, b)=>this.state.initialMapping[backlogBoardType].indexOf(a) > this.state.initialMapping[backlogBoardType].indexOf(b) ? 1 : -1);
+ }
+ let selectedObjects = {...this.state.selectedObjects, members: [...this.state.selectedObjects.members]};
+ selectedObjects.type = backlogBoardType;
+ this.setState({ [mapBoardType]: newMapBoard, [backlogBoardType]: newBacklogBoard, selectedObjects });
+ }
+
+ submitIsDisabled = () => {
+ // check for changes in sideMapBoard
+ const sideMapBoard = this.state.sideMapBoard;
+ const initialSideMapBoard = this.state.initialMapping.sideMapBoard;
+ let noChangesInSideMapBoard = sideMapBoard.length===initialSideMapBoard.length && sideMapBoard.every((v,i) => v===initialSideMapBoard[i]);
+ // check for changes in imageMapBoard
+ const imageMapBoard = this.state.imageMapBoard;
+ const initialImageMapBoard = this.state.initialMapping.imageMapBoard;
+ let noChangesInImageMapBoard = imageMapBoard.length===initialImageMapBoard.length && imageMapBoard.every((v,i) => v.url===initialImageMapBoard[i].url && v.manifestID===initialImageMapBoard[i].manifestID);
+ // compare both changes
+ const noChanges = noChangesInSideMapBoard && noChangesInImageMapBoard;
+ const unevenMatches = this.state.sideMapBoard.length!==this.state.imageMapBoard.length;
+ return unevenMatches || noChanges;
+ }
+
+ automatchIsDisabled = () => {
+ let findFolioNumber = function(image) {
+ return this.folioNumber && image.label.includes(this.folioNumber.toLowerCase());
+ }
+ for (const sideID of this.state.sideBacklog) {
+ const side = sideID.charAt(0)==="R" ? this.props.Rectos[sideID] : this.props.Versos[sideID];
+ // Return immediately if a match is found
+ if (this.state.imageBacklog.find(findFolioNumber, {folioNumber: side.folio_number})) return false;
+ }
+ return true;
+ }
+
+ automatch = () => {
+ let findByFolioNumber = function(image) {
+ let tokenizedLabel = image.label.split(".");
+ let label = tokenizedLabel[tokenizedLabel.length-2];
+ return label.endsWith(this.side.folio_number.toLowerCase())
+ }
+ let sideItemsToMap = [];
+ let imageItemsToMap = [];
+ for (const sideID of this.state.sideBacklog) {
+ const side = sideID.charAt(0)==="R" ? this.props.Rectos[sideID] : this.props.Versos[sideID];
+ const image = this.state.imageBacklog.find(findByFolioNumber, {side});
+ if (image) {
+ sideItemsToMap.push(sideID);
+ imageItemsToMap.push(image);
+ }
+ }
+ this.moveItemsToMap(sideItemsToMap, "sideMapBoard", "sideBacklog");
+ this.moveItemsToMap(imageItemsToMap, "imageMapBoard", "imageBacklog");
+ }
+
+ submitMapping = () => {
+ let newSideMappings = [];
+ let unlinkedSideMappings = [];
+ let sideIDsWithImage = this.state.sideMapBoard.map((sideID, i) => [sideID, this.state.imageMapBoard[i]]);
+ for (let [sideID, image] of sideIDsWithImage){
+ newSideMappings.push({ id: sideID, attributes: {image} });
+ }
+ for (let sideID of this.state.initialMapping.sideMapBoard) {
+ if (this.state.sideMapBoard.indexOf(sideID)===-1)
+ unlinkedSideMappings.push({ id: sideID, attributes: {image: {}}})
+ }
+ this.props.mapSidesToImages(newSideMappings.concat(unlinkedSideMappings));
+ // Update initial mapping list
+ this.setState({
+ initialMapping: {imageMapBoard: this.state.imageMapBoard, sideMapBoard: this.state.sideMapBoard, imageBacklog: this.state.imageBacklog, sideBacklog: this.state.sideBacklog}
+ })
+ }
+
+ renderMoveUpButton = (target) => {
+ const mapboard = target + "MapBoard";
+ const backlog = target + "Backlog";
+ return (
+ this.moveItemsToMap(undefined, mapboard, backlog)}
+ disabled={this.state.selectedObjects.members.length===0 || this.state.selectedObjects.type!==backlog}
+ {...btnBase()}
+ style={{...btnBase().style, marginRight:"0.2em"}}
+ tabIndex={this.props.tabIndex}
+ />
+ )
+ }
+ renderMoveDownButton = (target) => {
+ const mapboard = target + "MapBoard";
+ const backlog = target + "Backlog";
+ return (
+ this.moveItemsToBacklog(undefined, mapboard, backlog)}
+ disabled={this.state.selectedObjects.members.length===0 || this.state.selectedObjects.type!==mapboard}
+ tabIndex={this.props.tabIndex}
+ {...btnBase()}
+ />
+ )
+ }
+
+
+ render() {
+ if (Object.keys(this.props.manifests).length<1) {
+ return (
+
+
Getting started
+
To start mapping images to the collation, please add images in the "Manage Images" tab.
+
+ );
+ }
+
+ const mapBoard = (
+
+ );
+
+ const middlePanel = (
+
+
+ {this.renderMoveUpButton("side")}
+ {this.renderMoveDownButton("side")}
+
+
+
+
+
+ {this.renderMoveUpButton("image")}
+ {this.renderMoveDownButton("image")}
+
+
+ );
+
+ const sideBacklog = (
+
+ );
+
+ const imageBacklog = (
+
+
+
Image Backlog
+
+
+ this.handleManifestChange(manifestID)}
+ tabIndex={this.props.tabIndex}
+ data={Object.entries(this.props.manifests).map(([manifestID, manifest])=>{
+ return {value: manifestID, text: manifest.name}}
+ )}
+ />
+
+
+
+
+
+ backlogImage.manifestID===this.state.activeManifest.id)}
+ activeManifest={this.state.activeManifest}
+ manifests={this.props.manifests}
+ toggleImageModal={this.toggleImageModal}
+ handleObjectClick={this.handleObjectClick}
+ selectedObjects={this.state.selectedObjects}
+ moveItemsToMap={this.moveItemsToMap}
+ tabIndex={this.props.tabIndex}
+ windowWidth={this.props.windowWidth}
+ />
+
+
+ );
+
+ const mainToolBar = (
+
+
Mapping {this.state.sideMapBoard.length} sides to {this.state.imageMapBoard.length} images
+
+
+
+
+
+ );
+
+ const imageViewerModal = (
+ this.toggleImageModal(false)}
+ contentStyle={{background: "none", boxShadow: "inherit"}}
+ bodyStyle={{padding:0}}
+ >
+
+
+ );
+
+ return (
+
+
+ {middlePanel}
+
+ {sideBacklog}
+ {imageBacklog}
+
+ {mainToolBar}
+ {imageViewerModal}
+
+ );
+ }
+}
+
diff --git a/viscoll-app/src/components/imageManager/RemoveImageConfirmation.js b/viscoll-app/src/components/imageManager/RemoveImageConfirmation.js
new file mode 100644
index 00000000..a6b57fb8
--- /dev/null
+++ b/viscoll-app/src/components/imageManager/RemoveImageConfirmation.js
@@ -0,0 +1,67 @@
+import React from 'react';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import RaisedButton from 'material-ui/RaisedButton';
+
+/** Confirmation dialog to delete or unlink image(s) */
+export default class RemveImageConfirmation extends React.Component {
+
+ submit = () => {
+ if (this.props.actionType==="delete") {
+ this.props.deleteImages(this.props.imgs.map((img)=>img.id));
+ } else {
+ this.props.unlinkImages(this.props.imgs.map((img)=>img.id));
+ }
+ this.props.toggleConfirmation("");
+ }
+ render() {
+ const actions = [
+ this.props.toggleConfirmation("")}
+ />,
+ ,
+ ];
+ let title, message;
+ if (this.props.numToRemove===1) {
+ title = "Are you sure you want to " + this.props.actionType + " " + this.props.imgs[0].label + "?";
+ if (!this.props.collectionsMode && this.props.actionType==="hide") {
+ message = "You can add the image back to this project again by switching to 'view all images'.";
+ } else if (!this.props.collectionsMode && this.props.actionType==="delete") {
+ message = "If this image is used in other projects, deleting this image will remove it from other projects as well.";
+ } else if (this.props.actionType==="delete") {
+ message = "Deleting this image will remove it from all projects that it is linked to.";
+ }
+ } else {
+ // Multiple images to remove
+ title = "Are you sure you want to " + this.props.actionType + " " + this.props.numToRemove + " images?"
+ if (!this.props.collectionsMode && this.props.actionType==="hide") {
+ message = "You can add the images back to this project again by switching to 'view all images'.";
+ } else if (!this.props.collectionsMode && this.props.actionType==="delete") {
+ message = "If these images are used in other projects, deleting these images will remove them from other projects as well.";
+ } else if (this.props.actionType==="delete") {
+ message = "Deleting these images will remove them from all the projects that they are linked to.";
+ }
+ }
+ return (
+
+ this.props.toggleConfirmation("")}
+ >
+ { message }
+
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/src/components/imageManager/UploadImages.js b/viscoll-app/src/components/imageManager/UploadImages.js
new file mode 100644
index 00000000..1d9e9c5d
--- /dev/null
+++ b/viscoll-app/src/components/imageManager/UploadImages.js
@@ -0,0 +1,178 @@
+import React, {Component} from 'react';
+import update from 'immutability-helper';
+import RaisedButton from 'material-ui/RaisedButton';
+import Checkbox from 'material-ui/Checkbox';
+import { btnBase } from '../../styles/button';
+
+/** Upload image component */
+class UploadImages extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ uploadedImages: [],
+ uploadedDuplicates: [],
+ duplicatesToUpload: [],
+ filesizeExceeded: [],
+ };
+ }
+
+ /**
+ * Removes file extension and replaces any "." with underscore
+ */
+ parseFilename = (filename) => {
+ let tokenizedName = filename.split(/[\s.-]+/);
+ tokenizedName.splice(-1);
+ tokenizedName = tokenizedName.join("_");
+ return tokenizedName.toLowerCase();
+ }
+
+ submitUpload = (e) => {
+ e.preventDefault();
+ const duplicateImageUploads = this.state.uploadedDuplicates.filter((item,i)=>this.state.duplicatesToUpload[i]);
+ const uploadedImages = this.state.uploadedImages.concat(duplicateImageUploads);
+ this.props.action.uploadImages(uploadedImages);
+ // Clear input file list
+ this.setState({uploadedImages:[], filesizeExceeded: [], duplicatesToUpload:[], uploadedDuplicates:[]});
+ document.getElementById("uploadImages").value="";
+ }
+
+ resetUpload = (e) => {
+ e.preventDefault();
+ this.setState({uploadedImages:[], filesizeExceeded:[], duplicatesToUpload:[], uploadedDuplicates:[]});
+ document.getElementById("uploadImages").value="";
+ }
+
+ handleFiles = (files) => {
+ let findIfExists = function(img) {
+ return img.label===this.filename+"."+this.fileExtension;
+ }
+ let that = this;
+ let onLoadEnd = function(filename, targetState) {
+ return function (event) {
+ const newImageList = update(that.state[targetState], {$push:[{filename, content:this.result}]});
+ that.setState({[targetState]: newImageList});
+ }
+ }
+ this.setState({uploadedImages: []}, ()=>{
+ let duplicatesToUpload = [];
+ let filesizeExceeded = [];
+ for (let i=0; i15728640) {
+ // Filesize is greater than 15mb, do not upload
+ filesizeExceeded.push(filename);
+ } else {
+ const reader = new FileReader();
+ // Read file content
+ reader.readAsDataURL(file);
+ if (this.props.images.findIndex(findIfExists, {filename, fileExtension})>=0) {
+ // Filename already exists, read file and store in 'uploadedDuplicates' state
+ duplicatesToUpload.push(true);
+ reader.onloadend = onLoadEnd(filename,"uploadedDuplicates");
+ } else {
+ // Filename doesn't exist, read file and store in 'uploadedImages' state
+ reader.onloadend = onLoadEnd(filename, "uploadedImages");
+ }
+ }
+ }
+ if (duplicatesToUpload.length>0) {
+ this.setState({duplicatesToUpload});
+ }
+ if (filesizeExceeded.length>0) {
+ this.setState({filesizeExceeded});
+ }
+ })
+ }
+
+ toggleCheckbox = (index) => {
+ let duplicatesToUpload = [...this.state.duplicatesToUpload];
+ duplicatesToUpload[index] = !duplicatesToUpload[index];
+ this.setState({duplicatesToUpload});
+ }
+
+ render() {
+ // Generate file duplicate error message content if neccessary
+ let duplicateErrorSection = null;
+ let filesizeErrorSection = null;
+ let errorText = "";
+ if (this.state.duplicatesToUpload.length>0) {
+ duplicateErrorSection =
+
+
The following file{this.state.duplicatesToUpload.length>1?"s ":" "}
+ already exist{this.state.duplicatesToUpload.length>1?"":"s"}. If you choose to upload
+ {this.state.duplicatesToUpload.length>1?" them":" it"}, {this.state.duplicatesToUpload.length>1?"they ":"it "}
+ will get renamed.
+ {
+ this.state.uploadedDuplicates.map((file,i)=>
+
30?file.filename.substring(0,30)+"...": file.filename}
+ labelStyle={{color:"#bd4a4a"}}
+ checked={this.state.duplicatesToUpload[i]}
+ onClick={()=>this.toggleCheckbox(i)}
+ />
+ )
+ }
+
+
+ }
+ if (this.state.filesizeExceeded.length>0) {
+ filesizeErrorSection = 0?{wordWrap: "break-word",marginTop:5}:{wordWrap: "break-word"}}>
+
+
The following file{this.state.filesizeExceeded.length>1?"s are":" is"} greater than 15mb and will not be uploaded:
+
+ {this.state.filesizeExceeded.map((filename)=>{filename} )}
+
+
+
+ }
+
+ if ((duplicateErrorSection || filesizeErrorSection) && this.state.uploadedImages.length>0) {
+ errorText = There {this.state.uploadedImages.length>1?"are":"is"} {this.state.uploadedImages.length} other file{this.state.uploadedImages.length>1?"s":""} ready for upload.
;
+ }
+
+ return
+
+
this.handleFiles(event.target.files)}
+ onClick={(event)=>{event.target.value=null; this.setState({uploadedImages:[], filesizeExceeded:[], duplicatesToUpload:[], uploadedDuplicates:[]})}}
+ multiple
+ style={{width:"100%"}}
+ />
+
+ Accepted formats: .png, .jpeg, .jpg, .gif, .tiff
+
+
+ {duplicateErrorSection}
+ {filesizeErrorSection}
+ {errorText}
+
+ this.resetUpload(e)}
+ tabIndex={this.props.tabIndex}
+ {...btnBase()}
+ style={{...btnBase().style, marginRight: 5}}
+ />
+ x)<0)}
+ onClick={this.submitUpload}
+ primary
+ {...btnBase()}
+ />
+
+
+ }
+}
+export default UploadImages;
\ No newline at end of file
diff --git a/viscoll-app/src/components/imageManager/mapImages/ImageBacklog.js b/viscoll-app/src/components/imageManager/mapImages/ImageBacklog.js
new file mode 100644
index 00000000..c52585cf
--- /dev/null
+++ b/viscoll-app/src/components/imageManager/mapImages/ImageBacklog.js
@@ -0,0 +1,77 @@
+import React, { Component } from 'react';
+import ThumbnailIcon from 'material-ui/svg-icons/editor/insert-photo';
+import IconButton from 'material-ui/IconButton';
+import Add from 'material-ui/svg-icons/content/add-circle-outline';
+import VirtualList from 'react-tiny-virtual-list';
+
+/** Panel for unmapped images */
+export default class ImageBacklog extends Component {
+
+ renderImageItem = (index, style) => {
+ const image = this.props.images[index];
+ let actionButtons = (
+
+
{event.stopPropagation();this.props.moveItemsToMap([image], "imageMapBoard", "imageBacklog")}}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+
+ );
+ let activeStyle = {};
+ if (this.props.selectedObjects.members.find(item => item.url===image.url && item.manifestID===image.manifestID))
+ activeStyle = {backgroundColor: "#4ED6CB"}
+ return (
+ this.props.handleObjectClick(this.props.id, image, event)} >
+
+
{e.stopPropagation();this.props.toggleImageModal(true, image.url, image.manifestID.includes("DIY"))}}>
+
+
+
+
+
+ {image.label}
+
+ {this.props.activeManifest.name}
+
+
+ {actionButtons}
+
+ );
+ }
+
+ render() {
+ if (this.props.id==="imageMapBoard") {
+ return (
+
+ this.renderImageItem(index, style)}
+ overscanCount={10}
+ estimatedItemSize={400}
+ />
+
+ );
+ }
+
+ // imageBacklog
+ return (
+ this.renderImageItem(index, style)}
+ overscanCount={10}
+ estimatedItemSize={400}
+ />
+ );
+
+
+ }
+}
diff --git a/viscoll-app/src/components/imageManager/mapImages/MapBoard.js b/viscoll-app/src/components/imageManager/mapImages/MapBoard.js
new file mode 100644
index 00000000..bc26ac98
--- /dev/null
+++ b/viscoll-app/src/components/imageManager/mapImages/MapBoard.js
@@ -0,0 +1,175 @@
+import React, { Component } from 'react';
+import ThumbnailIcon from 'material-ui/svg-icons/editor/insert-photo';
+import IconButton from 'material-ui/IconButton';
+import ArrowDown from 'material-ui/svg-icons/navigation/arrow-downward';
+import ArrowUp from 'material-ui/svg-icons/navigation/arrow-upward';
+import Remove from 'material-ui/svg-icons/content/remove-circle-outline';
+import VirtualList from 'react-tiny-virtual-list';
+
+/** Panel with the mapped sides and images */
+export default class MapBoard extends Component {
+
+ renderSideItem = (index) => {
+ const sideID = this.props.sideIDs[index];
+ const sideType = sideID.charAt(0)==="R"? "recto" : "verso";
+ const side = sideType === "recto" ? this.props.Rectos[sideID] : this.props.Versos[sideID];
+ const folioNumber = side.folio_number && side.folio_number!=="" ? "("+side.folio_number+")" : "";
+ const pageNumber = side.page_number && side.page_number!=="" ? "("+side.page_number+")" : "";
+ const leafOrder = this.props.leafIDs.indexOf(side.parentID)+1;
+
+ let actionButtons = (
+ event.stopPropagation()}>
+
this.props.moveItemUpOrDown(sideID, "sideMapBoard", "up")}
+ disabled={index===0}
+ tabIndex={this.props.tabIndex}
+ style={this.props.windowWidth<=768?{width:40,height:40}:{}}
+ >
+
+
+
this.props.moveItemUpOrDown(sideID, "sideMapBoard", "down")}
+ disabled={index===this.props.sideIDs.length-1}
+ tabIndex={this.props.tabIndex}
+ style={this.props.windowWidth<=768?{width:40,height:40}:{}}
+ >
+
+
+
this.props.moveItemsToBacklog([sideID], "sideMapBoard", "sideBacklog")}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+
+ );
+ let activeStyle = {};
+ if (this.props.selectedObjects.members.includes(sideID))
+ activeStyle = {backgroundColor: "#4ED6CB"}
+ return (
+ this.props.handleObjectClick("sideMapBoard", sideID, event)}>
+
+ {"Leaf " + leafOrder + " " + side.memberType + " " + folioNumber + " " + pageNumber}
+
+
+ {actionButtons}
+
+ );
+ }
+
+ renderGhostSideItem = (index) => {
+ return (
);
+ }
+
+ renderImageItem = (index) => {
+ const image = this.props.images[index];
+ let actionButtons = (
+ event.stopPropagation()}>
+
this.props.moveItemUpOrDown(image, "imageMapBoard", "up")}
+ disabled={index===0}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+
this.props.moveItemUpOrDown(image, "imageMapBoard", "down")}
+ tabIndex={this.props.tabIndex}
+ disabled={index===this.props.images.length-1}
+ >
+
+
+
this.props.moveItemsToBacklog([image], "imageMapBoard", "imageBacklog")}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+
+ );
+ let activeStyle = {};
+ if (this.props.selectedObjects.members.find(item => item.url===image.url && item.manifestID===image.manifestID))
+ activeStyle = {backgroundColor: "#4ED6CB"}
+ let manifestName = this.props.manifests[image.manifestID].name;
+ if (this.props.windowWidth<=768) {
+ manifestName = manifestName.substring(0,8) + "...";
+ } else if (this.props.windowWidth<=1024) {
+ manifestName = manifestName.substring(0,25) + "...";
+ }
+ return (
+ this.props.handleObjectClick("imageMapBoard", image, event)} >
+
+
{e.stopPropagation();this.props.toggleImageModal(true, image.url, image.manifestID.includes("DIY"))}}>
+
+
+
+
+
+ {image.label}
+
+ {manifestName}
+
+
+ {actionButtons}
+
+ );
+ }
+
+
+ renderGhostImageItem = (index) => {
+ return (
);
+ }
+
+
+ renderItem = (index, style) => {
+ return (
+
+ {this.props.sideIDs[index] ?
+ this.renderSideItem(index)
+ :
+ this.renderGhostSideItem(index)
+ }
+ {this.props.images[index] ?
+ this.renderImageItem(index)
+ :
+ this.renderGhostImageItem(index)
+ }
+
+ );
+ }
+
+
+ render() {
+ if (this.props.sideIDs.length===0 && this.props.images.length===0){
+ return (
+ Move items from the "backlog" into this area
+ );
+ }
+
+ return (
+ this.renderItem(index, style)}
+ overscanCount={10}
+ estimatedItemSize={100}
+ />
+ );
+
+
+ }
+}
diff --git a/viscoll-app/src/components/imageManager/mapImages/SideBacklog.js b/viscoll-app/src/components/imageManager/mapImages/SideBacklog.js
new file mode 100644
index 00000000..7a7066b4
--- /dev/null
+++ b/viscoll-app/src/components/imageManager/mapImages/SideBacklog.js
@@ -0,0 +1,72 @@
+import React, { Component } from 'react';
+import IconButton from 'material-ui/IconButton';
+import Add from 'material-ui/svg-icons/content/add-circle-outline';
+import VirtualList from 'react-tiny-virtual-list';
+
+/** Panel for unmapped sides */
+export default class SideBacklog extends Component {
+
+ renderSideItem = (index, style) => {
+ const sideID = this.props.sideIDs[index];
+ const sideType = sideID.charAt(0)==="R"? "recto" : "verso";
+ const side = sideType === "recto" ? this.props.Rectos[sideID] : this.props.Versos[sideID];
+ const folioNumber = side.folio_number && side.folio_number!=="" ? "("+side.folio_number+")" : "";
+ const pageNumber = side.page_number && side.page_number!=="" ? "("+side.page_number+")" : "";
+ const leafOrder = this.props.leafIDs.indexOf(side.parentID)+1;
+ let actionButtons = (
+ event.stopPropagation()}>
+
this.props.moveItemsToMap([sideID], "sideMapBoard", "sideBacklog")}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+
+ );
+ let activeStyle = {};
+ if (this.props.selectedObjects.members.includes(sideID))
+ activeStyle = {backgroundColor: "#4ED6CB"}
+ return (
+ this.props.handleObjectClick(this.props.id, sideID, event)}>
+
+ {"Leaf " + leafOrder + " " + side.memberType + " " + folioNumber + " " + pageNumber}
+
+ {actionButtons}
+
+ );
+ }
+
+ render() {
+ if (this.props.id==="sideMapBoard") {
+ return (
+
+ this.renderSideItem(index, style)}
+ overscanCount={10}
+ estimatedItemSize={400}
+ />
+
+ );
+ }
+
+ // sideBacklog
+ return (
+ this.renderSideItem(index, style)}
+ overscanCount={10}
+ estimatedItemSize={400}
+ />
+ );
+
+ }
+}
diff --git a/viscoll-app/src/components/infoBox/GroupInfoBox.js b/viscoll-app/src/components/infoBox/GroupInfoBox.js
new file mode 100644
index 00000000..4b05d69d
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/GroupInfoBox.js
@@ -0,0 +1,689 @@
+import React from 'react';
+import MenuItem from 'material-ui/MenuItem';
+import RaisedButton from 'material-ui/RaisedButton';
+import Checkbox from 'material-ui/Checkbox';
+import TextField from 'material-ui/TextField';
+import IconSubmit from 'material-ui/svg-icons/action/done';
+import IconClear from 'material-ui/svg-icons/content/clear';
+import Visibility from 'material-ui/svg-icons/action/visibility';
+import VisibilityOff from 'material-ui/svg-icons/action/visibility-off';
+import AddGroupDialog from '../infoBox/dialog/AddGroupDialog';
+import DeleteConfirmationDialog from '../infoBox/dialog/DeleteConfirmationDialog';
+import Popover, {PopoverAnimationVertical} from 'material-ui/Popover';
+import Menu from 'material-ui/Menu';
+import Chip from 'material-ui/Chip';
+import IconButton from 'material-ui/IconButton';
+import IconAdd from 'material-ui/svg-icons/content/add';
+import IconPencil from 'material-ui/svg-icons/content/create';
+import Avatar from 'material-ui/Avatar';
+import AddNote from './dialog/AddNote';
+import VisualizationDialog from './dialog/VisualizationDialog';
+import SelectField from '../global/SelectField';
+import {btnBase} from '../../styles/button';
+import { checkboxStyle } from '../../styles/checkbox';
+import { renderNoteChip } from '../../helpers/renderHelper';
+
+/** Group infobox */
+export default class GroupInfoBox extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ ...this.emptyAttributeState(),
+ ...this.otherAttributeStates(),
+ ...this.visibilityHoverState(),
+ addButtonPopoverOpen: false,
+ addGroupDialogOpen: false,
+ addLeafDialogOpen: false,
+ visualizationDialogActive: "",
+ }
+ this.batchSubmit = this.batchSubmit.bind(this);
+ this.hasActiveAttributes = this.hasActiveAttributes.bind(this);
+ }
+
+ visibilityHoverState() {
+ let state = {};
+ for (var i in this.props.defaultAttributes) {
+ state["visibility_hover_" + this.props.defaultAttributes[i]["name"]]=false;
+ }
+ return state;
+ }
+
+ /**
+ * Creates a dictionary of attributes and if its toggled on or off during batch edit
+ * This is used for the checkbox states
+ */
+ otherAttributeStates() {
+ let state = {};
+ for (var i in this.props.defaultAttributes) {
+ state["batch_" + this.props.defaultAttributes[i]["name"]]=false;
+ state["editing_" + this.props.defaultAttributes[i]["name"]]=false;
+ }
+ return state;
+ }
+
+ /**
+ * Creates a dictionary of attributes with no values
+ */
+ emptyAttributeState() {
+ let state = {};
+ for (var i in this.props.defaultAttributes) {
+ state[this.props.defaultAttributes[i]["name"]]="";
+ }
+ return state;
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (this.props.selectedGroups.length < 2) {
+ this.setState({...this.emptyAttributeState()});
+ }
+ }
+
+ hasActiveAttributes() {
+ for (var i in this.props.defaultAttributes) {
+ if (this.state["batch_" + this.props.defaultAttributes[i]["name"]] &&
+ this.state[this.props.defaultAttributes[i]["name"]]!=="keep" &&
+ this.state[this.props.defaultAttributes[i]["name"]]!=="") {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ toggleAddGroupDialog = (value=false) => {
+ this.setState({ addGroupDialogOpen: value, addButtonPopoverOpen: false, })
+ if (value===false) this.props.togglePopUp(false);
+ }
+
+ toggleAddLeafDialog = (value=false) => {
+ this.setState({ addLeafDialogOpen: value, addButtonPopoverOpen: false, })
+ if (value===false) this.props.togglePopUp(false);
+ }
+
+ handleAddButtonTouchTap = (event) => {
+ event.preventDefault();
+ this.setState({
+ addButtonPopoverOpen: true,
+ popoverAnchorEl: event.currentTarget,
+ });
+ };
+
+ handleAddButtonRequestClose = () => {
+ this.setState({
+ addButtonPopoverOpen: false,
+ });
+ };
+
+ toggleCheckbox(target) {
+ let newToggleState = {};
+ newToggleState["batch_"+target]=!this.state["batch_"+target];
+ this.setState(newToggleState);
+ }
+
+ dropDownChange = (value, attributeName) => {
+ if (Object.keys(this.props.selectedGroups).length===1) {
+ // In single edit - we submit change immediately
+ this.singleSubmit(attributeName, value);
+ } else {
+ // In batch edit - save change of attribute to the state
+ let updatedAttribute = {};
+ updatedAttribute[attributeName] = value;
+ this.setState(updatedAttribute);
+ }
+ }
+
+ onTextboxChange = (value, attributeName) => {
+ let newAttributeState = {};
+ newAttributeState[attributeName] = value;
+ let newEditingState = {};
+ newEditingState["editing_"+attributeName] = true;
+ this.setState({...newAttributeState,...newEditingState});
+ };
+
+ textSubmit(e, attributeName) {
+ e.preventDefault();
+ let newEditingState = {};
+ newEditingState["editing_"+attributeName] = false;
+ this.setState({...newEditingState});
+ if (!this.state.isBatch) {
+ this.singleSubmit(attributeName, this.state[attributeName]);
+ }
+ }
+
+ textCancel(e, attributeName) {
+ let newAttributeState = {};
+ newAttributeState[attributeName] =
+ this.props.Groups[this.props.selectedGroups[0]][attributeName];
+ let newEditingState = {};
+ newEditingState["editing_"+attributeName] = false;
+ this.setState({...newAttributeState,...newEditingState});
+ }
+
+ singleSubmit = (attributeName, value) => {
+ let group = {};
+ group[attributeName] = value;
+ let id = this.props.selectedGroups[0];
+ this.props.action.updateGroup(id, group);
+ }
+ clickVisibility = (attributeName, value) => {
+ if (attributeName!=="type"||this.props.viewMode==="TABULAR") {
+ this.props.action.updatePreferences({group:{...this.props.preferences.group, [attributeName]:value}});
+ }
+ }
+ batchFlip() {
+ let groups = [];
+ for (let id of this.props.selectedGroups) {
+ let flippedDir
+ const dir = this.props.Groups[id].direction;
+ if(dir){
+ flippedDir = (dir === "left-to-right") ? "right-to-left" : "left-to-right";
+ } else {
+ flippedDir = "right-to-left"
+ }
+ const attributes = {"direction": flippedDir};
+ groups.push({id, attributes});
+ }
+ this.props.action.updateGroups(groups);
+ }
+
+ batchSubmit() {
+ let attributes = {};
+ for (var i in this.props.defaultAttributes) {
+ let attrName = this.props.defaultAttributes[i]["name"];
+ let attrValue = this.state[this.props.defaultAttributes[i]["name"]];
+ if (attrValue !== "" && attrValue !== "keep" && attrValue !== "Keep same") {
+ attributes[attrName] = attrValue;
+ }
+ }
+ let groups = [];
+ for (let id of this.props.selectedGroups) {
+ groups.push({id, attributes});
+ }
+ this.props.action.updateGroups(groups);
+ // Reset states
+ this.setState({...this.otherAttributeStates()});
+ }
+
+ getAttributeValues(selectedGroups=this.props.selectedGroups) {
+ let groupAttributes = {};
+ for (var i in this.props.defaultAttributes) {
+ let attributeName = this.props.defaultAttributes[i]['name'];
+ for (let id of selectedGroups) {
+ let group = this.props.Groups[id];
+ if (groupAttributes[attributeName]===undefined) {
+ groupAttributes[attributeName] = group[attributeName];
+ } else if (groupAttributes[attributeName]!==group[attributeName]) {
+ groupAttributes[attributeName] = null;
+ break;
+ }
+ }
+ }
+ return groupAttributes;
+ }
+
+ renderNotes = () => {
+ let chips = [];
+ for (let noteID of this.props.commonNotes) {
+ const note = this.props.Notes[noteID];
+ chips.push(renderNoteChip(this.props, note));
+ }
+ return chips;
+ }
+
+ closeNoteDialog = () => {
+ this.setState({activeNote: null});
+ }
+
+ toggleTacketDrawing = (e) => {
+ e.stopPropagation();
+ this.props.action.toggleVisualizationDrawing({type:"tacketed", value: this.props.selectedGroups[0]});
+ this.handleAddButtonRequestClose();
+ }
+
+ toggleSewingDrawing = (e) => {
+ e.stopPropagation();
+ this.props.action.toggleVisualizationDrawing({type:"sewing", value: this.props.selectedGroups[0]});
+ this.handleAddButtonRequestClose();
+ }
+
+ handleTacketSewingChange = (type, leafID, index) => {
+ const targetGroup = this.props.Groups[this.props.selectedGroups[0]];
+ const value = leafID==="spine"? null : leafID;
+ let groupPayload = {};
+ groupPayload[type] = targetGroup[type];
+ if (groupPayload[type].length===2) {
+ groupPayload[type][index] = value;
+ } else if (groupPayload[type].length===1 && index===0) {
+ // Array has one item, which is the endleaf. Insert startleaf ID
+ groupPayload[type].splice(index, 0, value);
+ } else if (groupPayload[type].length===1 && index===1) {
+ // Array has one item, which is the endleaf. Replace endleaf ID
+ groupPayload[type] = [value];
+ }
+ if (!groupPayload[type][0])
+ groupPayload[type].splice(0, 1)
+ this.props.action.updateGroup(targetGroup.id, groupPayload);
+ }
+
+ deleteTacket = () => {
+ this.singleSubmit("tacketed", []);
+ }
+
+ deleteSewing = () => {
+ this.singleSubmit("sewing", []);
+ }
+
+ toggleVisualizationDialog = (value) => {
+ this.setState({visualizationDialogActive:value});
+ if (value==="") {
+ this.props.togglePopUp(false);
+ } else {
+ this.props.togglePopUp(true);
+ }
+ }
+
+ toggleGroupDirection = () => {
+ if(this.props.Groups[this.props.selectedGroups[0]].direction === "right-to-left"){
+ this.singleSubmit("direction", "left-to-right")
+ } else {
+ this.singleSubmit("direction", "right-to-left")
+ }
+ }
+
+ clickVisibility = (attributeName, value) => {
+ if (attributeName!=="type"||this.props.viewMode==="TABULAR") {
+ this.props.action.updatePreferences({group:{...this.props.preferences.group, [attributeName]:value}});
+ }
+ }
+
+ renderTooltip = (eyeIsChecked, eyeStyle, attributeDict) => {
+ return (
+ 0?{display:"none"}:{}}>
+ {eyeIsChecked?
+ "Hide attribute in the collation"
+ : "Show attribute in the collation"
+ }
+
+ )
+ }
+
+ render() {
+ const isBatch = this.props.selectedGroups.length > 1;
+ let attributeDivs = [];
+ let groupAttributes = this.getAttributeValues();
+ this.props.defaultAttributes.forEach((attributeDict)=> {
+ let label = attributeDict.displayName;
+ let eyeCheckbox = "";
+ let eyeStyle = {};
+ let eyeIsChecked = this.props.preferences.group && this.props.preferences.group[attributeDict.name]?this.props.preferences.group[attributeDict.name] : false;
+ if (this.props.viewMode!=="TABULAR") {
+ if (attributeDict.name==="type") {
+ eyeStyle = {fill: "#C2C2C2", cursor:"not-allowed"};
+ eyeIsChecked = false;
+ }
+ }
+ if (isBatch && !this.props.isReadOnly) {
+ eyeCheckbox =
+
+ }
+ uncheckedIcon={ }
+ onClick={()=>this.clickVisibility(attributeDict.name, !eyeIsChecked)}
+ style={{display:this.props.windowWidth<=1024?"none":"inline-block",width:"25px",...eyeStyle}}
+ iconStyle={{...checkboxStyle().iconStyle,...eyeStyle}}
+ checked={eyeIsChecked}
+ onMouseEnter={()=>{this.setState({["visibility_hover_"+attributeDict.name]:true})}}
+ onMouseOut={()=>{this.setState({["visibility_hover_"+attributeDict.name]:false})}}
+ tabIndex={this.props.tabIndex}
+ />
+ {this.renderTooltip(eyeIsChecked, eyeStyle, attributeDict)}
+
+ label = this.toggleCheckbox(attributeDict.name)}
+ labelStyle={!this.state["batch_"+attributeDict.name]?{color:"gray",fontSize:this.props.windowWidth<=768?"12px":null}:{fontSize:this.props.windowWidth<=768?"12px":null}}
+ checked={this.state["batch_"+attributeDict.name]}
+ style={{display:"inline-block",width:"25px"}}
+ iconStyle={{...checkboxStyle().iconStyle}}
+ tabIndex={this.props.tabIndex}
+ />;
+ } else {
+ // In single edit - display eye icon with label
+ label =
+
+ }
+ uncheckedIcon={ }
+ onClick={()=>this.clickVisibility(attributeDict.name, !eyeIsChecked)}
+ style={{display:"inline-block",width:"25px",...eyeStyle}}
+ {...checkboxStyle()}
+ iconStyle={{...checkboxStyle().iconStyle, color:"gray", ...eyeStyle}}
+ checked={eyeIsChecked}
+ onMouseEnter={()=>{this.setState({["visibility_hover_"+attributeDict.name]:true})}}
+ onMouseOut={()=>{this.setState({["visibility_hover_"+attributeDict.name]:false})}}
+ tabIndex={this.props.tabIndex}
+ />
+ {this.renderTooltip(eyeIsChecked, eyeStyle, attributeDict)}
+
+ }
+ // Generate dropdown or text box depending on the current attribute
+ let input = groupAttributes[attributeDict.name];
+ if (!this.props.isReadOnly) {
+ if (attributeDict.options!==undefined) {
+ // Drop down menu
+ let menuItems = [];
+ let value = "keep";
+ attributeDict.options.forEach((option, index)=> {
+ menuItems.push({value:option, text: option});
+ });
+ if (groupAttributes[attributeDict.name]===null) {
+ menuItems.push({value:"keep", text:"Keep same"});
+ }
+ if (groupAttributes[attributeDict.name]!==null) {
+ value = groupAttributes[attributeDict.name];
+ }
+ input = (this.dropDownChange(v,attributeDict.name)}
+ disabled={isBatch && !this.state["batch_"+attributeDict.name]}
+ tabIndex={this.props.tabIndex}
+ data={menuItems}
+ value={value}
+ >
+
+ );
+ } else {
+ // Text box
+ let textboxButtons = "";
+ if (!isBatch && this.state["editing_"+attributeDict.name]) {
+ textboxButtons = (
+
+ }
+ style={{minWidth:this.props.windowWidth<=768?"35px":"60px",marginLeft:"5px"}}
+ onClick={(e)=>this.textSubmit(e,attributeDict.name)}
+ tabIndex={this.props.tabIndex}
+ />
+ }
+ style={{minWidth:this.props.windowWidth<=768?"35px":"60px",marginLeft:"5px"}}
+ onClick={(e)=>this.textCancel(e,attributeDict.name)}
+ tabIndex={this.props.tabIndex}
+ />
+
+ );
+ }
+ let value = "Keep same";
+ if (this.state["editing_"+attributeDict.name]) {
+ value = this.state[attributeDict.name];
+ } else if (groupAttributes[attributeDict.name]) {
+ value = groupAttributes[attributeDict.name];
+ }
+ input = (
+
this.textSubmit(e,attributeDict.name)}>
+ this.onTextboxChange(v,attributeDict.name)}
+ disabled={isBatch && !this.state["batch_"+attributeDict.name]}
+ tabIndex={this.props.tabIndex}
+ inputStyle={{fontSize:this.props.windowWidth<=1024?"12px":"16px"}}
+ />
+ {textboxButtons}
+
+ )
+ }
+ } else {
+ if (!input && this.props.selectedGroups.length>1) {
+ input = Different values
;
+ } else {
+ input = {input}
;
+ }
+ }
+ attributeDivs.push(
+
+
+ {eyeCheckbox}
+ {label}
+
+
+ {input}
+
+
+ );
+ });
+ let submitBtn = "";
+ if (isBatch && this.hasActiveAttributes()) {
+ submitBtn =
+ }
+ let addBtn = "";
+ let addButtonPopover = "";
+ if (!isBatch) {
+ addButtonPopover =
+
+ {this.props.togglePopUp(true);this.toggleAddGroupDialog(true)}} />
+ {this.props.togglePopUp(true);this.toggleAddLeafDialog(true)}}/>
+
+
+
+ addBtn =
+ }
+
+ let flipBtn = {if(this.props.selectedGroups.length > 1){this.batchFlip()}else{this.toggleGroupDirection()}}}
+ tabIndex={this.props.tabIndex}
+ {...btnBase()}
+ style={(this.props.selectedGroups && this.props.selectedGroups.length === 1) ? {...btnBase().style, width: "48%", float:"left", marginRight:"2%", marginTop:"2%", marginBottom: "2%"} : {width:"100%", float:"left", marginRight:"2%", marginTop:"2%", marginBottom:"2%"}}
+ />
+ let deleteBtn =
+
+ let attributeSewing = "";
+ const sewing = this.props.Groups[this.props.selectedGroups[0]].sewing;
+ if (this.props.selectedGroups.length===1 && sewing.length===0 && this.props.viewMode!=="VIEWING") {
+ attributeSewing =
+
+ {if(this.props.viewMode==="TABULAR"){this.toggleVisualizationDialog("sewing")}else{this.toggleSewingDrawing(e)}}}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+
+
Sewing
+
+ } else if (this.props.selectedGroups.length===1 && sewing.length>0) {
+ attributeSewing =
+
+
+
Sewing
+
{this.deleteSewing()}:null}
+ onClick={()=>{if(this.props.viewMode!=="VIEWING")this.toggleVisualizationDialog("sewing")}}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+ {sewing.length===1?
+ "Spine to Leaf " + (this.props.leafIDs.indexOf(sewing[0])+1) :
+ "Leaf " + (this.props.leafIDs.indexOf(sewing[0]) + 1) + " to Leaf " + (this.props.leafIDs.indexOf(sewing[1])+1)
+ }
+
+
+
+
+
+
+
+ }
+ const tacketed = this.props.Groups[this.props.selectedGroups[0]].tacketed;
+ let attributeTacket = "";
+ if (this.props.selectedGroups.length===1 && tacketed.length===0 && this.props.viewMode!=="VIEWING") {
+ attributeTacket =
+
+ {if(this.props.viewMode==="VISUAL"){this.toggleTacketDrawing(e)}else{this.toggleVisualizationDialog("tacketed")}}}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+
+
Tacket
+
+ } else if (this.props.selectedGroups.length===1 && tacketed.length>0) {
+ attributeTacket =
+
+
+
Tacket
+
{this.deleteTacket()}:null}
+ onClick={()=>{if(this.props.viewMode!=="VIEWING")this.toggleVisualizationDialog("tacketed")}}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+ {tacketed.length===1?
+ "Spine to Leaf " + (this.props.leafIDs.indexOf(tacketed[0])+1) :
+ "Leaf " + (this.props.leafIDs.indexOf(tacketed[0])+1) + " to Leaf " + (this.props.leafIDs.indexOf(tacketed[1])+1)
+ }
+
+
+
+
+
+
+
+ }
+ const notes = this.renderNotes();
+ return (
+
+
+ {attributeDivs}
+ {!this.props.isReadOnly?
+
: ""}
+
{this.props.selectedGroups.length>1?"Notes in common" : "Notes"}
+
+ {notes}
+
+
+ {attributeSewing}
+ {attributeTacket}
+ {submitBtn}
+ {addButtonPopover}
+
+
+
Actions
+
+ {addBtn}
+ {deleteBtn}
+ {flipBtn}
+
+
+
+
+
this.toggleVisualizationDialog("")}
+ group={this.props.selectedGroups.length>0? this.props.Groups[this.props.selectedGroups[0]] : null}
+ groupIDs={this.props.groupIDs}
+ leafIDs={this.props.leafIDs}
+ tacketed={this.props.Groups[this.props.selectedGroups[0]].tacketed}
+ sewing={this.props.Groups[this.props.selectedGroups[0]].sewing}
+ Leafs={this.props.Leafs}
+ activeGroup={this.props.Groups[this.props.selectedGroups[0]]}
+ handleTacketSewingChange={this.handleTacketSewingChange}
+ delete={()=>this.singleSubmit(this.state.visualizationDialogActive, [])}
+ updateGroup={this.singleSubmit}
+ popUpActive={this.props.popUpActive}
+ />
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/infoBox/LeafInfoBox.js b/viscoll-app/src/components/infoBox/LeafInfoBox.js
new file mode 100644
index 00000000..b6f63027
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/LeafInfoBox.js
@@ -0,0 +1,568 @@
+import React from 'react';
+import AddLeafDialog from '../infoBox/dialog/AddLeafDialog';
+import DeleteConfirmationDialog from '../infoBox/dialog/DeleteConfirmationDialog';
+import RaisedButton from 'material-ui/RaisedButton';
+import Checkbox from 'material-ui/Checkbox';
+import Visibility from 'material-ui/svg-icons/action/visibility';
+import VisibilityOff from 'material-ui/svg-icons/action/visibility-off';
+import {getLeafsOfGroup} from '../../helpers/getLeafsOfGroup';
+import Dialog from 'material-ui/Dialog';
+import AddNote from './dialog/AddNote';
+import ImageViewer from "../global/ImageViewer";
+import SelectField from '../global/SelectField';
+import { getMemberOrder } from '../../helpers/getMemberOrder';
+import { checkboxStyle } from '../../styles/checkbox';
+import { btnBase } from '../../styles/button';
+import FolioNumberDialog from '../infoBox/dialog/FolioNumberDialog';
+import { renderNoteChip } from '../../helpers/renderHelper';
+
+/** Leaf infobox */
+export default class LeafInfoBox extends React.Component {
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ imageModalOpen: false,
+ folioModalOpen: false,
+ isBatch: this.props.selectedLeaves.length>1,
+ ...this.emptyAttributeState(),
+ ...this.batchAttributeToggleState(),
+ ...this.visibilityHoverState(),
+ }
+ }
+
+ visibilityHoverState() {
+ let state = {};
+ for (var i in this.props.defaultAttributes) {
+ state["visibility_hover_" + this.props.defaultAttributes[i]["name"]]=false;
+ }
+ return state;
+ }
+
+ /**
+ * Creates a dictionary of attributes and if its toggled on or off during batch edit
+ * This is used for the checkbox states
+ */
+ batchAttributeToggleState = () => {
+ let state = {};
+ for (var i in this.props.defaultAttributes) {
+ state["batch_" + this.props.defaultAttributes[i]["name"]]=false;
+ }
+ return state;
+ }
+
+ /**
+ * Creates a dictionary of attributes with no values
+ */
+ emptyAttributeState = () => {
+ let state = {};
+ for (var i in this.props.defaultAttributes) {
+ state[this.props.defaultAttributes[i]["name"]]="";
+ }
+ return state;
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.setState({
+ isBatch: nextProps.selectedLeaves.length>1,
+ });
+ if (!this.state.isBatch) {
+ this.setState({...this.emptyAttributeState()});
+ }
+ }
+
+ hasActiveAttributes = () => {
+ for (var i in this.props.defaultAttributes) {
+ if (this.state["batch_" + this.props.defaultAttributes[i]["name"]] &&
+ this.state[this.props.defaultAttributes[i]["name"]]!=="keep" &&
+ this.state[this.props.defaultAttributes[i]["name"]]!=="") {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ dropDownChange = (value, attributeName) => {
+ if (this.props.selectedLeaves.length===1) {
+ // In single edit - we submit change immediately
+ let attributes = {};
+ attributes[attributeName] = value;
+ let leaf = {
+ ...attributes,
+ };
+ this.props.action.updateLeaf(this.props.selectedLeaves[0], leaf);
+ } else {
+ // In batch edit - save change of attribute to the state
+ let updatedAttribute = {};
+ updatedAttribute[attributeName] = value;
+ this.setState(updatedAttribute);
+ }
+ }
+
+ onConjoinChange = (leaf, newID) => {
+ if (newID==="None") newID = null;
+ let request = {conjoined_to: newID };
+ this.props.action.updateLeaf(leaf.id, request);
+ }
+
+ onAttachedToChange = (event, activeLeaf, location, id, method) => {
+ let request = {attached_to: activeLeaf.attached_to};
+ if (method==="None") {
+ request.attached_to[location+"ID"]="";
+ request.attached_to[location+"Method"]="";
+ } else {
+ request.attached_to[location+"ID"]=id;
+ request.attached_to[location+"Method"]=method;
+ }
+ this.props.action.updateLeaf(activeLeaf.id, request);
+ }
+
+ batchSubmit = () => {
+ let attributes = {};
+ for (var i in this.props.defaultAttributes) {
+ let attrName = this.props.defaultAttributes[i]["name"];
+ let attrValue = this.state[this.props.defaultAttributes[i]["name"]];
+ if (attrValue !== "" && attrValue !== "keep") {
+ attributes[attrName] = attrValue;
+ }
+ }
+ let leafs = [];
+ for (var key of this.props.selectedLeaves) {
+ const leaf = this.props.Leafs[key];
+ leafs.push({id: leaf.id, attributes});
+ }
+ this.props.action.updateLeafs(leafs);
+ // Reset states
+ this.setState({...this.batchAttributeToggleState()});
+ }
+
+ /**
+ * Returns dictionary of attribute names and values
+ * If multiple selected leaves have conflicting values,
+ * the value of that attribute will be set to null
+ */
+ getAttributeValues() {
+ let leafAttributes = {};
+ for (var i in this.props.defaultAttributes) {
+ let attributeName = this.props.defaultAttributes[i]['name'];
+ for (var key of this.props.selectedLeaves) {
+ const leaf = this.props.Leafs[key];
+ if (leafAttributes[attributeName]===undefined) {
+ leafAttributes[attributeName] = leaf[attributeName];
+ } else if (leafAttributes[attributeName]!==leaf[attributeName]) {
+ leafAttributes[attributeName] = null;
+ break;
+ }
+ }
+ }
+ return leafAttributes;
+ }
+
+ /**
+ * Handle checkbox toggling by updating relevant attribute state
+ */
+ toggleCheckbox = (target) => {
+ let newToggleState = {};
+ newToggleState["batch_"+target]= !this.state["batch_"+target];
+ this.setState(newToggleState);
+ }
+
+ renderNotes = () => {
+ let chips = [];
+ for (let noteID of this.props.commonNotes) {
+ const note = this.props.Notes[noteID];
+ chips.push(renderNoteChip(this.props, note));
+ }
+ return chips;
+ }
+
+ closeNoteDialog = () => {
+ this.setState({activeNote:null});
+ this.props.togglePopUp(false);
+ }
+
+ toggleImageModal = (imageModalOpen) => {
+ this.setState({imageModalOpen})
+ this.props.togglePopUp(imageModalOpen);
+ }
+
+ toggleFolioModal = (folioModalOpen) => {
+ this.setState({folioModalOpen})
+ this.props.togglePopUp(folioModalOpen);
+ }
+
+ clickVisibility = (attributeName, value) => {
+ this.props.action.updatePreferences({leaf:{...this.props.preferences.leaf, [attributeName]:value}});
+ }
+
+ render() {
+ let leafAttributes = this.getAttributeValues();
+ let attributeDivs = [];
+ const activeLeaf = this.props.Leafs[this.props.selectedLeaves[0]];
+ const activeLeafOrder = this.props.leafIDs.indexOf(activeLeaf.id)+1;
+ const parentGroup = this.props.Groups[activeLeaf.parentID];
+ const leafMembersOfCurrentGroup = getLeafsOfGroup(parentGroup, this.props.Leafs);
+ const isFirstLeaf = getMemberOrder(activeLeaf, this.props.Groups, this.props.groupIDs)===1;
+ const isLastLeaf = activeLeaf.id===leafMembersOfCurrentGroup[leafMembersOfCurrentGroup.length-1].id;
+ const hasOnlyActiveLeaf = leafMembersOfCurrentGroup.length===2; // 2 because there's none leaf
+ // Generate drop down for each leaf attribute
+ this.props.defaultAttributes.forEach((attributeDict)=> {
+ if (attributeDict.name.includes("attached")) {
+ if (hasOnlyActiveLeaf || (isFirstLeaf && attributeDict.name.includes("above")) || (isLastLeaf && attributeDict.name.includes("below"))) {
+ return;
+ }
+ }
+ let fontSize = null;
+ if (this.props.windowWidth<=768) {
+ fontSize = "12px";
+ } else if (this.props.windowWidth<=1024 && this.state.isBatch) {
+ fontSize = "13px";
+ } else if (this.props.windowWidth<=1024) {
+ fontSize = "14px";
+ }
+ let label = {attributeDict.displayName}
;
+ // Generate eye toggle checkbox
+ let eyeCheckbox = "";
+ let eyeIsChecked = this.props.preferences.leaf && this.props.preferences.leaf[attributeDict.name]?this.props.preferences.leaf[attributeDict.name]:false;
+
+ if (this.props.viewMode==="TABULAR" && this.state.isBatch) {
+
+ eyeCheckbox =
+
+
}
+ uncheckedIcon={
}
+ onClick={()=>this.clickVisibility(attributeDict.name, !eyeIsChecked)}
+ style={this.props.windowWidth<=1024?{display:"none"}:{display:"inline-block",width:"25px"}}
+ iconStyle={{...checkboxStyle().iconStyle}}
+ checked={eyeIsChecked}
+ onMouseEnter={()=>{this.setState({["visibility_hover_"+attributeDict.name]:true})}}
+ onMouseOut={()=>{this.setState({["visibility_hover_"+attributeDict.name]:false})}}
+ tabIndex={this.props.tabIndex}
+ />
+
+ {this.props.preferences[attributeDict.name]?
+ "Hide attribute in the collation"
+ : "Show attribute in the collation"
+ }
+
+
+ } else if (this.props.viewMode==="TABULAR") {
+ // In single edit tabular mode - display eye icon with label
+ label =
+
+
}
+ uncheckedIcon={
}
+ onClick={()=>this.clickVisibility(attributeDict.name, !eyeIsChecked)}
+ style={{display:"inline-block",width:"25px"}}
+ checked={eyeIsChecked}
+ iconStyle={{...checkboxStyle().iconStyle,color:"gray"}}
+ labelStyle={{...checkboxStyle().labelStyle}}
+ onMouseEnter={()=>{this.setState({["visibility_hover_"+attributeDict.name]:true})}}
+ onMouseOut={()=>{this.setState({["visibility_hover_"+attributeDict.name]:false})}}
+ tabIndex={this.props.tabIndex}
+ />
+
+ {eyeIsChecked?
+ "Hide attribute in the collation"
+ : "Show attribute in the collation"
+ }
+
+
+ }
+ if (this.state.isBatch && !this.props.isReadOnly) {
+ // In batch edit for either edit modes
+ label = this.toggleCheckbox(attributeDict.name)}
+ labelStyle={!this.state["batch_"+attributeDict.name]?{color:"gray"}:{}}
+ checked={this.state["batch_"+attributeDict.name]}
+ style={{display:"inline-block",width:"25px"}}
+ disabled={(attributeDict.name==="conjoined_to"||attributeDict.name.includes("attached"))}
+ tabIndex={this.props.tabIndex}
+ {...checkboxStyle()}
+ />;
+ }
+ let input = leafAttributes[attributeDict.name];
+ if (!this.props.isReadOnly) {
+ if (attributeDict.name ==="conjoined_to") {
+ let menuItems = [];
+ let value=this.state.isBatch?"":"None";
+ leafMembersOfCurrentGroup.forEach((member)=> {
+ if (activeLeafOrder!==this.props.leafIDs.indexOf(member.id)+1) {
+ menuItems.push({
+ value: member.id,
+ text: this.props.leafIDs.indexOf(member.id) > -1 ? (this.props.leafIDs.indexOf(member.id)+1).toString() : "None",
+ });
+ }
+ if (member.id === leafAttributes["conjoined_to"]) {
+ value = member.id;
+ }
+ });
+ input =
+ this.onConjoinChange(activeLeaf,v)}
+ disabled={this.state.isBatch}
+ tabIndex={this.props.tabIndex}
+ data={menuItems}
+ value={value}
+ >
+
+ } else {
+ // Populate drop down items
+ let menuItems = [];
+ attributeDict.options.forEach((option, index)=> {
+ menuItems.push({value:option, text:option});
+ });
+ if (leafAttributes[attributeDict.name]===null) {
+ menuItems.push({value:"keep", text:"Keep same"});
+ }
+ let value = "keep";
+ if (this.state[attributeDict.name]!=="" && this.state.isBatch) {
+ value = this.state[attributeDict.name];
+ } else if (leafAttributes[attributeDict.name]!==null) {
+ value = leafAttributes[attributeDict.name];
+ }
+ input =
+ this.dropDownChange(v,attributeDict.name)}
+ disabled={this.state.isBatch && !this.state["batch_"+attributeDict.name]}
+ tabIndex={this.props.tabIndex}
+ data={menuItems}
+ >
+
+ }
+ } else if (!input && this.props.selectedLeaves.length>1) {
+ // We're in readOnly mode with no common attribute value
+ input = Different values
;
+ } else if (attributeDict.name==="conjoined_to") {
+ if (leafAttributes[attributeDict.name]) {
+ input = this.props.leafIDs.indexOf(this.props.Leafs[leafAttributes[attributeDict.name]].id) + 1;
+ } else {
+ input = "None";
+ }
+ }
+ attributeDivs.push(
+
+
+ {eyeCheckbox}
+ {label}
+
+
+ {input}
+
+
+ );
+ });
+ let submitBtn = "";
+ if (this.state.isBatch && this.hasActiveAttributes()) {
+ submitBtn =
+ }
+ let addBtn = "";
+ if (!this.state.isBatch) {
+ addBtn =
+ }
+ let deleteBtn = (
+
+ );
+
+ let conjoinButton = (
+
+ );
+ let generateFolioButton = this.state.isBatch? (
+ this.toggleFolioModal(true)}
+ label="Generate folio/page numbers"
+ {...btnBase()}
+ style={{...btnBase().style,marginBottom:10,width:"100%"}}
+ tabIndex={this.props.tabIndex}
+ />) : "";
+
+ if (this.props.selectedLeaves.length<2){
+ conjoinButton = "";
+ } else {
+ let parentIDs = this.props.selectedLeaves.map((leafID)=>{return this.props.Leafs[leafID].parentID})
+ let parentIDsSet = new Set(parentIDs);
+ if (parentIDsSet.size!==1)
+ conjoinButton = "";
+ }
+
+ let imageModalContent;
+ let imageThumbnails = [];
+ if (this.props.viewMode!=="VIEWING") {
+ // Show the side image if available
+ if (this.props.selectedLeaves.length===1) {
+ const leaf = this.props.Leafs[this.props.selectedLeaves[0]]
+ const recto = this.props.Rectos[leaf.rectoID];
+ const verso = this.props.Versos[leaf.versoID];
+ // replace imageModalContent view OSD component
+ const rectoURL = recto.image ? recto.image.url : null;
+ const versoURL = verso.image ? verso.image.url : null;
+ const isRectoDIY = recto.image.manifestID? recto.image.manifestID.includes("DIY") : false;
+ const isVersoDIY = verso.image.manifestID? verso.image.manifestID.includes("DIY") : false;
+ imageModalContent = ( );
+ if (rectoURL) {
+ imageThumbnails.push(
+ this.toggleImageModal(true)}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+ {recto.folio_number}
+
+ )
+ }
+ if (versoURL) {
+ imageThumbnails.push(
+ this.toggleImageModal(true)}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+ {verso.folio_number}
+
+ )
+ }
+ }
+ }
+
+ const notes = this.renderNotes();
+ return (
+
+ {attributeDivs}
+
+ {imageThumbnails}
+
+ {this.props.isReadOnly&¬es.length===0?"":
+
+ {this.props.isReadOnly?"":
+
}
+
+
+ {Object.keys(this.props.selectedLeaves).length>1?"Notes in common" : "Notes"}
+
+
+ {notes}
+
+
+
+ }
+ {submitBtn}
+
+ {this.props.isReadOnly?"":
+
+
Actions
+ {conjoinButton}
+ {generateFolioButton}
+ {addBtn}
+ {deleteBtn}
+
+ }
+
+
this.toggleImageModal(false)}
+ contentStyle={{background: "none", boxShadow: "inherit"}}
+ bodyStyle={{padding:0}}
+ >
+ {imageModalContent}
+
+
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/infoBox/SideInfoBox.js b/viscoll-app/src/components/infoBox/SideInfoBox.js
new file mode 100644
index 00000000..1d26a99b
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/SideInfoBox.js
@@ -0,0 +1,479 @@
+import React from 'react';
+import RaisedButton from 'material-ui/RaisedButton';
+import Checkbox from 'material-ui/Checkbox';
+import TextField from 'material-ui/TextField';
+import IconSubmit from 'material-ui/svg-icons/action/done';
+import IconClear from 'material-ui/svg-icons/content/clear';
+import Visibility from 'material-ui/svg-icons/action/visibility';
+import VisibilityOff from 'material-ui/svg-icons/action/visibility-off';
+import AddNote from './dialog/AddNote';
+import Dialog from 'material-ui/Dialog';
+import ImageViewer from "../global/ImageViewer";
+import SelectField from '../global/SelectField';
+import { checkboxStyle } from '../../styles/checkbox';
+import { renderNoteChip } from '../../helpers/renderHelper';
+
+/** Side infobox */
+export default class SideInfoBox extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ imageModalOpen: false,
+ isBatch: this.props.selectedSides.length>1,
+ ...this.emptyAttributeState(),
+ ...this.otherAttributeStates(),
+ ...this.visibilityHoverState(),
+ }
+ }
+
+ visibilityHoverState = () => {
+ let state = {};
+ for (var i in this.props.defaultAttributes) {
+ state["visibility_hover_" + this.props.defaultAttributes[i]["name"]]=false;
+ }
+ return state;
+ }
+
+ /**
+ * Creates a dictionary of attributes and if its toggled on or off during batch edit
+ * This is used for the checkbox states
+ */
+ otherAttributeStates = () => {
+ let state = {};
+ for (var i in this.props.defaultAttributes) {
+ state["batch_" + this.props.defaultAttributes[i]["name"]]=false;
+ state["editing_" + this.props.defaultAttributes[i]["name"]]=false;
+ }
+ return state;
+ }
+
+ /**
+ * Creates a dictionary of attributes with no values
+ */
+ emptyAttributeState = () => {
+ let state = {};
+ for (var i in this.props.defaultAttributes) {
+ state[this.props.defaultAttributes[i]["name"]]=null;
+ }
+ return state;
+ }
+
+ hasActiveAttributes = () => {
+ for (var i in this.props.defaultAttributes) {
+ if ((this.props.defaultAttributes[i]["name"]==="folio_number"||this.props.defaultAttributes[i]["name"]==="page_number") &&
+ this.state["batch_" + this.props.defaultAttributes[i]["name"]] &&
+ this.state[this.props.defaultAttributes[i]["name"]]!=="Keep same") {
+ return true;
+ }
+ else if (this.state["batch_" + this.props.defaultAttributes[i]["name"]] &&
+ this.state[this.props.defaultAttributes[i]["name"]]!=="keep" &&
+ this.state[this.props.defaultAttributes[i]["name"]]!=="") {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.setState({
+ isBatch: nextProps.selectedSides.length>1,
+ });
+ if (!this.state.isBatch) {
+ this.setState({...this.emptyAttributeState()});
+ }
+ }
+
+ dropDownChange = (value, attributeName) => {
+ if (!this.state.isBatch) {
+ // In single edit - we submit change immediately
+ this.singleSubmit(attributeName, value);
+ } else {
+ // In batch edit - save change of attribute to the state
+ let updatedAttribute = {};
+ updatedAttribute[attributeName] = value;
+ this.setState(updatedAttribute);
+ }
+ }
+
+ onTextboxChange = (value, attributeName) => {
+ let newAttributeState = {};
+ newAttributeState[attributeName] = value;
+ let newEditingState = {};
+ newEditingState["editing_"+attributeName] = true;
+ this.setState({...newAttributeState,...newEditingState});
+ };
+
+ textSubmit = (e, attributeName) => {
+ e.preventDefault();
+ let newEditingState = {};
+ newEditingState["editing_"+attributeName] = false;
+ this.setState({...newEditingState});
+ if (!this.state.isBatch) {
+ this.singleSubmit(attributeName, this.state[attributeName]);
+ }
+ }
+
+ singleSubmit = (attributeName, value) => {
+ let attributes = {};
+ attributes[attributeName] = value;
+ let sideID = this.props.selectedSides[0];
+ let side =
+ {
+ ...attributes
+ }
+ ;
+ this.props.action.updateSide(sideID, side);
+ }
+
+ textCancel = (e, attributeName) => {
+ let newAttributeState = {};
+ newAttributeState[attributeName] =
+ this.props.Sides[this.props.selectedSides[0]][attributeName];
+ let newEditingState = {};
+ newEditingState["editing_"+attributeName] = false;
+ this.setState({...newAttributeState,...newEditingState});
+ }
+
+ /**
+ * Handle checkbox toggling by updating relevant attribute state
+ */
+ toggleCheckbox = (target) => {
+ let newToggleState = {};
+ newToggleState["batch_"+target]=!this.state["batch_"+target];
+ this.setState(newToggleState);
+ }
+
+ batchSubmit = () => {
+ let attributes = {};
+ let sides = [];
+ for (var i in this.props.defaultAttributes) {
+ // Go through each default attributes
+ let attrName = this.props.defaultAttributes[i]["name"];
+ if (this.state["batch_"+attrName]) {
+ // This side attribute was selected for batch edit
+ // Get its new value
+ let attrValue = this.state[this.props.defaultAttributes[i]["name"]];
+ if (attrValue !== null && attrValue !== "keep" && attrValue !== "Keep same") {
+ attributes[attrName] = attrValue;
+ }
+ }
+ }
+ if (Object.keys(attributes).length>0) {
+ for (let id of this.props.selectedSides){
+ if (Object.keys(attributes).length>0) {
+ sides.push({id, attributes});
+ }
+ }
+ this.props.action.updateSides(sides);
+ }
+ this.setState({...this.otherAttributeStates()})
+
+ }
+
+ getAttributeValues = (selectedSides=this.props.selectedSides) => {
+ let sideAttributes = {};
+ for (var i in this.props.defaultAttributes) {
+ let attributeName = this.props.defaultAttributes[i]['name'];
+ for (let sideID of selectedSides) {
+ const side = this.props.Sides[sideID];
+ if (sideAttributes[attributeName]===undefined) {
+ sideAttributes[attributeName] = side[attributeName];
+ } else if (sideAttributes[attributeName]!==side[attributeName]) {
+ sideAttributes[attributeName] = null;
+ break;
+ }
+ }
+ }
+ return sideAttributes;
+ }
+
+ renderNotes = () => {
+ let chips = [];
+ for (let noteID of this.props.commonNotes) {
+ const note = this.props.Notes[noteID];
+ chips.push(renderNoteChip(this.props, note));
+ }
+ return chips;
+ }
+
+ closeNoteDialog = () => {
+ this.setState({activeNote:null});
+ }
+
+ toggleImageModal = (imageModalOpen) => {
+ this.setState({imageModalOpen})
+ }
+
+ clickVisibility = (attributeName, value) => {
+ if (attributeName!=="script_direction") {
+ this.props.action.updatePreferences({side:{...this.props.preferences.side, [attributeName]:value}});
+ }
+ }
+
+ renderTooltip = (eyeIsChecked, eyeStyle, attributeDict) => {
+ return (
+ 0?{display:"none"}:{}}>
+ {eyeIsChecked?
+ "Hide attribute in the collation"
+ : "Show attribute in the collation"
+ }
+
+ )
+ }
+ render() {
+ let attributeDivs = [];
+ let sideAttributes = this.getAttributeValues();
+ for (var i in this.props.defaultAttributes) {
+ let attributeDict = this.props.defaultAttributes[i];
+ if (attributeDict.name === "uri") continue;
+ // Generate checkbox if we're in batch edit mode
+ let label = attributeDict.displayName;
+ // Generate eye toggle checkbox
+ let eyeCheckbox = "";
+
+ let eyeStyle = {};
+ let eyeIsChecked = this.props.preferences.side && this.props.preferences.side[attributeDict.name]?this.props.preferences.side[attributeDict.name]:false;
+ if (this.props.viewMode!=="TABULAR") {
+ if (attributeDict.name==="script_direction") {
+ eyeStyle = {fill: "#C2C2C2", cursor:"not-allowed"};
+ eyeIsChecked = false;
+ }
+ }
+ if (this.state.isBatch && !this.props.isReadOnly) {
+ eyeCheckbox =
+
+ }
+ uncheckedIcon={ }
+ onClick={()=>this.clickVisibility(attributeDict.name, !eyeIsChecked)}
+ style={this.props.windowWidth<=1024?{display:"none"}:{display:"inline-block",width:"25px",...eyeStyle}}
+ iconStyle={{...checkboxStyle().iconStyle,...eyeStyle}}
+ checked={eyeIsChecked}
+ onMouseEnter={()=>{this.setState({["visibility_hover_"+attributeDict.name]:true})}}
+ onMouseOut={()=>{this.setState({["visibility_hover_"+attributeDict.name]:false})}}
+ tabIndex={this.props.tabIndex}
+ />
+ {this.renderTooltip(eyeIsChecked, eyeStyle, attributeDict)}
+
+ label = this.toggleCheckbox(attributeDict.name)}
+ labelStyle={!this.state["batch_"+attributeDict.name]?{color:"gray",...checkboxStyle().labelStyle}:{...checkboxStyle().labelStyle}}
+ iconStyle={{...checkboxStyle().iconStyle}}
+ checked={this.state["batch_"+attributeDict.name]}
+ style={{display:"inline-block",width:"25px"}}
+ disabled={this.state.isBatch && attributeDict.name==="uri"}
+ tabIndex={this.props.tabIndex}
+ />;
+ } else {
+ // In single edit, display eye icon with label (no checkbox)
+ label =
+
+ }
+ uncheckedIcon={ }
+ onClick={()=>this.clickVisibility(attributeDict.name, !eyeIsChecked)}
+ style={{display:"inline-block",width:"25px",...eyeStyle}}
+ {...checkboxStyle()}
+ iconStyle={{...checkboxStyle().iconStyle, color:"gray", ...eyeStyle}}
+ checked={eyeIsChecked}
+ onMouseEnter={()=>{this.setState({["visibility_hover_"+attributeDict.name]:true})}}
+ onMouseOut={()=>{this.setState({["visibility_hover_"+attributeDict.name]:false})}}
+ tabIndex={this.props.tabIndex}
+ />
+ {this.renderTooltip(eyeIsChecked, eyeStyle, attributeDict)}
+
+ }
+ // Generate dropdown or text box depending on the current attribute
+ let input = sideAttributes[attributeDict.name];
+ if (!this.props.isReadOnly) {
+ if (attributeDict.options!==undefined) {
+ // Drop down menu
+ let menuItems = [];
+ for (var j in attributeDict.options) {
+ let option = attributeDict.options[j];
+ menuItems.push({value:option, text:option});
+ }
+ if (sideAttributes[attributeDict.name]===null) {
+ menuItems.push({value:"keep", text:"Keep same"});
+ }
+ let value = this.state.isBatch?"keep":"";
+ if (this.state[attributeDict.name]!==null && this.state.isBatch) {
+ value = this.state[attributeDict.name];
+ } else if (sideAttributes[attributeDict.name]!==null) {
+ value = sideAttributes[attributeDict.name];
+ }
+ input = (this.dropDownChange(v,attributeDict.name)}
+ disabled={this.state.isBatch && !this.state["batch_"+attributeDict.name]}
+ tabIndex={this.props.tabIndex}
+ data={menuItems}
+ >
+
+ );
+ } else {
+ // Text box
+ let textboxButtons = "";
+ if (!this.state.isBatch && this.state["editing_"+attributeDict.name]) {
+ textboxButtons = (
+
+ }
+ style={{minWidth:this.props.windowWidth<=1024?"35px":"60px",marginLeft:"5px"}}
+ onClick={(e)=>this.textSubmit(e,attributeDict.name)}
+ tabIndex={this.props.tabIndex}
+ />
+ }
+ style={{minWidth:this.props.windowWidth<=1024?"35px":"60px",marginLeft:"5px"}}
+ onClick={(e)=>this.textCancel(e,attributeDict.name)}
+ tabIndex={this.props.tabIndex}
+ />
+
+ );
+ }
+ let value = this.state.isBatch? "Keep same" : "";
+ if (this.state["editing_"+attributeDict.name]) {
+ value = this.state[attributeDict.name];
+ } else if (sideAttributes[attributeDict.name]!==null) {
+ value = sideAttributes[attributeDict.name];
+ }
+ input = (
+
this.textSubmit(e,attributeDict.name)}>
+ this.onTextboxChange(v,attributeDict.name)}
+ disabled={this.state.isBatch && !this.state["batch_"+attributeDict.name]}
+ tabIndex={this.props.tabIndex}
+ inputStyle={{fontSize:this.props.windowWidth<=768?"12px":"16px"}}
+ />
+ {textboxButtons}
+
+ )
+ }
+ } else {
+ // We're in readOnly mode with no common attribute value
+ if (!input && this.props.selectedSides.length>1) {
+ input = Different values
;
+ } else {
+ input = {input}
+ }
+ }
+ attributeDivs.push(
+
+
+ {eyeCheckbox}
+ {label}
+
+
+ {input}
+
+
+ );
+ }
+ const notes = this.renderNotes();
+ let notesDiv = [];
+ if (!(this.props.isReadOnly && notes.length===0)) {
+ notesDiv.push(
+
+ {this.props.isReadOnly?"":
+
this.props.action.linkNote(noteID, this.props.sideIndex),
+ createAndAttachNote: this.props.action.createAndAttachNote
+ }}
+ noteTypes={this.props.noteTypes}
+ togglePopUp={this.props.togglePopUp}
+ tabIndex={this.props.tabIndex}
+ groupIDs={this.props.groupIDs}
+ leafIDs={this.props.leafIDs}
+ />}
+ {Object.keys(this.props.selectedSides).length>1?"Notes in common" : "Notes"}
+
+ {notes}
+
+
+ );
+ }
+
+ let submitBtn = "";
+ if (this.state.isBatch && this.hasActiveAttributes()) {
+ submitBtn =
+ }
+
+ let imageModalContent;
+ let imageThumbnail = [];
+ if (this.props.viewMode!=="VIEWING") {
+ // Show the side image if available
+ if (this.props.selectedSides.length===1){
+ const side = this.props.Sides[this.props.selectedSides[0]];
+ // replace imageModalContent view OSD component
+ const rectoURL = side.memberType==="Recto" ? side.image.url : null;
+ const versoURL = side.memberType==="Verso" ? side.image.url : null;
+ const isRectoDIY = side.image.manifestID && side.image.manifestID.includes("DIY");
+ const isVersoDIY = side.image.manifestID && side.image.manifestID.includes("DIY");
+ imageModalContent = ( );
+ if (side.image.url){
+ imageThumbnail.push(
+ this.toggleImageModal(true)}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+ )
+ }
+ }
+ }
+
+ return (
+
+ {attributeDivs}
+
+ {imageThumbnail}
+
+ {notesDiv}
+ {submitBtn}
+
this.toggleImageModal(false)}
+ contentStyle={{background: "none", boxShadow: "inherit"}}
+ bodyStyle={{padding:0}}
+ >
+ {imageModalContent}
+
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/infoBox/dialog/AddGroupDialog.js b/viscoll-app/src/components/infoBox/dialog/AddGroupDialog.js
new file mode 100644
index 00000000..c960d1b8
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/dialog/AddGroupDialog.js
@@ -0,0 +1,508 @@
+import React from 'react';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import RaisedButton from 'material-ui/RaisedButton';
+import Checkbox from 'material-ui/Checkbox';
+import {RadioButton, RadioButtonGroup} from 'material-ui/RadioButton';
+import TextField from 'material-ui/TextField';
+import IconButton from 'material-ui/IconButton';
+import AddCircle from 'material-ui/svg-icons/content/add-circle';
+import RemoveCircle from 'material-ui/svg-icons/content/remove-circle-outline';
+import light from '../../../styles/light';
+import { getMemberOrder } from '../../../helpers/getMemberOrder';
+
+
+/** Dialog to add groups in a collation. This component is used in the visual and tabular edit modes. It is mounted by `InfoBox` and `GroupInfoBox` components. */
+export default class AddGroupDialog extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ numberOfGroups: 1,
+ hasLeaves: props.addLeafs || false,
+ numberOfLeaves: 1,
+ direction: "left-to-right",
+ conjoin: false,
+ oddLeaf: 2,
+ copies: 1,
+ location: "inside",
+ errorText: {
+ numberOfGroups: "",
+ numberOfLeaves: "",
+ oddLeaf: "",
+ copies: "",
+ },
+ }
+ };
+
+ componentWillReceiveProps() {
+ this.resetForm();
+ }
+
+ /**
+ * Increment a state's value by one, bounded by `max` and `min`. If the user previously
+ * entered an invalid value, the value is set to `min`.
+ */
+ incrementNumber = (name, min, max, e) => {
+ if (e) e.preventDefault();
+ let newCount = 0;
+ if (!this.isNormalInteger(this.state[name])) {
+ newCount = min;
+ } else {
+ newCount = Math.min(max, this.state[name]+1);
+ }
+ let newState = {errorText:{}};
+ newState[name]=(isNaN(newCount))?min:newCount;
+ newState.errorText[name]="";
+ this.setState({...newState});
+ }
+
+ /**
+ * Decrement a state by one, bounded by `max` and `min`. If the user previously
+ * entered an invalid value, the value is set to min.
+ */
+ decrementNumber = (name, min, max, e) => {
+ if (e) e.preventDefault();
+ let newCount = Math.min(max, Math.max(min, this.state[name]-1));
+ let newState = {errorText:{}};
+ newState[name]=(isNaN(newCount))?min:newCount;
+ newState.errorText[name]="";
+ this.setState({...newState});
+ }
+
+ /**
+ * Validate user input. If invalid, display error message, otherwise update relevant state
+ */
+ onNumberChange = (name, value) => {
+ let errorState = this.state.errorText;
+ errorState[name] = "";
+ if (!this.isNormalInteger(value)) {
+ errorState[name] = "Must be a number";
+ } else {
+ value = parseInt(value, 10);
+ }
+ if ((name==="numberOfGroups"||name==="copies") && (value<1 || value>99)) {
+ errorState[name] = "Number must be between 1 and 99";
+ } else if (name==="numberOfLeaves" && (value<1 || value>999)) {
+ errorState[name] = "Number must be between 1 and 999";
+ } else if (name==="oddLeaf" && (value<1 || value>this.state.numberOfLeaves)) {
+ errorState[name] = "Number must be between 1 and " + this.state.numberOfLeaves;
+ }
+ let newState = {};
+ newState[name] = value;
+ this.setState({...newState, errorText: errorState});
+ }
+
+ /**
+ * Check if string is an integer
+ */
+ isNormalInteger = (str) => {
+ return /^([1-9]\d*)$/.test(str);
+ }
+
+ /**
+ * Toggle a checkbox
+ */
+ onToggleCheckbox = (stateName, value) => {
+ let newState = {};
+ newState[stateName] = value;
+ this.setState(newState);
+ }
+
+ /**
+ * Update location radio button group
+ */
+ onLocationChange = (value) => {
+ this.setState({location: value});;
+ }
+ /**
+ * Returns next sibling of a group
+ */
+ getNextSibling = () => {
+ let activeGroup = this.props.Groups[this.props.selectedGroups[0]];
+ for (let groupID of this.props.groupIDs.slice(this.props.groupIDs.indexOf(activeGroup.id)+1)) {
+ const group = this.props.Groups[groupID]
+ if (group.nestLevel===activeGroup.nestLevel)
+ return group
+ }
+ return null;
+ }
+
+ /**
+ * Returns the last child group
+ */
+ findLastChildGroup = (memberIDs) => {
+ let lastGroup = null;
+ for (let memberID of memberIDs) {
+ if (memberID.charAt(0)==="G") {
+ const member = this.props.Groups[memberID]
+ if (lastGroup===null || (this.props.groupIDs.indexOf(member.id)>this.props.groupIDs.indexOf(lastGroup.id))) {
+ lastGroup = member;
+ }
+ if (member.memberIDs.length>0) {
+ let result = this.findLastChildGroup(member.memberIDs);
+ if (result && this.props.groupIDs.indexOf(result.id) > this.props.groupIDs.indexOf(lastGroup.id)) lastGroup = result;
+ }
+ }
+ }
+ return lastGroup;
+ }
+
+ /**
+ * Submit add group request
+ */
+ submit = () => {
+ if (this.props.addLeafs || !this.isDisabled()) {
+ let data = {group:{}, additional:{}};
+ data.additional["noOfGroups"] = this.state.numberOfGroups;
+ if (this.state.hasLeaves) {
+ data.additional["noOfLeafs"] = this.state.numberOfLeaves;
+ data.additional["conjoin"] = (this.state.numberOfLeaves>1)? this.state.conjoin : false;
+ if (this.state.numberOfLeaves>1 && this.state.conjoin && !(this.state.numberOfLeaves%2===0)) {
+ data.additional["oddMemberLeftOut"] = this.state.oddLeaf;
+ }
+ }
+ if (this.props.selectedGroups.length===0){
+ // Empty project. Add new group
+ data.additional["order"] = 1;
+ data.additional["memberOrder"] = 1;
+ data.group["direction"] = this.state.direction;
+ data.group["type"] = "Quire";
+ data.group["title"] = "None";
+ } else if(this.props.addLeafs) {
+ // Add Leafs inside
+ data = {leaf:{}, additional:{...data.additional}};
+ delete data.additional.noOfGroups
+ data.leaf["project_id"] = this.props.projectID;
+ data.leaf["parentID"] = this.props.selectedGroups[0];
+ data.additional["memberOrder"] = 1;
+ this.props.action.addLeafs(data);
+ this.props.closeDialog();
+ return;
+ }
+ else {
+ // Add group(s)
+ const group = this.props.Groups[this.props.selectedGroups[0]];
+ let memberOrder = getMemberOrder(group, this.props.Groups, this.props.groupIDs);
+ let groupOrder = this.props.groupIDs.indexOf(group.id)+1;
+ data.group = {
+ title: "None",
+ type: "Quire"
+ };
+ data.group["direction"]=this.state.direction;
+ if (group.parentID) {
+ // If active group is nested, the new group(s) must have the same parent as the active group
+ data.additional["parentGroupID"] = group.parentID;
+ }
+ if (this.state.location==="below") {
+ // Add group below
+ memberOrder += 1;
+ let sibling = this.getNextSibling();
+ if (sibling) {
+ groupOrder = this.props.groupIDs.indexOf(sibling.id)+1;
+ } else {
+ // No sibling..
+ if (!group.parentID) {
+ // Active group is a root group with no next sibling
+ groupOrder = this.props.groupIDs.length+1;
+ } else {
+ if (group.memberIDs.length>0) {
+ // Find the last child (possibly multi-nested)
+ let lastChild = this.findLastChildGroup(group.memberIDs);
+ if (lastChild===null) {
+ groupOrder = this.props.groupIDs.indexOf(group.id) + 2;
+ } else {
+ groupOrder = this.props.groupIDs.indexOf(lastChild.id) + 2;
+ }
+ } else {
+ // If no children
+ groupOrder = groupOrder+1;
+ }
+ }
+ }
+ } else if (this.state.location==="inside") {
+ // Add group inside
+ groupOrder += 1;
+ memberOrder = 1;
+ data.additional["parentGroupID"] = group.id;
+ }
+ data.additional["memberOrder"] = memberOrder;
+ data.additional["order"] = groupOrder;
+ }
+ data.group["project_id"] = this.props.projectID
+ this.props.action.addGroups(data);
+ this.props.closeDialog();
+ this.setState({location: "below"});
+ this.resetForm();
+ }
+ }
+
+ /**
+ * Return `true` if there are any errors in the input fields
+ */
+ isDisabled = () => {
+ let copiesError = !(this.state.errorText.copies===undefined) && this.state.errorText.copies.length>0;
+ let numberOfGroupsError = !(this.state.errorText.numberOfGroups===undefined) && this.state.errorText.numberOfGroups.length>0;
+ let numberOfLeavesError = !(this.state.errorText.numberOfLeaves===undefined) && this.state.errorText.numberOfLeaves.length>0;
+ let oddLeafError = !(this.state.errorText.oddLeaf===undefined) && this.state.errorText.oddLeaf.length>0;
+ return this.state.location==="" || copiesError || numberOfGroupsError || numberOfLeavesError || oddLeafError;
+ }
+
+ /**
+ * Reset state
+ */
+ resetForm = () => {
+ this.setState({
+ numberOfGroups: 1,
+ hasLeaves: this.props.addLeafs || false,
+ numberOfLeaves: 1,
+ direction: "left-to-right",
+ conjoin: false,
+ oddLeaf: 2,
+ copies: 1,
+ location: this.props.selectedGroups.length>0?"":"inside",
+ errorText: {
+ numberOfGroups: "",
+ numberOfLeaves: "",
+ oddLeaf: "",
+ copies: "",
+ },
+ });
+ }
+
+ render() {
+ const actions = [
+ {this.props.closeDialog()}}
+ style={{width:"49%", marginRight:"1%",border:"1px solid #ddd"}}
+ />,
+ ,
+ ];
+
+ const styles = {
+ radioButton: {
+ marginBottom: 5,
+ },
+ };
+
+ let conjoinOption = "";
+ let directionOption = "";
+ let oddLeaf = "";
+ // let copies = "";
+ let numberOfLeaves = "";
+ if (this.state.hasLeaves) {
+ numberOfLeaves =
+
+
+
Number of leaves
+
+
+
this.onNumberChange("numberOfLeaves", v)}
+ style={{width:"100px"}}
+ inputStyle={{textAlign:"center"}}
+ />
+ this.decrementNumber("numberOfLeaves", 1, 999, e)}
+ aria-label="Decrement number of leaves"
+ >
+
+
+ this.incrementNumber("numberOfLeaves", 1, 999, e)}
+ aria-label="Increment number of leaves"
+ >
+
+
+
+
;
+ }
+
+ if (this.state.hasLeaves && this.state.numberOfLeaves>1) {
+ conjoinOption =
+
+
+
Conjoin leaves?
+
+
+ this.onToggleCheckbox("conjoin", !this.state.conjoin)}
+ />
+
+
+ }
+ if (this.state.hasLeaves && this.state.conjoin && !(this.state.numberOfLeaves%2===0)) {
+ oddLeaf =
+
+
+
Odd leaf to not conjoin
+
+
+
this.onNumberChange("oddLeaf", v)}
+ style={{width:"100px"}}
+ inputStyle={{textAlign:"center"}}
+ />
+ this.decrementNumber("oddLeaf", 1, this.state.numberOfLeaves, e)}
+ aria-label="Decrement leaf number"
+ >
+
+
+ this.incrementNumber("oddLeaf", 1, this.state.numberOfLeaves, e)}
+ aria-label="Increment leaf number"
+ >
+
+
+
+
+ }
+ if(!this.props.addLeafs){
+ directionOption =
+
+
+
Right-to-Left Direction
+
+
+ {if(this.state.direction === "right-to-left"){this.onToggleCheckbox("direction", "left-to-right")}else if(this.state.direction === "left-to-right"){this.onToggleCheckbox("direction", "right-to-left")}}}
+ />
+
+
+ }
+
+ let numberOfGroups = this.state.location?
+
+
Number of groups
+
+
+
this.onNumberChange("numberOfGroups", v)}
+ style={{width:"100px"}}
+ inputStyle={{textAlign:"center"}}
+ />
+ this.decrementNumber("numberOfGroups", 1, 999, e)}
+ >
+
+
+ this.incrementNumber("numberOfGroups", 1, 999, e)}
+ >
+
+
+
+
: "";
+
+ let radioButtonGroupHeader = Add new group(s) ;
+ let radioButtonGroup = this.onLocationChange(v)}>
+
+
+
+
+
+ let addLeafsCheckbox = this.state.location!==""?
+
+
Add leaves inside?
+
+
+ this.onToggleCheckbox("hasLeaves", !this.state.hasLeaves)}
+ />
+
+
: "";
+
+
+ if (!this.props.selectedGroups) {
+ radioButtonGroupHeader="";
+ radioButtonGroup="";
+ }
+
+ if (this.props.selectedGroups.length===0){
+ radioButtonGroup="";
+ }
+
+ if (this.props.addLeafs) {
+ numberOfGroups="";
+ radioButtonGroupHeader="";
+ radioButtonGroup="";
+ addLeafsCheckbox="";
+ }
+
+ const dialog = (
+
+ {radioButtonGroupHeader}
+ {radioButtonGroup}
+ {numberOfGroups}
+ {addLeafsCheckbox}
+ {numberOfLeaves}
+ {directionOption}
+ {conjoinOption}
+ {oddLeaf}
+
+ );
+
+ return (
+
+ {dialog}
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/infoBox/dialog/AddLeafDialog.js b/viscoll-app/src/components/infoBox/dialog/AddLeafDialog.js
new file mode 100644
index 00000000..e853ac58
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/dialog/AddLeafDialog.js
@@ -0,0 +1,374 @@
+import React from 'react';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import RaisedButton from 'material-ui/RaisedButton';
+import Checkbox from 'material-ui/Checkbox';
+import {RadioButton, RadioButtonGroup} from 'material-ui/RadioButton';
+import TextField from 'material-ui/TextField';
+import IconButton from 'material-ui/IconButton';
+import AddCircle from 'material-ui/svg-icons/content/add-circle';
+import RemoveCircle from 'material-ui/svg-icons/content/remove-circle-outline';
+import light from '../../../styles/light';
+import { getMemberOrder } from '../../../helpers/getMemberOrder';
+import { btnBase } from '../../../styles/button';
+
+/** Dialog to add leaves in a collation. This component is used in the visual and tabular edit modes. */
+export default class AddLeafDialog extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ open: false,
+ numberOfLeaves: 1,
+ conjoin: false,
+ oddLeaf: 2,
+ location: "",
+ errorText: {
+ numberOfLeaves: "",
+ oddLeaf: "",
+ },
+ disabledAbove: false,
+ disabledBelow: false,
+ };
+ }
+
+ /** Open this modal component */
+ handleOpen = () => {
+ this.setState({open: true});
+ this.props.togglePopUp(true);
+ };
+
+ /** Close this modal component */
+ handleClose = () => {
+ this.clearForm();
+ this.setState({open: false});
+ this.props.togglePopUp(false);
+ };
+
+ /**
+ * Increment a state's value by one, bounded by `max` and `min`. If the user previously
+ * entered an invalid value, the value is set to `min`.
+ */
+ incrementNumber = (name, min, max, e) => {
+ if (e) e.preventDefault();
+ let newCount = 0;
+ if (!this.isNormalInteger(this.state[name])) {
+ newCount = min;
+ } else {
+ newCount = Math.min(max, this.state[name]+1);
+ }
+ let newState = {errorText:{}};
+ newState[name]=(isNaN(newCount))?1:newCount;
+ newState.errorText[name]="";
+ this.setState({...newState});
+ }
+
+ /**
+ * Decrement a state by one, bounded by `max` and `min`. If the user previously
+ * entered an invalid value, the value is set to min.
+ */
+ decrementNumber = (name, min, max, e) => {
+ if (e) e.preventDefault();
+ let newCount = Math.min(max, Math.max(min, this.state[name]-1));
+ let newState = {errorText:{}};
+ newState[name]=(isNaN(newCount))?1:newCount;
+ newState.errorText[name]="";
+ this.setState({...newState});
+ }
+
+ /**
+ * Validate user input. If invalid, display error message, otherwise update relevant state
+ */
+ onNumberChange = (stateName, value) => {
+ let errorState = this.state.errorText;
+ errorState[stateName] = "";
+ if (!this.isNormalInteger(value)) {
+ errorState[stateName] = "Must be a number";
+ } else {
+ value = parseInt(value, 10);
+ }
+ if (stateName==="numberOfLeaves" && (value<1 || value>999)) {
+ errorState[stateName] = "Number must be between 1 and 999";
+ } else if (stateName==="oddLeaf" && (value<1 || value>this.state.numberOfLeaves)) {
+ errorState[stateName] = "Number must be between 1 and " + this.state.numberOfLeaves;
+ }
+ let newState = {};
+ newState[stateName] = value;
+ this.setState({...newState, errorText: errorState});
+ }
+
+ /**
+ * Check if string is an integer
+ */
+ isNormalInteger = (str) => {
+ return /^([1-9]\d*)$/.test(str);
+ }
+
+ /**
+ * Toggle conjoin checkbox
+ */
+ onToggleConjoin = () => {
+ this.setState({conjoin: !this.state.conjoin});
+ }
+
+ /**
+ * Update location radio button group
+ */
+ onLocationChange = (value) => {
+ this.setState({location: value});;
+ }
+
+ /**
+ * Return `true` if there are any errors in the input fields
+ */
+ isDisabled = (activeLeaf) => {
+ let addable = activeLeaf.attached_above!=="None" && activeLeaf.attached_below!=="None";
+ let numberOfLeavesError = !(this.state.errorText.numberOfLeaves===undefined) && this.state.errorText.numberOfLeaves.length>0;
+ let oddLeafError = !(this.state.errorText.oddLeaf===undefined) && this.state.errorText.oddLeaf.length>0;
+ return this.state.location==="" || addable || numberOfLeavesError || oddLeafError;
+ }
+
+ /**
+ * Submit add leaf request
+ */
+ submit = () => {
+ const leaf = this.props.Leafs[this.props.selectedLeaves[0]];
+ let data = {leaf:{}, additional:{}};
+ data["additional"]["noOfLeafs"] = this.state.numberOfLeaves;
+ data["additional"]["conjoin"] = (this.state.numberOfLeaves>1)? this.state.conjoin : false;
+ if (this.state.conjoin && this.state.numberOfLeaves>1 && !(this.state.numberOfLeaves%2===0)) {
+ data["additional"]["oddMemberLeftOut"] = this.state.oddLeaf;
+ }
+ let memberOrder = getMemberOrder(leaf, this.props.Groups, this.props.groupIDs);
+ if (this.state.location==="below") {
+ memberOrder += 1;
+ data["additional"]["order"] = this.props.leafIDs.indexOf(leaf.id) + 2;
+ } else {
+ data["additional"]["order"] = this.props.leafIDs.indexOf(leaf.id) + 1;
+ }
+
+ data["additional"]["memberOrder"] = memberOrder;
+ data["leaf"]["project_id"] = this.props.projectID;
+ data["leaf"]["parentID"] = leaf.parentID;
+
+ this.props.action.addLeafs(data);
+ this.handleClose();
+ this.clearForm();
+ }
+
+ /**
+ * Reset state
+ */
+ clearForm = () => {
+ this.setState({
+ numberOfLeaves: 1,
+ conjoin: false,
+ oddLeaf: 2,
+ location: "",
+ errorText: {
+ numberOfLeaves: "",
+ oddLeaf: "",
+ },
+ disabledAbove: false,
+ disabledBelow: false,
+ })
+ }
+
+ render() {
+ const activeLeaf = this.props.Leafs[this.props.selectedLeaves[0]];
+ const activeLeafOrder = this.props.leafIDs.indexOf(activeLeaf.id)+1
+ let defaultAddLocation = "";
+
+ const actions = [
+ ,
+ ,
+ ];
+
+ const styles = {
+ radioButton: {
+ marginBottom: 5,
+ },
+ };
+
+ let noOfLeafs = "";
+ let conjoinOption = "";
+ let oddLeaf = "";
+
+ if (this.state.location!=="") {
+ noOfLeafs =
+
+
+
Number of leaves
+
+
+
this.onNumberChange("numberOfLeaves", v)}
+ style={{width:"100px"}}
+ inputStyle={{textAlign:"center"}}
+ />
+ this.decrementNumber("numberOfLeaves", 1, 999, e)}
+ >
+
+
+ this.incrementNumber("numberOfLeaves", 1, 999, e)}
+ >
+
+
+
+
;
+ }
+
+ if (this.state.numberOfLeaves>1) {
+ conjoinOption =
+
+
+
Conjoin leaves?
+
+
+ this.onToggleConjoin()}
+ checked={this.state.conjoin && this.state.numberOfLeaves>1}
+ style={{verticalAlign:"bottom"}}
+ />
+
+
+ }
+ if (this.state.conjoin && this.state.numberOfLeaves>1 && !(this.state.numberOfLeaves%2===0)) {
+ oddLeaf =
+
+
+
Odd leaf to not conjoin
+
+
+
this.onNumberChange("oddLeaf", v)}
+ style={{width:"100px"}}
+ inputStyle={{textAlign:"center"}}
+ />
+ this.decrementNumber("oddLeaf", 1, this.state.numberOfLeaves, e)}
+ >
+
+
+ this.incrementNumber("oddLeaf", 1, this.state.numberOfLeaves, e)}
+ >
+
+
+
+
+ }
+
+ let disabledAddAbove = (activeLeaf.attached_above!=="None"?
+
+
{this.setState({disabledAbove:true})}}
+ onMouseOut={()=>{this.setState({disabledAbove:false})}}>
+ above leaf {activeLeafOrder}
+
+
+ Cannot insert a new leaf above leaf {activeLeafOrder} because leaf {activeLeafOrder} attached to leaf {activeLeafOrder-1}
+
+
+ : ""
+ );
+ let disabledAddBelow = (activeLeaf.attached_below!=="None"?
+
+
{this.setState({disabledBelow:true})}}
+ onMouseOut={()=>{this.setState({disabledBelow:false})}}>
+ below leaf {activeLeafOrder}
+
+
+ Cannot insert a new leaf below leaf {activeLeafOrder} because leaf {activeLeafOrder} is attached to leaf {activeLeafOrder+1}
+
+
+ : ""
+ );
+
+ let addLeaves =
+
+
Add new leaves...
+ {disabledAddBelow}
+ this.onLocationChange(v)}
+ >
+
+
+
+ {disabledAddAbove}
+
+
+ const dialog = (
+
+ {addLeaves}
+ {noOfLeafs}
+ {conjoinOption}
+ {oddLeaf}
+
+ );
+
+ return (
+
+
+ {dialog}
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/infoBox/dialog/AddNote.js b/viscoll-app/src/components/infoBox/dialog/AddNote.js
new file mode 100644
index 00000000..28cf8875
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/dialog/AddNote.js
@@ -0,0 +1,216 @@
+import React from 'react';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import RaisedButton from 'material-ui/RaisedButton';
+import IconButton from 'material-ui/IconButton';
+import IconAdd from 'material-ui/svg-icons/content/add';
+import AutoComplete from 'material-ui/AutoComplete';
+import SelectField from 'material-ui/SelectField';
+import MenuItem from 'material-ui/MenuItem';
+import TextField from 'material-ui/TextField';
+import Checkbox from 'material-ui/Checkbox';
+
+
+/** Dialog to add a note to an object (leaf, side, or group). This component is used in the visual and tabular edit modes. */
+export default class AddNote extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ open: false,
+ type: '',
+ description:'',
+ show: false,
+ searchText: '',
+ noteID: null,
+ };
+ }
+
+ /** Open this modal component */
+ handleOpen = () => {
+ this.setState({
+ open: true,
+ type: '',
+ description:'',
+ show: false,
+ searchText: '',
+ noteID: null,
+ });
+ this.props.togglePopUp(true);
+ };
+
+ /** Close this modal component */
+ handleClose = () => {
+ this.setState({open: false});
+ this.props.togglePopUp(false);
+ };
+
+ handleUpdateInput = (searchText) => {
+ this.setState({
+ searchText: searchText,
+ noteID: null
+ });
+ };
+
+ handleNewRequest = (request) => {
+ // User pressed enter instead of selecting a note in drop down
+ // Look for key associated with user input
+ let noteID = null;
+ for (let id in this.props.Notes){
+ const note = this.props.Notes[id];
+ if (note.title===request) { noteID = note.id;}
+ }
+ this.setState({noteID}, ()=>{
+ if (noteID) this.submit()
+ });
+ };
+
+ submit = () => {
+ if (this.state.noteID!==null) {
+ // Attach existing note to selected objects
+ this.props.action.linkNote(this.state.noteID);
+ } else {
+ // Check if note exists (in case user types and did not press enter)
+ let noteID = null;
+ for (let id in this.props.Notes){
+ const note = this.props.Notes[id];
+ if (note.title===this.state.searchText) noteID = note.id;
+ }
+ if (noteID) {
+ this.props.action.linkNote(noteID);
+ } else {
+ // Did not find note, so create and attach new note to object
+ this.props.action.createAndAttachNote(this.state.searchText, this.state.type, this.state.description, this.state.show);
+ }
+ }
+ this.handleClose();
+ }
+
+ noteExists = () => {
+ for (let noteID in this.props.Notes) {
+ const note = this.props.Notes[noteID];
+ if (note.title===this.state.searchText) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Mapping function to render one note type menu item
+ */
+ renderNoteTypes = (name) => {
+ return ;
+ }
+
+ onChange = (name, value) => {
+ this.setState({[name]:value});
+ }
+
+ getFilteredNoteTitlesDropDown = () => {
+ return Object.keys(this.props.Notes).filter((noteID) => {return !this.props.commonNotes.includes(noteID)})
+ }
+
+ render() {
+ const dataSourceConfig = {
+ text: 'textKey',
+ value: 'valueKey',
+ };
+
+ const actions = [
+ ,
+ ,
+ ];
+
+ let newNoteForm =
;
+ if (!this.noteExists() && this.state.searchText.length>1) {
+ newNoteForm = (
+
+
this.onChange("type",v)}
+ floatingLabelText="Note type"
+ fullWidth
+ style={{marginTop:-20}}
+ >
+ {this.props.noteTypes.map(this.renderNoteTypes)}
+
+
this.onChange("description",v)}
+ multiLine
+ fullWidth
+ style={{marginTop:-20}}
+ />
+
+ Show in diagram
+
+
+ this.onChange("show",!this.state.show)}
+ />
+
+
+ );
+ }
+
+ let dialog = (
+ {return {textKey: this.props.Notes[noteID].title, valueKey: noteID}})}
+ filter={(searchText, key) => (key.indexOf(searchText) !== -1)}
+ openOnFocus={true}
+ dataSourceConfig={dataSourceConfig}
+ fullWidth
+ listStyle={{ maxHeight: 300, overflow: 'auto' }}
+ errorText={(!this.noteExists()&&this.state.searchText.length>0)?"This note doesn't exist. To create and attach it, fill out its note type and description.":""}
+ errorStyle={{color:"#727272"}}
+ floatingLabelFocusStyle={{color:"#3A4B55"}}
+ />
+ {newNoteForm}
+ )
+
+ return (
+
+
+
+
+ {dialog}
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/infoBox/dialog/DeleteConfirmationDialog.js b/viscoll-app/src/components/infoBox/dialog/DeleteConfirmationDialog.js
new file mode 100644
index 00000000..b68613e9
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/dialog/DeleteConfirmationDialog.js
@@ -0,0 +1,133 @@
+import React from 'react';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import RaisedButton from 'material-ui/RaisedButton';
+import {btnBase} from '../../../styles/button';
+
+/** Delete confirmation dialog for deleting group(s) and leaf(s) */
+export default class DeleteConfirmationDialog extends React.Component {
+ state = {
+ open: false,
+ windowWidth: window.innerWidth,
+ };
+
+ resizeHandler = () => {
+ this.setState({windowWidth:window.innerWidth});
+ }
+
+ componentDidMount() {
+ window.addEventListener('resize', this.resizeHandler);
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener("resize", this.resizeHandler);
+ }
+
+ handleOpen = () => {
+ this.setState({open: true});
+ this.props.togglePopUp(true);
+ };
+
+ handleClose = () => {
+ this.setState({open: false});
+ this.props.togglePopUp(false);
+ };
+
+ containsTacketedLeaf = () => {
+ if (this.props.memberType==="Leaf") {
+ for (const leafID of this.props.selectedObjects) {
+ const group = this.props.Groups[this.props.Leafs[leafID].parentID];
+ if (
+ (group.tacketed.length>0 && (group.tacketed[0]===leafID || (group.tacketed[1] && group.tacketed[1]===leafID)))
+ ||
+ (group.sewing.length>0 && (group.sewing[0]===leafID || (group.sewing[1] && group.sewing[1]===leafID)))
+ ) return true;
+ }
+ }
+ return false;
+ }
+
+ getTitle = () => {
+ const memberType = this.props.memberType;
+ const item = this.props[memberType+"s"][this.props.selectedObjects[0]];
+ let itemOrder = this.props[`${item.memberType.toLowerCase()}IDs`].indexOf(item.id)+1;
+
+ if (item){
+ if (this.containsTacketedLeaf()) {
+ if (this.props.selectedObjects.length>1) {
+ return "One of the selected leaves is tacketed or sewn. You cannot delete tacketed/sewn leaves.";
+ } else {
+ return "You cannot delete a leaf that is tacketed or sewn.";
+ }
+ } else if (this.props.selectedObjects.length===1) {
+ return "Are you sure you want to delete " + item.memberType.toLowerCase() + " " + itemOrder + "?";
+ } else {
+ let itemName = item.memberType.toLowerCase();
+ if (itemName==="leaf") itemName = "leave";
+ return "Are you sure you want to delete " +
+ this.props.selectedObjects.length + " " + itemName + "s?";
+ }
+ }
+
+ }
+
+ submit = (e) => {
+ if (e) e.preventDefault();
+ if (this.props.selectedObjects.length===1) {
+ // handle single delete
+ let id = this.props.selectedObjects[0]
+ this.props.action.singleDelete(id);
+ } else {
+ // handle batch delete
+ const memberType = this.props.memberType.toLowerCase();
+ let data = {};
+ data[memberType+"s"]= [];
+ for (var id of this.props.selectedObjects) {
+ data[memberType+"s"].push(id);
+ }
+ this.props.action.batchDelete(data);
+ }
+ this.handleClose();
+ }
+
+ render() {
+ const actions = [
+ ,
+ ,
+ ];
+
+ return (
+
+
+
+
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/infoBox/dialog/FolioNumberDialog.js b/viscoll-app/src/components/infoBox/dialog/FolioNumberDialog.js
new file mode 100644
index 00000000..637d437c
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/dialog/FolioNumberDialog.js
@@ -0,0 +1,181 @@
+import React from 'react';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import RaisedButton from 'material-ui/RaisedButton';
+import TextField from 'material-ui/TextField';
+import IconButton from 'material-ui/IconButton';
+import AddCircle from 'material-ui/svg-icons/content/add-circle';
+import RemoveCircle from 'material-ui/svg-icons/content/remove-circle-outline';
+import {RadioButton, RadioButtonGroup} from 'material-ui/RadioButton';
+import light from '../../../styles/light';
+
+/** Dialog to add leaves in a collation. This component is used in the visual and tabular edit modes. */
+export default class FolioNumberDialog extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ folioOrPage: null,
+ startNumber: this.props.defaultStartNumber,
+ errorText: {
+ startNumber: "",
+ },
+ };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.setState({startNumber: nextProps.defaultStartNumber});
+ }
+
+ /**
+ * Increment a state's value by one, bounded by `max` and `min`. If the user previously
+ * entered an invalid value, the value is set to `min`.
+ */
+ incrementNumber = (name, min, max, e) => {
+ if (e) e.preventDefault();
+ let newCount = 0;
+ if (!this.isNormalInteger(this.state[name])) {
+ newCount = min;
+ } else {
+ newCount = Math.min(max, this.state[name]+1);
+ }
+ let newState = {errorText:{}};
+ newState[name]=(isNaN(newCount))?1:newCount;
+ newState.errorText[name]="";
+ this.setState({...newState});
+ }
+
+ /**
+ * Decrement a state by one, bounded by `max` and `min`. If the user previously
+ * entered an invalid value, the value is set to min.
+ */
+ decrementNumber = (name, min, max, e) => {
+ if (e) e.preventDefault();
+ let newCount = Math.min(max, Math.max(min, this.state[name]-1));
+ let newState = {errorText:{}};
+ newState[name]=(isNaN(newCount))?1:newCount;
+ newState.errorText[name]="";
+ this.setState({...newState});
+ }
+
+ /**
+ * Validate user input. If invalid, display error message, otherwise update relevant state
+ */
+ onNumberChange = (stateName, value) => {
+ let errorState = this.state.errorText;
+ errorState[stateName] = "";
+ if (!this.isNormalInteger(value)) {
+ errorState[stateName] = "Must be a number";
+ } else {
+ value = parseInt(value, 10);
+ }
+ if (stateName==="startNumber" && (value<1 || value>9999)) {
+ errorState[stateName] = "Number must be between 1 and 9999";
+ }
+ let newState = {};
+ newState[stateName] = value;
+ this.setState({...newState, errorText: errorState});
+ }
+
+ isNormalInteger = (str) => {
+ return /^([1-9]\d*)$/.test(str);
+ }
+
+ clearForm = () => {
+ this.setState({
+ folioOrPage: null,
+ errorText: { startNumber: "" },
+ })
+ }
+
+ submit = () => {
+ if (this.state.folioOrPage==="folio_number") {
+ this.props.action.generateFolioNumbers(this.state.startNumber);
+ } else {
+ this.props.action.generatePageNumbers(this.state.startNumber);
+ }
+ this.clearForm();
+ this.props.toggleFolioModal(false);
+ }
+
+ render() {
+ const actions = [
+ {this.clearForm();this.props.toggleFolioModal(false)}}
+ style={{width:"49%", marginRight:"1%",border:"1px solid #ddd"}}
+
+ />,
+ ,
+ ];
+
+ return ({this.clearForm();this.props.toggleFolioModal(false)}}
+ contentStyle={{width:"450px"}}
+ titleStyle={{textAlign:"center"}}
+ paperClassName="addDialog"
+ >
+
+
Generate...
+
this.setState({folioOrPage: v})}
+ >
+
+
+
+
+ {this.state.folioOrPage?
+
+
+
Starting number
+
+
+
this.onNumberChange("startNumber", v)}
+ style={{width:"100px"}}
+ inputStyle={{textAlign:"center"}}
+ />
+ this.decrementNumber("startNumber", 1, 9999, e)}
+ >
+
+
+ this.incrementNumber("startNumber", 1, 9999, e)}
+ >
+
+
+
+
+ :""}
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/infoBox/dialog/NoteDialog.js b/viscoll-app/src/components/infoBox/dialog/NoteDialog.js
new file mode 100644
index 00000000..48cd289d
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/dialog/NoteDialog.js
@@ -0,0 +1,113 @@
+import React from 'react';
+import EditNoteForm from '../../notesManager/EditNoteForm';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+
+export default class NoteDialog extends React.Component {
+
+
+ getLinkedGroups = () => {
+ const groupsWithCurrentNote = Object.keys(this.props.Groups).filter((groupID) => {
+ return (this.props.Groups[groupID].notes.includes(this.props.activeNote.id))
+ });
+ return groupsWithCurrentNote.map((value) => {
+ const label = `Group ${this.props.Groups[value].order}`;
+ return {label, value};
+ });
+ }
+
+ getLinkedLeaves = () => {
+ const leafsWithCurrentNote = Object.keys(this.props.Leafs).filter((leafID) => {
+ return (this.props.Leafs[leafID].notes.includes(this.props.activeNote.id))
+ });
+ return leafsWithCurrentNote.map((value)=>{
+ const label = `Leaf ${this.props.Leafs[value].order}`;
+ return {label, value};
+ });
+ }
+
+ getLinkedSides = () => {
+ const rectosWithCurrentNote = Object.keys(this.props.Rectos).filter((rectoID) => {
+ return (this.props.Rectos[rectoID].notes.includes(this.props.activeNote.id))
+ });
+ const versosWithCurrentNote = Object.keys(this.props.Versos).filter((versoID) => {
+ return (this.props.Versos[versoID].notes.includes(this.props.activeNote.id))
+ });
+ const sidesWithCurrentNote = [];
+ for (let value of rectosWithCurrentNote){
+ const leafOrder = this.props.Leafs[this.props.Rectos[value].parentID].order;
+ const label = `Leaf ${leafOrder}: Side Recto}`;
+ sidesWithCurrentNote.push({label, value})
+ }
+ for (let value of versosWithCurrentNote){
+ const leafOrder = this.props.Leafs[this.props.Versos[value].parentID].order;
+ const label = `Leaf ${leafOrder}: Side Verso}`;
+ sidesWithCurrentNote.push({label, value})
+ }
+ return sidesWithCurrentNote;
+ }
+
+ getRectosAndVersos = () => {
+ const size = Object.keys(this.props.Rectos).length;
+ let result = {};
+ for (let i=0; i ,
+ ];
+ return (
+
+
+
+ );
+ }
+
+}
+
+
+
+
diff --git a/viscoll-app/src/components/infoBox/dialog/VisualizationDialog.js b/viscoll-app/src/components/infoBox/dialog/VisualizationDialog.js
new file mode 100644
index 00000000..b567b7f6
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/dialog/VisualizationDialog.js
@@ -0,0 +1,141 @@
+import React from 'react';
+import {getLeafsOfGroup} from '../../../helpers/getLeafsOfGroup';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import RaisedButton from 'material-ui/RaisedButton';
+import SelectField from '../../global/SelectField';
+
+/** Dialog for creating/editing sewing or tacketed attribute */
+export default class VisualizationDialog extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ startLeaf: null,
+ endLeaf: null,
+ }
+ }
+
+ componentWillReceiveProps() {
+ this.setState({
+ startLeaf: null,
+ endLeaf: null,
+ });
+ }
+
+ handleChange = (type, value) => {
+ this.setState({[type]:value});
+ }
+
+ renderMenuItem = (item, index) => {
+ if (item.id) {
+ return {
+ value:item.id,
+ text:"Leaf " + (this.props.leafIDs.indexOf(item.id)+1),
+ }
+ } else {
+ return {
+ value:"spine",
+ text:"Spine",
+ }
+ }
+ }
+
+ onCreate = () => {
+ let attributeValue = this.state.startLeaf==="spine"?[this.state.endLeaf]:[this.state.startLeaf,this.state.endLeaf];
+ this.props.updateGroup(this.props.type, attributeValue);
+ this.props.closeDialog();
+ }
+
+ render() {
+ let isCreateAction = (this.props.type!=="" && this.props.group[this.props.type].length===0);
+ const actions = [
+ {this.props.closeDialog()}}
+ />,
+ ,
+ {this.props.delete(); this.props.closeDialog()}}
+ />,
+ ];
+ let selectFields = [];
+ let leafMembersOfCurrentGroup = [{id:null}].concat(getLeafsOfGroup(this.props.activeGroup, this.props.Leafs, false));
+ if (isCreateAction) {
+ // Create new sewing/tacket
+ let selectField1 = (
+ this.handleChange("startLeaf",v)}
+ data={leafMembersOfCurrentGroup.map((v,i)=>this.renderMenuItem(v,0))}
+ />)
+ let selectField2 = (
+ this.handleChange("endLeaf",v)}
+ data={leafMembersOfCurrentGroup.filter((item) => (item.id !== null && (this.state.startLeaf === null || this.state.startLeaf === "spine" || this.props.leafIDs.indexOf(item.id) > this.props.leafIDs.indexOf(this.state.startLeaf)))).map((v,i,a)=>this.renderMenuItem(v,1))}
+ />)
+ selectFields.push(selectField1);
+ selectFields.push(selectField2);
+ } else if (this.props.type!=="" && this.props.group[this.props.type].length>0) {
+ // Edit existing sewing/tacket
+
+ const leafStart = this.props.group[this.props.type].length===2? this.props.Leafs[this.props.group[this.props.type][0]] : null;
+ const leafEnd = this.props.group[this.props.type].length===2? this.props.Leafs[this.props.group[this.props.type][1]] : this.props.Leafs[this.props.group[this.props.type][0]];
+ const data1 = [{ id: null }].concat(leafMembersOfCurrentGroup.filter((item) => (item.id !== null && this.props.leafIDs.indexOf(item.id) < this.props.leafIDs.indexOf(leafEnd.id)+1)));
+ const data2 = leafMembersOfCurrentGroup.filter((item) => (leafStart === null && item.id !== null) || (leafStart !== null && item.id !== null && this.props.leafIDs.indexOf(item.id) > this.props.leafIDs.indexOf(leafStart.id)+1));
+ let selectField1 = (
+ this.props.handleTacketSewingChange(this.props.type, v, 0)}
+ data={data1.map((v,i)=>this.renderMenuItem(v,0))}
+ />)
+ let selectField2 = (
+ this.props.handleTacketSewingChange(this.props.type, v, 1)}
+ data={data2.map((v,i)=>this.renderMenuItem(v,1))}
+ />)
+ selectFields.push(selectField1);
+ selectFields.push(selectField2);
+ }
+
+ return (
+
+ this.props.closeDialog()}
+ contentStyle={{width: "30%", minWidth:"320px", maxWidth:"450px"}}
+ >
+ {selectFields}
+
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/src/components/notesManager/DeleteConfirmation.js b/viscoll-app/src/components/notesManager/DeleteConfirmation.js
new file mode 100644
index 00000000..f6ea3c34
--- /dev/null
+++ b/viscoll-app/src/components/notesManager/DeleteConfirmation.js
@@ -0,0 +1,92 @@
+import React from 'react';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import RaisedButton from 'material-ui/RaisedButton';
+import IconDelete from 'material-ui/svg-icons/action/delete';
+import IconButton from 'material-ui/IconButton';
+
+/** Delete confirmation dialog for deleting notes and note types */
+export default class DeleteConfirmation extends React.Component {
+ state = {
+ open: false,
+ };
+
+ handleOpen = () => {
+ this.setState({open: true});
+ this.props.togglePopUp(true);
+ };
+
+ handleClose = () => {
+ this.setState({open: false});
+ this.props.togglePopUp(false);
+ };
+
+ submit = () => {
+ if (this.props.item==="note") {
+ this.props.action.deleteNote(this.props.noteID);
+ } else {
+ this.props.onDelete(this.props.index)
+ }
+ this.handleClose();
+ }
+
+ render() {
+ const actions = [
+ ,
+ ,
+ ];
+ let deleteIcon =
+
+
+ let message = "This note will be removed from all groups/sides/leaves that have this note.";
+ if (this.props.item==="note type") {
+ deleteIcon =
+
+
+ message = "Any existing notes associated with this note type will be assigned to the note type 'Unknown'.";
+ }
+ if (this.props.item!=="") {
+ return (
+
+ {deleteIcon}
+
+ {message}
+
+
+ );
+ } else {
+ return
;
+ }
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/src/components/notesManager/EditNoteForm.js b/viscoll-app/src/components/notesManager/EditNoteForm.js
new file mode 100644
index 00000000..40310998
--- /dev/null
+++ b/viscoll-app/src/components/notesManager/EditNoteForm.js
@@ -0,0 +1,324 @@
+import React, {Component} from 'react';
+import DeleteConfirmation from './DeleteConfirmation';
+import TextField from 'material-ui/TextField';
+import RaisedButton from 'material-ui/RaisedButton';
+import IconSubmit from 'material-ui/svg-icons/action/done';
+import IconClear from 'material-ui/svg-icons/content/clear';
+import Checkbox from 'material-ui/Checkbox';
+import SelectField from '../global/SelectField';
+import ChipInput from 'material-ui-chip-input'
+
+
+/** Create New Note tab in the Note Manager */
+export default class EditNoteForm extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ id: props.note.id,
+ title: props.note.title,
+ type: props.note.type,
+ description: props.note.description,
+ editing: {
+ title: false,
+ description: false,
+ },
+ errors: {
+ title: "",
+ }
+ };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.setState({
+ id: nextProps.note.id,
+ title: nextProps.note.title,
+ type: nextProps.note.type,
+ description: nextProps.note.description,
+ editing: {
+ title: false,
+ description: false,
+ },
+ errors: {
+ title: "",
+ }
+ })
+ }
+
+ validateTitle = (title) => {
+ for (let noteID in this.props.Notes) {
+ const note = this.props.Notes[noteID];
+ if (note.title===title && note.id!==this.state.id) {
+ this.setState({errors:{title:"This note title already exists."}});
+ return;
+ };
+ }
+ if (title.length>100) {
+ this.setState({errors:{title:"Title must be less than 100 characters."}});
+ } else if (title.length===0) {
+ this.setState({errors:{title:"Title must not be empty."}});
+ } else {
+ this.setState({errors:{title:""}});
+ }
+ }
+
+ onChange = (name, value) => {
+ this.setState({[name]:value, editing: {...this.state.editing, [name]: true}});
+ if (name==="title") this.validateTitle(value.trim());
+ if (name==="type") {
+ let editing = {
+ title: this.state.title,
+ type: value,
+ description: this.state.description,
+ }
+ if (this.props.note)
+ this.props.action.updateNote(this.props.note.id, editing);
+ }
+ }
+
+ update = (event, name) => {
+ event.preventDefault();
+ if (this.props.note) {
+ let editing = {
+ title: this.state.title,
+ type: this.state.type,
+ description: this.state.description,
+ }
+ this.setState({editing: {...this.state.editing, [name]:false}});
+ this.props.action.updateNote(this.props.note.id, editing);
+ }
+ }
+
+ /**
+ * Reset input field to original value
+ */
+ onCancelUpdate = (name) => {
+ this.setState({
+ [name]: this.props.note[name],
+ editing: {
+ ...this.state.editing,
+ [name]: false
+ },
+ errors: {
+ ...this.state.errors,
+ [name]: ""
+ }
+ });
+ }
+
+ renderNoteTypes = (name) => {
+ return {value:name, text:name};
+ }
+
+ renderSubmitButtons = (name) => {
+ if (this.state.editing[name] && this.props.note!==null && this.props.note!==undefined) {
+ return (
+
+ }
+ style={{minWidth:"60px",marginLeft:"5px"}}
+ name="submit"
+ type="submit"
+ disabled={name==="title" && this.state.errors.title!==""}
+ />
+ }
+ style={{minWidth:"60px",marginLeft:"5px"}}
+ onClick={(e)=>this.onCancelUpdate(name)}
+ />
+
+ )
+ } else {
+ return "";
+ }
+ }
+
+ handleAddChip = (chip, type) => {
+ this.props.action.linkNote(this.props.note.id, [{type, id:chip.value}]);
+ }
+
+ handleDeleteChip = (id, index, type) => {
+ this.props.action.unlinkNote(this.props.note.id, [{type, id}]);
+ }
+
+ render() {
+ let title = this.props.isReadOnly? this.props.note.title : "Edit " + this.props.note.title;
+ let linkedObjects = "";
+ let deleteButton = "";
+ let sideData = [];
+ for (let i=0; i
+ Groups
+
+ { return{value:itemID, label:"Group " + (index+1)}})}
+ onRequestAdd={(chip) => this.handleAddChip(chip, "Group")}
+ onRequestDelete={(chip, index) => this.handleDeleteChip(chip, index, "Group")}
+ openOnFocus={true}
+ fullWidth={true}
+ fullWidthInput={false}
+ hintText="Click here to attach groups to this note"
+ menuProps={{maxHeight:200}}
+ tabIndex={this.props.tabIndex}
+ />
+
+
+ );
+ const linkToLeaves = (
+
+
Leaves
+
+ { return{value:itemID, label:"Leaf " + (index+1)}})}
+ onRequestAdd={(chip) => this.handleAddChip(chip, "Leaf")}
+ onRequestDelete={(chip, index) => this.handleDeleteChip(chip, index, "Leaf")}
+ openOnFocus={true}
+ fullWidth={true}
+ fullWidthInput={false}
+ hintText="Click here to attach leaves to this note"
+ menuProps={{maxHeight:200}}
+ tabIndex={this.props.tabIndex}
+ />
+
+
+ );
+ const linkToSides = (
+
+
Sides
+
+ this.handleAddChip(chip, "Side")}
+ onRequestDelete={(chip, index) => this.handleDeleteChip(chip, index, chip.split("_")[0])}
+ openOnFocus={true}
+ fullWidth={true}
+ fullWidthInput={false}
+ hintText="Click here to attach sides to this note"
+ menuProps={{maxHeight:200}}
+ tabIndex={this.props.tabIndex}
+ />
+
+
+ );
+
+ if (this.props.note) {
+ linkedObjects = (
+
+ {this.props.isReadOnly?"":
+
Attached to
+ {linkToSides}
+ {linkToLeaves}
+ {linkToGroups}
+
+ }
+
);
+ deleteButton = this.props.isReadOnly?"":
+
+
+ }
+ return (
+
+
{title}
+
+
+ Title
+
+
+ {this.props.isReadOnly?
{this.state.title}
:
+
this.update(e, "title")}>
+ this.onChange("title",v)}
+ fullWidth
+ autoFocus
+ aria-invalid={this.state.errors.title.length>0}
+ tabIndex={this.props.tabIndex}
+ />
+ {this.renderSubmitButtons("title")}
+ }
+
+
+ Type
+
+
+ {this.props.isReadOnly?
{this.state.type}
:
+
this.onChange("type",v)}
+ tabIndex={this.props.tabIndex}
+ data={this.props.noteTypes.map(this.renderNoteTypes)}
+ />}
+
+
+ Description
+
+
+ {this.props.isReadOnly?
{this.state.description}
:
+
this.update(e, "description")}>
+ this.onChange("description",v)}
+ multiLine
+ fullWidth
+ tabIndex={this.props.tabIndex}
+ />
+ {this.renderSubmitButtons("description")}
+ }
+
+ {!this.props.isReadOnly &&
+
+
+ Show in diagram
+
+
+ this.props.action.updateNote(this.props.note.id, { title: this.state.title, type: this.state.type, description: this.state.description, show: !this.props.note.show })}
+ tabIndex={this.props.tabIndex}
+ />
+
+
+ }
+
+ {linkedObjects}
+ {deleteButton}
+
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/notesManager/ManageNotes.js b/viscoll-app/src/components/notesManager/ManageNotes.js
new file mode 100644
index 00000000..ec218e25
--- /dev/null
+++ b/viscoll-app/src/components/notesManager/ManageNotes.js
@@ -0,0 +1,221 @@
+import React, {Component} from 'react';
+import RaisedButton from 'material-ui/RaisedButton';
+import EditNoteForm from "./EditNoteForm";
+import NewNoteForm from "./NewNoteForm";
+import Add from "material-ui/svg-icons/content/add"
+import {btnMd,btnBase} from "../../styles/button";
+
+/** Create New Note tab in the Note Manager */
+export default class ManageNotes extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ activeNote: null,
+ title: "",
+ type: "",
+ description: "",
+ };
+ }
+
+ /**
+ * Update state when user clicks on new note item
+ */
+ onItemChange = (activeNote) => {
+ this.setState({activeNote});
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (this.state.activeNote)
+ this.setState({activeNote: nextProps.Notes[this.state.activeNote.id]});
+ }
+
+ /**
+ * Mapping function to render a note thumbnail
+ */
+ renderList = (noteID) => {
+ const note = this.props.Notes[noteID];
+ return (
+ this.onItemChange(note)}
+ tabIndex={this.props.tabIndex}
+ key={noteID}
+ >
+
+
{note.title.length>80? note.title.substring(0,80) + "...": note.title}
+
{note.type}
+
+
+ );
+ }
+
+ /**
+ * Clear values in the input fields
+ */
+ reset = () => {
+ this.setState({
+ title: "",
+ type: "",
+ description: "",
+ });
+ }
+
+ deleteNote = (noteID) => {
+ this.props.action.deleteNote(noteID);
+ this.setState({activeNote: null});
+ }
+
+ updateNote = (noteID, note) => {
+ this.props.action.updateNote(noteID, note);
+ }
+
+ linkNote = (noteID, object) => {
+ this.props.action.linkNote(noteID, object);
+ }
+
+ unlinkNote = (noteID, object) => {
+ this.props.action.unlinkNote(noteID, object);
+ }
+
+ linkAndUnlinkNotes = (noteID, linkObjects, unlinkObjects) => {
+ this.props.action.linkAndUnlinkNotes(noteID, linkObjects, unlinkObjects);
+ }
+
+ getLinkedGroups = () => {
+ const groupsWithCurrentNote = Object.keys(this.props.Groups).filter((groupID) => {
+ return (this.props.Groups[groupID].notes.includes(this.state.activeNote.id))
+ });
+ return groupsWithCurrentNote.map((value) => {
+ const label = `Group ${this.props.groupIDs.indexOf(value)+1}`;
+ return {label, value};
+ });
+ }
+
+ getLinkedLeaves = () => {
+ const leafsWithCurrentNote = Object.keys(this.props.Leafs).filter((leafID) => {
+ return (this.props.Leafs[leafID].notes.includes(this.state.activeNote.id))
+ });
+ return leafsWithCurrentNote.map((value)=>{
+ const label = `Leaf ${this.props.leafIDs.indexOf(value)+1}`;
+ return {label, value};
+ });
+ }
+
+ getLinkedSides = () => {
+ const rectosWithCurrentNote = Object.keys(this.props.Rectos).filter((rectoID) => {
+ return (this.props.Rectos[rectoID].notes.includes(this.state.activeNote.id))
+ });
+ const versosWithCurrentNote = Object.keys(this.props.Versos).filter((versoID) => {
+ return (this.props.Versos[versoID].notes.includes(this.state.activeNote.id))
+ });
+ const sidesWithCurrentNote = [];
+ for (let value of rectosWithCurrentNote){
+ const leafOrder = this.props.leafIDs.indexOf(this.props.Rectos[value].parentID) + 1;
+ const folioNumber = this.props.Rectos[value].folio_number && this.props.Rectos[value].folio_number!==""? `(${this.props.Rectos[value].folio_number})`:"";
+ const pageNumber = this.props.Rectos[value].page_number && this.props.Rectos[value].page_number!==""? `(${this.props.Rectos[value].page_number})`:"";
+ const label = `L${leafOrder} Recto ${folioNumber} ${pageNumber}`;
+ sidesWithCurrentNote.push({label, value})
+ }
+ for (let value of versosWithCurrentNote){
+ const leafOrder = this.props.leafIDs.indexOf(this.props.Versos[value].parentID) + 1;
+ const folioNumber = this.props.Versos[value].folio_number && this.props.Versos[value].folio_number!==""? `(${this.props.Versos[value].folio_number})`:"";
+ const pageNumber = this.props.Versos[value].page_number && this.props.Versos[value].page_number!==""? `(${this.props.Versos[value].page_number})`:"";
+ const label = `L${leafOrder} Verso ${folioNumber} ${pageNumber}`;
+ sidesWithCurrentNote.push({label, value})
+ }
+ return sidesWithCurrentNote;
+ }
+
+ getRectosAndVersos = () => {
+ const size = Object.keys(this.props.Rectos).length;
+ let result = {};
+ for (let i=0; i
+ );
+ } else {
+ noteForm = (
+
+ );
+ }
+
+ return (
+
+
+
+
this.onItemChange(null)}
+ style={{width:"90%"}}
+ {...btnMd}
+ label="Create New Note"
+ labelStyle={{...btnBase().labelStyle}}
+ icon={ }
+ labelColor={"#ffffff"}
+ backgroundColor={"#566476"}
+ tabIndex={this.props.tabIndex}
+ >
+
+ {Object.keys(this.props.Notes).map(this.renderList)}
+
+
+
+ {noteForm}
+
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/notesManager/NewNoteForm.js b/viscoll-app/src/components/notesManager/NewNoteForm.js
new file mode 100644
index 00000000..61f3263b
--- /dev/null
+++ b/viscoll-app/src/components/notesManager/NewNoteForm.js
@@ -0,0 +1,193 @@
+import React, {Component} from 'react';
+import TextField from 'material-ui/TextField';
+import RaisedButton from 'material-ui/RaisedButton';
+import Checkbox from 'material-ui/Checkbox';
+import SelectField from '../global/SelectField';
+
+/** Create New Note tab in the Note Manager */
+export default class NewNoteForm extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ title: "",
+ type: "",
+ description: "",
+ show: false,
+ errors: {
+ title: "",
+ }
+ };
+ }
+
+ validateTitle = (title) => {
+ for (let noteID in this.props.Notes) {
+ const note = this.props.Notes[noteID];
+ if (note.title===title) {
+ this.setState({errors:{title:"This note title already exists."}});
+ return;
+ };
+ }
+ if (title.length>100) {
+ this.setState({errors:{title:"Title must be less than 100 characters."}});
+ } else if (title.length===0) {
+ this.setState({errors:{title:"Title must not be empty."}});
+ } else {
+ this.setState({errors:{title:""}});
+ }
+ }
+
+ onChange = (name, value) => {
+ this.setState({[name]:value, editing: {...this.state.editing, [name]: true}});
+ if (name==="title") this.validateTitle(value.trim());
+ if (name==="type") {
+ let editing = {
+ title: this.state.title,
+ type: value,
+ description: this.state.description,
+ show: this.state.show
+ }
+ if (this.props.note)
+ this.props.action.updateNote(this.props.note.id, editing);
+ }
+ }
+
+ create = () => {
+ let note = {
+ "project_id": this.props.projectID,
+ "title": this.state.title,
+ "type": this.state.type,
+ "description": this.state.description,
+ "show": this.state.show
+ }
+ this.props.action.addNote(note);
+ // Reset form
+ this.setState({
+ title: "",
+ type: "",
+ description: "",
+ show: false,
+ errors: {
+ title: "",
+ }
+ });
+ }
+
+ /**
+ * Clear values in the input fields if we are creating a new note
+ * Reset to original values if we are editing an existing note
+ */
+ reset = (props) => {
+ this.setState({
+ title: "",
+ type: "",
+ description: "",
+ errors: {
+ title:"",
+ type:"",
+ description:"",
+ show: false
+ }
+ });
+ }
+
+
+ renderNoteTypes = (name) => {
+ return {value:name, text:name};
+ }
+
+ renderMenuItem = (item, type, index) => {
+ let label = "";
+ if (type==="Side") {
+ let sideName = item.charAt(0)==="R" ? "Recto" : "Verso";
+ label = `Leaf ${Math.ceil((index-3)/2)}: ${type} ${sideName}`
+ } else{
+ const itemOrder = this.props[`${type.toLowerCase()}IDs`].indexOf(item.id)+1;
+ label = `${type} ${itemOrder}`
+ }
+ return (
+
+ {label}
+
+ );
+ }
+
+ render() {
+ let title = "Create a new note";
+ let createButtons =
+ this.reset()}
+ style={{width:120}}
+ />
+ this.create()}
+ disabled={this.state.errors.title!=="" || this.state.type==="" || this.state.title===""}
+ />
+
+
+ return (
+
+
{title}
+
+
+ Title
+
+
+ this.onChange("title",v)}
+ fullWidth
+ aria-invalid={this.state.errors.title.length>0}
+ />
+
+
+ Type
+
+
+ this.onChange("type",v)}
+ data={this.props.noteTypes.map(this.renderNoteTypes)}
+ >
+
+
+
+ Description
+
+
+ this.onChange("description",v)}
+ multiLine
+ fullWidth
+ />
+
+
+ Show in diagram
+
+
+ this.onChange("show",!this.state.show)}
+ />
+
+ {createButtons}
+
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/notesManager/NoteType.js b/viscoll-app/src/components/notesManager/NoteType.js
new file mode 100644
index 00000000..b6b66aba
--- /dev/null
+++ b/viscoll-app/src/components/notesManager/NoteType.js
@@ -0,0 +1,223 @@
+import React, {Component} from 'react';
+import DeleteConfirmation from './DeleteConfirmation';
+import RaisedButton from 'material-ui/RaisedButton';
+import TextField from 'material-ui/TextField';
+import IconSubmit from 'material-ui/svg-icons/action/done';
+import IconClear from 'material-ui/svg-icons/content/clear';
+
+/** Note type page to add, edit and delete note types */
+export default class NoteType extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ newType: "",
+ types: [...props.noteTypes],
+ editing: new Array(props.noteTypes.length).fill(false),
+ errorNewType: "",
+ errorTypes: new Array(props.noteTypes.length).fill(""),
+ lastSubmitted: -2,
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (this.state.types.length !== nextProps.noteTypes.length || this.props.noteTypes!==nextProps.noteTypes) {
+ this.setState({types: [...nextProps.noteTypes]});
+ this.resetEditing();
+ }
+ }
+
+ resetEditing = () => {
+ this.setState({editing: new Array(this.props.noteTypes.length).fill(false), newType: ""})
+ }
+
+ onNewTypeChange = (newType) => {
+ this.setState({newType}, ()=>{
+ if (!this.isValid(newType.trim())){
+ let errorMessage = `Note type with name ${newType} already exists in this project`;
+ if (newType.length===0)
+ errorMessage = "";
+ this.setState({errorNewType: errorMessage});
+ } else {
+ this.setState({errorNewType: ""});
+ }
+ });
+ }
+
+ onChange = (newType, index) => {
+ this.setType(index, newType);
+ this.setEditing(index, true);
+ if (!this.isValid(newType)){
+ let errorMessage = `Note type with name ${newType} already exists in this project`;
+ if (newType===this.props.noteTypes[index])
+ errorMessage = "";
+ if (newType.length===0)
+ errorMessage = `Note type cannot be blank`;
+ this.setError(index, errorMessage);
+ } else {
+ this.setError(index, "");
+ }
+ }
+
+ onUpdate = (event, index) => {
+ event.preventDefault();
+ const newNoteType = this.state.types[index];
+ if (newNoteType!==this.props.noteTypes[index]) {
+ this.setState({lastSubmitted: index});
+ let noteType = {
+ project_id: this.props.projectID,
+ type: newNoteType,
+ old_type: this.props.noteTypes[index],
+ }
+ this.props.action.updateNoteType(noteType);
+ }
+ this.setEditing(index, false);
+ }
+
+ isValid = (newType) => {
+ return !this.props.noteTypes.includes(newType) && newType.length!==0;
+ }
+
+ onDelete = (index) => {
+ let noteType = {
+ project_id: this.props.projectID,
+ type: this.state.types[index],
+ }
+ let updatedEditing = [...this.state.editing];
+ updatedEditing.splice(index, 1);
+ this.setState({editing: updatedEditing}, ()=>{
+ this.props.action.deleteNoteType(noteType);
+ });
+ }
+
+ onCreate = (event) => {
+ event.preventDefault();
+ let noteType = {
+ project_id: this.props.projectID,
+ type: this.state.newType.trim(),
+ }
+ this.props.action.createNoteType(noteType);
+ this.resetEditing();
+ this.setState({lastSubmitted: -1});
+ }
+
+ onCancelUpdate = (index) => {
+ this.setType(index, this.props.noteTypes[index]);
+ this.setError(index, "");
+ this.setEditing(index, false);
+ }
+
+ setType = (index, value) => {
+ let newTypes = [...this.state.types];
+ newTypes[index]=value;
+ this.setState({types: newTypes});
+ }
+
+ setEditing = (index, value) => {
+ let newEditing = [...this.state.editing];
+ newEditing[index]=value;
+ this.setState({editing: newEditing});
+ }
+
+ setError = (index, value) => {
+ let newErrors = [...this.state.errorTypes];
+ newErrors[index] = value;
+ this.setState({errorTypes: newErrors});
+ }
+
+ renderSubmitButtons = (index) => {
+ if (this.state.editing[index]) {
+ return (
+
+ }
+ style={{minWidth:"60px",marginLeft:"5px"}}
+ name="submit"
+ type="submit"
+ disabled={!this.isValid(this.state.types[index])}
+ />
+ }
+ style={{minWidth:"60px",marginLeft:"5px"}}
+ onClick={(e)=>this.onCancelUpdate(index)}
+ />
+
+ )
+ } else {
+ return "";
+ }
+ }
+
+ renderNoteType = (noteType, index) => {
+ return (
+
+
this.onUpdate(e, index)}>
+ this.onChange(v,index)}
+ errorText={this.state.errorTypes[index]}
+ aria-invalid={this.state.errorTypes[index]!==undefined && this.state.errorTypes[index].length>0}
+ style={{width:"75%"}}
+ tabIndex={this.props.tabIndex}
+ />
+ { noteType==="Unknown"? "" :
+
+ }
+ {this.renderSubmitButtons(index)}
+
+
+ );
+ }
+
+ filterNoteType = (object, index) => {
+ return object.key!=="type_0";
+ }
+
+ render() {
+ return
+
Add a new note type
+
this.onCreate(e)}>
+
+
+ this.onNewTypeChange(v)}
+ errorText={this.state.errorNewType}
+ aria-invalid={this.state.errorNewType.length>0}
+ style={{width: 300}}
+ tabIndex={this.props.tabIndex}
+ />
+
+
+
+
+
+
+
Your note types
+
+ {this.props.noteTypes.map(this.renderNoteType).filter(this.filterNoteType)}
+
+
;
+ }
+}
diff --git a/viscoll-app/src/components/notesManager/NotesFilter.js b/viscoll-app/src/components/notesManager/NotesFilter.js
new file mode 100644
index 00000000..35633359
--- /dev/null
+++ b/viscoll-app/src/components/notesManager/NotesFilter.js
@@ -0,0 +1,66 @@
+import React, {Component} from 'react';
+import TextField from 'material-ui/TextField';
+import Checkbox from 'material-ui/Checkbox';
+import {floatFieldLight} from '../../styles/textfield';
+
+/** Filter notes */
+class NotesFilter extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ value: "",
+ }
+ }
+
+ render() {
+ return (
+
+
+ {this.setState({value});this.props.onValueChange(e,value)}}
+ style={window.innerWidth<=890?{marginLeft:10,marginRight:10, width:150}:{marginLeft:10,marginRight:10, width:200}}
+ value={this.state.value}
+ {...floatFieldLight}
+ tabIndex={this.props.tabIndex}
+ />
+
+
0)?"searchOptions active":"searchOptions"}>
+ this.props.onTypeChange("title", !this.props.filterTypes["title"])}
+ tabIndex={this.props.tabIndex}
+ />
+ this.props.onTypeChange("type", !this.props.filterTypes["type"])}
+ tabIndex={this.props.tabIndex}
+ />
+ this.props.onTypeChange("description", !this.props.filterTypes["description"])}
+ tabIndex={this.props.tabIndex}
+ />
+
+
+ );
+ }
+}
+
+
+export default NotesFilter;
diff --git a/viscoll-app/src/components/topbar/UserProfileForm.js b/viscoll-app/src/components/topbar/UserProfileForm.js
new file mode 100644
index 00000000..18062df2
--- /dev/null
+++ b/viscoll-app/src/components/topbar/UserProfileForm.js
@@ -0,0 +1,442 @@
+import React from 'react';
+import {floatFieldLight} from '../../styles/textfield';
+import TextField from 'material-ui/TextField';
+import Dialog from 'material-ui/Dialog';
+import RaisedButton from 'material-ui/RaisedButton';
+import FlatButton from 'material-ui/FlatButton';
+import IconSubmit from 'material-ui/svg-icons/action/done';
+import IconClear from 'material-ui/svg-icons/content/clear';
+
+/**
+ * Form to edit user account information
+ */
+class UserProfileForm extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ name: props.name,
+ email: props.email,
+ emailMessagePending: false,
+ emailMessage: false,
+ currentPassword: "",
+ newPassword: "",
+ newPasswordConfirm: "",
+ errors: {
+ name: "",
+ email: "",
+ newPassword: "",
+ newPasswordConfirm: "",
+ currentPassword: ""
+ },
+ editing: {
+ name: false,
+ email: false,
+ newPassword: false,
+ newPasswordConfirm: false,
+ currentPassword: false,
+ },
+ deleteDialog: false,
+ unsavedDialog: false,
+ };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.setState({
+ name: nextProps.name,
+ errors: {
+ ...this.state.errors,
+ currentPassword: nextProps.currentPasswordError.toString(),
+ email: nextProps.emailTakenError.toString(),
+ },
+ currentPassword: "",
+ newPassword: "",
+ newPasswordConfirm: "",
+ unsavedDialog: false,
+ changed: false,
+ }, () => {
+ if (this.state.emailMessagePending && this.state.errors.email === "") {
+ this.setState({emailMessage:true,emailMessagePending:false});
+ }
+ });
+ }
+
+ /**
+ * Validate user input and display appropriate error message
+ */
+ checkValidationError = () => {
+ const errors = {};
+ // Validate password
+ if (this.state.editing.currentPassword || this.state.newPassword) {
+ if (!this.state.currentPassword) {
+ errors.currentPassword = "Current password cannot be blank";
+ } else {
+ errors.currentPassword = "";
+ }
+ if (!this.state.newPasswordConfirm) {
+ errors.newPasswordConfirm = "New password confirmation cannot be blank";
+ } else if (this.state.newPassword !== this.state.newPasswordConfirm) {
+ errors.newPasswordConfirm = "Password confirmation does not match new password";
+ } else {
+ errors.newPasswordConfirm = "";
+ }
+ if (!this.state.newPassword) {
+ errors.newPassword = "New password cannot be blank";
+ } else {
+ errors.newPassword = "";
+ }
+ }
+ // Validate name
+ if (this.state.editing.name && !this.state.name) {
+ errors.name = "Name cannot be blank";
+ } else {
+ errors.name = "";
+ }
+ // Validate email
+ if (this.state.editing.email) {
+ let re = /[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}/igm;
+ if (!this.state.email) {
+ errors.email = "Email cannot be blank";
+ } else if (!re.test(this.state.email)) {
+ errors.email = "Invalid email address";
+ } else {
+ errors.email = "";
+ }
+ }
+ return errors;
+ }
+
+ ifErrorsExist = (type) => {
+ return (this.state.errors[type]!=="");
+ }
+
+ /**
+ * Return true if any input fields have been changed and not saved
+ */
+ isEditing = () => {
+ return (this.state.editing.name || this.state.editing.email || this.state.editing.currentPassword || this.state.editing.newPassword || this.state.editing.newPasswordConfirm);
+ }
+
+ onInputChange = (newValue, type) => {
+ this.setState({[type]: newValue, editing: {...this.state.editing, [type]:true}}, () => {
+ this.setState({errors: {...this.state.errors, ...this.checkValidationError()}})
+ });
+ }
+
+ handleDeleteDialogToggle = (deleteDialog=false) => {
+ this.setState({ deleteDialog });
+ };
+
+ /**
+ * Show ignore changes dialog or close user profile, depending on parameter
+ */
+ handleUserProfileClose = (ignoreChanges=false) => {
+ // Check for any unsaved changes before closing and show the warning dialog.
+ if (this.isEditing() && !ignoreChanges){
+ this.setState({ unsavedDialog: true });
+ }
+ else {
+ this.setState({
+ name: this.props.name,
+ email: this.props.email,
+ currentPassword: "",
+ newPassword: "",
+ newPasswordConfirm: "",
+ errors: {
+ name: "",
+ email: "",
+ newPassword: "",
+ newPasswordConfirm: "",
+ currentPassword: ""
+ },
+ editing: {
+ name: false,
+ email: false,
+ newPassword: false,
+ newPasswordConfirm: false,
+ currentPassword: false,
+ }
+ }, () => this.props.toggleUserProfile());
+
+ }
+ }
+
+ handleUserAccountDelete = () => {
+ this.props.toggleUserProfile(false);
+ this.props.handleUserAccountDelete();
+ }
+
+ handleUnsavedDialogToggle = (unsavedDialog=false) => {
+ this.setState({ unsavedDialog });
+ };
+
+ /**
+ * Reset input field to original value
+ */
+ handleCancelUpdate = (type) => {
+ if (type==="currentPassword") {
+ this.setState({
+ currentPassword:"",
+ newPassword:"",
+ newPasswordConfirm:"",
+ editing: {
+ ...this.state.editing,
+ currentPassword: false,
+ newPassword: false,
+ newPasswordConfirm: false,
+ },
+ errors: {
+ ...this.state.errors,
+ currentPassword:"",
+ newPassword:"",
+ newPasswordConfirm:"",
+ }
+ });
+ } else {
+ this.setState({
+ [type]: this.props[type],
+ editing: {
+ ...this.state.editing,
+ [type]: false,
+ },
+ errors: {
+ ...this.state.errors,
+ [type]: "",
+ }
+ })
+ }
+ }
+
+ submitButtons = (type) => {
+ if (this.state.editing[type]) {
+ return (
+
+ }
+ style={{minWidth:"60px",marginLeft:"5px"}}
+ name="submit"
+ type="submit"
+ disabled={type==="currentPassword"? (this.ifErrorsExist("currentPassword")||this.ifErrorsExist("newPassword")||this.ifErrorsExist("newPasswordConfirm")) : this.ifErrorsExist(type) }
+ onClick={() => this.handleUserUpdate(null, type)}
+ />
+ }
+ style={{minWidth:"60px",marginLeft:"5px"}}
+ onClick={() => this.handleCancelUpdate(type)}
+ />
+
+ )
+ } else {
+ return "";
+ }
+ }
+
+ handleUserUpdate = (event, type) => {
+ if (event) event.preventDefault();
+ let updatedUser = {
+ user: {
+ [type]: this.state[type],
+ }
+ };
+ if (this.state.currentPassword!=="") {
+ updatedUser = {user: {
+ current_password: this.state.currentPassword,
+ password: this.state.newPassword
+ }};
+ }
+ this.props.handleUserProfileUpdate(updatedUser);
+ let types = {};
+ if (type==="password") {
+ types = {currentPassword: false, newPassword: false, newPasswordConfirm: false};
+ } else {
+ types = {[type]: false};
+ }
+ this.setState({editing:{...this.state.editing, ...types}}, ()=> {
+ if (type==="email") {
+ this.setState({emailMessagePending:true});
+ }
+ });
+ }
+
+ render() {
+ const userProfileActions = [
+ this.handleUserProfileClose()}
+ />,
+ this.handleDeleteDialogToggle(true)}
+ style={{float: 'left'}}
+ />
+ ];
+
+ const deleteActions = [
+ this.handleDeleteDialogToggle()}
+ />,
+ this.handleUserAccountDelete()}
+ />,
+ ];
+
+ const unsaveActions = [
+ this.handleUnsavedDialogToggle()}
+
+ />,
+ this.handleUserProfileClose(true)}
+ />,
+ ];
+
+ const emailConfirmActions = [
+ this.setState({emailMessage:false})}
+ keyboardFocused
+ />
+ ];
+
+ let nameField = (
+
+
this.handleUserUpdate(e, "name")}>
+ this.onInputChange(v, "name")}
+ name="name"
+ floatingLabelText="Name"
+ floatingLabelStyle={{color:"#6e6e6e"}}
+ errorText={this.state.errors.name}
+ value={this.state.name}
+ fullWidth
+ />
+ {this.submitButtons("name")}
+
+
+ );
+
+ let emailField = (
+
+
this.handleUserUpdate(e, "email")}>
+ this.onInputChange(v, "email")}
+ name="email"
+ floatingLabelText="E-mail"
+ floatingLabelStyle={{color:"#6e6e6e"}}
+ errorText={this.state.errors.email}
+ value={this.state.email}
+ fullWidth
+ />
+ {this.submitButtons("email")}
+
+
+ );
+
+ let password = (
+
+
this.handleUserUpdate(e, "password")}>
+ this.onInputChange(v, "currentPassword")}
+ name="currentPassword"
+ floatingLabelText="Current Password"
+ {...floatFieldLight}
+ errorText={this.state.errors.currentPassword}
+ type="password"
+ value={this.state.currentPassword}
+ fullWidth
+ />
+ this.onInputChange(v, "newPassword")}
+ name="newPassword"
+ floatingLabelText="New Password"
+ {...floatFieldLight}
+ errorText={this.state.errors.newPassword}
+ type="password"
+ value={this.state.newPassword}
+ fullWidth
+ />
+ this.onInputChange(v, "newPasswordConfirm")}
+ name="newPasswordConfirm"
+ floatingLabelText="Confirm New Password"
+ {...floatFieldLight}
+ errorText={this.state.errors.newPasswordConfirm}
+ type="password"
+ value={this.state.newPasswordConfirm}
+ fullWidth
+ />
+ {this.submitButtons("currentPassword")}
+
+
+ );
+
+
+ return (
+
+
+
+ {nameField}
+
+ {emailField}
+
+ Update your password
+ {password}
+
+
+
+
+
+
+
+ A confirmation link has been sent to your new email address.
+
+ Your current email will still remain active until the new email is activated.
+
+
+
+ );
+ }
+}
+
+export default UserProfileForm;
diff --git a/viscoll-app/src/containers/App.js b/viscoll-app/src/containers/App.js
new file mode 100644
index 00000000..4871c0b0
--- /dev/null
+++ b/viscoll-app/src/containers/App.js
@@ -0,0 +1,67 @@
+import React, { Component } from 'react';
+import injectTapEventPlugin from 'react-tap-event-plugin';
+import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
+import getMuiTheme from 'material-ui/styles/getMuiTheme';
+import light from '../styles/light';
+import '../styles/App.css';
+import Authentication from './Authentication';
+import Dashboard from './Dashboard';
+import Project from './Project';
+import ProjectViewOnly from './ProjectViewOnly'
+import {persistStore} from 'redux-persist'
+import store from "../store/store";
+import {Provider} from "react-redux";
+import AppLoadingScreen from "../components/global/AppLoadingScreen";
+import localForage from 'localforage'
+import PageNotFound from '../components/global/PageNotFound';
+import {
+ BrowserRouter as Router,
+ Route,
+ Switch
+} from 'react-router-dom'
+
+injectTapEventPlugin();
+
+/** Main app */
+class App extends Component {
+
+ constructor() {
+ super()
+ this.state = { rehydrated: false }
+ }
+
+ componentWillMount(){
+ persistStore(store, {storage: localForage, whitelist:['user']}, () => {
+ setTimeout(()=>{this.setState({ rehydrated: true })}, 500);
+ })
+ }
+
+ render() {
+ if (!this.state.rehydrated) {
+ return (
+
+
+
+ )
+ }
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default App;
diff --git a/viscoll-app/src/containers/Authentication.js b/viscoll-app/src/containers/Authentication.js
new file mode 100644
index 00000000..ae9aecdc
--- /dev/null
+++ b/viscoll-app/src/containers/Authentication.js
@@ -0,0 +1,225 @@
+import React, { Component } from 'react';
+import RaisedButton from 'material-ui/RaisedButton';
+import imgCollation from '../assets/collation.png';
+import imgLogo from '../assets/logo_white.png';
+import Register from '../components/authentication/Register';
+import Login from '../components/authentication/Login';
+import ResetPassword from '../components/authentication/ResetPassword';
+import ResetPasswordRequest from '../components/authentication/ResetPasswordRequest';
+import ResendConfirmation from '../components/authentication/ResendConfirmation';
+import {btnLg} from '../styles/button';
+import { connect } from "react-redux";
+import NetworkErrorScreen from "../components/global/NetworkErrorScreen";
+import {
+ login,
+ register,
+ confirm,
+ resetPasswordRequest,
+ resetPassword,
+ logout,
+ resendConfirmation,
+} from "../actions/backend/userActions";
+
+
+/** Landing page of the app. Contain register, login and password reset forms. */
+class Landing extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ register: false,
+ login: false,
+ reset: false,
+ resetRequest: false,
+ reset_token: "",
+ message: "",
+ resendConfirmation: false,
+ resendConfirmationSuccess: false,
+ }
+ }
+
+ toggleRegister = () => {
+ this.setState({register: !this.state.register, message: ""});
+ }
+
+ toggleLogin = () => {
+ this.setState({login: !this.state.login, message: ""});
+ }
+
+ toggleResetRequest = () => {
+ this.setState({resetRequest: !this.state.resetRequest, message: ""});
+ }
+
+ tapCancel = () => {
+ this.setState({login: false, register: false, resetRequest: false, resendConfirmation: false, message: ""});
+ }
+
+ handleResetPasswordSuccess = () => {
+ this.setState({reset: false, message: "Your password has been successfully updated. Go ahead and login."});
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.user.errors.confirmation.length>0) {
+ this.setState({resendConfirmation: true});
+ }
+ if (nextProps.notification.includes("Successfully confirmed your account")) {
+ this.setState({message: nextProps.notification, resendConfirmationSuccess: true});
+ }
+ }
+
+ componentDidMount() {
+ const token = this.props.location.search.split('=')[1];
+ if (token) {
+ if (this.props.location.pathname.includes("confirmation")){
+ this.props.confirmUser(token);
+ if (this.props.user.authenticated) this.props.logoutUser();
+ } else if (this.props.location.pathname.includes("password")) {
+ this.setState({ reset: true, reset_token: token });
+ }
+ } else {
+ if (this.props.user.authenticated) {
+ this.props.history.push('/dashboard');
+ }
+ }
+ }
+
+ render() {
+ const message = this.state.message? {this.state.message}
: "";
+ let resetPassword = "";
+ let resetPasswordRequest = "";
+ let resendConfirmation = "";
+
+ let register = (
+
+ this.toggleRegister()}
+ label="Create account"
+ {...btnLg}
+ />
+
+ );
+ let login = (
+
+ this.toggleLogin()}
+ label="Login"
+ {...btnLg}
+ />
+
+ );
+
+ if (this.state.register) {
+ register = ;
+ login = "";
+ } else if (this.state.resetRequest) {
+ login = "";
+ register = "";
+ resetPassword = "";
+ resetPasswordRequest =
+ } else if (this.state.login) {
+ register = "";
+ login = ;
+ } else if (this.state.reset) {
+ login = "";
+ register = "";
+ resetPassword = ;
+
+ } else if (this.state.resendConfirmation) {
+ login = "";
+ register = "";
+ resendConfirmation =
+ } else if (this.state.resendConfirmationSuccess) {
+ register = "";
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ {message}
+ {register}
+ {login}
+ {resetPasswordRequest}
+ {resetPassword}
+ {resendConfirmation}
+
+
+
+
+ Developed by the University of Toronto Libraries
+ In collaboration with Dot Porter and Alberto Campagnolo
+
+
+
+
+ );
+ }
+}
+
+
+const mapStateToProps = (state) => {
+ return {
+ user: state.user,
+ notification: 'notification' in state.user? state.user.notification : state.global.notification
+ };
+};
+
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ logoutUser: () => {
+ dispatch(logout());
+ },
+ loginUser: (user) => {
+ dispatch(login(user));
+ },
+ registerUser: (user) => {
+ dispatch(register(user));
+ },
+ confirmUser: (confirmation_token) => {
+ dispatch(confirm(confirmation_token));
+ },
+ resetPasswordRequest: (email) => {
+ dispatch(resetPasswordRequest(email));
+ },
+ resetPassword: (reset_token, password) => {
+ dispatch(resetPassword(reset_token, password));
+ },
+ resendConfirmation: (email) => {
+ dispatch(resendConfirmation(email));
+ },
+ };
+};
+
+
+export default connect(mapStateToProps, mapDispatchToProps)(Landing);
diff --git a/viscoll-app/src/containers/CollationManager.js b/viscoll-app/src/containers/CollationManager.js
new file mode 100644
index 00000000..eaa01cf1
--- /dev/null
+++ b/viscoll-app/src/containers/CollationManager.js
@@ -0,0 +1,810 @@
+import React, {Component} from 'react';
+import {Tabs, Tab} from 'material-ui/Tabs';
+import FlatButton from 'material-ui/FlatButton';
+import TabularMode from '../components/collationManager/TabularMode';
+import VisualMode from '../components/collationManager/VisualMode';
+import ExportMode from '../components/collationManager/ExportMode';
+import ViewingMode from '../components/collationManager/ViewingMode';
+import Panel from '../components/global/Panel';
+import Export from '../components/export/Export';
+import InfoBox from './InfoBox';
+import Filter from './Filter';
+import TopBar from "./TopBar";
+import topbarStyle from "../styles/topbar";
+import IconClear from 'material-ui/svg-icons/content/clear';
+import IconButton from 'material-ui/IconButton';
+import {RadioButton, RadioButtonGroup} from 'material-ui/RadioButton';
+import { connect } from "react-redux";
+import { changeViewMode,
+ handleObjectClick,
+ handleObjectPress,
+ changeManagerMode,
+ toggleFilterPanel,
+
+ toggleVisualizationDrawing,
+} from "../actions/backend/interactionActions";
+import {
+ updateFilterSelection,
+ reapplyFilterProject,
+} from '../actions/backend/filterActions';
+import {
+ updateGroup,
+
+} from '../actions/backend/groupActions';
+import {
+ updateNote,
+ deleteNote,
+ linkNote,
+ unlinkNote,
+} from '../actions/backend/noteActions';
+import {
+ loadProject,
+ exportProject,
+ updateProject
+} from "../actions/backend/projectActions";
+import fileDownload from 'js-file-download';
+import NoteDialog from '../components/collationManager/dialog/NoteDialog';
+import {radioBtnDark} from "../styles/button";
+import ManagersPanel from '../components/global/ManagersPanel';
+
+/** Container for `TabularMode`, `VisualMode`, `InfoBox`, `TopBar`, `LoadingScreen`, and `Notification`. This container has the project sidebar embedded directly. */
+class CollationManager extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+
+ windowWidth: window.innerWidth,
+ contentStyle: {
+ transition: 'top 450ms cubic-bezier(0.23, 1, 0.32, 1)',
+ top: 40,
+ },
+ infoboxStyle: {
+ maxHeight: "90%"
+ },
+ export: {
+ open: false,
+ label: "",
+ type: "",
+ exportCols: 1,
+ exportNotes: true
+ },
+ selectAll: "",
+ leftSideBarOpen: true,
+ showTips: props.preferences.showTips,
+ imageViewerEnabled: false,
+ activeNote: null,
+ tipIndex: 0,
+ };
+ }
+
+ componentDidMount() {
+ window.addEventListener('resize', this.resizeHandler);
+ if (this.props.filterPanelOpen){
+ let filterContainer = document.getElementById('filterContainer');
+ if (filterContainer) {
+ let filterPanelHeight = filterContainer.offsetHeight;
+ this.filterHeightChange(filterPanelHeight);
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener("resize", this.resizeHandler);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const newSelectedObjects = nextProps.selectedObjects!==this.props.selectedObjects;
+ const differentNumOfSelectedObjects = this.state.selectAll && nextProps.selectedObjects.members.length!==Object.keys(this.props.project[this.state.selectAll]).length;
+ const differentTypeOfSelectedObjects = this.state.selectAll!==(nextProps.selectedObjects.type+"s");
+ if (this.state.selectAll && newSelectedObjects && (differentTypeOfSelectedObjects || differentNumOfSelectedObjects)) {
+ this.setState({selectAll:""});
+ }
+ if (nextProps.selectedObjects.type && Object.keys(nextProps.project[nextProps.selectedObjects.type+"s"]).length===nextProps.selectedObjects.members.length) {
+ this.setState({selectAll:nextProps.selectedObjects.type+"s"});
+ }
+
+ // Update active note
+ const commonNotes = this.getCommonNotes(nextProps);
+ if (this.state.activeNote!==null && commonNotes.findIndex((noteID)=>noteID===this.state.activeNote.id)<0 && !this.state.clickedFromDiagram) {
+ // Hide note when note was clicked from infobox and removed from selected object
+ this.setState({activeNote:null});
+ } else if (this.state.activeNote) {
+ // Update note object
+ this.setState({activeNote: nextProps.project.Notes[this.state.activeNote.id]})
+ }
+ }
+
+ resizeHandler = () => {
+ this.setState({windowWidth:window.innerWidth});
+ }
+
+ toggleFilterDrawer = () => {
+ this.props.toggleFilterPanel(!this.props.filterPanelOpen);
+ let filterPanelHeight = document.getElementById('filterContainer').offsetHeight;
+ if (this.props.filterPanelOpen) {
+ filterPanelHeight = 0;
+ }
+ this.filterHeightChange(filterPanelHeight);
+ }
+
+ handleObjectPress = (object, event) => {
+ this.props.handleObjectPress(this.props.selectedObjects, object, event);
+ }
+
+ handleObjectClick = (object, event) => {
+ event.stopPropagation();
+ this.props.handleObjectClick(
+ this.props.selectedObjects,
+ object,
+ event,
+ this.props.project.groupIDs,
+ this.props.project.leafIDs,
+ this.props.project.rectoIDs,
+ this.props.project.versoIDs,
+ );
+ }
+
+ handleViewModeChange = (value) => {
+ if (value==="VIEWING") {
+ this.setState({leftSideBarOpen: true, imageViewerEnabled: false}, ()=>this.props.changeViewMode(value));
+ } else if (value!=="VIEWING" && this.state.leftSideBarOpen===false) {
+ this.setState({leftSideBarOpen: true}, ()=>this.props.changeViewMode(value));
+ } else {
+ this.props.changeViewMode(value);
+ }
+ }
+
+ filterHeightChange = (value) => {
+ let infoboxHeight = "90%";
+ if (value>0) infoboxHeight = window.innerHeight - value - 56 - 30 + "px";
+ this.setState({
+ contentStyle:{...this.state.contentStyle, top:value+56},
+ infoBoxStyle: {maxHeight: infoboxHeight},
+ });
+ }
+
+ updateGroup = (groupID, group) => { this.props.updateGroup(groupID, group, this.props); }
+
+ closeTip = () => {
+ const project = {
+ preferences: {
+ showTips: false
+ }
+ };
+ this.props.hideProjectTip();
+ this.props.updateProject(project, this.props.project.id);
+ }
+
+ handleSelection = (selection) => {
+ this.props.updateFilterSelection(
+ selection,
+ this.props.project.groupIDs,
+ this.props.project.leafIDs,
+ this.props.project.rectoIDs,
+ this.props.project.versoIDs
+ );
+ }
+
+ handleExportToggle = (open, type, label, exportCols, exportNotes) => {
+ this.setState({export: {open, type, label, exportCols, exportNotes}}, ()=>{
+ if (this.state.export.open && type!=="png" && type !== "share")
+ this.props.exportProject(this.props.project.id, type);
+ });
+ this.props.togglePopUp(open);
+ };
+
+ showCopyToClipboardNotification = () => {
+ this.props.showCopyToClipboardNotification();
+ }
+
+ numRootGroups = () => {
+ let numRootGroups = 0;
+ for (let [, [, group]] of Object.entries(this.props.project.Groups).entries()) {
+ if (group.nestLevel === 1) {
+ numRootGroups++;
+ }
+ }
+ return numRootGroups;
+ }
+
+ combineDiagram = () => {
+ // Resize export canvas
+ let finalCanvas = document.getElementById("exportCanvas");
+ let canvasIDs = Array(this.numRootGroups()).fill().map((v,i)=>'canvas'+i);
+ let canvases = [];
+ for (let id of canvasIDs) {
+ canvases.push(document.getElementById(id));
+ }
+ let rows = [];
+ let numCols = parseInt(this.state.export.exportCols);
+ for (let i=0; i {
+ let canvas = document.getElementById("exportCanvas");
+ canvas.toBlob((blob)=>{
+ const filename = this.props.project.title.replace(/\s/g, "_");
+ fileDownload(blob, `${filename}.PNG`)
+ });
+ }
+
+ toggleImageViewer = () => {
+ this.setState({imageViewerEnabled: !this.state.imageViewerEnabled, leftSideBarOpen: !this.state.leftSideBarOpen});
+ }
+
+ closeNoteDialog = () => {
+ this.setState({activeNote: null, clickedFromDiagram: false}, ()=>this.props.togglePopUp(false));
+ }
+ openNoteDialog = (note, clickedFromDiagram=false) => {
+ this.setState({activeNote: note, clickedFromDiagram},()=>this.props.togglePopUp(true));
+ }
+
+ getCommonNotes = (props=this.props) => {
+ // Find the common notes of all currently selected objects
+ const memberType = props.selectedObjects.type;
+ const members = props.selectedObjects.members;
+ let notes = [];
+ if (members.length>0) {
+ notes = props.project[memberType+"s"][members[0]].notes;
+ for (let id of members) {
+ notes = this.intersect(notes, props.project[memberType+"s"][id].notes);
+ }
+ }
+ return notes;
+ }
+
+ /**
+ * Returns items in common
+ */
+ intersect = (list1, list2) => {
+ if (list1.length >= list2.length)
+ return list1.filter((id1)=>{return list2.includes(id1)});
+ else
+ return list2.filter((id1)=>{return list1.includes(id1)});
+ }
+
+ updateNote = (noteID, note) => {
+ this.props.updateNote(noteID, note, this.props.project.id, this.props.collationManager.filters);
+ }
+
+ linkDialogNote = (noteID, objects) => {
+ this.props.linkNote(noteID, objects, this.props.project.id, this.props.collationManager.filters);
+ }
+
+ linkAndUnlinkNotes = (noteID, linkObjects, unlinkObjects) => {
+ this.props.linkAndUnlinkNotes(noteID, linkObjects, unlinkObjects, this.props.project.id, this.props.collationManager.filters);
+ }
+
+ unlinkDialogNote = (noteID, objects) => {
+ this.props.unlinkNote(noteID, objects, this.props.project.id, this.props.collationManager.filters);
+ }
+
+ deleteNote = (noteID) => {
+ this.closeNoteDialog();
+ this.props.deleteNote(noteID, this.props.project.id, this.props.collationManager.filters);
+ }
+
+ renderRadioButton = (value, label) => {
+ return (
+
+ )
+ }
+
+ setExport = (name, value) => {
+ this.setState({export: {...this.state.export, [name]: value}});
+ }
+
+ render() {
+ const containerStyle = {top: 85, right: "2%", height: 'inherit', maxHeight: '80%', width: '28%'};
+ if (!this.state.leftSideBarOpen) {
+ containerStyle["width"] = "30%";
+ }
+
+ const topbar = (
+
+
+
+
+
+
+
+ );
+
+ const singleEditTip = 'Hold the CTRL key (or Command key for Mac users) to select multiple groups/leaves/sides. Hold SHIFT key to select a range of groups/leaves/sides.';
+ const batchEditTip = 'You are in batch edit mode. To leave this mode, click on any group/leaf/side without holding down any keys.';
+ const tip = [
+ this.props.selectedObjects.members.length>1 ? batchEditTip : singleEditTip,
+ "Generate folio/page numbers by selecting multiple leaves and clicking on the 'Generate folio/page numbers' button in the infobox on the right.",
+ "View a zoomed out version of the collation diagram by selecting PNG export in the Export section of this sidebar.",
+ "Undo an action with CTRL+Z (or CMD+Z for Mac users), and redo an action with CTRL+Y (or CMD+Y for Mac users).",
+ ];
+ let tipsDiv;
+ if (this.props.managerMode==="collationManager" && this.props.preferences.showTips===true) {
+ tipsDiv =
+
+
+
+
+
+
+
+
TIP: {tip[this.state.tipIndex]}
+
+ this.setState({tipIndex:((this.state.tipIndex+1)%tip.length)})}
+ tabIndex={this.props.popUpActive?-1:0}
+ style={{color:"#4ED6CB", background:"none", border:0}}
+ >
+ Next tip ❯
+
+
+
+
+ }
+
+ const selectionRadioGroup = (
+ this.setState({selectAll: v}, ()=>{this.handleSelection(v+"_all")})}
+ >
+ {this.renderRadioButton("Groups", "Select All Groups")}
+ {this.renderRadioButton("Leafs", "Select All Leaves")}
+ {this.renderRadioButton("Rectos", "Select All Rectos")}
+ {this.renderRadioButton("Versos", "Select All Versos")}
+
+ );
+
+ const exportDialog = (
+ {this.combineDiagram();this.handleDownloadCollationDiagram();}}
+ numRootGroups={this.numRootGroups()}
+ setExport={this.setExport}
+ exportCols={this.state.export.exportCols}
+ exportNotes={this.state.export.exportNotes}
+ />
+ );
+
+ let sidebarClasses = "sidebar";
+ if (!this.state.leftSideBarOpen) sidebarClasses += " hidden";
+ if (this.props.popUpActive) sidebarClasses += " lowerZIndex";
+
+ const sidebar = (
+
+
+ {tipsDiv}
+ { this.props.collationManager.viewMode !== "VIEWING"?
+
: "" }
+
+ {selectionRadioGroup}
+ this.setState({selectAll:""},this.handleSelection(""))}
+ secondary
+ fullWidth
+ style={this.state.selectAll===""?{display:"none"}:{}}
+ tabIndex={this.props.popUpActive?-1:0}
+ labelStyle={this.state.windowWidth<=768?{fontSize:"0.75em",padding:2}:{}}
+ />
+
+
+
+ {this.handleExportToggle(true, "share", "Share this project", 1, true)}}
+ tabIndex={this.props.popUpActive?-1:0}
+ />
+
+
+
+ Export Collation Data
+
+ this.handleExportToggle(true, "json", "JSON")}
+ tabIndex={this.props.popUpActive?-1:0}
+ disabled={this.props.project.leafIDs.length===0}
+ />
+ this.handleExportToggle(true, "xml", "XML")}
+ tabIndex={this.props.popUpActive?-1:0}
+ disabled={this.props.project.leafIDs.length===0}
+ />
+
+ Export Collation Diagram
+
+ {this.handleExportToggle(true, "png", "PNG", 1, true)}}
+ disabled={this.props.collationManager.viewMode==="TABULAR"||this.props.project.leafIDs.length===0}
+ tabIndex={this.props.popUpActive?-1:0}
+ />
+
+
+
+ );
+
+ const infobox = (
+
+
+
+ )
+
+ let workspace =
;
+ if (this.props.project.groupIDs.length>0){
+ if (this.props.collationManager.viewMode==="TABULAR") {
+ workspace = (
+
+
+
{this.props.project.title}
+
+
+ {infobox}
+ {exportDialog}
+
+ );
+ } else if (this.props.collationManager.viewMode==="VISUAL") {
+ workspace = (
+
+
+
{this.props.project.title}
+ this.openNoteDialog(note, true)}
+ tabIndex={this.props.popUpActive?-1:0}
+ />
+
+ {infobox}
+ {exportDialog}
+
+ );
+ } else {
+ workspace = (
+
+
+
{this.props.project.title}
+
+
+ {infobox}
+ {exportDialog}
+
+ );
+ }
+ }
+ if (this.props.project.groupIDs.length===0 && !this.props.loading){
+ workspace = (
+
+
+
+ It looks like you have an empty project. Add a new Group to start collating.
+
+
+ {infobox}
+
+ );
+ }
+ if (this.state.export.open && this.state.export.label==="PNG") {
+ workspace = (
+
+
+
{this.props.project.title}
+ this.openNoteDialog(note, true)}
+ tabIndex={this.props.popUpActive?-1:0}
+ showNotes={this.state.export.exportNotes}
+ />
+
+ {infobox}
+ {exportDialog}
+
+ );
+ }
+ return (
+
+ {topbar}
+ {sidebar}
+
+ {workspace}
+
+
+ );
+ }
+}
+
+
+const mapStateToProps = (state) => {
+ return {
+ user: state.user,
+ project: state.active.project,
+ preferences: state.active.project.preferences,
+ managerMode: state.active.managerMode,
+ filterPanelOpen: state.active.collationManager.filters.filterPanelOpen,
+ selectedObjects: state.active.collationManager.selectedObjects,
+ collationManager: state.active.collationManager,
+ loading: state.global.loading,
+ exportedData: state.active.exportedData,
+ exportedImages: state.active.exportedImages,
+ groupIDs: state.active.project.groupIDs,
+ leafIDs: state.active.project.leafIDs,
+ };
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ loadProject: (projectID, props) => {
+ dispatch(loadProject(projectID));
+ },
+
+ changeViewMode: (viewMode) => {
+ dispatch(changeViewMode(viewMode));
+ },
+
+ handleObjectPress: (selectedObjects, object, event) => {
+ dispatch(handleObjectPress(selectedObjects, object, event));
+ },
+
+ handleObjectClick: (selectedObjects, object, event, Groups, Leafs, Rectos, Versos) => {
+ dispatch(handleObjectClick(selectedObjects, object, event, { Groups, Leafs, Rectos, Versos}));
+ },
+
+ changeManagerMode: (managerMode) => {
+ dispatch(changeManagerMode(managerMode));
+ },
+
+ toggleFilterPanel: (value) => {
+ dispatch(toggleFilterPanel(value));
+ },
+
+ updateProject: (project, projectID) => {
+ dispatch(updateProject(projectID, project));
+ },
+
+ hideProjectTip: () => {
+ dispatch({type: "HIDE_PROJECT_TIP"});
+ },
+
+ updateGroup: (groupID, group, props) => {
+ dispatch(updateGroup(groupID, group));
+ },
+
+ updateNote: (noteID, note, projectID, filters) => {
+ dispatch(updateNote(noteID, note))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ deleteNote: (noteID, projectID, filters) => {
+ dispatch(deleteNote(noteID))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ linkNote: (noteID, object, projectID, filters) => {
+ dispatch(linkNote(noteID, object))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ unlinkNote: (noteID, object, projectID, filters) => {
+ dispatch(unlinkNote(noteID, object))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ linkAndUnlinkNotes: (noteID, linkObjects, unlinkObjects, projectID, filters) => {
+ if (linkObjects.length > 0 && unlinkObjects.length > 0){
+ dispatch(linkNote(noteID, linkObjects))
+ .then(action=>dispatch(unlinkNote(noteID, unlinkObjects)))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ }
+ else if (linkObjects.length > 0) {
+ dispatch(linkNote(noteID, linkObjects))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ }
+ else if (unlinkObjects.length > 0) {
+ dispatch(unlinkNote(noteID, unlinkObjects))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ }
+ },
+ toggleVisualizationDrawing: (data) => {
+ dispatch(toggleVisualizationDrawing(data));
+ },
+
+ updateFilterSelection: (
+ selection,
+ GroupIDs,
+ LeafIDs,
+ RectoIDs,
+ VersoIDs
+ ) => {
+ dispatch(updateFilterSelection(
+ selection,
+ [],
+ {GroupIDs, LeafIDs, RectoIDs, VersoIDs}
+ ));
+ },
+
+ exportProject: (projectID, format) => {
+ dispatch(exportProject(projectID, format));
+ },
+
+ showCopyToClipboardNotification: () => {
+ dispatch({
+ type: "SHOW_NOTIFICATION",
+ payload: "Successfully copied to clipboard"
+ });
+ setTimeout(()=>dispatch({type: "HIDE_NOTIFICATION"}), 4000);
+ }
+ };
+};
+
+
+export default connect(mapStateToProps, mapDispatchToProps)(CollationManager);
+
diff --git a/viscoll-app/src/containers/CollationManagerViewOnly.js b/viscoll-app/src/containers/CollationManagerViewOnly.js
new file mode 100644
index 00000000..ec9bcab3
--- /dev/null
+++ b/viscoll-app/src/containers/CollationManagerViewOnly.js
@@ -0,0 +1,206 @@
+import React, { Component } from 'react';
+import { connect } from "react-redux";
+
+import InfoBox from './InfoBox';
+import ViewingMode from '../components/collationManager/ViewingMode';
+import NoteDialog from '../components/collationManager/dialog/NoteDialog';
+import {
+ changeViewMode,
+ handleObjectClick,
+} from "../actions/backend/interactionActions";
+
+
+class CollationManagerViewOnly extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ windowWidth: window.innerWidth,
+ contentStyle: {
+ transition: 'top 450ms cubic-bezier(0.23, 1, 0.32, 1)',
+ top: 40,
+ },
+ leftSideBarOpen: false,
+ activeNote: null
+ };
+ }
+
+ componentDidMount() {
+ window.addEventListener('resize', this.resizeHandler);
+ this.props.changeViewMode('VIEWING')
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener("resize", this.resizeHandler);
+ }
+
+ resizeHandler = () => {
+ this.setState({ windowWidth: window.innerWidth });
+ }
+
+ handleObjectClick = (object, event) => {
+ event.stopPropagation();
+ this.props.handleObjectClick(
+ this.props.selectedObjects,
+ object,
+ event,
+ this.props.project.groupIDs,
+ this.props.project.leafIDs,
+ this.props.project.rectoIDs,
+ this.props.project.versoIDs,
+ );
+ }
+
+ closeNoteDialog = () => {
+ this.setState({ activeNote: null, clickedFromDiagram: false }, () => this.props.togglePopUp(false));
+ }
+
+ openNoteDialog = (note, clickedFromDiagram = false) => {
+ this.setState({ activeNote: note, clickedFromDiagram }, () => this.props.togglePopUp(true));
+ }
+
+ getCommonNotes = (props = this.props) => {
+ // Find the common notes of all currently selected objects
+ const memberType = props.selectedObjects.type;
+ const members = props.selectedObjects.members;
+ let notes = [];
+ if (members.length > 0) {
+ notes = props.project[memberType + "s"][members[0]].notes;
+ for (let id of members) {
+ notes = this.intersect(notes, props.project[memberType + "s"][id].notes);
+ }
+ }
+ return notes;
+ }
+
+ /**
+ * Returns items in common
+ */
+ intersect = (list1, list2) => {
+ if (list1.length >= list2.length)
+ return list1.filter((id1) => { return list2.includes(id1) });
+ else
+ return list2.filter((id1) => { return list1.includes(id1) });
+ }
+
+
+ render() {
+ const containerStyle = { top: 85, right: "2%", height: 'inherit', maxHeight: '80%', width: '28%' };
+ if (!this.state.leftSideBarOpen) {
+ containerStyle["width"] = "30%";
+ }
+
+ const infobox = (
+
+ { },
+ unlinkNote: () => { },
+ updatePreferences: () => { }
+ }}
+ togglePopUp={this.props.togglePopUp}
+ tabIndex={this.props.popUpActive ? -1 : 0}
+ windowWidth={this.state.windowWidth}
+ />
+
+ )
+
+ let workspace =
;
+ if (this.props.project.groupIDs.length > 0) {
+ workspace = (
+
+
+
{this.props.project.title}
+
+
+ {infobox}
+
+ );
+ }
+ if (this.props.project.groupIDs.length === 0 && !this.props.loading) {
+ workspace = (
+
+
+
+ Project is either empty or does not exist
+
+
+
+ );
+ }
+
+ return (
+
+ {workspace}
+ { },
+ deleteNote: () => { },
+ linkNote: () => { },
+ unlinkNote: () => { },
+ linkAndUnlinkNotes: () => { },
+ }}
+ projectID={this.props.project.id}
+ noteTypes={this.props.project.noteTypes}
+ Notes={this.props.project.Notes}
+ Groups={this.props.project.Groups}
+ groupIDs={this.props.project.groupIDs}
+ Leafs={this.props.project.Leafs}
+ leafIDs={this.props.project.leafIDs}
+ Rectos={this.props.project.Rectos}
+ rectoIDs={this.props.project.rectoIDs}
+ Versos={this.props.project.Versos}
+ versoIDs={this.props.project.versoIDs}
+ togglePopUp={this.props.togglePopUp}
+ isReadOnly={true}
+ />
+
+ );
+ }
+}
+
+
+const mapStateToProps = (state) => {
+ return {
+ user: state.user,
+ project: state.active.project,
+ managerMode: state.active.managerMode,
+ filterPanelOpen: state.active.collationManager.filters.filterPanelOpen,
+ selectedObjects: state.active.collationManager.selectedObjects,
+ collationManager: state.active.collationManager,
+ loading: state.global.loading,
+ groupIDs: state.active.project.groupIDs,
+ leafIDs: state.active.project.leafIDs,
+ };
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ changeViewMode: (viewMode) => {
+ dispatch(changeViewMode(viewMode));
+ },
+ handleObjectClick: (selectedObjects, object, event, Groups, Leafs, Rectos, Versos) => {
+ dispatch(handleObjectClick(selectedObjects, object, event, { Groups, Leafs, Rectos, Versos }));
+ },
+ };
+};
+
+
+export default connect(mapStateToProps, mapDispatchToProps)(CollationManagerViewOnly);
diff --git a/viscoll-app/src/containers/Dashboard.js b/viscoll-app/src/containers/Dashboard.js
new file mode 100644
index 00000000..0249b9c5
--- /dev/null
+++ b/viscoll-app/src/containers/Dashboard.js
@@ -0,0 +1,294 @@
+import React, {Component} from 'react';
+import Drawer from 'material-ui/Drawer';
+import RaisedButton from 'material-ui/RaisedButton';
+import NewProjectContainer from '../components/dashboard/NewProjectContainer';
+import EditProjectForm from '../components/dashboard/EditProjectForm';
+import ImageCollections from "../components/dashboard/ImageCollections";
+import ListView from '../components/dashboard/ListView';
+import LoadingScreen from "../components/global/LoadingScreen";
+import ServerErrorScreen from "../components/global/ServerErrorScreen";
+import NetworkErrorScreen from "../components/global/NetworkErrorScreen";
+import Notification from "../components/global/Notification";
+import TopBar from "./TopBar";
+import Feedback from "./Feedback";
+import {Tabs,Tab} from 'material-ui/Tabs';
+import topbarStyle from "../styles/topbar";
+import {btnMd} from '../styles/button';
+import { connect } from "react-redux";
+import {
+ createProject,
+ updateProject,
+ deleteProject,
+ loadProjects,
+ importProject,
+ cloneProject,
+} from "../actions/backend/projectActions";
+import {
+ uploadImages,
+ linkImages,
+ unlinkImages,
+ deleteImages,
+} from '../actions/backend/imageActions';
+
+/** Dashboard where user is directed to upon login. This is where the user an create a new project or edit an existing project. */
+class Dashboard extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ newProjectPopoverOpen: false,
+ newProjectModalOpen: false,
+ selectedProject: null,
+ projectDrawerOpen: false,
+ userProfileDialogOpen: false,
+ feedbackOpen: false,
+ deleteConfirmationOpen: false,
+ page: "collations",
+ };
+ }
+
+ componentWillMount() {
+ this.props.user.authenticated ? this.props.loadProjects(this.props) : this.props.history.push('/');
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.notification!=="") {
+ if (nextProps.projects.length>=this.props.projects.length && this.state.page==="collations") {
+ this.setState({
+ selectedProject: nextProps.projects[0],
+ projectDrawerOpen: true,
+ })
+ } else {
+ this.setState({
+ selectedProject: null,
+ projectDrawerOpen: false,
+ })
+ }
+ }
+ }
+
+ componentDidUpdate() {
+ if (!this.props.user.authenticated) this.props.history.push('/');
+ }
+
+ closeProjectPanel = () => {
+ this.setState({projectDrawerOpen: false, selectedProject: null});
+ }
+
+ doubleClick = projectID => {
+ this.props.resetActionHistory();
+ this.props.history.push(`/project/${projectID}`);
+ }
+
+ handleProjectSelection = (index) => {
+ if (index>=0) {
+ let project = this.props.projects[index];
+ this.setState({projectDrawerOpen: true, selectedProject: project});
+ }
+ }
+
+ handleNewProjectTouchTap = (event) => {
+ event.preventDefault();
+ this.setState({newProjectPopoverOpen: true, popoverAnchorEl: event.currentTarget});
+ };
+
+ handleNewProjectRequestClose = () => this.setState({ newProjectPopoverOpen: false });
+
+ handleNewProjectModalToggle = (value) => {
+ this.setState({newProjectModalOpen: value, newProjectPopoverOpen: false});
+ }
+
+ handleDeleteConfirmModalToggle = (value) => {
+ this.setState({deleteConfirmationOpen: value});
+ }
+
+ handleImportProject = (data) => {
+ this.props.importProject(data)
+ .then((action)=>{
+ if (action.type==="IMPORT_PROJECT_SUCCESS"){
+ this.handleProjectSelection(0);
+ this.props.importProjectCallback();
+ }
+ });
+ }
+
+ modalIsOpen = () => {
+ return this.state.deleteConfirmationOpen || this.state.feedbackOpen || this.state.newProjectModalOpen || this.state.newProjectPopoverOpen || this.state.userProfileDialogOpen;
+ }
+
+ userProfileToggle = (userProfileDialogOpen) => {
+ this.setState({userProfileDialogOpen});
+ }
+
+ render() {
+ let sidebar = (
+
+
+
+ this.handleNewProjectModalToggle(true)}
+ tabIndex={this.modalIsOpen()? -1 : 0}
+ aria-label="Create new collation"
+ />
+
+
+
+ this.setState({page:"collations", projectDrawerOpen: this.state.selectedProject!==null})}
+ tabIndex={this.modalIsOpen()?-1:0}
+ aria-label="Collations"
+ >
+ Collations
+
+ this.setState({page:"image",projectDrawerOpen: false})}
+ tabIndex={this.modalIsOpen()?-1:0}
+ aria-label="Image Collections"
+ >
+ Image Collections
+
+
+ );
+ let projectPane = (
+
+
+
+ );
+
+ return (
+
+
this.setState({page:"collations"})}
+ showUndoRedo={this.state.page==="image"}
+ >
+
+
+
+
+ {sidebar}
+ {projectPane}
+
this.handleNewProjectModalToggle(false)}
+ user={this.props.user}
+ createProject={this.props.createProject}
+ importProject={this.handleImportProject}
+ importStatus={this.props.importStatus}
+ cloneProject={this.props.cloneProject}
+ />
+
+ {this.state.page==="collations"?
+
+ :
+ this.setState({deleteConfirmationOpen:v})}
+ action={{
+ uploadImages: this.props.uploadImages,
+ linkImages: this.props.linkImages,
+ unlinkImages: this.props.unlinkImages,
+ deleteImages: this.props.deleteImages,
+ }}
+ />
+ }
+
+
+
+
+
+ this.setState({feedbackOpen:v})}/>
+
+ );
+ }
+}
+
+const mapStateToProps = (state) => {
+ return {
+ user: state.user,
+ projects: state.dashboard.projects,
+ images: state.dashboard.images,
+ importStatus: state.dashboard.importStatus,
+ loading: state.global.loading,
+ notification: state.global.notification,
+ actionHistory: state.history,
+ };
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ createProject: (newProject) => {
+ dispatch(createProject(newProject));
+ },
+ updateProject: (projectID, project) => {
+ dispatch(updateProject(projectID, project))
+ },
+ deleteProject: (projectID, deleteUnlinkedImages) => {
+ dispatch(deleteProject(projectID, deleteUnlinkedImages));
+ },
+ loadProjects: (props) => {
+ dispatch(loadProjects());
+ },
+ importProject: (data) => {
+ return dispatch(importProject(data))
+ },
+ importProjectCallback: () => {
+ dispatch({type: "IMPORT_PROJECT_SUCCESS_CALLBACK"});
+ },
+ cloneProject: (projectID) => {
+ dispatch(cloneProject(projectID));
+ },
+ uploadImages: (images) => {
+ dispatch(uploadImages(images));
+ },
+ linkImages: (projectIDs, imageIDs) => {
+ dispatch(linkImages(projectIDs, imageIDs));
+ },
+ unlinkImages: (projectIDs, imageIDs) => {
+ dispatch(unlinkImages(projectIDs, imageIDs));
+ },
+ deleteImages: (imageIDs) => {
+ dispatch(deleteImages(imageIDs));
+ },
+ resetActionHistory: () => {
+ dispatch({type:"CLEAR_ACTION_HISTORY"})
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
+
+
diff --git a/viscoll-app/src/containers/Feedback.js b/viscoll-app/src/containers/Feedback.js
new file mode 100644
index 00000000..4c9a56fe
--- /dev/null
+++ b/viscoll-app/src/containers/Feedback.js
@@ -0,0 +1,138 @@
+import React, { Component } from 'react';
+import { connect } from "react-redux";
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+import RaisedButton from 'material-ui/RaisedButton';
+import TextField from 'material-ui/TextField';
+import ClientJS from 'clientjs';
+import { exportProjectBeforeFeedback } from "../actions/backend/projectActions";
+import { sendFeedback } from "../actions/backend/userActions";
+
+/** Feedback form that sends an email to admin for each feedback */
+class Feedback extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ open: false,
+ title: "",
+ feedback: "",
+ }
+ }
+ handleOpen = () => {
+ this.setState({ open: true });
+ }
+ handleClose = () => {
+ this.setState({
+ open: false,
+ title: "",
+ feedback: "",
+ });
+ this.props.togglePopUp(false);
+ }
+ onChange = (type, value) => {
+ this.setState({ [type]: value });
+ }
+ submit = () => {
+ let feedback = this.state.feedback;
+ let browserInformation;
+ try {
+ const client = new ClientJS();
+ const result = client.getResult();
+ browserInformation = JSON.stringify(result);
+ } catch (e) { }
+ this.props.sendFeedback(this.state.title, feedback, browserInformation, this.props.projectID);
+ this.handleClose();
+ }
+ render() {
+ const actions = [
+ this.handleClose()}
+ />,
+ this.submit()}
+ />,
+ ];
+ return (
+
+
+ { this.handleOpen(); this.props.togglePopUp(true) }}
+ backgroundColor="rgba(82, 108, 145, 0.2)"
+ tabIndex={this.props.tabIndex}
+ />
+
+
+ Bug? Suggestions? Let us know!
+
+
+ Title
+
+
+ this.onChange("title", v)}
+ autoFocus
+ />
+
+
+
+
+ Feedback
+
+
+ this.onChange("feedback", e.target.value)}
+ rows={5}
+ />
+
+
+
+
+ );
+ }
+}
+const mapStateToProps = (state) => {
+ return {
+ userID: state.user.id,
+ projectID: state.active.project ? state.active.project.id : null
+ };
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ sendFeedback: (title, message, browserInformation, projectID) => {
+ if (projectID) {
+ dispatch(exportProjectBeforeFeedback(projectID, "json"))
+ .then((action) => {
+ if (action.type === "EXPORT_SUCCESS") {
+ const project = JSON.stringify(action.payload);
+ dispatch(sendFeedback(title, message, browserInformation, project));
+ }
+ })
+ } else {
+ dispatch(sendFeedback(title, message, browserInformation));
+ }
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(Feedback);
diff --git a/viscoll-app/src/containers/Filter.js b/viscoll-app/src/containers/Filter.js
new file mode 100644
index 00000000..6aecf5a0
--- /dev/null
+++ b/viscoll-app/src/containers/Filter.js
@@ -0,0 +1,469 @@
+import React, {Component} from 'react';
+import { connect } from "react-redux";
+import FilterRow from '../components/filter/FilterRow';
+import RaisedButton from 'material-ui/RaisedButton';
+import MenuItem from 'material-ui/MenuItem';
+import Toggle from 'material-ui/Toggle';
+import Popover, {PopoverAnimationVertical} from 'material-ui/Popover';
+import Menu from 'material-ui/Menu';
+import ArrowDown from 'material-ui/svg-icons/navigation/arrow-drop-down'
+import {
+ filterProject,
+ resetFilters,
+ toggleFilterDisplay,
+ updateFilterQuery,
+ updateFilterSelection
+} from "../actions/backend/filterActions";
+
+/** Filter groups, leaves and sides */
+class Filter extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ queries: props.queries,
+ submitDisabled: false,
+ conjoinedToAutoComplete: [],
+ select: "",
+ popoverAnchorEl: null,
+ selectPopover: false,
+ message: "",
+ filterPanelHeight: '0',
+ }
+ }
+
+ componentDidMount() {
+ let conjoinedToAutoComplete = [];
+ conjoinedToAutoComplete.push({
+ text: 'None',
+ value: 'None',
+ });
+ conjoinedToAutoComplete = [...conjoinedToAutoComplete,
+ ...this.props.leafIDs.map((id,index)=>{
+ return {text: `Leaf ${index+1}`, value: id }
+ })
+ ]
+ this.setState({
+ conjoinedToAutoComplete,
+ filterPanelHeight: document.getElementById('filterContainer').offsetHeight
+ });
+ }
+
+ componentWillReceiveProps(nextProps) {
+ let conjoinedToAutoComplete = [];
+ conjoinedToAutoComplete.push({
+ text: 'None',
+ value: 'None',
+ });
+ conjoinedToAutoComplete = [...conjoinedToAutoComplete,
+ ...this.props.leafIDs.map((id,index)=>{
+ return {text: `Leaf ${index+1}`, value: id }
+ })
+ ]
+ let matches = [];
+ if (nextProps.groupMatches.length>0) {
+ let plural = nextProps.groupMatches.length>1? "s" : "";
+ matches.push(nextProps.groupMatches.length + " group" + plural);
+ }
+ if (nextProps.leafMatches.length>0) {
+ let plural = nextProps.leafMatches.length>1? "ves" : "f";
+ matches.push(nextProps.leafMatches.length + " lea" + plural);
+ }
+ if (nextProps.sideMatches.length>0) {
+ let plural = nextProps.sideMatches.length>1? "s" : "";
+ matches.push(nextProps.sideMatches.length + " side" + plural);
+ }
+ if (nextProps.noteMatches.length>0) {
+ let plural = nextProps.noteMatches.length>1? "s" : "";
+ matches.push(nextProps.noteMatches.length + " note" + plural);
+ }
+ let message = "No matches found.";
+ if (matches.length>0) {
+ message = "Matches: " + matches.join(", ");
+ }
+ if (nextProps.queries.length===1 &&
+ (nextProps.queries[0].type===null || nextProps.queries[0].attribute===""
+ || nextProps.queries[0].condition===""||nextProps.queries[0].values.length===0)) {
+ // Set message to empty if user cleared all filters
+ message = "";
+ }
+
+ let filterPanelHeight = document.getElementById('filterContainer').offsetHeight;
+
+ this.setState({
+ queries: nextProps.queries,
+ conjoinedToAutoComplete,
+ message, filterPanelHeight
+ });
+ }
+
+ removeRow = (queryIndex) => {
+ let newQueries = this.state.queries;
+ newQueries.splice(queryIndex,1);
+ this.setState({ queries: newQueries}, ()=>{
+ this.filter();
+ this.props.filterHeightChange(document.getElementById('filterContainer').offsetHeight);
+ });
+ }
+
+ addRow = () => {
+ if (!this.disableAddRow()) {
+ let newQueries = this.state.queries;
+ newQueries.push({
+ type: null,
+ attribute: "",
+ attributeIndex: "",
+ values: [],
+ condition: "",
+ conjunction: "",
+ })
+ this.setState({ queries: newQueries}, () => {
+ this.filter();
+ this.props.filterHeightChange(document.getElementById('filterContainer').offsetHeight);
+ });
+
+ }
+ }
+
+ onChange = (queryIndex, fieldName, index, value, dataSource) => {
+ if (dataSource){
+ for (let member of dataSource){
+ if (member.text===value){
+ value = [member.value];
+ break;
+ }
+ }
+ }
+ let updatedQueries = this.state.queries;
+ if (["group", "leaf", "side", "note"].includes(value))
+ updatedQueries = this.clearFilterRowOnType(queryIndex, value);
+ if (fieldName==="attribute") {
+ updatedQueries[queryIndex]["attributeIndex"] = index;
+ }
+ updatedQueries[queryIndex][fieldName] = value;
+ this.props.updateFilterQuery(updatedQueries);
+ // If queries are valid, filter!
+ let validQueries = true;
+ for (let i=0; i0 && updatedQueries[i].values.findIndex((value)=>value==="")<0;
+ if (!isComplete) {
+ validQueries = false;
+ break;
+ }
+ }
+ if (validQueries) this.filterProject();
+ }
+
+ filterProject = () => {
+ // Check if correct values are being passed in auto-complete dropdown cases
+ let toFilter = true;
+ for (let query of this.state.queries){
+ if ((query.type==="leaf" && query.attribute==="conjoined_leaf_order") ||
+ (query.type==="note" && query.attribute==="type")){
+ if (!Array.isArray(query.values)){
+ toFilter = false;
+ break;
+ }
+ }
+ }
+ if (toFilter) {
+ this.props.filterProject(this.props.projectID, {queries: this.state.queries});
+ }
+ }
+
+ resetFilters = () => {
+ this.setState({
+ queries: [
+ {
+ type: "",
+ attribute: "",
+ attributeIndex: "",
+ values: [],
+ condition: "",
+ conjunction: "",
+ }
+ ],
+ }, () => {
+ this.props.resetFilters(this.state.queries);
+ this.props.filterHeightChange(document.getElementById('filterContainer').offsetHeight);
+ });
+ }
+
+ clearFilterRowOnType = (index, type) => {
+ let queries = [];
+ let currentIndex = 0;
+ for (let query of this.state.queries) {
+ if (currentIndex===index && type!==query.type)
+ queries.push({
+ type: type,
+ attribute: "",
+ attributeIndex: "",
+ values: [],
+ condition: "",
+ conjunction: "",
+ })
+ else
+ queries.push(query);
+ currentIndex += 1;
+ }
+ return queries;
+ }
+
+ clearFilterRowOnAttribute = (index, attribute, attributeIndex) => {
+ let queries = [];
+ let currentIndex = 0;
+ for (let query of this.state.queries) {
+ if (currentIndex===index && attribute!==query.attribute && query.attribute!==""){
+ queries.push({
+ type: query.type,
+ attribute: attribute,
+ attributeIndex: attributeIndex,
+ values: [],
+ condition: "",
+ conjunction: "",
+ })
+ }
+ else
+ queries.push(query);
+ currentIndex += 1;
+ }
+ this.setState({queries}, () => this.props.resetFilters(queries))
+ }
+
+
+ filter = () => {
+ let index = 0;
+ let haveErrors = false
+ for (let query of this.state.queries) {
+ if (query.type === null)
+ haveErrors = true
+ if (query.attribute === "")
+ haveErrors = true
+ if (query.values.length === 0)
+ haveErrors = true
+ if (query.values.find((value)=>value==="")>=0)
+ haveErrors = true
+ if (query.condition === "")
+ haveErrors = true
+ if (index !== this.state.queries.length-1)
+ if (query.conjunction === "")
+ haveErrors = true
+ index += 1;
+ }
+ if (!haveErrors) this.filterProject();
+ }
+
+ disableAddRow = () => {
+ if (this.state.queries[this.state.queries.length-1].type === null)
+ return true;
+ if (this.state.queries[this.state.queries.length-1].attribute === "")
+ return true;
+ if (this.state.queries[this.state.queries.length-1].values.length === 0)
+ return true;
+ if (this.state.queries[this.state.queries.length-1].condition === "")
+ return true;
+ return false;
+ }
+
+ disableNewRow = () => {
+ return (this.state.queries.length>1 && this.state.queries[this.state.queries.length-2].conjunction === "");
+ }
+
+
+ handleSelection = (selection) => {
+ if (this.props.filterSelection!==selection || selection==="")
+ this.props.updateFilterSelection(
+ selection,
+ this.props.matchingFilterObjects,
+ this.props.groupIDs,
+ this.props.leafIDs,
+ this.props.rectoIDs,
+ this.props.versoIDs
+ );
+ }
+
+ handleSelectOpen = (event) => {
+ // This prevents ghost click.
+ event.preventDefault();
+ if (this.props.filterActive) {
+ this.setState({
+ selectPopover: true,
+ popoverAnchorEl: event.currentTarget,
+ });
+ }
+ }
+
+ handleSelectClose = () => {
+ this.setState({
+ selectPopover: false,
+ });
+ };
+
+ render() {
+ let queries = [];
+ if (this.state.queries && this.props.open)
+ for (let i=0; i
+ );
+ }
+ let filterContainerStyle = this.props.open?{top:'56px'}:{top:'-'+this.state.filterPanelHeight+'px'};
+ if (this.props.fullWidth) filterContainerStyle.width="100%";
+ let panel =
+
+
+ {queries}
+
+
+
+
+
+
+
+
}
+ tabIndex={this.props.tabIndex}
+ style={this.props.open?{}:{display:"none"}}
+ />
+
+ {this.setState({select:v});this.handleSelection(v)}}
+ >
+ 0?false:true} />
+ 0?false:true}/>
+ 0?false:true}/>
+ 0?false:true}/>
+ {this.props.selectedObjects.members.length > 0 ?
+
+ : null
+ }
+
+
+
+
+ this.resetFilters()}
+ backgroundColor="#b53c3c"
+ labelColor="#ffffff"
+ tabIndex={this.props.tabIndex}
+ style={this.props.open?{marginRight: 15}:{display:"none"}}
+ />
+
+
+
+
+
+ return panel;
+ }
+}
+
+const mapStateToProps = (state) => {
+ return {
+ projectID: state.active.project.id,
+ selectedObjects: state.active.collationManager.selectedObjects,
+ viewMode: state.active.collationManager.viewMode,
+ defaultAttributes: state.active.collationManager.defaultAttributes,
+ Groups: state.active.project.Groups,
+ Leafs: state.active.project.Leafs,
+ Rectos: state.active.project.Rectos,
+ Versos: state.active.project.Versos,
+ Notes: state.active.project.Notes,
+ attachedToLeafs: state.active.project.attachedToLeafs,
+ queries: state.active.collationManager.filters.queries,
+ hideOthers: state.active.collationManager.filters.hideOthers,
+ filterActive: state.active.collationManager.filters.active,
+ filterSelection: state.active.collationManager.filters.selection,
+ noteTypes: state.active.project.noteTypes,
+ groupMatches: state.active.collationManager.filters.Groups,
+ leafMatches: state.active.collationManager.filters.Leafs,
+ sideMatches: state.active.collationManager.filters.Sides,
+ noteMatches: state.active.collationManager.filters.Notes,
+ matchingFilterObjects: state.active.collationManager.filters,
+ leafIDs: state.active.project.leafIDs,
+ groupIDs: state.active.project.groupIDs,
+ rectoIDs: state.active.project.rectoIDs,
+ versoIDs: state.active.project.versoIDs,
+ };
+};
+const mapDispatchToProps = (dispatch) => {
+ return {
+ filterProject: (projectID, queries) => {
+ dispatch(filterProject(projectID, queries));
+ },
+ resetFilters: (queries) => {
+ dispatch(resetFilters(queries));
+ },
+ toggleFilterDisplay: () => {
+ dispatch(toggleFilterDisplay());
+ },
+ updateFilterQuery: (newQueries) => {
+ dispatch(updateFilterQuery(newQueries));
+ },
+ updateFilterSelection: (
+ selection,
+ matchingFilterObjects,
+ GroupIDs,
+ LeafIDs,
+ RectoIDs,
+ VersoIDs
+ ) => {
+ dispatch(updateFilterSelection(
+ selection,
+ matchingFilterObjects,
+ {GroupIDs, LeafIDs, RectoIDs, VersoIDs}
+ ));
+ }
+ };
+};
+export default connect(mapStateToProps, mapDispatchToProps)(Filter);
diff --git a/viscoll-app/src/containers/ImageManager.js b/viscoll-app/src/containers/ImageManager.js
new file mode 100644
index 00000000..d65a90eb
--- /dev/null
+++ b/viscoll-app/src/containers/ImageManager.js
@@ -0,0 +1,275 @@
+import React, {Component} from 'react';
+import { connect } from "react-redux";
+import TopBar from "./TopBar";
+import {Tabs, Tab} from 'material-ui/Tabs';
+import topbarStyle from "../styles/topbar";
+import Panel from '../components/global/Panel';
+import {
+ changeManagerMode,
+ changeImageTab,
+} from "../actions/backend/interactionActions";
+import {
+ createManifest,
+ updateManifest,
+ deleteManifest,
+ cancelCreateManifest,
+} from "../actions/backend/manifestActions";
+import {
+ sendFeedback,
+} from "../actions/backend/userActions";
+import {
+ mapSidesToImages,
+ uploadImages,
+ linkImages,
+ unlinkImages,
+ deleteImages,
+} from "../actions/backend/imageActions";
+import ManageManifests from '../components/imageManager/ManageManifests';
+import MapImages from '../components/imageManager/MapImages';
+import {RadioButton, RadioButtonGroup} from 'material-ui/RadioButton';
+import FlatButton from 'material-ui/FlatButton';
+import {radioBtnDark} from "../styles/button";
+import ManagersPanel from '../components/global/ManagersPanel';
+
+class ImageManager extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ selectAll: "",
+ windowWidth: window.innerWidth,
+ };
+ }
+
+ componentDidMount() {
+ window.addEventListener('resize', this.resizeHandler);
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener("resize", this.resizeHandler);
+ }
+
+ resizeHandler = () => {
+ this.setState({windowWidth:window.innerWidth});
+ }
+
+ createManifest = (manifest) => {
+ this.props.createManifest(this.props.projectID, manifest)
+ }
+
+ updateManifest = (manifest) => {
+ this.props.updateManifest(this.props.projectID, manifest)
+ }
+
+ deleteManifest = (manifest) => {
+ this.props.deleteManifest(this.props.projectID, manifest)
+ }
+
+ linkImages = (imageIDs) => {
+ this.props.linkImages([this.props.projectID], imageIDs, this.props.manifests["DIYImages"], this.props.images);
+ }
+
+ unlinkImages = (imageIDs) => {
+ this.props.unlinkImages([this.props.projectID], imageIDs, this.props.manifests["DIYImages"]);
+ }
+
+ uploadImages = (images) => {
+ this.props.uploadImages(images, this.props.projectID);
+ }
+
+ handleSelection = (selectAll) => {
+ this.setState({ selectAll })
+ }
+
+
+ deleteImages = (imageIDs) => {
+ this.props.deleteImages(imageIDs, this.props.manifests["DIYImages"]);
+ }
+
+ renderRadioButton = (value, label) => {
+ return (
+
+ )
+ }
+
+ render() {
+ let content = "";
+ if (this.props.activeTab==="MANAGE") {
+ content = (
+
+ )
+ } else {
+ content = (
+
+ )
+ }
+
+ let selectionRadioGroup = (
+ this.setState({selectAll: v})}
+ >
+ {this.renderRadioButton("sideMapBoard", "Select All Mapped Sides")}
+ {this.renderRadioButton("imageMapBoard", "Select All Mapped Images")}
+ {this.renderRadioButton("sideBacklog", "Select All Backlog Sides")}
+ {this.renderRadioButton("imageBacklog", "Select All Backlog Images")}
+
+ );
+
+ let sidebarClasses = "sidebar";
+ if (this.props.popUpActive) sidebarClasses += " lowerZIndex";
+
+ const sidebar = (
+
+
+
+ {this.props.activeTab==="MAP" ?
+
+ {selectionRadioGroup}
+ this.setState({selectAll:""})}
+ secondary
+ fullWidth
+ style={this.state.selectAll===""?{display:"none"}:{}}
+ tabIndex={this.props.popUpActive?-1:0}
+ />
+
+ : null
+ }
+
+ );
+
+ return (
+
+
+
+ this.props.changeImageTab(v)}
+ >
+
+
+
+
+ {sidebar}
+
+ {content}
+
+
+
+ );
+ }
+}
+
+const mapStateToProps = (state) => {
+ return {
+ projectID: state.active.project.id,
+ manifests: state.active.project.manifests,
+ Leafs: state.active.project.Leafs,
+ Rectos: state.active.project.Rectos,
+ Versos: state.active.project.Versos,
+ leafIDs: state.active.project.leafIDs,
+ rectoIDs: state.active.project.rectoIDs,
+ versoIDs: state.active.project.versoIDs,
+ activeTab: state.active.imageManager.activeTab,
+ managerMode: state.active.managerMode,
+ createManifestError: state.active.imageManager.manageSources.error,
+ images: state.dashboard.images,
+ };
+};
+const mapDispatchToProps = (dispatch) => {
+ return {
+ changeManagerMode: (managerMode) => {
+ dispatch(changeManagerMode(managerMode));
+ },
+ changeImageTab: (tabName) => {
+ dispatch(changeImageTab(tabName));
+ },
+ sendFeedback: (title, message) => {
+ dispatch(sendFeedback(title, message))
+ },
+ createManifest: (projectID, manifest) => {
+ dispatch(createManifest(projectID, manifest))
+ },
+ updateManifest: (projectID, manifest) => {
+ dispatch(updateManifest(projectID, manifest))
+ },
+ deleteManifest: (projectID, manifest) => {
+ dispatch(deleteManifest(projectID, manifest))
+ },
+ cancelCreateManifest: () => {
+ dispatch(cancelCreateManifest())
+ },
+ mapSidesToImages: (sideMappings) => {
+ dispatch(mapSidesToImages(sideMappings))
+ },
+ linkImages: (projectIDs, imageIDs, manifest, allImages) => {
+ dispatch(linkImages(projectIDs, imageIDs));
+ },
+ unlinkImages: (projectIDs, imageIDs, manifest) => {
+ dispatch(unlinkImages(projectIDs, imageIDs));
+ },
+ deleteImages: (imageIDs, manifest) => {
+ dispatch(deleteImages(imageIDs));
+ },
+ uploadImages: (images, projectID) => {
+ dispatch(uploadImages(images, projectID));
+ }
+ };
+};
+export default connect(mapStateToProps, mapDispatchToProps)(ImageManager);
diff --git a/viscoll-app/src/containers/InfoBox.js b/viscoll-app/src/containers/InfoBox.js
new file mode 100644
index 00000000..23747bb0
--- /dev/null
+++ b/viscoll-app/src/containers/InfoBox.js
@@ -0,0 +1,524 @@
+import React from 'react';
+import GroupInfoBox from '../components/infoBox/GroupInfoBox';
+import LeafInfoBox from '../components/infoBox/LeafInfoBox';
+import SideInfoBox from '../components/infoBox/SideInfoBox';
+import infoBoxStyle from '../styles/infobox';
+import {Tabs, Tab} from 'material-ui/Tabs';
+import RaisedButton from 'material-ui/RaisedButton';
+import AddGroupDialog from '../components/infoBox/dialog/AddGroupDialog';
+import { connect } from "react-redux";
+import {
+ addLeafs,
+ updateLeaf,
+ updateLeafs,
+ autoConjoinLeafs,
+ deleteLeaf,
+ deleteLeafs,
+ generateFolioNumbers,
+ generatePageNumbers,
+} from '../actions/backend/leafActions';
+import {
+ addGroups,
+ updateGroup,
+ updateGroups,
+ deleteGroup,
+ deleteGroups,
+} from '../actions/backend/groupActions';
+import {
+ updateSide,
+ updateSides,
+} from '../actions/backend/sideActions';
+import {
+ addNote,
+ linkNote,
+} from '../actions/backend/noteActions';
+import {
+ changeInfoBoxTab,
+ toggleVisualizationDrawing,
+} from '../actions/backend/interactionActions';
+import {
+ reapplyFilterProject,
+} from '../actions/backend/filterActions';
+import {updatePreferences} from "../actions/backend/projectActions";
+
+/** Container of the leaf, group and side infoboxes */
+class InfoBox extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ addGroupDialogOpen: false
+ }
+ }
+
+ toggleAddGroupDialog = (value=false) => {
+ this.setState({ addGroupDialogOpen: value });
+ this.props.togglePopUp(value);
+ }
+
+ addLeafs = (data) => {
+ let leafIDs = [];
+ const userID = this.props.user.id;
+ for (let count = 0; count < data.additional.noOfLeafs; count++){
+ const date = Date.now().toString();
+ const IDHash = userID + date + count;
+ leafIDs.push(IDHash.substr(IDHash.length - 24))
+ }
+ let sideIDs = []
+ for (let count = leafIDs.length; count < leafIDs.length+data.additional.noOfLeafs*2; count++) {
+ const date = Date.now().toString();
+ const IDHash = userID + date + count;
+ sideIDs.push(IDHash.substr(IDHash.length - 24));
+ }
+ data.additional["leafIDs"] = leafIDs;
+ data.additional["sideIDs"] = sideIDs;
+ this.props.addLeafs(data.leaf, data.additional, this.props.projectID, this.props.filters);
+ }
+
+ updateLeaf = (leafID, leaf) => { this.props.updateLeaf(leafID, leaf, this.props.projectID, this.props.filters); }
+
+ updateLeafs = (leafs) => { this.props.updateLeafs(leafs, this.props.projectID, this.props.filters); }
+
+ autoConjoinLeafs = () => {
+ this.props.autoConjoinLeafs(this.props.selectedObjects.members, this.props.projectID, this.props.filters); }
+
+ deleteLeaf = (leafID) => { this.props.deleteLeaf(leafID, this.props.projectID, this.props.filters); }
+
+ deleteLeafs = (leafs) => { this.props.deleteLeafs(leafs, this.props.projectID, this.props.filters); }
+
+ updateGroup = (groupID, group) => { this.props.updateGroup(groupID, group, this.props.projectID, this.props.filters); }
+
+ updateGroups = (groups) => { this.props.updateGroups(groups, this.props.projectID, this.props.filters); }
+
+ addGroups = (data) => {
+ const userID = this.props.user.id;
+ let groupIDs = [];
+ for (let count = 0; count < data.additional.noOfGroups; count++) {
+ const date = Date.now().toString();
+ const IDHash = userID + date + count;
+ groupIDs.push(IDHash.substr(IDHash.length - 24))
+ }
+ let leafIDs = [];
+ for (let count = groupIDs.length; count < groupIDs.length+groupIDs.length*data.additional.noOfLeafs; count++) {
+ const date = Date.now().toString();
+ const IDHash = userID + date + count;
+ leafIDs.push(IDHash.substr(IDHash.length - 24))
+ }
+ let sideIDs = []
+ for (let count = groupIDs.length*leafIDs.length; count < groupIDs.length*leafIDs.length+leafIDs.length*2; count++) {
+ const date = Date.now().toString();
+ const IDHash = userID + date + count;
+ sideIDs.push(IDHash.substr(IDHash.length - 24));
+ }
+ data.additional["groupIDs"] = groupIDs;
+ data.additional["leafIDs"] = leafIDs;
+ data.additional["sideIDs"] = sideIDs;
+ this.props.addGroups(data.group, data.additional, this.props.projectID, this.props.filters);
+ }
+
+ deleteGroup = (groupID) => { this.props.deleteGroup(groupID, this.props.projectID, this.props.filters); }
+
+ deleteGroups = (groups) => { this.props.deleteGroups(groups, this.props.projectID, this.props.filters); }
+
+ updateSide = (sideID, side) => { this.props.updateSide(sideID, side, this.props.projectID, this.props.filters); }
+
+ updateSides = (sides) => { this.props.updateSides(sides, this.props.projectID, this.props.filters); }
+
+ linkNote = (noteID) => {
+ let objects = [];
+ let type = this.props.selectedObjects.type;
+ if (type==="Recto" || type==="Verso")
+ type = "Side";
+ for (let id of this.props.selectedObjects.members) {
+ objects.push({id, type});
+ }
+ this.props.action.linkNote(noteID, objects, this.props.projectID, this.props.filters);
+ }
+
+ unlinkNote = (noteID, sideIndex) => {
+ let objects = [];
+ let type = this.props.selectedObjects.type;
+ for (let id of this.props.selectedObjects.members) {
+ objects.push({id, type});
+ }
+ this.props.action.unlinkNote(noteID, objects, this.props.projectID, this.props.filters);
+ }
+
+ createAndAttachNote = (noteTitle, noteType, description, show) => {
+ let objects = [];
+ let type = this.props.selectedObjects.type;
+ if (type==="Recto" || type==="Verso")
+ type = "Side";
+ for (let id of this.props.selectedObjects.members) {
+ objects.push({id, type});
+ }
+ const userID = this.props.user.id;
+ const date = Date.now().toString();
+ const IDHash = userID + date;
+ const id = IDHash.substr(IDHash.length - 24);
+ let note = {
+ project_id: this.props.projectID,
+ id: id,
+ title: noteTitle,
+ type: noteType,
+ description: description,
+ show: show
+ }
+ this.props.createAndAttachNote(note, objects, this.props.projectID, this.props.filters);
+ }
+
+ generateFolioNumbers = (startNumber) => {
+ let leafIDs = this.props.collationManager.selectedObjects.members;
+ let rectoIDs = [];
+ let versoIDs = [];
+
+ for (const leafID of leafIDs) {
+ const leaf = this.props.Leafs[leafID];
+ rectoIDs.push(leaf.rectoID);
+ versoIDs.push(leaf.versoID);
+ }
+
+ this.props.generateFolioNumbers(startNumber, rectoIDs, versoIDs);
+ }
+
+ generatePageNumbers = (startNumber) => {
+ let leafIDs = this.props.collationManager.selectedObjects.members;
+ let rectoIDs = [];
+ let versoIDs = [];
+
+ for (const leafID of leafIDs) {
+ const leaf = this.props.Leafs[leafID];
+ rectoIDs.push(leaf.rectoID);
+ versoIDs.push(leaf.versoID);
+ }
+ this.props.generatePageNumbers(startNumber, rectoIDs, versoIDs);
+ }
+
+ handleChangeInfoBoxTab = (value, event) => {
+ this.props.changeInfoBoxTab(
+ value,
+ this.props.selectedObjects,
+ this.props.Leafs,
+ this.props.Rectos,
+ this.props.Versos,
+ )
+ }
+
+ updatePreferences = (newPreferences) => {
+ const preferences = {...this.props.preferences, ...newPreferences};
+ this.props.updatePreferences(this.props.projectID, {preferences});
+ }
+
+ render() {
+ if (Object.keys(this.props.Groups).length===0){
+ return (
+
+
this.toggleAddGroupDialog(true)}
+ />
+
+
+ );
+ }
+
+ if (this.props.selectedObjects.members.length === 0){
+ return (
);
+ }
+
+ const leafSideTabs = (
+
+
+
+
+
+ );
+
+ const groupTab = (
+
+
+
+ );
+
+ const noteActions = {
+ linkNote: this.linkNote,
+ unlinkNote: this.unlinkNote,
+ createAndAttachNote: this.createAndAttachNote
+ }
+
+ if (this.props.selectedObjects.type === "Group") {
+ return (
+
+ {groupTab}
+
+
+ );
+ } else if (this.props.selectedObjects.type === "Leaf") {
+ return (
+
+ {leafSideTabs}
+
+
+ );
+ } else if (this.props.selectedObjects.type === "Recto"||this.props.selectedObjects.type === "Verso") {
+ const sideIndex = this.props.selectedObjects.type === "Recto"? 0 : 1;
+ return (
+
+ {leafSideTabs}
+
+
+ );
+ } else {
+ return (
);
+ }
+ }
+}
+const mapStateToProps = (state) => {
+ return {
+ user: state.user,
+ projectID: state.active.project.id,
+ preferences: state.active.project.preferences,
+ Groups: state.active.project.Groups,
+ groupIDs: state.active.project.groupIDs,
+ leafIDs: state.active.project.leafIDs,
+ rectoIDs: state.active.project.rectoIDs,
+ versoIDs: state.active.project.versoIDs,
+ Leafs: state.active.project.Leafs,
+ Rectos: state.active.project.Rectos,
+ Versos: state.active.project.Versos,
+ Notes: state.active.project.Notes,
+ noteTypes: state.active.project.noteTypes,
+ selectedObjects: state.active.collationManager.selectedObjects,
+ collationManager: state.active.collationManager,
+ notesManager: state.active.notesManager,
+ filters: state.active.collationManager.filters,
+ tacketed: state.active.collationManager.visualizations.tacketed,
+ };
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ toggleVisualizationDrawing: (data) => {
+ dispatch(toggleVisualizationDrawing(data));
+ },
+
+ addLeafs: (leaf, additional, projectID, filters) => {
+ dispatch(addLeafs(leaf, additional))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ updateLeaf: (leafID, leaf, projectID, filters) => {
+ dispatch(updateLeaf(leafID, leaf))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ updateLeafs: (leafs, projectID, filters) => {
+ dispatch(updateLeafs(leafs, projectID))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ autoConjoinLeafs: (leafIDs, projectID, filters) => {
+ dispatch(autoConjoinLeafs(leafIDs))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ deleteLeaf: (leafID, projectID, filters) => {
+ dispatch(deleteLeaf(leafID))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ deleteLeafs: (leafs, projectID, filters) => {
+ dispatch(deleteLeafs(leafs))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ addGroups: (group, additional, projectID, filters) => {
+ dispatch(addGroups(group, additional))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ updateGroup: (groupID, group, projectID, filters) => {
+ dispatch(updateGroup(groupID, group))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ updateGroups: (groups, projectID, filters) => {
+ dispatch(updateGroups(groups))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ deleteGroup: (groupID, projectID, filters) => {
+ dispatch(deleteGroup(groupID))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ deleteGroups: (groups, projectID, filters) => {
+ dispatch(deleteGroups(groups, projectID))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ updateSide: (sideID, side, projectID, filters) => {
+ dispatch(updateSide(sideID, side))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ updateSides: (sides, projectID, filters) => {
+ dispatch(updateSides(sides))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ changeInfoBoxTab: (newType, selectedObjects, Leafs, Rectos, Versos) => {
+ dispatch(changeInfoBoxTab(newType, selectedObjects, {Leafs, Rectos, Versos}));
+ },
+
+ createAndAttachNote: (note, objects, projectID, filters) => {
+ dispatch(addNote(note))
+ .then(() => dispatch(linkNote(note.id, objects)))
+ .then(()=>dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ generateFolioNumbers: (startNumber, rectoIDs, versoIDs) => {
+ dispatch(generateFolioNumbers(startNumber, rectoIDs, versoIDs));
+ },
+
+ generatePageNumbers: (startNumber, rectoIDs, versoIDs) => {
+ dispatch(generatePageNumbers(startNumber, rectoIDs, versoIDs));
+ },
+
+ updatePreferences: (projectID, project) => {
+ dispatch(updatePreferences(projectID, project));
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(InfoBox);
diff --git a/viscoll-app/src/containers/NotesManager.js b/viscoll-app/src/containers/NotesManager.js
new file mode 100644
index 00000000..a19a009b
--- /dev/null
+++ b/viscoll-app/src/containers/NotesManager.js
@@ -0,0 +1,278 @@
+import React, {Component} from 'react';
+import { connect } from "react-redux";
+import TopBar from "./TopBar";
+import ManageNotes from "../components/notesManager/ManageNotes";
+import NoteType from "../components/notesManager/NoteType";
+import {Tabs, Tab} from 'material-ui/Tabs';
+// import Panel from '../components/global/Panel';
+import topbarStyle from "../styles/topbar";
+import {
+ changeManagerMode,
+ changeNotesTab,
+} from "../actions/backend/interactionActions";
+import {
+ addNote,
+ updateNote,
+ deleteNote,
+ createNoteType,
+ updateNoteType,
+ deleteNoteType,
+ linkNote,
+ unlinkNote
+} from "../actions/backend/noteActions";
+import { sendFeedback } from "../actions/backend/userActions";
+import ManagersPanel from '../components/global/ManagersPanel';
+
+class NotesManager extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ Notes: props.Notes,
+ value: "",
+ filterTypes: {
+ title: true,
+ type: true,
+ description: true,
+ },
+ windowWidth: window.innerWidth,
+ };
+ }
+
+ componentDidMount() {
+ window.addEventListener('resize', this.resizeHandler);
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener("resize", this.resizeHandler);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.setState({Notes: nextProps.Notes}, ()=>this.applyFilter())
+ }
+
+ resizeHandler = () => {
+ this.setState({windowWidth:window.innerWidth});
+ }
+
+ applyFilter = () => {
+ this.filterNotes(this.state.value, this.state.filterTypes);
+ }
+
+ onValueChange = (e, value) => {
+ this.setState({value}, ()=>this.applyFilter())
+ }
+
+ onTypeChange = (type, checked) => {
+ this.setState({filterTypes: {...this.state.filterTypes, [type]: checked}}, () => this.applyFilter())
+ }
+
+ handleAddNote = (note) => {
+ const userID = this.props.user.id;
+ const date = Date.now().toString();
+ const IDHash = userID + date;
+ note["id"] = IDHash.substr(IDHash.length - 24);
+ this.props.addNote(note)
+ }
+
+ filterNotes = (value, filterTypes) => {
+ if (value==="") {
+ this.setState({Notes: this.props.Notes});
+ } else {
+ let filteredNotes = {};
+ let isNoneSelected = true;
+ for (let type of Object.keys(filterTypes)) {
+ if (filterTypes[type]){
+ isNoneSelected = false;
+ break;
+ }
+ }
+ if (isNoneSelected)
+ filterTypes = {title: true, type: true, description: true};
+ for (let noteID in this.props.Notes) {
+ const note = this.props.Notes[noteID]
+ for (let type of Object.keys(filterTypes)) {
+ if (filterTypes[type] && note[type].toUpperCase().includes(value.toUpperCase()))
+ if (filteredNotes[noteID])
+ break;
+ else
+ filteredNotes[noteID] = note;
+ }
+ };
+ this.setState({Notes: filteredNotes});
+ }
+ }
+
+ toggleFilterDrawer = () => {
+ this.setState({filterOpen: !this.state.filterOpen});
+ }
+
+ updateNote = (noteID, note) => {
+ this.props.updateNote(noteID, note, this.props);
+ }
+
+ linkNote = (noteID, object) => {
+ this.props.linkNote(noteID, object, this.props);
+ }
+
+ unlinkNote = (noteID, object) => {
+ this.props.unlinkNote(noteID, object, this.props);
+ }
+
+ linkAndUnlinkNotes = (noteID, linkObjects, unlinkObjects) => {
+ this.props.linkAndUnlinkNotes(noteID, linkObjects, unlinkObjects, this.props);
+ }
+
+ render() {
+ let content = "";
+
+ if (this.props.activeTab==="MANAGE") {
+ content =
+ } else if (this.props.activeTab==="TYPES") {
+ content = this.props.deleteNoteType(noteTypes, this.props) }}
+ togglePopUp={this.props.togglePopUp}
+ tabIndex={this.props.popUpActive?-1:0}
+ />
+ }
+
+ let sidebarClasses = "sidebar";
+ if (this.props.popUpActive) sidebarClasses += " lowerZIndex";
+
+ const sidebar = (
+
+
+
+
+ );
+
+ return (
+
+
+ this.props.changeNotesTab(v)}
+ >
+
+
+
+
+ {sidebar}
+
+ {content}
+
+
+ )
+ }
+}
+const mapStateToProps = (state) => {
+ return {
+ user: state.user,
+ projectID: state.active.project.id,
+ groupIDs: state.active.project.groupIDs,
+ leafIDs: state.active.project.leafIDs,
+ rectoIDs: state.active.project.rectoIDs,
+ versoIDs: state.active.project.versoIDs,
+ Groups: state.active.project.Groups,
+ Leafs: state.active.project.Leafs,
+ Rectos: state.active.project.Rectos,
+ Versos: state.active.project.Versos,
+ Notes: state.active.project.Notes,
+ noteTypes: state.active.project.noteTypes,
+ activeTab: state.active.notesManager.activeTab,
+ notesManager: state.active.notesManager,
+ managerMode: state.active.managerMode,
+ };
+};
+const mapDispatchToProps = (dispatch) => {
+ return {
+ changeManagerMode: (managerMode) => {
+ dispatch(changeManagerMode(managerMode));
+ },
+ changeNotesTab: (tabName) => {
+ dispatch(changeNotesTab(tabName));
+ },
+ addNote: (note) => {
+ dispatch(addNote(note))
+ },
+ updateNote: (noteID, note, props) => {
+ dispatch(updateNote(noteID, note))
+ },
+ deleteNote: (noteID) => {
+ dispatch(deleteNote(noteID));
+ },
+ createNoteType: (noteType) => {
+ dispatch(createNoteType(noteType));
+ },
+ updateNoteType: (noteType) => {
+ dispatch(updateNoteType(noteType));
+ },
+ deleteNoteType: (noteType, props) => {
+ dispatch(deleteNoteType(noteType))
+ },
+ linkNote: (noteID, object, props) => {
+ dispatch(linkNote(noteID, object))
+ },
+ unlinkNote: (noteID, object, props) => {
+ dispatch(unlinkNote(noteID, object))
+ },
+ linkAndUnlinkNotes: (noteID, linkObjects, unlinkObjects, props) => {
+ if (linkObjects.length > 0) {
+ dispatch(linkNote(noteID, linkObjects))
+ }
+ if (unlinkObjects.length > 0) {
+ dispatch(unlinkNote(noteID, unlinkObjects))
+ }
+ },
+ sendFeedback: (title, message, userID) => {
+ dispatch(sendFeedback(title, message, userID))
+ }
+ };
+};
+export default connect(mapStateToProps, mapDispatchToProps)(NotesManager);
diff --git a/viscoll-app/src/containers/Project.js b/viscoll-app/src/containers/Project.js
new file mode 100644
index 00000000..a8bd4d58
--- /dev/null
+++ b/viscoll-app/src/containers/Project.js
@@ -0,0 +1,88 @@
+import React, {Component} from 'react';
+import { connect } from "react-redux";
+import CollationManager from './CollationManager'
+import NotesManager from './NotesManager';
+import ImageManager from './ImageManager';
+import LoadingScreen from "../components/global/LoadingScreen";
+import Notification from "../components/global/Notification";
+import ServerErrorScreen from "../components/global/ServerErrorScreen";
+import NetworkErrorScreen from "../components/global/NetworkErrorScreen";
+import Feedback from "./Feedback";
+import { loadProject } from "../actions/backend/projectActions";
+
+/** Container for 'Manager (Collation or Notes or Image)', `LoadingScreen`, and `Notification`. */
+class Project extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ popUpActive: false,
+ };
+ }
+
+ componentWillMount() {
+ const projectID = this.props.location.pathname.split("/")[2];
+ this.props.user.authenticated ? this.props.loadProject(projectID) : this.props.history.push('/');
+ }
+
+ componentDidUpdate() {
+ if (!this.props.user.authenticated) this.props.history.push('/');
+ }
+
+ togglePopUp = (value) => {
+ this.setState({popUpActive: value});
+ }
+
+ render() {
+ const collationManager = ( );
+ const notesManager = ( );
+ const imageManager = ( );
+ let manager;
+ switch (this.props.managerMode) {
+ case "collationManager":
+ manager = collationManager;
+ break;
+ case "notesManager":
+ manager = notesManager;
+ break;
+ case "imageManager":
+ manager = imageManager;
+ break;
+ default:
+ // Must never reach here.
+ manager = ( Oh No !! Something went wrong
);
+ }
+ return (
+
+ {manager}
+
+
+
+
+
+
+ )
+ }
+}
+
+const mapStateToProps = (state) => {
+ return {
+ user: state.user,
+ managerMode: state.active.managerMode,
+ loading: state.global.loading,
+ notification: state.global.notification,
+ projectID: state.active.project.id,
+ };
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ loadProject: (projectID) => {
+ dispatch(loadProject(projectID))
+ },
+ };
+};
+
+
+export default connect(mapStateToProps, mapDispatchToProps)(Project);
+
diff --git a/viscoll-app/src/containers/ProjectViewOnly.js b/viscoll-app/src/containers/ProjectViewOnly.js
new file mode 100644
index 00000000..799473d4
--- /dev/null
+++ b/viscoll-app/src/containers/ProjectViewOnly.js
@@ -0,0 +1,63 @@
+import React, {Component} from 'react';
+import { connect } from "react-redux";
+import CollationManager from './CollationManagerViewOnly'
+import LoadingScreen from "../components/global/LoadingScreen";
+import Notification from "../components/global/Notification";
+import NetworkErrorScreen from "../components/global/NetworkErrorScreen";
+import { loadProjectViewOnly } from "../actions/backend/projectActions";
+
+/** Container for 'Manager (Collation or Notes or Image)', `LoadingScreen`, and `Notification`. */
+class Project extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ popUpActive: false,
+ };
+ }
+
+ componentWillMount() {
+ const projectID = this.props.location.pathname.split("/")[2];
+ this.props.loadProjectViewOnly(projectID);
+ }
+
+ togglePopUp = (value) => {
+ this.setState({popUpActive: value});
+ }
+
+ render() {
+ return (
+
+
+
+
+
+
+ )
+ }
+}
+
+const mapStateToProps = (state) => {
+ return {
+ user: state.user,
+ loading: state.global.loading,
+ notification: state.global.notification,
+ projectID: state.active.project.id,
+ };
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ loadProjectViewOnly: (projectID) => {
+ dispatch(loadProjectViewOnly(projectID))
+ },
+ };
+};
+
+
+export default connect(mapStateToProps, mapDispatchToProps)(Project);
+
diff --git a/viscoll-app/src/containers/TopBar.js b/viscoll-app/src/containers/TopBar.js
new file mode 100644
index 00000000..209597b9
--- /dev/null
+++ b/viscoll-app/src/containers/TopBar.js
@@ -0,0 +1,237 @@
+import React, {Component} from 'react';
+import Toolbar from 'material-ui/Toolbar';
+import {ToolbarGroup} from 'material-ui/Toolbar';
+import IconMenu from 'material-ui/IconMenu';
+import MenuItem from 'material-ui/MenuItem';
+import IconButton from 'material-ui/IconButton';
+import Avatar from 'material-ui/Avatar';
+import UserProfileForm from '../components/topbar/UserProfileForm';
+import FlatButton from 'material-ui/FlatButton';
+import NotesFilter from "../components/notesManager/NotesFilter";
+import FilterIcon from 'material-ui/svg-icons/content/filter-list';
+import Undo from 'material-ui/svg-icons/content/undo';
+import Redo from 'material-ui/svg-icons/content/redo';
+import Image from 'material-ui/svg-icons/image/image';
+import imgLogo from '../assets/logo_white.svg';
+import {btnBase} from "../styles/button";
+import { connect } from "react-redux";
+import {
+ logout,
+ updateProfile,
+ deleteProfile
+} from "../actions/backend/userActions";
+
+/** The topbar menu used in `Dashboard` and `Project` components */
+class TopBar extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ userProfileModalOpen: false
+ };
+ }
+
+ componentDidMount() {
+ window.addEventListener("keydown", this.shortcutListener);
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener("keydown", this.shortcutListener);
+ }
+
+ shortcutListener = (event) => {
+ if ((event.ctrlKey || event.metaKey) && event.code==="KeyZ") {
+ if (this.props.actionHistory.undo.length>0) this.props.undo();
+ } else if ((event.ctrlKey || event.metaKey) && event.code==="KeyY") {
+ event.preventDefault();
+ if (this.props.actionHistory.redo.length>0) this.props.redo();
+ }
+ }
+
+ handleUserProfileUpdate = (user) => {
+ const userID = this.props.user.id;
+ this.props.updateProfile(user, userID);
+ }
+
+ toggleUserProfile = (userProfileModalOpen=false) => {
+ this.setState({ userProfileModalOpen });
+ this.props.togglePopUp(userProfileModalOpen);
+ }
+
+ handleUserAccountDelete = () => {
+ const userID = this.props.user.id;
+ this.props.deleteProfile(userID);
+ }
+
+ handleUserLogout = () => {
+ this.props.logoutUser();
+ }
+
+ goHome = () => {
+ if (this.props.history.location.pathname.includes("dashboard")) {
+ this.props.goToDashboardProjectList();
+ } else {
+ this.props.resetActionHistory();
+ this.props.history.push('/dashboard');
+ }
+ }
+
+ render() {
+ // User icon menu on the right corner of Toolbar
+ let UserMenu;
+ if (this.props.user.name) {
+ UserMenu = (
+
+ {this.props.user.name.charAt(0).toUpperCase()}
+ }
+ targetOrigin={{horizontal: 'right', vertical: 'top'}}
+ anchorOrigin={{horizontal: 'right', vertical: 'bottom'}}
+ >
+ this.toggleUserProfile(true)}
+ />
+ this.handleUserLogout()}
+ />
+
+ );
+ }
+
+ let topbarClasses = "topbar";
+ if (this.props.popUpActive) topbarClasses += " lowerZIndex"
+ let imageViewerTitle="";
+ if (this.props.windowWidth>768 && this.props.imageViewerEnabled) {
+ imageViewerTitle = "Hide image viewer";
+ } else if (this.props.windowWidth>768) {
+ imageViewerTitle = "Image viewer";
+ }
+ return (
+
+
+
+
+
+
+ {this.props.children}
+
+
+ {this.props.showUndoRedo?
+
+ {e.preventDefault();this.props.undo()}}
+ >
+
+
+ {e.preventDefault();this.props.redo()}}
+ >
+
+
+
+ : null
+ }
+
+ {this.props.showImageViewerButton ?
+ this.props.toggleImageViewer()}
+ icon={}
+ labelStyle={{...btnBase().labelStyle, padding:this.props.windowWidth<=1024?"0px 10px 0px 0px":10}}
+ style={{...btnBase().style, marginRight: 5, }}
+ tabIndex={this.props.tabIndex}
+ />
+ : null
+ }
+ {this.props.toggleFilterDrawer?
+
+ {e.preventDefault();this.props.toggleFilterDrawer()}}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+
+ : null
+ }
+ {this.props.notesFilter ? : null}
+
+ {UserMenu}
+
+
+
+
+ )
+ }
+}
+const mapStateToProps = (state) => {
+ return {
+ user: state.user,
+ notes: state.active.notes,
+ actionHistory: state.history,
+ };
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ logoutUser: () => {
+ dispatch(logout());
+ },
+ updateProfile: (user, userID) => {
+ dispatch(updateProfile(user, userID));
+ },
+ deleteProfile: (userID) => {
+ dispatch(deleteProfile(userID));
+ },
+ undo: () => {
+ dispatch({type:"UNDO"})
+ },
+ redo: () => {
+ dispatch({type:"REDO"})
+ },
+ resetActionHistory: () => {
+ dispatch({type:"CLEAR_ACTION_HISTORY"})
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(TopBar);
+
+
diff --git a/viscoll-app/src/helpers/MultiSelectAutoComplete.js b/viscoll-app/src/helpers/MultiSelectAutoComplete.js
new file mode 100644
index 00000000..3ee3e098
--- /dev/null
+++ b/viscoll-app/src/helpers/MultiSelectAutoComplete.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import SuperSelectField from 'material-ui-superselectfield';
+
+const selectionsRenderer = (values) => {
+ if (values.length===1) return "1 item selected"
+ if (values.length>1) return `${values.length} items selected`
+ return "Select item(s)..."
+}
+
+const MultiSelectAutoComplete = (props) => {
+ return (
+
+ {props.children}
+
+ );
+}
+
+export default MultiSelectAutoComplete;
diff --git a/viscoll-app/src/helpers/getLeafsOfGroup.js b/viscoll-app/src/helpers/getLeafsOfGroup.js
new file mode 100644
index 00000000..575d9bc2
--- /dev/null
+++ b/viscoll-app/src/helpers/getLeafsOfGroup.js
@@ -0,0 +1,13 @@
+export function getLeafsOfGroup(group, Leafs, includeNone=true){
+ let leafMembersOfCurrentGroup = [];
+ if (includeNone) {
+ leafMembersOfCurrentGroup.push({
+ order: "None",
+ id: "None",
+ })
+ }
+ for (let memberID of group.memberIDs) {
+ if (memberID.charAt(0)==="L") leafMembersOfCurrentGroup.push(Leafs[memberID]);
+ }
+ return leafMembersOfCurrentGroup;
+}
diff --git a/viscoll-app/src/helpers/getMemberOrder.js b/viscoll-app/src/helpers/getMemberOrder.js
new file mode 100644
index 00000000..3b1a98d5
--- /dev/null
+++ b/viscoll-app/src/helpers/getMemberOrder.js
@@ -0,0 +1,10 @@
+export function getMemberOrder(member, Groups, groupIDs) {
+ const parentID = member.parentID
+ if (parentID){
+ return Groups[parentID].memberIDs.indexOf(member.id)+1
+ } else {
+ // member is a root Group. Find member order from groupIDs
+ return groupIDs.indexOf(member.id)+1
+ }
+
+}
diff --git a/viscoll-app/src/helpers/renderHelper.js b/viscoll-app/src/helpers/renderHelper.js
new file mode 100644
index 00000000..65ef06af
--- /dev/null
+++ b/viscoll-app/src/helpers/renderHelper.js
@@ -0,0 +1,17 @@
+import React from 'react';
+import Chip from 'material-ui/Chip';
+
+export function renderNoteChip(props, note) {
+ let deleteFn = () => {props.action.unlinkNote(note.id)};
+ if (props.isReadOnly) deleteFn = null;
+ return props.openNoteDialog(note)}
+ tabIndex={props.tabIndex}
+ labelStyle={{fontSize:props.windowWidth<=1024?12:null}}
+ >
+ {note.title}
+
+}
\ No newline at end of file
diff --git a/viscoll-app/src/index.js b/viscoll-app/src/index.js
new file mode 100644
index 00000000..32e45730
--- /dev/null
+++ b/viscoll-app/src/index.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import App from './containers/App';
+import registerServiceWorker from './registerServiceWorker';
+import './styles/index.css';
+
+
+ReactDOM.render(
+ ,
+ document.getElementById('root')
+);
+registerServiceWorker();
+
diff --git a/viscoll-app/src/reducers/dashboardReducer.js b/viscoll-app/src/reducers/dashboardReducer.js
new file mode 100644
index 00000000..4f649bc2
--- /dev/null
+++ b/viscoll-app/src/reducers/dashboardReducer.js
@@ -0,0 +1,68 @@
+import { initialState } from './initialStates/projects';
+
+export default function dashboardReducer(state=initialState, action) {
+ try {
+ if (action.error) {
+ action = {type: action.type, payload: action.error.response.data}
+ }
+ } catch (e) {}
+
+ switch(action.type) {
+ case "LOAD_PROJECT_SUCCESS":
+ state = action.payload.dashboard
+ break;
+ case "LOAD_PROJECTS_SUCCESS":
+ case "CREATE_PROJECT_SUCCESS":
+ case "CLONE_PROJECT_IMPORT_SUCCESS":
+ case "IMPORT_MANIFEST_SUCCESS":
+ state = action.payload;
+ break;
+ case "IMPORT_PROJECT_SUCCESS":
+ state = {
+ projects: action.payload.projects,
+ images: action.payload.images,
+ importStatus: "SUCCESS"
+ }
+ break;
+ case "IMPORT_PROJECT_SUCCESS_CALLBACK":
+ state = {...state, importStatus: null}
+ break;
+ case "LOGOUT_SUCCESS":
+ case "DELETE_PROFILE_SUCCESS":
+ state = initialState
+ break;
+ case "IMPORT_PROJECT_FAILED":
+ state = {
+ ...state,
+ importStatus: action.payload.error
+ }
+ break;
+ case "CREATE_PROJECT_FAILED":
+ case "LOAD_PROJECTS_FAILED":
+ case "CLONE_PROJECT_IMPORT_FAILED":
+ break;
+
+
+ // FRONT-END ACTIONS
+ case "UPDATE_PROJECT_FRONTEND":
+ case "DELETE_PROJECT_FRONTEND":
+ state = action.payload.response
+ break;
+ case "LINK_IMAGES_FRONTEND":
+ case "UNLINK_IMAGES_FRONTEND":
+ case "DELETE_IMAGES_FRONTEND":
+ case "MAP_SIDES_FRONTEND":
+ state = action.payload.response.dashboard
+ break;
+ case "UPLOAD_IMAGES_SUCCESS_BACKEND":
+ state = action.payload.response.dashboard
+ break;
+ case "DELETE_PROJECT_SUCCESS_BACKEND":
+ state = action.payload
+ break;
+ default:
+ break;
+ }
+ return state;
+}
+
diff --git a/viscoll-app/src/reducers/editCollationReducer.js b/viscoll-app/src/reducers/editCollationReducer.js
new file mode 100644
index 00000000..af8d30b6
--- /dev/null
+++ b/viscoll-app/src/reducers/editCollationReducer.js
@@ -0,0 +1,161 @@
+import { initialState } from './initialStates/active';
+import { cloneDeep } from 'lodash';
+
+export default function editCollationReducer(state=initialState, action) {
+ try {
+ if (action.error) { action = {type: action.type, payload: action.error.response.data} }
+ } catch (e) { }
+
+ if (!action.type.includes("FRONTEND") && action.type!=="UPLOAD_IMAGES_SUCCESS_BACKEND")
+ state = cloneDeep(state)
+ switch(action.type) {
+ // MODIFICATIONS
+ case "LOAD_PROJECT_SUCCESS":
+ state.project = action.payload.active
+ break;
+ case "LOAD_PROJECT_VIEW_ONLY_SUCCESS":
+ state.project = {
+ ...action.payload.project,
+ ...action.payload,
+ Groups: action.payload.groups,
+ Leafs: action.payload.leafs,
+ Rectos: action.payload.rectos,
+ Versos: action.payload.versos,
+ Notes: action.payload.notes
+ }
+ break;
+ case "CREATE_MANIFEST_SUCCESS":
+ state.project = action.payload.active
+ state.imageManager.manageSources.error = ""
+ break;
+ case "CREATE_MANIFEST_FAILED":
+ state.imageManager.manageSources.error = action.payload.errors
+ break;
+ case "CANCEL_CREATE_MANIFEST_FRONTEND":
+ state.imageManager.manageSources.error = ""
+ break;
+ case "TOGGLE_VISUALIZATION_DRAWING":
+ state.collationManager.visualizations[action.payload.type] = action.payload.value
+ break;
+ case "LOGOUT_SUCCESS":
+ case "DELETE_PROFILE_SUCCESS":
+ case "LOAD_PROJECTS_SUCCESS":
+ state = initialState;
+ break;
+ case "LOAD_PROJECT_FAILED":
+ case "UPLOAD_IMAGES_FAILED":
+ case "FILTER_PROJECT_FAILED":
+ case "EXPORT_FAILED":
+ break;
+ case "HIDE_PROJECT_TIP":
+ state.project.preferences.showTips = false
+ break;
+ case "CHANGE_VIEW_MODE":
+ state.collationManager.viewMode = action.payload
+ break;
+ case "CHANGE_MANAGER_MODE":
+ state.managerMode = action.payload
+ break;
+ case "CHANGE_NOTES_TAB":
+ state.notesManager.activeTab = action.payload
+ break;
+ case "CHANGE_IMAGES_TAB":
+ state.imageManager.activeTab = action.payload
+ break;
+ case "TOGGLE_FILTER_PANEL":
+ state.collationManager.filters.filterPanelOpen = action.payload
+ break;
+ case "TOGGLE_SELECTED_OBJECTS":
+ case "UPDATE_CURRENT_SELECTED_OBJECTS":
+ state.collationManager.selectedObjects = action.payload
+ break;
+ case "FILTER_PROJECT_SUCCESS":
+ state.collationManager.filters = {
+ ...state.collationManager.filters,
+ ...action.payload,
+ active: true
+ }
+ state.project.preferences = {
+ group: { ...action.payload.visibleAttributes.group},
+ leaf: { ...action.payload.visibleAttributes.leaf},
+ side: { ...action.payload.visibleAttributes.side},
+ }
+ delete state.collationManager.filters["visibleAttributes"];
+ break;
+ case "RESET_FILTERS":
+ state.collationManager.filters = {
+ ...initialState.collationManager.filters,
+ filterPanelOpen: true,
+ queries: action.payload
+ }
+ break;
+ case "TOGGLE_FILTER_DISPLAY":
+ state.collationManager.filters.hideOthers = !state.collationManager.filters.hideOthers
+ break;
+ case "UPDATE_FILTER_QUERY":
+ state.collationManager.filters.queries = action.payload
+ break;
+ case "UPDATE_FILTER_SELECTION":
+ state.collationManager.filters.selection = action.payload.selection
+ state.collationManager.filters.hideOthers = false
+ state.collationManager.selectedObjects = action.payload.selectedObjects
+ break;
+ case "UNFLASH":
+ state.collationManager.flashItems.groups = []
+ state.collationManager.flashItems.leaves = []
+ break;
+ case "EXPORT_SUCCESS":
+ state.exportedData = action.payload.type === "xml" ? action.payload.data : JSON.stringify(action.payload.Export, null, 4);
+ state.exportedImages = action.payload.Images.exportedImages
+ break;
+
+ // FRONT-END ACTIONS
+ case "CREATE_NOTETYPE_FRONTEND":
+ case "UPDATE_NOTETYPE_FRONTEND":
+ case "DELETE_NOTETYPE_FRONTEND":
+ case "UPDATE_NOTE_FRONTEND":
+ case "DELETE_NOTE_FRONTEND":
+ case "LINK_NOTE_FRONTEND":
+ case "UNLINK_NOTE_FRONTEND":
+ case "AUTOCONJOIN_LEAFS_FRONTEND":
+ case "CREATE_GROUPS_FRONTEND":
+ case "UPDATE_GROUP_FRONTEND":
+ case "UPDATE_GROUPS_FRONTEND":
+ case "DELETE_GROUP_FRONTEND":
+ case "DELETE_GROUPS_FRONTEND":
+ case "CREATE_LEAVES_FRONTEND":
+ case "UPDATE_LEAF_FRONTEND":
+ case "UPDATE_LEAVES_FRONTEND":
+ case "DELETE_LEAF_FRONTEND":
+ case "DELETE_LEAVES_FRONTEND":
+ case "UPDATE_SIDE_FRONTEND":
+ case "UPDATE_SIDES_FRONTEND":
+ case "UPDATE_MANIFEST_FRONTEND":
+ case "DELETE_MANIFEST_FRONTEND":
+ case "CREATE_NOTE_FRONTEND":
+ case "GENERATE_FOLIO_NUMBERS_FRONTEND":
+ case "GENERATE_PAGE_NUMBERS_FRONTEND":
+ state = action.payload.response
+ break;
+ case "UPDATE_PREFERENCES_FRONTEND":
+ const showTips = action.payload.response.project.preferences.showTips!==undefined?action.payload.response.project.preferences.showTips:state.project.preferences.showTips;
+ state.project.preferences = {
+ showTips,
+ group: {...state.project.preferences.group, ...action.payload.response.project.preferences.group},
+ leaf: {...state.project.preferences.leaf, ...action.payload.response.project.preferences.leaf},
+ side: {...state.project.preferences.side, ...action.payload.response.project.preferences.side},
+ }
+ break;
+ case "LINK_IMAGES_FRONTEND":
+ case "UNLINK_IMAGES_FRONTEND":
+ case "DELETE_IMAGES_FRONTEND":
+ case "MAP_SIDES_FRONTEND":
+ case "UPLOAD_IMAGES_SUCCESS_BACKEND":
+ state = action.payload.response.active
+ break;
+ default:
+ break;
+ }
+ return state;
+}
+
diff --git a/viscoll-app/src/reducers/globalReducer.js b/viscoll-app/src/reducers/globalReducer.js
new file mode 100644
index 00000000..71200409
--- /dev/null
+++ b/viscoll-app/src/reducers/globalReducer.js
@@ -0,0 +1,35 @@
+import { initialState } from './initialStates/global';
+
+export default function globalReducer(state=initialState, action) {
+ if (action.error && action.error.status===0) {
+ state = {...state, serverError: true}
+ }
+ switch(action.type) {
+ case "SHOW_LOADING":
+ state = {...state, loading: true}
+ break;
+ case "HIDE_LOADING":
+ state = {...state, loading: false}
+ break;
+ case "SHOW_NOTIFICATION":
+ state = {...state, notification: action.payload}
+ break;
+ case "HIDE_NOTIFICATION":
+ state = {...state, notification: ""}
+ break;
+ case "UPDATE_LOADING_COUNT":
+ state = {...state, loadingRequestCount: action.payload};
+ break;
+ case "DELETE_PROFILE_SUCCESS":
+ case "LOGOUT_SUCCESS":
+ state = initialState
+ break;
+ case "BACKEND_SERVER_ERROR":
+ state = {...state, serverError: true}
+ break;
+ default:
+ break;
+ }
+ return state;
+}
+
diff --git a/viscoll-app/src/reducers/historyReducer.js b/viscoll-app/src/reducers/historyReducer.js
new file mode 100644
index 00000000..2c4a43be
--- /dev/null
+++ b/viscoll-app/src/reducers/historyReducer.js
@@ -0,0 +1,21 @@
+import { initialState } from './initialStates/history';
+
+export default function historyReducer(state=initialState, action) {
+ switch(action.type) {
+ case "UNDO":
+ case "PUSH_UNDO":
+ state.undo = action.payload;
+ break;
+ case "REDO":
+ case "PUSH_REDO":
+ state.redo = action.payload;
+ break;
+ case "CLEAR_ACTION_HISTORY":
+ state.undo = [];
+ state.redo = [];
+ break;
+ default:
+ break;
+ }
+ return state;
+}
\ No newline at end of file
diff --git a/viscoll-app/src/reducers/initialStates/active.js b/viscoll-app/src/reducers/initialStates/active.js
new file mode 100644
index 00000000..eef5139c
--- /dev/null
+++ b/viscoll-app/src/reducers/initialStates/active.js
@@ -0,0 +1,181 @@
+export const initialState = {
+ project: {
+ id: "",
+ title: "",
+ shelfmark: "",
+ uri: "",
+ metadata: {
+ date: ""
+ },
+ manifests: {
+ "DIYImages": {
+ id: "DIYImages",
+ name: "Uploaded Images",
+ images: [],
+ }
+ },
+ groupIDs: [],
+ leafIDs: [],
+ rectoIDs: [],
+ versoIDs: [],
+ Groups: {},
+ Leafs: {},
+ Rectos: {},
+ Versos: {},
+ noteTypes: [],
+ Notes: {},
+ preferences: {
+ showTips: true
+ }
+ },
+
+ managerMode: "collationManager",
+ collationManager: {
+ selectedObjects: {
+ type: "",
+ members: [],
+ lastSelected: ""
+ },
+ viewMode: "VISUAL",
+ defaultAttributes: {
+ leaf: [
+ {
+ name: 'type',
+ displayName: 'Type',
+ options: ['None', 'Original', 'Added', 'Missing', 'Hook', 'Endleaf', 'Replaced'],
+ isDropdown: true,
+ },
+ {
+ name: 'material',
+ displayName: 'Material',
+ options: ['None', 'Parchment', 'Paper', 'Other'],
+ isDropdown: true,
+ },
+ {
+ name: 'conjoined_to',
+ displayName: 'Conjoined To',
+ isDropdown: true,
+ },
+ {
+ name: 'attached_above',
+ displayName: 'Attached Above',
+ options: ['None', 'Glued (Partial)', 'Glued (Complete)', 'Glued (Drumming)', 'Other'],
+ isDropdown: true,
+ },
+ {
+ name: 'attached_below',
+ displayName: 'Attached Below',
+ options: ['None', 'Glued (Partial)', 'Glued (Complete)', 'Glued (Drumming)', 'Other'],
+ isDropdown: true,
+ },
+ {
+ name: 'stub',
+ displayName: 'Stub',
+ options: ['None', 'Original', 'Added'],
+ isDropdown: true,
+ },
+ ],
+ group: [
+ {
+ name: 'type',
+ displayName: 'Type',
+ options: ['Quire', 'Booklet'],
+ isDropdown: true,
+ },
+ {
+ name: 'title',
+ displayName: 'Title',
+ },
+ ],
+ side: [
+ {
+ name: 'texture',
+ displayName: 'Texture',
+ options: ['None', 'Hair', 'Flesh', 'Felt', 'Wire'],
+ isDropdown: true,
+ },
+ {
+ name: 'folio_number',
+ displayName: 'Folio Number',
+ },
+ {
+ name: 'page_number',
+ displayName: 'Page Number',
+ },
+ {
+ name: 'script_direction',
+ displayName: 'Script Direction',
+ options: ['None', 'Left-to-Right', 'Right-To-Left', 'Top-To-Bottom'],
+ isDropdown: true,
+ },
+ {
+ name: 'uri',
+ displayName: 'URI',
+ },
+ ],
+ note: [
+ {
+ name: 'title',
+ displayName: 'Title',
+ },
+ {
+ name: 'type',
+ displayName: 'Type',
+ isDropdown: true,
+ },
+ {
+ name: 'description',
+ displayName: 'Description',
+ },
+ ]
+ },
+ filters: {
+ filterPanelOpen: false,
+ Groups: [],
+ Leafs: [],
+ Sides: [],
+ Notes: [],
+ GroupsOfMatchingLeafs: [],
+ LeafsOfMatchingSides: [],
+ GroupsOfMatchingSides: [],
+ GroupsOfMatchingNotes: [],
+ LeafsOfMatchingNotes: [],
+ SidesOfMatchingNotes: [],
+ active: false,
+ hideOthers: false,
+ queries: [
+ {
+ type: null,
+ attribute: "",
+ attributeIndex: "",
+ values: [],
+ condition: "",
+ conjunction: "",
+ }
+ ],
+ selection: ""
+ },
+ flashItems: {
+ leaves: [],
+ groups: []
+ },
+ visualizations: {
+ tacketed: "",
+ sewing: "",
+ }
+ },
+ notesManager: {
+ activeTab: "MANAGE",
+ },
+ imageManager: {
+ activeTab: "MANAGE",
+ manageSources: {
+ error: ""
+ }
+ },
+ exportedData: "",
+ exportedImages: ""
+};
+
+
+export default initialState;
diff --git a/viscoll-app/src/reducers/initialStates/global.js b/viscoll-app/src/reducers/initialStates/global.js
new file mode 100644
index 00000000..0b85f428
--- /dev/null
+++ b/viscoll-app/src/reducers/initialStates/global.js
@@ -0,0 +1,9 @@
+export const initialState = {
+ loading: false,
+ loadingRequestCount: 0,
+ notification: "",
+ serverError: false,
+ unauthorizedError: false,
+};
+
+export default initialState;
diff --git a/viscoll-app/src/reducers/initialStates/history.js b/viscoll-app/src/reducers/initialStates/history.js
new file mode 100644
index 00000000..cc205840
--- /dev/null
+++ b/viscoll-app/src/reducers/initialStates/history.js
@@ -0,0 +1,5 @@
+export const initialState = {
+ undo: [],
+ redo: [],
+}
+export default initialState;
\ No newline at end of file
diff --git a/viscoll-app/src/reducers/initialStates/projects.js b/viscoll-app/src/reducers/initialStates/projects.js
new file mode 100644
index 00000000..52b3099d
--- /dev/null
+++ b/viscoll-app/src/reducers/initialStates/projects.js
@@ -0,0 +1,7 @@
+export const initialState = {
+ projects: [],
+ images: [],
+ importStatus: null
+};
+
+export default initialState;
diff --git a/viscoll-app/src/reducers/initialStates/user.js b/viscoll-app/src/reducers/initialStates/user.js
new file mode 100644
index 00000000..dd93ac08
--- /dev/null
+++ b/viscoll-app/src/reducers/initialStates/user.js
@@ -0,0 +1,12 @@
+export const initialState = {
+ authenticated: false,
+ token: "",
+ errors: {
+ login: {errorMessage: ""},
+ register: {email: "", password: ""},
+ update: {password: "", current_password: "", email: ""},
+ confirmation: "",
+ }
+}
+
+export default initialState;
diff --git a/viscoll-app/src/reducers/userReducer.js b/viscoll-app/src/reducers/userReducer.js
new file mode 100644
index 00000000..3e95ce68
--- /dev/null
+++ b/viscoll-app/src/reducers/userReducer.js
@@ -0,0 +1,105 @@
+import { initialState } from './initialStates/user';
+
+export default function userReducer(state=initialState, action) {
+ try {
+ if (action.error) action = {type: action.type, payload: action.error.response.data}
+ } catch (e) {}
+ let errorMessage = "";
+ state.notification = "";
+ switch(action.type) {
+ case "persist/REHYDRATE":
+ state = {...state, ...action.payload.user, errors: initialState.errors}
+ delete state.registerSuccess
+ break;
+ case "LOGIN_SUCCESS":
+ state = {
+ ...state,
+ id: action.payload.session.id,
+ name: action.payload.session.name,
+ email: action.payload.session.email,
+ token: action.payload.session.jwt,
+ authenticated: true,
+ lastLoggedIn: action.payload.session.lastLoggedIn,
+ preferences: action.payload.session.preferences,
+ }
+ break;
+ case "LOGIN_FAILED":
+ if (action.payload && action.payload.errors) errorMessage = action.payload.errors.session;
+ if (action.error) errorMessage = [action.error.data];
+ state = {
+ ...state,
+ errors: {
+ ...state.errors,
+ login: {
+ errorMessage,
+ },
+ }
+ }
+ break;
+ case "REGISTER_SUCCESS":
+ state = {
+ ...state,
+ registerSuccess: true
+ }
+ break;
+ case "REGISTER_FAILED":
+ state = {
+ ...state,
+ errors: {
+ ...state.errors,
+ register: action.payload.errors
+ }
+ }
+ break;
+ case "UPDATE_PROFILE_SUCCESS":
+ state = {
+ ...state,
+ errors: initialState.errors,
+ ...action.payload
+ }
+ break;
+ case "UPDATE_PROFILE_FAILED":
+ state = {
+ ...state,
+ errors: {
+ ...state.errors,
+ update: {...state.errors.update, ...action.payload}
+ }
+ }
+ break;
+ case "LOGOUT_SUCCESS":
+ case "DELETE_PROFILE_SUCCESS":
+ state = initialState
+ break;
+ case "CONFIRM_SUCCESS":
+ state = {
+ ...state,
+ notification: "Successfully confirmed your account!",
+ }
+ break;
+ case "REQUEST_RESET_SUCCESS":
+ case "REQUEST_RESET_FAILED":
+ case "RESET_SUCCESS":
+ case "RESET_FAILED":
+ case "LOGOUT_FAILED":
+ case "DELETE_PROFILE_FAILED":
+ break;
+ case "CONFIRM_FAILED":
+ errorMessage = "Error confirming your account!";
+ if (action.payload.errors.confirmation_token.length>0) {
+ errorMessage = "Confirmation token " + action.payload.errors.confirmation_token[0];
+ }
+ state = {
+ ...state,
+ errors: {
+ ...state.errors,
+ confirmation: errorMessage,
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ return state;
+}
diff --git a/viscoll-app/src/registerServiceWorker.js b/viscoll-app/src/registerServiceWorker.js
new file mode 100644
index 00000000..9966897d
--- /dev/null
+++ b/viscoll-app/src/registerServiceWorker.js
@@ -0,0 +1,51 @@
+// In production, we register a service worker to serve assets from local cache.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on the "N+1" visit to a page, since previously
+// cached resources are updated in the background.
+
+// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
+// This link also includes instructions on opting out of this behavior.
+
+export default function register() {
+ if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+ window.addEventListener('load', () => {
+ const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+ navigator.serviceWorker
+ .register(swUrl)
+ .then(registration => {
+ registration.onupdatefound = () => {
+ const installingWorker = registration.installing;
+ installingWorker.onstatechange = () => {
+ if (installingWorker.state === 'installed') {
+ if (navigator.serviceWorker.controller) {
+ // At this point, the old content will have been purged and
+ // the fresh content will have been added to the cache.
+ // It's the perfect time to display a "New content is
+ // available; please refresh." message in your web app.
+ console.log('New content is available; please refresh.');
+ } else {
+ // At this point, everything has been precached.
+ // It's the perfect time to display a
+ // "Content is cached for offline use." message.
+ console.log('Content is cached for offline use.');
+ }
+ }
+ };
+ };
+ })
+ .catch(error => {
+ console.error('Error during service worker registration:', error);
+ });
+ });
+ }
+}
+
+export function unregister() {
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister();
+ });
+ }
+}
diff --git a/viscoll-app/src/store/axiosConfig.js b/viscoll-app/src/store/axiosConfig.js
new file mode 100644
index 00000000..1c6da75f
--- /dev/null
+++ b/viscoll-app/src/store/axiosConfig.js
@@ -0,0 +1,45 @@
+import axios from "axios";
+
+export let API_URL = '/api';
+
+// IN DEVELOPMENT
+if (process.env.NODE_ENV === 'development') {
+ API_URL = 'http://localhost:3001'
+}
+export const client = axios.create({
+ baseURL: API_URL,
+ responseType: 'json'
+});
+
+export const clientOptions = {
+ interceptors: {
+ request: [
+ ({getState, dispatch, getSourceAction}, request) => {
+ if (getState().user.token) request.headers['Authorization'] = getState().user.token
+ return request;
+ }
+ ],
+ response: [{
+ success: function ({getState, dispatch, getSourceAction}, response) {
+ if (getState().global.loading) {
+ if (getState().global.loadingRequestCount>0)
+ dispatch({type:"UPDATE_LOADING_COUNT", payload:getState().global.loadingRequestCount-1})
+ if (getState().global.loadingRequestCount<=1)
+ dispatch({ type: "HIDE_LOADING" });
+ }
+ return Promise.resolve(response.data);
+ },
+ error: function ({getState, dispatch, getSourceAction}, error) {
+ if (getState().global.loading) dispatch({ type: "HIDE_LOADING" });
+ if (error.config.errorMessage) {
+ dispatch({ type: "SHOW_NOTIFICATION", payload: error.config.errorMessage });
+ setTimeout(()=>dispatch({type: "HIDE_NOTIFICATION"}), 4000);
+ }
+ if (error.response && (error.response.status===401 || error.response.status!==422)) {
+ dispatch({type: "BACKEND_SERVER_ERROR"});
+ }
+ return Promise.reject(error);
+ }
+ }]
+ }
+}
diff --git a/viscoll-app/src/store/middleware/frontendAfterActionsMiddleware.js b/viscoll-app/src/store/middleware/frontendAfterActionsMiddleware.js
new file mode 100644
index 00000000..3685cada
--- /dev/null
+++ b/viscoll-app/src/store/middleware/frontendAfterActionsMiddleware.js
@@ -0,0 +1,19 @@
+import { cloneDeep } from 'lodash';
+import {
+ updateImagesAfterUpload,
+} from '../../actions/frontend/after/imageActions';
+
+const frontendAfterActionsMiddleware = store => next => action => {
+ switch (action.type) {
+ // Image Actions
+ case "UPLOAD_IMAGES_SUCCESS_BACKEND":
+ action.payload.response = updateImagesAfterUpload(action, cloneDeep(store.getState().dashboard), cloneDeep(store.getState().active))
+ break;
+ default:
+ break;
+ }
+ next(action);
+}
+
+
+export default frontendAfterActionsMiddleware;
\ No newline at end of file
diff --git a/viscoll-app/src/store/middleware/frontendBeforeActionsMiddleware.js b/viscoll-app/src/store/middleware/frontendBeforeActionsMiddleware.js
new file mode 100644
index 00000000..0c479ef5
--- /dev/null
+++ b/viscoll-app/src/store/middleware/frontendBeforeActionsMiddleware.js
@@ -0,0 +1,182 @@
+import {cloneDeep} from 'lodash';
+
+import {
+ updateProject,
+ updatePreferences,
+ deleteProject
+} from '../../actions/frontend/before/projectActions';
+
+import {
+ createNoteType,
+ updateNoteType,
+ deleteNoteType,
+ createNote,
+ updateNote,
+ linkNote,
+ unlinkNote,
+ deleteNote
+} from '../../actions/frontend/before/noteActions';
+
+import {
+ createGroups,
+ updateGroup,
+ updateGroups,
+ deleteGroup,
+ deleteGroups
+} from '../../actions/frontend/before/groupActions';
+
+import {
+ updateSide,
+ updateSides,
+ mapSides,
+} from '../../actions/frontend/before/sideActions';
+
+import {
+ autoConjoinLeafs,
+ createLeaves,
+ updateLeaf,
+ updateLeaves,
+ deleteLeaf,
+ deleteLeaves,
+ generateFolioPageNumbers
+} from '../../actions/frontend/before/leafActions';
+
+import {
+ linkImages,
+ unlinkImages,
+ deleteImages
+} from '../../actions/frontend/before/imageActions';
+
+import {
+ updateManifest,
+ deleteManifest
+} from '../../actions/frontend/before/manifestActions';
+
+
+const frontendBeforeActionsMiddleware = store => next => action => {
+ if (action.type.includes("FRONTEND")){
+ if (action.payload.request.successMessage && action.payload.request.successMessage!=="" && !action.isUndo && !action.isRedo){
+ next({ type: "SHOW_NOTIFICATION", payload: action.payload.request.successMessage });
+ setTimeout(()=>next({type: "HIDE_NOTIFICATION"}), 4000);
+ }
+ }
+ switch(action.type) {
+ // Project Actions
+ case "UPDATE_PROJECT_FRONTEND":
+ action.payload.response = updateProject(action, cloneDeep(store.getState().dashboard))
+ break;
+ case "UPDATE_PREFERENCES_FRONTEND":
+ action.payload.response = updatePreferences(action, cloneDeep(store.getState().active))
+ break;
+ case "DELETE_PROJECT_FRONTEND":
+ action.payload.response = deleteProject(action, cloneDeep(store.getState().dashboard))
+ break;
+ // NoteType Actions
+ case "CREATE_NOTETYPE_FRONTEND":
+ action.payload.response = createNoteType(action, cloneDeep(store.getState().active))
+ break;
+ case "UPDATE_NOTETYPE_FRONTEND":
+ action.payload.response = updateNoteType(action, cloneDeep(store.getState().active))
+ break;
+ case "DELETE_NOTETYPE_FRONTEND":
+ action.payload.response = deleteNoteType(action, cloneDeep(store.getState().active))
+ break;
+ // Note Actions
+ case "CREATE_NOTE_FRONTEND":
+ action.payload.response = createNote(action, cloneDeep(store.getState().active))
+ break;
+ case "UPDATE_NOTE_FRONTEND":
+ action.payload.response = updateNote(action, cloneDeep(store.getState().active))
+ break;
+ case "LINK_NOTE_FRONTEND":
+ action.payload.response = linkNote(action, cloneDeep(store.getState().active))
+ break;
+ case "UNLINK_NOTE_FRONTEND":
+ action.payload.response = unlinkNote(action, cloneDeep(store.getState().active))
+ break;
+ case "DELETE_NOTE_FRONTEND":
+ action.payload.response = deleteNote(action, cloneDeep(store.getState().active))
+ break;
+ // Group Actions
+ case "CREATE_GROUPS_FRONTEND":
+ action.payload.response = createGroups(action, cloneDeep(store.getState().active))
+ setTimeout(()=>next({type: "UNFLASH"}), 5000)
+ break;
+ case "UPDATE_GROUP_FRONTEND":
+ action.payload.response = updateGroup(action, cloneDeep(store.getState().active))
+ break;
+ case "UPDATE_GROUPS_FRONTEND":
+ action.payload.response = updateGroups(action, cloneDeep(store.getState().active))
+ break;
+ case "DELETE_GROUP_FRONTEND":
+ const deletedGroupID = action.payload.request.url.split("/").pop()
+ action.payload.response = deleteGroup(deletedGroupID, cloneDeep(store.getState().active))
+ break;
+ case "DELETE_GROUPS_FRONTEND":
+ const deletedGroupIDs = action.payload.request.data.groups
+ action.payload.response = deleteGroups(deletedGroupIDs, cloneDeep(store.getState().active))
+ break;
+ // Leaf Actions
+ case "GENERATE_FOLIO_NUMBERS_FRONTEND":
+ action.payload.response = generateFolioPageNumbers(action, cloneDeep(store.getState().active), "folio_number")
+ break;
+ case "GENERATE_PAGE_NUMBERS_FRONTEND":
+ action.payload.response = generateFolioPageNumbers(action, cloneDeep(store.getState().active), "page_number")
+ break;
+ case "AUTOCONJOIN_LEAFS_FRONTEND":
+ let leafIDsToAutoConjoin = action.payload.request.data.leafs
+ action.payload.response = autoConjoinLeafs(action, cloneDeep(store.getState().active), leafIDsToAutoConjoin)
+ break;
+ case "CREATE_LEAVES_FRONTEND":
+ action.payload.response = createLeaves(action, cloneDeep(store.getState().active))
+ setTimeout(()=>next({type: "UNFLASH"}), 5000)
+ break;
+ case "UPDATE_LEAF_FRONTEND":
+ action.payload.response = updateLeaf(action, cloneDeep(store.getState().active))
+ break;
+ case "UPDATE_LEAVES_FRONTEND":
+ action.payload.response = updateLeaves(action, cloneDeep(store.getState().active))
+ break;
+ case "DELETE_LEAF_FRONTEND":
+ const deletedLeafID = action.payload.request.url.split("/").pop()
+ action.payload.response = deleteLeaf(deletedLeafID, cloneDeep(store.getState().active))
+ break;
+ case "DELETE_LEAVES_FRONTEND":
+ const deletedLeafIDs = action.payload.request.data.leafs
+ action.payload.response = deleteLeaves(deletedLeafIDs, cloneDeep(store.getState().active))
+ break;
+ // Side Actions
+ case "UPDATE_SIDE_FRONTEND":
+ action.payload.response = updateSide(action, cloneDeep(store.getState().active))
+ break;
+ case "UPDATE_SIDES_FRONTEND":
+ action.payload.response = updateSides(action, cloneDeep(store.getState().active))
+ break;
+ case "MAP_SIDES_FRONTEND":
+ action.payload.response = mapSides(action, cloneDeep(store.getState().active), cloneDeep(store.getState().dashboard))
+ break;
+ // Image Actions
+ case "LINK_IMAGES_FRONTEND":
+ action.payload.response = linkImages(action, cloneDeep(store.getState().dashboard), cloneDeep(store.getState().active))
+ break;
+ case "UNLINK_IMAGES_FRONTEND":
+ action.payload.response = unlinkImages(action, cloneDeep(store.getState().dashboard), cloneDeep(store.getState().active))
+ break;
+ case "DELETE_IMAGES_FRONTEND":
+ action.payload.response = deleteImages(action, cloneDeep(store.getState().dashboard), cloneDeep(store.getState().active))
+ break;
+ // Manifest Actions
+ case "UPDATE_MANIFEST_FRONTEND":
+ action.payload.response = updateManifest(action, cloneDeep(store.getState().active))
+ break;
+ case "DELETE_MANIFEST_FRONTEND":
+ action.payload.response = deleteManifest(action, cloneDeep(store.getState().active))
+ break;
+ default:
+ break;
+ }
+ next(action);
+}
+
+
+export default frontendBeforeActionsMiddleware;
\ No newline at end of file
diff --git a/viscoll-app/src/store/middleware/undoRedoMiddleware.js b/viscoll-app/src/store/middleware/undoRedoMiddleware.js
new file mode 100644
index 00000000..7bd21b6a
--- /dev/null
+++ b/viscoll-app/src/store/middleware/undoRedoMiddleware.js
@@ -0,0 +1,238 @@
+import {cloneDeep} from 'lodash';
+
+import {
+ undoCreateLeaves,
+ undoUpdateLeaf,
+ undoUpdateLeaves,
+ undoDeleteLeaf,
+ undoDeleteLeaves,
+ undoAutoconjoin,
+ undoFolioPageNumbers,
+} from '../../actions/undoRedo/leafHelper';
+
+import {
+ undoUpdateGroups,
+ undoUpdateGroup,
+ undoCreateGroups,
+ undoDeleteGroup,
+ undoDeleteGroups,
+} from '../../actions/undoRedo/groupHelper';
+
+import {
+ undoUpdateSide,
+ undoUpdateSides,
+} from '../../actions/undoRedo/sideHelper';
+
+import {
+ undoLinkImages,
+ undoUnlinkImages,
+} from '../../actions/undoRedo/imageHelper';
+
+import {
+ undoUpdateManifest,
+ undoDeleteManifest,
+} from '../../actions/undoRedo/manifestHelper';
+
+import {
+ undoCreateNoteType,
+ undoUpdateNoteType,
+ undoDeleteNoteType,
+ undoLinkNote,
+ undoUnlinkNote,
+ undoDeleteNote,
+} from '../../actions/undoRedo/noteHelper';
+
+
+const undoRedoMiddleware = store => next => action => {
+ async function sequentialDispatch(requests) {
+ if (requests[0].types[0].includes("DELETE")) {
+ // Only dispatch the first request (a delete request)
+ // since subsequent requests will be invalid due to the first request
+ store.dispatch(requests[0])
+ } else {
+ if (requests.length>1) {
+ // Add loading screen if there are multiple requests
+ requests.splice(0, 0, {type:"UPDATE_LOADING_COUNT", payload:requests.length});
+ requests.splice(0, 0, {type:"SHOW_LOADING"});
+ }
+ for (const request of requests) {
+ await store.dispatch(request);
+ }
+ }
+ }
+ let historyAction = "";
+ const d = new Date();
+ const id = d.getTime();
+ switch(action.type) {
+ case "UNDO":
+ let newUndoStack = cloneDeep(store.getState().history.undo);
+ let undoActionList = newUndoStack.pop();
+ action.payload = newUndoStack;
+ if (undoActionList) {
+ undoActionList = undoActionList.map((request)=>{
+ request.isUndo=true;
+ request.urID=id;
+ return request;
+ });
+ sequentialDispatch(undoActionList);
+ }
+ break;
+ case "REDO":
+ let newRedoStack = cloneDeep(store.getState().history.redo);
+ let redoActionList = newRedoStack.pop();
+ action.payload = newRedoStack;
+ if (redoActionList) {
+ redoActionList = redoActionList.map((request)=>{
+ request.isRedo=true;
+ request.urID=id;
+ return request;
+ });
+ sequentialDispatch(redoActionList);
+ }
+ break;
+ case "UPDATE_LEAF_FRONTEND":
+ historyAction = undoUpdateLeaf(action, store.getState().active);
+ break;
+ case "UPDATE_LEAVES_FRONTEND":
+ historyAction = undoUpdateLeaves(action, store.getState().active);
+ break;
+ case "DELETE_LEAF_FRONTEND":
+ historyAction = undoDeleteLeaf(action, store.getState().active);
+ break;
+ case "DELETE_LEAVES_FRONTEND":
+ historyAction = undoDeleteLeaves(action, store.getState().active);
+ break;
+ case "CREATE_LEAVES_FRONTEND":
+ historyAction = undoCreateLeaves(action, store.getState().active);
+ break;
+ case "AUTOCONJOIN_LEAFS_FRONTEND":
+ historyAction = undoAutoconjoin(action, store.getState().active);
+ break;
+ case "GENERATE_FOLIO_NUMBERS_FRONTEND":
+ historyAction = undoFolioPageNumbers(action, store.getState().active, "folio_number");
+ break;
+ case "GENERATE_PAGE_NUMBERS_FRONTEND":
+ historyAction = undoFolioPageNumbers(action, store.getState().active, "page_number");
+ break;
+ case "UPDATE_GROUPS_FRONTEND":
+ historyAction = undoUpdateGroups(action, store.getState().active);
+ break;
+ case "UPDATE_GROUP_FRONTEND":
+ historyAction = undoUpdateGroup(action, store.getState().active);
+ break;
+ case "CREATE_GROUPS_FRONTEND":
+ historyAction = undoCreateGroups(action, store.getState().active);
+ break;
+ case "DELETE_GROUP_FRONTEND":
+ historyAction = undoDeleteGroup(action, store.getState().active);
+ break;
+ case "DELETE_GROUPS_FRONTEND":
+ historyAction = undoDeleteGroups(action, store.getState().active);
+ break;
+ case "UPDATE_SIDE_FRONTEND":
+ historyAction = undoUpdateSide(action, store.getState().active);
+ break;
+ case "UPDATE_SIDES_FRONTEND":
+ historyAction = undoUpdateSides(action, store.getState().active);
+ break;
+ case "MAP_SIDES_FRONTEND":
+ historyAction = undoUpdateSides(action, store.getState().active);
+ break;
+ case "LINK_IMAGES_FRONTEND":
+ historyAction = undoLinkImages(action);
+ break;
+ case "UNLINK_IMAGES_FRONTEND":
+ historyAction = undoUnlinkImages(action);
+ break;
+ case "UPDATE_MANIFEST_FRONTEND":
+ historyAction = undoUpdateManifest(action, store.getState().active);
+ break;
+ case "DELETE_MANIFEST_FRONTEND":
+ historyAction = undoDeleteManifest(action, store.getState().active);
+ break;
+ case "UPDATE_NOTETYPE_FRONTEND":
+ historyAction = undoUpdateNoteType(action, store.getState().active);
+ break;
+ case "CREATE_NOTETYPE_FRONTEND":
+ historyAction = undoCreateNoteType(action, store.getState().active);
+ break;
+ case "DELETE_NOTETYPE_FRONTEND":
+ historyAction = undoDeleteNoteType(action, store.getState().active);
+ break;
+ case "LINK_NOTE_FRONTEND":
+ historyAction = undoLinkNote(action, store.getState().active);
+ break;
+ case "UNLINK_NOTE_FRONTEND":
+ historyAction = undoUnlinkNote(action, store.getState().active);
+ break;
+ case "DELETE_NOTE_FRONTEND":
+ historyAction = undoDeleteNote(action, store.getState().active);
+ break;
+ default:
+ break;
+ }
+ if (action.isUndoable) {
+ let updatedStack;
+ if (action.isUndo) {
+ // Grab redo stack and add new action
+ updatedStack = cloneDeep(store.getState().history.redo);
+ if (updatedStack.length>0) {
+ let lastRedoGroup = updatedStack[updatedStack.length-1];
+ if (action.urID && lastRedoGroup[0].urID===action.urID) {
+ let updatedGroup = updatedStack.pop();
+ updatedStack.push(updatedGroup.concat(historyAction));
+ } else {
+ historyAction = historyAction.map((request)=>{
+ request.urID = action.urID;
+ return request;
+ });
+ updatedStack.push(historyAction);
+ }
+ } else {
+ historyAction = historyAction.map((request)=>{
+ request.urID = action.urID;
+ return request;
+ });
+ updatedStack.push(historyAction);
+ }
+ } else {
+ // Action is normal or a redo
+ // Grab undo stack and add new action
+ updatedStack = cloneDeep(store.getState().history.undo)
+ if (updatedStack.length>0) {
+ let lastUndoGroup = updatedStack[updatedStack.length-1];
+ if (action.urID && lastUndoGroup[0].urID===action.urID) {
+ let updatedGroup = updatedStack.pop();
+ updatedStack.push(updatedGroup.concat(historyAction));
+ } else {
+ historyAction = historyAction.map((request)=>{
+ request.urID = action.urID;
+ return request;
+ });
+ updatedStack.push(historyAction)
+ }
+ } else {
+ historyAction = historyAction.map((request)=>{
+ request.urID = action.urID;
+ return request;
+ });
+ updatedStack.push(historyAction);
+ }
+ }
+ // Cut stack to only have 10 history actions
+ let cutCount = 0;
+ if (updatedStack.length>10) {
+ cutCount = updatedStack.length-10;
+ }
+ updatedStack.splice(0, cutCount)
+ if (action.isUndo) {
+ next({type:"PUSH_REDO", payload: updatedStack})
+ } else {
+ next({type:"PUSH_UNDO", payload: updatedStack})
+ }
+ }
+ next(action);
+}
+
+
+export default undoRedoMiddleware;
diff --git a/viscoll-app/src/store/store.js b/viscoll-app/src/store/store.js
new file mode 100644
index 00000000..9bf90744
--- /dev/null
+++ b/viscoll-app/src/store/store.js
@@ -0,0 +1,50 @@
+import { createStore, combineReducers, compose, applyMiddleware } from "redux";
+import { autoRehydrate } from 'redux-persist'
+import user from "../reducers/userReducer";
+import dashboard from "../reducers/dashboardReducer";
+import active from "../reducers/editCollationReducer";
+import global from "../reducers/globalReducer";
+import history from "../reducers/historyReducer";
+import axiosMiddleware from 'redux-axios-middleware';
+import { client, clientOptions } from './axiosConfig';
+import frontendBeforeActionsMiddleware from './middleware/frontendBeforeActionsMiddleware';
+import frontendAfterActionsMiddleware from './middleware/frontendAfterActionsMiddleware';
+import undoRedoMiddleware from "./middleware/undoRedoMiddleware";
+
+let storeEnhancers;
+if (process.env.NODE_ENV === 'development') {
+ storeEnhancers = compose(
+ applyMiddleware(
+ axiosMiddleware(client, clientOptions),
+ undoRedoMiddleware,
+ frontendBeforeActionsMiddleware,
+ frontendAfterActionsMiddleware,
+ ),
+ autoRehydrate(),
+ // window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
+ )
+} else {
+ storeEnhancers = compose(
+ applyMiddleware(
+ axiosMiddleware(client, clientOptions),
+ undoRedoMiddleware,
+ frontendBeforeActionsMiddleware,
+ frontendAfterActionsMiddleware,
+ ),
+ autoRehydrate()
+ )
+}
+
+const store = createStore(
+ combineReducers({
+ user,
+ dashboard,
+ active,
+ global,
+ history
+ }),
+ {},
+ storeEnhancers
+);
+
+export default store;
diff --git a/viscoll-app/src/styles/App.css b/viscoll-app/src/styles/App.css
new file mode 100644
index 00000000..c4d8add6
--- /dev/null
+++ b/viscoll-app/src/styles/App.css
@@ -0,0 +1,1356 @@
+.landing {
+ width: 100vw;
+ height: 100vh;
+ background: #2B4352;
+ text-align: center;
+}
+.landing .container {
+ margin: 0 auto;
+ width: 80vw;
+ height: 90vh;
+ display: flex;
+ align-items: center;
+ align-content: center;
+}
+.landing img {
+ width: 100%;
+}
+.landing .panelLogo {
+ width: 55%;
+}
+.landing .panelLogin {
+ width: 40%;
+ padding-left: 5%;
+}
+.landing .panelBottom {
+ display: table;
+ width: 100%;
+ height: 10vh;
+ background: #4ED6CB;
+}
+.landing .panelBottom div {
+ display: table-cell;
+ vertical-align: middle;
+}
+.landing .panelBottom span:first-child {
+ font-weight: bold;
+}
+.landing .panelBottom span {
+ color: #2b4352;
+ font-size: 1.1em;
+ display: block;
+ margin-bottom: 0.5em;
+}
+.landing hr {
+ border: 1px solid #4ED6CB;
+}
+.landing .spacingBottom {
+ margin-bottom: 1.5em;
+}
+.landing .spacingTop {
+ margin-top: 1.5em;
+}
+.landing p {
+ color: #4ED6CB;
+}
+.landing a {
+ color: #4ED6CB;
+ cursor: pointer;
+ text-decoration: underline;
+}
+.landing a:hover {
+ color: #a1e9e3;
+}
+
+.sidebar {
+ position: fixed;
+ display: block;
+ top: 55px;
+ width: 18%;
+ height: calc(100% - 55px);
+ background: #3A4B55;
+ opacity: 1;
+ -webkit-transition: all 200ms ease-in-out;
+ -ms-transition: all 200ms ease-in-out;
+ transition: all 200ms ease-in-out;
+ overflow-y: auto;
+ z-index: 2200;
+}
+.sidebar.lowerZIndex {
+ z-index: inherit;
+}
+.sidebar.hidden {
+ opacity: 0;
+}
+.sidebar hr {
+ border: 1px solid #4ED6CB;
+ margin: 0;
+}
+.sidebar h1 {
+ text-transform: uppercase;
+ color: #FFFFFF;
+ font-size: 1em;
+ font-weight: 600;
+}
+.sidebar h2 {
+ color: #FFFFFF;
+ font-size: 0.8em;
+ font-weight: lighter;
+ padding: 1em 0em;
+ margin: 0em;
+ text-transform: uppercase;
+}
+.sidebar h2:nth-child(1) {
+ padding-top: 0em;
+}
+@media screen and (max-width: 768px) {
+ .sidebar h2 {
+ font-size: 0.7em;
+ }
+}
+.sidebar .panel .header {
+ padding: 0.5em 1em;
+ display: flex;
+ justify-content: space-between;
+}
+@media screen and (max-width: 768px) {
+ .sidebar .panel .header {
+ font-size: 0.85em;
+ }
+}
+.sidebar .panel .content {
+ padding: 1em;
+ background: rgba(82, 108, 145, 0.2);
+}
+.sidebar .panel .content.hidden {
+ display: none;
+}
+.sidebar .panel:last-child {
+ margin-bottom: 50px;
+}
+.sidebar .selectMode {
+ padding: 1em 1em 1em 1em;
+ text-align: center;
+ background: #34434c;
+}
+.sidebar .selectMode span {
+ font-size: 13px;
+ color: #4ED6CB;
+ text-transform: uppercase;
+}
+.sidebar .selectMode .tip {
+ font-size: 0.8em;
+ color: #F2F2F2;
+ text-align: left;
+ line-height: 1.5em;
+}
+.sidebar .selectMode .close {
+ text-align: right;
+ margin-right: -10px;
+ margin-top: -10px;
+}
+.sidebar .navigation, .sidebar .dashboard, .sidebar .manager {
+ text-align: center;
+ margin: 0px;
+ text-transform: uppercase;
+ -webkit-transition: all 100ms ease-in-out;
+ -ms-transition: all 100ms ease-in-out;
+ transition: all 100ms ease-in-out;
+ width: 100%;
+ border: 0;
+ background: none;
+ color: #FFFFFF;
+ font-size: 1em;
+}
+.sidebar .navigation:hover, .sidebar .dashboard:hover, .sidebar .manager:hover {
+ background: rgba(78, 214, 203, 0.1);
+ font-weight: bold;
+}
+.sidebar .navigation.active, .sidebar .active.dashboard, .sidebar .active.manager {
+ background: #4ED6CB;
+ font-weight: bold;
+ color: #4e4e4e;
+}
+.sidebar .manager {
+ padding: 0.5em 0em;
+}
+@media screen and (max-width: 768px) {
+ .sidebar .manager {
+ font-size: 0.8em;
+ }
+}
+.sidebar .dashboard {
+ text-transform: none;
+ padding: 0.75em 0em;
+}
+.sidebar .dashboard:hover {
+ background: rgba(255, 255, 255, 0.05);
+}
+.sidebar .dashboard.active {
+ background: rgba(255, 255, 255, 0.1);
+ font-weight: bold;
+ color: #FFFFFF;
+}
+@media screen and (max-width: 768px) {
+ .sidebar .dashboard {
+ font-size: 0.8em;
+ }
+}
+.sidebar .export {
+ line-height: 45px;
+}
+
+.feedback {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ width: 18%;
+ z-index: 10000;
+ text-align: center;
+}
+
+.editIcon {
+ -webkit-transition: all 200ms ease-in-out;
+ -ms-transition: all 200ms ease-in-out;
+ transition: all 200ms ease-in-out;
+ background: #BABABA !important;
+}
+.editIcon:hover {
+ background: #A5A5A5 !important;
+ cursor: pointer;
+}
+
+.projectPanelInfo {
+ padding: 1em;
+ line-height: 2em;
+}
+.projectPanelInfo .info {
+ padding-top: 1em;
+ font-size: 0.9em;
+}
+.projectPanelInfo .info span {
+ color: rgba(78, 78, 78, 0.8);
+}
+
+.infoBox {
+ position: fixed;
+ display: inline-block;
+ width: 22%;
+ vertical-align: top;
+ right: 0;
+ top: 56px;
+ background: white;
+ max-height: 90%;
+ overflow-y: auto;
+ margin: 2% 2% 0% 0%;
+ -webkit-box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
+ box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
+}
+.infoBox .inner {
+ padding: 10px 20px 15px 20px;
+}
+@media screen and (max-width: 1024px) {
+ .infoBox .inner {
+ padding: 5px 15px 7px 15px;
+ }
+}
+@media screen and (max-width: 768px) {
+ .infoBox .inner {
+ padding: 5px 10px 7px 10px;
+ }
+}
+.infoBox .inner .row {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: center;
+}
+.infoBox .inner .label {
+ width: 35%;
+}
+.infoBox .inner .input {
+ width: 55%;
+}
+@media screen and (max-width: 768px) {
+ .infoBox .inner .input {
+ width: 50%;
+ }
+}
+.infoBox button.image {
+ border: 0;
+ background: none;
+ padding: 0;
+ margin: 0;
+ font-size: 1em;
+ overflow: none;
+ max-width: 40%;
+}
+.infoBox button.image + button {
+ margin-left: 0.5em;
+}
+
+.workspace, .notesWorkspace, .imageWorkspace, .dashboardWorkspace, .projectWorkspace {
+ position: absolute;
+ left: 18%;
+ top: 56px;
+}
+
+.projectWorkspace {
+ width: 54%;
+ margin: 2%;
+}
+.projectWorkspace .viewingMode {
+ display: flex;
+}
+.projectWorkspace .viewingMode > div:first-child {
+ margin-top: 4px;
+}
+.projectWorkspace .viewingMode > div:nth-child(2) {
+ margin-top: 14px;
+}
+
+.dashboardWorkspace {
+ width: 82%;
+ -webkit-transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1);
+ -ms-transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1);
+ transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+.dashboardWorkspace.projectPanelOpen {
+ width: 70%;
+}
+
+.notesWorkspace, .imageWorkspace {
+ width: 82%;
+}
+
+.groupContainer {
+ -webkit-box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
+ box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
+ -webkit-transition: border 150ms ease-in-out;
+ -ms-transition: border 150ms ease-in-out;
+ transition: border 150ms ease-in-out;
+ background: #FFFFFF;
+ margin-bottom: 1em;
+ cursor: pointer;
+ border: 2px solid #FFFFFF;
+}
+.groupContainer input {
+ opacity: 0;
+ width: 1px;
+ height: 1px;
+ margin-top: -10px;
+ float: right;
+}
+.groupContainer.focus {
+ border: 2px solid #4ED6CB;
+}
+.groupContainer.active {
+ background: #4ED6CB;
+}
+.groupContainer .groupMembers {
+ padding: 0em 1em 1em 1em;
+}
+.groupContainer .groupMembers.hidden {
+ display: none;
+}
+
+.itemContainer {
+ display: flex;
+ align-items: stretch;
+}
+.itemContainer.group {
+ justify-content: space-between;
+}
+.itemContainer.group .groupSection {
+ display: flex;
+}
+.itemContainer.group .toggleButton {
+ float: right;
+}
+
+.leafContainer {
+ -webkit-box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
+ box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
+ margin-bottom: 0.2em;
+ cursor: pointer;
+ background: #FFFFFF;
+}
+.leafContainer .leafSection {
+ display: flex;
+ flex-grow: 1;
+ -webkit-transition: border 100ms ease-in-out;
+ -ms-transition: border 100ms ease-in-out;
+ transition: border 100ms ease-in-out;
+ border: 2px solid #FFFFFF;
+}
+.leafContainer .leafSection.active {
+ background: #4ED6CB;
+}
+.leafContainer .leafSection.focus {
+ border: 2px solid #4ED6CB;
+}
+
+.itemName {
+ width: 70px;
+ display: flex;
+ align-items: center;
+ font-weight: 500;
+ padding-left: 20px;
+ min-height: 45px;
+}
+@media screen and (max-width: 768px) {
+ .itemName {
+ font-size: 0.9em;
+ }
+}
+
+.itemAttributes {
+ flex-grow: 4;
+ display: flex;
+}
+
+.attribute {
+ display: flex;
+ align-items: center;
+ max-width: 100px;
+ padding: 0.5em 0.8em;
+ color: rgba(78, 78, 78, 0.6);
+ font-weight: 400;
+ border-left: 1px solid rgba(78, 78, 78, 0.05);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+@media screen and (max-width: 768px) {
+ .attribute {
+ font-size: 0.9em;
+ }
+}
+.attribute span {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-size: 11px;
+}
+@media screen and (max-width: 768px) {
+ .attribute span {
+ font-size: 10px;
+ }
+}
+.attribute span:nth-child(1) {
+ color: rgba(78, 78, 78, 0.4);
+ display: block;
+}
+.attribute.active, .attribute:hover {
+ color: #4e4e4e;
+}
+.attribute.active.small {
+ color: #4e4e4e;
+}
+.attribute.active span:nth-child(1) {
+ color: rgba(78, 78, 78, 0.7);
+}
+
+.sideSection {
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ border-left: 1px solid rgba(78, 78, 78, 0.15);
+ overflow: hidden;
+}
+.sideSection .side {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ border: 2px solid #FFFFFF;
+ cursor: pointer;
+}
+.sideSection .side.active {
+ background: #4ED6CB;
+}
+.sideSection .side.focus {
+ border: 2px solid #4ED6CB;
+ color: #4e4e4e;
+}
+.sideSection .side .name {
+ width: 40px;
+ display: inline-block;
+ padding: 7px 10px 0px 10px;
+ vertical-align: top;
+ font-weight: normal;
+ border-right: 1px solid rgba(78, 78, 78, 0.05);
+}
+.sideSection .side .attribute {
+ display: inline-block;
+ vertical-align: top;
+ padding: 0px 10px;
+ padding-top: 2px;
+ border-right: 1px solid rgba(78, 78, 78, 0.05);
+ color: rgba(78, 78, 78, 0.6);
+ -webkit-transition: color 200ms ease-in-out;
+ -ms-transition: color 200ms ease-in-out;
+ transition: color 200ms ease-in-out;
+ font-size: 13px;
+}
+.sideSection .side .attribute span {
+ font-weight: normal;
+ color: rgba(78, 78, 78, 0.6);
+}
+.sideSection .side .attribute:hover {
+ color: #4e4e4e;
+}
+.sideSection .side .attribute.active {
+ color: #4e4e4e;
+}
+.sideSection .side:first-child {
+ border-bottom: 2px solid rgba(78, 78, 78, 0.1);
+}
+.sideSection .side:first-child.focus {
+ border-bottom: 2px solid #4ED6CB;
+}
+
+.sideToggle {
+ width: 20px;
+ height: 49px;
+ border-left: 1px solid rgba(78, 78, 78, 0.15);
+ cursor: pointer;
+}
+.sideToggle .side {
+ display: block;
+ width: 16px;
+ height: 18px;
+ padding-top: 3px;
+ text-align: center;
+ color: rgba(78, 78, 78, 0.6);
+ border: 2px solid #FFFFFF;
+}
+.sideToggle .side:first-child {
+ border-bottom: 1px solid rgba(78, 78, 78, 0.15);
+}
+.sideToggle .side.active {
+ background: #4ED6CB;
+}
+.sideToggle .side.focus {
+ border: 2px solid #4ED6CB;
+ color: #4e4e4e;
+}
+
+.flash {
+ animation-name: flashify;
+ animation-duration: 3s;
+}
+
+@-webkit-keyframes flashify {
+ 0% {
+ border: 2px solid white;
+ }
+ 35% {
+ border: 2px solid #4ed6cb;
+ }
+ 80% {
+ border: 2px solid #4ed6cb;
+ }
+ 100% {
+ border: 2px solid white;
+ }
+}
+@-moz-keyframes flashify {
+ 0% {
+ border: 2px solid white;
+ }
+ 35% {
+ border: 2px solid #4ed6cb;
+ }
+ 80% {
+ border: 2px solid #4ed6cb;
+ }
+ 100% {
+ border: 2px solid white;
+ }
+}
+@-ms-keyframes flashify {
+ 0% {
+ border: 2px solid white;
+ }
+ 35% {
+ border: 2px solid #4ed6cb;
+ }
+ 80% {
+ border: 2px solid #4ed6cb;
+ }
+ 100% {
+ border: 2px solid white;
+ }
+}
+@keyframes flashify {
+ 0% {
+ border: 2px solid white;
+ }
+ 35% {
+ border: 2px solid #4ed6cb;
+ }
+ 80% {
+ border: 2px solid #4ed6cb;
+ }
+ 100% {
+ border: 2px solid white;
+ }
+}
+.topbar {
+ position: fixed;
+ left: 0px;
+ width: 100%;
+ z-index: 2200;
+ -webkit-box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.05);
+ box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.05);
+}
+.topbar.lowerZIndex {
+ z-index: 1500;
+}
+.topbar .logo {
+ float: left;
+ width: 18%;
+ height: 55px;
+ text-align: center;
+ position: relative;
+ background: #3A4B55;
+}
+.topbar .logo img {
+ width: 60%;
+ margin: 0;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ -ms-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+}
+@media screen and (max-width: 768px) {
+ .topbar .logo img {
+ width: 85%;
+ }
+}
+
+.notesManager {
+ height: 100%;
+}
+.notesManager .container {
+ padding: 1em 2em 0em 2em;
+}
+.notesManager .browse {
+ height: 100%;
+}
+.notesManager .browse .notesList {
+ position: fixed;
+ top: 56px;
+ left: 18%;
+ width: 300px;
+ height: calc(100% - 56px);
+ overflow-y: auto;
+ text-align: center;
+ background: #e5e5e5;
+}
+.notesManager .browse .notesList .item {
+ margin: 0.5em 0em 0em 0em;
+ overflow: hidden;
+ -webkit-transition: background 200ms ease-in-out;
+ -ms-transition: background 200ms ease-in-out;
+ transition: background 200ms ease-in-out;
+ width: 90%;
+ font-size: 1em;
+ background: #F2F2F2;
+ border: 0px;
+ -webkit-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
+ box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
+}
+.notesManager .browse .notesList .item:hover {
+ cursor: pointer;
+ background: #FFFFFF;
+}
+.notesManager .browse .notesList .item .title {
+ padding-top: 10px;
+ font-weight: 500;
+ color: #4e4e4e;
+}
+.notesManager .browse .notesList .item .type {
+ padding-top: 5px;
+ padding-bottom: 10px;
+ color: #727272;
+ font-style: italic;
+ font-size: 0.9em;
+}
+.notesManager .browse .notesList .item.active {
+ background: #4ED6CB;
+}
+.notesManager .browse .details {
+ position: relative;
+ width: calc(100% - 320px);
+ left: 300px;
+}
+.notesManager .noteType {
+ padding: 1em 2em;
+}
+.notesManager .noteType .items {
+ display: flex;
+ flex-wrap: wrap;
+}
+.notesManager .noteType .item {
+ width: 220px;
+ margin-right: 1em;
+}
+.notesManager .noteType .create {
+ display: flex;
+ margin-bottom: 2em;
+}
+.notesManager .noteType .create .input {
+ margin-right: 1em;
+}
+
+.notesInfobox {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.noteSearch {
+ height: 56px;
+}
+@media screen and (max-width: 890px) {
+ .noteSearch {
+ width: 170px;
+ }
+}
+.noteSearch .searchTextbox {
+ padding-top: 5px;
+}
+
+.searchOptions {
+ visibility: hidden;
+ opacity: 0;
+ background: #FFFFFF;
+ width: 200px;
+ -webkit-border-radius: 0px 0px 6px 6px;
+ -moz-border-radius: 0px 0px 6px 6px;
+ -ms-border-radius: 0px 0px 6px 6px;
+ border-radius: 0px 0px 6px 6px;
+ -webkit-transition: all 200ms ease-in-out;
+ -ms-transition: all 200ms ease-in-out;
+ transition: all 200ms ease-in-out;
+ padding: 0em 1em;
+}
+@media screen and (max-width: 890px) {
+ .searchOptions {
+ width: 140px;
+ }
+}
+.searchOptions.active {
+ visibility: visible;
+ opacity: 1;
+}
+
+.noteForm {
+ width: 100%;
+ margin-left: 2%;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: flex-start;
+}
+.noteForm .label {
+ padding-top: 1em;
+ width: 20%;
+}
+@media screen and (max-width: 768px) {
+ .noteForm .label {
+ font-size: 0.8em;
+ }
+}
+.noteForm .input {
+ width: 80%;
+}
+@media screen and (max-width: 768px) {
+ .noteForm .input {
+ padding-left: 5%;
+ width: 75%;
+ }
+}
+.noteForm .input .textOnly {
+ margin-top: 1em;
+ color: #4e4e4e;
+}
+.noteForm .buttons {
+ text-align: right;
+ width: 100%;
+ padding-top: 2em;
+}
+.noteForm .objectAttachments {
+ width: 100%;
+}
+
+.filter {
+ width: 100%;
+ max-height: 45%;
+ position: relative;
+ z-index: 2;
+ left: 0;
+ display: flex;
+ justify-content: flex-end;
+ -webkit-transition: opacity 200ms linear;
+ -ms-transition: opacity 200ms linear;
+ transition: opacity 200ms linear;
+}
+
+.filterContainer {
+ border-top: 1px solid #F2F2F2;
+ position: fixed;
+ width: 82%;
+ max-height: 45%;
+ background: #FFFFFF;
+ padding-bottom: 10px;
+ overflow: auto;
+ -webkit-transition: top 450ms cubic-bezier(0.23, 1, 0.32, 1);
+ -ms-transition: top 450ms cubic-bezier(0.23, 1, 0.32, 1);
+ transition: top 450ms cubic-bezier(0.23, 1, 0.32, 1);
+ -webkit-box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.3);
+ box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.3);
+}
+
+.filterRow {
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+}
+.filterRow + .filterRow {
+ margin-top: -20px;
+}
+.filterRow .filterField {
+ width: 230px;
+ margin-left: 10px;
+}
+.filterRow .filterField:first-child {
+ margin-left: 20px;
+}
+.filterRow .filterField:last-child {
+ width: 160px;
+ text-align: center;
+}
+
+.filterMessage {
+ text-transform: uppercase;
+ font-size: 0.9em;
+ font-weight: 500;
+ color: #727272;
+}
+
+.appLoading {
+ width: 100vw;
+ height: 100vh;
+ background: #3A4B55;
+ display: flex;
+ align-items: center;
+}
+.appLoading .container {
+ width: 100vw;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ margin-top: -5%;
+}
+.appLoading .logo {
+ width: 30%;
+ max-width: 300px;
+ margin-bottom: 1em;
+}
+
+.fourOhFour {
+ width: 100vw;
+ height: 100vh;
+ background: #3A4B55;
+ display: flex;
+ align-items: center;
+}
+.fourOhFour .container {
+ width: 100vw;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ margin-top: -10%;
+}
+.fourOhFour .container h1 {
+ font-size: 8em;
+ color: #FFFFFF;
+ padding-bottom: 0;
+ margin-bottom: 10px;
+}
+.fourOhFour .container p {
+ color: #FFFFFF;
+ margin-bottom: 30px;
+}
+
+#listView .header {
+ display: flex;
+ padding: 1em 2em;
+ font-size: 0.8em;
+}
+#listView .header div {
+ width: 50%;
+}
+#listView button {
+ width: 100%;
+ display: flex;
+ text-align: left;
+ border-left: 1px solid rgba(255, 255, 255, 0.5);
+ border-right: 1px solid rgba(255, 255, 255, 0.5);
+ border-top: 1px solid rgba(114, 114, 114, 0.2);
+ border-bottom: 1px solid rgba(255, 255, 255, 0.5);
+ padding: 1em 2em;
+ background: rgba(255, 255, 255, 0.5);
+ font-size: 0.9em;
+ color: #4e4e4e;
+ cursor: pointer;
+ -webkit-transition: background 100ms ease-in-out;
+ -ms-transition: background 100ms ease-in-out;
+ transition: background 100ms ease-in-out;
+}
+#listView button:hover {
+ background: #FFFFFF;
+}
+#listView button:focus {
+ outline-style: solid;
+ outline-color: rgba(78, 214, 203, 0.5);
+ outline-width: 2px;
+ border: 1px solid #4ED6CB;
+}
+#listView button.selected {
+ background: #4ED6CB;
+}
+#listView button.selected:focus {
+ outline: 0;
+}
+#listView button div {
+ width: 50%;
+}
+
+.imageManager .form .row {
+ display: flex;
+}
+.imageManager .form .row .label {
+ padding-top: 1em;
+ min-width: 120px;
+}
+.imageManager .form .row .input {
+ flex-grow: 1;
+}
+.imageManager .manageManifests {
+ padding: 1em 2em 0em 2em;
+}
+.imageManager .manageManifests h2 {
+ font-size: 1.1em;
+ padding: 0em 0em 0em 0em;
+ margin: 0em;
+ color: #4e4e4e;
+}
+.imageManager .manageManifests .manifestCard {
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ padding: 1.5em 1.5em 1em 1.5em;
+}
+.imageManager .manageManifests .manifestCard span {
+ font-size: 1em;
+ font-weight: normal;
+ color: rgba(78, 78, 78, 0.8);
+}
+.imageManager .manageManifests .manifestCard > div {
+ flex-grow: 1;
+}
+.imageManager .manageManifests .manifestCard .thumbnails {
+ text-align: right;
+}
+.imageManager .manageManifests .addImages {
+ display: flex;
+}
+.imageManager .manageManifests .addImages > div {
+ width: 50%;
+ padding: 1.5em 1.5em 1em 1.5em;
+ background: #FFFFFF;
+ -webkit-box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
+ box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
+}
+.imageManager .manageManifests .addImages > div:first-child {
+ margin-right: 0.5em;
+}
+.imageManager .manageManifests .addImages > div:nth-child(2) {
+ margin-left: 0.5em;
+}
+.imageManager .imageMapper .moveableItem {
+ height: 50px;
+ background: #FFFFFF;
+ border-width: 0px 1px 0px 1px;
+ border-style: solid;
+ border-color: #F2F2F2;
+ cursor: pointer;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ -ms-border-radius: 3px;
+ border-radius: 3px;
+ padding-left: 0.5em;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+.imageManager .imageMapper .moveableItem .text {
+ color: #4e4e4e;
+ padding-left: 0.5em;
+}
+@media screen and (max-width: 768px) {
+ .imageManager .imageMapper .moveableItem .text {
+ font-size: 0.9em;
+ }
+}
+.imageManager .imageMapper .moveableItem .text > span {
+ font-size: 0.8em;
+}
+.imageManager .imageMapper .moveableItem .thumbnail {
+ opacity: 0.5;
+ -webkit-transition: all 200ms ease-in-out;
+ -ms-transition: all 200ms ease-in-out;
+ transition: all 200ms ease-in-out;
+}
+.imageManager .imageMapper .moveableItem .thumbnail:hover {
+ opacity: 1;
+ cursor: pointer;
+}
+.imageManager .imageMapper .middleBar {
+ display: flex;
+ justify-content: space-around;
+ background: #FFFFFF;
+ margin: 0.5em 1em;
+ padding: 0.2em 0em;
+}
+@media screen and (max-width: 1024px) {
+ .imageManager .imageMapper .middleBar {
+ margin: 0.5em 0em;
+ }
+}
+.imageManager .imageMapper .panelBar {
+ background: #FFFFFF;
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ border-bottom: 2px solid #F2F2F2;
+ height: 40px;
+}
+.imageManager .imageMapper .panelBar .title {
+ padding-left: 1em;
+ text-transform: uppercase;
+ color: #4e4e4e;
+ font-weight: bold;
+}
+.imageManager .imageMapper .panelBar .action {
+ padding-right: 0.5em;
+}
+.imageManager .imageMapper .topPanel {
+ flex-grow: 2;
+ display: flex;
+ flex-direction: column;
+ width: 97%;
+ margin-top: 1em;
+ margin-left: 1em;
+ background: #eaeaea;
+ color: #4e4e4e;
+}
+.imageManager .imageMapper .topPanel > div {
+ width: 100%;
+ height: 100%;
+ margin: 0em;
+}
+.imageManager .imageMapper .topPanel .boards {
+ display: flex;
+ width: 100%;
+ overflow-y: auto;
+}
+.imageManager .imageMapper .topPanel .boards > div {
+ width: 50%;
+}
+.imageManager .imageMapper .topPanel .binText {
+ text-transform: uppercase;
+ text-align: center;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100% !important;
+ height: 38vh;
+}
+.imageManager .imageMapper .bottomPanel {
+ flex-grow: 2;
+ display: flex;
+ justify-content: space-between;
+ width: 97%;
+ margin-left: 1em;
+}
+.imageManager .imageMapper .bottomPanel .backlog, .imageManager .imageMapper .bottomPanel .imageBacklog, .imageManager .imageMapper .bottomPanel .sideBacklog {
+ width: 49%;
+ padding: 0em;
+}
+.imageManager .imageMapper .bottomPanel .backlog .scrollable, .imageManager .imageMapper .bottomPanel .imageBacklog .scrollable, .imageManager .imageMapper .bottomPanel .sideBacklog .scrollable {
+ overflow-y: auto;
+}
+.imageManager .imageMapper .bottomPanel .sideBacklog .scrollable {
+ text-align: left;
+ height: 32vh;
+}
+.imageManager .imageMapper .bottomPanel .imageBacklog {
+ height: 37vh;
+}
+.imageManager .imageMapper .bottomPanel .imageBacklog .manifestSelection {
+ height: 40px;
+ padding: 0em 1em;
+ display: flex;
+ justify-content: space-evenly;
+ border-bottom: 2px solid #F2F2F2;
+ background: #FFFFFF;
+ width: 100%;
+}
+.imageManager .imageMapper .bottomPanel .imageBacklog .manifestSelection .title {
+ width: 70px;
+ padding-top: 10px;
+ padding-right: 10px;
+ color: #4e4e4e;
+}
+.imageManager .imageMapper .bottomPanel .imageBacklog .manifestSelection .form {
+ flex-grow: 1;
+}
+.imageManager .imageMapper .bottomPanel .imageBacklog .scrollable {
+ height: 27vh;
+}
+.imageManager .imageMapper .mainToolbar {
+ margin-top: 0.5em;
+ width: 100%;
+ height: 50px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background: #FFFFFF;
+ -webkit-box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.05);
+ box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.05);
+}
+.imageManager .imageMapper .mainToolbar .message {
+ padding-left: 1em;
+ text-transform: uppercase;
+ color: #4e4e4e;
+ font-size: 0.9em;
+}
+.imageManager .imageMapper .mainToolbar .actions {
+ padding-right: 1em;
+}
+
+.imageFilter {
+ -webkit-box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
+ box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
+ background: #FFFFFF;
+ margin: 0em 0em 0.5em 0em;
+ display: flex;
+ justify-content: space-between;
+}
+
+.addDialog .title {
+ color: #4e4e4e;
+}
+.addDialog h3 {
+ border-bottom: 1px solid #ddd;
+}
+.addDialog h4 {
+ color: #4e4e4e;
+ margin-top: 2em;
+ margin-bottom: 0em;
+ font-weight: 600;
+}
+.addDialog .label {
+ width: 200px;
+ display: inline-block;
+}
+.addDialog .input {
+ width: 200px;
+ display: inline-block;
+ text-align: right;
+}
+
+.feedbackDialog p {
+ color: #4e4e4e;
+}
+.feedbackDialog .label {
+ color: #4e4e4e;
+ width: 100px;
+ display: inline-block;
+ vertical-align: top;
+}
+.feedbackDialog .input {
+ width: 250px;
+ display: inline-block;
+ text-align: right;
+}
+
+.newProjectDialog p {
+ color: #4e4e4e;
+}
+.newProjectDialog h1 {
+ font-weight: normal;
+ text-transform: inherit;
+}
+.newProjectDialog h3 {
+ font-size: 1em;
+ margin-top: 0em;
+}
+.newProjectDialog .section {
+ margin-left: 1em;
+}
+.newProjectDialog .newProjectSelection button.btnSelection {
+ width: 100%;
+ background: #FFFFFF;
+ border: 0;
+ -webkit-box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.2);
+ box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.2);
+ margin-bottom: 1em;
+ outline: 0;
+}
+.newProjectDialog .newProjectSelection button.btnSelection:focus, .newProjectDialog .newProjectSelection button.btnSelection:hover {
+ outline-style: solid;
+ outline-width: 0.5em;
+ outline-color: rgba(78, 214, 203, 0.5);
+ cursor: pointer;
+}
+.newProjectDialog .newProjectSelection .selectItem {
+ display: flex;
+ padding: 2.5em 0em;
+}
+.newProjectDialog .newProjectSelection .selectItem .icon {
+ width: 50px;
+ padding: 0px 15px;
+}
+.newProjectDialog .newProjectSelection .selectItem .text {
+ line-height: 2.5em;
+}
+.newProjectDialog .newProjectSelection .selectItem .text span:nth-child(1) {
+ text-align: left;
+ font-size: 2.5em;
+ display: block;
+ color: #4e4e4e;
+ width: 100%;
+}
+.newProjectDialog .newProjectSelection .selectItem .text span:nth-child(2) {
+ font-size: 1.3em;
+ color: #747474;
+ width: 100%;
+ display: block;
+}
+
+.tooltip {
+ position: relative;
+ display: inline-block;
+ width: 100%;
+}
+.tooltip .text {
+ visibility: hidden;
+ width: 210px;
+ font-weight: 300;
+ background: rgba(40, 40, 40, 0.9);
+ color: #fff;
+ text-align: center;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ -ms-border-radius: 6px;
+ border-radius: 6px;
+ padding: 0.5em;
+ margin: 1em;
+ font-size: 0.9em;
+ opacity: 0;
+ -webkit-transition: all 200ms ease-in-out;
+ -ms-transition: all 200ms ease-in-out;
+ transition: all 200ms ease-in-out;
+ /* Position the tooltip */
+ position: absolute;
+ z-index: 100;
+}
+.tooltip .text::after {
+ content: " ";
+ position: absolute;
+ bottom: 100%;
+ /* At the top of the tooltip */
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: transparent transparent rgba(40, 40, 40, 0.9) transparent;
+}
+.tooltip.addDialog .text {
+ width: 70%;
+}
+.tooltip.addDialog .text.active {
+ visibility: visible;
+ opacity: 1;
+}
+.tooltip.addDialog .text::after {
+ left: 20%;
+}
+.tooltip.eyeToggle {
+ width: initial;
+}
+.tooltip.eyeToggle .text {
+ left: -5%;
+ margin-left: 0;
+ width: 100px;
+}
+.tooltip.eyeToggle .text::after {
+ bottom: 100%;
+ /* At the top of the tooltip */
+ left: 15%;
+ margin-left: -5px;
+}
+.tooltip.eyeToggle .text.active {
+ visibility: visible;
+ opacity: 1;
+}
+
+textarea {
+ border: 1px solid #e0e0e0;
+ width: 100%;
+ font-size: 1em;
+}
+textarea:focus {
+ outline-color: #4ED6CB;
+}
+
+h1 {
+ font-size: 1.6em;
+ font-weight: bold;
+ color: #4e4e4e;
+}
+@media screen and (max-width: 768px) {
+ h1 {
+ font-size: 1.3em;
+ }
+}
+
+h2 {
+ color: #727272;
+ padding-top: 0.8em;
+ font-size: 1.15em;
+}
+@media screen and (max-width: 768px) {
+ h2 {
+ font-size: 1em;
+ }
+}
+
+@media screen and (max-width: 768px) {
+ h3 {
+ font-size: 1em;
+ }
+}
+
+html, body {
+ background: #F2F2F2;
+}
+
+/*# sourceMappingURL=App.css.map */
diff --git a/viscoll-app/src/styles/App.css.map b/viscoll-app/src/styles/App.css.map
new file mode 100644
index 00000000..28935306
--- /dev/null
+++ b/viscoll-app/src/styles/App.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["../../sass/layout/_landing.scss","../../sass/lib/_variables.scss","../../sass/layout/_sidebar.scss","../../sass/lib/_mixins.scss","../../sass/layout/_projectPanel.scss","../../sass/layout/_infobox.scss","../../sass/layout/_workspace.scss","../../sass/layout/_tabular.scss","../../sass/layout/_topbar.scss","../../sass/layout/_notes.scss","../../sass/layout/_filter.scss","../../sass/layout/_loading.scss","../../sass/layout/_404.scss","../../sass/layout/_dashboard.scss","../../sass/layout/_imageManager.scss","../../sass/layout/_imageCollection.scss","../../sass/components/_dialog.scss","../../sass/components/_tooltip.scss","../../sass/components/_textarea.scss","../../sass/typography.scss","../../sass/index.scss"],"names":[],"mappings":"AAAA;EACE;EACA;EACA;EACE;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA,YC5BU;;AD8BV;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAIJ;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE,OC5DU;;AD+DZ;EACE,OChEU;EDiEV;EACA;;AAEA;EACE;;;AEzEN;EACE;EACA;EACA;EACA;EACA;EACA,YDJY;ECKZ;ECMA;EACI;EACI;EDNR;EACA;;AAEA;EACE;;AAGF;EACE;;AAEF;EACE;EACA;;AAEF;EACE;EACA,ODpBU;ECqBV;EACA;;AAEF;EACE,ODzBU;EC0BV;EACA;EACA;EACA;EACA;;AACA;EACE;;AAEF;EAVF;IAWI;;;AAIF;EACE;EACA;EACA;;AACA;EAJF;IAKI;;;AAGJ;EACE;EACA;;AACA;EACE;;AAGJ;EACE;;AAGJ;EACE;EACA;EAkBA;;AAhBA;EACE;EACA,ODjEQ;ECkER;;AAEF;EACE;EACA,ODpEQ;ECqER;EACA;;AAEF;EACE;EACA;EACA;;AAKJ;EACE;EACA;EACA;EC5EF;EACI;EACI;ED4EN;EACA;EACA;EACA,ODzFU;EC0FV;;AACA;EACE;EACA;;AAEF;EACE,YDjGQ;ECkGR;EACA,OD/FQ;;ACkGZ;EAEE;;AACA;EAHF;IAII;;;AAGJ;EAEE;EACA;;AACA;EACE;;AAEF;EACE;EACA;EACA,ODtHQ;;ACwHV;EAZF;IAaI;;;AAIJ;EACI;;;AAIN;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;ECnIE;EACI;EACI;EDmIR;;AACA;EACE;EACA;;;AErJJ;EACE;EACA;;AAEA;EACE;EACA;;AACA;EACE;;;ACRN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EFFC,oBEGD;EFFC,YEED;;AAEA;EACE;;AACA;EAFF;IAGI;;;AAEF;EALF;IAMI;;;AAGF;EACE;EACA;EACA;EACA;;AAEF;EACE;;AAEF;EACE;;AACA;EAFF;IAGI;;;AAIN;EACE;EACA;EACA;EACA;EACA;EAIA;EACA;;AAJA;EACE;;;AC7CN;EACI;EACA;EACA;;;AAEJ;EAEI;EACA;;AACA;EACI;;AACA;EACI;;AAEJ;EACI;;;AAKZ;EAEI;EHTF;EACI;EACI;;AGUN;EACI;;;AAGR;EAEI;;;AC/BJ;EJQG,oBICD;EJAC;EAID;EACI;EACI;EIJR,YNNY;EMOZ;EACA;EACA;;AAbA;EACE;EACA;EACA;EACA;EACA;;AAUF;EACE;;AAEF;EACE,YNhBU;;AMmBZ;EACE;;AAEA;EACE;;;AAKN;EACE;EACA;;AACA;EACE;;AAEA;EACE;;AAGF;EACE;;;AAIN;EJvCG,oBIwCD;EJvCC,YIuCD;EACA;EACA;EACA,YN9CY;;AMgDZ;EACE;EACA;EJ1CF;EACI;EACI;EI0CN;;AAEA;EACE,YNxDQ;;AM2DV;EACE;;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;;AACA;EAPF;IAQI;;;;AAGJ;EACE;EACA;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EAXF;IAYI;;;AAGF;EACE;EACA;EACA;EACA;;AACA;EALF;IAMI;;;AAIJ;EACE;EACA;;AAGF;EACE,ON3GU;;AM+GV;EACE,ONhHQ;;AMkHV;EACE;;;AAKN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE,YN3IQ;;AM6IV;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EJvJJ;EACI;EACI;EIuJJ;;AAEA;EACE;EACA;;AAEF;EACE;;AAEF;EACE;;AAGJ;EACE;;AACA;EACA;;;AAMN;EACE;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEF;EACE,YN1MQ;;AM4MV;EACE;EACA;;;AAKN;EACE;EACA;;;AJjLA;EIqLA;IAAO;;EACP;IAAM;;EAEN;IAAM;;EACN;IAAS;;;AJtLT;EIkLA;IAAO;;EACP;IAAM;;EAEN;IAAM;;EACN;IAAS;;;AJnLT;EI+KA;IAAO;;EACP;IAAM;;EAEN;IAAM;;EACN;IAAS;;;AJhLT;EI4KA;IAAO;;EACP;IAAM;;EAEN;IAAM;;EACN;IAAS;;;ACjOX;EACE;EACA;EACA;EACA;ELIC,oBKHD;ELIC,YKJD;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA,YPfU;;AOgBV;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EARF;IASI;;;;AC3BR;EACE;;AACA;EACE;;AAGF;EACE;;AACA;EA8BE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AApCA;EACE;EACA;ENEN;EACI;EACI;EMFF;EACA;EACA,YRTM;EQUN;ENRL,oBMSK;ENRL,YMQK;;AACA;EACE;EACA,YRfI;;AQiBN;EACE;EACA;EACA,ORjBI;;AQmBN;EACE;EACA;EACA,ORvBI;EQwBJ;EACA;;AAEF;EACE,YR/BI;;AQ2CV;EACE;EACA;EACA;;AAGJ;EACE;;AACA;EACE;EACA;;AAEF;EACE;EACA;;AAEF;EACE;EACA;;AACA;EACE;;;AAKR;EACE;EACA;;;AAEF;EACE;;AACA;EAFF;IAGI;;;AAGF;EACE;;;AAGJ;EACE;EACA;EACA,YRpFY;EQqFZ;ENzFA,uBM0FuB;ENzFpB,oBMyFoB;ENxFnB,mBMwFmB;ENvFf,eMuFe;EN9EvB;EACI;EACI;EMmFR;;AAJA;EARF;IASI;;;AAIF;EACE;EACA;;;AAGJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AACA;EAHF;IAII;;;AAGJ;EACE;;AACA;EAFF;IAGI;IACA;;;AAEF;EACE;EACA,ORtHQ;;AQyHZ;EACE;EACA;EACA;;AAEF;EACE;;;ACvIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EPMA;EACI;EACI;;;AOLV;EACE;EACA;EACA;EACA;EACA,YTVY;ESWZ;EACA;EPJA;EACI;EACI;EAPP,oBOWD;EPVC,YOUD;;;AAEF;EACE;EACA;EACA;;AACA;EACA;;AAGA;EACE;EACA;;AACA;EACE;;AAEF;EACE;EACA;;;AAIN;EACE;EACA;EACA;EACA,OTtCY;;;AUPd;EACE;EACA;EACA,YVDY;EUEZ;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;;;ACjBJ;EACE;EACA;EACA,YXDY;EWEZ;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;EACA,OXVQ;EWWR;EACA;;AAEF;EACE,OXfQ;EWgBR;;;ACpBJ;EACE;EACA;EACA;;AACA;EACE;;AAGJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OZZU;EYaV;EVRF;EACI;EACI;;AUSN;EACE,YZpBQ;;AYsBV;EACE;EACA;EACA;EACA;;AAEF;EACE,YZ9BQ;;AYiCV;EACE;;AAGF;EACE;;;ACxCF;EACE;;AACA;EACE;EACA;;AAEF;EACE;;AAIN;EACE;;AACA;EACE;EACA;EACA;EACA,ObXQ;;AaaV;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEF;EACE;;AAEF;EACE;;AAGJ;EACE;;AACA;EACE;EAEA;EACA,YbxCM;EEGX,oBWsCK;EXrCL,YWqCK;;AAGA;EACE;;AAEF;EACE;;AAMN;EACE;EACA,YbxDQ;EayDR;EACA;EACA,cb1DQ;Ea2DR;EXhEJ,uBWiE2B;EXhExB,oBWgEwB;EX/DvB,mBW+DuB;EX9DnB,eW8DmB;EACvB;EACA;EACA;EACA;;AAEA;EACE,ObjEM;EakEN;;AACA;EAHF;IAII;;;AAEF;EACE;;AAGJ;EACE;EXtEN;EACI;EACI;;AWuEF;EACE;EACA;;AAKN;EACE;EACA;EACA,Yb3FQ;Ea4FR;EACA;;AACA;EANF;IAOI;;;AAIJ;EACE,YbpGQ;EaqGR;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;EACA,Ob3GM;Ea4GN;;AAEF;EACE;;AAGJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,Ob1HQ;;Aa2HR;EAEE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AACA;EACE;;AAGJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACE;EACA;EACA;EACA;EAEA;;AACA;EACE;EACA;;AAEA;EACE;;AAKF;EACE;EACA;;AAGJ;EAEE;;AACA;EAEE;EACA;EACA;EACA;EACA;EACA,YbvLI;EawLJ;;AACA;EACE;EACA;EACA;EACA,Ob1LE;;Aa4LJ;EACE;;AAIJ;EACE;;AAIN;EAGE;EACA;EACA;EACA;EACA;EACA;EACA,YblNQ;EEGX,oBWgNG;EX/MH,YW+MG;;AACA;EACE;EACA;EACA,ObpNM;EaqNN;;AAEF;EACE;;;AChOR;EZQG,oBYPD;EZQC,YYRD;EACA,YdGY;EcFZ;EACA;EACA;;;ACHA;EACE,OfKU;;AeFZ;EACE;;AAGF;EACE,OfHU;EeIV;EACA;EACA;;AAGF;EACE;EACA;;AAEF;EACE;EACA;EACA;;;AAIF;EACE,OfrBU;;AeuBZ;EACE,OfxBU;EeyBV;EACA;EACA;;AAEF;EACE;EACA;EACA;;;AAIF;EACE,OfrCU;;AeuCZ;EACE;EACA;;AAEF;EACE;EACA;;AAEF;EACE;;AAGA;EACE;EACA,YfxDQ;EeyDR;EbtDH,oBauDG;EbtDH,YasDG;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGJ;EACE;EACA;;AAEA;EACE;EACA;;AAEF;EACE;;AACA;EACE;EACA;EACA;EACA,OfhFI;EeiFJ;;AAEF;EACE;EACA;EACA;EACA;;;AC/FV;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EdVF,uBcWyB;EdVtB,oBcUsB;EdTrB,mBcSqB;EdRjB,ecQiB;EACvB;EACA;EACA;EACA;EdHF;EACI;EACI;AcIN;EACA;EACA;;AAEA;EACE;EACA;EACA;AAAe;EACf;EACA;EACA;EACA;EACA;;AAKF;EACE;;AACA;EACE;EACA;;AAEF;EACE;;AAKN;EACE;;AACA;EACE;EACA;EACA;;AAEA;EACE;AAAe;EACf;EACA;;AAEF;EACE;EACA;;;AC9DR;EACE;EACA;EACA;;AACA;EACE,ejBDU;;;AkBJd;EACE;EACA;EACA,OlBKY;;AkBJZ;EAJF;IAKI;;;;AAGJ;EACE,OlBFY;EkBGZ;EACA;;AACA;EAJF;IAKI;;;;AAIF;EADF;IAEI;;;;ACGJ;EACE","file":"App.css"}
\ No newline at end of file
diff --git a/viscoll-app/src/styles/button.js b/viscoll-app/src/styles/button.js
new file mode 100644
index 00000000..f0ed02f9
--- /dev/null
+++ b/viscoll-app/src/styles/button.js
@@ -0,0 +1,70 @@
+export let btnLg = {
+ buttonStyle: {
+ height: 60,
+ },
+ labelStyle: {
+ fontSize: window.innerWidth<=768?18:20,
+ },
+ overlayStyle: {
+ paddingTop: 12,
+ height: 48,
+ }
+}
+
+
+export let btnMd = {
+ buttonStyle: {
+ height: 50,
+ },
+ labelStyle: {
+ fontSize: window.innerWidth<=768?16:18,
+ },
+ overlayStyle: {
+ paddingTop: 8,
+ height: 42,
+ }
+}
+
+export let btnAuthCancel = {
+ labelStyle: {
+ color: "#a5bde0",
+ }
+}
+
+
+export let btnBase = () => {
+ let fontSize = "0.9em";
+ if (window.innerWidth<=1024) {
+ fontSize = "0.8em";
+ }
+ if (window.innerWidth<=768) {
+ fontSize = "0.7em";
+ }
+ return {
+ labelStyle:{
+ fontSize,
+ },
+ buttonStyle: {
+ lineHeight: window.innerWidth<=768?"32px":"36px",
+ },
+ style: {
+ minWidth: window.innerWidth<=1024?"30px":"78px",
+ },
+ }
+}
+
+export let radioBtnDark = () => {
+ return {
+ labelStyle: {
+ color:"#ffffff",
+ fontSize:window.innerWidth<=768?"0.6em":"0.9em",
+ width:window.innerWidth<=768?"inherit":"",
+ lineHeight: window.innerWidth<=768?"inherit":null,
+ paddingTop:window.innerWidth<=768?5:null,
+ },
+ iconStyle: {
+ fill:"#4ED6CB",
+ marginRight:window.innerWidth<=768?"10px":"12px",
+ }
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/src/styles/checkbox.js b/viscoll-app/src/styles/checkbox.js
new file mode 100644
index 00000000..7b9b8f9b
--- /dev/null
+++ b/viscoll-app/src/styles/checkbox.js
@@ -0,0 +1,21 @@
+export let checkboxStyle = () => {
+ let fontSize = null;
+ if (window.innerWidth<=1024) {
+ fontSize = "14px";
+ }
+ if (window.innerWidth<=768) {
+ fontSize = "12px";
+ }
+ return {
+ iconStyle:{
+ height:window.innerWidth<=1024?15:20,
+ width:window.innerWidth<=1024?15:20,
+ marginTop:window.innerWidth<=1024?"2px":null,
+ marginRight:window.innerWidth<=1024?"5px":"10px",
+ },
+ labelStyle: {
+ fontSize,
+ lineHeight:"21px",
+ }
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/src/styles/index.css b/viscoll-app/src/styles/index.css
new file mode 100644
index 00000000..b4cc7250
--- /dev/null
+++ b/viscoll-app/src/styles/index.css
@@ -0,0 +1,5 @@
+body {
+ margin: 0;
+ padding: 0;
+ font-family: sans-serif;
+}
diff --git a/viscoll-app/src/styles/infobox.js b/viscoll-app/src/styles/infobox.js
new file mode 100644
index 00000000..85e5cb3a
--- /dev/null
+++ b/viscoll-app/src/styles/infobox.js
@@ -0,0 +1,14 @@
+let fontSize = null;
+if (window.innerWidth<=768) {
+ fontSize = "12px";
+} else if (window.innerWidth<=1024) {
+ fontSize = "14px";
+}
+
+let infoBoxStyle = {
+ tab: {
+ color: '#6A6A6A',
+ fontSize,
+ },
+}
+export default infoBoxStyle;
\ No newline at end of file
diff --git a/viscoll-app/src/styles/light.js b/viscoll-app/src/styles/light.js
new file mode 100644
index 00000000..029420db
--- /dev/null
+++ b/viscoll-app/src/styles/light.js
@@ -0,0 +1,41 @@
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _colors = require('material-ui/styles/colors');
+
+var _colorManipulator = require('material-ui/utils/colorManipulator');
+
+var _spacing = require('material-ui/styles/spacing');
+
+var _spacing2 = _interopRequireDefault(_spacing);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = {
+ spacing: _spacing2.default,
+ fontFamily: 'Roboto, sans-serif',
+ borderRadius: 2,
+ palette: {
+ primary1Color: '#526C91',
+ primary2Color: '#3A4B55',
+ primary3Color: _colors.grey400,
+ accent1Color: '#4ED6CB',
+ accent2Color: _colors.grey100,
+ accent3Color: _colors.grey500,
+ textColor: "#4e4e4e",
+ secondaryTextColor: (0, _colorManipulator.fade)(_colors.darkBlack, 0.54),
+ alternateTextColor: _colors.white,
+ canvasColor: _colors.white,
+ borderColor: _colors.grey300,
+ disabledColor: (0, _colorManipulator.fade)(_colors.darkBlack, 0.3),
+ pickerHeaderColor: _colors.cyan500,
+ clockCircleColor: (0, _colorManipulator.fade)(_colors.darkBlack, 0.07),
+ shadowColor: _colors.fullBlack
+ },
+ tableRow: {
+ selectedColor: '#fff',
+ },
+}; /**
+ * NB: If you update this file, please also update `docs/src/app/customization/Themes.js`
+ */
\ No newline at end of file
diff --git a/viscoll-app/src/styles/sidebar.js b/viscoll-app/src/styles/sidebar.js
new file mode 100644
index 00000000..4bd8eada
--- /dev/null
+++ b/viscoll-app/src/styles/sidebar.js
@@ -0,0 +1,22 @@
+import light from "./light.js";
+
+let sidebarStyle = {
+ panel: {
+ main: {
+ background: light.palette.primary2Color,
+ boxShadow: "none",
+
+ },
+ title: {
+ textTransform: "uppercase",
+ fontSize: "1.1em"
+ },
+ text: {
+ background: "rgba(82, 108, 145, 0.2)",
+ overflowY: "auto",
+ maxHeight: "40vh",
+
+ }
+ },
+}
+export default sidebarStyle;
\ No newline at end of file
diff --git a/viscoll-app/src/styles/tabular.js b/viscoll-app/src/styles/tabular.js
new file mode 100644
index 00000000..959b6831
--- /dev/null
+++ b/viscoll-app/src/styles/tabular.js
@@ -0,0 +1,32 @@
+let tabularStyle = {
+ group: {
+ card: {
+ marginBottom: 10,
+ boxShadow: "0px 1px 2px 1px rgba(0,0,0,0.1)",
+ paddingLeft: 0,
+ border: "2px solid white"
+ },
+ cardHeader: {
+ padding: 0,
+ overflow: "hidden",
+ },
+ containerStyle: {
+ paddingBottom: 0,
+ paddingTop: 0
+ },
+ cardTextStyle: {
+ paddingTop: 0,
+ paddingBottom: 0
+ }
+ },
+ leaf: {
+ card: {
+ marginBottom: 5,
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ boxShadow: "0px 1px 1px 1px rgba(0,0,0,0.1)",
+ border: "2px solid white"
+ }
+ }
+}
+export default tabularStyle;
\ No newline at end of file
diff --git a/viscoll-app/src/styles/textfield.js b/viscoll-app/src/styles/textfield.js
new file mode 100644
index 00000000..ca52bd74
--- /dev/null
+++ b/viscoll-app/src/styles/textfield.js
@@ -0,0 +1,24 @@
+const floatFieldDark = {
+ floatingLabelStyle: {
+ color: "#a5bde0",
+ },
+ underlineStyle: {
+ border: "1px solid #526C91",
+ },
+ underlineFocusStyle: {
+ border: "1px solid #4ED6CB",
+ },
+ inputStyle: {
+ color: "white",
+ }
+}
+const floatFieldLight = {
+ floatingLabelShrinkStyle: {color: "#526C91"},
+ floatingLabelStyle: {color: "#6E6E6E"},
+ hintStyle: {color: "#6E6E6E"},
+}
+
+export {
+ floatFieldDark,
+ floatFieldLight,
+}
\ No newline at end of file
diff --git a/viscoll-app/src/styles/topbar.js b/viscoll-app/src/styles/topbar.js
new file mode 100644
index 00000000..e70b061b
--- /dev/null
+++ b/viscoll-app/src/styles/topbar.js
@@ -0,0 +1,17 @@
+let topbarStyle = () => {
+ let width = 200;
+ if (window.innerWidth<=870) {
+ width = 120;
+ } else if (window.innerWidth<=1024) {
+ width = 150;
+ }
+ return {
+ tab: {
+ width,
+ height: 55,
+ color: '#6A6A6A',
+ fontSize: window.innerWidth<=870?"12px":null,
+ },
+ }
+}
+export default topbarStyle;
\ No newline at end of file