diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..b7dab5e9
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,2 @@
+node_modules
+build
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 48fb168f..250c834e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,10 +8,66 @@
/.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/*
+
+# secret scan
+secrets_scan.json
+
+.env
+.docker-environment-dev
+viscoll-api/.generators
+viscoll-api/public/uploads
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 00000000..c736801a
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,137 @@
+include:
+ - project: "devops/gitlab/ci-templates/docker"
+ ref: "0.0.1"
+ file:
+ - ".build_docker_image.yml"
+ - ".push_docker_image.yml"
+ - ".remove_docker_image.yml"
+ - project: "devops/gitlab/ci-templates/sast"
+ ref: "master"
+ file:
+ - ".shiftleft_container_scanning.yml"
+ - ".trivy_container_scanning.yml"
+
+workflow:
+ rules:
+ - if: $CI_MERGE_REQUEST_IID
+ - if: $CI_COMMIT_TAG
+ - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+ - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == 'master'
+ - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == 'development'
+
+stages:
+ - sast:project
+ - build:web
+ - build:xproc
+ - sast:web
+ - sast:xproc
+ - push:web
+ - push:xproc
+ - remove:web
+ - remove:xproc
+
+shiftleft_project_scanning:
+ stage: sast:project
+ extends:
+ - .shiftleft_container_scanning
+ tags:
+ - build
+ allow_failure: true
+
+build_web_image:
+ stage: build:web
+ extends:
+ - .build_docker_image
+ needs:
+ - shiftleft_project_scanning
+ variables:
+ CI_IMAGE_NAME: "${CI_PROJECT_ID}-${CI_PIPELINE_ID}-web"
+ tags:
+ - build
+
+build_xproc_image:
+ stage: build:xproc
+ extends:
+ - .build_docker_image
+ needs:
+ - shiftleft_project_scanning
+ variables:
+ CI_IMAGE_NAME: "${CI_PROJECT_ID}-${CI_PIPELINE_ID}-xproc"
+ DOCKER_BUILD_CONTEXT: "viscoll-xproc/"
+ DOCKERFILE_PATH: "viscoll-xproc/"
+ tags:
+ - build
+
+trivy_web_container_scanning:
+ stage: sast:web
+ extends:
+ - .trivy_container_scanning
+ needs:
+ - build_web_image
+ variables:
+ CI_IMAGE_NAME: "${CI_PROJECT_ID}-${CI_PIPELINE_ID}-web"
+ tags:
+ - build
+ allow_failure: true
+
+trivy_xproc_container_scanning:
+ stage: sast:xproc
+ extends:
+ - .trivy_container_scanning
+ needs:
+ - build_xproc_image
+ variables:
+ CI_IMAGE_NAME: "${CI_PROJECT_ID}-${CI_PIPELINE_ID}-xproc"
+ tags:
+ - build
+ allow_failure: true
+
+push_web_image_to_registry:
+ stage: push:web
+ extends:
+ - .push_docker_image
+ needs:
+ - trivy_web_container_scanning
+ variables:
+ CI_IMAGE_NAME: "${CI_PROJECT_ID}-${CI_PIPELINE_ID}-web"
+ DOCKER_IMAGE_NAME: "vceditor_web"
+ tags:
+ - build
+
+push_xproc_image_to_registry:
+ stage: push:xproc
+ extends:
+ - .push_docker_image
+ needs:
+ - trivy_xproc_container_scanning
+ variables:
+ CI_IMAGE_NAME: "${CI_PROJECT_ID}-${CI_PIPELINE_ID}-xproc"
+ DOCKER_IMAGE_NAME: "vceditor_xproc"
+ tags:
+ - build
+
+remove_web_image:
+ stage: remove:web
+ extends:
+ - .remove_docker_image
+ needs:
+ - push_web_image_to_registry
+ variables:
+ CI_IMAGE_NAME: "${CI_PROJECT_ID}-${CI_PIPELINE_ID}-web"
+ rules:
+ - when: always
+ tags:
+ - build
+
+remove_xproc_image:
+ stage: remove:xproc
+ extends:
+ - .remove_docker_image
+ needs:
+ - push_xproc_image_to_registry
+ variables:
+ CI_IMAGE_NAME: "${CI_PROJECT_ID}-${CI_PIPELINE_ID}-xproc"
+ rules:
+ - when: always
+ tags:
+ - build
diff --git a/.gitleaks.toml b/.gitleaks.toml
new file mode 100644
index 00000000..a7e9ecc4
--- /dev/null
+++ b/.gitleaks.toml
@@ -0,0 +1,111 @@
+title = "gitleaks config"
+[[rules]]
+ description = "Password in string"
+ regex = '''(?i)[\w]*(password|secret)[\w]* *[:=>,]+ *['"][\S]{2,}['"]'''
+ [rules.allowlist]
+ description = "Exceptions"
+ paths = [
+ '''(.*?)\.erb$''',
+ '''(.*?)\.yml$''',
+ '''spec/(.*?)''',
+ '''reference_data\.rb''',
+ '''lib/sdbmss/legacy\.rb''',
+ '''lib/sdbmss/seed_data.rb''',
+ '''viscoll-api/config/environments/development.rb''',
+ '''jena/jena.env''',
+ '''.lando.yml''',
+ '''config/initializers/mailer.rb''',
+ '''vendor/assets/javascripts/URI\.min\.js'''
+ ]
+ regexes = [
+ '''\w\.password_(field|confirmation)''',
+ '''^\s*#'''
+ ]
+[[rules]]
+ description = "Password in YAML config"
+ regex = '''(?i)[\w]*(password|secret)[\w]* *: *[\S]{2,}'''
+ [rules.allowlist]
+ description = "Exceptions"
+ paths = [
+ '''(.*?)\.e?rb$''',
+ '''spec/(.*?)''',
+ '''reference_data\.rb''',
+ '''config/secrets.yml''',
+ '''lib/sdbmss/legacy\.rb''',
+ '''vendor/assets/javascripts/URI\.min\.js'''
+ ]
+[[rules]]
+ description = "AWS"
+ regex = '''AKIA[0-9A-Z]{16}'''
+[[rules]]
+ description = "RKCS8"
+ regex = '''-----BEGIN PRIVATE KEY-----'''
+[[rules]]
+ description = "RSA"
+ regex = '''-----BEGIN RSA PRIVATE KEY-----'''
+[[rules]]
+ description = "Github"
+ regex = '''(?i)github.*['\"][0-9a-zA-Z]{35,40}['\"]'''
+[[rules]]
+ description = "SSH"
+ regex = '''-----BEGIN OPENSSH PRIVATE KEY-----'''
+[[rules]]
+ description = "Facebook"
+ regex = '''(?i)facebook.*['\"][0-9a-f]{32}['\"]'''
+[[rules]]
+ description = "Twitter"
+ regex = '''(?i)twitter.*['\"][0-9a-zA-Z]{35,44}['\"]'''
+[[rules]]
+ description = "PGP"
+ regex = '''-----BEGIN PGP PRIVATE KEY BLOCK-----'''
+[[rules]]
+ description = "Slack token"
+ regex = '''xox[baprs]-.*'''
+[[rules]]
+ description = "Strip API Key"
+ regex = '''(?i)(sk|pk)_(test|live)_[0-9a-zA-Z]{10,32}'''
+# Global allowlist
+[allowlist]
+ description = "Global Allowlists"
+ files = [
+ '''(.*?)(jpg|gif|doc|pdf|bin|md)$''',
+ '''viscoll-api/config/environments/development.rb''',
+ '''.*__test__.*`''',
+ '''viscoll-api/app/mailers/mailer\.rb''',
+ '''\.gitleaks\.toml'''
+ ]
+ regexes = [
+ # Ignore resetPasswordRequest method
+ '''resetPasswordRequest''',
+ # Ignore docker set secrets
+ '''(?i)(/run/secrets/)''',
+ # Values set by Ansible variables
+ '''{{ *[\S]+ *}}''',
+ # Values set by environment variables
+ '''ENV\[['"][\S]+['"]\]''',
+ # ansible
+ '''security_ssh_password_authentication''',
+ # miscallaneous false positives
+ # password in comments
+ '''^\s*# password:''',
+ # state resets in React
+ '''update: \{password: "", current_password: "", email: ""\}''',
+ '''register: \{email: "", password: ""\}''',
+ '''[Pp]assword\w*:\s*""''',
+ '''[Pp]assword\w*:\s*false''',
+ '''newPasswordConfirm:\s*false''',
+ '''current_password: this\.state\.currentPassword''',
+ '''password: this\.state\.newPassword''',
+ '''currentPassword:\s*nextProps\.currentPasswordError\.toString''',
+ '''password_confirmation: this.state.passwordConfirm''',
+ '''password: this.state.password''',
+ '''[Pp]assword\w*:\s*PropTypes\.''',
+ # method names and non-password vars
+ '''resetPassword: \(reset_token, password\)''',
+ '''@reset_password_url =''',
+ # error message
+ '''password:\s*\["can't be''',
+ # test data
+ '''reset_password_token = "5951303fc9bf3c7b9a573a3f"''',
+ '''password\w*: "secret"'''
+ ]
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
index 262714f1..e70b4523 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-ruby-2.4.0
+2.6.0
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..609004ac
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,32 @@
+FROM node:14 as app
+
+WORKDIR /app
+
+ENV PATH /app/node_modules/.bin:$PATH
+
+COPY viscoll-app/package.json ./
+COPY viscoll-app/package-lock.json ./
+
+RUN npm ci --silent
+
+COPY viscoll-app .
+
+RUN npm run build
+
+FROM ruby:2.7
+RUN apt-get update && apt-get install -y librsvg2-bin
+
+# throw errors if Gemfile has been modified since Gemfile.lock
+RUN bundle config --global frozen 1
+
+WORKDIR /usr/src/app
+
+COPY viscoll-api/Gemfile viscoll-api/Gemfile.lock ./
+RUN bundle install
+
+COPY viscoll-api .
+
+COPY --from=app /app/build /usr/src/app/public
+
+ENTRYPOINT ["bundle", "exec", "rails"]
+CMD []
diff --git a/Dockerfile.api b/Dockerfile.api
new file mode 100644
index 00000000..4b633058
--- /dev/null
+++ b/Dockerfile.api
@@ -0,0 +1,8 @@
+FROM ruby:2.7
+
+RUN apt-get update && apt-get install -y librsvg2-bin
+
+WORKDIR /app
+COPY viscoll-api/Gemfile viscoll-api/Gemfile.lock ./
+RUN bundle install
+
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..8df870cb 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,217 @@
-# README
+## Introduction
-This README would normally document whatever steps are necessary to get the
-application up and running.
+VCEditor is for building models of the physical collation of manuscripts, and then visualizing them in various ways. The VCEditor project is led by Dot Porter at the [Schoenberg Institute for Manuscript Studies](https://schoenberginstitute.org/) at the University of Pennsylvania and Alberto Campagnolo. VCEditor is built on code developed by the [University of Toronto Libraries](https://onesearch.library.utoronto.ca/about) and the [Old Books New Science lab](https://oldbooksnewscience.com/), under the direction of Alexandra Gillespie.
-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
+```
-* ...
+TOOO: Change following: give instructions for creating Ethereal mail account. The following paragraph needs to be removed.
+
+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'
+```
+
+Once the account is created, set the credentials in the `.docker-environment-dev` file:
+
+```
+MAILER_USR=@ethereal.email
+MAILER_PWD=
+```
+
+Replace `` with the actual account name and `` with the actual password.
+
+## Deploying with Docker Swarm and Traefik
+
+To deploy the application with Docker Swarm using Traefik, first deploy the Traefik stack by running the following command:
+
+```
+docker stack deploy -c docker-compose.traefik.yml traefik
+```
+
+Before running the project you will need to set the environment variable `PROJECT_URL` to the URL you are using (e.g. `export PROJECT_URL=my-viscoll-url.com`). Then deploy the project:
+
+```
+docker stack deploy -c docker-compose.yml viscoll
+```
+
+#### Other required environment variables
+
+Set in the ENV the following:
+
+* `MAILER_USR` -- the SMTP account to use (if needed)
+* `MAILER_PWD` -- the password of of the SMTP account (if needed)
+* `MAILER_DEFAULT_FROM` -- the default mail from account
+* `MAILER_HOST` -- the SMTP host
+* `MAILER_DOMAIN` -- the application host (e.g., `my-app.com`)
+* `PROJECT_URL` -- the application host; used by Traefik
+* `RELEASE_TAG` -- the release tag of the docker image (e.g., `lastest`)
+* `ADMIN_EMAIL` -- the mailto address for admin emails
+* `APPLICATION_HOST` -- the application host; used by VCEditor
+* `SECRET_KEY_BASE` -- the Rails secret key base (production and staging environments)
+* `RAILS_ENV` -- 'production', use only if deploying to staging or production
+ environments
+* `XPROC_URL` -- full URL to the xproc service; e.g., `http://host.com:`
+
+In development set environment in `.docker-environment-dev`. See the `docker-environment-dev-sample` file for a template.
+
+## 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.
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 00000000..5eac5b52
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,32 @@
+# Penn Libraries TODO list
+
+### Idrovora integration and deployment tasks
+
+- [ ] TODO: Idrovora integration work
+ - [X] TODO: VisColl XML to SVG
+ - [ ] TODO: VisColl XML to formulas
+ - [ ] TODO: VisColl XML to HTML
+ - [ ] TODO: Add image list creation; note that we can't simply return URLs to the images
+ - [ ] TODO: Configure Idrovora clean up
+ - [ ] TODO: Test Idrovora integration
+
+- [ ] TODO: Refactor Idrovora interface components
+ - [X] TODO: Revisit filenames in `public/xproc/zip`; perhaps use
+ `-.zip`; like `987654321-svg2.zip`
+ - [X] TODO: Have `Export.js` -- `downloadZip()` return zip with file name on disk
+ - [X] TODO: Refactor `export_controller.rb` SVG generation
+ - [ ] TODO: Create job to cleanup zip files in `public/xproc/zip`
+ - [X] TODO: Change Idrovora XPL/XSL config to pass job-dir to XSL
+
+- [ ] TODO: Clean up configuration and use of environment variables
+ - [ ] TODO: Review URL/HOST constants and their uses: `APPLICATION_HOST`, `PROJECT_URL`
+ - [ ] TODO: Add environment variable for Idrovora URL
+ - [ ] TODO: Address hacky implementation of `ApplicationController#set_base_api_url`
+
+- [ ] TODO: Other tasks
+ - [X] TODO: Update VisColl Logo, b/c of course
+ - [ ] TODO: Use S3 interface for image storage
+ - [ ] TODO: Rename app to VCEditor, wherever that makes sense
+ - [ ] TODO: Update application README for VCEditor
+ - [ ] TODO: Document configuration and deployment; dev docker setup
+ - [ ] TODO: Look at license text; esp. `Copyright 2020 University of Toronto Libraries`
diff --git a/ansible/.gitignore b/ansible/.gitignore
new file mode 100644
index 00000000..1936b4e8
--- /dev/null
+++ b/ansible/.gitignore
@@ -0,0 +1,6 @@
+.vagrant/
+
+.python-version
+
+roles/*.*
+vault-password
\ No newline at end of file
diff --git a/ansible/README.md b/ansible/README.md
new file mode 100644
index 00000000..f92131cf
--- /dev/null
+++ b/ansible/README.md
@@ -0,0 +1,31 @@
+## Staging Server Setup
+
+### Requirements
+
+* [Python](https://python.org/)
+
+### Installation
+
+Optionally, install a virtual environment via
+[pyenv](https://github.com/pyenv/pyenv-installer):
+
+```plaintext
+$ curl https://pyenv.run | bash
+$ pyenv virtualenv vceditor-ansible
+$ pyenv local vceditor-ansible
+```
+
+Now install dependencies, i. e. [Ansible](https://www.ansible.com/) and roles
+from [Ansible Galaxy](https://galaxy.ansible.com/):
+
+```plaintext
+$ pip install -r requirements.txt
+$ ansible-galaxy install -r roles.yml
+```
+
+### Deployment
+
+```plaintext
+$ ansible-playbook playbooks/site.yml
+```
+
diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg
new file mode 100644
index 00000000..92ed4c71
--- /dev/null
+++ b/ansible/ansible.cfg
@@ -0,0 +1,9 @@
+[defaults]
+inventory = inventory
+roles_path = roles
+interpreter_python = auto_silent
+
+[ssh_connection]
+pipelining = True
+ssh_args = -o ControlMaster=auto -o ControlPersist=30m
+control_path = ~/.ssh/ansible-%%r@%%h:%%p
\ No newline at end of file
diff --git a/ansible/inventory/hcloud.yml b/ansible/inventory/hcloud.yml
new file mode 100644
index 00000000..c77b6609
--- /dev/null
+++ b/ansible/inventory/hcloud.yml
@@ -0,0 +1,6 @@
+---
+hcloud:
+ children:
+ vceditor:
+ hosts:
+ vceditor.middell.com:
diff --git a/ansible/playbooks/group_vars/hcloud/certbot.yml b/ansible/playbooks/group_vars/hcloud/certbot.yml
new file mode 100644
index 00000000..aac4ad23
--- /dev/null
+++ b/ansible/playbooks/group_vars/hcloud/certbot.yml
@@ -0,0 +1,4 @@
+---
+certbot_auto_renew_user: root
+certbot_create_if_missing: true
+certbot_admin_email: gregor@middell.net
diff --git a/ansible/playbooks/group_vars/hcloud/firewall.yml b/ansible/playbooks/group_vars/hcloud/firewall.yml
new file mode 100644
index 00000000..b910f0e9
--- /dev/null
+++ b/ansible/playbooks/group_vars/hcloud/firewall.yml
@@ -0,0 +1,5 @@
+---
+firewall_allowed_tcp_ports:
+ - "22"
+ - "80"
+ - "443"
diff --git a/ansible/playbooks/group_vars/hcloud/postfix.yml b/ansible/playbooks/group_vars/hcloud/postfix.yml
new file mode 100644
index 00000000..c6e83fe7
--- /dev/null
+++ b/ansible/playbooks/group_vars/hcloud/postfix.yml
@@ -0,0 +1,4 @@
+---
+postfix_aliases:
+ - user: "root"
+ alias: "gregor@middell.net"
diff --git a/ansible/playbooks/group_vars/hcloud/security.yml b/ansible/playbooks/group_vars/hcloud/security.yml
new file mode 100644
index 00000000..642ac058
--- /dev/null
+++ b/ansible/playbooks/group_vars/hcloud/security.yml
@@ -0,0 +1,5 @@
+---
+security_ssh_password_authentication: "yes"
+security_ssh_permit_root_login: "yes"
+
+security_autoupdate_enabled: false
diff --git a/ansible/playbooks/group_vars/vceditor/pip.yml b/ansible/playbooks/group_vars/vceditor/pip.yml
new file mode 100644
index 00000000..df9b1e02
--- /dev/null
+++ b/ansible/playbooks/group_vars/vceditor/pip.yml
@@ -0,0 +1,7 @@
+---
+pip_package: python3-pip
+pip_install_packages:
+ - docker
+ - docker-compose
+ - passlib
+ - bcrypt
diff --git a/ansible/playbooks/host_vars/vceditor.middell.com/certbot.yml b/ansible/playbooks/host_vars/vceditor.middell.com/certbot.yml
new file mode 100644
index 00000000..0845e916
--- /dev/null
+++ b/ansible/playbooks/host_vars/vceditor.middell.com/certbot.yml
@@ -0,0 +1,5 @@
+---
+certbot_certs:
+ - email: gregor@middell.net
+ domains:
+ - vceditor.middell.com
diff --git a/ansible/playbooks/host_vars/vceditor.middell.com/nginx.yml b/ansible/playbooks/host_vars/vceditor.middell.com/nginx.yml
new file mode 100644
index 00000000..e336f8a7
--- /dev/null
+++ b/ansible/playbooks/host_vars/vceditor.middell.com/nginx.yml
@@ -0,0 +1,39 @@
+---
+nginx_remove_default_vhost: true
+nginx_extra_http_options: |
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Scheme $scheme;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header Host $http_host;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+
+nginx_upstreams:
+ - name: vceditor-app
+ servers: ["localhost:3000"]
+ - name: vceditor-idrovora
+ servers: ["localhost:2000"]
+
+nginx_vhosts:
+ - listen: "80 default_server"
+ server_name: "vceditor.middell.com"
+ filename: "vceditor.middell.com.conf"
+ root: "/var/www"
+ extra_parameters: |
+ return 301 https://vceditor.middell.com$request_uri;
+ - listen: "443 ssl"
+ server_name: "vceditor.middell.com"
+ filename: "vceditor.middell.com-ssl.conf"
+ root: "/var/www"
+ extra_parameters: |
+ ssl_certificate "/etc/letsencrypt/live/vceditor.middell.com/fullchain.pem";
+ ssl_certificate_key "/etc/letsencrypt/live/vceditor.middell.com/privkey.pem";
+
+ location /xproc {
+ proxy_pass http://vceditor-idrovora/xproc;
+ }
+
+ location / {
+ proxy_pass http://vceditor-app/;
+ }
diff --git a/ansible/playbooks/setup.yml b/ansible/playbooks/setup.yml
new file mode 100644
index 00000000..f53049af
--- /dev/null
+++ b/ansible/playbooks/setup.yml
@@ -0,0 +1,33 @@
+---
+- hosts: all
+ pre_tasks:
+ - name: Update package cache
+ apt:
+ update_cache: yes
+# - name: Remove python-configparser (incompatible with docker-py via pip)
+# apt:
+# pkg: python-configparser
+# state: absent
+ roles:
+ - geerlingguy.git
+ - geerlingguy.pip
+ - geerlingguy.firewall
+ - geerlingguy.security
+ - geerlingguy.certbot
+ - geerlingguy.nginx
+ - geerlingguy.docker
+ - oefenweb.postfix
+
+- hosts: all
+ tasks:
+ - name: Install unattended upgrades package.
+ package: name=unattended-upgrades state=present
+
+ - name: Copy unattended-upgrades configuration files in place.
+ template:
+ src: "templates/99unattended-upgrades.j2"
+ dest: "/etc/apt/apt.conf.d/99unattended-upgrades"
+ owner: root
+ group: root
+ mode: 0644
+
diff --git a/ansible/playbooks/site.yml b/ansible/playbooks/site.yml
new file mode 100644
index 00000000..e734b408
--- /dev/null
+++ b/ansible/playbooks/site.yml
@@ -0,0 +1,3 @@
+---
+- import_playbook: setup.yml
+- import_playbook: vceditor.yml
diff --git a/ansible/playbooks/templates/99unattended-upgrades.j2 b/ansible/playbooks/templates/99unattended-upgrades.j2
new file mode 100644
index 00000000..3dab0e43
--- /dev/null
+++ b/ansible/playbooks/templates/99unattended-upgrades.j2
@@ -0,0 +1,4 @@
+Unattended-Upgrade::Automatic-Reboot "true";
+
+Unattended-Upgrade::Mail "root";
+Unattended-Upgrade::MailOnlyOnError "false";
diff --git a/ansible/playbooks/vceditor.yml b/ansible/playbooks/vceditor.yml
new file mode 100644
index 00000000..8af34e83
--- /dev/null
+++ b/ansible/playbooks/vceditor.yml
@@ -0,0 +1,17 @@
+---
+- hosts: vceditor
+ tasks:
+ - name: Retrieve sources from GitHub
+ git:
+ repo: "https://github.com/KislakCenter/VisualCollation.git"
+ dest: "/usr/src/vceditor"
+ version: "feature/production-setup"
+ depth: 1
+ - name: Deploy Docker services
+ docker_compose:
+ project_src: "/usr/src/vceditor"
+ build: True
+ nocache: True
+ recreate: always
+ restarted: True
+
diff --git a/ansible/requirements.txt b/ansible/requirements.txt
new file mode 100644
index 00000000..56d15540
--- /dev/null
+++ b/ansible/requirements.txt
@@ -0,0 +1 @@
+ansible==2.8.5
diff --git a/ansible/roles.yml b/ansible/roles.yml
new file mode 100644
index 00000000..bd6723c6
--- /dev/null
+++ b/ansible/roles.yml
@@ -0,0 +1,9 @@
+---
+- geerlingguy.certbot,3.1.0
+- geerlingguy.docker,2.5.3
+- geerlingguy.firewall,2.4.1
+- geerlingguy.git,2.1.0
+- geerlingguy.nginx,2.7.0
+- geerlingguy.pip,1.3.0
+- geerlingguy.security,1.8.0
+- oefenweb.postfix,v2.6.1
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/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
deleted file mode 100644
index 286b2239..00000000
--- a/app/mailers/application_mailer.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-class ApplicationMailer < ActionMailer::Base
- default from: 'from@example.com'
- layout 'mailer'
-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/application.rb b/config/application.rb
deleted file mode 100644
index 0b45f014..00000000
--- a/config/application.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require_relative 'boot'
-
-require "rails"
-# Pick the frameworks you want:
-require "active_model/railtie"
-require "active_job/railtie"
-# require "active_record/railtie"
-require "action_controller/railtie"
-require "action_mailer/railtie"
-require "action_view/railtie"
-require "action_cable/engine"
-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
- 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.
- end
-end
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-dev.yml b/docker-compose-dev.yml
new file mode 100644
index 00000000..ef21d4f3
--- /dev/null
+++ b/docker-compose-dev.yml
@@ -0,0 +1,57 @@
+version: '3.5'
+services:
+ app:
+ container_name: viscoll_app
+ image: node:14
+ volumes:
+ - ./viscoll-app:/app
+ working_dir: /app
+ command: ["bash", "-c", "npm install && npm start"]
+ ports:
+ - 3000:3000
+ depends_on:
+ - api
+ stdin_open: true
+ api:
+ tty: true
+ stdin_open: true
+ env_file: ./.docker-environment-dev
+ container_name: viscoll_api
+ image: viscoll-api
+ build:
+ context: .
+ dockerfile: Dockerfile.api
+ volumes:
+ - ./viscoll-api:/app
+ command: bundle exec rails server -p 3001 -b 0.0.0.0
+ ports:
+ - 3001:3001
+ depends_on:
+ - mongo
+ - xproc
+ xproc:
+ container_name: vceditor_xproc
+ build:
+ context: ./viscoll-xproc
+ volumes:
+ - ./viscoll-xproc/xpl:/app/xpl
+ - ./viscoll-xproc/htdocs:/app/htdocs
+ ports:
+ - 2000:2000
+ mongo:
+ container_name: viscoll_mongo
+ image: mongo:4.0
+ volumes:
+ - mongo:/data/db
+ mongo-express:
+ container_name: viscoll_mongo_express
+ image: mongo-express:0.54
+ ports:
+ - 127.0.0.1:3002:8081
+ depends_on:
+ - mongo
+ environment:
+ ME_CONFIG_MONGODB_SERVER: mongo
+volumes:
+ mongo:
+ name: "viscoll_mongo"
diff --git a/docker-compose-traefik.yml b/docker-compose-traefik.yml
new file mode 100644
index 00000000..eb9b7124
--- /dev/null
+++ b/docker-compose-traefik.yml
@@ -0,0 +1,30 @@
+version: "3.7"
+
+services:
+ traefik:
+ image: traefik:v2.3
+ command:
+ - "--entrypoints.production.address=:80"
+ - "--entryPoints.production.forwardedHeaders.trustedIPs=127.0.0.1/32,192.168.0.0/16,172.16.0.0/12,10.0.0.0/8"
+ - "--entrypoints.staging.address=:8080"
+ - "--entryPoints.staging.forwardedHeaders.trustedIPs=127.0.0.1/32,192.168.0.0/16,172.16.0.0/12,10.0.0.0/8"
+ - "--providers.docker=true"
+ - "--providers.docker.endpoint=unix:///var/run/docker.sock"
+ - "--providers.docker.exposedbydefault=false"
+ - "--providers.docker.network=traefik"
+ - "--providers.docker.swarmMode=true"
+ - "--providers.docker.swarmModeRefreshSeconds=15"
+ - "--providers.docker.watch=true"
+ networks:
+ - traefik
+ ports:
+ - "80:80"
+ - "8080:8080"
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+
+networks:
+ traefik:
+ driver: overlay
+ name: traefik
+ attachable: true
\ No newline at end of file
diff --git a/docker-compose.traefik.yml b/docker-compose.traefik.yml
new file mode 100644
index 00000000..aa850aa9
--- /dev/null
+++ b/docker-compose.traefik.yml
@@ -0,0 +1,26 @@
+version: "3.7"
+
+services:
+ traefik:
+ image: traefik:v2.3
+ command:
+ - "--entrypoints.web.address=:80"
+ - "--providers.docker=true"
+ - "--providers.docker.endpoint=unix:///var/run/docker.sock"
+ - "--providers.docker.exposedbydefault=false"
+ - "--providers.docker.network=traefik"
+ - "--providers.docker.swarmMode=true"
+ - "--providers.docker.swarmModeRefreshSeconds=15"
+ - "--providers.docker.watch=true"
+ networks:
+ - traefik
+ ports:
+ - "80:80"
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+
+networks:
+ traefik:
+ driver: overlay
+ name: traefik
+ attachable: true
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..124cd371
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,67 @@
+version: "3.7"
+
+services:
+ app:
+ image: "quay.io/upennlibraries/vceditor_web:${RELEASE_TAG}"
+ command: server -b 0.0.0.0
+ environment:
+ - ADMIN_EMAIL
+ - APPLICATION_HOST
+ - HONEYBADGER_API_KEY
+ - MAILER_DEFAULT_FROM
+ - MAILER_DOMAIN
+ - MAILER_HOST
+ - MAILER_PORT
+ - MAILER_PWD
+ - MAILER_USR
+ - PROJECT_URL
+ - RAILS_ENV
+ - RAILS_SERVE_STATIC_FILES
+ - RELEASE_TAG
+ - SECRET_KEY_BASE
+ - XPROC_URL
+ deploy:
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.${INSTANCE}-app.rule=Host(`${PROJECT_URL}`)"
+ - "traefik.http.routers.${INSTANCE}-app.entrypoints=${INSTANCE}"
+ - "traefik.http.services.${INSTANCE}-app.loadbalancer.server.port=3000"
+ networks:
+ - internal
+ - traefik
+ volumes:
+ - images:/usr/src/app/public/uploads
+
+ mongo:
+ image: mongo:4.0
+ volumes:
+ - db:/data/db
+ networks:
+ - internal
+
+ xproc:
+ image: "quay.io/upennlibraries/vceditor_xproc:${RELEASE_TAG}"
+ deploy:
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.${INSTANCE}-xproc.rule=Host(`${PROJECT_URL}`) && PathPrefix(`/xproc`)"
+ - "traefik.http.routers.${INSTANCE}-xproc.entrypoints=${INSTANCE}"
+ - "traefik.http.services.${INSTANCE}-xproc.loadbalancer.server.port=2000"
+ networks:
+ - internal
+ - traefik
+ volumes:
+ - xproc:/app/htdocs
+
+networks:
+ internal:
+ traefik:
+ external: true
+
+volumes:
+ db:
+ name: ${INSTANCE}-db
+ xproc:
+ name: ${INSTANCE}-xproc
+ images:
+ name: ${INSTANCE}-images
diff --git a/docker-environment-dev-sample b/docker-environment-dev-sample
new file mode 100644
index 00000000..10e6d8a3
--- /dev/null
+++ b/docker-environment-dev-sample
@@ -0,0 +1,7 @@
+MAILER_HOST=smtp.ethereal.email
+MAILER_PORT=587
+MAILER_DEFAULT_FROM=test@example.edu
+MAILER_DOMAIN=localhost
+APPLICATION_HOST=localhost
+MAILER_USR=SOME_ADDRESS@ethereal.email
+MAILER_PWD=SOME_PASSWORD
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/viscoll-api/.ruby-version b/viscoll-api/.ruby-version
new file mode 100644
index 00000000..e70b4523
--- /dev/null
+++ b/viscoll-api/.ruby-version
@@ -0,0 +1 @@
+2.6.0
diff --git a/Gemfile b/viscoll-api/Gemfile
similarity index 52%
rename from Gemfile
rename to viscoll-api/Gemfile
index 6325e251..d4210de0 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.4.2'
# 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,20 @@ 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'
+ gem 'pry'
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'
@@ -49,20 +44,15 @@ group :development do
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
-gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
-
+#gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
-gem 'devise'
-gem 'mongoid'
-gem 'bootstrap-sass'
+gem 'mongoid', '~> 6.2'
+gem 'rails_jwt_auth', '0.16.1'
+gem 'shrine', '~> 2.17.1'
+gem 'rubyzip', '1.3.0'
+# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
+gem 'rack-cors', '1.1.1'
-group :test do
- gem 'rspec'
- gem 'mongoid-rspec'
- gem 'capybara'
- gem 'cucumber-rails', require: false
- gem 'database_cleaner'
- gem 'email_spec'
- gem 'launchy'
-end
+# Exception Notification with Honeybadger
+gem "honeybadger", "~> 4.0"
diff --git a/viscoll-api/Gemfile.lock b/viscoll-api/Gemfile.lock
new file mode 100644
index 00000000..ec7d6383
--- /dev/null
+++ b/viscoll-api/Gemfile.lock
@@ -0,0 +1,278 @@
+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.4.2)
+ actionpack (= 5.2.4.2)
+ nio4r (~> 2.0)
+ websocket-driver (>= 0.6.1)
+ actionmailer (5.2.4.2)
+ actionpack (= 5.2.4.2)
+ actionview (= 5.2.4.2)
+ activejob (= 5.2.4.2)
+ mail (~> 2.5, >= 2.5.4)
+ rails-dom-testing (~> 2.0)
+ actionpack (5.2.4.2)
+ actionview (= 5.2.4.2)
+ activesupport (= 5.2.4.2)
+ 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.4.2)
+ activesupport (= 5.2.4.2)
+ builder (~> 3.1)
+ erubi (~> 1.4)
+ rails-dom-testing (~> 2.0)
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
+ activejob (5.2.4.2)
+ activesupport (= 5.2.4.2)
+ globalid (>= 0.3.6)
+ activemodel (5.2.4.2)
+ activesupport (= 5.2.4.2)
+ activerecord (5.2.4.2)
+ activemodel (= 5.2.4.2)
+ activesupport (= 5.2.4.2)
+ arel (>= 9.0)
+ activestorage (5.2.4.2)
+ actionpack (= 5.2.4.2)
+ activerecord (= 5.2.4.2)
+ marcel (~> 0.3.1)
+ activesupport (5.2.4.2)
+ 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 (4.8.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)
+ honeybadger (4.9.0)
+ i18n (1.8.9)
+ 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.0)
+ crass (~> 1.0.2)
+ nokogiri (>= 1.5.9)
+ lumberjack (1.2.8)
+ mail (2.7.1)
+ mini_mime (>= 0.1.1)
+ marcel (0.3.3)
+ mimemagic (~> 0.3.2)
+ method_source (1.0.0)
+ mimemagic (0.3.10)
+ nokogiri (~> 1)
+ rake
+ mini_mime (1.0.3)
+ mini_portile2 (2.5.0)
+ 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.2)
+ mini_portile2 (~> 2.5.0)
+ racc (~> 1.4)
+ notiffany (0.1.3)
+ nenv (~> 0.1)
+ shellany (~> 0.0)
+ pry (0.14.0)
+ 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.4.2)
+ actioncable (= 5.2.4.2)
+ actionmailer (= 5.2.4.2)
+ actionpack (= 5.2.4.2)
+ actionview (= 5.2.4.2)
+ activejob (= 5.2.4.2)
+ activemodel (= 5.2.4.2)
+ activerecord (= 5.2.4.2)
+ activestorage (= 5.2.4.2)
+ activesupport (= 5.2.4.2)
+ bundler (>= 1.3.0)
+ railties (= 5.2.4.2)
+ 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.4.2)
+ actionpack (= 5.2.4.2)
+ activesupport (= 5.2.4.2)
+ 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.4)
+ 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 (2.17.1)
+ content_disposition (~> 1.0)
+ down (~> 4.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.2)
+ 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
+ honeybadger (~> 4.0)
+ jbuilder (~> 2.7)
+ listen (~> 3.0.5)
+ mongoid (~> 6.2)
+ mongoid-rspec!
+ pry
+ puma (~> 3.0)
+ rack-cors (= 1.1.1)
+ rails (= 5.2.4.2)
+ 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 (~> 2.17.1)
+ simplecov
+ spring
+ spring-watcher-listen (~> 2.0.0)
+ 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..a2209654
--- /dev/null
+++ b/viscoll-api/README.md
@@ -0,0 +1,58 @@
+# VCEditor (Rails API Back-End)
+
+## Introduction
+
+This is the the Rails-driven back-end for VCEditor.
+
+## 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..ef521f08
--- /dev/null
+++ b/viscoll-api/app/controllers/application_controller.rb
@@ -0,0 +1,40 @@
+class ApplicationController < ActionController::API
+ class VCError < StandardError; end
+
+ rescue_from Mongoid::Errors::DocumentNotFound do |e|
+ Honeybadger.notify(e)
+ Rails.logger.error(e.message + "\n" + e.backtrace.join("\n"))
+ render json: { errors: e.message }, status: :not_found
+ end
+
+ rescue_from VCError do |e|
+ Honeybadger.notify(e)
+ Rails.logger.error(e.message + "\n" + e.backtrace.join("\n"))
+ render json: { errors: e.message }, status: :bad_request
+ end
+
+ rescue_from StandardError do |e|
+ Honeybadger.notify(e)
+ Rails.logger.error(e.message + "\n" + e.backtrace.join("\n"))
+ render json: { errors: e.message }, status: :bad_request
+ end
+
+ before_action :set_base_api_url
+ def set_base_api_url
+ # TODO: we need an env var with a complete URL for this
+ @base_api_url = Rails.application.secrets.api_url ? Rails.application.secrets.api_url : "https://#{ENV['APPLICATION_HOST']}"
+ 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..793cb603
--- /dev/null
+++ b/viscoll-api/app/controllers/export_controller.rb
@@ -0,0 +1,208 @@
+require 'zip'
+require 'securerandom'
+
+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
+ @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
+
+ exportData = buildDotModel(@project)
+ xml = Nokogiri::XML(exportData)
+ schema = Nokogiri::XML::RelaxNG(File.open("public/viscoll-datamodel2.0.rng"))
+ errors = schema.validate(xml)
+
+ if errors.empty?
+ case @format
+ when "xml"
+ render json: { data: exportData, type: @format, Images: { exportedImages: @zipFilePath ? @zipFilePath : false } }, status: :ok and return
+ when "json"
+ @data = buildJSON(@project)
+ render :'exports/show', status: :ok and return
+ when 'svg'
+ collation_file = 'collation.css'
+ config_xml = %Q{#{collation_file} }
+ job_response = process_pipeline 'viscoll2svg', xml.to_xml, config_xml
+ outfile = write_zip_file job_response, 'svg'
+ @zipFilePath = "#{@base_api_url}/transformations/zip/#{@project.id}-svg"
+ exportData = []
+ Zip::File.open(outfile) do |zip_file|
+ zip_file.each do |entry|
+ if File.extname(entry.name) === '.svg'
+ exportData << entry.get_input_stream.read.force_encoding("UTF-8")
+ end
+ end
+ end
+
+ render json: { data: exportData, type: @format, Images: { exportedImages: @zipFilePath ? @zipFilePath : false } }, status: :ok and return
+ when 'png'
+ collation_file = 'collation.css'
+ config_xml = %Q{#{collation_file} }
+ job_response = process_pipeline 'viscoll2svg', xml.to_xml, config_xml
+ # outfile = write_zip_file job_response, 'png'
+ outfile = "#{Rails.root}/public/xproc/#{@project.id}-png.zip"
+ @zipFilePath = "#{@base_api_url}/transformations/zip/#{@project.id}-png"
+ exportData = []
+ # open zip output stream (so we can write to the zip)
+ Zip::OutputStream.open(outfile) do |zio|
+ # Zip::OutputStream.write_buffer do |zio|
+ Zip::File.open_buffer StringIO.new(job_response.body).read do |zip_input|
+ zip_input.each do |input_entry|
+ zio.put_next_entry input_entry.name
+ zio.write input_entry.get_input_stream.read
+ if File.extname(input_entry.name) == '.svg'
+ # use SecureRandom to prevent file name collisions;
+ # e.g., MST1-1.svg => MST1-1.svg-d40498e50a.svg, MST1-1.svg-d40498e50a.svg.png
+ basename = File.basename(input_entry.name, ".svg").parameterize
+ tmp_svg = File.join Dir.tmpdir, "#{basename}-#{SecureRandom.hex 5}.svg"
+ tmp_png = "#{tmp_svg}.png"
+
+ # write the svg to disk
+ File.open(tmp_svg, 'w+') { |f| f.puts input_entry.get_input_stream.read.force_encoding("UTF-8") }
+ system "rsvg-convert -w 1024 #{tmp_svg} > #{tmp_png}"
+
+ # the png has the same name as the svg
+ png_name = input_entry.name.sub /\.svg$/, '.png'
+ zio.put_next_entry png_name
+ zio.write open(tmp_png, 'rb').read
+
+ # clean up
+ FileUtils.rm_f [tmp_svg, tmp_png]
+ end
+ end
+ end
+ end
+
+ render json: { data: exportData, type: @format, Images: { exportedImages: @zipFilePath ? @zipFilePath : false } }, status: :ok and return
+ when 'formula'
+ job_response = process_pipeline 'viscoll2formulas', xml.to_xml
+
+ outfile = write_zip_file job_response, 'formula'
+ @zipFilePath = "#{@base_api_url}/transformations/zip/#{@project.id}-formula"
+
+ files = []
+ Zip::File.open(outfile) do |zip_file|
+ zip_file.each do |entry|
+ if File.basename(entry.name).include? "formula"
+ nokogiri_entry = zip_file.get_input_stream(entry) { |f| Nokogiri::XML(f) }
+ content = nokogiri_entry.xpath('//vc:formula/text()')
+ type = nokogiri_entry.xpath('//vc:formula/@type')
+ format = nokogiri_entry.xpath('//vc:formula/@format')
+ formula = "Type: #{type}\nFormat: #{format}\nFormula: #{content}\n\n"
+ files << formula
+ end
+ end
+ end
+ exportData = files.sort
+
+ render json: { data: exportData, type: @format, Images: { exportedImages: @zipFilePath ? @zipFilePath : false } }, status: :ok and return
+ when 'html'
+ collation_file = 'collation.css'
+ config_xml = %Q{#{collation_file} }
+ image_list = build_image_list @project
+ job_response = process_pipeline 'viscoll2html', xml.to_xml, config_xml, image_list
+ outfile = write_zip_file job_response, 'html'
+ Zip::File.open(outfile) do |zip_file|
+ zip_file.each do |file|
+ if File.extname(file.name) == '.html'
+ remove_xml_declaration(zip_file, file)
+ add_doctype(zip_file, file)
+ zip_file.rename(file.name, "HTML/#{file.name}")
+ elsif File.extname(file.name) == '.xml'
+ zip_file.rename(file.name, "XML/#{file.name}")
+ elsif File.extname(file.name) == '.svg'
+ zip_file.rename(file.name, "SVG/#{file.name}")
+ end
+ end
+ end
+ @zipFilePath = "#{@base_api_url}/transformations/zip/#{@project.id}-html"
+
+ exportData = 'Please download your HTML below.'
+
+ render json: { data: exportData, type: 'formula', Images: { exportedImages: @zipFilePath ? @zipFilePath : false } }, status: :ok and return
+ else
+ raise VCError, "Export format must be one of [json, xml, svg, formula, html]. The format received is: '#{@format}'."
+ end
+ else
+ raise VCError, "Something went wrong when exporting #{@format}: #{errors}"
+ end
+
+ end
+
+ private
+
+ def set_project
+ @project = Project.find(params[:id])
+ authorize_project! @project
+ @format = params[:format]
+ end
+
+ def remove_xml_declaration zip_file, input_file
+ content = zip_file.read(input_file.name)
+ new_content = content.lines.to_a[1..-1].join
+ zip_file.get_output_stream(input_file.name) { |f| f.puts new_content }
+ zip_file.commit
+ end
+
+ def add_doctype zip_file, input_file
+ content = zip_file.read(input_file.name)
+ zip_file.get_output_stream(input_file.name) { |f| f.puts "\n\n" + content }
+ zip_file.commit
+ end
+
+ def process_pipeline pipeline, xml_string, config_xml = nil, image_list = nil
+ # run the pipeline
+ xproc_uri = URI.parse "#{Rails.configuration.xproc['url']}/xproc/#{pipeline}/"
+ xproc_req = Net::HTTP::Post.new(xproc_uri)
+ form = [['input', StringIO.new(xml_string)]]
+ form << ['config', StringIO.new(config_xml)] if config_xml
+ form << ['images', StringIO.new(image_list)] if image_list
+
+ xproc_req.set_form(form, 'multipart/form-data')
+ xproc_response = Net::HTTP.start(xproc_uri.hostname, xproc_uri.port) do |http|
+ http.request(xproc_req)
+ end
+ response_hash = JSON.parse(xproc_response.body)
+
+ # TODO: Xproc#retreive_data; returns IO object
+ job_url = response_hash["_links"]["job"]["href"]
+ job_uri = URI.parse job_url
+ job_req = Net::HTTP::Get.new(job_uri)
+ job_req["Accept"] = 'application/zip'
+ job_response = Net::HTTP.start(job_uri.hostname, job_uri.port) do |http|
+ http.request(job_req)
+ end
+ job_response
+ end
+
+ def write_zip_file response, format
+ outfile = "#{Rails.root}/public/xproc/#{@project.id}-#{format}.zip"
+ File.open outfile, 'wb' do |f|
+ f.puts response.body
+ end
+ outfile
+ 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..f009e5b8
--- /dev/null
+++ b/viscoll-api/app/controllers/feedback_controller.rb
@@ -0,0 +1,30 @@
+class FeedbackController < ApplicationController
+ before_action :authenticate!
+
+ # POST /feedback
+ def create
+ unless 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?
+ raise VCError, "Title and message required."
+ end
+ FeedbackMailer.sendFeedback(
+ @title,
+ @message,
+ @browserInformation,
+ @projectJSONExport,
+ current_user
+ ).deliver_now
+ render json: {}, status: :ok and return
+ 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..95e79d6b
--- /dev/null
+++ b/viscoll-api/app/controllers/filter_controller.rb
@@ -0,0 +1,207 @@
+class FilterController < ApplicationController
+ before_action :authenticate!
+ before_action :set_project, only: [:show]
+
+ # PUT /projects/filter
+ def show
+ queries = filter_params.to_h[:queries]
+ errors = runValidations(queries)
+ if errors != []
+ raise VCError, "Errors: #{errors.join('\n')}"
+ end
+ @objectIDs = { Groups: [], Leafs: [], Sides: [], Terms: [] }
+ @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]
+ @terms = finalResponse[:Terms]
+ @groupsOfMatchingLeafs = finalResponse[:GroupsOfMatchingLeafs]
+ @leafsOfMatchingSides = finalResponse[:LeafsOfMatchingSides]
+ @groupsOfMatchingSides = finalResponse[:GroupsOfMatchingSides]
+ @groupsOfMatchingTerms = finalResponse[:GroupsOfMatchingTerms]
+ @leafsOfMatchingTerms = finalResponse[:LeafsOfMatchingTerms]
+ @sidesOfMatchingTerms = finalResponse[:SidesOfMatchingTerms]
+ 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
+ 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 = []
+ terms = []
+
+ 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 'term'
+ termQueryResult = @project.terms.only(:id).where(query_condition_params)
+ terms = termQueryResult.collect { |nqr| nqr.id.to_s }
+ @objectIDs[:Terms] += terms
+ end
+ sets.push(Set.new([*groups, *leafs, *sides, *terms]))
+ 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: [], Terms: [], GroupsOfMatchingTerms: [], LeafsOfMatchingTerms: [], SidesOfMatchingTerms:[], 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[:Terms].include?(objectID)
+ term = Term.find(objectID)
+ groupIDs = term.objects[:Group]
+ leafIDs = term.objects[:Leaf]
+ rectoIDs = term.objects[:Recto]
+ versoIDs = term.objects[:Verso]
+ groupIDs.each do |groupID|
+ if !(response[:Groups].include?(groupID))
+ response[:Groups].push(groupID)
+ response[:GroupsOfMatchingTerms].push(groupID)
+ end
+ end
+ leafIDs.each do |leafID|
+ if !(response[:Leafs].include?(leafID))
+ response[:Leafs].push(leafID)
+ response[:LeafsOfMatchingTerms].push(leafID)
+ end
+ end
+ rectoIDs.each do |sideID|
+ if !(response[:Sides].include?(sideID))
+ response[:Sides].push(sideID)
+ response[:SidesOfMatchingTerms].push(sideID)
+ end
+ end
+ versoIDs.each do |sideID|
+ if !(response[:Sides].include?(sideID))
+ response[:Sides].push(sideID)
+ response[:SidesOfMatchingTerms].push(sideID)
+ end
+ end
+ response[:Terms].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
+ @project = Project.find(params[:id])
+ authorize_project! @project
+ 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..8a1b95ad
--- /dev/null
+++ b/viscoll-api/app/controllers/groups_controller.rb
@@ -0,0 +1,147 @@
+class GroupsController < ApplicationController
+ before_action :authenticate!
+ before_action :set_group, only: [:update, :destroy]
+
+ # POST /groups
+ def create
+ 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]
+ # 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)
+ raise VCError, "Additional group errors: #{@additionalErrors}"
+ end
+ @groupErrors = { project_id: [] }
+ if (project_id == nil)
+ raise VCError, "Project ID is nil. Group has following errors: #{@groupErrors}"
+ end
+
+ @project = Project.find(project_id)
+
+ 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
+ raise VCError, "Group (#{group.id}) was unable to save: #{group.errors.full_messages.join('\n')}"
+ 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
+ end
+
+ # PATCH/PUT /groups/1
+ def update
+ unless @group.update(group_params)
+ raise VCError, "Some failed to update Group #{@group.id}"
+ end
+ end
+
+ # PATCH/PUT /groups
+ def updateMultiple
+ allGroups = group_params_batch_update.to_h[:groups]
+ # Run validations
+ errors = validateGroupBatchUpdate(allGroups)
+ if not errors.empty?
+ raise VCError, "Batch update error: #{errors}"
+ end
+ allGroups.each do |group_params|
+ @group = Group.find(group_params[:id])
+ @project = Project.find(@group.project_id)
+ authorize_project! @project
+ if !@group.update(group_params[:attributes])
+ raise VCError, "Group: #{@group} could not be updated. Errors: #{errors}"
+ end
+ end
+ end
+
+ # DELETE /groups/1
+ def destroy
+ @group = Group.find(params[:id])
+ @group.destroy
+ end
+
+ # DELETE /groups
+ def destroyMultiple
+ 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
+ group = Group.find(groupID)
+ @project = Project.find(group.project_id)
+ authorize_project! @project
+ group.destroy
+ end
+ end
+
+ private
+
+ def set_group
+ @group = Group.find(params[:id])
+ @project = Project.find(@group.project_id)
+ authorize_project! @project
+ end
+
+ def group_params
+ params.require(:group).permit(:project_id, :type, :title, :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, :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..af36e095
--- /dev/null
+++ b/viscoll-api/app/controllers/images_controller.rb
@@ -0,0 +1,168 @@
+class ImagesController < ApplicationController
+ before_action :authenticate!, except: [:show, :getZipImages]
+
+ def authorize_image! image
+ return if current_user.id == image.user_id
+ raise ApplicationController::VCError, "Image (#{image.id}) is not authorized for current user (#{current_user.id}); expected: '#{image.user_id}'."
+ end
+
+ # POST /images
+ def uploadImages
+ projectIDs = []
+ if image_create_params.to_h.key?("projectID")
+ @project = Project.find(image_create_params.to_h[:projectID])
+ authorize_project! @project
+ projectIDs.push(@project.id.to_s)
+ 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
+ raise VCError, "Image failed: #{image.errors.full_messages.join("\n")}"
+ 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
+ # p params[:imageID_filename]
+ imageID = params[:imageID_filename].split("_", 2)[0]
+ filename = params[:imageID_filename].split("_", 2)[1]
+ @image = Image.find(imageID)
+ # 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
+ projectID = params[:id]
+ zipFilePath = "#{Rails.root}/public/uploads/#{projectID}_images.zip"
+ send_file zipFilePath, :type => 'application/zip', :disposition => 'inline'
+ 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|
+ project = Project.find(projectID)
+ authorize_project! project
+ projects.push(project)
+ end
+ images = []
+ imageIDs.each do |imageID|
+ image = Image.find(imageID)
+ authorize_image! image
+ images.push(image)
+ 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|
+ project = Project.find(projectID)
+ authorize_project! project
+ projects.push(project)
+ end
+ images = []
+ imageIDs.each do |imageID|
+ image = Image.find(imageID.split("_", 2)[0])
+ authorize_image! image
+ images.push(image)
+ 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|
+ imageID = imageIDParam.split("_", 2)[0]
+ image = Image.find(imageID)
+ images.push(image)
+ authorize_image! image
+ 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..6e6d9a69
--- /dev/null
+++ b/viscoll-api/app/controllers/import_controller.rb
@@ -0,0 +1,39 @@
+class ImportController < ApplicationController
+ before_action :authenticate!
+
+ # PUT /projects/import
+ def index
+ importData = imported_data.to_h[:importData]
+ importFormat = imported_data.to_h[:importFormat]
+ imageData = imported_data.to_h[:imageData]
+ 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
+ raise VCError, "XML import failed: #{(errors + errors2).join "\n"}"
+ 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
+ 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/instance_controller.rb b/viscoll-api/app/controllers/instance_controller.rb
new file mode 100644
index 00000000..d5da59be
--- /dev/null
+++ b/viscoll-api/app/controllers/instance_controller.rb
@@ -0,0 +1,6 @@
+class InstanceController < ApplicationController
+ def getInstance
+ instance_hash = {current_instance: ENV['INSTANCE']}
+ render json: instance_hash, status: :ok
+ end
+end
\ No newline at end of file
diff --git a/viscoll-api/app/controllers/leafs_controller.rb b/viscoll-api/app/controllers/leafs_controller.rb
new file mode 100644
index 00000000..9c094d12
--- /dev/null
+++ b/viscoll-api/app/controllers/leafs_controller.rb
@@ -0,0 +1,270 @@
+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
+ raise VCError, "Leaf validation failed: #{@leafErrors.join "\n"}"
+ 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
+ raise VCError, "Validation failed: #{@additionalErrors}"
+ end
+
+ # Attempt to validate ownership
+ @project = Project.find(project_id)
+ authorize_project! @project
+
+ # 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
+ raise VCError, @leaf.errors.full_messages.join("\n")
+ 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
+
+ # PUT /leafs/generateFolio
+ def generateFolio
+ folioNumberCount = leaf_params_generate.to_h[:startNumber].to_i
+ leafIDs = leaf_params_generate.to_h[:leafIDs]
+ leafIDs.each_with_index do |leafID, index|
+ leaf = Leaf.find(leafID)
+ leaf.update_attribute(:folio_number, folioNumberCount.to_s)
+ folioNumberCount += 1
+ if index == 0
+ @project = Project.find(leaf.project_id)
+ end
+ end
+ 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
+ raise VCError, "Leaf failed to update: #{@leaf.errors.full_messages.join "\n"}"
+ end
+ end
+
+ # PATCH/PUT /leafs
+ def updateMultiple
+ allLeafs = leaf_params_batch_update.to_h[:leafs]
+ @project = Project.find(leaf_params_batch_update.to_h[:project_id])
+ allLeafs.each do |leaf_params, index|
+ @leaf = Leaf.find(leaf_params[:id])
+ authorize_project! @project
+ if !@leaf.update(leaf_params[:attributes])
+ raise VCError, "Leaf could not be updated: #{leaf.errors.full_messages.join "\n"}"
+ 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
+ end
+
+ # DELETE /leafs/1
+ def destroy
+ 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
+ end
+
+ # DELETE /leafs.json
+ def destroyMultiple
+ 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
+ raise VCError, "Leaf belongs to user (#{leaf.project.user_id}) which does not match the current user's ID (#{current_user.id})"
+ 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
+ end
+
+ # CONJOIN /leafs.json
+ def conjoinLeafs
+ 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)
+ raise VCError, "Conjoin not allowed."
+ 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
+ raise VCError, "Error with conjoin: #{@errors.join "\n"}"
+ end
+ @project = Project.find(leaves[0].project_id)
+ autoConjoinLeaves(leaves, (leaves.length + 1) / 2)
+ end
+
+ private
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_leaf
+ @leaf = Leaf.find(params[:id])
+ @project = Project.find(@leaf.project_id)
+ authorize_project! @project
+ end
+
+ # Never trust parameters from the scary internet, only allow the white list through.
+ def leaf_params
+ params.require(:leaf).permit(:folio_number, :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 => [:folio_number, :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
+
+ def leaf_params_generate
+ params.permit(:startNumber, :leafIDs => [])
+ 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..eafc8be6
--- /dev/null
+++ b/viscoll-api/app/controllers/projects_controller.rb
@@ -0,0 +1,191 @@
+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
+ # 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])
+ raise VCError, "Validation of create groups params failed: #{validationResult[:errors]}"
+ end
+ # Instantiate a new project with the given params
+ @project = Project.new(project_params)
+ # If the project contains taxonomies, add the 'Unknown' type if its not present
+ if (not @project.taxonomies.empty? and not @project.taxonomies.include?('Unknown'))
+ @project.taxonomies.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
+ raise VCError, "Project could not save: #{@project.errors.full_messages.join "\n"}"
+ end
+ end
+
+ # PATCH/PUT /projects/1
+ def update
+ @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
+ raise VCError, "Project could not update: #{@project.errors.full_messages.join "\n"}"
+ end
+ end
+
+ # DELETE /projects/1
+ def destroy
+ deleteUnlinkedImages = project_delete_params.to_h["deleteUnlinkedImages"]
+ begin
+ # Skip some callbacks
+ Leaf.skip_callback(:destroy, :before, :unlink_terms)
+ 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
+ ensure
+ # Enable callbacks again
+ Image.set_callback(:destroy, :before, :unlink_sides_before_delete)
+ Leaf.set_callback(:destroy, :before, :unlink_terms)
+ end
+ end
+
+ # POST /projects/:id/manifests
+ def createManifest
+ 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
+ end
+
+ # PATCH/PUT /projects/:id/manifests
+ def updateManifest
+ manifest = manifest_params.to_h
+ if not manifest.key?("id")
+ raise VCError, "ID is required."
+ end
+ if not @project.manifests.key?(manifest["id"])
+ raise VCError, "Manifest (#{manifest["id"]}) is not found in project (#{@project.id})"
+ end
+ # ONLY UPDATING MANIFEST NAME FOR NOW
+ @project.manifests[manifest["id"]]["name"] = manifest["name"]
+ @project.save
+ end
+
+ # DELETE /projects/:id/manifests
+ def deleteManifest
+ manifestIDToDelete = manifest_params.to_h[:id]
+ if not @project.manifests.key?(manifestIDToDelete)
+ raise VCError, "Manifest (#{manifest["id"]}) is not found in project (#{@project.id})"
+ 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
+ end
+
+ # GET /projects/:id/clone
+ def clone
+ exportedData = buildJSON(@project)
+ export = {
+ project: exportedData[:project],
+ Groups: exportedData[:groups],
+ Leafs: exportedData[:leafs],
+ Rectos: exportedData[:rectos],
+ Versos: exportedData[:versos],
+ Terms: exportedData[:terms],
+ }
+ 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
+ end
+
+ private
+
+ def set_project
+ @project = Project.find(params[:id])
+ authorize_project! @project
+ end
+
+ # Never trust parameters from the scary Internet, only allow the white list through.
+ def project_params
+ params.require(:project).permit(:title, :shelfmark, :notationStyle, :metadata => {}, :taxonomies => [], :preferences => {})
+ end
+
+ def project_delete_params
+ params.permit(:deleteUnlinkedImages)
+ end
+
+ def group_params
+ params.permit(:folioNumber, :pageNumber, :startingTexture, :groups => [:number, :leaves, :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..2593bb4f
--- /dev/null
+++ b/viscoll-api/app/controllers/registrations_controller.rb
@@ -0,0 +1,14 @@
+class RegistrationsController < RailsJwtAuth::RegistrationsController
+
+ def create
+ user = RailsJwtAuth.model.new(registration_create_params)
+ user.save ? render_registration(user) : render_422(user.errors)
+ 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..ae257549
--- /dev/null
+++ b/viscoll-api/app/controllers/sessions_controller.rb
@@ -0,0 +1,67 @@
+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
+ authenticateDestroy!
+ current_user.destroy_auth_token Jwt::Request.new(request).auth_token
+ render_204
+ 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..dd0832e3
--- /dev/null
+++ b/viscoll-api/app/controllers/sides_controller.rb
@@ -0,0 +1,110 @@
+class SidesController < ApplicationController
+ before_action :authenticate!
+ before_action :set_side, only: [:update]
+
+ # PATCH/PUT /sides/1
+ def update
+ if !@side.update(side_params)
+ raise VCError, "Side (#{@side.id} could not be updated: #{@side.errors.full_messages.join "\n"})"
+ end
+ end
+
+ # PATCH/PUT /sides
+ def updateMultiple
+ 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)
+ authorize_project! @project
+ if haveErrors
+ raise VCError, "Errors occurred when updating: #{@errors.join "\n"}"
+ end
+ allSides.each_with_index do |side_params, index|
+ side = sides[index]
+ previousSideImage = side.image.clone
+ if !side.update(side_params[:attributes])
+ raise VCError, "Errors occurred when updating sides: #{side.errors.full_messages.join "\n"}"
+ 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]
+ imageLinked = Image.find(imageID)
+ !(imageLinked.sideIDs.include?(side.id.to_s)) ? imageLinked.sideIDs.push(side.id.to_s) : nil
+ imageLinked.save
+ 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]
+ 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
+ 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]
+ image = Image.find(imageID)
+ image.sideIDs.include?(side.id.to_s) ? image.sideIDs.delete(side.id.to_s) : nil
+ image.save
+ end
+ end
+ 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
+ @side = Side.find(params[:id])
+ @project = Project.find(@side.project_id)
+ authorize_project! @project
+ end
+
+ # Never trust parameters from the scary internet, only allow the white list through.
+ def side_params
+ params.require(:side).permit(:page_number, :texture, :script_direction, :image => [:manifestID, :label, :url])
+ end
+
+ def side_params_batch_update
+ params.permit(:sides => [:id, :attributes => [: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/static_controller.rb b/viscoll-api/app/controllers/static_controller.rb
new file mode 100644
index 00000000..478199aa
--- /dev/null
+++ b/viscoll-api/app/controllers/static_controller.rb
@@ -0,0 +1,5 @@
+class StaticController < ApplicationController
+ def index
+ render file: Rails.root.join('public', 'index.html')
+ end
+end
diff --git a/viscoll-api/app/controllers/terms_controller.rb b/viscoll-api/app/controllers/terms_controller.rb
new file mode 100644
index 00000000..580c7ece
--- /dev/null
+++ b/viscoll-api/app/controllers/terms_controller.rb
@@ -0,0 +1,182 @@
+class TermsController < ApplicationController
+ before_action :authenticate!
+ before_action :set_term, only: [:update, :link, :unlink, :destroy]
+ before_action :set_attached_project, only: [:createTaxonomy, :deleteTaxonomy, :updateTaxonomy]
+
+ # POST /terms
+ def create
+ @term = Term.new(term_create_params)
+ @project = Project.find(@term.project_id)
+ authorize_project! @project
+ if @term.save
+ if not Project.find(@term.project_id).taxonomies.include?(@term.taxonomy)
+ @term.delete
+ raise VCError, "Taxonomy (#{@term.taxonomy}) does not belong to project (#{@project.id})."
+ end
+ else
+ raise VCError, "Something went wrong with saving terms: #{@term.errors.full_messages.join("\n")}"
+ end
+ end
+
+ # PATCH/PUT /terms/1
+ def update
+ taxonomy = term_update_params.to_h[:taxonomy]
+ if not Project.find(@term.project_id).taxonomies.include?(taxonomy)
+ raise VCError, "Taxonomy (#{@term.taxonomy}) does not belong to project (#{@project.id})."
+ end
+ if !@term.update(term_update_params)
+ raise VCError, "Term (#{@term.id}) could not update: #{@term.errors.full_messages.join "\n"}"
+ end
+ end
+
+ # DELETE /terms/1
+ def destroy
+ @term.destroy
+ end
+
+ # PUT /terms/1/link
+ def link
+ objects = term_object_link_params.to_h[:objects]
+ objects.each do |object|
+ type = object[:type]
+ id = object[:id]
+ 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
+ raise VCError, "Object not found with type: #{type}"
+ end
+ unless authorized
+ raise VCError, "Action not authorized."
+ end
+ @object.terms.push(@term)
+ @object.save
+ if (not @term.objects[type].include?(id))
+ @term.objects[type].push(id)
+ end
+ @term.save
+ end
+ end
+
+ # PUT /terms/1/unlink
+ def unlink
+ objects = term_object_link_params.to_h[:objects]
+ objects.each do |object|
+ type = object[:type]
+ id = object[:id]
+ 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
+ raise VCError, "Object not found with type: #{type}"
+ end
+ unless authorized
+ raise VCError, "Action not authorized."
+ end
+ @object.terms.delete(@term)
+ @object.save
+ @term.objects[type].delete(id)
+ @term.save
+ end
+ end
+
+ # POST /terms/taxonomy
+ def createTaxonomy
+ taxonomy = taxonomy_params.to_h[:taxonomy]
+ if @project.taxonomies.include?(taxonomy)
+ raise VCError, "Taxonomy (#{taxonomy}) already exists in the project (#{@project.id})"
+ else
+ @project.taxonomies.push(taxonomy)
+ @project.save
+ end
+ end
+
+ # DELETE /terms/taxonomy
+ def deleteTaxonomy
+ taxonomy = taxonomy_params.to_h[:taxonomy]
+ if not @project.taxonomies.include?(taxonomy)
+ raise VCError, "Taxonomy (#{taxonomy}) does not exist in the project (#{@project.id})"
+ else
+ @project.taxonomies.delete(taxonomy)
+ @project.save
+ @project.terms.where(taxonomy: taxonomy).each do |term|
+ term.update(taxonomy: "Unknown")
+ term.save
+ end
+ end
+ end
+
+ # PUT /terms/taxonomy
+ def updateTaxonomy
+ old_taxonomy = taxonomy_params.to_h[:old_taxonomy]
+ taxonomy = taxonomy_params.to_h[:taxonomy]
+ if not @project.taxonomies.include?(old_taxonomy)
+ raise VCError, "Taxonomy (#{taxonomy}) does not exist in the project (#{@project.id})"
+ elsif @project.taxonomies.include?(taxonomy)
+ raise VCError, "Taxonomy (#{taxonomy}) already exists in the project (#{@project.id})"
+ else
+ indexToEdit = @project.taxonomies.index(old_taxonomy)
+ @project.taxonomies[indexToEdit] = taxonomy
+ @project.save
+ @project.terms.where(taxonomy: old_taxonomy).each do |term|
+ term.update(taxonomy: taxonomy)
+ term.save
+ end
+ end
+ end
+
+ private
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_term
+ # when the ID is first sent to the backend to be created,
+ # it doesn't have 'Term_' - we need to append it in the
+ # controller in order for our Mongo query to execute
+ term_id = if params[:id].include? 'Term_'
+ params[:id]
+ else
+ 'Term_' + params[:id]
+ end
+ @term = Term.find(term_id)
+ @project = Project.find(@term.project_id)
+ authorize_project! @project
+ end
+
+ def set_attached_project
+ project_id = taxonomy_params.to_h[:project_id]
+ @project = Project.find(project_id)
+ authorize_project! @project
+ end
+
+ # Never trust parameters from the scary internet, only allow the white list through.
+ def term_create_params
+ params.require(:term).permit(:project_id, :id, :title, :taxonomy, :description, :uri, :show)
+ end
+
+ def term_update_params
+ params.require(:term).permit(:title, :taxonomy, :description, :uri, :show)
+ end
+
+ def term_object_link_params
+ params.permit(:objects => [:id, :type])
+ end
+
+ def taxonomy_params
+ params.require(:taxonomy).permit(:taxonomy, :project_id, :old_taxonomy)
+ 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..b14bda06
--- /dev/null
+++ b/viscoll-api/app/controllers/users_controller.rb
@@ -0,0 +1,49 @@
+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
+ raise VCError, "User update failed: #{current_user.errors.full_messages.join "\n"}"
+ end
+
+ end
+
+ # DELETE /users/1
+ def destroy
+ @user.destroy
+ end
+
+ private
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_user
+ @user = User.find(params[:id])
+ if (@user != current_user)
+ raise VCError, "Unauthorized. User's do not match."
+ 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/controllers/xproc_controller.rb b/viscoll-api/app/controllers/xproc_controller.rb
new file mode 100644
index 00000000..0cceb0b5
--- /dev/null
+++ b/viscoll-api/app/controllers/xproc_controller.rb
@@ -0,0 +1,17 @@
+class XprocController < ApplicationController
+ before_action :authenticate!, except: [:show, :get_zip]
+
+ # GET /xproc/zip/:job_id
+ def get_zip
+ job_id = params[:job_id]
+ zip_file_path = "#{Rails.root}/public/xproc/#{job_id}.zip"
+ send_file zip_file_path, :type => 'application/zip', :disposition => 'inline'
+ end
+
+ private
+
+ def zip_params
+ params.permit(:job_id)
+ 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..5ae30a79
--- /dev/null
+++ b/viscoll-api/app/helpers/controller_helper/export_helper.rb
@@ -0,0 +1,543 @@
+require 'erb'
+
+module ControllerHelper
+ module ExportHelper
+
+ IMAGE_LIST_ERB = File.expand_path '../image_list.xml.erb', __FILE__
+
+ def buildJSON(project)
+ @project.reload
+ @projectInformation = {}
+ @groupIDs = @project.groupIDs
+ @leafIDs = []
+ @rectoIDs = []
+ @versoIDs = []
+ @groups = {}
+ @leafs = {}
+ @rectos = {}
+ @versos = {}
+ @terms = {}
+
+ @projectInformation = {
+ "title": @project.title,
+ "shelfmark": @project.shelfmark,
+ "notationStyle": @project.notationStyle,
+ "metadata": @project.metadata,
+ "preferences": @project.preferences,
+ "manifests": @project.manifests,
+ "taxonomies": @project.taxonomies
+ }
+
+ rootMemberOrder = 1
+ @groupIDs.each_with_index do |groupID, index|
+ group = @project.groups.find(groupID)
+ @groups[index + 1] = {
+ "params": {
+ "type": group.type,
+ "title": group.title,
+ "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": {
+ "folio_number": leaf.folio_number ? leaf.folio_number : '',
+ "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": {
+ "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": {
+ "page_number": verso.page_number ? verso.page_number : "",
+ "texture": verso.texture,
+ "image": verso.image,
+ "script_direction": verso.script_direction
+ },
+ "parentOrder": parentOrder
+ }
+ end
+
+ @project.terms.each_with_index do |term, index|
+ @terms[index + 1] = {
+ "params": {
+ "title": term.title,
+ "taxonomy": term.taxonomy,
+ "description": term.description,
+ "show": term.show
+ },
+ "objects": {}
+ }
+ if term.uri.present?
+ @terms[index + 1][:params][:uri] = term.uri
+ end
+
+ @terms[index + 1][:objects][:Group] = term.objects["Group"].map { |groupID| @groupIDs.index(groupID) + 1 }
+ @terms[index + 1][:objects][:Leaf] = term.objects["Leaf"].map { |leafID| @leafIDs.index(leafID) + 1 }
+ @terms[index + 1][:objects][:Recto] = term.objects["Recto"].map { |rectoID| @rectoIDs.index(rectoID) + 1 }
+ @terms[index + 1][:objects][:Verso] = term.objects["Verso"].map { |versoID| @versoIDs.index(versoID) + 1 }
+ end
+
+ return {
+ "project": @projectInformation,
+ "groups": @groups,
+ "leafs": @leafs,
+ "rectos": @rectos,
+ "versos": @versos,
+ "terms": @terms,
+ }
+ 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 findSideParents hash, key, side
+ id_list = hash[key]
+ return [] if id_list.nil?
+ side_IDs = id_list.select { |i| i[0] == side[0] }
+ side_IDs.map { |s| Side.find(s).parentID }
+ end
+
+ def findNonSides hash, key
+ id_list = hash[key]
+ return [] if id_list.nil?
+ sides = ["R", "V"]
+ id_list.reject { |i| sides.include? i[0] }
+ end
+
+ def buildDotModel(project)
+ @groupIDs = project.groupIDs
+ @groups = {}
+ @leafIDs = []
+ @leafs = {}
+ @rectos = {}
+ @versos = {}
+ @terms = {}
+ @termTitles = []
+ @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
+
+ schema_xml = Nokogiri::XML File.open("public/viscoll-datamodel2.0.rng")
+ schema_xml.remove_namespaces!
+ path = <<~X
+ /grammar/start/element[@name = "viscoll"]/optional/attribute[@name="version"]/choice/value/text()
+ X
+ version = schema_xml.xpath path
+
+ return Nokogiri::XML::Builder.new { |xml|
+ xml.viscoll :xmlns => "http://viscoll.org/schema/collation/", :version => version do
+ idPrefix = project.shelfmark.parameterize.underscore
+
+ # STRUCTURE
+ xml.textblock 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]
+ next if group.parentID.present?
+ quireAttributes = {}
+ quireAttributes["xml:id"] = group.id
+ quireAttributes[:n] = group.group_notation
+ quireAttributes[:certainty] = 1
+ if group.parentID
+ quireAttributes[:parent] = group.parentID
+ end
+ xml.quire quireAttributes do
+ # xml.text index + 1
+ # TODO: loop though quire's subquires
+ end
+ @groups[groupID]["xmlID"] = quireAttributes["xml:id"]
+ end
+ end
+ xml.leaves do
+ @leafIDs.each_with_index do |leafID, index|
+ leaf = project.leafs.find(leafID)
+ leafAttributes = {}
+ leafAttributes["xml:id"] = leaf.id
+ leafAttributes["stub"] = "yes" if leaf.stubType != "No"
+ xml.leaf leafAttributes do
+
+ # if leaf.folio_number
+ # folioNumberAttr = {}
+ # folioNumberAttr[:certainty] = 1
+ # folioNumber = leaf.folio_number
+ # folioNumberAttr[:val] = folioNumber
+ # xml.folioNumber folioNumberAttr do
+ # xml.text folioNumber
+ # end
+ # elsif rectoSide.page_number && leaf.folio_number.nil?
+ # pageNumberAttr = {}
+ # pageNumberAttr[:certainty] = 1
+ # pageNumber = "#{rectoSide.page_number.to_s}-#{versoSide.page_number.to_s}"
+ # pageNumberAttr[:val] = pageNumber
+ # xml.folioNumber pageNumberAttr do
+ # xml.text pageNumber
+ # end
+ # end
+
+ # get side objects
+ rectoSide = project.sides.find(leaf.rectoID)
+ versoSide = project.sides.find(leaf.versoID)
+
+ # generate page notation
+ numbers = []
+ numbers[0] = leaf.folio_number
+ pages = [rectoSide.page_number, versoSide.page_number]
+ pages.compact!
+ page_number = pages.empty? ? nil : pages.join('-')
+ numbers[1] = page_number
+ pageNotation = nil
+ pageNotation = numbers.empty? ? nil : numbers.compact.join('; ')
+
+ unless pageNotation.empty?
+ # folioNumber element
+ folioNumberAttr = {}
+ folioNumberAttr[:certainty] = 1
+ folioNumberAttr[:val] = pageNotation
+ xml.folioNumber folioNumberAttr do
+ xml.text pageNotation
+ end
+ end
+
+ mode = {}
+ if ['original', 'added', 'replaced', 'false', 'missing'].include? leaf.type.downcase
+ mode[:val] = leaf.type.downcase
+ mode[:certainty] = 1
+ end
+ xml.mode mode
+
+ # TODO: come up with consistent way of caching and assigning xml IDs
+ qAttributes = {}
+ qAttributes[:target] = "#" + leaf.parentID
+ qAttributes[:position] = leaf.position_in_top_level_group
+ qAttributes[:n] = project.groups.find(leaf.parentID).group_notation
+ qAttributes[:certainty] = 1
+ xml.q qAttributes do
+ if leaf.conjoined_to
+ xml.conjoin :certainty => 1, :target => "#" + leaf.conjoined_to
+ else
+ xml.single :val => "yes"
+ end
+ end
+
+ #
+ attachmentAttributes = {}
+ attachmentAttributes[:certainty] = 1
+
+ if leaf.attached_above != "None"
+ attachmentAttributes[:type] = leaf.attached_above.downcase
+ attachmentAttributes[:target] = "#" + @leafIDs[@leafIDs.index(leaf.id) - 1]
+ xml.send('attachment-method', attachmentAttributes)
+ end
+
+ if leaf.attached_below != "None"
+ attachmentAttributes[:type] = leaf.attached_below.downcase
+ attachmentAttributes[:target] = "#" + @leafIDs[@leafIDs.index(leaf.id) + 1]
+ xml.send('attachment-method', attachmentAttributes)
+ end
+
+ rectoSide = project.sides.find(leaf.rectoID)
+ rectoAttributes = {}
+ rectoAttributes["xml:id"] = leafAttributes["xml:id"]
+ rectoAttributes[:type] = "Recto"
+ 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.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
+ end
+
+ # Hard-coded parchment sides taxonomy
+ parch_att = { 'xml:id': "id-sides", ref: "http://w3id.org/lob/" }
+ xml.taxonomy parch_att do
+ xml.label do
+ xml.text "Parchment Sides"
+ end
+ hs_attributes = { 'xml:id': "id-hs", ref: "http://w3id.org/lob/concept/1381" }
+ xml.term hs_attributes do
+ xml.label do
+ xml.text "hairside"
+ end
+ end
+ fs_attributes = { 'xml:id': "id-fs", ref: "http://w3id.org/lob/concept/1336" }
+ xml.term fs_attributes do
+ xml.label do
+ xml.text "fleshside"
+ end
+ end
+ left_attributes = { 'xml:id': "id-left", ref: "http://w3id.org/lob/concept/2947" }
+ xml.term left_attributes do
+ xml.label do
+ xml.text "left"
+ end
+ end
+ right_attributes = { 'xml:id': "id-right", ref: "http://w3id.org/lob/concept/3004" }
+ xml.term right_attributes do
+ xml.label do
+ xml.text "right"
+ end
+ end
+ end
+
+ # terms taxonomy
+ if project.terms.present?
+ terms_att = { 'xml:id': "id-terms" }
+ xml.taxonomy terms_att do
+ xml.label do
+ xml.text "List of all Terms"
+ end
+ project.terms.each do |term|
+ term_att = { 'xml:id': "#{term.id}" }
+ xml.term term_att do
+ xml.label do
+ xml.text term.title
+ end
+ end
+ end
+ end
+ end
+
+ # Creating taxonomy elements
+ project.taxonomies.each_with_index do |taxonomy|
+ require 'digest'
+ taxAtt = { 'xml:id': "id-#{Digest::MD5.hexdigest(taxonomy)}" }
+ # grab an array of terms with the current taxonomy
+ children = project.terms.select { |term| term.taxonomy == taxonomy }
+ if children.present?
+ xml.taxonomy taxAtt do
+ xml.label do
+ xml.text taxonomy
+ end
+ # add proper attributes and crete term elements
+ children.each do |childTerm|
+ termAttributes = { 'xml:id': "#{childTerm.id}" }
+ if childTerm.uri.present?
+ termAttributes['ref'] = childTerm.uri
+ end
+ xml.term termAttributes do
+ xml.label do
+ xml.text childTerm.title
+ end
+ end
+ end
+ end
+ end
+ end
+
+ # check if any mappings exist
+ if project.mapping?
+ mappings_hash = {}
+ project.mappings.each do |mapping|
+ mappings_hash[mapping.keys.first] ||= []
+ mappings_hash[mapping.keys.first] << mapping[mapping.keys.first]
+ end
+ # MAPPING
+ xml.mapping do
+ mappings_hash.keys.each do |k|
+ xml_id = case k
+ when "Hair"
+ "id-hs"
+ when "Flesh"
+ "id-fs"
+ else
+ k
+ end
+ term_recto_parents = findSideParents mappings_hash, k, "R"
+ if term_recto_parents.present?
+ term_recto_att = { target: term_recto_parents.map { |m| "##{m}" }.join(' '), side: project.recto_side }
+ xml.map term_recto_att do
+ xml.term target: "##{xml_id}"
+ end
+ end
+
+ term_verso_parents = findSideParents mappings_hash, k, "V"
+ if term_verso_parents.present?
+ term_verso_att = { target: term_verso_parents.map { |m| "##{m}" }.join(' '), side: project.verso_side }
+ xml.map term_verso_att do
+ xml.term target: "##{xml_id}"
+ end
+ end
+
+ non_side_IDs = findNonSides mappings_hash, k
+ if non_side_IDs.present?
+ non_side_att = {target: non_side_IDs.map { |m| "##{m}" }.join(' ')}
+ xml.map non_side_att do
+ xml.term target: "##{xml_id}"
+ end
+ end
+ 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
+
+ def build_image_list project
+ erb = ERB.new open(IMAGE_LIST_ERB).read
+ image_list = erb.result binding
+ image_list
+ 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..71a8ce3b
--- /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', 'does not equal'],
+ 'title' => ['equals', 'does not equal', 'contains', 'does not contain']
+ },
+ 'leaf' => {
+ 'type' => ['equals', 'does not equal'],
+ 'material' => ['equals', 'does not equal'],
+ 'conjoined_to' => ['equals', 'does not equal'],
+ 'attached_above' => ['equals', 'does not equal'],
+ 'attached_below' => ['equals', 'does not equal'],
+ 'stub' => ['equals', 'does not equal'],
+ 'folio_number' => ['equals', 'does not equal', 'contains', 'does not contain']
+ },
+ 'side' => {
+ 'texture' => ['equals', 'does not equal'],
+ 'script_direction' => ['equals', 'does not equal'],
+ 'page_number' => ['equals', 'does not equal', 'contains', 'does not contain'],
+ 'uri' => ['equals', 'does not equal', 'contains', 'does not contain'],
+ },
+ 'term' => {
+ 'title' => ['equals', 'does not equal', 'contains', 'does not contain'],
+ 'taxonomy' => ['equals', 'does not equal', 'contains', 'does not contain'],
+ 'description' => ['equals', 'does not equal', 'contains', 'does not contain'],
+ 'uri' => ['equals', 'does not equal', 'contains', 'does not contain']
+ }
+ },
+ 'conjunction' => ['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/image_list.xml.erb b/viscoll-api/app/helpers/controller_helper/image_list.xml.erb
new file mode 100644
index 00000000..ff71dfc3
--- /dev/null
+++ b/viscoll-api/app/helpers/controller_helper/image_list.xml.erb
@@ -0,0 +1,80 @@
+
+
+
+
+ Dot Porter
+ Patrick Perkins
+ 2015-05-09T23:08:49Z
+ 2015-05-15T04:53:34Z
+ University of Pennsylvania Libraries
+ 16.00
+
+
+
+
+
+ 15500
+ 25040
+ 32767
+ 2720
+ 500
+ False
+ False
+ False
+
+
+
+
+
+
+
+ <% project.leafs.each do |leaf| %>
+
+ <%= leaf.folio_number %>r |
+ <% if Side.find(leaf.rectoID).image["url"].present? %>
+ <%= Side.find(leaf.rectoID).image_url %> |
+ <% else %>
+ https://cdn.rawgit.com/leoba/VisColl/master/data/support/images/x.jpg |
+ <% end %>
+
+
+ <%= leaf.folio_number %>v |
+ <% if Side.find(leaf.versoID).image["url"].present? %>
+ <%= Side.find(leaf.versoID).image_url %> |
+ <% else %>
+ https://cdn.rawgit.com/leoba/VisColl/master/data/support/images/x.jpg |
+ <% end %>
+
+ <% end %>
+
+
+
+
+ -4
+ -4
+
+
+
+
+ 3
+ 4
+ 3
+
+
+ False
+ False
+
+
+
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..32863067
--- /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 Terms
+ data["Terms"].each do |termOrder, data|
+ data["params"]["project_id"] = project.id
+ term = Term.new(data["params"])
+ # Generate objectIDs of Groups, Leafs, Rectos, Versos with this term
+ groupIDs = []
+ data["objects"]["Group"].each do |groupOrder|
+ groupID = allGroupsIDsInOrder[groupOrder-1]
+ group = project.groups.find(groupID)
+ group.terms.push(term)
+ group.save
+ groupIDs.push(groupID)
+ end
+ leafIDs = []
+ data["objects"]["Leaf"].each do |leafOrder|
+ leafID = allLeafsIDsInOrder[leafOrder-1]
+ leaf = project.leafs.find(leafID)
+ leaf.terms.push(term)
+ leaf.save
+ leafIDs.push(leafID)
+ end
+ rectoIDs = []
+ data["objects"]["Recto"].each do |rectoOrder|
+ rectoID = allRectosIDsInOrder[rectoOrder-1]
+ recto = project.sides.find(rectoID)
+ recto.terms.push(term)
+ recto.save
+ rectoIDs.push(rectoID)
+ end
+ versoIDs = []
+ data["objects"]["Verso"].each do |versoOrder|
+ versoID = allVersosIDsInOrder[versoOrder-1]
+ verso = project.sides.find(versoID)
+ verso.terms.push(term)
+ verso.save
+ versoIDs.push(versoID)
+ end
+ term.objects[:Group] = groupIDs
+ term.objects[:Leaf] = leafIDs
+ term.objects[:Recto] = rectoIDs
+ term.objects[:Verso] = versoIDs
+ term.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://viscoll.org/schema/collation/")
+ allLeaves = xml.xpath('//x:leaf', "x" => "http://viscoll.org/schema/collation/")
+ allTerms = xml.xpath('//x:note', "x" => "http://viscoll.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
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..5ade6b1f
--- /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 Terms
+ data["Terms"].each do |termOrder, data|
+ data["params"]["project_id"] = project.id
+ term = Term.new(data["params"])
+ # Generate objectIDs of Groups, Leafs, Rectos, Versos with this term
+ groupIDs = []
+ data["objects"]["Group"].each do |groupOrder|
+ groupID = allGroupsIDsInOrder[groupOrder-1]
+ group = project.groups.find(groupID)
+ group.terms.push(term)
+ group.save
+ groupIDs.push(groupID)
+ end
+ leafIDs = []
+ data["objects"]["Leaf"].each do |leafOrder|
+ leafID = allLeafsIDsInOrder[leafOrder-1]
+ leaf = project.leafs.find(leafID)
+ leaf.terms.push(term)
+ leaf.save
+ leafIDs.push(leafID)
+ end
+ rectoIDs = []
+ data["objects"]["Recto"].each do |rectoOrder|
+ rectoID = allRectosIDsInOrder[rectoOrder-1]
+ recto = project.sides.find(rectoID)
+ recto.terms.push(term)
+ recto.save
+ rectoIDs.push(rectoID)
+ end
+ versoIDs = []
+ data["objects"]["Verso"].each do |versoOrder|
+ versoID = allVersosIDsInOrder[versoOrder-1]
+ verso = project.sides.find(versoID)
+ verso.terms.push(term)
+ verso.save
+ versoIDs.push(versoID)
+ end
+ term.objects[:Group] = groupIDs
+ term.objects[:Leaf] = leafIDs
+ term.objects[:Recto] = rectoIDs
+ term.objects[:Verso] = versoIDs
+ term.save
+ end
+
+ # Update project groupIDs
+ project.groupIDs = allGroupsIDsInOrder
+ project.save
+ end
+ end
+end
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..3848ab71
--- /dev/null
+++ b/viscoll-api/app/helpers/controller_helper/import_xml_helper.rb
@@ -0,0 +1,316 @@
+require 'uri'
+
+module ControllerHelper
+ module ImportXmlHelper
+ # XML IMPORT
+ def handleXMLImport(xml)
+ @allGroupNodeIDsInOrder = []
+ @allLeafNodeIDsInOrder = []
+ @groups = {}
+ @leafs = {}
+ @rectos = {}
+ @versos = {}
+ @terms = {}
+
+ # Project Information
+ @projectInformation = {
+ title: "",
+ shelfmark: "",
+ metadata: {date: ""},
+ preferences: {showTips: true},
+ manifests: {},
+ taxonomies: ["Unknown"]
+ }
+ # Grab project Title
+ projectTitleNode = xml.xpath("//x:title", "x" => "http://viscoll.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://viscoll.org/schema/collation/")
+ @projectInformation[:shelfmark] = projectShelfmarkNode.text
+ # grap prohect Date
+ projectDateNode = xml.xpath("//x:date", "x" => "http://viscoll.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://viscoll.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://viscoll.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://viscoll.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: "",
+ 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://viscoll.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://viscoll.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_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://viscoll.org/schema/collation/")
+ allLeafNodes.each_with_index do |leafNode, index|
+ leafNodeID = leafNode.attributes["id"].value
+ stub = leafNode.attributes["stub"] ? "Original" : "No"
+ 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: {
+ folio_number: nil,
+ 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: {
+ page_number: nil,
+ texture: "None",
+ image: {},
+ script_direction: "None"
+ },
+ parentOrder: leafOrder,
+ noteTitles: []
+ }
+ @versos[leafOrder] = {
+ params: {
+ 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://viscoll.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://viscoll.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://viscoll.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
+
+ # 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://viscoll.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,
+ Terms: @terms
+ }
+
+ handleJSONImport(JSON.parse(jsonImport.to_json))
+
+ end
+ end
+end
\ No newline at end of file
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..569d1c35
--- /dev/null
+++ b/viscoll-api/app/helpers/controller_helper/projects_helper.rb
@@ -0,0 +1,281 @@
+require 'net/http'
+module ControllerHelper
+ module ProjectsHelper
+ include ControllerHelper::LeafsHelper
+
+ def authorize_project! project
+ return if current_user.id == project.user_id
+ raise ApplicationController::VCError, "Project (#{project.id}) is not authorized for current user (#{current_user.id}); expected: '#{project.user_id}'."
+ end
+
+ def addGroupsLeafsConjoin(project, allGroups, folioNumber, pageNumber, startingTexture)
+ groupIDs = []
+ allGroups.each do |groupInfo|
+ group = Group.new({ project_id: project, title: "Default", type: "Quire" })
+
+ # 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|
+ leaf.update_attribute(:folio_number, folioNumber.to_s)
+ 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)
+ begin
+ images = []
+ response = JSON.parse(Net::HTTP.get(URI(url)))
+ iiif_version = response["@context"]
+ if iiif_version.include? "2"
+ response["sequences"][0]["canvases"].each do |canvas|
+ images.push({ label: canvas["label"],
+ url: canvas["images"][0]["resource"]["service"]["@id"] })
+ end
+ return { name: response["label"][0..150],
+ images: images } unless response["label"].empty?
+ return { name: "Unnamed manifest", images: images }
+ elsif iiif_version.include? "3"
+ response["items"].each do |item|
+ images.push({ label: item["label"].values.first[0],
+ url: item["items"][0]["items"][0]["body"]["id"] })
+ end
+ unless response["label"].values.first[0].empty?
+ return { name: response["label"].values.first[0][0..150], images: images }
+ end
+ return { name: "Unnamed manifest", images: images }
+ else
+ raise "IIIF Version: #{iiif_version}"
+ end
+ rescue Exception => e
+ Honeybadger.notify e
+ return { name: "Unparseable manifest URL", images: images }
+ end
+ 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 = {}
+ @terms = {}
+
+ @projectInformation = {
+ "id": @project.id.to_s,
+ "title": @project.title,
+ "shelfmark": @project.shelfmark,
+ "notationStyle": @project.notationStyle,
+ "metadata": @project.metadata,
+ "preferences": @project.preferences,
+ "manifests": @project.manifests,
+ "taxonomies": @project.taxonomies
+ }
+ @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,
+ "title": group.title,
+ "tacketed": group.tacketed,
+ "sewing": group.sewing,
+ "nestLevel": group.nestLevel,
+ "parentID": group.parentID,
+ "terms": [],
+ "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,
+ "folio_number": leaf.folio_number,
+ "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,
+ "terms": [],
+ "memberType": "Leaf",
+ }
+ end
+
+
+ @project.sides.each do |side|
+ parentOrder = @leafIDs.index(side.parentID) + 1
+ obj = {
+ "id": side.id.to_s,
+ "parentID": side.parentID,
+ "page_number": side.page_number,
+ "texture": side.texture,
+ "image": side.image,
+ "script_direction": side.script_direction,
+ "terms": [],
+ "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.terms.each do |term|
+ @terms[term.id.to_s] = {
+ "id": term.id.to_s,
+ "title": term.title,
+ "taxonomy": term.taxonomy,
+ "description": term.description,
+ "uri": term.uri,
+ "show": term.show,
+ "objects": term.objects,
+ }
+ term.objects["Group"].each do |id|
+ @groups[id][:terms].append(term.id.to_s) unless @groups[id].nil?
+ end
+ term.objects["Leaf"].each do |id|
+ @leafs[id][:terms].append(term.id.to_s) unless @leafs[id].nil?
+ end
+ term.objects["Recto"].each do |id|
+ @rectos[id][:terms].append(term.id.to_s) unless @rectos[id].nil?
+ end
+ term.objects["Verso"].each do |id|
+ @versos[id][:terms].append(term.id.to_s) unless @versos[id].nil?
+ end
+ end
+
+ return {
+ "project": @projectInformation,
+ "groupIDs": @groupIDs,
+ "leafIDs": @leafIDs,
+ "rectoIDs": @rectoIDs,
+ "versoIDs": @versoIDs,
+ "groups": @groups,
+ "leafs": @leafs,
+ "rectos": @rectos,
+ "versos": @versos,
+ "terms": @terms,
+ }
+ 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..7ae5623e
--- /dev/null
+++ b/viscoll-api/app/mailers/account_approval_mailer.rb
@@ -0,0 +1,12 @@
+class AccountApprovalMailer < ApplicationMailer
+ default from: RailsJwtAuth.mailer_sender
+
+ def sendApprovalStatus(user)
+ @user = User.find(user)
+ mail(
+ subject: "VCEditor Account Approval",
+ to: @user.email,
+ template_name: 'sendApprovalStatus'
+ )
+ end
+end
\ No newline at end of file
diff --git a/viscoll-api/app/mailers/application_mailer.rb b/viscoll-api/app/mailers/application_mailer.rb
new file mode 100644
index 00000000..b4524c34
--- /dev/null
+++ b/viscoll-api/app/mailers/application_mailer.rb
@@ -0,0 +1,5 @@
+class ApplicationMailer < ActionMailer::Base
+ # TODO
+ default from: Rails.application.secrets.mailer_default_from
+ 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..eb728732
--- /dev/null
+++ b/viscoll-api/app/mailers/feedback_mailer.rb
@@ -0,0 +1,14 @@
+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: Rails.application.secrets.admin_email,
+ template_name: 'sendFeedback'
+ )
+ end
+end
diff --git a/viscoll-api/app/mailers/mailer.rb b/viscoll-api/app/mailers/mailer.rb
new file mode 100644
index 00000000..03a7b513
--- /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
+ 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/viscoll-api/app/mailers/test_mailer.rb b/viscoll-api/app/mailers/test_mailer.rb
new file mode 100644
index 00000000..1385439e
--- /dev/null
+++ b/viscoll-api/app/mailers/test_mailer.rb
@@ -0,0 +1,7 @@
+class TestMailer < ApplicationMailer
+ default from: 'emeryr@upenn.edu'
+
+ def test_email
+ mail to: 'emeryr@upenn.edu', subject: 'Test of the mailer', body: 'The body of the email'
+ end
+end
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..8fcdb0e4
--- /dev/null
+++ b/viscoll-api/app/models/group.rb
@@ -0,0 +1,139 @@
+class Group
+ include Mongoid::Document
+ include Mongoid::Timestamps
+
+ # Fields
+ field :title, type: String, default: "None"
+ field :type, type: String, default: "Quire"
+ 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 :terms, inverse_of: nil
+
+ # Callbacks
+ before_create :edit_ID
+ before_destroy :unlink_terms, :unlink_project, :unlink_group, :destroy_members
+ before_save :check_member_ids
+
+ def mapping?
+ # if any terms are attached to group, mappings exist
+ return true if terms.present?
+ memberIDs.any? do |memberID|
+ if memberID[0] == 'G'
+ member = Group.find(memberID)
+ elsif memberID[0] == 'L'
+ member = Leaf.find(memberID)
+ end
+ member.mapping?
+ end
+ end
+
+ def mappings
+ mappings_array = []
+ self.memberIDs.each do |memberID|
+ if memberID[0] == "L"
+ member = Leaf.find(memberID)
+ mappings_array += member.mappings if member.mapping?
+ end
+ end
+ terms.each do |term|
+ mappings_array.push({term.id => self.id})
+ end
+ mappings_array
+ end
+
+ # code here must mirror groupNotation function in PaperManager.js:44
+ def group_notation
+ outer_groups = project.groups.where(nestLevel: 1).to_a
+ outer_groupIDs = outer_groups.map(&:id)
+ if self.nestLevel == 1
+ group_order = outer_groupIDs.index(self.id) + 1 # index of this group (self.id) in context of outer_groups + 1
+ notation = group_order.to_s
+ else
+ parent_group = Group.find(self.parentID)
+ parent_group_children = parent_group.memberIDs.select { |g| g.start_with? 'G' }
+ subquire_notation = parent_group_children.index(self.id) + 1 # index of this group in context of all children of this group's parent
+ notation = "#{parent_group.group_notation}.#{subquire_notation}"
+ end
+ notation
+ end
+
+ 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 term(s), remove link from the term(s)'s side
+ def unlink_terms
+ if self.terms
+ self.terms.each do |term|
+ term.objects[:Group].delete(self.id.to_s)
+ term.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
+
+ def all_leafIDs_in_order
+ return @child_leafs if @child_leafs.present?
+ @child_leafs = []
+ memberIDs.each do |memberID|
+ if memberID[0] === "G"
+ @child_leafs += Group.find(memberID).all_leafIDs_in_order
+ elsif memberID[0] === "L"
+ @child_leafs << memberID
+ end
+ end
+ @child_leafs
+ end
+
+ def check_member_ids
+ return if memberIDs.all?
+
+ Rails.logger.error("Group #{id} tried to save with nil values." + "\n" + self.to_json)
+ raise StandardError, "Group #{id} tried to save with nil values." + "\n" + self.to_json
+ 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..ff7c60a1
--- /dev/null
+++ b/viscoll-api/app/models/leaf.rb
@@ -0,0 +1,130 @@
+class Leaf
+ include Mongoid::Document
+ include Mongoid::Timestamps
+
+ # Fields
+ field :folio_number, type: String, default: nil
+ field :material, type: String, default: "None"
+ field :type, type: String, default: "Original"
+ 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: "No"
+ 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 :terms, inverse_of: nil
+
+ # Callbacks
+ before_create :edit_ID, :create_sides
+ before_destroy :unlink_terms, :destroy_sides, :update_parent_group
+ before_save :handle_empty_folio_number
+
+ def mapping?
+ # if terms are attached to leaf, mappings exist
+ return true if terms.present?
+ # check sides for mappings
+ recto = Side.find(self.rectoID)
+ verso = Side.find(self.versoID)
+ [ recto, verso ].compact.any? { |side| side.mapping? }
+ end
+
+ def mappings
+ mappings_array = []
+ recto = Side.find(self.rectoID)
+ verso = Side.find(self.versoID)
+ mappings_array += recto.mappings if recto.mapping?
+ mappings_array += verso.mappings if verso.mapping?
+ terms.each do |term|
+ mappings_array.push({term.id => self.id})
+ end
+ mappings_array
+ end
+
+ def parent_project
+ group = Group.find(self.parentID)
+ Project.find(group.parentID)
+ end
+
+ # Remove itself from its parent group
+ def remove_from_group
+ Group.find(self.parentID).remove_members([self.id.to_s])
+ end
+
+ def top_level_group
+ parent = Group.find(self.parentID)
+ nest_level = parent.nestLevel
+ while nest_level > 1
+ parent = Group.find(parent.parentID)
+ nest_level = parent.nestLevel
+ end
+ parent
+ end
+
+ def position_in_top_level_group
+ self.top_level_group.all_leafIDs_in_order.index(self.id) + 1
+ end
+
+ protected
+ def edit_ID
+ self.id = "Leaf_"+self.id.to_s unless self.id.to_s[0]=="L"
+ end
+
+ # If linked to term(s), remove link from the term(s)'s side
+ def unlink_terms
+ self.terms.each do | term |
+ term.objects[:Leaf].delete(self.id.to_s)
+ term.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
+
+ def handle_empty_folio_number
+ self.folio_number = nil if self.folio_number.to_s.strip.empty?
+ end
+end
+
diff --git a/viscoll-api/app/models/project.rb b/viscoll-api/app/models/project.rb
new file mode 100644
index 00000000..b546dba0
--- /dev/null
+++ b/viscoll-api/app/models/project.rb
@@ -0,0 +1,91 @@
+class Project
+ include Mongoid::Document
+ include Mongoid::Timestamps
+
+ # Fields
+ field :title, type: String
+ field :shelfmark, type: String # (eg) "MS 1754"
+ field :notationStyle, type: String, default: "r-v" # (eg) "r-v"
+ field :metadata, type: Hash, default: lambda { { } } # (eg) {date: "19th century"}
+ field :manifests, type: Hash, default: lambda { { } } # (eg) { "1234556": { id: "123456, url: ""} }
+ field :taxonomies, type: Array, default: ["Unknown"] # custom taxonomies
+ 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 :terms, 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
+
+ # do any groups have mappings?
+ def mapping?
+ groups.any? { |group| group.mapping? }
+ end
+
+ def text_direction
+ 'l-r'
+ end
+
+ def recto_side
+ if text_direction == 'l-r'
+ 'left'
+ else
+ 'right'
+ end
+ end
+
+ def verso_side
+ if text_direction == 'l-r'
+ 'right'
+ else
+ 'left'
+ end
+ end
+
+ def mappings
+ mappings_array = []
+ self.groups.each do |group|
+ mappings_array += group.mappings if group.mapping?
+ end
+ mappings_array
+ end
+
+ 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..660e1a82
--- /dev/null
+++ b/viscoll-api/app/models/side.rb
@@ -0,0 +1,68 @@
+class Side
+ include Mongoid::Document
+ include Mongoid::Timestamps
+
+ # Fields
+ 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
+ field :side, type: String # either 'r' or 'v'
+
+ # Relations
+ belongs_to :project
+ has_and_belongs_to_many :terms, inverse_of: nil
+
+ # Callbacks
+ before_destroy :unlink_terms, :unlink_image
+ before_save :handle_empty_page_number
+
+ def parent_leaf
+ Leaf.find(parentID)
+ end
+
+ # if any terms are attached, mappings exist
+ def mapping?
+ return true if terms.present?
+ texture != 'None'
+ end
+
+ def mappings
+ mappings_array = []
+ mappings_array.push({self.texture => self.id.to_s}) if self.texture != 'None'
+ terms.each do |term|
+ mappings_array.push({term.id => self.id})
+ end
+ mappings_array
+ end
+
+ def image_url
+ return image["url"] if image["manifestID"].include? "DIY"
+ image["url"] + '/full/,1000/0/default.jpg'
+ end
+
+ protected
+ # If linked to term(s), remove link from the term(s)'s side
+ def unlink_terms
+ self.terms.each do | term |
+ term.objects[:Recto].delete(self.id.to_s)
+ term.objects[:Verso].delete(self.id.to_s)
+ term.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
+
+ def handle_empty_page_number
+ self.page_number = nil if self.page_number.to_s.strip.empty?
+ end
+end
diff --git a/viscoll-api/app/models/term.rb b/viscoll-api/app/models/term.rb
new file mode 100644
index 00000000..219250f9
--- /dev/null
+++ b/viscoll-api/app/models/term.rb
@@ -0,0 +1,55 @@
+class Term
+ include Mongoid::Document
+ include Mongoid::Timestamps
+
+ # Fields
+ field :title, type: String, default: "None"
+ field :taxonomy, type: String, default: ""
+ field :description, type: String, default: ""
+ field :uri, type: String, default: ""
+ field :objects, type: Hash, default: {Group: [], Leaf: [], Recto: [], Verso: []}
+ field :show, type: Boolean, default: false
+
+ # Relations
+ belongs_to :project, inverse_of: :terms
+
+ # 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 :taxonomy, :message => "Taxonomy is required."
+
+ # Callbacks
+ before_create :edit_ID
+ before_destroy :update_objects_before_delete
+
+ def edit_ID
+ self.id = "Term_" + self.id.to_s unless self.id.to_s[0] == "T"
+ end
+
+ def update_objects_before_delete
+ self.objects[:Group].each do |groupID|
+ if group = Group.where(:id => groupID).first
+ group.terms.delete(self)
+ group.save
+ end
+ end
+ self.objects[:Leaf].each do |leafID|
+ if leaf = Leaf.where(:id => leafID).first
+ leaf.terms.delete(self)
+ leaf.save
+ end
+ end
+ self.objects[:Recto].each do |sideID|
+ if side = Side.where(:id => sideID).first
+ side.terms.delete(self)
+ side.save
+ end
+ end
+ self.objects[:Verso].each do |sideID|
+ if side = Side.where(:id => sideID).first
+ side.terms.delete(self)
+ side.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..95acdbce
--- /dev/null
+++ b/viscoll-api/app/models/user.rb
@@ -0,0 +1,15 @@
+class User
+ include Mongoid::Document
+ include RailsJwtAuth::Authenticatable
+ include RailsJwtAuth::Confirmable
+ include RailsJwtAuth::Recoverable
+ include RailsJwtAuth::Trackable
+
+ field :name, type: String, default: ""
+
+ before_save { self.email = email.to_s.downcase }
+
+ 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..09501e79
--- /dev/null
+++ b/viscoll-api/app/views/account_approval_mailer/sendApprovalStatus.html.erb
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
Hi <%= @user.name %>,
+
Congratulations! Your request to join VCEditor has been successfully approved.
+
You can now log in with the credentials that you used to register.
+
Click here to sign up for occasional updates on VCEditor and the VisColl Project.
+
You can join the VCEditor Users’ community here .
+
+
\ 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..f57f3ff8
--- /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.Terms @data[:terms]
+end
+
+json.set! 'Images' do
+ if @zipFilePath
+ json.exportedImages @zipFilePath
+ else
+ json.exportedImages ""
+ end
+end
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..cdbad840
--- /dev/null
+++ b/viscoll-api/app/views/feedback_mailer/sendFeedback.html.erb
@@ -0,0 +1,18 @@
+
+
+
+
+
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..e1357fd3
--- /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.Terms @terms
+json.GroupsOfMatchingLeafs @groupsOfMatchingLeafs
+json.LeafsOfMatchingSides @leafsOfMatchingSides
+json.GroupsOfMatchingSides @groupsOfMatchingSides
+json.groupsOfMatchingTerms @groupsOfMatchingTerms
+json.LeafsOfMatchingTerms @leafsOfMatchingTerms
+json.SidesOfMatchingTerms @sidesOfMatchingTerms
+json.visibleAttributes @visibleAttributes
diff --git a/viscoll-api/app/views/layouts/application.html.erb b/viscoll-api/app/views/layouts/application.html.erb
new file mode 100644
index 00000000..83a9e1ae
--- /dev/null
+++ b/viscoll-api/app/views/layouts/application.html.erb
@@ -0,0 +1,16 @@
+
+
+
+ Layouttest
+ <%= csrf_meta_tags %>
+ <%= csp_meta_tag %>
+
+ <%#= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
+ <%#= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
+ <%#= javascript_include_tag 'main' %>
+
+
+
+<%= yield %>
+
+
\ No newline at end of file
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..50a4e4d9
--- /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, :notationStyle, :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..27e5e2a7
--- /dev/null
+++ b/viscoll-api/app/views/projects/show.json.jbuilder
@@ -0,0 +1,45 @@
+json.set! "active" do
+ json.id @data[:project][:id]
+ json.title @data[:project][:title]
+ json.notationStyle @data[:project][:notationStyle]
+ json.shelfmark @data[:project][:shelfmark]
+ json.metadata @data[:project][:metadata]
+ json.preferences @data[:project][:preferences]
+ json.Taxonomies @data[:project][:taxonomies]
+
+ 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.Terms @data[:terms]
+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
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..82f6261e
--- /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 VCEditor. 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..dc659bfd
--- /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..90d36042
--- /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/viscoll-api/config/application.rb b/viscoll-api/config/application.rb
new file mode 100644
index 00000000..939f4a3b
--- /dev/null
+++ b/viscoll-api/config/application.rb
@@ -0,0 +1,60 @@
+require_relative 'boot'
+require_relative 'shrine'
+
+require "rails"
+# Pick the frameworks you want:
+require "active_model/railtie"
+require "active_job/railtie"
+# require "active_record/railtie"
+require "action_controller/railtie"
+require "action_mailer/railtie"
+require "action_view/railtie"
+require "action_cable/engine"
+# 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 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
+
+ config.action_mailer.smtp_settings = {
+ :user_name => ENV['MAILER_USR'],
+ :password => ENV['MAILER_PWD'],
+ :from => ENV['MAILER_DEFAULT_FROM'],
+ :domain => ENV['MAILER_DOMAIN'],
+ :address => ENV['MAILER_HOST'],
+ :port => ENV['MAILER_PORT'] || 587,
+ :authentication => :plain,
+ :enable_starttls_auto => true
+ }
+ config.action_mailer.default_url_options = { :host => ENV['APPLICATION_HOST'] }
+
+ # load configuration information for xproc service
+ config.xproc = config_for :xproc
+ 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 62%
rename from config/environments/development.rb
rename to viscoll-api/config/environments/development.rb
index 58d20de6..db402fa8 100644
--- a/config/environments/development.rb
+++ b/viscoll-api/config/environments/development.rb
@@ -30,17 +30,23 @@
config.action_mailer.raise_delivery_errors = false
config.action_mailer.perform_caching = false
-
+ config.action_mailer.delivery_method = :file
+ config.action_mailer.default_url_options = { :host => "localhost", :port => 3000 }
+ # config.action_mailer.smtp_settings = {
+ # :address => 'smtp.ethereal.email',
+ # :port => 587,
+ # :user_name => 'brady.wiegand@ethereal.email',
+ # :password => '2DzDyRf6Q22n21FV2q'
+ # }
+ config.action_mailer.smtp_settings = {
+ :address => ENV['MAILER_HOST'],
+ :port => ENV['MAILER_PORT'],
+ :user_name => ENV['MAILER_USR'],
+ :password => ENV['MAILER_PWD']
+ }
# 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 +54,16 @@
# 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
+
+ logger = ActiveSupport::Logger.new(STDOUT)
+ logger.formatter = config.log_formatter
+ config.logger = ActiveSupport::TaggedLogging.new(logger)
+ config.log_level = :debug
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..a63d8ea5 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.
@@ -80,4 +78,4 @@
logger.formatter = config.log_formatter
config.logger = ActiveSupport::TaggedLogging.new(logger)
end
-end
+end
\ No newline at end of file
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/viscoll-api/config/initializers/honeybadger.rb b/viscoll-api/config/initializers/honeybadger.rb
new file mode 100644
index 00000000..287c3fd6
--- /dev/null
+++ b/viscoll-api/config/initializers/honeybadger.rb
@@ -0,0 +1,7 @@
+# Adding Honeybadger configuration.
+
+if Rails.env.production? && ENV['HONEYBADGER_API_KEY']
+ Honeybadger.configure do |config|
+ config.api_key = ENV['HONEYBADGER_API_KEY']
+ end
+end
\ No newline at end of file
diff --git a/config/initializers/inflections.rb b/viscoll-api/config/initializers/inflections.rb
similarity index 87%
rename from config/initializers/inflections.rb
rename to viscoll-api/config/initializers/inflections.rb
index ac033bf9..bad922c8 100644
--- a/config/initializers/inflections.rb
+++ b/viscoll-api/config/initializers/inflections.rb
@@ -14,3 +14,6 @@
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.acronym 'RESTful'
# end
+ActiveSupport::Inflector.inflections(:en) do |inflect|
+ inflect.uncountable 'welcome'
+end
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..7cdf8d85
--- /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 = 'emeryr@upenn.edu'
+
+ # url used to create email link with confirmation token
+ config.confirmation_url = "https://#{ENV['APPLICATION_HOST']}/confirmation"
+
+ # 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 = "https://#{ENV['APPLICATION_HOST']}/password"
+
+
+ # 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 52%
rename from config/mongoid.yml
rename to viscoll-api/config/mongoid.yml
index b4f74dd1..fbc5d8d4 100644
--- a/config/mongoid.yml
+++ b/viscoll-api/config/mongoid.yml
@@ -5,16 +5,15 @@ 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:
# w: 1
-
# Change the default read preference. Valid options for mode are: :secondary,
# :secondary_preferred, :primary, :primary_preferred, :nearest
# (default: primary)
@@ -22,126 +21,182 @@ development:
# mode: :secondary_preferred
# tag_sets:
# - use: web
-
# The name of the user for authentication.
# user: 'user'
-
# The password of the user for authentication.
# password: 'password'
-
# The user's database roles.
# roles:
# - '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.
# (default: the database specified above or admin)
# auth_source: admin
-
# Force a the driver cluster to behave in a certain manner instead of auto-
# discovering. Can be one of: :direct, :replica_set, :sharded. Set to :direct
# when connecting to hidden members of a replica set.
# connect: :direct
-
# Changes the default time in seconds the server monitors refresh their status
# via ismaster commands. (default: 10)
# heartbeat_frequency: 10
-
# The time in seconds for selecting servers for a near read preference. (default: 0.015)
# local_threshold: 0.015
-
# The timeout in seconds for selecting a server for an operation. (default: 30)
# server_selection_timeout: 30
-
# The maximum number of connections in the connection pool. (default: 5)
# max_pool_size: 5
-
# The minimum number of connections in the connection pool. (default: 1)
# min_pool_size: 1
-
# The time to wait, in seconds, in the connection pool for a connection
# to be checked in before timing out. (default: 5)
# wait_queue_timeout: 5
-
# The time to wait to establish a connection before timing out, in seconds.
# (default: 5)
# connect_timeout: 5
-
# The timeout to wait to execute operations on a socket before raising an error.
# (default: 5)
# socket_timeout: 5
-
# The name of the replica set to connect to. Servers provided as seeds that do
# not belong to this replica set will be ignored.
# replica_set: name
-
# Whether to connect to the servers via ssl. (default: false)
# ssl: true
-
# The certificate file used to identify the connection against MongoDB.
# ssl_cert: /path/to/my.cert
-
# The private keyfile used to identify the connection against MongoDB.
# Note that even if the key is stored in the same file as the certificate,
# both need to be explicitly specified.
# ssl_key: /path/to/my.key
-
# A passphrase for the private key.
# ssl_key_pass_phrase: password
-
# Whether or not to do peer certification validation. (default: true)
# ssl_verify: true
-
# The file containing a set of concatenated certification authority certifications
# used to validate certs passed from the other end of the connection.
# ssl_ca_cert: /path/to/ca.cert
-
# Configure Mongoid specific options. (optional)
options:
# Includes the root model name in json serialization. (default: false)
# include_root_in_json: false
-
# Include the _type field in serialization. (default: false)
# include_type_for_serialization: false
-
# Preload all models in development, needed when models use
# inheritance. (default: false)
# preload_models: false
-
# Raise an error when performing a #find and the document is not found.
# (default: true)
# raise_not_found_error: true
-
# Raise an error when defining a scope with the same name as an
# 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
-
# Ensure all times are UTC in the app side. (default: false)
# use_utc: false
-
# Set the Mongoid and Ruby driver log levels when not in a Rails
# environment. The Mongoid logger will be set to the Rails logger
# 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://mongo:27017/viscoll_test
options:
read:
mode: :primary
max_pool_size: 1
+production:
+ # Configure available database clients. (required)
+ clients:
+ # Defines the default client. (required)
+ default:
+ # Defines the name of the default database that Mongoid can connect to.
+ # (required).
+ database: viscoll_api
+ # Provides the hosts the default client can connect to. Must be an array
+ # of host:port pairs. (required)
+ hosts:
+ - mongo:27017
+ options:
+ # Change the default write concern. (default = { w: 1 })
+ # write:
+ # w: 1
+ # Change the default read preference. Valid options for mode are: :secondary,
+ # :secondary_preferred, :primary, :primary_preferred, :nearest
+ # (default: primary)
+ # read:
+ # mode: :secondary_preferred
+ # tag_sets:
+ # - use: web
+ # The name of the user for authentication.
+ # user: 'user'
+ # The password of the user for authentication.
+ # password: 'password'
+ # The user's database roles.
+ # roles:
+ # - 'dbOwner'
+ # Change the default authentication mechanism. Valid options are: :scram,
+ # :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.
+ # (default: the database specified above or admin)
+ # auth_source: admin
+ # Force a the driver cluster to behave in a certain manner instead of auto-
+ # discovering. Can be one of: :direct, :replica_set, :sharded. Set to :direct
+ # when connecting to hidden members of a replica set.
+ # connect: :direct
+ # Changes the default time in seconds the server monitors refresh their status
+ # via ismaster commands. (default: 10)
+ # heartbeat_frequency: 10
+ # The time in seconds for selecting servers for a near read preference. (default: 0.015)
+ # local_threshold: 0.015
+ # The timeout in seconds for selecting a server for an operation. (default: 30)
+ # server_selection_timeout: 30
+ # The maximum number of connections in the connection pool. (default: 5)
+ # max_pool_size: 5
+ # The minimum number of connections in the connection pool. (default: 1)
+ # min_pool_size: 1
+ # The time to wait, in seconds, in the connection pool for a connection
+ # to be checked in before timing out. (default: 5)
+ # wait_queue_timeout: 5
+ # The time to wait to establish a connection before timing out, in seconds.
+ # (default: 5)
+ # connect_timeout: 5
+ # The timeout to wait to execute operations on a socket before raising an error.
+ # (default: 5)
+ # socket_timeout: 5
+ # The name of the replica set to connect to. Servers provided as seeds that do
+ # not belong to this replica set will be ignored.
+ # replica_set: name
+ # Whether to connect to the servers via ssl. (default: false)
+ # ssl: true
+ # The certificate file used to identify the connection against MongoDB.
+ # ssl_cert: /path/to/my.cert
+ # The private keyfile used to identify the connection against MongoDB.
+ # Note that even if the key is stored in the same file as the certificate,
+ # both need to be explicitly specified.
+ # ssl_key: /path/to/my.key
+ # A passphrase for the private key.
+ # ssl_key_pass_phrase: password
+ # Whether or not to do peer certification validation. (default: true)
+ # ssl_verify: true
+ # The file containing a set of concatenated certification authority certifications
+ # used to validate certs passed from the other end of the connection.
+ # ssl_ca_cert: /path/to/ca.cert
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..20caf61e
--- /dev/null
+++ b/viscoll-api/config/routes.rb
@@ -0,0 +1,81 @@
+Rails.application.routes.draw do
+
+ get 'welcome/index'
+ # 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]
+
+ # XPROC endpoints
+ get '/transformations/zip/:job_id', to: 'xproc#get_zip', defaults: { format: :json }
+
+ # 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/generateFolio', to: 'leafs#generateFolio', defaults: {format: :json}, only: [:update]
+ 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/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]
+
+ # TERM ENDPOINTS
+ put '/terms/:id/link', to: 'terms#link', defaults: {format: :json}, only: [:update]
+ put '/terms/:id/unlink', to: 'terms#unlink', defaults: {format: :json}, only: [:update]
+ post '/terms/taxonomy', to: 'terms#createTaxonomy', defaults: {format: :json}, only: [:create]
+ put '/terms/taxonomy', to: 'terms#updateTaxonomy', defaults: {format: :json}, only: [:update]
+ delete '/terms/taxonomy', to: 'terms#deleteTaxonomy', defaults: {format: :json}, only: [:destroy]
+ resources :terms, defaults: {format: :json}, only: [:show, :update, :destroy, :create]
+
+ # DOCUMENTATION
+ get '/docs' => redirect('/docs/index.html')
+
+ # Add a healthcheck route for api online/offline polling
+ get '/status', to: proc { [200, {}, ['']] }
+
+ get '/instance', to: 'instance#getInstance'
+
+ ##
+ # We need to route other calls to static index so that in production/staging
+ # Rails will send the '/index.html' generated by npm build.
+ # This isn't ideal, but attempts to render the HTML by mapping '/', simply
+ # haven't work -- rails keeps trying to interpret 'public/index.html' as route.
+ # This method is used in combination with RAILS_SERVE_STATIC_FILES so that
+ # browsers can request the React/etc. JS files in 'public/static/js'
+ # TODO: look into a reverse proxy to serve static files (nginx?)
+ get '*other', to: 'static#index'
+
+end
diff --git a/config/secrets.yml b/viscoll-api/config/secrets.yml
similarity index 50%
rename from config/secrets.yml
rename to viscoll-api/config/secrets.yml
index 34116365..a2ecca68 100644
--- a/config/secrets.yml
+++ b/viscoll-api/config/secrets.yml
@@ -11,12 +11,20 @@
# if you're sharing your code publicly.
development:
- secret_key_base: fe3e18f8b5a59698d997c8e5dd20303ed84a86ed28f4a94469adcebf8e2c4905e635761aa743ce87d4b88275ad44c696ee83cc2cd8859f0cdc393c494caf092c
+ secret_key_base: 98eb3bcbe406c141ad93c58e5f5ff08ab7348c82d688b78ee1fb1a30559d7104081e0dd9bf97c8e080a54f1d408f7f9d22710439c44cbbddc332861994b1c531
+# admin_email: 'smtp://localhost:1025'
+ mailer_default_from: 'test@vceditor.library.upenn.edu'
+ api_url: 'http://localhost:3001'
test:
- secret_key_base: 3cf10572eca25c5e5785dd56e638a47d51523510d298d839023940487dc82982fc798add37eeb1b1abee64d4c159deecdb7f30e0bc1b812d2af264bb94b1d582
+ secret_key_base: a8986cd44e89b6547fe0c8ebd320706dc2dbd6aa617e42c5625b9420fa8ab8347beeedb0690138e178e79583b0f7c71b45fac99409d96653030c6a94c14d9d9d
+ admin_email: 'test@test.com'
+ mailer_default_from: 'test@vceditor.library.upenn.edu'
+ api_url: 'https://vceditor.library.upenn.edu'
# Do not keep production secrets in the repository,
# instead read values from the environment.
production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
+ mailer_default_from: <%= ENV['MAILER_DEFAULT_FROM'] %>
+ admin_email: <%= ENV["ADMIN_EMAIL"] %>
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/viscoll-api/config/xproc.yml b/viscoll-api/config/xproc.yml
new file mode 100644
index 00000000..0501cd0f
--- /dev/null
+++ b/viscoll-api/config/xproc.yml
@@ -0,0 +1,4 @@
+development:
+ url: http://xproc:2000
+production:
+ url: <%= ENV['XPROC_URL'] %>
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/viscoll-api/lib/tasks/vceditor.rake b/viscoll-api/lib/tasks/vceditor.rake
new file mode 100644
index 00000000..05df43f0
--- /dev/null
+++ b/viscoll-api/lib/tasks/vceditor.rake
@@ -0,0 +1,19 @@
+namespace :vce do
+ desc "Update Leaf#stubType to Yes|No values"
+ task :update_stubs => :environment do
+ Leaf.all.each do |leaf|
+ next if %w{No Yes}.include? leaf.stubType
+ case leaf.stubType
+ when 'None'
+ leaf.stubType = 'No'
+ when 'Added'
+ leaf.stubType = 'Yes'
+ leaf.type = 'Added'
+ when 'Original'
+ leaf.stubType = 'Yes'
+ leaf.type = 'Original'
+ end
+ leaf.save!
+ end
+ end
+end
\ No newline at end of file
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..ee0150e4
--- /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: Terms
+ description: Operations on Term 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}/terms:
+ get:
+ tags:
+ - Projects
+ summary: gets all terms in a project
+ operationId: getTerms
+ description: |
+ By passing in the appropriate options, you can view all terms 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 terms for the project
+ schema:
+ $ref: '#/definitions/TermsFullResponse'
+ 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
+
+ /terms:
+ post:
+ tags:
+ - Terms
+ summary: creates a new term
+ operationId: createTerm
+ description: |
+ By passing in the appropriate options, you can create a new term
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: term
+ required: true
+ description: term information
+ schema:
+ $ref: '#/definitions/TermCreateParams'
+ responses:
+ 200:
+ description: successfully created the term
+ schema:
+ $ref: '#/definitions/TermsFullResponse'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/TermCreateError'
+ 401:
+ description: Unauthorized Action
+
+
+
+ /terms/{termID}:
+ put:
+ tags:
+ - Terms
+ summary: updates a terms
+ operationId: updateTerm
+ description: |
+ By passing in the appropriate options, you can update a term
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: termID
+ type: string
+ required: true
+ description: ID of the term
+ - in: body
+ name: term
+ required: true
+ description: term information
+ schema:
+ $ref: '#/definitions/TermUpdateParams'
+ responses:
+ 200:
+ description: successfully updated the leaf
+ schema:
+ $ref: '#/definitions/TermsFullResponse'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/TermCreateError'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: term with termID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: term not found
+ delete:
+ tags:
+ - Terms
+ summary: deletes a term
+ operationId: deleteTerm
+ description: |
+ By passing in the appropriate options, you can delete a term
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: termID
+ type: string
+ required: true
+ description: ID of the term
+ responses:
+ 204:
+ description: successfully deleted the term
+ schema:
+ $ref: '#/definitions/termsFullResponse'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: term with termID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: term not found
+ /terms/{termID}/link:
+ put:
+ tags:
+ - Terms
+ summary: links a term to the given objects
+ operationId: linkTerm
+ description: |
+ By passing in the appropriate options, you can link a term to objects
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: termID
+ type: string
+ required: true
+ description: ID of the term
+ - in: body
+ name: term
+ required: true
+ description: term information
+ schema:
+ $ref: '#/definitions/TermLinkParams'
+ responses:
+ 200:
+ description: successfully linked the term
+ schema:
+ $ref: '#/definitions/TermsFullResponse'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/TermLinkError'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: term with termID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: term not found
+ /terms/{termID}/unlink:
+ put:
+ tags:
+ - Terms
+ summary: unlinks a term from the given objects
+ operationId: unlinkTerm
+ description: |
+ By passing in the appropriate options, you can unlink a term from objects
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: path
+ name: termID
+ type: string
+ required: true
+ description: ID of the term
+ - in: body
+ name: term
+ required: true
+ description: term information
+ schema:
+ $ref: '#/definitions/TermLinkParams'
+ responses:
+ 200:
+ description: successfully unlinked the term
+ schema:
+ $ref: '#/definitions/TermsFullResponse'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/TermLinkError'
+ 401:
+ description: Unauthorized Action
+ 404:
+ description: term with termID not found
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: term not found
+ /terms/type:
+ post:
+ tags:
+ - Terms
+ summary: creates a term type
+ operationId: createTaxonomy
+ description: |
+ By passing in the appropriate options, you can create a term type
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: taxonomy
+ required: true
+ description: term information
+ schema:
+ $ref: '#/definitions/TaxonomyCreateParams'
+ responses:
+ 200:
+ description: successfully created the taxonomy
+ schema:
+ $ref: '#/definitions/TaxonomyResponse'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/TaxonomyCreateError'
+ 401:
+ description: Unauthorized Action
+ put:
+ tags:
+ - Terms
+ summary: updates a taxonomy
+ operationId: updateTaxonomy
+ description: |
+ By passing in the appropriate options, you can update a taxonomy
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: taxonomy
+ required: true
+ description: term information
+ schema:
+ $ref: '#/definitions/TaxonomyUpdateParams'
+ responses:
+ 200:
+ description: successfully updated the taxonomy
+ schema:
+ $ref: '#/definitions/TaxonomyResponse'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/TaxonomyUpdateError'
+ 401:
+ description: Unauthorized Action
+ delete:
+ tags:
+ - Terms
+ summary: deletes a taxonomy
+ operationId: deleteTaxonomy
+ description: |
+ By passing in the appropriate options, you can delete a taxonomy
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: header
+ name: Authorization
+ type: string
+ required: true
+ description: authentication token
+ - in: body
+ name: taxonomy
+ required: true
+ description: term information
+ schema:
+ $ref: '#/definitions/TaxonomyCreateParams'
+ responses:
+ 200:
+ description: successfully deleted the taxonomy
+ schema:
+ $ref: '#/definitions/TaxonomyResponse'
+ 422:
+ description: bad input parameter
+ schema:
+ $ref: '#/definitions/TaxonomyDeleteError'
+ 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"
+ taxonomies:
+ type: array
+ items:
+ type: string
+ example: [Unknown, Ink, Hand]
+ terms:
+ $ref: '#/definitions/TermsFullResponse'
+ 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
+
+ TermCreateParams:
+ type: object
+ properties:
+ term:
+ type: object
+ properties:
+ project_id:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ title:
+ type: string
+ example: some title for term
+ taxonomy:
+ type: string
+ example: Ink
+ description:
+ type: string
+ example: blue ink
+
+ TermUpdateParams:
+ type: object
+ properties:
+ term:
+ type: object
+ properties:
+ title:
+ type: string
+ example: some title for term
+ taxonomy:
+ type: string
+ example: Ink
+ description:
+ type: string
+ example: blue ink
+
+ TermsFullResponse:
+ type: array
+ items:
+ $ref: '#definitions/TermFullResponse'
+
+
+ TermFullResponse:
+ type: object
+ properties:
+ id:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ title:
+ type: string
+ example: some title for term
+ 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
+
+ TermLinkParams:
+ type: object
+ properties:
+ objects:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ type:
+ type: string
+ example: Group
+
+ TermCreateError:
+ type: object
+ properties:
+ title:
+ type: array
+ example: [Term title should be uniue]
+ type:
+ type: array
+ example: [Taxonomy is required]
+
+ TermLinkError:
+ type: object
+ properties:
+ id:
+ type: string
+ example: Group object not found with id 5984d709c9bf3c1f76fd3fb
+ type:
+ type: string
+ example: object not found with type Groupssss
+
+ TermTypeCreateParams:
+ type: object
+ properties:
+ taxonomy:
+ type: object
+ properties:
+ project_id:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ taxonomy:
+ type: string
+ example: Ink
+
+ TermTypeUpdateParams:
+ type: object
+ properties:
+ taxonomy:
+ type: object
+ properties:
+ project_id:
+ type: string
+ example: 5951303fc9bf3c7b9a573a3f
+ taxonomy:
+ type: string
+ example: Ink
+ old_taxonomy:
+ type: string
+ example: Inkss
+
+ TermTypeCreateError:
+ type: object
+ properties:
+ project_id:
+ type: string
+ example: project object not found with id 5984d709c9bf3c1f76fd3fb
+ type:
+ type: string
+ example: already exists in the project
+
+
+ TaxonomyUpdateError:
+ 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
+
+
+ TaxonomyDeleteError:
+ 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
+
+ TaxonomyResponse:
+ type: object
+ properties:
+ taxonomies:
+ 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]
+ Terms:
+ 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 100755
index 00000000..fc1fd1a6
--- /dev/null
+++ b/viscoll-api/public/viscoll-datamodel2.0.rng
@@ -0,0 +1,643 @@
+
+
+
+
+
+
+
+ 2.1.0
+
+
+
+
+
+ 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 fol.*, agenda 4to*, 4to*, 8vo*, 12mo, long 12mo, 16mo*,
+ 18mo, 24mo, long 24mo, 32mo*, 48mo, 64mo*, 72mo, 96mo, 128mo
+ based on the DCRM(B) standard, the national standard for
+ cataloging rare books in library collections, generally adopted
+ by English-speaking countries and some non-English-speaking
+ countries, with the addition of "agenda quarto" (Needham, Paul.
+ 2017. “Format and Paper Size in Fifteenth-Century Printing.” In
+ Materielle Aspekte in Der Inkunabelforschung , edited
+ by Christoph Reske and Wolfgang Schmitz, 59–108. Wolfenbüttel
+ Writings on the History of Bookkeeping 49. Wiesbaden:
+ Harrassowitz Verlag in Kommission.). * indicates formats
+ considered by the Needham Calculator
+ (http://www.needhamcalculator.net/), see paper size below. In
+ case of mixed formats, e.g., F° and 4°, or other options being
+ used in one textblock, the information should be mapped directly
+ to the sheets instead.
+
+
+
+ fol.
+ agenda 4to
+ 4to
+ 8vo
+ 12mo
+ long 12mo
+ 16mo
+ 18mo
+ 24mo
+ long 24mo
+ 32mo
+ 48mo
+ 64mo
+ 72mo
+ 96mo
+ 128mo
+ mixed format
+ other
+
+
+
+
+
+
+
+
+
+
+
+ Refers to the category list in the "Table of Fifteenth-Century
+ Paper Flavors" for the Needham Calculator
+ (http://www.needhamcalculator.net/). For more information see
+ also: Needham, Paul. 2017. “Format and Paper Size in
+ Fifteenth-Century Printing.” In Materielle Aspekte in Der
+ Inkunabelforschung , edited by Christoph Reske and
+ Wolfgang Schmitz, 59–108. Wolfenbüttel Writings on the History
+ of Bookkeeping 49. Wiesbaden: Harrassowitz Verlag in Kommission.
+ In case of mixed sizes or other options being used in one
+ textblock, the information should be mapped directly to the
+ sheets instead.
+
+ Papal: 56 x 38.5 cm (half sheet)
+ Imperial: 48 x 34 cm
+ Super-Royal (Reale Bolognese): 45 x 30 cm
+ Royal (Reale, Regal): 42 x 30 cm
+ Super-Median (Mezzana Grande?): 37 x 25 cm
+ Median (Mezzana): 34.5/35 x 25 cm
+ Super-Chancery: 33 x 23 cm
+ Chancery (Reçute, Comune, Kanzleiformat): 31/31.5 x 23
+ cm
+ Half-Median: 25 x 17.5 cm
+
+
+
+
+ papal
+ imperial
+ super-royal
+ royal
+ super-median
+ median
+ super-chancery
+ chancery
+ half-median
+ mixed_sizes
+ other
+
+
+
+
+
+
+
+
+
+ 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
+ node 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). Stitching
+ goes through the margin instead of through the fold.
+ Stitching will have one @target identifying the exit
+ point. Tacketing works in a similar manner to sewing
+ through the fold.
+
+
+
+
+
+
+ sewn
+ pasted
+ tipped
+ drummed
+ stitched
+ tacketed
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ notes
+
+
+
+
+
+
+
+ mapping
+
+
+ map
+
+
+ The @side attribute permits the mapping of features on one
+ of the two surfaces of a sheet. The possible values are
+ 'left' and 'right'.
+
+ The place on the left of the centre of a book opened as
+ if to be read. All the components or features of a
+ binding on this side of the book can therefore be
+ described as left (e.g. left board, left endleaves,
+ etc.). This removes any confusion about what is the
+ front board on books written in arabic or latin, for
+ instance
+ (http://w3id.org/lob/concept/2947).
+ left
+ The place on the right of the centre of a book opened
+ as if to be read. All the components or features of a
+ binding on this side of the book can therefore be
+ described as right (e.g. right board, right endleaves,
+ etc.). This removes any confusion about what is the back
+ board on books written in arabic or latin, for instance
+ (http://w3id.org/lob/concept/3004).
+ right
+
+
+
+
+
+
+ The location attribute permits the mapping of features on
+ the surface of a page. The model provides a list of
+ possible values, but the user can add values too. If
+ more than one location is to be selected, the values
+ should be separated by a space, for this reason, spaces
+ are not allowed in the location values, use camelCasing,
+ _underscore, or similar instead.
+ Suggested values, as defined in the Language of Bindings
+ or the Getty's AAT, are:
+ head: the top of a bound book as placed
+ vertically on a shelf. http://w3id.org/lob/concept/3803
+ tail: the bottom of a bound book as placed
+ vertically on a shelf. http://w3id.org/lob/concept/3805
+ fore-edge: the edge of a codex-form book from
+ which it is opened, opposite the spine. http://w3id.org/lob/concept/3808
+ inner: the edge of a codex-form book closer to
+ the inside of the binding. http://w3id.org/lob/concept/3812
+ margins: the areas of a page between the
+ printed, written, or illustrative matter (text
+ area) and the edges of the leaf. The four margins
+ are usually called the 'head' (or top);
+ 'fore-edge' (or outer, outside, side); 'tail' (or
+ bottom foot); and 'inner' (or gutter). http://w3id.org/lob/concept/4579
+ gutters: the adjoining inner margins of two
+ facing pages, i.e. the margin at the sewn fold of
+ a section. http://w3id.org/lob/concept/4577
+ corner: the meeting-place of converging sides or
+ edges of a book, forming an angular extremity or
+ projection. On books, they can be defined
+ differently according to where they are found. The
+ sides of a binding therefore will have upper and
+ lower corners on either the inner (spine) or outer
+ (fore-edge) edges whereas the spines or fore-edges
+ of a book will have upper and lower corners at
+ either the left or right sides. http://w3id.org/lob/concept/4543
+ text_area: The area of a page containing the
+ printed, written, or illustrative matter, within
+ the four margins. http://w3id.org/lob/concept/4583
+ upper: situated or passing above the level,
+ surface, or base of measurement of something else.
+ http://vocab.getty.edu/page/aat/300010291
+ lower: situated or passing below the level,
+ surface, or base of measurement of something else.
+ http://vocab.getty.edu/page/aat/300010283
+ central: positioned at or near the middle of
+ something. http://vocab.getty.edu/page/aat/300010273
+ projecting: jutting or sticking out beyond the
+ general surface or adjacent parts. http://vocab.getty.edu/page/aat/300010286
+ parallel: extending in the same direction and
+ everywhere equidistant; usually said of lines and
+ linear objects and structures. http://vocab.getty.edu/page/aat/300010284
+ vertical: parallel relationships to a vertical
+ axis in designs for graphic works, objects, and
+ structures. http://vocab.getty.edu/page/aat/300056325
+ horizontal: parallel relationships to a
+ horizontal axis in designs for graphic works,
+ objects, and structures. http://vocab.getty.edu/page/aat/300065463
+ oblique: in form or position, a deviation from a
+ parallel state, or perpendicularity. http://vocab.getty.edu/page/aat/300265773
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ term
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ values for @certainty: 1 = very certain, 2 = fairly certain, 3 = not certain
+
+ 1
+ 2
+ 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ For
+ printed books, the alphanumerical code printed (or derived) for the
+ gathering.
+
+
+
+
+
+ id
+
+
+
+
+
+
+
+
+
+
+
+
+
+ certainty
+
+
+
+
+ id
+
+
+
+
+
+
diff --git a/lib/assets/.keep b/viscoll-api/public/xproc/.keep
similarity index 100%
rename from lib/assets/.keep
rename to viscoll-api/public/xproc/.keep
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..6543c369
--- /dev/null
+++ b/viscoll-api/spec/factories/leafs.rb
@@ -0,0 +1,11 @@
+include ActionDispatch::TestProcess
+FactoryGirl.define do
+ factory :leaf do
+ material "Paper"
+ type "Original"
+
+ factory :parchment do
+ material "Parchment"
+ end
+ 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/terms.rb b/viscoll-api/spec/factories/terms.rb
new file mode 100644
index 00000000..833e42ca
--- /dev/null
+++ b/viscoll-api/spec/factories/terms.rb
@@ -0,0 +1,34 @@
+FactoryGirl.define do
+ sequence :term_title do |n|
+ "Term #{n}"
+ end
+ sequence :term_text do |n|
+ "Blah #{n}"
+ end
+
+ factory :term do
+ transient do
+ attachments []
+ end
+ before(:build) do |term, 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('Terms can only be attached to groups, leafs and sides')
+ end
+ end
+ end
+ title { generate(:term_title) }
+ taxonomy "Unknown"
+ 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..d309c702
--- /dev/null
+++ b/viscoll-api/spec/fixtures/sample_import_json.json
@@ -0,0 +1,252 @@
+{
+ "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."
+ }
+ },
+ "taxonomies": ["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": {
+ "folio_number": "1",
+ "material": "Paper",
+ "type": "Original",
+ "attached_above": "None",
+ "attached_below": "None",
+ "stub": "No",
+ "nestLevel": 1
+ },
+ "conjoined_leaf_order": null,
+ "parentOrder": 1,
+ "rectoOrder": 1,
+ "versoOrder": 1
+ },
+ "2": {
+ "params": {
+ "folio_number": "2",
+ "material": "Paper",
+ "type": "Original",
+ "attached_above": "None",
+ "attached_below": "None",
+ "stub": "No",
+ "nestLevel": 1
+ },
+ "conjoined_leaf_order": null,
+ "parentOrder": 1,
+ "rectoOrder": 2,
+ "versoOrder": 2
+ },
+ "3": {
+ "params": {
+ "folio_number": "3",
+ "material": "Paper",
+ "type": "Original",
+ "attached_above": "None",
+ "attached_below": "None",
+ "stub": "No",
+ "nestLevel": 2
+ },
+ "conjoined_leaf_order": 4,
+ "parentOrder": 2,
+ "rectoOrder": 3,
+ "versoOrder": 3
+ },
+ "4": {
+ "params": {
+ "folio_number": "4",
+ "material": "Paper",
+ "type": "Original",
+ "attached_above": "None",
+ "attached_below": "None",
+ "stub": "No",
+ "nestLevel": 2
+ },
+ "conjoined_leaf_order": 3,
+ "parentOrder": 2,
+ "rectoOrder": 4,
+ "versoOrder": 4
+ },
+ "5": {
+ "params": {
+ "folio_number": "5",
+ "material": "Paper",
+ "type": "Original",
+ "attached_above": "None",
+ "attached_below": "None",
+ "stub": "No",
+ "nestLevel": 1
+ },
+ "conjoined_leaf_order": null,
+ "parentOrder": 1,
+ "rectoOrder": 5,
+ "versoOrder": 5
+ },
+ "6": {
+ "params": {
+ "folio_number": "6",
+ "material": "Paper",
+ "type": "Endleaf",
+ "attached_above": "None",
+ "attached_below": "None",
+ "stub": "No",
+ "nestLevel": 1
+ },
+ "conjoined_leaf_order": null,
+ "parentOrder": 1,
+ "rectoOrder": 6,
+ "versoOrder": 6
+ }
+ },
+ "Rectos": {
+ "1": {
+ "params": {
+ "texture": "Hair",
+ "image": {},
+ "script_direction": "None"
+ },
+ "parentOrder": 1
+ },
+ "2": {
+ "params": {
+ "texture": "Hair",
+ "image": {},
+ "script_direction": "None"
+ },
+ "parentOrder": 2
+ },
+ "3": {
+ "params": {
+ "texture": "Hair",
+ "image": {},
+ "script_direction": "None"
+ },
+ "parentOrder": 3
+ },
+ "4": {
+ "params": {
+ "texture": "Hair",
+ "image": {},
+ "script_direction": "None"
+ },
+ "parentOrder": 4
+ },
+ "5": {
+ "params": {
+ "texture": "Hair",
+ "image": {},
+ "script_direction": "None"
+ },
+ "parentOrder": 5
+ },
+ "6": {
+ "params": {
+ "texture": "Hair",
+ "image": {},
+ "script_direction": "None"
+ },
+ "parentOrder": 6
+ }
+ },
+ "Versos": {
+ "1": {
+ "params": {
+ "texture": "Flesh",
+ "image": {},
+ "script_direction": "None"
+ },
+ "parentOrder": 1
+ },
+ "2": {
+ "params": {
+ "texture": "Flesh",
+ "image": {},
+ "script_direction": "None"
+ },
+ "parentOrder": 2
+ },
+ "3": {
+ "params": {
+ "texture": "Flesh",
+ "image": {},
+ "script_direction": "None"
+ },
+ "parentOrder": 3
+ },
+ "4": {
+ "params": {
+ "texture": "Flesh",
+ "image": {},
+ "script_direction": "None"
+ },
+ "parentOrder": 4
+ },
+ "5": {
+ "params": {
+ "texture": "Flesh",
+ "image": {},
+ "script_direction": "None"
+ },
+ "parentOrder": 5
+ },
+ "6": {
+ "params": {
+ "texture": "Flesh",
+ "image": {},
+ "script_direction": "None"
+ },
+ "parentOrder": 6
+ }
+ },
+ "Terms": {
+ "1": {
+ "params": {
+ "title": "Test Term",
+ "taxonomy": "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..0c29e52c
--- /dev/null
+++ b/viscoll-api/spec/fixtures/sample_import_xml.xml
@@ -0,0 +1,146 @@
+
+
+
+
+ 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 Term
+
+
+ Whether to show Note in Visualizations
+ true
+
+
+ Sample project
+ Ravenna 384.2339
+ 18th century
+
+
+ 1
+ 2
+
+
+ 1
+
+
+
+
+
+ 2
+
+
+
+
+
+ 3
+
+
+
+
+
+
+ 4
+
+
+
+
+
+
+ 5
+
+
+
+
+
+ 6
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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..4ce260a4
--- /dev/null
+++ b/viscoll-api/spec/helpers/controller_helper/export_helper_spec.rb
@@ -0,0 +1,117 @@
+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',
+ 'notationStyle' => 'r-v',
+ 'metadata' => { date: '18th century' },
+ 'preferences' => { 'showTips' => true },
+ 'taxonomies' => ['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')
+ @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')
+ @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)
+ @testterm = FactoryGirl.create(:term, project: @project, title: 'Test Note', taxonomy: 'Ink', description: 'This is a test', uri: 'https://www.test.com/', 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',
+ notationStyle: 'r-v',
+ 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.' } },
+ taxonomies: ['Ink', 'Unknown']
+ })
+ expect(result[:groups]).to eq({
+ 1 => {:params=>{:type=>"Quire", :title=>"Group 1", :nestLevel=>1}, :tacketed=>[], :sewing=>[], :parentOrder=>nil, :memberOrders=>["Leaf_1", "Leaf_2", "Group_2", "Leaf_5", "Leaf_6"]},
+ 2 => {:params=>{:type=>"Quire", :title=>"Group 2", :nestLevel=>2}, :tacketed=>[], :sewing=>[], :parentOrder=>1, :memberOrders=>["Leaf_3", "Leaf_4"]}
+ })
+ expect(result[:leafs]).to eq({
+ 1 => {:params=>{:folio_number=>"", :material=>"Paper", :type=>"Original", :attached_above=>"None", :attached_below=>"None", :stub=>"No", :nestLevel=>1}, :conjoined_leaf_order=>nil, :parentOrder=>1, :rectoOrder=>1, :versoOrder=>1},
+ 2 => {:params=>{:folio_number=>"", :material=>"Paper", :type=>"Original", :attached_above=>"None", :attached_below=>"None", :stub=>"No", :nestLevel=>1}, :conjoined_leaf_order=>nil, :parentOrder=>1, :rectoOrder=>2, :versoOrder=>2},
+ 3 => {:params=>{:folio_number=>"", :material=>"Paper", :type=>"Original", :attached_above=>"None", :attached_below=>"None", :stub=>"No", :nestLevel=>2}, :conjoined_leaf_order=>nil, :parentOrder=>2, :rectoOrder=>3, :versoOrder=>3},
+ 4 => {:params=>{:folio_number=>"", :material=>"Paper", :type=>"Original", :attached_above=>"None", :attached_below=>"None", :stub=>"No", :nestLevel=>2}, :conjoined_leaf_order=>nil, :parentOrder=>2, :rectoOrder=>4, :versoOrder=>4},
+ 5 => {:params=>{:folio_number=>"", :material=>"Paper", :type=>"Original", :attached_above=>"None", :attached_below=>"None", :stub=>"No", :nestLevel=>1}, :conjoined_leaf_order=>nil, :parentOrder=>1, :rectoOrder=>5, :versoOrder=>5},
+ 6 => {:params=>{:folio_number=>"", :material=>"Paper", :type=>"Endleaf", :attached_above=>"None", :attached_below=>"None", :stub=>"No", :nestLevel=>1}, :conjoined_leaf_order=>nil, :parentOrder=>1, :rectoOrder=>6, :versoOrder=>6}
+ })
+ expect(result[:rectos]).to eq({
+ 1 => {:params=>{:page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>1},
+ 2 => {:params=>{:page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>2},
+ 3 => {:params=>{:page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>3},
+ 4 => {:params=>{:page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>4},
+ 5 => {:params=>{:page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>5},
+ 6 => {:params=>{:page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>6}
+ })
+ expect(result[:versos]).to eq({
+ 1 => {:params=>{:page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>1},
+ 2 => {:params=>{:page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>2},
+ 3 => {:params=>{:page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>3},
+ 4 => {:params=>{:page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>4},
+ 5 => {:params=>{:page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>5},
+ 6 => {:params=>{:page_number=>"", :texture=>"None", :image=>{}, :script_direction=>"None"}, :parentOrder=>6}
+ })
+ expect(result[:terms]).to eq({
+ 1 => {:params=>{:title=>"Test Note", :taxonomy=>"Ink", :description=>"This is a test", :uri=>"https://www.test.com/", :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("textblock title").text).to eq 'Sample project'
+ expect(result.css("textblock shelfmark").text).to eq 'Ravenna 384.2339'
+ expect(result.css("textblock 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'],
+ )
+ # first element should be a group, second element should be a string of all leaves in that group, separated by a space
+ groups_and_members = result.css("taxonomy[xml|id='group_members'] term").collect { |t| [t['xml:id'], t.text] }
+ groups_and_members.each do |gm|
+ expect(gm[0]).to match /^group_members_Group/
+ expect(gm[1]).to match /^#Leaf/
+ end
+ # Leaves
+ expect(result.css("taxonomy[xml|id='leaf_material'] term").collect { |t| [t['xml:id'], t.text] }).to include(
+ ['leaf_material_paper', 'Paper']
+ )
+ # Check that there are 6 rectos and 6 versos
+ ns = {n: "http://viscoll.org/schema/collation/"}
+ expect(result.xpath("//n:mapping/n:map[@side='recto']", ns).size).to eq(6)
+ expect(result.xpath("//n:mapping/n:map[@side='verso']", ns).size).to eq(6)
+ # Check that the @target contains either Group or Leaf
+ map_targets = result.xpath("//n:mapping/n:map[@target]/@target", ns)
+ map_targets.each do |t|
+ expect(t).to match /^#(Leaf|Group)/
+ end
+ # check that mapping/map/term/@target matches either Group or #side_page_number_EMPTY
+ term_targets = result.xpath("//n:mapping/n:map/n:term[@target]/@target", ns)
+ term_targets.each do |t|
+ expect(t.to_s).to match /^\s?#(side_page_number_EMPTY|group)/
+ end
+ 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..2a992ced
--- /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, term]')
+ 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 'Term queries' do
+ it 'should reject invalid attribute for sides' do
+ result = runValidations([
+ { 'type' => 'term', 'attribute' => 'waahoo', 'condition' => 'equals', 'values' => ['hint'] }
+ ])
+ expect(result).to include a_hash_including('attribute' => 'valid attributes for term: [title, type, description]')
+ end
+
+ it 'should reject invalid conditions for type' do
+ result = runValidations([
+ { 'type' => 'term', 'attribute' => 'type', 'condition' => 'waahoo', 'values' => ['hint'] }
+ ])
+ expect(result).to include a_hash_including('condition' => 'valid conditions for term attribute type : [equals, not equals]')
+ end
+
+ it 'should accept valid conditions for type' do
+ ['equals', 'not equals'].each do |condition|
+ result = runValidations([
+ { 'type' => 'term', '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' => 'term', 'attribute' => attribute, 'condition' => 'waahoo', 'values' => ['hint'] }
+ ])
+ expect(result).to include a_hash_including('condition' => "valid conditions for term 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' => 'term', '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..28d6c218
--- /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.' } },
+ "taxonomies" => ['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" => {"folio_number" => "1", "material" => "Paper", "type" => "Original", "attached_above" => "None", "attached_below" => "None", "stub" => "No", "nestLevel" => 1}, "conjoined_leaf_order" => nil, "parentOrder" => 1, "rectoOrder" => 1, "versoOrder" => 1},
+ "2" => {"params" => {"folio_number" => "2", "material" => "Paper", "type" => "Original", "attached_above" => "None", "attached_below" => "None", "stub" => "No", "nestLevel" => 1}, "conjoined_leaf_order" => nil, "parentOrder" => 1, "rectoOrder" => 2, "versoOrder" => 2},
+ "3" => {"params" => {"folio_number" => "3", "material" => "Paper", "type" => "Original", "attached_above" => "None", "attached_below" => "None", "stub" => "No", "nestLevel" => 2}, "conjoined_leaf_order" => nil, "parentOrder" => 2, "rectoOrder" => 3, "versoOrder" => 3},
+ "4" => {"params" => {"folio_number" => "4", "material" => "Paper", "type" => "Original", "attached_above" => "None", "attached_below" => "None", "stub" => "No", "nestLevel" => 2}, "conjoined_leaf_order" => nil, "parentOrder" => 2, "rectoOrder" => 4, "versoOrder" => 4},
+ "5" => {"params" => {"folio_number" => "5", "material" => "Paper", "type" => "Original", "attached_above" => "None", "attached_below" => "None", "stub" => "No", "nestLevel" => 1}, "conjoined_leaf_order" => nil, "parentOrder" => 1, "rectoOrder" => 5, "versoOrder" => 5},
+ "6" => {"params" => {"folio_number" => "6", "material" => "Paper", "type" => "Endleaf", "attached_above" => "None", "attached_below" => "None", "stub" => "No", "nestLevel" => 1}, "conjoined_leaf_order" => nil, "parentOrder" => 1, "rectoOrder" => 6, "versoOrder" => 6}
+ },
+ "Rectos" => {
+ "1" => {"params" => {"texture" => "Hair", "image" => {}, "script_direction" => "None"}, "parentOrder" => 1},
+ "2" => {"params" => {"texture" => "Hair", "image" => {}, "script_direction" => "None"}, "parentOrder" => 2},
+ "3" => {"params" => {"texture" => "Hair", "image" => {}, "script_direction" => "None"}, "parentOrder" => 3},
+ "4" => {"params" => {"texture" => "Hair", "image" => {}, "script_direction" => "None"}, "parentOrder" => 4},
+ "5" => {"params" => {"texture" => "Hair", "image" => {}, "script_direction" => "None"}, "parentOrder" => 5},
+ "6" => {"params" => {"texture" => "Hair", "image" => {}, "script_direction" => "None"}, "parentOrder" => 6}
+ },
+ "Versos" => {
+ "1" => {"params" => {"texture" => "Flesh", "image" => {}, "script_direction" => "None"}, "parentOrder" => 1},
+ "2" => {"params" => {"texture" => "Flesh", "image" => {}, "script_direction" => "None"}, "parentOrder" => 2},
+ "3" => {"params" => {"texture" => "Flesh", "image" => {}, "script_direction" => "None"}, "parentOrder" => 3},
+ "4" => {"params" => {"texture" => "Flesh", "image" => {}, "script_direction" => "None"}, "parentOrder" => 4},
+ "5" => {"params" => {"texture" => "Flesh", "image" => {}, "script_direction" => "None"}, "parentOrder" => 5},
+ "6" => {"params" => {"texture" => "Flesh", "image" => {}, "script_direction" => "None"}, "parentOrder" => 6}
+ },
+ "Terms" => {
+ "1" => {"params" => {"title" => "Test Term", "taxonomy" => "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.taxonomies).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.terms[0].title).to eq 'Test Term'
+ expect(project.terms[0].taxonomy).to eq 'Ink'
+ expect(project.terms[0].description).to eq 'This is a test'
+ expect(project.terms[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.taxonomies).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.terms[0].title).to eq 'Test Term'
+ expect(project.terms[0].taxonomy).to eq 'Ink'
+ expect(project.terms[0].description).to eq 'This is a test'
+ expect(project.terms[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..660ed6cb
--- /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.taxonomies).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.terms[0].title).to eq 'Test Term'
+ expect(project.terms[0].taxonomy).to eq 'Ink'
+ expect(project.terms[0].description).to eq 'This is a test'
+ expect(project.terms[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.taxonomies).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.terms[0].title).to eq 'Test Term'
+ expect(project.terms[0].taxonomy).to eq 'Ink'
+ expect(project.terms[0].description).to eq 'This is a test'
+ expect(project.terms[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..3cd83d07
--- /dev/null
+++ b/viscoll-api/spec/helpers/controller_helper/import_xml_helper_spec.rb
@@ -0,0 +1,53 @@
+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.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.terms[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.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.terms[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..75df2067
--- /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((Leaf.find(project.groups[0].memberIDs[0])).folio_number).to eq "2"
+ expect((Leaf.find(project.groups[0].memberIDs[1])).folio_number).to eq "3"
+ 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 },
+ taxonomies: ['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)
+ @testterm = FactoryGirl.create(:term, project: @project, title: 'Test Term', taxonomy: 'Ink', description: 'This is a test', uri: 'https://www.test.com/', 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',
+ 'notationStyle': 'r-v',
+ 'metadata': { 'date' => '18th century' },
+ 'preferences': { 'showTips' => true },
+ 'taxonomies': [ '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[:terms]).to eq({@testterm.id.to_s => {
+ id: @testterm.id.to_s,
+ title: 'Test Term',
+ taxonomy: 'Ink',
+ description: 'This is a test',
+ uri: 'https://www.test.com/',
+ 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..c51ffa6b
--- /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(['test@test.com'])
+ 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..bbdcb994
--- /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(:terms) }
+
+ 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 term" do
+ term = FactoryGirl.create(:term, project: @project, objects: {Group: [@group.id], Leaf: [], Recto: [], Verso: []})
+ @group.terms << term
+ @group.save
+ @group.destroy
+ expect(term.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..f5deaeab
--- /dev/null
+++ b/viscoll-api/spec/models/leaf_spec.rb
@@ -0,0 +1,69 @@
+require 'rails_helper'
+
+RSpec.describe Leaf, 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(: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(:terms) }
+
+ 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 terms" do
+ subterm = FactoryGirl.create(:term, project: @project, objects: {Group: [], Leaf: [@leaf.id], Recto: [], Verso: []})
+ @leaf.terms << subterm
+ @leaf.save
+ @leaf.destroy
+ expect(subterm.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/project_spec.rb b/viscoll-api/spec/models/project_spec.rb
new file mode 100644
index 00000000..47ca769f
--- /dev/null
+++ b/viscoll-api/spec/models/project_spec.rb
@@ -0,0 +1,79 @@
+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(:notationStyle).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(:taxonomies).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(:terms) }
+
+ 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..1944b5b2
--- /dev/null
+++ b/viscoll-api/spec/models/side_spec.rb
@@ -0,0 +1,42 @@
+require 'rails_helper'
+
+RSpec.describe Side, type: :model do
+ it { is_expected.to be_mongoid_document }
+
+ 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(:terms) }
+
+ 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 terms" do
+ term = FactoryGirl.create(:term, project: @project, objects: {Group: [], Leaf: [], Recto: [@side.id.to_s], Verso: []} )
+ term2 = FactoryGirl.create(:term, project: @project, objects: {Group: [], Leaf: [], Recto: [], Verso: [@side.id.to_s]} )
+ @side.terms << [term, term2]
+ @side.save
+ expect(@side.terms).to include term
+ expect(@side.terms).to include term2
+ @side.destroy
+ expect(term.objects[:Recto]).to be_empty
+ expect(term2.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/viscoll-api/spec/models/term_spec.rb b/viscoll-api/spec/models/term_spec.rb
new file mode 100644
index 00000000..73e56e50
--- /dev/null
+++ b/viscoll-api/spec/models/term_spec.rb
@@ -0,0 +1,78 @@
+require 'rails_helper'
+
+RSpec.describe Term, 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(:taxonomy).of_type(String) }
+ it { is_expected.to have_field(:description).of_type(String) }
+ it { is_expected.to have_field(:uri).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, taxonomies: ['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)
+ @term = FactoryGirl.create(:term, project: @project, taxonomy: ['Ink'], objects: {Group: [@group.id.to_s], Leaf: [@leaf.id.to_s], Recto: [@side1.id.to_s], Verso: [@side2.id.to_s]} )
+ @group.terms << @term
+ @group.save
+ @leaf.terms << @term
+ @leaf.save
+ @side1.terms << @term
+ @side1.save
+ @side2.terms << @term
+ @side2.save
+ end
+
+ describe "Validations" do
+ it "should require a title" do
+ @term.title = ''
+ expect(@term).not_to be_valid
+ end
+ it "should be unique within the project" do
+ duplicate_term = FactoryGirl.create(:term, project: @project, taxonomy: ['Ink'], objects: {Group: [@group.id.to_s], Leaf: [], Recto: [], Verso: []} )
+ duplicate_term.title = @term.title
+ expect(duplicate_term).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)
+ term2 = FactoryGirl.create(:term, project: project2, taxonomy: ['Ink'], objects: {Group: [group2.id.to_s], Leaf: [], Recto: [], Verso: []})
+ expect(term2).to be_valid
+ end
+ it "should require a taxonomy" do
+ @term.taxonomy = ''
+ expect(@term).not_to be_valid
+ end
+ end
+
+ describe "Destroy hooks" do
+ before do
+ @term.destroy
+ end
+ it "updates linked group" do
+ @group.reload
+ expect(@group.terms).to be_empty
+ end
+ it "updates linked leaf" do
+ @leaf.reload
+ expect(@leaf.terms).to be_empty
+ end
+ it "updates linked recto side" do
+ @side1.reload
+ expect(@side1.terms).to be_empty
+ end
+ it "updates linked verso side" do
+ @side2.reload
+ expect(@side2.terms).to be_empty
+ end
+ end
+end
diff --git a/viscoll-api/spec/models/user_spec.rb b/viscoll-api/spec/models/user_spec.rb
new file mode 100644
index 00000000..6ed39cb9
--- /dev/null
+++ b/viscoll-api/spec/models/user_spec.rb
@@ -0,0 +1,24 @@
+# 🤖 AI Usage Disclosure: Designed and implemented by Claude (Anthropic).
+require 'rails_helper'
+
+RSpec.describe User, type: :model do
+ it { is_expected.to be_mongoid_document }
+
+ it { is_expected.to have_field(:name).of_type(String) }
+
+ it { is_expected.to have_many(:images) }
+ it { is_expected.to have_many(:projects) }
+
+ describe "email downcasing" do
+ it "saves email as lowercase" do
+ user = FactoryGirl.create(:user, email: "User@Example.COM")
+ expect(user.email).to eq "user@example.com"
+ end
+
+ it "downcases email on update" do
+ user = FactoryGirl.create(:user, email: "original@example.com")
+ user.update(email: "UPDATED@EXAMPLE.COM")
+ expect(user.reload.email).to eq "updated@example.com"
+ 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..54a5b7d7
--- /dev/null
+++ b/viscoll-api/spec/requests/groups/groups_create_spec.rb
@@ -0,0 +1,178 @@
+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, taxonomies: ["Ink"]})
+ @parameters = {
+ "group": {
+ "project_id": @project.id.to_str,
+ "title": "New Quire",
+ "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 term' 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..50065e1c
--- /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,
+ taxonomies: ["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..d9047e58
--- /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,
+ taxonomies: ["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..3fcbb1e9
--- /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,
+ taxonomies: ["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..8476efef
--- /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,
+ taxonomies: ["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_generateFolio_spec.rb b/viscoll-api/spec/requests/leafs/leafs_generateFolio_spec.rb
new file mode 100644
index 00000000..b1f8437d
--- /dev/null
+++ b/viscoll-api/spec/requests/leafs/leafs_generateFolio_spec.rb
@@ -0,0 +1,40 @@
+require 'rails_helper'
+
+describe "PUT /leafs/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 = {
+ startNumber: 9,
+ leafIDs: [@leaf1.id, @leaf2.id],
+ }
+ end
+
+ context 'generate folio number' do
+ before do
+ put '/leafs/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 leaf folio numbers' do
+ leaf1 = @project.leafs.find(@leaf1.id)
+ leaf2 = @project.leafs.find(@leaf2.id)
+ expect(leaf1.folio_number).to eq "9"
+ expect(leaf2.folio_number).to eq "10"
+ end
+ end
+end
\ No newline at end of file
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/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..ff44d96d
--- /dev/null
+++ b/viscoll-api/spec/requests/projects/export_projects_spec.rb
@@ -0,0 +1,268 @@
+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',
+ 'notationStyle' => 'r-v',
+ 'metadata' => { date: '18th century' },
+ 'preferences' => { 'showTips' => true },
+ 'taxonomies' => ['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')
+ @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')
+ @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)
+ @testterm = FactoryGirl.create(:term, project: @project, title: 'Test Term', taxonomy: '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',
+ 'notationStyle' => 'r-v',
+ '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.' } },
+ 'taxonomies' => ['Ink', 'Unknown']
+ })
+ expect(export_result['Groups']).to eq({
+ '1' => {'params'=>{'type'=>"Quire", 'title'=>"Group 1", 'nestLevel'=>1}, 'tacketed'=>[], 'sewing'=>[], 'parentOrder'=>nil, 'memberOrders'=>["Leaf_1", "Leaf_2", "Group_2", "Leaf_5", "Leaf_6"]},
+ '2' => {'params'=>{'type'=>"Quire", 'title'=>"Group 2", 'nestLevel'=>2}, 'tacketed'=>[], 'sewing'=>[], 'parentOrder'=>1, 'memberOrders'=>["Leaf_3", "Leaf_4"]}
+ })
+ expect(export_result['Leafs']).to eq({
+ '1' => {'params'=>{'folio_number'=>'', 'material'=>"Paper", 'type'=>"Original", 'attached_above'=>"None", 'attached_below'=>"None", 'stub'=>"No", 'nestLevel'=>1}, 'conjoined_leaf_order'=>nil, 'parentOrder'=>1, 'rectoOrder'=>1, 'versoOrder'=>1},
+ '2' => {'params'=>{'folio_number'=>'', 'material'=>"Paper", 'type'=>"Original", 'attached_above'=>"None", 'attached_below'=>"None", 'stub'=>"No", 'nestLevel'=>1}, 'conjoined_leaf_order'=>nil, 'parentOrder'=>1, 'rectoOrder'=>2, 'versoOrder'=>2},
+ '3' => {'params'=>{'folio_number'=>'', 'material'=>"Paper", 'type'=>"Original", 'attached_above'=>"None", 'attached_below'=>"None", 'stub'=>"No", 'nestLevel'=>2}, 'conjoined_leaf_order'=>nil, 'parentOrder'=>2, 'rectoOrder'=>3, 'versoOrder'=>3},
+ '4' => {'params'=>{'folio_number'=>'', 'material'=>"Paper", 'type'=>"Original", 'attached_above'=>"None", 'attached_below'=>"None", 'stub'=>"No", 'nestLevel'=>2}, 'conjoined_leaf_order'=>nil, 'parentOrder'=>2, 'rectoOrder'=>4, 'versoOrder'=>4},
+ '5' => {'params'=>{'folio_number'=>'', 'material'=>"Paper", 'type'=>"Original", 'attached_above'=>"None", 'attached_below'=>"None", 'stub'=>"No", 'nestLevel'=>1}, 'conjoined_leaf_order'=>nil, 'parentOrder'=>1, 'rectoOrder'=>5, 'versoOrder'=>5},
+ '6' => {'params'=>{'folio_number'=>'', 'material'=>"Paper", 'type'=>"Endleaf", 'attached_above'=>"None", 'attached_below'=>"None", 'stub'=>"No", 'nestLevel'=>1}, 'conjoined_leaf_order'=>nil, 'parentOrder'=>1, 'rectoOrder'=>6, 'versoOrder'=>6}
+ })
+ expect(export_result['Rectos']).to eq({
+ '1' => {'params'=>{'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'=>{'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>2},
+ '3' => {'params'=>{'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>3},
+ '4' => {'params'=>{'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>4},
+ '5' => {'params'=>{'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>5},
+ '6' => {'params'=>{'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>6}
+ })
+ expect(export_result['Versos']).to eq({
+ '1' => {'params'=>{'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>1},
+ '2' => {'params'=>{'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>2},
+ '3' => {'params'=>{'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>3},
+ '4' => {'params'=>{'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>4},
+ '5' => {'params'=>{'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>5},
+ '6' => {'params'=>{'page_number'=>"", 'texture'=>"None", 'image'=>{}, 'script_direction'=>"None"}, 'parentOrder'=>6}
+ })
+ expect(export_result['Terms']).to eq({
+ '1' => {'params'=>{'title'=>"Test Term", 'taxonomy'=>"Ink", 'description'=>"This is a test", 'show'=>true}, 'objects'=>{'Group'=>[1], 'Leaf'=>[5], 'Recto'=>[5], 'Verso'=>[5]}}
+ })
+ expect(image_result['exportedImages']).to eq("https://vceditor.library.upenn.edu/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://vceditor.library.upenn.edu/images/zip/#{@project.id}")
+ result = Nokogiri::XML(@body['data'])
+ # Metadata elements
+ expect(result.css("textblock title").text).to eq 'Sample project'
+ expect(result.css("textblock shelfmark").text).to eq 'Ravenna 384.2339'
+ expect(result.css("textblock 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'],
+ )
+ groups_and_members = result.css("taxonomy[xml|id='group_members'] term").collect { |t| [t['xml:id'], t.text] }
+ groups_and_members.each do |gm|
+ expect(gm[0]).to match /^group_members_Group/
+ expect(gm[1]).to match /^#Leaf/
+ end
+ # Leaves
+ expect(result.css("taxonomy[xml|id='leaf_material'] term").collect { |t| [t['xml:id'], t.text] }).to include(
+ ['leaf_material_paper', 'Paper']
+ )
+ #TODO test for folio_number generation
+ # Sides and Terms
+ mappings = result.css("mapping map").collect { |t| [t['target'], t['side'], t.css('term').first['target']]}
+ # expect each mapping to have a target:
+ mappings.each do |mapping|
+ # temp fix, we need to be testing for the right content
+ # and not just making it work
+ expect(mapping.first).to match /^#(Leaf|Group|ravenna)/
+ expect(['recto', 'verso', nil]).to include mapping[1]
+ expect(mapping[2]).to match /^#side_page_number_EMPTY|#group/
+ end
+ 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, svg, formula, html]"
+ 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..de3a8125
--- /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(page_number: 'PN0')
+ @parameters = {
+ "queries": [
+ {
+ "type": "side",
+ "attribute": "page_number",
+ "condition": "contains",
+ "values": [ 'PN' ]
+ }
+ ]
+ }
+ 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(page_number: 'PN0')
+ @project1.sides[11].update(page_number: 'QR1')
+ @project2.sides[7].update(page_number: 'PN0')
+ @parameters = {
+ "queries": [
+ {
+ "type": "side",
+ "attribute": "page_number",
+ "condition": "contains",
+ "values": [ 'PN', '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(page_number: 'PN0')
+ @parameters = {
+ "queries": [
+ {
+ "type": "side",
+ "attribute": "page_number",
+ "condition": "not contains",
+ "values": [ 'PN' ]
+ }
+ ]
+ }
+ 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(page_number: 'PN0')
+ @project1.sides[11].update(page_number: 'QR1')
+ @project2.sides[7].update(page_number: 'PN0')
+ @parameters = {
+ "queries": [
+ {
+ "type": "side",
+ "attribute": "page_number",
+ "condition": "not contains",
+ "values": [ 'PN', '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 term-based queries' do
+ before do
+ @term1 = FactoryGirl.create(:term, project_id: @project1.id, attachments: [@project1.groups[1], @project1.leafs[5], @project1.sides[14], @project1.sides[15]], title: "ULTRA WAAHOO")
+ @term2 = FactoryGirl.create(:term, project_id: @project1.id, attachments: [@project1.groups[2], @project1.leafs[7], @project1.sides[2], @project1.sides[3]], title: "XTREME FOOBAR")
+ @term3 = FactoryGirl.create(:term, project_id: @project1.id, attachments: [@project1.groups[3], @project1.leafs[3], @project1.sides[10], @project1.sides[11]], title: "CREEPY WAAHOO")
+ @termbad = FactoryGirl.create(:term, 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": "term",
+ "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['Terms']).to eq [@term1.id.to_s]
+ end
+ end
+
+ context "equals multiple" do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "term",
+ "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['Terms'].count).to eq 2
+ expect(body['Terms']).to include @term2.id.to_s
+ expect(body['Terms']).to include @term3.id.to_s
+ end
+ end
+
+ context "not equals one" do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "term",
+ "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['Terms'].count).to eq @project1.terms.count-1
+ expect(body['Terms']).not_to include @term1.id.to_s
+ expect(body['Terms']).not_to include @termbad.id.to_s
+ end
+ end
+
+ context "not equals multiple" do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "term",
+ "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['Terms'].count).to eq @project1.terms.count-2
+ expect(body['Terms']).not_to include @term1.id.to_s
+ expect(body['Terms']).not_to include @term3.id.to_s
+ expect(body['Terms']).not_to include @termbad.id.to_s
+ end
+ end
+
+ context "contains one" do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "term",
+ "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['Terms']).to eq [@term1.id.to_s]
+ end
+ end
+
+ context "contains multiple" do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "term",
+ "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['Terms'].count).to eq 2
+ expect(body['Terms']).to include @term2.id.to_s
+ expect(body['Terms']).to include @term3.id.to_s
+ end
+ end
+
+ context "not contains one" do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "term",
+ "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['Terms'].count).to eq @project1.terms.count-1
+ expect(body['Terms']).not_to include @term1.id.to_s
+ end
+ end
+
+ context "not contains multiple" do
+ before do
+ @parameters = {
+ "queries": [
+ {
+ "type": "term",
+ "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['Terms'].count).to eq @project1.terms.count-2
+ expect(body['Terms']).not_to include @term2.id.to_s
+ expect(body['Terms']).not_to include @term3.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..a421e3a2
--- /dev/null
+++ b/viscoll-api/spec/requests/projects/import_projects_spec.rb
@@ -0,0 +1,140 @@
+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.taxonomies).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.terms[0].title).to eq 'Test Term'
+ expect(project.terms[0].taxonomy).to eq 'Ink'
+ expect(project.terms[0].description).to eq 'This is a test'
+ expect(project.terms[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 })
+ # TODO: import taxonomies
+ 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
+ # TODO: import terms
+ 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..c27aaf67
--- /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"}
+ ],
+ "taxonomies": [
+ "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_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/terms/terms_create_spec.rb b/viscoll-api/spec/requests/terms/terms_create_spec.rb
new file mode 100644
index 00000000..e50bf36e
--- /dev/null
+++ b/viscoll-api/spec/requests/terms/terms_create_spec.rb
@@ -0,0 +1,152 @@
+require 'rails_helper'
+
+describe "POST /terms", :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, taxonomies: ["Ink"]})
+ @parameters = {
+ term: {
+ "project_id": @project.id.to_str,
+ "title": "some title for term",
+ "taxonomy": "Ink",
+ "description": "blue ink"
+ }
+ }
+ end
+
+ context 'and valid authorization' do
+ context 'and standard terms' do
+ before do
+ post '/terms', 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 term to the project' do
+ expect(@project.terms.length).to eq 1
+ expect(@project.terms[0].title).to eq "some title for term"
+ end
+ end
+
+ context 'and out-of-context terms' do
+ before do
+ @parameters[:term][:taxonomy] = "WAAHOO"
+ post '/terms', 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 taxonomies are allowed' do
+ expect(@body['taxonomy']).to include('should be one of ["Ink"]')
+ end
+ end
+
+ context 'and missing project' do
+ before do
+ @parameters[:term][:project_id] += "WAAHOO"
+ post '/terms', 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[:term][:project_id]}"
+ end
+ end
+
+ context 'and failing params for the term' do
+ before do
+ allow_any_instance_of(Term).to receive(:save).and_return(false)
+ post '/terms', 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, taxonomies: ["Ink"] })
+ @parameters[:term][:project_id] = @project2.id.to_str
+ post '/terms', 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 terms to the project' do
+ expect(@project2.terms).not_to include an_object_having_attributes({ title: "some title for term" })
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ post '/terms', 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 '/terms', 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 '/terms', 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 '/terms'
+ 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/terms/terms_create_taxonomy_spec.rb b/viscoll-api/spec/requests/terms/terms_create_taxonomy_spec.rb
new file mode 100644
index 00000000..9cfd3aa2
--- /dev/null
+++ b/viscoll-api/spec/requests/terms/terms_create_taxonomy_spec.rb
@@ -0,0 +1,147 @@
+require 'rails_helper'
+
+describe "POST /terms/taxonomy", :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, taxonomies: ["Ink"]})
+ @parameters = {
+ "taxonomy": {
+ "project_id": @project.id.to_str,
+ "taxonomy": "Paper"
+ }
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'with valid parameters' do
+ before do
+ post '/terms/taxonomy', 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 taxonomy to the project' do
+ expect(@project.taxonomies).to include "Ink"
+ expect(@project.taxonomies).to include "Paper"
+ end
+ end
+
+ context 'with missing project' do
+ before do
+ @parameters[:taxonomy][:project_id] += 'missing'
+ post '/terms/taxonomy', 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 taxonomy' do
+ before do
+ @parameters[:taxonomy][:taxonomy] = "Ink"
+ post '/terms/taxonomy', 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['taxonomy']).to eq "Ink taxonomy already exists in the project"
+ end
+
+ it 'should leave the project alone' do
+ expect(@project.taxonomies).to eq ["Ink"]
+ end
+ end
+
+ context 'with unauthorized project' do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project2 = FactoryGirl.create(:project, {user: @user2, taxonomies: ["Ink"]})
+ @parameters[:taxonomy][:project_id] = @project2.id.to_str
+ post '/terms/taxonomy', 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 taxonomies alone' do
+ expect(@project2.taxonomies).not_to include("Paper")
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ post '/terms/taxonomy', 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 '/terms/taxonomy', 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 '/terms/taxonomy', 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 '/terms/taxonomy'
+ 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/terms/terms_delete_taxonomy_spec.rb b/viscoll-api/spec/requests/terms/terms_delete_taxonomy_spec.rb
new file mode 100644
index 00000000..799a41a9
--- /dev/null
+++ b/viscoll-api/spec/requests/terms/terms_delete_taxonomy_spec.rb
@@ -0,0 +1,163 @@
+require 'rails_helper'
+
+describe "DELETE /terms/taxonomy", :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, taxonomies: ["Ink", "Paper"]})
+ @project.terms << FactoryGirl.create(:term, {
+ project_id: @project.id,
+ taxonomy: "Ink",
+ description: "Sepia"
+ })
+ @project.terms << FactoryGirl.create(:term, {
+ project_id: @project.id,
+ taxonomy: "Paper",
+ description: "Parchment"
+ })
+ @project.save
+ @parameters = {
+ "taxonomy": {
+ "project_id": @project.id.to_str,
+ "taxonomy": "Ink"
+ }
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'with valid parameters' do
+ before do
+ delete '/terms/taxonomy', 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 taxonomy from the project' do
+ expect(@project.taxonomies).not_to include "Ink"
+ expect(@project.taxonomies).to include "Paper"
+ end
+
+ it 'should change taxonomy of the term to Unknown' do
+ expect(@project.terms).to include an_object_having_attributes(taxonomy: "Unknown")
+ expect(@project.terms).to include an_object_having_attributes(taxonomy: "Paper")
+ end
+ end
+
+ context 'with missing project' do
+ before do
+ @parameters[:taxonomy][:project_id] += 'missing'
+ delete '/terms/taxonomy', 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 taxonomy' do
+ before do
+ @parameters[:taxonomy][:taxonomy] = "Waahoo"
+ delete '/terms/taxonomy', 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['taxonomy']).to eq "Waahoo taxonomy doesn't exist in the project"
+ end
+
+ it 'should leave the project alone' do
+ expect(@project.taxonomies).to eq ["Ink", "Paper"]
+ end
+ end
+
+ context 'with unauthorized project' do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project.user = @user2
+ @project.save
+ delete '/terms/taxonomy', 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 taxonomies alone' do
+ expect(@project.taxonomies).to eq ["Ink", "Paper"]
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ delete '/terms/taxonomy', 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 '/terms/taxonomy', 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 '/terms/taxonomy', 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 '/terms/taxonomy'
+ 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/terms/terms_destroy_spec.rb b/viscoll-api/spec/requests/terms/terms_destroy_spec.rb
new file mode 100644
index 00000000..1d784a11
--- /dev/null
+++ b/viscoll-api/spec/requests/terms/terms_destroy_spec.rb
@@ -0,0 +1,124 @@
+require 'rails_helper'
+
+describe "DELETE /terms/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,
+ taxonomies: ["Ink"]
+ })
+ @term = FactoryGirl.create(:term, {
+ taxonomy: "Ink",
+ project: @project
+ })
+ @parameters = {}
+ end
+
+ context 'with valid authorization' do
+ context 'and valid term ID' do
+ before do
+ delete '/terms/'+@term.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 term' do
+ expect(Term.where(id: @term.id).exists?).to be false
+ end
+ end
+
+ context 'and invalid term ID' do
+ before do
+ delete '/terms/'+@term.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 terms" do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project2 = FactoryGirl.create(:project, {
+ user: @user2,
+ taxonomies: ["Hand"]
+ })
+ @term2 = FactoryGirl.create(:term, {
+ taxonomy: "Hand",
+ project: @project2
+ })
+ delete '/terms/'+@term2.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 term alone' do
+ expect(Term.where(id: @term2.id).exists?).to be true
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ delete '/terms/'+@term.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 '/terms/'+@term.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 '/terms/'+@term.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 '/terms/'+@term.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/terms/terms_link_spec.rb b/viscoll-api/spec/requests/terms/terms_link_spec.rb
new file mode 100644
index 00000000..caed337c
--- /dev/null
+++ b/viscoll-api/spec/requests/terms/terms_link_spec.rb
@@ -0,0 +1,310 @@
+require 'rails_helper'
+
+describe "PUT /terms/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, taxonomies: ["Ink"]})
+ @defaultGroup = FactoryGirl.create(:quire, project: @project)
+ @project.add_groupIDs([@defaultGroup.id.to_s], 0)
+ @term = FactoryGirl.create(:term, {
+ project: @project,
+ title: "some title for term",
+ taxonomy: "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 '/terms/'+@term.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 term to the target' do
+ expect(@group.terms.length).to eq 1
+ expect(@group.terms[0].id).to eq @term.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 '/terms/'+@term.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 term to the target' do
+ expect(@leaf.terms.length).to eq 1
+ expect(@leaf.terms[0].id).to eq @term.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 '/terms/'+@term.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 term to the target' do
+ expect(@side.terms.length).to eq 1
+ expect(@side.terms[0].id).to eq @term.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, taxonomies: ["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 '/terms/'+@term.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.terms).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 '/terms/'+@term.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.terms).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 '/terms/'+@term.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.terms).to be_empty
+ end
+ end
+ end
+ context 'and unknown target type' do
+ before do
+ @parameters[:objects][0][:type] = "unknown"
+ put '/terms/'+@term.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 term' do
+ before do
+ put '/terms/'+@term.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 '/terms/'+@term.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(Term).to receive(:save).and_raise("Exception!")
+ @group = FactoryGirl.create(:group, { project: @project })
+ @parameters = {
+ objects: [
+ {
+ id: @group.id.to_str,
+ type: "Group"
+ }
+ ]
+ }
+ put '/terms/'+@term.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 '/terms/'+@term.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 '/terms/'+@term.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 '/terms/'+@term.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 '/terms/'+@term.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/terms/terms_unlink_spec.rb b/viscoll-api/spec/requests/terms/terms_unlink_spec.rb
new file mode 100644
index 00000000..27ba4214
--- /dev/null
+++ b/viscoll-api/spec/requests/terms/terms_unlink_spec.rb
@@ -0,0 +1,307 @@
+require 'rails_helper'
+
+describe "PUT /terms/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, taxonomies: ["Ink"]})
+ @defaultGroup = FactoryGirl.create(:quire, project: @project)
+ @project.add_groupIDs([@defaultGroup.id.to_s], 0)
+ @term = FactoryGirl.create(:term, {
+ project: @project,
+ title: "some title for term",
+ taxonomy: "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, terms: [@term] })
+ @project.add_groupIDs([@group.id.to_s], 0)
+ @parameters = {
+ objects: [
+ {
+ id: @group.id.to_str,
+ type: "Group"
+ }
+ ]
+ }
+ put '/terms/'+@term.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 term from the target' do
+ expect(@group.terms).to be_empty
+ end
+ end
+ context 'and correct leaf target' do
+ before do
+ @leaf = FactoryGirl.create(:leaf, {project: @project, terms: [@term] })
+ @defaultGroup.add_members([@leaf.id.to_s], 1)
+ @parameters = {
+ objects: [
+ {
+ id: @leaf.id.to_str,
+ type: "Leaf"
+ }
+ ]
+ }
+ put '/terms/'+@term.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 term from the target' do
+ expect(@leaf.terms).to be_empty
+ end
+ end
+ context 'and correct side target' do
+ before do
+ @leaf = FactoryGirl.create(:leaf, {project: @project, terms: [@term] })
+ @defaultGroup.add_members([@leaf.id.to_s], 1)
+ @side = @project.sides.find(@leaf.rectoID)
+ @side.terms << @term
+ @side.save
+ @parameters = {
+ objects: [
+ {
+ id: @side.id.to_str,
+ type: "Recto"
+ }
+ ]
+ }
+ put '/terms/'+@term.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 term from the target' do
+ expect(@side.terms).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, taxonomies: ["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 '/terms/'+@term.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.terms).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 '/terms/'+@term.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.terms).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 '/terms/'+@term.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.terms).to be_empty
+ end
+ end
+ end
+ context 'and unknown target type' do
+ before do
+ @parameters[:objects][0][:type] = "unknown"
+ put '/terms/'+@term.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 term' do
+ before do
+ put '/terms/'+@term.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 '/terms/'+@term.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(Term).to receive(:save).and_raise("Exception!")
+ @group = FactoryGirl.create(:group, { project: @project })
+ @parameters = {
+ objects: [
+ {
+ id: @group.id.to_str,
+ type: "Group"
+ }
+ ]
+ }
+ put '/terms/'+@term.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 '/terms/'+@term.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 '/terms/'+@term.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 '/terms/'+@term.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 '/terms/'+@term.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/terms/terms_update_spec.rb b/viscoll-api/spec/requests/terms/terms_update_spec.rb
new file mode 100644
index 00000000..eb10e0b3
--- /dev/null
+++ b/viscoll-api/spec/requests/terms/terms_update_spec.rb
@@ -0,0 +1,168 @@
+require 'rails_helper'
+
+describe "PUT /terms/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,
+ taxonomies: ["Ink"]
+ })
+ @term = FactoryGirl.create(:term, {
+ taxonomy: "Ink",
+ project: @project,
+ description: "vermilion"
+ })
+ @parameters = {
+ term: {
+ "project_id": @project.id.to_str,
+ "title": "some title for term",
+ "taxonomy": "Ink",
+ "description": "sepia"
+ }
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'and valid term ID' do
+ before do
+ put '/terms/'+@term.id, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @term.reload
+ end
+
+ it 'returns 204' do
+ expect(response).to have_http_status(:no_content)
+ end
+
+ it 'Updates the term' do
+ expect(@term.description).to eq "sepia"
+ end
+ end
+
+ context 'and invalid term ID' do
+ before do
+ put '/terms/'+@term.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(Term).to receive(:update).and_return(false)
+ put '/terms/'+@term.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 term taxonomy' do
+ before do
+ @parameters[:term][:taxonomy] = "waahoo"
+ put '/terms/'+@term.id, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @term.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['taxonomy']).to eq 'should be one of ["Ink"]'
+ end
+
+ it 'leaves the term alone' do
+ expect(@term.description).to eq "vermilion"
+ expect(@term.taxonomy).to eq "Ink"
+ end
+ end
+
+ context "and someone else's terms" do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project2 = FactoryGirl.create(:project, {
+ user: @user2,
+ taxonomies: ["Ink"]
+ })
+ @term2 = FactoryGirl.create(:term, {
+ taxonomy: "Ink",
+ project: @project2,
+ description: "Prussian blue"
+ })
+ put '/terms/'+@term2.id, params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
+ @term2.reload
+ end
+
+ it 'returns 401' do
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'leaves the term alone' do
+ expect(@term2.description).to eq "Prussian blue"
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/terms/'+@term.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 '/terms/'+@term.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 '/terms/'+@term.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 '/terms/'+@term.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/terms/terms_update_type_spec.rb b/viscoll-api/spec/requests/terms/terms_update_type_spec.rb
new file mode 100644
index 00000000..d14de4db
--- /dev/null
+++ b/viscoll-api/spec/requests/terms/terms_update_type_spec.rb
@@ -0,0 +1,190 @@
+require 'rails_helper'
+
+describe "PUT /terms/taxonomy", :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,
+ taxonomies: ["Ink", "Paper"]
+ })
+ @project.terms << FactoryGirl.create(:term, {
+ project_id: @project.id,
+ taxonomy: "Ink",
+ description: "Sepia"
+ })
+ @project.terms << FactoryGirl.create(:term, {
+ project_id: @project.id,
+ taxonomy: "Paper",
+ description: "Parchment"
+ })
+ @project.save
+ @parameters = {
+ "taxonomy": {
+ "project_id": @project.id.to_str,
+ "taxonomy": "New Paper",
+ "old_taxonomy": "Paper"
+ }
+ }
+ end
+
+ context 'with valid authorization' do
+ context 'with valid parameters' do
+ before do
+ put '/terms/taxonomy', 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 taxonomy from the project' do
+ expect(@project.taxonomies).to include "Ink"
+ expect(@project.taxonomies).to include "New Paper"
+ expect(@project.taxonomies).not_to include "Paper"
+ end
+
+ it 'should rename terms with that taxonomy' do
+ expect(@project.terms).to include an_object_having_attributes(taxonomy: "Ink")
+ expect(@project.terms).to include an_object_having_attributes(taxonomy: "New Paper")
+ expect(@project.terms).not_to include an_object_having_attributes(taxonomy: "Paper")
+ end
+ end
+
+ context 'with missing project' do
+ before do
+ @parameters[:taxonomy][:project_id] += 'missing'
+ put '/terms/taxonomy', 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 taxonomy' do
+ before do
+ @parameters[:taxonomy][:old_taxonomy] = "Waahoo"
+ put '/terms/taxonomy', 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_taxonomy']).to eq "Waahoo taxonomy doesn't exist in the project"
+ end
+
+ it 'should leave the project alone' do
+ expect(@project.taxonomies).to eq ["Ink", "Paper"]
+ end
+ end
+
+ context 'with duplicated target taxonomy' do
+ before do
+ @parameters[:taxonomy][:taxonomy] = "Ink"
+ put '/terms/taxonomy', 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['taxonomy']).to eq "Ink already exists in the project"
+ end
+
+ it 'should leave the project alone' do
+ expect(@project.taxonomies).to eq ["Ink", "Paper"]
+ end
+ end
+
+ context 'with unauthorized project' do
+ before do
+ @user2 = FactoryGirl.create(:user)
+ @project.user = @user2
+ @project.save
+ put '/terms/taxonomy', 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 taxonomys alone' do
+ expect(@project.taxonomies).to eq ["Ink", "Paper"]
+ end
+ end
+ end
+
+ context 'with corrupted authorization' do
+ before do
+ put '/terms/taxonomy', 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 '/terms/taxonomy', 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 '/terms/taxonomy', 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 '/terms/taxonomy'
+ 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/tasks/.keep b/viscoll-api/tmp/.keep
similarity index 100%
rename from lib/tasks/.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..4408fb96
--- /dev/null
+++ b/viscoll-app/README.md
@@ -0,0 +1,52 @@
+# VCEditor (Redux Front-End)
+
+## Introduction
+
+This is the the Redux-driven user interface for VCEditor.
+
+## 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..cb1469b8
--- /dev/null
+++ b/viscoll-app/__test__/actions/frontendBeforeActions/groupActions.spec.js
@@ -0,0 +1,385 @@
+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,
+ terms: [],
+ memberIDs: ['Leaf_111'],
+ memberType: 'Group',
+ };
+ expectedState.project.Groups['Group_456456'] = {
+ id: 'Group_456456',
+ type: 'Quire',
+ title: 'None',
+ tacketed: [],
+ sewing: [],
+ nestLevel: 1,
+ parentID: null,
+ terms: [],
+ memberIDs: ['Leaf_222'],
+ memberType: 'Group',
+ };
+ expectedState.project.Leafs['Leaf_111'] = {
+ id: 'Leaf_111',
+ folio_number: null,
+ attached_above: 'None',
+ attached_below: 'None',
+ conjoined_to: null,
+ material: 'None',
+ stub: 'No',
+ type: 'None',
+ memberType: 'Leaf',
+ nestLevel: 1,
+ terms: [],
+ parentID: 'Group_123123',
+ rectoID: 'Recto_11',
+ versoID: 'Verso_22',
+ };
+ expectedState.project.Leafs['Leaf_222'] = {
+ id: 'Leaf_222',
+ folio_number: null,
+ attached_above: 'None',
+ attached_below: 'None',
+ conjoined_to: null,
+ material: 'None',
+ stub: 'No',
+ type: 'None',
+ memberType: 'Leaf',
+ nestLevel: 1,
+ terms: [],
+ parentID: 'Group_456456',
+ rectoID: 'Recto_33',
+ versoID: 'Verso_44',
+ };
+ expectedState.project.Rectos['Recto_11'] = {
+ id: 'Recto_11',
+ parentID: 'Leaf_111',
+ page_number: null,
+ script_direction: 'None',
+ texture: 'Hair',
+ image: {},
+ terms: [],
+ memberType: 'Recto',
+ };
+ expectedState.project.Rectos['Recto_33'] = {
+ id: 'Recto_33',
+ parentID: 'Leaf_222',
+ page_number: null,
+ script_direction: 'None',
+ texture: 'Hair',
+ image: {},
+ terms: [],
+ memberType: 'Recto',
+ };
+ expectedState.project.Versos['Verso_22'] = {
+ id: 'Verso_22',
+ parentID: 'Leaf_111',
+ page_number: null,
+ script_direction: 'None',
+ texture: 'Flesh',
+ image: {},
+ terms: [],
+ memberType: 'Verso',
+ };
+ expectedState.project.Versos['Verso_44'] = {
+ id: 'Verso_44',
+ parentID: 'Leaf_222',
+ page_number: null,
+ script_direction: 'None',
+ texture: 'Flesh',
+ image: {},
+ terms: [],
+ 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);
+ });
+});
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..75927fd9
--- /dev/null
+++ b/viscoll-app/__test__/actions/frontendBeforeActions/leafActions.spec.js
@@ -0,0 +1,461 @@
+import {
+ createLeaves,
+ updateLeaf,
+ updateLeaves,
+ deleteLeaf,
+ deleteLeaves,
+ autoConjoinLeafs,
+ generateFolioNumbers,
+ generatePageNumbers,
+} 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',
+ folio_number: null,
+ attached_above: 'None',
+ attached_below: 'None',
+ conjoined_to: null,
+ material: 'None',
+ stub: 'No',
+ type: 'None',
+ memberType: 'Leaf',
+ nestLevel: 1,
+ terms: [],
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_11',
+ versoID: 'Verso_22',
+ };
+ expectedState.project.Rectos['Recto_11'] = {
+ id: 'Recto_11',
+ parentID: 'Leaf_111',
+ page_number: null,
+ script_direction: 'None',
+ texture: 'Hair',
+ image: {},
+ terms: [],
+ memberType: 'Recto',
+ };
+ expectedState.project.Versos['Verso_22'] = {
+ id: 'Verso_22',
+ parentID: 'Leaf_111',
+ page_number: null,
+ script_direction: 'None',
+ texture: 'Flesh',
+ image: {},
+ terms: [],
+ 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.Terms['5a57825a4cfad13070870df9'].objects.Leaf.splice(
+ 0,
+ 1
+ );
+ expectedState.project.Terms[
+ '5a57825a4cfad13070870df9'
+ ].objects.Verso.splice(0, 1);
+ expectedState.project.Terms[
+ '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.Terms['5a57825a4cfad13070870df9'].objects.Leaf.splice(
+ 0,
+ 1
+ );
+ expectedState.project.Terms[
+ '5a57825a4cfad13070870df9'
+ ].objects.Verso.splice(0, 1);
+ expectedState.project.Terms[
+ '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 generateFolioNumbers', () => {
+ const leafPayload = {
+ payload: {
+ request: {
+ url: `/leafs/generateFolio`,
+ method: 'put',
+ data: {
+ startNumber: 3,
+ leafIDs: [
+ 'Leaf_5a57825a4cfad13070870dc4',
+ 'Leaf_5a57825a4cfad13070870dc7',
+ 'Leaf_5a57825a4cfad13070870dca',
+ ],
+ },
+ successMessage: 'Successfully generated the folio numbers',
+ errorMessage: 'Ooops! Something went wrong',
+ },
+ },
+ };
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+
+ let gotState = generateFolioNumbers(
+ leafPayload,
+ beforeState,
+ 'folio_number'
+ );
+ gotState = generateFolioNumbers(leafPayload, gotState, 'page_number');
+
+ expectedState.project.Leafs['Leaf_5a57825a4cfad13070870dc4'].folio_number =
+ '3';
+ expectedState.project.Leafs['Leaf_5a57825a4cfad13070870dc7'].folio_number =
+ '4';
+ expectedState.project.Leafs['Leaf_5a57825a4cfad13070870dca'].folio_number =
+ '5';
+
+ expect(gotState).toEqual(expectedState);
+ });
+ it('+++ actionCreator generatePageNumbers', () => {
+ const leafPayload = {
+ payload: {
+ request: {
+ url: `/sides/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 page numbers',
+ errorMessage: 'Ooops! Something went wrong',
+ },
+ },
+ };
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+
+ let gotState = generatePageNumbers(leafPayload, beforeState, 'page_number');
+ leafPayload.payload.request.data.startNumber = 6;
+ gotState = generatePageNumbers(leafPayload, gotState, 'page_number');
+
+ 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);
+ });
+});
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/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..bd3be630
--- /dev/null
+++ b/viscoll-app/__test__/actions/frontendBeforeActions/sideActions.spec.js
@@ -0,0 +1,158 @@
+import {
+ updateSide,
+ updateSides,
+ mapSides,
+} 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);
+ });
+});
diff --git a/viscoll-app/__test__/actions/frontendBeforeActions/termActions.spec.js b/viscoll-app/__test__/actions/frontendBeforeActions/termActions.spec.js
new file mode 100644
index 00000000..ac9eb59d
--- /dev/null
+++ b/viscoll-app/__test__/actions/frontendBeforeActions/termActions.spec.js
@@ -0,0 +1,272 @@
+import {
+ createTaxonomy,
+ updateTaxonomy,
+ deleteTaxonomy,
+ createTerm,
+ updateTerm,
+ linkTerm,
+ unlinkTerm,
+ deleteTerm,
+} from '../../../src/actions/frontend/before/termActions';
+
+import { projectState001 } from '../../testData/projectState001';
+
+import { cloneDeep } from 'lodash';
+
+describe('>>>A C T I O N --- Test term actions', () => {
+ it('+++ actionCreator createTaxonomy', () => {
+ const taxonomyPayload = {
+ payload: {
+ request: {
+ url: `/terms/taxonomy`,
+ method: 'post',
+ data: {
+ taxonomy: {
+ project_id: '5951303fc9bf3c7b9a573a3f',
+ taxonomy: 'Watermark',
+ },
+ },
+ successMessage: 'Successfully created the taxonomy',
+ errorMessage: 'Ooops! Something went wrong',
+ },
+ },
+ };
+ const beforeState = cloneDeep(projectState001);
+ const createTaxonomyAction = createTaxonomy(taxonomyPayload, beforeState);
+ let afterState = cloneDeep(projectState001);
+ afterState.project.Taxonomies.push('Watermark');
+ expect(createTaxonomyAction).toEqual(afterState);
+ });
+
+ it('+++ actionCreator updateTaxonomy', () => {
+ const taxonomyPayload = {
+ payload: {
+ request: {
+ url: '/terms/taxonomy',
+ method: 'put',
+ data: {
+ taxonomy: {
+ project_id: '5951303fc9bf3c7b9a573a3f',
+ old_taxonomy: 'Damage',
+ taxonomy: 'Damages',
+ },
+ },
+ successMessage: 'Successfully updated the term taxonomy',
+ errorMessage: 'Oops! Something went wrong',
+ },
+ },
+ };
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+ expectedState.project.Taxonomies[3] = 'Damages';
+ expectedState.project.Terms['5a57825a4cfad13070870dfa'].taxonomy = 'Damages';
+ let gotState = updateTaxonomy(taxonomyPayload, beforeState);
+ expect(gotState).toEqual(expectedState);
+ });
+
+ it('+++ actionCreator deleteTaxonomy', () => {
+ const taxonomyPayload = {
+ payload: {
+ request: {
+ url: '/terms/taxonomy',
+ method: 'delete',
+ data: {
+ taxonomy: {
+ project_id: '5951303fc9bf3c7b9a573a3f',
+ taxonomy: 'Hand',
+ },
+ },
+ successMessage: 'Successfully deleted the term taxonomy',
+ errorMessage: 'Oops! Something went wrong',
+ },
+ },
+ };
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(projectState001);
+ expectedState.project.Taxonomies = ['Unknown', 'Ink', 'Damage'];
+ expectedState.project.Terms['5a57825a4cfad13070870df9'].taxonomy = 'Unknown';
+ let gotState = deleteTaxonomy(taxonomyPayload, beforeState);
+ expect(gotState).toEqual(expectedState);
+ });
+
+ it('+++ actionCreator createTerm', () => {
+ const termPayload = {
+ payload: {
+ request: {
+ url: '/terms',
+ method: 'post',
+ data: {
+ term: {
+ id: 'f951303fc9bf3c7b9a573a3f',
+ project_id: '5951303fc9bf3c7b9a573a3f',
+ title: 'Example Term',
+ taxonomy: 'asd',
+ description: 'example content',
+ uri: 'https://www.test.com/',
+ show: true,
+ },
+ },
+ successMessage: '',
+ errorMessage: '',
+ },
+ },
+ };
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(beforeState);
+ expectedState.project.Terms['f951303fc9bf3c7b9a573a3f'] = {
+ id: 'f951303fc9bf3c7b9a573a3f',
+ title: 'Example Term',
+ taxonomy: 'asd',
+ description: 'example content',
+ uri: 'https://www.test.com/',
+ show: true,
+ objects: { Group: [], Leaf: [], Recto: [], Verso: [] },
+ };
+ let gotState = createTerm(termPayload, beforeState);
+ expect(gotState).toEqual(expectedState);
+ });
+
+ it('+++ actionCreator updateTerm', () => {
+ const termPayload = {
+ payload: {
+ request: {
+ url: '/terms/5a57825a4cfad13070870df8',
+ method: 'put',
+ data: {
+ term: {
+ description: 'Some lot of black ink over here',
+ title: 'Black inks',
+ taxonomy: 'Ink',
+ uri: 'https://www.test2.com/',
+ },
+ },
+ successMessage: '',
+ errorMessage: '',
+ },
+ },
+ };
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(beforeState);
+ expectedState.project.Terms['5a57825a4cfad13070870df8'].title =
+ 'Black inks';
+ expectedState.project.Terms['5a57825a4cfad13070870df8'].taxonomy = 'Ink';
+ expectedState.project.Terms['5a57825a4cfad13070870df8'].description =
+ 'Some lot of black ink over here';
+ expectedState.project.Terms['5a57825a4cfad13070870df8'].uri =
+ 'https://www.test2.com/';
+ let gotState = updateTerm(termPayload, beforeState);
+ expect(gotState).toEqual(expectedState);
+ });
+
+ it('+++ actionCreator linkTerm', () => {
+ const termPayload = {
+ payload: {
+ request: {
+ url: '/terms/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.Terms['5a57825a4cfad13070870df8'].objects.Group.push(
+ 'Group_5a57825a4cfad13070870df6'
+ );
+ expectedState.project.Terms['5a57825a4cfad13070870df8'].objects.Leaf.push(
+ 'Leaf_5a57825a4cfad13070870dee'
+ );
+ expectedState.project.Terms['5a57825a4cfad13070870df8'].objects.Verso.push(
+ 'Verso_5a57825a4cfad13070870dc6'
+ );
+ expectedState.project.Groups['Group_5a57825a4cfad13070870df6'].terms.push(
+ '5a57825a4cfad13070870df8'
+ );
+ expectedState.project.Leafs['Leaf_5a57825a4cfad13070870dee'].terms.push(
+ '5a57825a4cfad13070870df8'
+ );
+ expectedState.project.Versos['Verso_5a57825a4cfad13070870dc6'].terms.push(
+ '5a57825a4cfad13070870df8'
+ );
+ let gotState = linkTerm(termPayload, beforeState);
+ expect(gotState).toEqual(expectedState);
+ });
+
+ it('+++ actionCreator unlinkTerm', () => {
+ const termPayload = {
+ payload: {
+ request: {
+ url: '/terms/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.Terms[
+ '5a57825a4cfad13070870df8'
+ ].objects.Group.splice(-1, 1);
+ expectedState.project.Terms['5a57825a4cfad13070870df8'].objects.Leaf.splice(
+ 1,
+ 1
+ );
+ expectedState.project.Groups['Group_5a57825a4cfad13070870df5'].terms = [];
+ expectedState.project.Leafs['Leaf_5a57825a4cfad13070870de8'].terms = [];
+ let gotState = unlinkTerm(termPayload, beforeState);
+ expect(gotState).toEqual(expectedState);
+ });
+
+ it('+++ actionCreator deleteTerm', () => {
+ const termPayload = {
+ payload: {
+ request: {
+ url: '/terms/5a57825a4cfad13070870df8',
+ method: 'delete',
+ successMessage: '',
+ errorMessage: '',
+ },
+ },
+ };
+ const beforeState = cloneDeep(projectState001);
+ let expectedState = cloneDeep(beforeState);
+ delete expectedState.project.Terms['5a57825a4cfad13070870df8'];
+ expectedState.project.Groups['Group_5a57825a4cfad13070870df4'].terms = [];
+ expectedState.project.Groups['Group_5a57825a4cfad13070870df5'].terms = [];
+ expectedState.project.Leafs['Leaf_5a57825a4cfad13070870de5'].terms = [];
+ expectedState.project.Leafs['Leaf_5a57825a4cfad13070870de8'].terms = [];
+ expectedState.project.Leafs['Leaf_5a57825a4cfad13070870deb'].terms = [];
+ let gotState = deleteTerm(termPayload, beforeState);
+ expect(gotState).toEqual(expectedState);
+ });
+});
diff --git a/viscoll-app/__test__/components/ImageManager/manifestName.test.js b/viscoll-app/__test__/components/ImageManager/manifestName.test.js
new file mode 100644
index 00000000..aa76f4ad
--- /dev/null
+++ b/viscoll-app/__test__/components/ImageManager/manifestName.test.js
@@ -0,0 +1,37 @@
+import {
+ getPrimaryManifestName,
+ getTrimmedPrimaryManifestName,
+ isManifestNameUpdateValid,
+} from '../../../src/components/imageManager/manifestName';
+
+describe('manifestName helpers', () => {
+ it('returns string manifest names unchanged', () => {
+ expect(getPrimaryManifestName('Single label')).toEqual('Single label');
+ });
+
+ it('returns the first label from an array of manifest labels', () => {
+ expect(getPrimaryManifestName(['First label', 'Second label'])).toEqual('First label');
+ });
+
+ it('trims the first label before comparing or submitting manifest names', () => {
+ expect(getTrimmedPrimaryManifestName([' First label ', 'Second label'])).toEqual('First label');
+ });
+
+ it('falls back to an empty string for missing or non-string names', () => {
+ expect(getPrimaryManifestName()).toEqual('');
+ expect(getPrimaryManifestName([{value: 'First label'}])).toEqual('');
+ });
+
+ it('does not allow submit when the first label is unchanged', () => {
+ expect(isManifestNameUpdateValid('', ['First label', 'Second label'], ['First label', 'Second label'])).toEqual(false);
+ });
+
+ it('allows submit when the first label changes', () => {
+ expect(isManifestNameUpdateValid('', 'Updated label', ['First label', 'Second label'])).toEqual(true);
+ });
+
+ it('does not allow submit with a validation error or an empty first label', () => {
+ expect(isManifestNameUpdateValid('Manifest name already exists.', 'Updated label', 'First label')).toEqual(false);
+ expect(isManifestNameUpdateValid('', ['', 'Second label'], 'First label')).toEqual(false);
+ });
+});
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..fbbe4f8c
--- /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",
+ terms: []
+}
+
+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",
+ terms: []
+}
+
+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",
+ terms: []
+}
+
+
+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",
+ terms: []
+}
+
+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",
+ terms: []
+}
+
+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",
+ terms: []
+}
+
+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",
+ terms: []
+}
+
+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",
+ terms: []
+}
+
+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",
+ terms: []
+}
+
+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",
+ terms: []
+}
+
+
+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: "No",
+ terms: [],
+ 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: "No",
+ terms: [],
+ 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: "No",
+ terms: [],
+ 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: "No",
+ terms: [],
+ 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: "No",
+ terms: [],
+ sides: [
+ side0_leaf5,
+ side1_leaf5
+ ]
+}
+
+export const group1 = {
+ id: "group1_id",
+ member_type: "Group",
+ member_order: 1,
+ order: 1,
+ title: "Default",
+ type: "Quire",
+ terms: [],
+ members: [
+ leaf1,
+ leaf2
+ ]
+}
+
+
+export const group4 = {
+ id: "group4_id",
+ member_type: "Group",
+ member_order: 2,
+ order: 4,
+ title: "Default",
+ type: "Quire",
+ terms: [],
+ members: [
+ leaf4
+ ]
+}
+
+export const group3 = {
+ id: "group3_id",
+ member_type: "Group",
+ member_order: 1,
+ order: 3,
+ title: "Default",
+ type: "Quire",
+ terms: [],
+ members: [
+ leaf3,
+ group4
+ ]
+}
+
+export const group2 = {
+ id: "group2_id",
+ member_type: "Group",
+ member_order: 2,
+ order: 2,
+ title: "Default",
+ type: "Quire",
+ terms: [],
+ 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..56c0b536
--- /dev/null
+++ b/viscoll-app/__test__/testData/projectState001.js
@@ -0,0 +1,5421 @@
+export const projectState001 = {
+ project: {
+ id: '5a57825a4cfad13070870dc3',
+ title: 'my prject',
+ shelfmark: 'mss 568',
+ metadata: {
+ date: '19th century',
+ },
+ preferences: {
+ showTips: true,
+ },
+ Taxonomies: ['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,
+ terms: ['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,
+ terms: ['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',
+ terms: [],
+ 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',
+ terms: [],
+ 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: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dc5',
+ versoID: 'Verso_5a57825a4cfad13070870dc6',
+ terms: ['5a57825a4cfad13070870df9'],
+ memberType: 'Leaf',
+ },
+ Leaf_5a57825a4cfad13070870dc7: {
+ id: 'Leaf_5a57825a4cfad13070870dc7',
+ material: 'None',
+ type: 'Missing',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dd0',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dc8',
+ versoID: 'Verso_5a57825a4cfad13070870dc9',
+ terms: [],
+ memberType: 'Leaf',
+ },
+ Leaf_5a57825a4cfad13070870dca: {
+ id: 'Leaf_5a57825a4cfad13070870dca',
+ material: 'Parchment',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dcd',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dcb',
+ versoID: 'Verso_5a57825a4cfad13070870dcc',
+ terms: ['5a57825a4cfad13070870dfa'],
+ memberType: 'Leaf',
+ },
+ Leaf_5a57825a4cfad13070870dcd: {
+ id: 'Leaf_5a57825a4cfad13070870dcd',
+ material: 'Parchment',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dca',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dce',
+ versoID: 'Verso_5a57825a4cfad13070870dcf',
+ terms: ['5a57825a4cfad13070870dfa'],
+ memberType: 'Leaf',
+ },
+ Leaf_5a57825a4cfad13070870dd0: {
+ id: 'Leaf_5a57825a4cfad13070870dd0',
+ material: 'Parchment',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dc7',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dd1',
+ versoID: 'Verso_5a57825a4cfad13070870dd2',
+ terms: ['5a57825a4cfad13070870dfa'],
+ memberType: 'Leaf',
+ },
+ Leaf_5a57825a4cfad13070870dd3: {
+ id: 'Leaf_5a57825a4cfad13070870dd3',
+ material: 'None',
+ type: 'Endleaf',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dc4',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dd4',
+ versoID: 'Verso_5a57825a4cfad13070870dd5',
+ terms: [],
+ memberType: 'Leaf',
+ },
+ Leaf_5a57825a4cfad13070870dd6: {
+ id: 'Leaf_5a57825a4cfad13070870dd6',
+ material: 'None',
+ type: 'None',
+ conjoined_to: null,
+ attached_above: 'None',
+ attached_below: 'Glued (Partial)',
+ stub: 'No',
+ nestLevel: 3,
+ parentID: 'Group_5a57825a4cfad13070870df7',
+ rectoID: 'Recto_5a57825a4cfad13070870dd7',
+ versoID: 'Verso_5a57825a4cfad13070870dd8',
+ terms: [],
+ memberType: 'Leaf',
+ },
+ Leaf_5a57825a4cfad13070870dd9: {
+ id: 'Leaf_5a57825a4cfad13070870dd9',
+ material: 'None',
+ type: 'None',
+ conjoined_to: null,
+ attached_above: 'Glued (Partial)',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 3,
+ parentID: 'Group_5a57825a4cfad13070870df7',
+ rectoID: 'Recto_5a57825a4cfad13070870dda',
+ versoID: 'Verso_5a57825a4cfad13070870ddb',
+ terms: [],
+ memberType: 'Leaf',
+ },
+ Leaf_5a57825a4cfad13070870ddc: {
+ id: 'Leaf_5a57825a4cfad13070870ddc',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870ddf',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 2,
+ parentID: 'Group_5a57825a4cfad13070870df6',
+ rectoID: 'Recto_5a57825a4cfad13070870ddd',
+ versoID: 'Verso_5a57825a4cfad13070870dde',
+ terms: [],
+ memberType: 'Leaf',
+ },
+ Leaf_5a57825a4cfad13070870ddf: {
+ id: 'Leaf_5a57825a4cfad13070870ddf',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870ddc',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 2,
+ parentID: 'Group_5a57825a4cfad13070870df6',
+ rectoID: 'Recto_5a57825a4cfad13070870de0',
+ versoID: 'Verso_5a57825a4cfad13070870de1',
+ terms: [],
+ memberType: 'Leaf',
+ },
+ Leaf_5a57825a4cfad13070870de2: {
+ id: 'Leaf_5a57825a4cfad13070870de2',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870df1',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870de3',
+ versoID: 'Verso_5a57825a4cfad13070870de4',
+ terms: ['5a57825a4cfad13070870df9'],
+ memberType: 'Leaf',
+ },
+ Leaf_5a57825a4cfad13070870de5: {
+ id: 'Leaf_5a57825a4cfad13070870de5',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dee',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870de6',
+ versoID: 'Verso_5a57825a4cfad13070870de7',
+ terms: ['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',
+ terms: ['5a57825a4cfad13070870df8'],
+ memberType: 'Leaf',
+ },
+ Leaf_5a57825a4cfad13070870deb: {
+ id: 'Leaf_5a57825a4cfad13070870deb',
+ material: 'None',
+ type: 'None',
+ conjoined_to: null,
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870dec',
+ versoID: 'Verso_5a57825a4cfad13070870ded',
+ terms: ['5a57825a4cfad13070870df8'],
+ memberType: 'Leaf',
+ },
+ Leaf_5a57825a4cfad13070870dee: {
+ id: 'Leaf_5a57825a4cfad13070870dee',
+ material: 'None',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870de5',
+ attached_above: 'None',
+ attached_below: 'Glued (Complete)',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870def',
+ versoID: 'Verso_5a57825a4cfad13070870df0',
+ terms: [],
+ memberType: 'Leaf',
+ },
+ Leaf_5a57825a4cfad13070870df1: {
+ id: 'Leaf_5a57825a4cfad13070870df1',
+ material: 'None',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870de2',
+ attached_above: 'Glued (Complete)',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870df2',
+ versoID: 'Verso_5a57825a4cfad13070870df3',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: ['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',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: [],
+ memberType: 'Recto',
+ },
+ Recto_5a57825a4cfad13070870de0: {
+ id: 'Recto_5a57825a4cfad13070870de0',
+ parentID: 'Leaf_5a57825a4cfad13070870ddf',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Recto',
+ },
+ Recto_5a57825a4cfad13070870de3: {
+ id: 'Recto_5a57825a4cfad13070870de3',
+ parentID: 'Leaf_5a57825a4cfad13070870de2',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Recto',
+ },
+ Recto_5a57825a4cfad13070870de6: {
+ id: 'Recto_5a57825a4cfad13070870de6',
+ parentID: 'Leaf_5a57825a4cfad13070870de5',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Recto',
+ },
+ Recto_5a57825a4cfad13070870de9: {
+ id: 'Recto_5a57825a4cfad13070870de9',
+ parentID: 'Leaf_5a57825a4cfad13070870de8',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Recto',
+ },
+ Recto_5a57825a4cfad13070870dec: {
+ id: 'Recto_5a57825a4cfad13070870dec',
+ parentID: 'Leaf_5a57825a4cfad13070870deb',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Recto',
+ },
+ Recto_5a57825a4cfad13070870def: {
+ id: 'Recto_5a57825a4cfad13070870def',
+ parentID: 'Leaf_5a57825a4cfad13070870dee',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Recto',
+ },
+ Recto_5a57825a4cfad13070870df2: {
+ id: 'Recto_5a57825a4cfad13070870df2',
+ parentID: 'Leaf_5a57825a4cfad13070870df1',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ 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',
+ terms: ['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',
+ terms: ['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',
+ terms: [],
+ memberType: 'Verso',
+ },
+ Verso_5a57825a4cfad13070870dcf: {
+ id: 'Verso_5a57825a4cfad13070870dcf',
+ parentID: 'Leaf_5a57825a4cfad13070870dcd',
+ folio_number: 'custom XV',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso',
+ },
+ Verso_5a57825a4cfad13070870dd2: {
+ id: 'Verso_5a57825a4cfad13070870dd2',
+ parentID: 'Leaf_5a57825a4cfad13070870dd0',
+ folio_number: 'custom YV',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso',
+ },
+ Verso_5a57825a4cfad13070870dd5: {
+ id: 'Verso_5a57825a4cfad13070870dd5',
+ parentID: 'Leaf_5a57825a4cfad13070870dd3',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso',
+ },
+ Verso_5a57825a4cfad13070870dd8: {
+ id: 'Verso_5a57825a4cfad13070870dd8',
+ parentID: 'Leaf_5a57825a4cfad13070870dd6',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso',
+ },
+ Verso_5a57825a4cfad13070870ddb: {
+ id: 'Verso_5a57825a4cfad13070870ddb',
+ parentID: 'Leaf_5a57825a4cfad13070870dd9',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso',
+ },
+ Verso_5a57825a4cfad13070870dde: {
+ id: 'Verso_5a57825a4cfad13070870dde',
+ parentID: 'Leaf_5a57825a4cfad13070870ddc',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso',
+ },
+ Verso_5a57825a4cfad13070870de1: {
+ id: 'Verso_5a57825a4cfad13070870de1',
+ parentID: 'Leaf_5a57825a4cfad13070870ddf',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso',
+ },
+ Verso_5a57825a4cfad13070870de4: {
+ id: 'Verso_5a57825a4cfad13070870de4',
+ parentID: 'Leaf_5a57825a4cfad13070870de2',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso',
+ },
+ Verso_5a57825a4cfad13070870de7: {
+ id: 'Verso_5a57825a4cfad13070870de7',
+ parentID: 'Leaf_5a57825a4cfad13070870de5',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso',
+ },
+ Verso_5a57825a4cfad13070870dea: {
+ id: 'Verso_5a57825a4cfad13070870dea',
+ parentID: 'Leaf_5a57825a4cfad13070870de8',
+ folio_number: null,
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: [],
+ memberType: 'Verso',
+ },
+ },
+ Terms: {
+ '5a57825a4cfad13070870df8': {
+ id: '5a57825a4cfad13070870df8',
+ title: 'Black ink',
+ taxonomy: 'Ink',
+ description: 'Some black ink over here\n',
+ uri: 'https://www.test.com/',
+ show: true,
+ objects: {
+ Group: [
+ 'Group_5a57825a4cfad13070870df4',
+ 'Group_5a57825a4cfad13070870df5',
+ ],
+ Leaf: [
+ 'Leaf_5a57825a4cfad13070870de5',
+ 'Leaf_5a57825a4cfad13070870de8',
+ 'Leaf_5a57825a4cfad13070870deb',
+ ],
+ Recto: [],
+ Verso: [],
+ },
+ },
+ '5a57825a4cfad13070870df9': {
+ id: '5a57825a4cfad13070870df9',
+ title: "John's hand",
+ taxonomy: 'Hand',
+ description: 'Look ! ',
+ uri: 'https://www.test.com/',
+ show: false,
+ objects: {
+ Group: [],
+ Leaf: [
+ 'Leaf_5a57825a4cfad13070870dc4',
+ 'Leaf_5a57825a4cfad13070870de2',
+ ],
+ Recto: [],
+ Verso: ['Verso_5a57825a4cfad13070870dc6'],
+ },
+ },
+ '5a57825a4cfad13070870dfa': {
+ id: '5a57825a4cfad13070870dfa',
+ title: 'Fire',
+ taxonomy: 'Damage',
+ description: 'Some burnt marks',
+ uri: 'https://www.test.com/',
+ 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: 'folio_number',
+ displayName: 'Folio Number',
+ },
+ {
+ 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: 'script_direction',
+ displayName: 'Script Direction',
+ options: ['None', 'Left-to-Right', 'Right-To-Left', 'Top-To-Bottom'],
+ isDropdown: true,
+ },
+ {
+ name: 'uri',
+ displayName: 'URI',
+ },
+ ],
+ term: [
+ {
+ name: 'title',
+ displayName: 'Title',
+ },
+ {
+ name: 'taxonomy',
+ displayName: 'Taxonomy',
+ isDropdown: true,
+ },
+ {
+ name: 'description',
+ displayName: 'Description',
+ },
+ {
+ name: 'uri',
+ displayName: 'URI',
+ },
+ ],
+ },
+ filters: {
+ filterPanelOpen: false,
+ Groups: [],
+ Leafs: [],
+ Sides: [],
+ Terms: [],
+ GroupsOfMatchingLeafs: [],
+ LeafsOfMatchingSides: [],
+ GroupsOfMatchingSides: [],
+ GroupsOfMatchingTerms: [],
+ LeafsOfMatchingTerms: [],
+ SidesOfMatchingTerms: [],
+ active: false,
+ hideOthers: false,
+ queries: [
+ {
+ type: null,
+ attribute: '',
+ attributeIndex: '',
+ values: [],
+ condition: '',
+ conjunction: '',
+ },
+ ],
+ selection: '',
+ },
+ flashItems: {
+ leaves: [],
+ groups: [],
+ },
+ visualizations: {
+ tacketed: '',
+ sewing: '',
+ },
+ },
+ termsManager: {
+ activeTab: 'MANAGE',
+ },
+ imageManager: {
+ activeTab: 'MANAGE',
+ manageSources: {
+ error: '',
+ },
+ },
+};
diff --git a/viscoll-app/__test__/testData/state001.js b/viscoll-app/__test__/testData/state001.js
new file mode 100644
index 00000000..ce39f7df
--- /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
+ },
+ Taxonomies: [
+ '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,
+ terms: [
+ '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,
+ terms: [
+ '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',
+ terms: [],
+ 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',
+ terms: [],
+ 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: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dc5',
+ versoID: 'Verso_5a57825a4cfad13070870dc6',
+ terms: [
+ '5a57825a4cfad13070870df9'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dc7: {
+ id: 'Leaf_5a57825a4cfad13070870dc7',
+ material: 'None',
+ type: 'Missing',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dd0',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dc8',
+ versoID: 'Verso_5a57825a4cfad13070870dc9',
+ terms: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dca: {
+ id: 'Leaf_5a57825a4cfad13070870dca',
+ material: 'Parchment',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dcd',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dcb',
+ versoID: 'Verso_5a57825a4cfad13070870dcc',
+ terms: [
+ '5a57825a4cfad13070870dfa'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dcd: {
+ id: 'Leaf_5a57825a4cfad13070870dcd',
+ material: 'Parchment',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dca',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dce',
+ versoID: 'Verso_5a57825a4cfad13070870dcf',
+ terms: [
+ '5a57825a4cfad13070870dfa'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dd0: {
+ id: 'Leaf_5a57825a4cfad13070870dd0',
+ material: 'Parchment',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dc7',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dd1',
+ versoID: 'Verso_5a57825a4cfad13070870dd2',
+ terms: [
+ '5a57825a4cfad13070870dfa'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dd3: {
+ id: 'Leaf_5a57825a4cfad13070870dd3',
+ material: 'None',
+ type: 'Endleaf',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dc4',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df4',
+ rectoID: 'Recto_5a57825a4cfad13070870dd4',
+ versoID: 'Verso_5a57825a4cfad13070870dd5',
+ terms: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dd6: {
+ id: 'Leaf_5a57825a4cfad13070870dd6',
+ material: 'None',
+ type: 'None',
+ conjoined_to: null,
+ attached_above: 'None',
+ attached_below: 'Glued (Partial)',
+ stub: 'No',
+ nestLevel: 3,
+ parentID: 'Group_5a57825a4cfad13070870df7',
+ rectoID: 'Recto_5a57825a4cfad13070870dd7',
+ versoID: 'Verso_5a57825a4cfad13070870dd8',
+ terms: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dd9: {
+ id: 'Leaf_5a57825a4cfad13070870dd9',
+ material: 'None',
+ type: 'None',
+ conjoined_to: null,
+ attached_above: 'Glued (Partial)',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 3,
+ parentID: 'Group_5a57825a4cfad13070870df7',
+ rectoID: 'Recto_5a57825a4cfad13070870dda',
+ versoID: 'Verso_5a57825a4cfad13070870ddb',
+ terms: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870ddc: {
+ id: 'Leaf_5a57825a4cfad13070870ddc',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870ddf',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 2,
+ parentID: 'Group_5a57825a4cfad13070870df6',
+ rectoID: 'Recto_5a57825a4cfad13070870ddd',
+ versoID: 'Verso_5a57825a4cfad13070870dde',
+ terms: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870ddf: {
+ id: 'Leaf_5a57825a4cfad13070870ddf',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870ddc',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 2,
+ parentID: 'Group_5a57825a4cfad13070870df6',
+ rectoID: 'Recto_5a57825a4cfad13070870de0',
+ versoID: 'Verso_5a57825a4cfad13070870de1',
+ terms: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870de2: {
+ id: 'Leaf_5a57825a4cfad13070870de2',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870df1',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870de3',
+ versoID: 'Verso_5a57825a4cfad13070870de4',
+ terms: [
+ '5a57825a4cfad13070870df9'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870de5: {
+ id: 'Leaf_5a57825a4cfad13070870de5',
+ material: 'Other',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870dee',
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870de6',
+ versoID: 'Verso_5a57825a4cfad13070870de7',
+ terms: [
+ '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',
+ terms: [
+ '5a57825a4cfad13070870df8'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870deb: {
+ id: 'Leaf_5a57825a4cfad13070870deb',
+ material: 'None',
+ type: 'None',
+ conjoined_to: null,
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870dec',
+ versoID: 'Verso_5a57825a4cfad13070870ded',
+ terms: [
+ '5a57825a4cfad13070870df8'
+ ],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870dee: {
+ id: 'Leaf_5a57825a4cfad13070870dee',
+ material: 'None',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870de5',
+ attached_above: 'None',
+ attached_below: 'Glued (Complete)',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870def',
+ versoID: 'Verso_5a57825a4cfad13070870df0',
+ terms: [],
+ memberType: 'Leaf'
+ },
+ Leaf_5a57825a4cfad13070870df1: {
+ id: 'Leaf_5a57825a4cfad13070870df1',
+ material: 'None',
+ type: 'None',
+ conjoined_to: 'Leaf_5a57825a4cfad13070870de2',
+ attached_above: 'Glued (Complete)',
+ attached_below: 'None',
+ stub: 'No',
+ nestLevel: 1,
+ parentID: 'Group_5a57825a4cfad13070870df5',
+ rectoID: 'Recto_5a57825a4cfad13070870df2',
+ versoID: 'Verso_5a57825a4cfad13070870df3',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: [
+ '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',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870de0: {
+ id: 'Recto_5a57825a4cfad13070870de0',
+ parentID: 'Leaf_5a57825a4cfad13070870ddf',
+ folio_number: '10R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870de3: {
+ id: 'Recto_5a57825a4cfad13070870de3',
+ parentID: 'Leaf_5a57825a4cfad13070870de2',
+ folio_number: '11R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870de6: {
+ id: 'Recto_5a57825a4cfad13070870de6',
+ parentID: 'Leaf_5a57825a4cfad13070870de5',
+ folio_number: '12R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870de9: {
+ id: 'Recto_5a57825a4cfad13070870de9',
+ parentID: 'Leaf_5a57825a4cfad13070870de8',
+ folio_number: '13R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870dec: {
+ id: 'Recto_5a57825a4cfad13070870dec',
+ parentID: 'Leaf_5a57825a4cfad13070870deb',
+ folio_number: '14R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870def: {
+ id: 'Recto_5a57825a4cfad13070870def',
+ parentID: 'Leaf_5a57825a4cfad13070870dee',
+ folio_number: '15R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Recto'
+ },
+ Recto_5a57825a4cfad13070870df2: {
+ id: 'Recto_5a57825a4cfad13070870df2',
+ parentID: 'Leaf_5a57825a4cfad13070870df1',
+ folio_number: '16R',
+ generated_folio_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ 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',
+ terms: [
+ '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',
+ terms: [
+ '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',
+ terms: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dcf: {
+ id: 'Verso_5a57825a4cfad13070870dcf',
+ parentID: 'Leaf_5a57825a4cfad13070870dcd',
+ folio_number: '4V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dd2: {
+ id: 'Verso_5a57825a4cfad13070870dd2',
+ parentID: 'Leaf_5a57825a4cfad13070870dd0',
+ folio_number: '5V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dd5: {
+ id: 'Verso_5a57825a4cfad13070870dd5',
+ parentID: 'Leaf_5a57825a4cfad13070870dd3',
+ folio_number: '6V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dd8: {
+ id: 'Verso_5a57825a4cfad13070870dd8',
+ parentID: 'Leaf_5a57825a4cfad13070870dd6',
+ folio_number: '7V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870ddb: {
+ id: 'Verso_5a57825a4cfad13070870ddb',
+ parentID: 'Leaf_5a57825a4cfad13070870dd9',
+ folio_number: '8V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dde: {
+ id: 'Verso_5a57825a4cfad13070870dde',
+ parentID: 'Leaf_5a57825a4cfad13070870ddc',
+ folio_number: '9V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870de1: {
+ id: 'Verso_5a57825a4cfad13070870de1',
+ parentID: 'Leaf_5a57825a4cfad13070870ddf',
+ folio_number: '10V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870de4: {
+ id: 'Verso_5a57825a4cfad13070870de4',
+ parentID: 'Leaf_5a57825a4cfad13070870de2',
+ folio_number: '11V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870de7: {
+ id: 'Verso_5a57825a4cfad13070870de7',
+ parentID: 'Leaf_5a57825a4cfad13070870de5',
+ folio_number: '12V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ memberType: 'Verso'
+ },
+ Verso_5a57825a4cfad13070870dea: {
+ id: 'Verso_5a57825a4cfad13070870dea',
+ parentID: 'Leaf_5a57825a4cfad13070870de8',
+ folio_number: '13V',
+ generated_folio_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: [],
+ 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',
+ terms: [],
+ memberType: 'Verso'
+ }
+ },
+ Terms: {
+ '5a57825a4cfad13070870df8': {
+ id: '5a57825a4cfad13070870df8',
+ title: 'Black ink',
+ taxonomy: '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',
+ taxonomy: 'Hand',
+ description: 'Look ! ',
+ show: false,
+ objects: {
+ Group: [],
+ Leaf: [
+ 'Leaf_5a57825a4cfad13070870dc4',
+ 'Leaf_5a57825a4cfad13070870de2'
+ ],
+ Recto: [],
+ Verso: [
+ 'Verso_5a57825a4cfad13070870dc6'
+ ]
+ }
+ },
+ '5a57825a4cfad13070870dfa': {
+ id: '5a57825a4cfad13070870dfa',
+ title: 'Fire',
+ taxonomy: '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'
+ }
+ ],
+ term: [
+ {
+ name: 'title',
+ displayName: 'Title'
+ },
+ {
+ name: 'taxonomy',
+ displayName: 'Taxonomy',
+ isDropdown: true
+ },
+ {
+ name: 'description',
+ displayName: 'Description'
+ }
+ ]
+ },
+ filters: {
+ filterPanelOpen: false,
+ Groups: [],
+ Leafs: [],
+ Sides: [],
+ Terms: [],
+ GroupsOfMatchingLeafs: [],
+ LeafsOfMatchingSides: [],
+ GroupsOfMatchingSides: [],
+ GroupsOfMatchingTerms: [],
+ LeafsOfMatchingTerms: [],
+ SidesOfMatchingTerms: [],
+ active: false,
+ hideOthers: false,
+ queries: [
+ {
+ type: null,
+ attribute: '',
+ attributeIndex: '',
+ values: [],
+ condition: '',
+ conjunction: ''
+ }
+ ],
+ selection: ''
+ },
+ flashItems: {
+ leaves: [],
+ groups: []
+ },
+ visualizations: {
+ tacketed: '',
+ sewing: ''
+ }
+ },
+ termManager: {
+ 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/config-overrides.js b/viscoll-app/config-overrides.js
new file mode 100644
index 00000000..da928b84
--- /dev/null
+++ b/viscoll-app/config-overrides.js
@@ -0,0 +1,10 @@
+module.exports = {
+ jest(config) {
+ config.roots = [''];
+ config.testMatch = [
+ '**/__tests__/**/*.{js,jsx,ts,tsx}',
+ '**/*.{spec,test}.{js,jsx,ts,tsx}'
+ ];
+ return config;
+ }
+};
diff --git a/viscoll-app/generate-build-version.js b/viscoll-app/generate-build-version.js
new file mode 100644
index 00000000..6b14726e
--- /dev/null
+++ b/viscoll-app/generate-build-version.js
@@ -0,0 +1,21 @@
+/* generate-build-version.js */
+
+const fs = require('fs');
+const packageJson = require('./package.json');
+
+const appVersion = packageJson.version;
+
+const jsonData = {
+ version: appVersion
+};
+
+var jsonContent = JSON.stringify(jsonData);
+
+fs.writeFile('./public/meta.json', jsonContent, 'utf8', function(err) {
+ if (err) {
+ console.log('An error occured while writing JSON Object to meta.json');
+ return console.log(err);
+ }
+
+ console.log('meta.json file has been saved with latest version number');
+});
diff --git a/viscoll-app/jest.config.js b/viscoll-app/jest.config.js
new file mode 100644
index 00000000..f69d20c8
--- /dev/null
+++ b/viscoll-app/jest.config.js
@@ -0,0 +1,198 @@
+/**
+ * For a detailed explanation regarding each configuration property, visit:
+ * https://jestjs.io/docs/configuration
+ */
+
+/** @type {import('jest').Config} */
+const config = {
+ // All imported modules in your tests should be mocked automatically
+ // automock: false,
+
+ // Stop running tests after `n` failures
+ // bail: 0,
+
+ // The directory where Jest should store its cached dependency information
+ // cacheDirectory: "/private/var/folders/w_/tzz8p85d3sb8jxv31_tkw7t40000gs/T/jest_e1",
+
+ // Automatically clear mock calls, instances, contexts and results before every test
+ clearMocks: true,
+
+ // Indicates whether the coverage information should be collected while executing the test
+ collectCoverage: true,
+
+ // An array of glob patterns indicating a set of files for which coverage information should be collected
+ // collectCoverageFrom: undefined,
+
+ // The directory where Jest should output its coverage files
+ coverageDirectory: "coverage",
+
+ // An array of regexp pattern strings used to skip coverage collection
+ // coveragePathIgnorePatterns: [
+ // "/node_modules/"
+ // ],
+
+ // Indicates which provider should be used to instrument code for coverage
+ coverageProvider: "v8",
+
+ // A list of reporter names that Jest uses when writing coverage reports
+ // coverageReporters: [
+ // "json",
+ // "text",
+ // "lcov",
+ // "clover"
+ // ],
+
+ // An object that configures minimum threshold enforcement for coverage results
+ // coverageThreshold: undefined,
+
+ // A path to a custom dependency extractor
+ // dependencyExtractor: undefined,
+
+ // Make calling deprecated APIs throw helpful error messages
+ // errorOnDeprecated: false,
+
+ // The default configuration for fake timers
+ // fakeTimers: {
+ // "enableGlobally": false
+ // },
+
+ // Force coverage collection from ignored files using an array of glob patterns
+ // forceCoverageMatch: [],
+
+ // A path to a module which exports an async function that is triggered once before all test suites
+ // globalSetup: undefined,
+
+ // A path to a module which exports an async function that is triggered once after all test suites
+ // globalTeardown: undefined,
+
+ // A set of global variables that need to be available in all test environments
+ // globals: {},
+
+ // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
+ // maxWorkers: "50%",
+
+ // An array of directory names to be searched recursively up from the requiring module's location
+ // moduleDirectories: [
+ // "node_modules"
+ // ],
+
+ // An array of file extensions your modules use
+ // moduleFileExtensions: [
+ // "js",
+ // "mjs",
+ // "cjs",
+ // "jsx",
+ // "ts",
+ // "tsx",
+ // "json",
+ // "node"
+ // ],
+
+ // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
+ // moduleNameMapper: {},
+
+ // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
+ // modulePathIgnorePatterns: [],
+
+ // Activates notifications for test results
+ // notify: false,
+
+ // An enum that specifies notification mode. Requires { notify: true }
+ // notifyMode: "failure-change",
+
+ // A preset that is used as a base for Jest's configuration
+ // preset: undefined,
+
+ // Run tests from one or more projects
+ // projects: undefined,
+
+ // Use this configuration option to add custom reporters to Jest
+ // reporters: undefined,
+
+ // Automatically reset mock state before every test
+ // resetMocks: false,
+
+ // Reset the module registry before running each individual test
+ // resetModules: false,
+
+ // A path to a custom resolver
+ // resolver: undefined,
+
+ // Automatically restore mock state and implementation before every test
+ // restoreMocks: false,
+
+ // The root directory that Jest should scan for tests and modules within
+ // rootDir: undefined,
+
+ // A list of paths to directories that Jest should use to search for files in
+ // roots: [
+ // ""
+ // ],
+
+ // Allows you to use a custom runner instead of Jest's default test runner
+ // runner: "jest-runner",
+
+ // The paths to modules that run some code to configure or set up the testing environment before each test
+ // setupFiles: [],
+
+ // A list of paths to modules that run some code to configure or set up the testing framework before each test
+ // setupFilesAfterEnv: [],
+
+ // The number of seconds after which a test is considered as slow and reported as such in the results.
+ // slowTestThreshold: 5,
+
+ // A list of paths to snapshot serializer modules Jest should use for snapshot testing
+ // snapshotSerializers: [],
+
+ // The test environment that will be used for testing
+ // testEnvironment: "jest-environment-node",
+
+ // Options that will be passed to the testEnvironment
+ // testEnvironmentOptions: {},
+
+ // Adds a location field to test results
+ // testLocationInResults: false,
+
+ // The glob patterns Jest uses to detect test files
+ // testMatch: [
+ // "**/__tests__/**/*.[jt]s?(x)",
+ // "**/?(*.)+(spec|test).[tj]s?(x)"
+ // ],
+
+ // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
+ // testPathIgnorePatterns: [
+ // "/node_modules/"
+ // ],
+
+ // The regexp pattern or array of patterns that Jest uses to detect test files
+ // testRegex: [],
+
+ // This option allows the use of a custom results processor
+ // testResultsProcessor: undefined,
+
+ // This option allows use of a custom test runner
+ // testRunner: "jest-circus/runner",
+
+ // A map from regular expressions to paths to transformers
+ // transform: undefined,
+
+ // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
+ // transformIgnorePatterns: [
+ // "/node_modules/",
+ // "\\.pnp\\.[^\\/]+$"
+ // ],
+
+ // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
+ // unmockedModulePathPatterns: undefined,
+
+ // Indicates whether each individual test should be reported during the run
+ // verbose: undefined,
+
+ // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
+ // watchPathIgnorePatterns: [],
+
+ // Whether to use watchman for file crawling
+ // watchman: true,
+};
+
+module.exports = config;
diff --git a/viscoll-app/package-lock.json b/viscoll-app/package-lock.json
new file mode 100644
index 00000000..44d7910b
--- /dev/null
+++ b/viscoll-app/package-lock.json
@@ -0,0 +1,18358 @@
+{
+ "name": "viscoll-app",
+ "version": "0.14.9",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
+ "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.10.4"
+ }
+ },
+ "@babel/compat-data": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz",
+ "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.12.0",
+ "invariant": "^2.2.4",
+ "semver": "^5.5.0"
+ }
+ },
+ "@babel/core": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz",
+ "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.8.3",
+ "@babel/generator": "^7.9.0",
+ "@babel/helper-module-transforms": "^7.9.0",
+ "@babel/helpers": "^7.9.0",
+ "@babel/parser": "^7.9.0",
+ "@babel/template": "^7.8.6",
+ "@babel/traverse": "^7.9.0",
+ "@babel/types": "^7.9.0",
+ "convert-source-map": "^1.7.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.1",
+ "json5": "^2.1.2",
+ "lodash": "^4.17.13",
+ "resolve": "^1.3.2",
+ "semver": "^5.4.1",
+ "source-map": "^0.5.0"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.11.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.5.tgz",
+ "integrity": "sha512-9UqHWJ4IwRTy4l0o8gq2ef8ws8UPzvtMkVKjTLAiRmza9p9V6Z+OfuNd9fB1j5Q67F+dVJtPC2sZXI8NM9br4g==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.11.5",
+ "jsesc": "^2.5.1",
+ "source-map": "^0.6.1"
+ },
+ "dependencies": {
+ "jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "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
+ }
+ }
+ },
+ "@babel/helper-annotate-as-pure": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz",
+ "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-builder-binary-assignment-operator-visitor": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz",
+ "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-explode-assignable-expression": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-builder-react-jsx": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz",
+ "integrity": "sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-builder-react-jsx-experimental": {
+ "version": "7.11.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.11.5.tgz",
+ "integrity": "sha512-Vc4aPJnRZKWfzeCBsqTBnzulVNjABVdahSPhtdMD3Vs80ykx4a87jTHtF/VR+alSrDmNvat7l13yrRHauGcHVw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.10.4",
+ "@babel/helper-module-imports": "^7.10.4",
+ "@babel/types": "^7.11.5"
+ }
+ },
+ "@babel/helper-compilation-targets": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz",
+ "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.10.4",
+ "browserslist": "^4.12.0",
+ "invariant": "^2.2.4",
+ "levenary": "^1.1.1",
+ "semver": "^5.5.0"
+ }
+ },
+ "@babel/helper-create-class-features-plugin": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz",
+ "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.10.4",
+ "@babel/helper-member-expression-to-functions": "^7.10.5",
+ "@babel/helper-optimise-call-expression": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-replace-supers": "^7.10.4",
+ "@babel/helper-split-export-declaration": "^7.10.4"
+ }
+ },
+ "@babel/helper-create-regexp-features-plugin": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz",
+ "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.10.4",
+ "@babel/helper-regex": "^7.10.4",
+ "regexpu-core": "^4.7.0"
+ },
+ "dependencies": {
+ "regexpu-core": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz",
+ "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==",
+ "dev": true,
+ "requires": {
+ "regenerate": "^1.4.0",
+ "regenerate-unicode-properties": "^8.2.0",
+ "regjsgen": "^0.5.1",
+ "regjsparser": "^0.6.4",
+ "unicode-match-property-ecmascript": "^1.0.4",
+ "unicode-match-property-value-ecmascript": "^1.2.0"
+ }
+ },
+ "regjsgen": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz",
+ "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==",
+ "dev": true
+ },
+ "regjsparser": {
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz",
+ "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==",
+ "dev": true,
+ "requires": {
+ "jsesc": "~0.5.0"
+ }
+ }
+ }
+ },
+ "@babel/helper-define-map": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz",
+ "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.10.4",
+ "@babel/types": "^7.10.5",
+ "lodash": "^4.17.19"
+ }
+ },
+ "@babel/helper-explode-assignable-expression": {
+ "version": "7.11.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz",
+ "integrity": "sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz",
+ "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.10.4",
+ "@babel/template": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz",
+ "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-hoist-variables": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz",
+ "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-member-expression-to-functions": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz",
+ "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.11.0"
+ }
+ },
+ "@babel/helper-module-imports": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz",
+ "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-module-transforms": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz",
+ "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.10.4",
+ "@babel/helper-replace-supers": "^7.10.4",
+ "@babel/helper-simple-access": "^7.10.4",
+ "@babel/helper-split-export-declaration": "^7.11.0",
+ "@babel/template": "^7.10.4",
+ "@babel/types": "^7.11.0",
+ "lodash": "^4.17.19"
+ }
+ },
+ "@babel/helper-optimise-call-expression": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz",
+ "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-plugin-utils": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
+ "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
+ "dev": true
+ },
+ "@babel/helper-regex": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz",
+ "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.19"
+ }
+ },
+ "@babel/helper-remap-async-to-generator": {
+ "version": "7.11.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz",
+ "integrity": "sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.10.4",
+ "@babel/helper-wrap-function": "^7.10.4",
+ "@babel/template": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-replace-supers": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz",
+ "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-member-expression-to-functions": "^7.10.4",
+ "@babel/helper-optimise-call-expression": "^7.10.4",
+ "@babel/traverse": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-simple-access": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz",
+ "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz",
+ "integrity": "sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.11.0"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz",
+ "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.11.0"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
+ "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
+ "dev": true
+ },
+ "@babel/helper-wrap-function": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz",
+ "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.10.4",
+ "@babel/template": "^7.10.4",
+ "@babel/traverse": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helpers": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz",
+ "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.10.4",
+ "@babel/traverse": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
+ "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.10.4",
+ "chalk": "^2.0.0",
+ "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.11.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz",
+ "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==",
+ "dev": true
+ },
+ "@babel/plugin-proposal-async-generator-functions": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz",
+ "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-remap-async-to-generator": "^7.10.4",
+ "@babel/plugin-syntax-async-generators": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-class-properties": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz",
+ "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@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.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz",
+ "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-export-namespace-from": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz",
+ "integrity": "sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-export-namespace-from": "^7.8.3"
+ }
+ },
+ "@babel/plugin-proposal-json-strings": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz",
+ "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-logical-assignment-operators": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz",
+ "integrity": "sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
+ }
+ },
+ "@babel/plugin-proposal-nullish-coalescing-operator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz",
+ "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4"
+ }
+ },
+ "@babel/plugin-proposal-object-rest-spread": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz",
+ "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.0",
+ "@babel/plugin-transform-parameters": "^7.10.4"
+ }
+ },
+ "@babel/plugin-proposal-optional-catch-binding": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz",
+ "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-optional-chaining": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz",
+ "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-private-methods": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz",
+ "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-proposal-unicode-property-regex": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz",
+ "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@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-class-properties": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz",
+ "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-decorators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.10.4.tgz",
+ "integrity": "sha512-2NaoC6fAk2VMdhY1eerkfHV+lVYC1u8b+jmRJISqANCJlTxYy19HGdIkkQtix2UtkcPuPu+IlDgrVseZnU03bw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@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-export-namespace-from": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz",
+ "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-syntax-flow": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.10.4.tgz",
+ "integrity": "sha512-yxQsX1dJixF4qEEdzVbst3SZQ58Nrooz8NV9Z9GL4byTE25BvJgl5lf0RECUf0fh28rZBb/RYTWn/eeKwCMrZQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@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.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz",
+ "integrity": "sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@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.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@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.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz",
+ "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-typescript": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.10.4.tgz",
+ "integrity": "sha512-oSAEz1YkBCAKr5Yiq8/BNtvSAPwkp/IyUnwZogd8p+F0RuYQQrLeRUzIQhueQTTBy/F+a40uS7OFKxnkRvmvFQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-arrow-functions": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz",
+ "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-async-to-generator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz",
+ "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-remap-async-to-generator": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-block-scoped-functions": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz",
+ "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-block-scoping": {
+ "version": "7.11.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz",
+ "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-classes": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz",
+ "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.10.4",
+ "@babel/helper-define-map": "^7.10.4",
+ "@babel/helper-function-name": "^7.10.4",
+ "@babel/helper-optimise-call-expression": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-replace-supers": "^7.10.4",
+ "@babel/helper-split-export-declaration": "^7.10.4",
+ "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.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz",
+ "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-destructuring": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz",
+ "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-dotall-regex": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz",
+ "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-duplicate-keys": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz",
+ "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-exponentiation-operator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz",
+ "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-flow-strip-types": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.9.0.tgz",
+ "integrity": "sha512-7Qfg0lKQhEHs93FChxVLAvhBshOPQDtJUTVHr/ZwQNRccCm4O9D79r9tVSoV8iNwjP1YgfD+e/fgHcPkN1qEQg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-syntax-flow": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-for-of": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz",
+ "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-function-name": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz",
+ "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-literals": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz",
+ "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-member-expression-literals": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz",
+ "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-modules-amd": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz",
+ "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-transforms": "^7.10.5",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "babel-plugin-dynamic-import-node": "^2.3.3"
+ }
+ },
+ "@babel/plugin-transform-modules-commonjs": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz",
+ "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-transforms": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-simple-access": "^7.10.4",
+ "babel-plugin-dynamic-import-node": "^2.3.3"
+ }
+ },
+ "@babel/plugin-transform-modules-systemjs": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz",
+ "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-hoist-variables": "^7.10.4",
+ "@babel/helper-module-transforms": "^7.10.5",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "babel-plugin-dynamic-import-node": "^2.3.3"
+ }
+ },
+ "@babel/plugin-transform-modules-umd": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz",
+ "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-transforms": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-named-capturing-groups-regex": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz",
+ "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-new-target": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz",
+ "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-object-super": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz",
+ "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-replace-supers": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-parameters": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz",
+ "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-property-literals": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz",
+ "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-react-constant-elements": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.10.4.tgz",
+ "integrity": "sha512-cYmQBW1pXrqBte1raMkAulXmi7rjg3VI6ZLg9QIic8Hq7BtYXaWuZSxsr2siOMI6SWwpxjWfnwhTUrd7JlAV7g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-react-display-name": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.4.tgz",
+ "integrity": "sha512-Zd4X54Mu9SBfPGnEcaGcOrVAYOtjT2on8QZkLKEq1S/tHexG39d9XXGZv19VfRrDjPJzFmPfTAqOQS1pfFOujw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-react-jsx": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.4.tgz",
+ "integrity": "sha512-L+MfRhWjX0eI7Js093MM6MacKU4M6dnCRa/QPDwYMxjljzSCzzlzKzj9Pk4P3OtrPcxr2N3znR419nr3Xw+65A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-builder-react-jsx": "^7.10.4",
+ "@babel/helper-builder-react-jsx-experimental": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-jsx": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-react-jsx-development": {
+ "version": "7.11.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.11.5.tgz",
+ "integrity": "sha512-cImAmIlKJ84sDmpQzm4/0q/2xrXlDezQoixy3qoz1NJeZL/8PRon6xZtluvr4H4FzwlDGI5tCcFupMnXGtr+qw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-builder-react-jsx-experimental": "^7.11.5",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-jsx": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-react-jsx-self": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.4.tgz",
+ "integrity": "sha512-yOvxY2pDiVJi0axdTWHSMi5T0DILN+H+SaeJeACHKjQLezEzhLx9nEF9xgpBLPtkZsks9cnb5P9iBEi21En3gg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-jsx": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-react-jsx-source": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.5.tgz",
+ "integrity": "sha512-wTeqHVkN1lfPLubRiZH3o73f4rfon42HpgxUSs86Nc+8QIcm/B9s8NNVXu/gwGcOyd7yDib9ikxoDLxJP0UiDA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-jsx": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-react-pure-annotations": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.4.tgz",
+ "integrity": "sha512-+njZkqcOuS8RaPakrnR9KvxjoG1ASJWpoIv/doyWngId88JoFlPlISenGXjrVacZUIALGUr6eodRs1vmPnF23A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-regenerator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz",
+ "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==",
+ "dev": true,
+ "requires": {
+ "regenerator-transform": "^0.14.2"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.11.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
+ "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
+ "dev": true,
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
+ "dev": true
+ },
+ "regenerator-transform": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz",
+ "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.8.4"
+ }
+ }
+ }
+ },
+ "@babel/plugin-transform-reserved-words": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz",
+ "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-runtime": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz",
+ "integrity": "sha512-pUu9VSf3kI1OqbWINQ7MaugnitRss1z533436waNXp+0N3ur3zfut37sXiQMxkuCF4VUjwZucen/quskCh7NHw==",
+ "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.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz",
+ "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-spread": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz",
+ "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0"
+ }
+ },
+ "@babel/plugin-transform-sticky-regex": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz",
+ "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-regex": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-template-literals": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz",
+ "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-typeof-symbol": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz",
+ "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-typescript": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.11.0.tgz",
+ "integrity": "sha512-edJsNzTtvb3MaXQwj8403B7mZoGu9ElDJQZOKjGUnvilquxBA3IQoEIOvkX/1O8xfAsnHS/oQhe2w/IXrr+w0w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.10.5",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-typescript": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-unicode-escapes": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz",
+ "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-unicode-regex": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz",
+ "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/preset-env": {
+ "version": "7.11.5",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz",
+ "integrity": "sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.11.0",
+ "@babel/helper-compilation-targets": "^7.10.4",
+ "@babel/helper-module-imports": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-proposal-async-generator-functions": "^7.10.4",
+ "@babel/plugin-proposal-class-properties": "^7.10.4",
+ "@babel/plugin-proposal-dynamic-import": "^7.10.4",
+ "@babel/plugin-proposal-export-namespace-from": "^7.10.4",
+ "@babel/plugin-proposal-json-strings": "^7.10.4",
+ "@babel/plugin-proposal-logical-assignment-operators": "^7.11.0",
+ "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4",
+ "@babel/plugin-proposal-numeric-separator": "^7.10.4",
+ "@babel/plugin-proposal-object-rest-spread": "^7.11.0",
+ "@babel/plugin-proposal-optional-catch-binding": "^7.10.4",
+ "@babel/plugin-proposal-optional-chaining": "^7.11.0",
+ "@babel/plugin-proposal-private-methods": "^7.10.4",
+ "@babel/plugin-proposal-unicode-property-regex": "^7.10.4",
+ "@babel/plugin-syntax-async-generators": "^7.8.0",
+ "@babel/plugin-syntax-class-properties": "^7.10.4",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.0",
+ "@babel/plugin-syntax-export-namespace-from": "^7.8.3",
+ "@babel/plugin-syntax-json-strings": "^7.8.0",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@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.10.4",
+ "@babel/plugin-transform-arrow-functions": "^7.10.4",
+ "@babel/plugin-transform-async-to-generator": "^7.10.4",
+ "@babel/plugin-transform-block-scoped-functions": "^7.10.4",
+ "@babel/plugin-transform-block-scoping": "^7.10.4",
+ "@babel/plugin-transform-classes": "^7.10.4",
+ "@babel/plugin-transform-computed-properties": "^7.10.4",
+ "@babel/plugin-transform-destructuring": "^7.10.4",
+ "@babel/plugin-transform-dotall-regex": "^7.10.4",
+ "@babel/plugin-transform-duplicate-keys": "^7.10.4",
+ "@babel/plugin-transform-exponentiation-operator": "^7.10.4",
+ "@babel/plugin-transform-for-of": "^7.10.4",
+ "@babel/plugin-transform-function-name": "^7.10.4",
+ "@babel/plugin-transform-literals": "^7.10.4",
+ "@babel/plugin-transform-member-expression-literals": "^7.10.4",
+ "@babel/plugin-transform-modules-amd": "^7.10.4",
+ "@babel/plugin-transform-modules-commonjs": "^7.10.4",
+ "@babel/plugin-transform-modules-systemjs": "^7.10.4",
+ "@babel/plugin-transform-modules-umd": "^7.10.4",
+ "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4",
+ "@babel/plugin-transform-new-target": "^7.10.4",
+ "@babel/plugin-transform-object-super": "^7.10.4",
+ "@babel/plugin-transform-parameters": "^7.10.4",
+ "@babel/plugin-transform-property-literals": "^7.10.4",
+ "@babel/plugin-transform-regenerator": "^7.10.4",
+ "@babel/plugin-transform-reserved-words": "^7.10.4",
+ "@babel/plugin-transform-shorthand-properties": "^7.10.4",
+ "@babel/plugin-transform-spread": "^7.11.0",
+ "@babel/plugin-transform-sticky-regex": "^7.10.4",
+ "@babel/plugin-transform-template-literals": "^7.10.4",
+ "@babel/plugin-transform-typeof-symbol": "^7.10.4",
+ "@babel/plugin-transform-unicode-escapes": "^7.10.4",
+ "@babel/plugin-transform-unicode-regex": "^7.10.4",
+ "@babel/preset-modules": "^0.1.3",
+ "@babel/types": "^7.11.5",
+ "browserslist": "^4.12.0",
+ "core-js-compat": "^3.6.2",
+ "invariant": "^2.2.2",
+ "levenary": "^1.1.1",
+ "semver": "^5.5.0"
+ }
+ },
+ "@babel/preset-modules": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz",
+ "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/plugin-proposal-unicode-property-regex": "^7.4.4",
+ "@babel/plugin-transform-dotall-regex": "^7.4.4",
+ "@babel/types": "^7.4.4",
+ "esutils": "^2.0.2"
+ }
+ },
+ "@babel/preset-react": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.10.4.tgz",
+ "integrity": "sha512-BrHp4TgOIy4M19JAfO1LhycVXOPWdDbTRep7eVyatf174Hff+6Uk53sDyajqZPu8W1qXRBiYOfIamek6jA7YVw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-transform-react-display-name": "^7.10.4",
+ "@babel/plugin-transform-react-jsx": "^7.10.4",
+ "@babel/plugin-transform-react-jsx-development": "^7.10.4",
+ "@babel/plugin-transform-react-jsx-self": "^7.10.4",
+ "@babel/plugin-transform-react-jsx-source": "^7.10.4",
+ "@babel/plugin-transform-react-pure-annotations": "^7.10.4"
+ }
+ },
+ "@babel/preset-typescript": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.9.0.tgz",
+ "integrity": "sha512-S4cueFnGrIbvYJgwsVFKdvOmpiL0XGw9MFW9D0vgRys5g36PBhZRL8NX8Gr2akz8XRtzq6HuDXPD/1nniagNUg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-transform-typescript": "^7.9.0"
+ }
+ },
+ "@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/runtime-corejs3": {
+ "version": "7.11.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz",
+ "integrity": "sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A==",
+ "dev": true,
+ "requires": {
+ "core-js-pure": "^3.0.0",
+ "regenerator-runtime": "^0.13.4"
+ },
+ "dependencies": {
+ "regenerator-runtime": {
+ "version": "0.13.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/template": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz",
+ "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/parser": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.11.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz",
+ "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/generator": "^7.11.5",
+ "@babel/helper-function-name": "^7.10.4",
+ "@babel/helper-split-export-declaration": "^7.11.0",
+ "@babel/parser": "^7.11.5",
+ "@babel/types": "^7.11.5",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0",
+ "lodash": "^4.17.19"
+ },
+ "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.11.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz",
+ "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.10.4",
+ "lodash": "^4.17.19",
+ "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.9",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz",
+ "integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==",
+ "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.13",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.13.tgz",
+ "integrity": "sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==",
+ "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/glob": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
+ "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==",
+ "dev": true,
+ "requires": {
+ "@types/minimatch": "*",
+ "@types/node": "*"
+ }
+ },
+ "@types/istanbul-lib-coverage": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
+ "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==",
+ "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.2",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz",
+ "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-coverage": "*",
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "@types/json-schema": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
+ "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
+ "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": "14.6.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.2.tgz",
+ "integrity": "sha512-onlIwbaeqvZyniGPfdw/TEhKIh79pz66L1q06WUQqJLnAb6wbjvOtepLYTGHTqzdXgBYIE3ZdmqHDGsRsbBz7A==",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz",
+ "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==",
+ "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.10",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.10.tgz",
+ "integrity": "sha512-MU10TSgzNABgdzKvQVW1nuuT+sgBMWeXNc3XOs5YXV5SDAK+PPja2eUuBNB9iqElu03xyEDqlnGw0jgl4nbqGQ==",
+ "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.34.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz",
+ "integrity": "sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/experimental-utils": "2.34.0",
+ "functional-red-black-tree": "^1.0.1",
+ "regexpp": "^3.0.0",
+ "tsutils": "^3.17.1"
+ }
+ },
+ "@typescript-eslint/experimental-utils": {
+ "version": "2.34.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz",
+ "integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.3",
+ "@typescript-eslint/typescript-estree": "2.34.0",
+ "eslint-scope": "^5.0.0",
+ "eslint-utils": "^2.0.0"
+ }
+ },
+ "@typescript-eslint/parser": {
+ "version": "2.34.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.34.0.tgz",
+ "integrity": "sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA==",
+ "dev": true,
+ "requires": {
+ "@types/eslint-visitor-keys": "^1.0.0",
+ "@typescript-eslint/experimental-utils": "2.34.0",
+ "@typescript-eslint/typescript-estree": "2.34.0",
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "@typescript-eslint/typescript-estree": {
+ "version": "2.34.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz",
+ "integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==",
+ "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": "^7.3.2",
+ "tsutils": "^3.17.1"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "7.3.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
+ "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.4.tgz",
+ "integrity": "sha512-Eu9ELJWCz/c1e9gTiCY+FceWxcqzjYEbqMgtndnuSqZSUCOL73TWNK2mHfIj4Cw2E/ongOp+JISVNCmovt2KYQ==",
+ "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.4.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz",
+ "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==",
+ "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.1.0",
+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz",
+ "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==",
+ "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.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "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.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "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.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.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.2.0",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
+ "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+ "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.1"
+ }
+ }
+ }
+ },
+ "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.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "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.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.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.2.0",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
+ "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+ "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.1"
+ }
+ }
+ }
+ },
+ "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": "5.4.1",
+ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
+ "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0",
+ "safer-buffer": "^2.1.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
+ "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
+ "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.8.6",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz",
+ "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.12.0",
+ "caniuse-lite": "^1.0.30001109",
+ "colorette": "^1.2.1",
+ "normalize-range": "^0.1.2",
+ "num2fraction": "^1.2.2",
+ "postcss": "^7.0.32",
+ "postcss-value-parser": "^4.1.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.10.1",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz",
+ "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==",
+ "dev": true
+ },
+ "axios": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
+ "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
+ "requires": {
+ "follow-redirects": "1.5.10"
+ },
+ "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"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
+ "axobject-query": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
+ "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==",
+ "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.1.0",
+ "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
+ "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@babel/parser": "^7.7.0",
+ "@babel/traverse": "^7.7.0",
+ "@babel/types": "^7.7.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.1.0",
+ "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz",
+ "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==",
+ "dev": true,
+ "requires": {
+ "find-cache-dir": "^2.1.0",
+ "loader-utils": "^1.4.0",
+ "mkdirp": "^0.5.3",
+ "pify": "^4.0.1",
+ "schema-utils": "^2.6.5"
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz",
+ "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==",
+ "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.11.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
+ "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
+ "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.1.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz",
+ "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "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.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
+ "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.2",
+ "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-9.1.2.tgz",
+ "integrity": "sha512-k58RtQOKH21NyKtzptoAvtAODuAJJs3ZhqBMl456/GnXEQ/0La92pNmwgWoMn5pBTrsvk3YYXdY7zpY4e3UIxA==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "7.9.0",
+ "@babel/plugin-proposal-class-properties": "7.8.3",
+ "@babel/plugin-proposal-decorators": "7.8.3",
+ "@babel/plugin-proposal-nullish-coalescing-operator": "7.8.3",
+ "@babel/plugin-proposal-numeric-separator": "7.8.3",
+ "@babel/plugin-proposal-optional-chaining": "7.9.0",
+ "@babel/plugin-transform-flow-strip-types": "7.9.0",
+ "@babel/plugin-transform-react-display-name": "7.8.3",
+ "@babel/plugin-transform-runtime": "7.9.0",
+ "@babel/preset-env": "7.9.0",
+ "@babel/preset-react": "7.9.1",
+ "@babel/preset-typescript": "7.9.0",
+ "@babel/runtime": "7.9.0",
+ "babel-plugin-macros": "2.8.0",
+ "babel-plugin-transform-react-remove-prop-types": "0.4.24"
+ },
+ "dependencies": {
+ "@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-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-optional-chaining": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz",
+ "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.0"
+ }
+ },
+ "@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/preset-env": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.0.tgz",
+ "integrity": "sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.9.0",
+ "@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-numeric-separator": "^7.8.3",
+ "@babel/plugin-proposal-object-rest-spread": "^7.9.0",
+ "@babel/plugin-proposal-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-proposal-optional-chaining": "^7.9.0",
+ "@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-numeric-separator": "^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.9.0",
+ "@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.9.0",
+ "@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.9.0",
+ "@babel/plugin-transform-modules-commonjs": "^7.9.0",
+ "@babel/plugin-transform-modules-systemjs": "^7.9.0",
+ "@babel/plugin-transform-modules-umd": "^7.9.0",
+ "@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/preset-modules": "^0.1.3",
+ "@babel/types": "^7.9.0",
+ "browserslist": "^4.9.1",
+ "core-js-compat": "^3.6.2",
+ "invariant": "^2.2.2",
+ "levenary": "^1.1.1",
+ "semver": "^5.5.0"
+ }
+ },
+ "@babel/preset-react": {
+ "version": "7.9.1",
+ "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.9.1.tgz",
+ "integrity": "sha512-aJBYF23MPj0RNdp/4bHnAP0NVqqZRr9kl0NAOP4nJCex6OYVio59+dnQzsAWFuogdLyeaKA1hmfUIVZkY5J+TQ==",
+ "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.9.1",
+ "@babel/plugin-transform-react-jsx-development": "^7.9.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.9.0",
+ "@babel/plugin-transform-react-jsx-source": "^7.9.0"
+ }
+ },
+ "@babel/runtime": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.0.tgz",
+ "integrity": "sha512-cTIudHnzuWLS56ik4DnRnqqNf8MkdUzV4iFFI1h7Jo9xvrpQROYaAnaSd2mHLQAzzZAPfATynX5ord6YlNYNMA==",
+ "dev": true,
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
+ "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.1.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
+ "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
+ "dev": true
+ },
+ "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": "5.1.3",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz",
+ "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==",
+ "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": "1.0.0",
+ "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
+ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==",
+ "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"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "browserify-sign": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
+ "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^5.1.1",
+ "browserify-rsa": "^4.0.1",
+ "create-hash": "^1.2.0",
+ "create-hmac": "^1.1.7",
+ "elliptic": "^6.5.3",
+ "inherits": "^2.0.4",
+ "parse-asn1": "^5.1.5",
+ "readable-stream": "^3.6.0",
+ "safe-buffer": "^5.2.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
+ }
+ }
+ },
+ "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.14.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.0.tgz",
+ "integrity": "sha512-pUsXKAF2lVwhmtpeA3LJrZ76jXuusrNyhduuQs7CDFf9foT4Y38aQOserd2lMe5DSSrjf3fx34oHwryuvxAUgQ==",
+ "dev": true,
+ "requires": {
+ "caniuse-lite": "^1.0.30001111",
+ "electron-to-chromium": "^1.3.523",
+ "escalade": "^3.0.2",
+ "node-releases": "^1.1.60"
+ }
+ },
+ "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.30001122",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001122.tgz",
+ "integrity": "sha512-pxjw28CThdrqfz06nJkpAc5SXM404TXB/h5f4UJX+rrXJKE/1bu/KAILc2AY+O6cQIFtRjV9qOR2vaEp9LDGUA==",
+ "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.4.2",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz",
+ "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==",
+ "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.4.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": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
+ "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
+ "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.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
+ "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="
+ },
+ "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"
+ }
+ }
+ }
+ },
+ "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"
+ }
+ },
+ "colorette": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz",
+ "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==",
+ "dev": true
+ },
+ "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.3.1",
+ "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz",
+ "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==",
+ "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.5",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz",
+ "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.8.5",
+ "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-js-pure": {
+ "version": "3.6.5",
+ "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz",
+ "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz",
+ "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "elliptic": "^6.5.3"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "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.7.0",
+ "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz",
+ "integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==",
+ "requires": {
+ "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.3",
+ "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz",
+ "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==",
+ "dev": true,
+ "requires": {
+ "css-tree": "1.0.0-alpha.39"
+ },
+ "dependencies": {
+ "css-tree": {
+ "version": "1.0.0-alpha.39",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz",
+ "integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==",
+ "dev": true,
+ "requires": {
+ "mdn-data": "2.0.6",
+ "source-map": "^0.6.1"
+ }
+ },
+ "mdn-data": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz",
+ "integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==",
+ "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
+ }
+ }
+ },
+ "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"
+ }
+ },
+ "csstype": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz",
+ "integrity": "sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag=="
+ },
+ "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"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "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.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
+ }
+ }
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
+ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
+ "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.557",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.557.tgz",
+ "integrity": "sha512-M2p3nWulBqSEIisykYUVYnaSuRikHvxv8Wf209/Vg/sjrOew12hBQv2JvNGy+i+eDeJU9uQ3dbUbCCQ/CkudEg==",
+ "dev": true
+ },
+ "elliptic": {
+ "version": "6.5.3",
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
+ "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
+ "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"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "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.3.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz",
+ "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==",
+ "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.8",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+ "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
+ },
+ "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"
+ }
+ },
+ "escalade": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz",
+ "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==",
+ "dev": true
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
+ "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
+ "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"
+ }
+ },
+ "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"
+ }
+ },
+ "globals": {
+ "version": "12.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
+ "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-5.2.1.tgz",
+ "integrity": "sha512-pGIZ8t0mFLcV+6ZirRgYK6RVqUIKRIi9MmgzUEmrIknsn3AdO0I32asO86dJgloHq+9ZPl8UIg8mYrvgP5u2wQ==",
+ "dev": true,
+ "requires": {
+ "confusing-browser-globals": "^1.0.9"
+ }
+ },
+ "eslint-import-resolver-node": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz",
+ "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==",
+ "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.6.0",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz",
+ "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz",
+ "integrity": "sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==",
+ "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.11.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
+ "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
+ "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.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-plugin-react": {
+ "version": "7.19.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz",
+ "integrity": "sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ==",
+ "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.15.1",
+ "semver": "^6.3.0",
+ "string.prototype.matchall": "^4.0.2",
+ "xregexp": "^4.3.0"
+ },
+ "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.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "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.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.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.2.0",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
+ "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+ "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.1"
+ }
+ },
+ "object.entries": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz",
+ "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5",
+ "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"
+ }
+ },
+ "resolve": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
+ "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "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.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz",
+ "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.1.0",
+ "estraverse": "^4.1.1"
+ }
+ },
+ "eslint-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+ "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "dev": true
+ },
+ "espree": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
+ "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
+ "dev": true,
+ "requires": {
+ "acorn": "^7.1.1",
+ "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.3.1",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz",
+ "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.1.0"
+ },
+ "dependencies": {
+ "estraverse": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
+ "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+ "dev": true
+ }
+ }
+ },
+ "esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.2.0"
+ },
+ "dependencies": {
+ "estraverse": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
+ "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+ "dev": true
+ }
+ }
+ },
+ "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.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "dev": true
+ },
+ "events": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz",
+ "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==",
+ "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.1.0",
+ "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz",
+ "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==",
+ "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.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "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.2",
+ "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
+ "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==",
+ "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=="
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
+ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
+ "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.13.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
+ "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==",
+ "dev": true
+ },
+ "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.6.1",
+ "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.6.1.tgz",
+ "integrity": "sha512-hT9yh/tiinkmirKrlv4KWOjztdoZo1mx9Qh4KvWqC7isoXwdUY3PNWUxceF4/qO9R6riA2C29jdTOeQOIROjgw=="
+ },
+ "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.1",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
+ "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
+ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
+ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==",
+ "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.5",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
+ "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.12.3",
+ "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.1.0",
+ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
+ "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.6.0",
+ "safe-buffer": "^5.2.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
+ }
+ }
+ },
+ "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.3.1",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz",
+ "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==",
+ "dev": true
+ },
+ "html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true
+ },
+ "html-minifier-terser": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz",
+ "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==",
+ "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"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
+ }
+ }
+ },
+ "http-proxy": {
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
+ "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "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.3.3",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz",
+ "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==",
+ "dev": true,
+ "requires": {
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.1.0",
+ "cli-cursor": "^3.1.0",
+ "cli-width": "^3.0.0",
+ "external-editor": "^3.0.3",
+ "figures": "^3.0.0",
+ "lodash": "^4.17.19",
+ "mute-stream": "0.0.8",
+ "run-async": "^2.4.0",
+ "rxjs": "^6.6.0",
+ "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": "4.1.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+ "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+ "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.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "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"
+ }
+ },
+ "internal-slot": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz",
+ "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==",
+ "dev": true,
+ "requires": {
+ "es-abstract": "^1.17.0-next.1",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.2"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.17.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "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.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.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.2.0",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
+ "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+ "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.1"
+ }
+ }
+ }
+ },
+ "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"
+ }
+ },
+ "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.1.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz",
+ "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==",
+ "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-negative-zero": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz",
+ "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=",
+ "dev": true
+ },
+ "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-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.13",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
+ "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
+ "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.2",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
+ "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
+ "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.12",
+ "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz",
+ "integrity": "sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg=="
+ },
+ "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.14.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
+ "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
+ "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
+ },
+ "parse5": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
+ "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==",
+ "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-parse-even-better-errors": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.0.tgz",
+ "integrity": "sha512-o3aP+RsWDJZayj1SbHNQAI8x0v3T3SKiGoZlNYfbUP1S3omJQ6i9CnqADqkSPaOAxwua4/1YWx5CM7oiChJt2Q==",
+ "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.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
+ "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
+ "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.4.1",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz",
+ "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.1.1",
+ "object.assign": "^4.1.0"
+ }
+ },
+ "jszip": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz",
+ "integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==",
+ "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="
+ },
+ "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==",
+ "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
+ },
+ "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.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "requires": {
+ "immediate": "~3.0.5"
+ }
+ },
+ "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.9.0",
+ "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.9.0.tgz",
+ "integrity": "sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g==",
+ "requires": {
+ "lie": "3.1.1"
+ },
+ "dependencies": {
+ "lie": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
+ "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
+ "requires": {
+ "immediate": "~3.0.5"
+ }
+ }
+ }
+ },
+ "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.20",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
+ },
+ "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.7.0",
+ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.0.tgz",
+ "integrity": "sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==",
+ "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-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
+ },
+ "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.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "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"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "mime": {
+ "version": "2.4.6",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz",
+ "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==",
+ "dev": true
+ },
+ "mime-db": {
+ "version": "1.44.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
+ "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "2.1.27",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
+ "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
+ "dev": true,
+ "requires": {
+ "mime-db": "1.44.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.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz",
+ "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
+ "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
+ "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
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "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"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
+ }
+ }
+ }
+ }
+ },
+ "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.60",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz",
+ "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==",
+ "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
+ },
+ "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.8.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz",
+ "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==",
+ "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.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "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.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.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.2.0",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
+ "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+ "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.1"
+ }
+ }
+ }
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "requires": {
+ "mimic-fn": "^2.1.0"
+ }
+ },
+ "open": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/open/-/open-7.2.1.tgz",
+ "integrity": "sha512-xbYCJib4spUdmcs0g/2mK1nKo/jO2T7INClWd/beL7PFkXRWgr8B23ssDHX/USPn2M2IjDR5UdpYs6I67SnTSA==",
+ "dev": true,
+ "requires": {
+ "is-docker": "^2.0.0",
+ "is-wsl": "^2.1.1"
+ },
+ "dependencies": {
+ "is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "dev": true,
+ "requires": {
+ "is-docker": "^2.0.0"
+ }
+ }
+ }
+ },
+ "openseadragon": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/openseadragon/-/openseadragon-2.4.2.tgz",
+ "integrity": "sha512-398KbZwRtOYA6OmeWRY4Q0737NTacQ9Q6whmr9Lp1MNQO3p0eBz5LIASRne+4gwequcSM1vcHcjfy3dIndQziw=="
+ },
+ "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-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-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-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "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.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+ },
+ "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.6",
+ "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz",
+ "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==",
+ "dev": true,
+ "requires": {
+ "asn1.js": "^5.2.0",
+ "browserify-aes": "^1.0.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"
+ }
+ },
+ "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.1.1",
+ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
+ "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
+ "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.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
+ "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz",
+ "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==",
+ "dev": true,
+ "requires": {
+ "ts-pnp": "^1.1.6"
+ }
+ },
+ "portfinder": {
+ "version": "1.0.28",
+ "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
+ "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==",
+ "dev": true,
+ "requires": {
+ "async": "^2.6.2",
+ "debug": "^3.1.1",
+ "mkdirp": "^0.5.5"
+ },
+ "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.32",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz",
+ "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.4.tgz",
+ "integrity": "sha512-0I79VRAd1UTkaHzY9w83P39YGO/M3bG7/tNLrHGEunBolfoGM0hSjrGvjoeaj0JE/zIw5GsI2KZ0UwDJqv5hjw==",
+ "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.3",
+ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz",
+ "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==",
+ "dev": true,
+ "requires": {
+ "icss-utils": "^4.1.1",
+ "postcss": "^7.0.32",
+ "postcss-selector-parser": "^6.0.2",
+ "postcss-value-parser": "^4.1.0"
+ }
+ },
+ "postcss-modules-scope": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz",
+ "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==",
+ "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.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz",
+ "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
+ "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.4.1",
+ "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.4.1.tgz",
+ "integrity": "sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz",
+ "integrity": "sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==",
+ "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.8.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+ "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==",
+ "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"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "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.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "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": "sha512-DHyivomgg2kMUWsT3mfMtIKNxJyAtlcFtCd+vWvk4u/mAsnXqrhkDVAzZR7aSS/kk2hvAS7rwlia6zyAcJjcsg==",
+ "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.5",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
+ "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==",
+ "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.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
+ "dev": true
+ }
+ }
+ },
+ "react-app-rewired": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/react-app-rewired/-/react-app-rewired-2.2.1.tgz",
+ "integrity": "sha512-uFQWTErXeLDrMzOJHKp0h8P1z0LV9HzPGsJ6adOtGlA/B9WfT6Shh4j2tLTTGlXOfiVx6w6iWpp7SOC5pvk+gA==",
+ "dev": true,
+ "requires": {
+ "semver": "^5.6.0"
+ }
+ },
+ "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.1",
+ "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz",
+ "integrity": "sha512-XxTbgJnYZmxuPtY3y/UV0D8/65NKkmaia4rXzViknVnZeVlklSh8u6TnaEYPfAi/Gh1TP4mEOXHI6jQOPbeakQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "7.8.3",
+ "address": "1.1.2",
+ "browserslist": "4.10.0",
+ "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.7",
+ "recursive-readdir": "2.2.2",
+ "shell-quote": "1.7.2",
+ "strip-ansi": "6.0.0",
+ "text-table": "0.2.0"
+ },
+ "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"
+ }
+ },
+ "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.10.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.10.0.tgz",
+ "integrity": "sha512-TpfK0TDgv71dzuTsEAlQiHeWQ/tiPqgNZVdv046fvNtBZrjbv2O3TsWCDU0AWGJJKCF/KsjNdLzR9hXOsh/CfA==",
+ "dev": true,
+ "requires": {
+ "caniuse-lite": "^1.0.30001035",
+ "electron-to-chromium": "^1.3.378",
+ "node-releases": "^1.1.52",
+ "pkg-up": "^3.1.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": {
+ "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
+ }
+ }
+ },
+ "cli-width": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz",
+ "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==",
+ "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": "sha512-qxrSETbovswZ0xXs1o5+1o9NfNYbLnQWUqhef36tDWb+tGrbsHjEel5DZuncWMbO0rWd5e4AvCoJBOWm3x6p3A==",
+ "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.7",
+ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz",
+ "integrity": "sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==",
+ "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.2",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.2.tgz",
+ "integrity": "sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "hoist-non-react-statics": "^3.3.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.11.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
+ "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "requires": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
+ }
+ }
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.3.tgz",
+ "integrity": "sha512-oSnoWmii/iKdeQiwaO6map1lUaZLmG0xIUyb/HwCVFLT7gNbj8JZ9RmpvMCZ4fB98ZUMRfNmp/ft8uy/xD1RLA==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "7.9.0",
+ "@svgr/webpack": "4.3.3",
+ "@typescript-eslint/eslint-plugin": "^2.10.0",
+ "@typescript-eslint/parser": "^2.10.0",
+ "babel-eslint": "10.1.0",
+ "babel-jest": "^24.9.0",
+ "babel-loader": "8.1.0",
+ "babel-plugin-named-asset-import": "^0.3.6",
+ "babel-preset-react-app": "^9.1.2",
+ "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.1",
+ "eslint-loader": "3.0.3",
+ "eslint-plugin-flowtype": "4.6.0",
+ "eslint-plugin-import": "2.20.1",
+ "eslint-plugin-jsx-a11y": "6.2.3",
+ "eslint-plugin-react": "7.19.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.4",
+ "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.1",
+ "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.8",
+ "ts-pnp": "1.1.6",
+ "url-loader": "2.3.0",
+ "webpack": "4.42.0",
+ "webpack-dev-server": "3.11.0",
+ "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": "sha512-5YxRdmy+YsJHjLjLvOZ4Jl7i6QzR0VZVSXoM4T/hLXcgo+tEg91RU9gZPDX/yQD2fKIOPidktRj5UoMwjoUH1Q==",
+ "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.22.2",
+ "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.2.tgz",
+ "integrity": "sha512-5j4h4FhxTdOpBKtePSs1yk6LDNT4oGtUwjT7Nkh61Z8vv3fTG/XeOf8J4li1AYaexOwTXnw0HFVxsV0GBUqwRw==",
+ "requires": {
+ "@babel/runtime": "^7.7.2",
+ "clsx": "^1.0.4",
+ "dom-helpers": "^5.1.3",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.7.2",
+ "react-lifecycles-compat": "^3.0.4"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.11.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
+ "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "dom-helpers": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz",
+ "integrity": "sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==",
+ "requires": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
+ }
+ }
+ },
+ "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.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ },
+ "readdirp": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
+ "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
+ "dev": true,
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
+ },
+ "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.1",
+ "resolved": "https://registry.npmjs.org/redux-axios-middleware/-/redux-axios-middleware-4.0.1.tgz",
+ "integrity": "sha512-bQGt75GYNl2VvEtsYE1Hcw8ua869RkUC7W98IJwiQKpyFMs416wqvMV1PBfyTbV83aRzIJzaYytcdcPi+8Bbrg=="
+ },
+ "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.4",
+ "resolved": "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.4.tgz",
+ "integrity": "sha512-xmcA0O/tjCLXhh9Fuiq6pMrJCwFRaouA8436zcikdIpYWWCjU76CRk+i2bHx8EeiSiMGnB85/lZdU3wIJVXHTA==",
+ "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.2.0",
+ "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz",
+ "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==",
+ "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.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "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.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.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.2.0",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
+ "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+ "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.1"
+ }
+ }
+ }
+ },
+ "regexpp": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
+ "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
+ "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.19"
+ }
+ },
+ "request-promise-native": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz",
+ "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==",
+ "dev": true,
+ "requires": {
+ "request-promise-core": "1.1.4",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
+ "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
+ "dev": true
+ },
+ "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.6.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz",
+ "integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==",
+ "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.7.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz",
+ "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.5",
+ "ajv": "^6.12.4",
+ "ajv-keywords": "^3.5.2"
+ }
+ },
+ "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": "4.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
+ "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "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"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
+ },
+ "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
+ },
+ "side-channel": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz",
+ "integrity": "sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==",
+ "dev": true,
+ "requires": {
+ "es-abstract": "^1.18.0-next.0",
+ "object-inspect": "^1.8.0"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.18.0-next.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz",
+ "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==",
+ "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.2.0",
+ "is-negative-zero": "^2.0.0",
+ "is-regex": "^1.1.1",
+ "object-inspect": "^1.8.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.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.2.0",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
+ "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+ "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.1"
+ }
+ }
+ }
+ },
+ "signal-exit": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
+ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
+ "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.5",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+ "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.20",
+ "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz",
+ "integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==",
+ "dev": true,
+ "requires": {
+ "faye-websocket": "^0.10.0",
+ "uuid": "^3.4.0",
+ "websocket-driver": "0.6.5"
+ }
+ },
+ "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.19",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
+ "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
+ "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
+ "dev": true,
+ "requires": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-exceptions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
+ "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
+ "dev": true
+ },
+ "spdx-expression-parse": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+ "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.2",
+ "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz",
+ "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==",
+ "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.matchall": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz",
+ "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.0",
+ "has-symbols": "^1.0.1",
+ "internal-slot": "^1.0.2",
+ "regexp.prototype.flags": "^1.3.0",
+ "side-channel": "^1.0.2"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.17.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "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.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.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.2.0",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
+ "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+ "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.1"
+ }
+ }
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
+ "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.17.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "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.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.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.2.0",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
+ "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+ "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.1"
+ }
+ }
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
+ "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.17.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "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.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.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.2.0",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
+ "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+ "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.1"
+ }
+ }
+ }
+ },
+ "string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.2.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
+ }
+ }
+ },
+ "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.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "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.3.0",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.3.0.tgz",
+ "integrity": "sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg==",
+ "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.8.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
+ "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
+ "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.8",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz",
+ "integrity": "sha512-/fKw3R+hWyHfYx7Bv6oPqmk4HGQcrWLtV3X6ggvPuwPNHSnzvVV51z6OaaCOus4YLjutYGOz3pEpbhe6Up2s1w==",
+ "dev": true,
+ "requires": {
+ "cacache": "^13.0.1",
+ "find-cache-dir": "^3.3.1",
+ "jest-worker": "^25.4.0",
+ "p-limit": "^2.3.0",
+ "schema-utils": "^2.6.6",
+ "serialize-javascript": "^4.0.0",
+ "source-map": "^0.6.1",
+ "terser": "^4.6.12",
+ "webpack-sources": "^1.4.3"
+ },
+ "dependencies": {
+ "find-cache-dir": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz",
+ "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==",
+ "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.5.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz",
+ "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==",
+ "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.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "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.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "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.6",
+ "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.6.tgz",
+ "integrity": "sha512-CrG5GqAAzMT7144Cl+UIFP7mz/iIhiy+xQ6GGcnjTezhALT02uPMRw7tgDSESgB5MsfKt55+GPWw4ir1kVtMIQ==",
+ "dev": true
+ },
+ "tslib": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
+ "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==",
+ "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.2.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz",
+ "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==",
+ "dev": true
+ },
+ "unicode-property-aliases-ecmascript": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz",
+ "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==",
+ "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.4.0",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz",
+ "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==",
+ "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.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "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.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.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.2.0",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
+ "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+ "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.1"
+ }
+ }
+ }
+ },
+ "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.4.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+ "dev": true
+ },
+ "v8-compile-cache": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
+ "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
+ "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.2",
+ "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
+ "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==",
+ "dev": true,
+ "requires": {
+ "browser-process-hrtime": "^1.0.0"
+ }
+ },
+ "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.7.4",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz",
+ "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==",
+ "dev": true,
+ "requires": {
+ "chokidar": "^3.4.1",
+ "graceful-fs": "^4.1.2",
+ "neo-async": "^2.5.0",
+ "watchpack-chokidar2": "^2.0.0"
+ }
+ },
+ "watchpack-chokidar2": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz",
+ "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "chokidar": "^2.1.8"
+ },
+ "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,
+ "optional": true
+ },
+ "chokidar": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
+ "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
+ "dev": true,
+ "optional": 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.13",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
+ "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
+ "dev": true,
+ "optional": true
+ },
+ "glob-parent": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+ "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==",
+ "dev": true,
+ "optional": 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": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==",
+ "dev": true,
+ "optional": 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": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "binary-extensions": "^1.0.0"
+ }
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "dev": true,
+ "optional": 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,
+ "optional": true
+ },
+ "readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "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"
+ }
+ },
+ "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,
+ "optional": 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,
+ "optional": 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.42.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.42.0.tgz",
+ "integrity": "sha512-EzJRHvwQyBiYrYqhyjW9AqM90dE4+s1/XtCfn7uWg6cS72zH+2VPFAlsnW0+W0cDi0XRjNKUMoJtpSi50+Ph6w==",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
+ "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
+ "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.5",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz",
+ "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==",
+ "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": "^4.0.0",
+ "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.11.0",
+ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz",
+ "integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==",
+ "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.3.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.8",
+ "opn": "^5.5.0",
+ "p-retry": "^3.0.1",
+ "portfinder": "^1.0.26",
+ "schema-utils": "^1.0.0",
+ "selfsigned": "^1.10.7",
+ "semver": "^6.3.0",
+ "serve-index": "^1.9.1",
+ "sockjs": "0.3.20",
+ "sockjs-client": "1.4.0",
+ "spdy": "^4.0.2",
+ "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": "^13.3.2"
+ },
+ "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.13",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
+ "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
+ "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-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"
+ }
+ },
+ "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"
+ }
+ },
+ "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_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"
+ }
+ },
+ "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"
+ }
+ }
+ }
+ },
+ "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.6.5",
+ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz",
+ "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=",
+ "dev": true,
+ "requires": {
+ "websocket-extensions": ">=0.1.1"
+ }
+ },
+ "websocket-extensions": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
+ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
+ "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.11.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
+ "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
+ "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.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
+ "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.11.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
+ "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
+ "dev": true,
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
+ "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
+ },
+ "xregexp": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz",
+ "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime-corejs3": "^7.8.3"
+ }
+ },
+ "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.10.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",
+ "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==",
+ "dev": true
+ },
+ "yargs": {
+ "version": "13.3.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
+ "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
+ "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.2"
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
+ "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
+ "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..4b22a2a2
--- /dev/null
+++ b/viscoll-app/package.json
@@ -0,0 +1,74 @@
+{
+ "name": "viscoll-app",
+ "version": "0.14.9",
+ "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.4.2",
+ "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-app-rewired": "^2.2.1",
+ "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",
+ "test": "react-app-rewired test",
+ "eject": "react-scripts eject",
+ "generate-build-version": "node generate-build-version",
+ "prebuild": "npm run generate-build-version"
+ },
+ "babel": {
+ "presets": [
+ "@babel/preset-env",
+ "@babel/preset-react"
+ ]
+ },
+ "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..42e08526
--- /dev/null
+++ b/viscoll-app/public/index.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+ VCEditor
+
+
+
+ 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/public/meta.json b/viscoll-app/public/meta.json
new file mode 100644
index 00000000..e0fbfa7a
--- /dev/null
+++ b/viscoll-app/public/meta.json
@@ -0,0 +1 @@
+{"version":"0.14.9"}
\ No newline at end of file
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..70263ccf
--- /dev/null
+++ b/viscoll-app/sass/index.scss
@@ -0,0 +1,25 @@
+@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/terms';
+@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;
+}
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..f5c818c7
--- /dev/null
+++ b/viscoll-app/sass/layout/_landing.scss
@@ -0,0 +1,60 @@
+.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 {
+ width: 100%;
+ height:10vh;
+ background: $teal;
+ }
+
+ 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/_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/_terms.scss b/viscoll-app/sass/layout/_terms.scss
new file mode 100644
index 00000000..099ba11b
--- /dev/null
+++ b/viscoll-app/sass/layout/_terms.scss
@@ -0,0 +1,138 @@
+.termsManager {
+ height: 100%;
+ .container {
+ padding: 1em 2em 0em 2em;
+ }
+
+ .browse {
+ height: 100%;
+ .termsList {
+ .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;
+ }
+ .taxonomy {
+ 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;
+ }
+ }
+ .taxonomy {
+ 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;
+ }
+ }
+ }
+}
+.termsInfobox {
+ display: flex;
+ flex-wrap: wrap;
+}
+.termSearch {
+ 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;
+ }
+}
+.termForm {
+ 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%;
+ }
+}
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..8f0faacc
--- /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%;
+ }
+}
+.termsWorkspace,
+.imageWorkspace {
+ @extend .workspace;
+ width: 82%;
+}
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..3f9a413f
--- /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,
+ };
+}
\ No newline at end of file
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..6c529f04
--- /dev/null
+++ b/viscoll-app/src/actions/backend/interactionActions.js
@@ -0,0 +1,198 @@
+export function changeViewMode(viewMode) {
+ return {
+ type: 'CHANGE_VIEW_MODE',
+ payload: viewMode,
+ };
+}
+
+export function changeManagerMode(managerMode) {
+ return {
+ type: 'CHANGE_MANAGER_MODE',
+ payload: managerMode,
+ };
+}
+
+export function changeTermsTab(newTab) {
+ return {
+ type: 'CHANGE_TERMS_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 && notCmd) || 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,
+ };
+}
diff --git a/viscoll-app/src/actions/backend/leafActions.js b/viscoll-app/src/actions/backend/leafActions.js
new file mode 100644
index 00000000..905c45d2
--- /dev/null
+++ b/viscoll-app/src/actions/backend/leafActions.js
@@ -0,0 +1,170 @@
+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, leafIDs) {
+ return {
+ types: [
+ 'GENERATE_FOLIO_NUMBERS_FRONTEND',
+ 'GENERATE_FOLIO_NUMBERS_SUCCESS_BACKEND',
+ 'GENERATE_FOLIO_NUMBERS_FAILED_BACKEND',
+ ],
+ payload: {
+ request: {
+ url: `/leafs/generateFolio`,
+ method: 'put',
+ data: { startNumber, leafIDs },
+ 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,
+ };
+}
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/projectActions.js b/viscoll-app/src/actions/backend/projectActions.js
new file mode 100644
index 00000000..f28b5559
--- /dev/null
+++ b/viscoll-app/src/actions/backend/projectActions.js
@@ -0,0 +1,174 @@
+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',
+ },
+ },
+ };
+}
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/termActions.js b/viscoll-app/src/actions/backend/termActions.js
new file mode 100644
index 00000000..87ea251c
--- /dev/null
+++ b/viscoll-app/src/actions/backend/termActions.js
@@ -0,0 +1,210 @@
+export function addTerm(term) {
+ /**
+ term: {
+ "project_id": "5951303fc9bf3c7b9a573a3f",
+ "id": "595130sadsadsa9bf3c7b9a573a3f"
+ "title": "some title for term",
+ "taxonomy": "Ink",
+ "description": "blue ink",
+ "uri": "https://www.test.com/",
+ "show": "true"
+ }
+ */
+ return {
+ types: [
+ 'CREATE_TERM_FRONTEND',
+ 'CREATE_TERM_SUCCESS_BACKEND',
+ 'CREATE_TERM_FAILED_BACKEND',
+ ],
+ payload: {
+ request: {
+ url: `/terms`,
+ method: 'post',
+ data: { term },
+ successMessage: 'Successfully created the term',
+ errorMessage: 'Ooops! Something went wrong',
+ },
+ },
+ };
+}
+
+export function updateTerm(termID, term) {
+ /**
+ term: {
+ "title": "some title for term",
+ "taxonomy": "Ink",
+ "description": "blue ink"
+ "uri": "https://www.test.com/",
+ }
+ */
+ return {
+ types: [
+ 'UPDATE_TERM_FRONTEND',
+ 'UPDATE_TERM_SUCCESS_BACKEND',
+ 'UPDATE_TERM_FAILED_BACKEND',
+ ],
+ payload: {
+ request: {
+ url: `/terms/${termID}`,
+ method: 'put',
+ data: { term },
+ successMessage: 'Successfully updated the term',
+ errorMessage: 'Ooops! Something went wrong',
+ },
+ },
+ };
+}
+
+export function deleteTerm(termID) {
+ return {
+ types: [
+ 'DELETE_TERM_FRONTEND',
+ 'DELETE_TERM_SUCCESS_BACKEND',
+ 'DELETE_TERM_FAILED_BACKEND',
+ ],
+ payload: {
+ request: {
+ url: `/terms/${termID}`,
+ method: 'delete',
+ successMessage: 'Successfully deleted the term',
+ errorMessage: 'Ooops! Something went wrong',
+ },
+ },
+ isUndoable: true,
+ };
+}
+
+export function linkTerm(termID, objects) {
+ /**
+ objects: [
+ {
+ "id": "5951303fc9bf3c7b9a573a3f",
+ "type": "Group"
+ },
+ ]
+ */
+ return {
+ types: [
+ 'LINK_TERM_FRONTEND',
+ 'LINK_TERM_SUCCESS_BACKEND',
+ 'LINK_TERM_FAILED_BACKEND',
+ ],
+ payload: {
+ request: {
+ url: `/terms/${termID}/link`,
+ method: 'put',
+ data: { objects },
+ successMessage: 'Successfully linked the term',
+ errorMessage: 'Ooops! Something went wrong',
+ },
+ },
+ isUndoable: true,
+ };
+}
+
+export function unlinkTerm(termID, objects) {
+ /**
+ objects: [
+ {
+ "id": "5951303fc9bf3c7b9a573a3f",
+ "type": "Group"
+ },
+ ]
+ */
+ return {
+ types: [
+ 'UNLINK_TERM_FRONTEND',
+ 'UNLINK_TERM_SUCCESS_BACKEND',
+ 'UNLINK_TERM_FAILED_BACKEND',
+ ],
+ payload: {
+ request: {
+ url: `/terms/${termID}/unlink`,
+ method: 'put',
+ data: { objects },
+ successMessage: 'Successfully unlinked the term',
+ errorMessage: 'Ooops! Something went wrong',
+ },
+ },
+ isUndoable: true,
+ };
+}
+
+export function createTaxonomy(taxonomy) {
+ /**
+ "taxonomy": {
+ "project_id": "5951303fc9bf3c7b9a573a3f",
+ "taxonomy": "Ink"
+ }
+ */
+ return {
+ types: [
+ 'CREATE_TAXONOMY_FRONTEND',
+ 'CREATE_TAXONOMY_SUCCESS_BACKEND',
+ 'CREATE_TAXONOMY_FAILED_BACKEND',
+ ],
+ payload: {
+ request: {
+ url: `/terms/taxonomy`,
+ method: 'post',
+ data: { taxonomy },
+ successMessage: 'Successfully created the term taxonomy',
+ errorMessage: 'Ooops! Something went wrong',
+ },
+ },
+ isUndoable: true,
+ };
+}
+
+export function updateTaxonomy(taxonomy) {
+ /**
+ "taxonomy": {
+ "project_id": "5951303fc9bf3c7b9a573a3f",
+ "taxonomy": "Ink",
+ "old_taxonomy": "Inkss"
+ }
+ */
+ return {
+ types: [
+ 'UPDATE_TAXONOMY_FRONTEND',
+ 'UPDATE_TAXONOMY_SUCCESS_BACKEND',
+ 'UPDATE_TAXONOMY_FAILED_BACKEND',
+ ],
+ payload: {
+ request: {
+ url: `/terms/taxonomy`,
+ method: 'put',
+ data: { taxonomy },
+ successMessage: 'Successfully updated the term taxonomy',
+ errorMessage: 'Ooops! Something went wrong',
+ },
+ },
+ isUndoable: true,
+ };
+}
+
+export function deleteTaxonomy(taxonomy) {
+ /**
+ "taxonomy": {
+ "project_id": "5951303fc9bf3c7b9a573a3f",
+ "taxonomy": "Ink"
+ }
+ */
+ return {
+ types: [
+ 'DELETE_TAXONOMY_FRONTEND',
+ 'DELETE_TAXONOMY_SUCCESS_BACKEND',
+ 'DELETE_TAXONOMY_FAILED_BACKEND',
+ ],
+ payload: {
+ request: {
+ url: `/terms/taxonomy`,
+ method: 'delete',
+ data: { taxonomy },
+ successMessage: 'Successfully deleted the term taxonomy',
+ errorMessage: 'Ooops! Something went wrong',
+ },
+ },
+ isUndoable: true,
+ };
+}
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..e9ab4cc9
--- /dev/null
+++ b/viscoll-app/src/actions/frontend/before/groupActions.js
@@ -0,0 +1,124 @@
+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 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",
+ tacketed: tacketed? tacketed : [],
+ sewing: sewing? sewing : [],
+ nestLevel: parentGroup ? parentGroup.nestLevel+1 : 1,
+ parentID: parentGroup ? parentGroup.id : null,
+ terms: [],
+ 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 Terms of deletedGroupID
+ for (let termID in state.project.Terms) {
+ deletedGroupIDIndex = state.project.Terms[termID].objects.Group.indexOf(deletedGroupID)
+ if (deletedGroupIDIndex !== -1)
+ state.project.Terms[termID].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
+}
\ No newline at end of file
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..b1218450
--- /dev/null
+++ b/viscoll-app/src/actions/frontend/before/leafActions.js
@@ -0,0 +1,340 @@
+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',
+ folio_number: null,
+ conjoined_to: null,
+ attached_above: 'None',
+ attached_below: 'None',
+ stub: action.payload.request.data.leaf.stub
+ ? action.payload.request.data.leaf.stub
+ : 'No',
+ nestLevel: parentGroup.nestLevel,
+ parentID: parentGroupID,
+ rectoID: 'Recto_' + sideIDs[sideCount],
+ versoID: 'Verso_' + sideIDs[sideCount + 1],
+ terms: [],
+ 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],
+ page_number: null,
+ texture: 'Hair',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ 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],
+ page_number: null,
+ texture: 'Flesh',
+ image: {},
+ script_direction: 'None',
+ terms: [],
+ 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 Terms of deletedLeafID. Also unlink Terms in Recto and Verso of deletedLeafID
+ for (let termID in state.project.Terms) {
+ deletedLeafIDIndex = state.project.Terms[termID].objects.Leaf.indexOf(
+ deletedLeafID
+ );
+ let rectoIDIndex = state.project.Terms[termID].objects.Recto.indexOf(
+ deletedLeaf.rectoID
+ );
+ let versoIDIndex = state.project.Terms[termID].objects.Verso.indexOf(
+ deletedLeaf.versoID
+ );
+ if (deletedLeafIDIndex !== -1)
+ state.project.Terms[termID].objects.Leaf.splice(deletedLeafIDIndex, 1);
+ if (rectoIDIndex !== -1)
+ state.project.Terms[termID].objects.Recto.splice(rectoIDIndex, 1);
+ if (versoIDIndex !== -1)
+ state.project.Terms[termID].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 generatePageNumbers(action, state) {
+ 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]];
+ recto['page_number'] = numberCount;
+ numberCount++;
+ verso['page_number'] = numberCount;
+ numberCount++;
+ }
+ return state;
+}
+
+export function generateFolioNumbers(action, state) {
+ let numberCount = action.payload.request.data.startNumber;
+ let leafIDs = action.payload.request.data.leafIDs;
+ for (const index in leafIDs) {
+ const leaf = state.project.Leafs[leafIDs[index]];
+ leaf['folio_number'] = `${numberCount}`;
+ numberCount++;
+ }
+ return state;
+}
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/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/frontend/before/termActions.js b/viscoll-app/src/actions/frontend/before/termActions.js
new file mode 100644
index 00000000..9e0d290c
--- /dev/null
+++ b/viscoll-app/src/actions/frontend/before/termActions.js
@@ -0,0 +1,130 @@
+export function createTaxonomy(action, state) {
+ const newTaxonomy = action.payload.request.data.taxonomy.taxonomy;
+ state.project.Taxonomies.push(newTaxonomy);
+ return state;
+}
+
+export function updateTaxonomy(action, state) {
+ const updatedTaxonomy = action.payload.request.data.taxonomy.taxonomy;
+ const oldTaxonomy = action.payload.request.data.taxonomy.old_taxonomy;
+ // Rename the taxonomy of each Note that had oldTaxonomy
+ for (let termID in state.project.Terms) {
+ if (state.project.Terms[termID].taxonomy === oldTaxonomy)
+ state.project.Terms[termID].taxonomy = updatedTaxonomy;
+ }
+ // Rename the taxonomy in the taxonomies array
+ const oldTaxonomyIndex = state.project.Taxonomies.indexOf(oldTaxonomy);
+ state.project.Taxonomies[oldTaxonomyIndex] = updatedTaxonomy;
+ return state;
+}
+
+export function deleteTaxonomy(action, state) {
+ const deletedTaxonomy = action.payload.request.data.taxonomy.taxonomy;
+ // Rename the taxonomy of each Note that had deleteTaxonomy to Unknown
+ for (let termID in state.project.Terms) {
+ if (state.project.Terms[termID].taxonomy === deletedTaxonomy)
+ state.project.Terms[termID].taxonomy = 'Unknown';
+ }
+ // Delete the taxonomy from the taxonomies array
+ const deletedTaxonomyIndex = state.project.Taxonomies.indexOf(deletedTaxonomy);
+ state.project.Taxonomies.splice(deletedTaxonomyIndex, 1);
+ return state;
+}
+
+export function createTerm(action, state) {
+ const newTerm = action.payload.request.data.term;
+ // Add new term to Terms
+ state.project.Terms[newTerm.id] = {
+ id: newTerm.id,
+ title: newTerm.title,
+ taxonomy: newTerm.taxonomy,
+ description: newTerm.description,
+ uri: newTerm.uri,
+ show: newTerm.show,
+ objects: { Group: [], Leaf: [], Recto: [], Verso: [] },
+ };
+ return state;
+}
+
+export function updateTerm(action, state) {
+ const updatedTermID = action.payload.request.url.split('/').pop();
+ const updatedTerm = action.payload.request.data.term;
+ // Update the term with id updatedTermID
+ state.project.Terms[updatedTermID] = {
+ ...state.project.Terms[updatedTermID],
+ ...updatedTerm,
+ };
+ return state;
+}
+
+export function linkTerm(action, state) {
+ const linkedTermID = action.payload.request.url.split('/').slice(-2)[0];
+ const linkedObjects = action.payload.request.data.objects;
+ // Update each object with linkedTermID
+ 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].terms.push(linkedTermID);
+ // Update the objects property of term with linkedTermID
+ state.project.Terms[linkedTermID].objects[object.type].push(object.id);
+ }
+ return state;
+}
+
+export function unlinkTerm(action, state) {
+ const unlinkedTermID = action.payload.request.url.split('/').slice(-2)[0];
+ const unlinkedObjects = action.payload.request.data.objects;
+ // Update each object by removing unlinkedTermID
+ for (let object of unlinkedObjects) {
+ if (object.type === 'Side')
+ object.type = object.id.charAt(0) === 'R' ? 'Recto' : 'Verso';
+ const unlinkedTermIDIndex = state.project[`${object.type}s`][
+ object.id
+ ].terms.indexOf(unlinkedTermID);
+ state.project[`${object.type}s`][object.id].terms.splice(
+ unlinkedTermIDIndex,
+ 1
+ );
+ // Update the objects property of term with unlinkedTermID
+ const unlinkedObjectIDIndex = state.project.Terms[unlinkedTermID].objects[
+ object.type
+ ].indexOf(object.id);
+ state.project.Terms[unlinkedTermID].objects[object.type].splice(
+ unlinkedObjectIDIndex,
+ 1
+ );
+ }
+ return state;
+}
+
+export function deleteTerm(action, state) {
+ const deletedTermID = action.payload.request.url.split('/').pop();
+ // Delete the reference on all Groups,Leaves,Sides that this deletedTerm had
+ for (let groupID of state.project.Terms[deletedTermID].objects.Group) {
+ const deletedTermIDIndex = state.project.Groups[groupID].terms.indexOf(
+ deletedTermID
+ );
+ state.project.Groups[groupID].terms.splice(deletedTermIDIndex, 1);
+ }
+ for (let leafID of state.project.Terms[deletedTermID].objects.Leaf) {
+ const deletedTermIDIndex = state.project.Leafs[leafID].terms.indexOf(
+ deletedTermID
+ );
+ state.project.Leafs[leafID].terms.splice(deletedTermIDIndex, 1);
+ }
+ for (let rectoID of state.project.Terms[deletedTermID].objects.Recto) {
+ const deletedTermIDIndex = state.project.Rectos[rectoID].terms.indexOf(
+ deletedTermID
+ );
+ state.project.Rectos[rectoID].terms.splice(deletedTermIDIndex, 1);
+ }
+ for (let versoID of state.project.Terms[deletedTermID].objects.Verso) {
+ const deletedTermIDIndex = state.project.Versos[versoID].terms.indexOf(
+ deletedTermID
+ );
+ state.project.Versos[versoID].terms.splice(deletedTermIDIndex, 1);
+ }
+ // Delete the term with id deletedTermID in Terms
+ delete state.project.Terms[deletedTermID];
+ return state;
+}
diff --git a/viscoll-app/src/actions/undoRedo/groupHelper.js b/viscoll-app/src/actions/undoRedo/groupHelper.js
new file mode 100644
index 00000000..e6351914
--- /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 {
+ linkTerm,
+} from '../backend/termActions';
+
+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 terms to group
+ if (group.terms.length>0) {
+ const objects = [{ id: groupID, type: "Group" }];
+ for (const termID of group.terms) {
+ const termRequest = linkTerm(termID, objects);
+ historyActions.push(termRequest);
+ }
+ }
+ 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 < leafIDs.length; i++) {
+ const leafID = leafIDs[i];
+ const latestGroup = leafNeighbours[leafNeighbours.length - 1];
+ const latestLeafID = latestGroup[latestGroup.length - 1];
+ if (
+ state.project.Leafs[latestLeafID].parentID ===
+ state.project.Leafs[leafID].parentID &&
+ state.project.leafIDs.indexOf(leafID) -
+ state.project.leafIDs.indexOf(latestLeafID) ===
+ 1
+ ) {
+ // Neighbour of last leaf, so add to same group
+ latestGroup.push(leafID);
+ } else {
+ // Make a new group
+ leafNeighbours.push([leafID]);
+ }
+ }
+ return leafNeighbours;
+}
+
+export function helperUndoDeleteLeaves(leafIDs, state) {
+ const historyActions = [];
+ // Make create requests
+ // There'll be multiple create requests if not all leaves are under the same parent or are sequential
+ const groupedLeaves = helperSeparateLeavesByGroup(leafIDs, state);
+
+ for (const leafIDs of groupedLeaves) {
+ const parentID = state.project.Leafs[leafIDs[0]].parentID;
+ const sideIDs = leafIDs
+ .map(leafID => [
+ 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['folio_number'] = leaf.folio_number;
+ 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 terms
+ 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.terms.length > 0) {
+ const objects = [{ id: leafID, type: 'Leaf' }];
+ for (const termID of leaf.terms) {
+ const termRequest = linkTerm(termID, objects);
+ historyActions.push(termRequest);
+ }
+ }
+ if (recto.terms.length > 0) {
+ const objects = [{ id: recto.id, type: 'Side' }];
+ for (const termID of recto.terms) {
+ const termRequest = linkTerm(termID, objects);
+ historyActions.push(termRequest);
+ }
+ }
+ if (verso.terms.length > 0) {
+ const objects = [{ id: verso.id, type: 'Side' }];
+ for (const termID of verso.terms) {
+ const termRequest = linkTerm(termID, objects);
+ historyActions.push(termRequest);
+ }
+ }
+ }
+ // 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 undoFolioNumbers(action, state) {
+ const leafIDs = action.payload.request.data.leafIDs;
+ const leaves = [];
+ for (const leafID of leafIDs) {
+ const item = {
+ id: leafID,
+ attributes: {
+ folio_number: state.project['Leafs'][leafID]['folio_number'],
+ },
+ };
+ leaves.push(item);
+ }
+ const historyActions = updateLeafs(leaves, state.project.id);
+ return [historyActions];
+}
+
+export function undoPageNumbers(action, state) {
+ 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: {
+ page_number: state.project[sideType][sideID]['page_number'],
+ },
+ };
+ sides.push(item);
+ }
+ const historyActions = updateSides(sides);
+ return [historyActions];
+}
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/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/actions/undoRedo/termHelper.js b/viscoll-app/src/actions/undoRedo/termHelper.js
new file mode 100644
index 00000000..1872cbcc
--- /dev/null
+++ b/viscoll-app/src/actions/undoRedo/termHelper.js
@@ -0,0 +1,96 @@
+import {
+ deleteTaxonomy,
+ updateTaxonomy,
+ createTaxonomy,
+ updateTerm,
+ unlinkTerm,
+ linkTerm,
+ addTerm,
+} from '../backend/termActions';
+
+export function undoUpdateTaxonomy(action, state) {
+ const taxonomy = {
+ project_id: state.project.id,
+ old_taxonomy: action.payload.request.data.taxonomy.taxonomy,
+ taxonomy: action.payload.request.data.taxonomy.old_taxonomy,
+ }
+ const historyAction = updateTaxonomy(taxonomy);
+ return [historyAction];
+}
+
+export function undoCreateTaxonomy(action, state) {
+ const taxonomy = {
+ project_id: state.project.id,
+ taxonomy: action.payload.request.data.taxonomy.taxonomy,
+ }
+ const historyAction = deleteTaxonomy(taxonomy);
+ return [historyAction];
+}
+
+export function undoDeleteTaxonomy(action, state) {
+ const historyActions = [];
+ // Create term taxonomy
+ const tax = action.payload.request.data.taxonomy.taxonomy;
+ const taxonomy = {
+ project_id: state.project.id,
+ tax,
+ }
+ historyActions.push(createTaxonomy(taxonomy));
+ // Update terms that had this taxonomy
+ for (const key in state.project.Terms) {
+ if (!state.project.Terms.hasOwnProperty(key)) continue;
+ if (state.project.Terms[key].taxonomy === tax) {
+ historyActions.push(updateTerm(key, {tax}));
+ }
+ }
+ return historyActions;
+}
+
+export function undoLinkTerm(action, state) {
+ const urlSplit = action.payload.request.url.split("/");
+ const termID = urlSplit[urlSplit.length-2];
+ const historyAction = unlinkTerm(termID, action.payload.request.data.objects);
+ return [historyAction];
+}
+
+export function undoUnlinkTerm(action, state) {
+ const urlSplit = action.payload.request.url.split("/");
+ const termID = urlSplit[urlSplit.length-2];
+ const historyAction = linkTerm(termID, action.payload.request.data.objects);
+ return [historyAction];
+}
+
+export function undoDeleteTerm(action, state) {
+ const historyActions = [];
+ const termID = action.payload.request.url.split("/").pop();
+ const term = state.project.Terms[termID];
+
+ // Create term
+ const termData = {
+ project_id: state.project.id,
+ id: termID,
+ title: term.title,
+ taxonomy: term.taxonomy,
+ description: term.description,
+ show: term.show,
+ }
+ historyActions.push(addTerm(termData));
+
+ // Relink leaves, groups, sides
+ const objects = [];
+ for (const id of term.objects.Group) {
+ objects.push({id, type:"Group"});
+ }
+ for (const id of term.objects.Leaf) {
+ objects.push({id, type:"Leaf"});
+ }
+ for (const id of term.objects.Recto) {
+ objects.push({id, type:"Recto"});
+ }
+ for (const id of term.objects.Verso) {
+ objects.push({id, type:"Verso"});
+ }
+ historyActions.push(linkTerm(termID, objects));
+
+ 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/upenn_logo.png b/viscoll-app/src/assets/upenn_logo.png
new file mode 100644
index 00000000..9eea6c5a
Binary files /dev/null and b/viscoll-app/src/assets/upenn_logo.png differ
diff --git a/viscoll-app/src/assets/ut_logo.png b/viscoll-app/src/assets/ut_logo.png
new file mode 100644
index 00000000..0dd831c3
Binary files /dev/null and b/viscoll-app/src/assets/ut_logo.png differ
diff --git a/viscoll-app/src/assets/vceditor_logo.png b/viscoll-app/src/assets/vceditor_logo.png
new file mode 100644
index 00000000..920603b1
Binary files /dev/null and b/viscoll-app/src/assets/vceditor_logo.png differ
diff --git a/viscoll-app/src/assets/vceditor_logo.svg b/viscoll-app/src/assets/vceditor_logo.svg
new file mode 100644
index 00000000..b024ef58
--- /dev/null
+++ b/viscoll-app/src/assets/vceditor_logo.svg
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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..ec81e825
--- /dev/null
+++ b/viscoll-app/src/assets/visualMode/PaperGroup.js
@@ -0,0 +1,158 @@
+import paper from 'paper';
+
+PaperGroup.prototype = {
+ constructor: PaperGroup,
+ draw: function () {
+ // Create rectangle shapes
+ let rectangle = new paper.Rectangle(
+ new paper.Point(this.x + 10, this.y),
+ new paper.Size(this.width - this.x - 20, this.groupHeight)
+ );
+ if (this.viewingMode) {
+ rectangle = new paper.Rectangle(
+ new paper.Point(this.x + 10, this.y),
+ new paper.Size(this.width - this.x, this.groupHeight)
+ );
+ }
+ let highlightRectangle = rectangle.clone();
+ highlightRectangle.set(
+ new paper.Point(this.x, this.y - 10),
+ new paper.Size(this.width - 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
+ );
+ },
+ // code here must mirror group_notation function in group.rb:23
+ groupNotation: function(group) {
+ // get all groupIDs as base nest level
+ let outerGroupIDs = []
+ this.groupIDs.forEach(gID => {
+ if (this.Groups[gID].nestLevel === 1) {
+ outerGroupIDs.push(gID)
+ }
+ })
+ let notation = '';
+ if (group.nestLevel === 1){
+ // get index of current group within the context of all outer groups
+ let groupOrder = outerGroupIDs.indexOf(group.id) + 1;
+ notation = `${groupOrder}`;
+ } else {
+ // get parent of current group
+ let parentGroup = this.Groups[group.parentID];
+ // get children of parent group
+ let parentGroupChildren = parentGroup.memberIDs.filter(g => g[0] === 'G');
+ let subquireNotation = parentGroupChildren.indexOf(group.id) + 1;
+ notation = `${this.groupNotation(parentGroup)}.${subquireNotation}`;
+ }
+ return notation;
+ },
+ setVisibility: function (visibleAttributes) {
+ this.visibleAttributes = visibleAttributes;
+ let groupText = this.group.type + ' ' + this.groupNotation(this.group);
+ if (this.visibleAttributes && this.visibleAttributes.title)
+ groupText = groupText + ': ' + this.group.title;
+ this.text.set({
+ content: groupText,
+ });
+ },
+};
+// Constructor for group
+function PaperGroup(args) {
+ this.manager = args.manager;
+ this.group = args.group;
+ this.Groups = args.Groups;
+ this.groupIDs = args.groupIDs;
+ this.groupOrder = args.groupIDs.indexOf(args.group.id) + 1;
+ this.notation = this.groupNotation(this.group);
+ this.y = args.y;
+ this.x = args.x;
+ 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..785f7d3b
--- /dev/null
+++ b/viscoll-app/src/assets/visualMode/PaperLeaf.js
@@ -0,0 +1,720 @@
+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 =
+ this.multiplier < 1
+ ? 10 + this.indent * this.spacing * this.multiplier
+ : this.indent * this.spacing * this.multiplier;
+ let x2 = this.width;
+ this.path.add(new paper.Point(x1, this.y));
+ if (this.leaf.stub !== 'No') {
+ x2 = this.width * 0.15 + x1;
+ }
+ 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 leafTermsToShow = this.leaf.terms
+ .filter(termID => {
+ return this.Terms[termID].show;
+ })
+ .reverse();
+ const rectoTermsToShow = this.recto.terms
+ .filter(termID => {
+ return this.Terms[termID].show;
+ })
+ .reverse();
+ const versoTermsToShow = this.verso.terms.filter(termID => {
+ return this.Terms[termID].show;
+ });
+
+ let textX = 0;
+ let textY = this.y;
+ let fontSize = this.spacing * 0.5;
+ 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 + 10;
+ }
+ if (this.leaf.attached_above.includes('Partial')) {
+ // This leaf has a partial glue attachment
+ // Place text to the right of attachment drawing
+ textX = this.attachment.bounds.right + 5;
+ } else if (this.leaf.attached_above.includes('Glued')) {
+ // Other type of glueing exists
+ textY -= this.spacing * 0.7;
+ }
+ let that = this;
+ let clickListener = function (term) {
+ return function (event) {
+ that.openTermDialog(term);
+ };
+ };
+ // Draw recto term text
+ for (let termIndex = 0; termIndex < rectoTermsToShow.length; termIndex++) {
+ const term = this.Terms[rectoTermsToShow[termIndex]];
+ const termTitle = this.folio_number
+ ? '▼ ' + this.folio_number + ' : ' + term.title.substr(0, numChars)
+ : '▼ R : ' + term.title.substr(0, numChars);
+ let textTerm = new paper.PointText({
+ content: termTitle,
+ point: [
+ textX,
+ textY - termIndex * (this.spacing * 0.7) - this.spacing * 0.3,
+ ],
+ fillColor: this.strokeColor,
+ fontSize: fontSize,
+ });
+ textTerm.onClick = !this.viewingMode && clickListener(term);
+ this.textTerms.addChild(textTerm);
+ }
+ // Draw leaf term text
+ for (let termIndex = 0; termIndex < leafTermsToShow.length; termIndex++) {
+ const term = this.Terms[leafTermsToShow[termIndex]];
+
+ let textTerm = new paper.PointText({
+ content: '▼ L' + this.order + ' : ' + term.title.substr(0, numChars),
+ point: [
+ textX,
+ textY -
+ rectoTermsToShow.length * (this.spacing * 0.7) -
+ termIndex * (this.spacing * 0.7) -
+ this.spacing * 0.3,
+ ],
+ fillColor: this.strokeColor,
+ fontSize: fontSize,
+ });
+ textTerm.onClick = !this.viewingMode && clickListener(term);
+ this.textTerms.addChild(textTerm);
+ }
+ // Draw verso term text
+ for (let termIndex = 0; termIndex < versoTermsToShow.length; termIndex++) {
+ const term = this.Terms[versoTermsToShow[termIndex]];
+ const termTitle = this.folio_number
+ ? '▲ ' + this.folio_number + ' : ' + term.title.substr(0, numChars)
+ : '▲ V : ' + term.title.substr(0, numChars);
+ let textTerm = new paper.PointText({
+ content: termTitle,
+ point: [
+ textX,
+ this.y + termIndex * (this.spacing * 0.7) + this.spacing * 0.8,
+ ],
+ fillColor: this.strokeColor,
+ fontSize: fontSize,
+ });
+ textTerm.onClick = !this.viewingMode && clickListener(term);
+ this.textTerms.addChild(textTerm);
+ }
+ this.textTerms.onMouseEnter = function (event) {
+ document.body.style.cursor = 'pointer';
+ };
+ this.textTerms.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 () {
+ // determine what attachment is drawn based on method indicated
+ if (this.order > 1) {
+ if (this.leaf.attached_above !== 'None') {
+ this.createAttachment();
+ }
+ }
+ },
+ createAttachment: function () {
+ let x = this.path.segments[0].point.x;
+ if (this.isConjoined() && this.conjoined_to < this.order) {
+ if (this.conjoined_to + 1 === this.order) {
+ x += this.strokeWidth;
+ } else {
+ x = this.prevPaperLeaf().path.segments[0].point.x;
+ }
+ }
+ if (this.leaf.attached_above.includes('Sewn')) {
+ let glueLineCount = 2;
+ // Draw tip glue
+ for (let i = 0; i < glueLineCount; i++) {
+ let glueLine = new paper.Path();
+ glueLine.add(new paper.Point(x + 10, this.y - this.spacing));
+ glueLine.add(new paper.Point(x + 10, this.y - this.spacing * 0.1));
+ glueLine.strokeColor = '#707070';
+ glueLine.strokeWidth = 2;
+ this.attachment.addChild(glueLine);
+ x += 5;
+ }
+ } else if (this.leaf.attached_above.includes('Stitched')) {
+ console.log(this)
+ // Complete stitch
+ let glueLineCount = 1;
+ for (let i = 0; i < glueLineCount; i++) {
+ let glueLine = new paper.Path();
+ glueLine.add(new paper.Point(x + 10, this.y - this.spacing));
+ glueLine.add(new paper.Point(x + 10, this.y - (this.spacing * 0.1)));
+ glueLine.strokeColor = '#707070';
+ glueLine.strokeWidth = 2;
+ this.attachment.addChild(glueLine);
+ x += 5;
+ }
+ } else if (this.leaf.attached_above.includes('Tipped')) {
+ let glueLineCount = 4;
+ // Draw tip glue
+ for (let i = 0; i < glueLineCount; i++) {
+ 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 if (this.leaf.attached_above.includes('Drummed')) {
+ let glueLineCount = 15;
+ // Draw left drum glue
+ for (let i = 0; i < glueLineCount; i++) {
+ 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;
+ }
+ if (
+ this.leaf.stub === 'No' &&
+ this.prevPaperLeaf().leaf.stub === 'No'
+ ) {
+ // Draw right drum glue if above or below are not stubs
+ x = this.path.segments[this.path.segments.length - 1].point.x;
+ for (let i = 0; i < glueLineCount; i++) {
+ let glueLine = new paper.Path();
+ glueLine.add(new paper.Point(x - 10, this.y - this.spacing * 0.3));
+ glueLine.add(new paper.Point(x, this.y - this.spacing * 0.7));
+ glueLine.strokeColor = '#707070';
+ glueLine.strokeWidth = 2;
+ this.attachment.addChild(glueLine);
+ x -= 5;
+ }
+ }
+ } else if (this.leaf.attached_above.includes('Pasted')) {
+ // Complete glue
+ if (
+ this.leaf.stub !== 'No' ||
+ this.prevPaperLeaf().leaf.stub !== 'No'
+ ) {
+ let glueLineCount = 15;
+ for (let i = 0; i < glueLineCount; i++) {
+ let glueLine = new paper.Path();
+ glueLine.add(new paper.Point(x + 10, this.y - this.spacing * 0.3));
+ glueLine.add(new paper.Point(x, this.y - this.spacing * 0.7));
+ glueLine.strokeColor = '#707070';
+ glueLine.strokeWidth = 2;
+ this.attachment.addChild(glueLine);
+ x += 5;
+ }
+ } else {
+ while (
+ x <=
+ this.path.segments[this.path.segments.length - 1].point.x - 10
+ ) {
+ let glueLine = new paper.Path();
+ glueLine.add(new paper.Point(x + 10, this.y - this.spacing * 0.3));
+ glueLine.add(new paper.Point(x, this.y - this.spacing * 0.7));
+ glueLine.strokeColor = '#707070';
+ glueLine.strokeWidth = 2;
+ this.attachment.addChild(glueLine);
+ x += 5;
+ }
+ }
+ }
+ },
+ createOtherAttachment: function () {
+ let x = this.path.segments[0].point.x;
+ if (this.isConjoined() && this.conjoined_to < this.order) {
+ if (this.conjoined_to + 1 === this.order) {
+ x += this.strokeWidth;
+ } else {
+ x = this.prevPaperLeaf().path.segments[0].point.x;
+ }
+ }
+ for (let i = 0; i < 6; i++) {
+ let attachLine = new paper.Path();
+ attachLine.add(new paper.Point(x, this.y - 10));
+ attachLine.add(new paper.Point(x, this.y - 20));
+ attachLine.strokeColor = '#707070';
+ attachLine.strokeWidth = 2;
+ this.attachment.addChild(attachLine);
+ x += 5;
+ }
+ },
+ isConjoined: function () {
+ return this.conjoined_to > 0;
+ },
+ 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 < y_center) {
+ var val = this.y_nextPaperLeaf() - this.spacing * 0.3;
+ return val;
+ } else {
+ val = this.y_prevLeaf() - this.spacing * 0.3;
+ return val;
+ }
+ },
+ prevPaperLeaf: function () {
+ // Previous leaf better exist or else!
+ return this.manager.getLeaf(this.order - 1);
+ },
+ y_prevLeaf: function () {
+ if (this.order - 1 < 0) {
+ return 0;
+ } else {
+ return this.manager.getLeaf(this.order).path.segments[1].point.y;
+ }
+ },
+ y_nextPaperLeaf: function () {
+ if (this.order + 1 >= 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 + 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;
+ } else if (this.order > 1 && this.prevPaperLeaf().parentID !== this.leaf.parentID) {
+ 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.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);
+ }
+ }
+ if (this.visibleAttributes.leaf) {
+ if (this.visibleAttributes.leaf.folio_number) {
+ let notationStyles = this.notationStyle.split('-');
+ if (this.leaf.folio_number) {
+ rectoValues.push(this.leaf.folio_number + notationStyles[0]);
+ versoValues.push(this.leaf.folio_number + notationStyles[1]);
+ }
+ }
+ }
+ let rectoContent = '';
+ let versoContent = '';
+ for (let i = 0; i < rectoValues.length; i++) {
+ rectoContent = rectoContent + rectoValues[i] + ' ';
+ }
+ for (let i = 0; i < versoValues.length; i++) {
+ versoContent = versoContent + versoValues[i] + ' ';
+ }
+
+ const reducer = (acc, key) => {
+ if (this.visibleAttributes.side[key]) return acc + 1;
+ if (this.visibleAttributes.leaf[key]) return acc + 1;
+ return acc;
+ };
+ const visibleAttributeCount = this.visibleAttributes.side
+ ? Object.keys(this.visibleAttributes.side).reduce(reducer, 0) +
+ Object.keys(this.visibleAttributes.leaf).reduce(reducer, 0)
+ : 0;
+
+ if (visibleAttributeCount === 3) {
+ if (this.leaf.stub === 'No') {
+ // Reduce leaf width so we can fit attribute text
+ this.path.segments[this.path.segments.length - 1].point.x =
+ this.width - this.spacing * 3;
+ this.highlight.segments[this.highlight.segments.length - 1].point.x =
+ this.width - this.spacing * 2.8;
+ this.filterHighlight.segments[
+ this.filterHighlight.segments.length - 1
+ ].point.x = this.width - this.spacing * 2.8;
+ }
+ this.textLeafOrder.set({
+ point: [this.width - this.spacing * 2.8, this.textLeafOrder.point.y],
+ });
+ this.textRecto.set({
+ point: [
+ this.textLeafOrder.bounds.right + this.spacing * 0.4,
+ this.textRecto.point.y,
+ ],
+ content: rectoContent,
+ });
+ this.textVerso.set({
+ point: [
+ this.textLeafOrder.bounds.right + this.spacing * 0.4,
+ this.textVerso.point.y,
+ ],
+ content: versoContent,
+ });
+ } else if (visibleAttributeCount === 2) {
+ if (this.leaf.stub === 'No') {
+ // Reduce leaf width so we can fit attribute text
+ this.path.segments[this.path.segments.length - 1].point.x =
+ this.width - this.spacing * 2;
+ this.highlight.segments[this.highlight.segments.length - 1].point.x =
+ this.width - this.spacing * 1.8;
+ this.filterHighlight.segments[
+ this.filterHighlight.segments.length - 1
+ ].point.x = this.width - this.spacing * 1.8;
+ }
+ this.textLeafOrder.set({
+ point: [this.width - this.spacing * 1.8, this.textLeafOrder.point.y],
+ });
+ this.textRecto.set({
+ point: [
+ this.textLeafOrder.bounds.right + this.spacing * 0.2,
+ this.textRecto.point.y,
+ ],
+ content: rectoContent,
+ });
+ this.textVerso.set({
+ point: [
+ this.textLeafOrder.bounds.right + this.spacing * 0.2,
+ this.textVerso.point.y,
+ ],
+ content: versoContent,
+ });
+ } else if (visibleAttributeCount === 1) {
+ if (this.leaf.stub === 'No') {
+ // Reduce leaf width so we can fit folio number text
+ this.path.segments[this.path.segments.length - 1].point.x =
+ this.width - this.spacing;
+ this.highlight.segments[this.highlight.segments.length - 1].point.x =
+ this.width - this.spacing * 0.8;
+ this.filterHighlight.segments[
+ this.filterHighlight.segments.length - 1
+ ].point.x = this.width - this.spacing * 0.8;
+ }
+ this.textLeafOrder.set({
+ point: [this.width - this.spacing * 0.8, this.textLeafOrder.point.y],
+ });
+ this.textRecto.set({
+ point: [
+ this.textLeafOrder.bounds.right + this.spacing * 0.2,
+ this.textRecto.point.y,
+ ],
+ content: rectoContent,
+ });
+ this.textVerso.set({
+ point: [
+ this.textLeafOrder.bounds.right + this.spacing * 0.2,
+ this.textVerso.point.y,
+ ],
+ content: versoContent,
+ });
+ } else {
+ // Reset leaf
+ if (this.leaf.stub === 'No') {
+ this.path.segments[this.path.segments.length - 1].point.x = this.width;
+ this.highlight.segments[this.highlight.segments.length - 1].point.x =
+ this.width + 5;
+ this.filterHighlight.segments[
+ this.filterHighlight.segments.length - 1
+ ].point.x = this.width + 5;
+ }
+ this.textLeafOrder.set({
+ point: [this.width + 10, this.textLeafOrder.point.y],
+ });
+ }
+ },
+};
+// Constructor for leaf
+function PaperLeaf(args) {
+ this.manager = args.manager;
+ this.Terms = args.Terms;
+ 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.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.textTerms = new paper.Group();
+ this.openTermDialog = args.openTermDialog;
+ this.notationStyle = args.notationStyle;
+
+ 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.2;
+ } else if (this.leaf.material === 'Paper') {
+ this.path.strokeWidth = this.path.strokeWidth * 0.8;
+ }
+}
+
+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..b10e2611
--- /dev/null
+++ b/viscoll-app/src/assets/visualMode/PaperManager.js
@@ -0,0 +1,852 @@
+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,
+ Groups: this.Groups,
+ groupIDs: this.groupIDs,
+ y: this.groupYs[this.groupIDs.indexOf(group.id)],
+ x: (this.paperLeaves.find(g => g.leaf.id === group.memberIDs[0])) ?
+ (this.paperLeaves.find(g => g.leaf.id === group.memberIDs[0]).indent - 1) * this.spacing
+ : (group.nestLevel - 1) * this.spacing,
+ // x: (group.nestLevel - 1) * this.spacing,
+ 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,
+ });
+
+ 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,
+ Terms: this.Terms,
+ 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,
+ openTermDialog: this.openTermDialog,
+ notationStyle: this.notationStyle,
+ });
+ 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.textTerms);
+ 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 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();
+ });
+
+ let nestLevel = 1;
+ while (true) {
+ let groupsAtLevel = Object.values(this.Groups).filter(g => g.nestLevel === nestLevel);
+ if (groupsAtLevel.length === 0) {
+ break;
+ }
+ for (let key in groupsAtLevel) {
+ const g = groupsAtLevel[key];
+ this.createGroup(g);
+ }
+ nestLevel++;
+ }
+
+ // 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();
+ },
+ tacketTargetLeaves: function (groupID, targets) {
+ 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();
+ }
+ } else if (memberID.charAt(0) === 'G') {
+ this.tacketTargetLeaves(memberID, targets)
+ }
+ })
+ },
+ 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 = [];
+ this.tacketTargetLeaves(groupID, targets)
+ };
+ this.tool.activate();
+ },
+ drawTacketGuide: function (groupID, type) {
+ const targetGroup = this.paperGroups.find(member => {
+ return member.group.id === groupID;
+ });
+ const guideY = targetGroup.path.bounds.height / 2;
+ const guideX = targetGroup.path.bounds.left;
+ 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 + 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 - 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 - 10,
+ guideLine.segments[1].point.y + 10
+ );
+ let guideLineX1 = new paper.Path();
+ guideLineX1.strokeColor = '#ffffff';
+ guideLineX1.strokeWidth = 3;
+ guideLineX1.add(
+ new paper.Point(guideX - 10, guideLine.segments[0].point.y - 10)
+ );
+ guideLineX1.add(
+ new paper.Point(guideX + 10, guideLine.segments[0].point.y + 10)
+ );
+ let guideLineX2 = new paper.Path();
+ guideLineX2.strokeColor = '#ffffff';
+ guideLineX2.strokeWidth = 3;
+ guideLineX2.add(
+ new paper.Point(guideX - 10, guideLine.segments[0].point.y + 10)
+ );
+ guideLineX2.add(
+ new paper.Point(guideX + 10, guideLine.segments[0].point.y - 10)
+ );
+
+ const drawType = type === 'tacketed' ? 'TACKET' : 'SEWING';
+ let guideText = new paper.PointText({
+ content: 'DRAW ' + drawType + ' LINE',
+ point: [guideX + 20, targetGroup.path.bounds.y + guideY - 20],
+ fillColor: '#000000',
+ fontSize: 12,
+ });
+ let guideTextRectangle = new paper.Rectangle(
+ new paper.Point(guideX + 15, targetGroup.path.bounds.y + guideY - 35),
+ new paper.Size(guideText.bounds.width + 10, 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 => {
+ 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 - 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 + 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 => {
+ 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 - 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 + this.strokeWidth, endY - 2));
+ tacketPath1.add(
+ new paper.Point(
+ tacketPath1.segments[1].point.x + 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 + this.strokeWidth, endY + 2));
+ tacketPath2.add(
+ new paper.Point(
+ tacketPath2.segments[1].point.x + 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];
+ },
+ getGroup: function (group_order) {
+ return this.paperGroups[group_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 termsToShowAbove = memberObject.terms.filter(termID => {
+ return this.Terms[termID].show;
+ }).length;
+ let termsToShowBelow = 0;
+ let glueSpacing = 0;
+ if (memberObject.memberType === 'Leaf') {
+ // Find if it has side terms
+ termsToShowAbove += this.Rectos[memberObject.rectoID].terms.filter(
+ termID => {
+ return this.Terms[termID].show;
+ }
+ ).length;
+ termsToShowBelow += this.Versos[memberObject.versoID].terms.filter(
+ termID => {
+ return this.Terms[termID].show;
+ }
+ ).length;
+ // Find if leaf has glue that's not a partial glue
+ glueSpacing =
+ termsToShowAbove > 0 &&
+ memberObject.attached_above.includes('Glued') &&
+ !memberObject.attached_above.includes('Partial')
+ ? 1
+ : 0;
+ }
+
+ if (
+ memberObject.memberType === 'Leaf' &&
+ getMemberOrder(memberObject, this.Groups, this.groupIDs) === 1 &&
+ termsToShowAbove > 0
+ ) {
+ // First leaf in the group with a term
+ this.multipliers[
+ this.leafIDs.indexOf(memberObject.id) + 1
+ ] = multiplier;
+ currentY = currentY + spacing * (termsToShowAbove + 1);
+ this.leafYs.push(currentY);
+ currentY = currentY + spacing * termsToShowBelow * 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, termsToShowAbove) +
+ 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 * termsToShowBelow * 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.Terms = project.Terms;
+ },
+ 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.Terms = args.Terms;
+ 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.openTermDialog = args.openTermDialog;
+ this.leafIDs = args.leafIDs;
+ this.notationStyle = args.notationStyle;
+
+ let that = this;
+ // Flash newly added items
+ paper.view.onFrame = function (event) {
+ for (let i = 0; i < that.flashLeaves.length; i++) {
+ that.flashLeaves[i].highlight.opacity = Math.min(
+ 0.8,
+ that.flashLeaves[i].highlight.opacity + 0.05
+ );
+ }
+ for (let i = 0; i < that.flashGroups.length; i++) {
+ that.flashGroups[i].highlight.opacity = Math.min(
+ 0.8,
+ that.flashGroups[i].highlight.opacity + 0.05
+ );
+ }
+ if (that.tacketToolIsActive) {
+ if (that.slideForward) {
+ that.groupTacketGuideLine.position.x += 0.5;
+ if (
+ that.groupTacketGuideLine.position.x >
+ 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..9acb4bae
--- /dev/null
+++ b/viscoll-app/src/assets/visualMode/export/PaperGroup.js
@@ -0,0 +1,120 @@
+import paper from 'paper';
+
+PaperGroup.prototype = {
+ constructor: PaperGroup,
+ draw: function() {
+ // Create rectangle shapes
+ let rectangle = new paper.Rectangle(
+ new paper.Point(this.x+10, this.y),
+ new paper.Size(this.width-this.x-20, this.groupHeight)
+ );
+ if (this.viewingMode) {
+ rectangle = new paper.Rectangle(
+ new paper.Point(this.x+10, this.y),
+ new paper.Size(this.width-this.x, this.groupHeight)
+ );
+ }
+ let highlightRectangle = rectangle.clone();
+ highlightRectangle.set(
+ new paper.Point(this.x, this.y-10),
+ new paper.Size(this.width-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,
+ });
+ },
+}
+// Constructor for group
+function PaperGroup(args) {
+ this.manager = args.manager;
+ this.group = args.group;
+ this.groupOrder = args.allGroupIDs.indexOf(args.group.id)+1
+ this.y = args.y;
+ this.x = args.x;
+ 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;
\ No newline at end of file
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..1a6a79fe
--- /dev/null
+++ b/viscoll-app/src/assets/visualMode/export/PaperLeaf.js
@@ -0,0 +1,567 @@
+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 = this.multiplier<1? 10 + this.indent*this.spacing*this.multiplier : this.indent*this.spacing*this.multiplier;
+ let x2 = this.width;
+ this.path.add(new paper.Point(x1, this.y));
+ if (this.leaf.stub !== "No") {
+ x2 = this.width*0.15+x1;
+ }
+ 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 leafTermsToShow = this.leaf.terms.filter((termID)=>{return this.Terms[termID].show}).reverse();
+ const rectoTermsToShow = this.recto.terms.filter((termID)=>{return this.Terms[termID].show}).reverse();
+ const versoTermsToShow = this.verso.terms.filter((termID)=>{return this.Terms[termID].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 + 10;
+ }
+ if (this.leaf.attached_above.includes("Partial")) {
+ // This leaf has a partial glue attachment
+ // Place text to the right of attachment drawing
+ textX = this.attachment.bounds.right+5;
+ } else if (this.leaf.attached_above.includes("Glued")) {
+ // Other type of glueing exists
+ textY -= this.spacing*0.7;
+ }
+ let that = this;
+ let clickListener = function(term) {
+ return function(event) {
+ that.openTermDialog(term);
+ }
+ }
+ if (this.showTerms) {
+ // Draw recto term text
+ for (let termIndex = 0; termIndex < rectoTermsToShow.length; termIndex++) {
+ const term = this.Terms[rectoTermsToShow[termIndex]];
+ const termTitle = this.recto.folio_number? "▼ " + this.recto.folio_number + " : " + term.title.substr(0,numChars) : "▼ R : " + term.title.substr(0,numChars) ;
+ let textTerm = new paper.PointText({
+ content: termTitle,
+ point: [textX, textY - termIndex*(this.spacing*0.7) - this.spacing*0.3],
+ fillColor: this.strokeColor,
+ fontSize: fontSize,
+ });
+ textTerm.onClick = !this.viewingMode && clickListener(term);
+ this.textTerms.addChild(textTerm);
+ }
+ // Draw leaf term text
+ for (let termIndex = 0; termIndex < leafTermsToShow.length; termIndex++) {
+ const term = this.Terms[leafTermsToShow[termIndex]];
+
+ let textTerm = new paper.PointText({
+ content: "▼ L" + this.order + " : " + term.title.substr(0,numChars),
+ point: [textX, textY - rectoTermsToShow.length*(this.spacing*0.7) - termIndex*(this.spacing*0.7) - this.spacing*0.3],
+ fillColor: this.strokeColor,
+ fontSize: fontSize,
+ });
+ textTerm.onClick = !this.viewingMode && clickListener(term);
+ this.textTerms.addChild(textTerm);
+ }
+ // Draw verso term text
+ for (let termIndex = 0; termIndex < versoTermsToShow.length; termIndex++) {
+ const term = this.Terms[versoTermsToShow[termIndex]];
+ const termTitle = this.verso.folio_number? "▲ " + this.verso.folio_number + " : " + term.title.substr(0,numChars) : "▲ V : " + term.title.substr(0,numChars);
+ let textTerm = new paper.PointText({
+ content: termTitle,
+ point: [textX, this.y + termIndex*(this.spacing*0.7) + this.spacing*0.8],
+ fillColor: this.strokeColor,
+ fontSize: fontSize,
+ });
+ textTerm.onClick = !this.viewingMode && clickListener(term);
+ this.textTerms.addChild(textTerm);
+ }
+ this.textTerms.onMouseEnter = function(event) {
+ document.body.style.cursor = "pointer";
+ }
+ this.textTerms.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_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 + 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) {
+ if (this.leaf.stub === "No") {
+ // Reduce leaf width so we can fit attribute text
+ this.path.segments[this.path.segments.length-1].point.x = this.width-this.spacing*3;
+ this.highlight.segments[this.highlight.segments.length-1].point.x = this.width-this.spacing*2.8;
+ this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x = this.width-this.spacing*2.8;
+ }
+ this.textLeafOrder.set({point: [this.width-this.spacing*2.8, this.textLeafOrder.point.y]});
+ this.textRecto.set({point:[this.textLeafOrder.bounds.right+this.spacing*0.4, this.textRecto.point.y], content: rectoContent});
+ this.textVerso.set({point:[this.textLeafOrder.bounds.right+this.spacing*0.4, this.textVerso.point.y], content: versoContent});
+ } else if (visibleAttributeCount===2) {
+ if (this.leaf.stub === "No") {
+ // Reduce leaf width so we can fit attribute text
+ this.path.segments[this.path.segments.length-1].point.x = this.width-this.spacing*2;
+ this.highlight.segments[this.highlight.segments.length-1].point.x = this.width-this.spacing*1.8;
+ this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x = this.width-this.spacing*1.8;
+ }
+ this.textLeafOrder.set({point: [this.width-this.spacing*1.8, this.textLeafOrder.point.y]});
+ this.textRecto.set({point:[this.textLeafOrder.bounds.right+this.spacing*0.2, this.textRecto.point.y], content: rectoContent});
+ this.textVerso.set({point:[this.textLeafOrder.bounds.right+this.spacing*0.2, this.textVerso.point.y], content: versoContent});
+ }
+ else if (visibleAttributeCount===1) {
+ if (this.leaf.stub === "No") {
+ // Reduce leaf width so we can fit folio number text
+ this.path.segments[this.path.segments.length-1].point.x = this.width - this.spacing;
+ this.highlight.segments[this.highlight.segments.length-1].point.x = this.width - this.spacing*0.8;
+ this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x = this.width - this.spacing*0.8;
+ }
+ this.textLeafOrder.set({point: [this.width-this.spacing*0.8, this.textLeafOrder.point.y]});
+ this.textRecto.set({point:[this.textLeafOrder.bounds.right+this.spacing*0.2, this.textRecto.point.y], content: rectoContent});
+ this.textVerso.set({point:[this.textLeafOrder.bounds.right+this.spacing*0.2, this.textVerso.point.y], content: versoContent});
+ } else {
+ // Reset leaf
+ if (this.leaf.stub === "No") {
+ this.path.segments[this.path.segments.length-1].point.x = this.width;
+ this.highlight.segments[this.highlight.segments.length-1].point.x = this.width + 5;
+ this.filterHighlight.segments[this.filterHighlight.segments.length-1].point.x = this.width + 5;
+ }
+ this.textLeafOrder.set({point: [this.width+10, this.textLeafOrder.point.y]});
+ }
+ },
+}
+// Constructor for leaf
+function PaperLeaf(args) {
+ this.manager = args.manager;
+ this.Terms = args.Terms;
+ 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.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.textTerms = new paper.Group();
+ this.openTermDialog = args.openTermDialog;
+ this.showTerms = args.showTerms;
+
+ 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..d7d3186e
--- /dev/null
+++ b/viscoll-app/src/assets/visualMode/export/PaperManager.js
@@ -0,0 +1,709 @@
+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,
+ y: this.groupYs[this.groupIDs.indexOf(group.id)],
+ x: (group.nestLevel-1)*(this.spacing),
+ 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,
+ });
+
+ 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,
+ Terms: this.Terms,
+ 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,
+ openTermDialog: this.openTermDialog,
+ showTerms: this.showTerms,
+ });
+ 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.textTerms);
+ 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 targetGroup = this.paperGroups.find((member)=>{return (member.group.id===groupID)});
+ const guideY = targetGroup.path.bounds.height/2;
+ const guideX = targetGroup.path.bounds.left;
+ 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+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-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-10, guideLine.segments[1].point.y+10);
+ let guideLineX1 = new paper.Path();
+ guideLineX1.strokeColor = "#ffffff";
+ guideLineX1.strokeWidth = 3;
+ guideLineX1.add(new paper.Point(guideX-10, guideLine.segments[0].point.y-10));
+ guideLineX1.add(new paper.Point(guideX+10, guideLine.segments[0].point.y+10));
+ let guideLineX2 = new paper.Path();
+ guideLineX2.strokeColor = "#ffffff";
+ guideLineX2.strokeWidth = 3;
+ guideLineX2.add(new paper.Point(guideX-10, guideLine.segments[0].point.y+10));
+ guideLineX2.add(new paper.Point(guideX+10, guideLine.segments[0].point.y-10));
+
+ const drawType = type==="tacketed"? "TACKET" : "SEWING";
+ let guideText = new paper.PointText({
+ content: "DRAW " + drawType + " LINE",
+ point: [guideX+20, targetGroup.path.bounds.y + guideY - 20],
+ fillColor: "#000000",
+ fontSize: 12,
+ });
+ let guideTextRectangle = new paper.Rectangle(
+ new paper.Point(guideX+15, targetGroup.path.bounds.y + guideY - 35),
+ new paper.Size(guideText.bounds.width + 10, 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)=> {
+ 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-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+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)=> {
+ 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-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+this.strokeWidth, endY-2));
+ tacketPath1.add(new paper.Point(tacketPath1.segments[1].point.x+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+this.strokeWidth, endY+2));
+ tacketPath2.add(new paper.Point(tacketPath2.segments[1].point.x+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 termsToShowAbove = memberObject.terms.filter((termID)=>{return this.Terms[termID].show&&this.showTerms}).length;
+ let termsToShowBelow = 0;
+ let glueSpacing = 0;
+ if (memberObject.memberType==="Leaf") {
+ // Find if it has side terms
+ termsToShowAbove += this.Rectos[memberObject.rectoID].terms.filter((termID)=>{return this.Terms[termID].show&&this.showTerms}).length;
+ termsToShowBelow += this.Versos[memberObject.versoID].terms.filter((termID)=>{return this.Terms[termID].show&&this.showTerms}).length;
+ // Find if leaf has glue that's not a partial glue
+ glueSpacing = (termsToShowAbove>0 && memberObject.attached_above.includes("Glued") && !memberObject.attached_above.includes("Partial"))? 1 : 0;
+ }
+
+ if (memberObject.memberType === "Leaf" && getMemberOrder(memberObject, this.Groups, this.groupIDs)===1 && termsToShowAbove>0) {
+ // First leaf in the group with a term
+ this.multipliers[this.leafIDs.indexOf(memberObject.id)+1] = multiplier;
+ currentY = currentY + spacing*(termsToShowAbove+1);
+ this.leafYs.push(currentY);
+ currentY = currentY + spacing*termsToShowBelow*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,termsToShowAbove)) + 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*termsToShowBelow*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.Terms = project.Terms;
+ },
+ 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.Terms = args.Terms;
+ 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.openTermDialog = args.openTermDialog;
+ this.leafIDs = args.leafIDs;
+ this.showTerms = args.showTerms;
+
+ 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..4d22faca
--- /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.Terms!==nextProps.project.Terms ||
+ 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.showTerms !== nextProps.showTerms
+ );
+ }
+
+ 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,
+ Terms: this.props.project.Terms,
+ 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,
+ openTermDialog: this.props.openTermDialog,
+ showTerms: this.props.showTerms
+ })
+ )
+ }
+ 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..f3e3e61a
--- /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.Terms.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..21a347ff
--- /dev/null
+++ b/viscoll-app/src/components/collationManager/ViewingMode.js
@@ -0,0 +1,146 @@
+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,
+ Terms: this.props.project.Terms,
+ 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,
+ notationStyle: this.props.project.notationStyle,
+ })
+ }, ()=>{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.Terms!==nextProps.project.Terms ||
+ 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..c7a146a7
--- /dev/null
+++ b/viscoll-app/src/components/collationManager/VisualMode.js
@@ -0,0 +1,174 @@
+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,
+ Terms: this.props.project.Terms,
+ 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,
+ openTermDialog: this.props.openTermDialog,
+ notationStyle: this.props.project.notationStyle,
+ }),
+ },
+ () => {
+ 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.Terms !== nextProps.project.Terms ||
+ 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/TermDialog.js b/viscoll-app/src/components/collationManager/dialog/TermDialog.js
new file mode 100644
index 00000000..e359fcfe
--- /dev/null
+++ b/viscoll-app/src/components/collationManager/dialog/TermDialog.js
@@ -0,0 +1,122 @@
+import React from 'react';
+import EditTermForm from '../../termsManager/EditTermForm';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+
+/** Term dialog */
+export default class TermDialog extends React.Component {
+ getLinkedGroups = () => {
+ const groupsWithCurrentTerm = Object.keys(this.props.Groups).filter((groupID) => {
+ return (this.props.Groups[groupID].terms.includes(this.props.activeTerm.id))
+ });
+ return groupsWithCurrentTerm.map((value) => {
+ const label = `Group ${this.props.groupIDs.indexOf(value)+1}`;
+ return {label, value};
+ });
+ }
+
+ getLinkedLeaves = () => {
+ const leafsWithCurrentTerm = Object.keys(this.props.Leafs).filter((leafID) => {
+ return (this.props.Leafs[leafID].terms.includes(this.props.activeTerm.id))
+ });
+ return leafsWithCurrentTerm.map((value)=>{
+ const label = `Leaf ${this.props.leafIDs.indexOf(value)+1}`;
+ return {label, value};
+ });
+ }
+
+ getLinkedSides = () => {
+ const rectosWithCurrentTerm = Object.keys(this.props.Rectos).filter((rectoID) => {
+ return (this.props.Rectos[rectoID].terms.includes(this.props.activeTerm.id))
+ });
+ const versosWithCurrentTerm = Object.keys(this.props.Versos).filter((versoID) => {
+ return (this.props.Versos[versoID].terms.includes(this.props.activeTerm.id))
+ });
+ const sidesWithCurrentTerm = [];
+ for (let value of rectosWithCurrentTerm){
+ 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}`;
+ sidesWithCurrentTerm.push({label, value})
+ }
+ for (let value of versosWithCurrentTerm){
+ 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}`;
+ sidesWithCurrentTerm.push({label, value})
+ }
+ return sidesWithCurrentTerm;
+ }
+
+ 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..31709b1f
--- /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.GroupsOfMatchingTerms;
+ 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..5a7fc48a
--- /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.LeafsOfMatchingTerms;
+ 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..dcdef6ca
--- /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.SidesOfMatchingTerms;
+ 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..3785abbc
--- /dev/null
+++ b/viscoll-app/src/components/dashboard/ImportProject.js
@@ -0,0 +1,164 @@
+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..f71e927d
--- /dev/null
+++ b/viscoll-app/src/components/dashboard/NewProjectContainer.js
@@ -0,0 +1,358 @@
+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: '',
+ notationStyle: 'r-v',
+ date: '',
+ quireNo: 2,
+ leafNo: 10,
+ conjoined: true,
+ startFolioPageNumber: 1,
+ generateFolioNumber: 'folio_number',
+ generatePageNumber: 'page_number',
+ 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: '',
+ notationStyle: '',
+ date: '',
+ quireNo: 1,
+ leafNo: 10,
+ conjoined: true,
+ startFolioPageNumber: 1,
+ generateFolioNumber: null,
+ generatePageNumber: 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 < parseInt(this.state.quireNo, 10); i++) {
+ newCollationGroups.push({
+ number: newCollationGroups.length + 1,
+ leaves: this.state.leafNo,
+ conjoin: this.state.conjoined,
+ oddLeaf: 1,
+ });
+ }
+ this.setState({ collationGroups: newCollationGroups });
+ };
+
+ /**
+ * Update a group
+ * @param {object} event
+ * @param {object} updatedGroup
+ * @param {string} field
+ * @param {string} value
+ * @public
+ */
+ onInputChangeCollationGroupsRows = (event, updatedGroup, field, value) => {
+ 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 });
+ };
+
+ finish = () => {
+ const user = {
+ id: this.props.user.id,
+ token: this.props.user.token,
+ };
+ let request = {
+ project: {
+ title: this.state.title,
+ shelfmark: this.state.shelfmark,
+ notationStyle: this.state.notationStyle,
+ metadata: {
+ date: this.state.date,
+ },
+ preferences: {
+ showTips: true,
+ side:
+ this.state.generatePageNumber !== null
+ ? { [this.state.generatePageNumber]: true }
+ : {},
+ leaf:
+ this.state.generateFolioNumber !== null
+ ? { [this.state.generateFolioNumber]: true }
+ : {},
+ },
+ },
+ groups: [],
+ folioNumber:
+ // generateFolioNumber and generatePageNumber could be replaced by a single
+ // generateFolioPageNumber variable that is assigned the correct value based on state
+ // either 'folio_number' or 'page_number'
+ this.state.generateFolioNumber === 'folio_number'
+ ? this.state.startFolioPageNumber
+ : null,
+ pageNumber:
+ this.state.generatePageNumber === 'page_number'
+ ? this.state.startFolioPageNumber
+ : null,
+ startingTexture: this.state.startingTexture,
+ };
+ this.state.collationGroups.forEach(group => request.groups.push(group));
+ this.props.createProject(request, user);
+ this.reset();
+ this.props.close();
+ console.log(request);
+ };
+
+ 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,
+ conjoined: true,
+ collationGroups: [],
+ })
+ }
+ nextStep={() => this.set('step', 4)}
+ set={this.set}
+ quireNo={this.state.quireNo}
+ leafNo={this.state.leafNo}
+ conjoined={this.state.conjoined}
+ collationGroups={this.state.collationGroups}
+ handleToggleConjoin={this.handleToggleConjoin}
+ 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..ecf0afdf
--- /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 VCEditor (JSON Only)
+
+
+
+
+
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..8b7e1f7a
--- /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..bd342d91
--- /dev/null
+++ b/viscoll-app/src/components/dashboard/ProjectOptions.js
@@ -0,0 +1,171 @@
+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 => {
+
+ let handleChange = (e, v) => {
+ // assigning values to null would not have to happen if generateFolioNumber
+ // and generatePageNumber were combined into a single variable
+ if (v === 'folio_number') {
+ props.set('generateFolioNumber', v);
+ props.set('generatePageNumber', null)
+ } else if (v === 'page_number') {
+ props.set('generatePageNumber', v);
+ props.set('generateFolioNumber', null)
+ } else {
+ console.log(e, v);
+ }
+ };
+
+ return (
+
+
Project Options
+
Generate page/folio numbers
+
+ handleChange(e, v)}
+ style={{width: 240}}
+ >
+
+
+
+
+
+ {props.generateFolioNumber || props.generatePageNumber ? (
+
+
+ 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}
+ />
+
+
+ Select side notation style
+
+
+
+
+
+ {
+ props.set('notationStyle', e);
+ console.log(e);
+ }}
+ width={250}
+ />
+
+
+
+ props.previousStep()}
+ />
+ props.finish()}
+ />
+
+
+ );
+};
+export default ProjectOptions;
diff --git a/viscoll-app/src/components/dashboard/ProjectStructure.js b/viscoll-app/src/components/dashboard/ProjectStructure.js
new file mode 100644
index 00000000..6d9c279b
--- /dev/null
+++ b/viscoll-app/src/components/dashboard/ProjectStructure.js
@@ -0,0 +1,190 @@
+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.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.
+
+
+ Note: you can change this later.
+
+
+
+ # 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"
+ />
+
+
+ props.set("conjoined", !props.conjoined)}
+ />
+
+
+ props.addCollationRows()}
+ primary
+ />
+
+
+ {collationGroupsRows.length>0?
+
+
+
+
+ Quire no.
+ Number of leaves
+ 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..baf0a2d1
--- /dev/null
+++ b/viscoll-app/src/components/export/Export.js
@@ -0,0 +1,156 @@
+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';
+
+/** Dialog to export collation to JSON, XML or PNG */
+const Export = props => {
+ const filename = props.projectTitle.replace(/\s/g, '_');
+ const actions = [
+ }
+ style={
+ props.exportedImages
+ ? { marginRight: 10 }
+ : { display: 'none' }
+ }
+ onClick={() => {
+ downloadZip();
+ }}
+ />,
+ }
+ style={props.exportedType === 'xml' ? { marginRight: 10 } : { display: 'none' }}
+ onClick={() => fileDownload(props.exportedData, `${filename}.${props.exportedType}`)}
+ />,
+ }
+ style={
+ props.exportedImages ||
+ props.exportedType === 'xml' ||
+ props.exportedType === 'share'
+ ? { display: 'none' }
+ : { marginRight: 10 }
+ }
+ onClick={() =>
+ downloadZip()
+ }
+ />,
+ 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}_${props.exportedType}.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.exportedType === 'svg' ? (
+ //
+ // {Array.from(props.exportedData)
+ // .map((value, index) => {
+ // return (
+ //
+ // );
+ // })}
+ //
+
Please download your SVGs below.
+ ) : (
+
{props.exportedData}
+ )}
+
+ ) : (
+
+ Please download your PNGs below.
+
+
+
+
+
+ );
+ 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..5bd146d0
--- /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 [];
+ }
+ }
+
+ mapTermAttributeMenuItems = (taxonomy, index) => {
+ return { key:taxonomy+index, value:taxonomy, text:taxonomy }
+ }
+
+ 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.Taxonomies.map((taxonomy) => {
+ return {text: taxonomy, value: taxonomy}
+ })
+ 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:"term",text:"Term"}]}
+ />
+
+
+ {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:"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..eef303b9
--- /dev/null
+++ b/viscoll-app/src/components/global/AppLoadingScreen.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import logoImg from '../../assets/vceditor_logo.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/CacheBuster.js b/viscoll-app/src/components/global/CacheBuster.js
new file mode 100644
index 00000000..ecb28633
--- /dev/null
+++ b/viscoll-app/src/components/global/CacheBuster.js
@@ -0,0 +1,65 @@
+import React from 'react'
+import packageJson from '../../../package.json'
+
+global.appVersion = packageJson.version;
+
+// version from response is the first param
+// local version is the second param
+const semverGreaterThan = (versionA, versionB) => {
+ const versionsA = versionA.split(/\./g);
+ const versionsB = versionB.split(/\./g);
+
+ while (versionsA.length || versionsB.length) {
+ const a = Number(versionsA.shift());
+ const b = Number(versionsB.shift());
+
+ if (a === b) continue;
+ return a > b || isNaN(b);
+ }
+ return false;
+};
+
+class CacheBuster extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loading: true,
+ isLatestVersion: false,
+ refreshCacheAndReload: () => {
+ console.log('Clearing cache and hard reloading...')
+ if (caches) {
+ caches.keys().then(function (names) {
+ for (let name of names) caches.delete(name)
+ })
+ }
+ // delete cache and reload
+ window.location.reload(true);
+ }
+ }
+ }
+
+ componentDidMount() {
+ fetch('/meta.json')
+ .then((response) => response.json())
+ .then((meta) => {
+ const latestVersion = meta.version; // from latest build
+ const currentVersion = global.appVersion; //cached browser version
+
+ const shouldForceRefresh = semverGreaterThan(latestVersion, currentVersion);
+ if (shouldForceRefresh) {
+ console.log(`We have a new version - ${latestVersion}. Should force refresh`)
+ this.setState({ loading: false, isLatestVersion: false })
+ } else {
+ console.log(`You already have the latest version - ${latestVersion}. No cache refresh needed.`)
+ this.setState({ loading: false, isLatestVersion: true })
+ }
+ })
+ }
+
+ render() {
+ const { loading, isLatestVersion, refreshCacheAndReload } = this.state;
+ return this.props.children({ loading, isLatestVersion, refreshCacheAndReload });
+ }
+}
+
+export default CacheBuster;
\ No newline at end of file
diff --git a/viscoll-app/src/components/global/ImageViewer.js b/viscoll-app/src/components/global/ImageViewer.js
new file mode 100644
index 00000000..78183d61
--- /dev/null
+++ b/viscoll-app/src/components/global/ImageViewer.js
@@ -0,0 +1,118 @@
+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 = [];
+ // .+?(?=full)
+ // https://exhibits.library.stonybrook.edu/iiif-img/3/5846/full/800,476/0/default.jpg
+ if (this.props.rectoURL && !this.props.isRectoDIY) {
+ tilesSources.push(this.getInfoURL(this.props.rectoURL));
+ } 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.getInfoURL(this.props.versoURL))
+ } 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.4.2/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);
+ }
+
+ getInfoURL(url) {
+ if (url.includes("full")) {
+ return /.+?(?=\/full)/.exec(url)[0] + '/info.json'
+ } else {
+ return url + '/info.json'
+ }
+ }
+
+ addTiles(rDIY, vDIY, r, v) {
+ if (this.state.osd) {
+ let tilesSources = [];
+ if (r && !rDIY) {
+ tilesSources.push(this.getInfoURL(r))
+ } else if (r && rDIY) {
+ tilesSources.push({type: "image", url: r});
+ }
+ if (v && !vDIY) {
+ tilesSources.push(this.getInfoURL(v))
+ } 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..ae6f8415
--- /dev/null
+++ b/viscoll-app/src/components/global/ManagersPanel.js
@@ -0,0 +1,48 @@
+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('termsManager')}
+ tabIndex={props.popUpActive ? -1 : 0}
+ aria-label="Taxonomies Manager"
+ >
+ Taxonomies
+
+ props.changeManagerMode('imageManager')}
+ tabIndex={props.popUpActive ? -1 : 0}
+ aria-label="Image Manager"
+ >
+ Images
+
+
+ );
+};
+export default ManagersPanel;
diff --git a/viscoll-app/src/components/global/NetworkErrorScreen.js b/viscoll-app/src/components/global/NetworkErrorScreen.js
new file mode 100644
index 00000000..ebe84c71
--- /dev/null
+++ b/viscoll-app/src/components/global/NetworkErrorScreen.js
@@ -0,0 +1,48 @@
+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 healthCheckURL = API_URL + '/status';
+ let apiRequest = () => {
+ fetch(healthCheckURL)
+ .then(() => {
+ if (that.state.isOffline) that.setState({isOffline:false});
+ }).catch(e=> {
+ if (!that.state.isOffline) that.setState({isOffline:true});
+ });
+ }
+ setInterval(apiRequest, 20000);
+ }
+
+ 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 VCEditor. 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..f220c56e
--- /dev/null
+++ b/viscoll-app/src/components/global/ServerErrorScreen.js
@@ -0,0 +1,47 @@
+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..acb99b42
--- /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 manifest
+
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..8a9cc02f
--- /dev/null
+++ b/viscoll-app/src/components/imageManager/EditManifest.js
@@ -0,0 +1,267 @@
+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';
+import {getPrimaryManifestName, getTrimmedPrimaryManifestName, isManifestNameUpdateValid} from './manifestName';
+
+/** Dialog to edit manifest name */
+export default class EditManifest extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ name: props.manifest? getPrimaryManifestName(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: getPrimaryManifestName(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: getTrimmedPrimaryManifestName(this.state.name)}
+ 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 && getTrimmedPrimaryManifestName(manifest.name)===getTrimmedPrimaryManifestName(this.state.name)){
+ this.setState({nameError: `Manifest with name: ${getPrimaryManifestName(manifest.name)} already exists.`});
+ return;
+ } else if (getPrimaryManifestName(this.state.name).length>51) {
+ this.setState({nameError: `Manifest name must be under 50 characters.`});
+ return;
+ }
+ }
+ // No validation errors
+ this.setState({nameError: ""});
+ }
+
+ isValid = () => {
+ return isManifestNameUpdateValid(this.state.nameError, this.state.name, this.props.manifest.name)
+ }
+
+ 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..a3b75ffa
--- /dev/null
+++ b/viscoll-app/src/components/imageManager/ManageManifests.js
@@ -0,0 +1,137 @@
+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..6724d493
--- /dev/null
+++ b/viscoll-app/src/components/imageManager/MapImages.js
@@ -0,0 +1,447 @@
+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);
+ if (manifest[0] === "DIYImages") {
+ imageBacklog = imageBacklog.sort((a, b) => (a.label > b.label) ? 1 : -1);
+ }
+ }
+ 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/manifestName.js b/viscoll-app/src/components/imageManager/manifestName.js
new file mode 100644
index 00000000..923a5456
--- /dev/null
+++ b/viscoll-app/src/components/imageManager/manifestName.js
@@ -0,0 +1,12 @@
+export const getPrimaryManifestName = (name) => {
+ const primaryName = [name].flat()[0];
+ return typeof primaryName === "string" ? primaryName : "";
+}
+
+export const getTrimmedPrimaryManifestName = (name) => getPrimaryManifestName(name).trim();
+
+export const isManifestNameUpdateValid = (nameError, currentName, originalName) => (
+ nameError === "" &&
+ getPrimaryManifestName(currentName) !== "" &&
+ getTrimmedPrimaryManifestName(originalName) !== getTrimmedPrimaryManifestName(currentName)
+);
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..7ca624cc
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/GroupInfoBox.js
@@ -0,0 +1,921 @@
+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 AddTerm from './dialog/AddTerm';
+import VisualizationDialog from './dialog/VisualizationDialog';
+import SelectField from '../global/SelectField';
+import { btnBase } from '../../styles/button';
+import { checkboxStyle } from '../../styles/checkbox';
+import { renderTermChip } 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);
+ };
+
+ 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;
+ }
+
+ renderTerms = () => {
+ let chips = [];
+ for (let termID of this.props.commonTerms) {
+ const term = this.props.Terms[termID];
+ chips.push(renderTermChip(this.props, term));
+ }
+ return chips;
+ };
+
+ closeTermDialog = () => {
+ this.setState({ activeTerm: 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);
+ }
+ };
+
+ 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 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 terms = this.renderTerms();
+ return (
+
+
+ {attributeDivs}
+ {!this.props.isReadOnly ? (
+
+ ) : (
+ ''
+ )}
+
+ {this.props.selectedGroups.length > 1 ? 'Terms in common' : 'Terms'}
+
+
+ {terms}
+
+
+ {attributeSewing}
+ {attributeTacket}
+ {submitBtn}
+ {addButtonPopover}
+
+
+
Actions
+
+ {addBtn}
+ {deleteBtn}
+
+
+
+
+
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..cabb7f30
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/LeafInfoBox.js
@@ -0,0 +1,1021 @@
+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 AddTerm from './dialog/AddTerm';
+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 { renderTermChip } from '../../helpers/renderHelper';
+import TextField from 'material-ui/TextField/TextField';
+import IconSubmit from 'material-ui/svg-icons/action/done';
+import IconClear from 'material-ui/svg-icons/content/clear';
+
+/** Leaf infobox */
+export default class LeafInfoBox extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ imageModalOpen: false,
+ insideBifoliaModalOpen: false,
+ outsideBifoliaModalOpen: 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;
+ };
+
+ 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 leafID = this.props.selectedLeaves[0];
+ let leaf = {
+ ...attributes,
+ };
+ this.props.action.updateLeaf(leafID, leaf);
+ };
+
+ 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 });
+ };
+
+ 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);
+ };
+
+ renderTerms = () => {
+ let chips = [];
+ for (let termID of this.props.commonTerms) {
+ const term = this.props.Terms[termID];
+ chips.push(renderTermChip(this.props, term));
+ }
+ return chips;
+ };
+
+ closeTermDialog = () => {
+ this.setState({ activeTerm: null });
+ this.props.togglePopUp(false);
+ };
+
+ toggleImageModal = imageModalOpen => {
+ this.setState({ imageModalOpen });
+ this.props.togglePopUp(imageModalOpen);
+ };
+
+ toggleInsideBifoliaModal = insideBifoliaModalOpen => {
+ this.setState({ insideBifoliaModalOpen });
+ this.props.togglePopUp(insideBifoliaModalOpen)
+ }
+
+ toggleOutsideBifoliaModal = outsideBifoliaModalOpen => {
+ this.setState({ outsideBifoliaModalOpen });
+ this.props.togglePopUp(outsideBifoliaModalOpen)
+ }
+
+ 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'}
+
+
+ );
+ } else if (
+ this.props.viewMode === 'VISUAL' &&
+ attributeDict.name === 'folio_number'
+ ) {
+ // 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}
+ >
+ );
+ // folio number should be a text box, not a dropdown
+ } else if (attributeDict.name === 'folio_number') {
+ // 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 = '';
+ if (this.state['editing_' + attributeDict.name]) {
+ value = this.state[attributeDict.name];
+ } else if (leafAttributes[attributeDict.name] !== null) {
+ value = leafAttributes[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',
+ }}
+ fullWidth
+ />
+ {textboxButtons}
+
+
+ );
+ } else {
+ let menuItems = [];
+ let value = 'keep';
+ // Populate drop down items
+ if (attributeDict.options) {
+ attributeDict.options.forEach((option, index) => {
+ menuItems.push({ value: option, text: option });
+ });
+ }
+ if (leafAttributes[attributeDict.name] === null) {
+ menuItems.push({ value: 'keep', text: 'Keep same' });
+ }
+ 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';
+ }
+ } else if (attributeDict.name === 'folio_number') {
+ input = this.props.Leafs[this.props.selectedLeaves[0]].folio_number
+ }
+ attributeDivs.push(
+
+
+ {eyeCheckbox}
+ {label}
+
+
+ {input}
+
+
+ );
+ });
+
+ const leaf = this.props.Leafs[this.props.selectedLeaves[0]];
+ let leafIndex = this.props.leafIDs.indexOf(leaf.id);
+ let conjoinIndex = this.props.leafIDs.indexOf(leaf.conjoined_to);
+ let firstInConjoin = leafIndex < conjoinIndex;
+
+ let rectoIsPresent = Object.keys(this.props.Rectos[leaf.rectoID].image).length != 0
+ let versoIsPresent = Object.keys(this.props.Versos[leaf.versoID].image).length != 0
+ let leafImagePresent = [rectoIsPresent, versoIsPresent].every(v => v === true)
+
+ let outsideFacingBtn = '';
+ let insideFacingBtn = '';
+ if (this.props.Leafs[this.props.selectedLeaves[0]].conjoined_to !== null && leafImagePresent === true) {
+ if (firstInConjoin === true) {
+ outsideFacingBtn = (
+ this.toggleOutsideBifoliaModal(true)}
+ label="Inner Bifolia"
+ style={{ marginBottom: 10}}
+ tabIndex={this.props.tabIndex}
+ />
+ )
+ } else if (firstInConjoin === false) {
+ outsideFacingBtn = (
+ this.toggleInsideBifoliaModal(true)}
+ label="Inner Bifolia"
+ style={{ marginBottom: 10}}
+ tabIndex={this.props.tabIndex}
+ />
+ )
+ }
+ }
+ if (this.props.Leafs[this.props.selectedLeaves[0]].conjoined_to !== null && leafImagePresent === true) {
+ if (firstInConjoin === true) {
+ insideFacingBtn = (
+ this.toggleInsideBifoliaModal(true)}
+ label="Outer Bifolia"
+ style={{ marginBottom: 10}}
+ tabIndex={this.props.tabIndex}
+ />
+ )
+ } else if (firstInConjoin === false) {
+ insideFacingBtn = (
+ this.toggleOutsideBifoliaModal(true)}
+ label="Outer Bifolia"
+ style={{ marginBottom: 10}}
+ tabIndex={this.props.tabIndex}
+ />
+ )
+ }
+ }
+ 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 insideBifoliaModalContent;
+ let outsideBifoliaModalContent;
+ let imageThumbnails = [];
+ // 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;
+ const isRecto3 = !!(recto.image.url && recto.image.url.includes("full"));
+ const isVerso3 = !!(verso.image.url && verso.image.url.includes("full"));
+ imageModalContent = (
+
+ );
+
+ const conjoinLeaf = this.props.Leafs[leaf.conjoined_to];
+ if (conjoinLeaf !== undefined) {
+ // get bifolia side objects
+ const insideBifoliaVerso = this.props.Versos[leaf.versoID];
+ const insideBifoliaRecto = this.props.Rectos[conjoinLeaf.rectoID];
+ const outsideBifoliaRecto = this.props.Rectos[leaf.rectoID];
+ const outsideBifoliaVerso = this.props.Versos[conjoinLeaf.versoID];
+
+ //set URLs for bifolia images
+ const insideBifoliaVersoURL = insideBifoliaVerso.image ? insideBifoliaVerso.image.url : null;
+ const insideBifoliaRectoURL = insideBifoliaRecto.image ? insideBifoliaRecto.image.url : null;
+ const outsideBifoliaRectoURL = outsideBifoliaRecto.image ? outsideBifoliaRecto.image.url : null;
+ const outsideBifoliaVersoURL = outsideBifoliaVerso.image ? outsideBifoliaVerso.image.url : null;
+
+ //test if the images are DIY (user uploaded instead of from a manifest)
+ const isInsideBifoliaVersoDIY = insideBifoliaVerso.image.manifestID
+ ? insideBifoliaVerso.image.manifestID.includes('DIY')
+ : false;
+ const isInsideBifoliaRectoDIY = insideBifoliaRecto.image.manifestID
+ ? insideBifoliaRecto.image.manifestID.includes('DIY')
+ : false;
+ const isOutsideBifoliaRectoDIY = outsideBifoliaRecto.image.manifestID
+ ? outsideBifoliaRecto.image.manifestID.includes('DIY')
+ : false;
+ const isOutsideBifoliaVersoDIY = outsideBifoliaVerso.image.manifestID
+ ? outsideBifoliaVerso.image.manifestID.includes('DIY')
+ : false;
+
+ //set content for the modals
+ insideBifoliaModalContent = (
+
+ );
+ outsideBifoliaModalContent = (
+
+ );
+ }
+ if (rectoURL) {
+ imageThumbnails.push(
+ this.toggleImageModal(true)}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+ {recto.memberType}
+
+ );
+ }
+ if (versoURL) {
+ imageThumbnails.push(
+ this.toggleImageModal(true)}
+ tabIndex={this.props.tabIndex}
+ >
+
+
+ {verso.memberType}
+
+ );
+ }
+ }
+
+ const terms = this.renderTerms();
+ return (
+
+ {attributeDivs}
+
+ {imageThumbnails}
+
+
+ {insideFacingBtn}
+ {outsideFacingBtn}
+
+ {this.props.isReadOnly && terms.length === 0 ? (
+ ''
+ ) : (
+
+ {this.props.isReadOnly ? (
+ ''
+ ) : (
+
+ )}
+
+
+ {Object.keys(this.props.selectedLeaves).length > 1
+ ? 'Terms in common'
+ : 'Terms'}
+
+
{terms}
+
+
+ )}
+ {submitBtn}
+
+ {this.props.isReadOnly ? (
+ ''
+ ) : (
+
+
Actions
+ {conjoinButton}
+ {generateFolioButton}
+ {addBtn}
+ {deleteBtn}
+
+ )}
+
+
this.toggleImageModal(false)}
+ contentStyle={{ background: 'none', boxShadow: 'inherit' }}
+ bodyStyle={{ padding: 0 }}
+ >
+ {imageModalContent}
+
+
this.toggleInsideBifoliaModal(false)}
+ contentStyle={{background: 'none', boxShadow: 'inherit'}}
+ bodyStyle={{padding: 0}}
+ >
+ {insideBifoliaModalContent}
+
+
this.toggleOutsideBifoliaModal(false)}
+ contentStyle={{background: 'none', boxShadow: 'inherit'}}
+ bodyStyle={{padding: 0}}
+ >
+ {outsideBifoliaModalContent}
+
+
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/infoBox/SideInfoBox.js b/viscoll-app/src/components/infoBox/SideInfoBox.js
new file mode 100644
index 00000000..d2df40e9
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/SideInfoBox.js
@@ -0,0 +1,615 @@
+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 AddTerm from './dialog/AddTerm';
+import Dialog from 'material-ui/Dialog';
+import ImageViewer from '../global/ImageViewer';
+import SelectField from '../global/SelectField';
+import { checkboxStyle } from '../../styles/checkbox';
+import { renderTermChip } 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'] === '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;
+ };
+
+ renderTerms = () => {
+ let chips = [];
+ for (let termID of this.props.commonTerms) {
+ const term = this.props.Terms[termID];
+ chips.push(renderTermChip(this.props, term));
+ }
+ return chips;
+ };
+
+ closeTermDialog = () => {
+ this.setState({ activeTerm: 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 terms = this.renderTerms();
+ let termsDiv = [];
+ if (!(this.props.isReadOnly && terms.length === 0)) {
+ termsDiv.push(
+
+ {this.props.isReadOnly ? (
+ ''
+ ) : (
+
+ this.props.action.linkTerm(termID, this.props.sideIndex),
+ createAndAttachTerm: this.props.action.createAndAttachTerm,
+ }}
+ Taxonomies={this.props.Taxonomies}
+ togglePopUp={this.props.togglePopUp}
+ tabIndex={this.props.tabIndex}
+ groupIDs={this.props.groupIDs}
+ leafIDs={this.props.leafIDs}
+ />
+ )}
+
+ {Object.keys(this.props.selectedSides).length > 1
+ ? 'Terms in common'
+ : 'Terms'}
+
+ {terms}
+
+ );
+ }
+
+ let submitBtn = '';
+ if (this.state.isBatch && this.hasActiveAttributes()) {
+ submitBtn = (
+
+ );
+ }
+
+ let imageModalContent;
+ let imageThumbnail = [];
+ // 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}
+
+ {termsDiv}
+ {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..d6f3ab61
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/dialog/AddGroupDialog.js
@@ -0,0 +1,659 @@
+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 SelectField from '../../global/SelectField';
+
+
+/** 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,
+ conjoin: false,
+ oddLeaf: 2,
+ copies: 1,
+ location: 'inside',
+ placementLocation: '',
+ selectedChild: '',
+ errorText: {
+ numberOfGroups: '',
+ numberOfLeaves: '',
+ oddLeaf: '',
+ copies: '',
+ },
+ memberOrder: 1,
+ }
+ };
+
+ 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 });
+ }
+
+ /**
+ * get all children of a given group, including the children of child groups
+ */
+ getAllChildrenOfGroup = (inputGroupID) => {
+ let allChildrenOfGroup = []
+ let group = this.props.Groups[inputGroupID]
+ group.memberIDs.forEach(memberID => {
+ if (memberID[0] === 'L') {
+ if (!allChildrenOfGroup.includes(memberID)) {
+ allChildrenOfGroup.push(memberID);
+ } else {
+ allChildrenOfGroup.push(this.getAllChildrenOfGroup(memberID))
+ }
+ }
+ })
+ return allChildrenOfGroup;
+ }
+
+ countGroupChildren = (inputGroupID) => {
+ let group = this.props.Groups[inputGroupID]
+ let groupCount = 0;
+ group.memberIDs.forEach(memberID => {
+ if (memberID[0] === 'G') {
+ groupCount++;
+ // go into group
+ groupCount += this.countGroupChildren(memberID)
+ }
+ })
+ return groupCount;
+ }
+
+ /**
+ * Generate group notation for dropdown.
+ * Code here must mirror PaperGroup and Group model notation logic.
+ */
+ groupNotation = (group) => {
+ // get all groups as base nest level
+ let outerGroups = Object.values(this.props.Groups).filter(g => g.nestLevel === 1);
+ let outerGroupIDs = outerGroups.map(g => g.id);
+ let notation = '';
+ if (group.nestLevel === 1) {
+ // get index of current group within the context of all outer groups
+ let groupOrder = outerGroupIDs.indexOf(group.id) + 1;
+ notation = `${groupOrder}`;
+ } else {
+ // get parent of current group
+ let parentGroup = this.props.Groups[group.parentID];
+ // get children of parent group
+ let parentGroupChildren = parentGroup.memberIDs.filter(g => g[0] === 'G');
+ let subquireNotation = parentGroupChildren.indexOf(group.id) + 1;
+ notation = `${this.groupNotation(parentGroup)}.${subquireNotation}`;
+ }
+ return notation;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Change dropdown value
+ */
+ dropDownChange = (value, stateValue) => {
+ if (Object.keys(this.props.selectedGroups).length === 1) {
+ let updatedStateValue = {};
+ updatedStateValue[stateValue] = value;
+ this.setState(updatedStateValue);
+ }
+ }
+
+ /**
+ * Update location radio button group
+ */
+ onLocationChange = (value) => {
+ this.setState({ location: value });
+ ;
+ }
+ onPlacementLocationChange = (value) => {
+ this.setState({ placementLocation: 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['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;
+ 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') {
+ // two values need to be calculated here. these values are memberOrder and groupOrder.
+ // memberOrder represents the placement of the new group in context of it's parent group's memberIDs
+ this.setState({ selectedChild: this.props.Groups[this.props.selectedGroups]['memberIDs'][0] })
+ let selectedChildIndex = this.props.Groups[this.props.selectedGroups]['memberIDs'].indexOf(this.state.selectedChild)
+ if (this.state.placementLocation === 'above') {
+ memberOrder = selectedChildIndex + 1
+ } else if (this.state.placementLocation === 'below') {
+ memberOrder = selectedChildIndex + 2
+ }
+ //// determine groupOrder
+ // generate an array of all groups and leaves in the project in order
+ let orderedElements = []
+ this.props.groupIDs.forEach(groupID => {
+ if (!orderedElements.includes(groupID)) {
+ orderedElements.push(groupID)
+ // recursively get group children for nested subquires
+ orderedElements.push(...this.getAllChildrenOfGroup(groupID))
+ }
+ })
+ // find the user selected leaf/group in this array
+ let selectedChildIndexInOrderedElements = orderedElements.indexOf(this.state.selectedChild)
+ let orderedElementsSliced = []
+ if (this.state.placementLocation === 'above') {
+ orderedElementsSliced = orderedElements.slice(0, selectedChildIndexInOrderedElements)
+ } else if (this.state.placementLocation === 'below') {
+ orderedElementsSliced = orderedElements.slice(0, selectedChildIndexInOrderedElements + 1)
+ }
+ // count how many groups occur before this value in the array
+ let groupCount = 0;
+ orderedElementsSliced.forEach(member => {
+ if (member[0] === 'G') {
+ groupCount++;
+ }
+ })
+ // add 1 to determine the new groupOrder
+ if (this.state.selectedChild) {
+ if (this.state.selectedChild[0] === 'G') {
+ groupCount += this.countGroupChildren(this.state.selectedChild)
+ }
+ } else {
+ groupCount = 1
+ }
+ groupOrder = groupCount + 1;
+ data.additional['parentGroupID'] = group.id;
+ }
+ data.group = {
+ title: 'None',
+ type: 'Quire'
+ };
+ 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,
+ conjoin: false,
+ oddLeaf: 2,
+ copies: 1,
+ location: this.props.selectedGroups.length > 0 ? '' : 'inside',
+ placementLocation: '',
+ selectedChild: this.props.Groups !== undefined ? this.props.Groups[this.props.selectedGroups[0]]['memberIDs'][0] : '',
+ errorText: {
+ numberOfGroups: '',
+ numberOfLeaves: '',
+ oddLeaf: '',
+ copies: '',
+ },
+ memberOrder: 1,
+ });
+ }
+
+ render() {
+ const actions = [
+ {
+ this.props.closeDialog()
+ }}
+ style={{ width: '49%', marginRight: '1%', border: '1px solid #ddd' }}
+ />,
+ ,
+ ];
+
+ const styles = {
+ radioButton: {
+ marginBottom: 5,
+ },
+ };
+
+ let conjoinOption = '';
+ 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"
+ >
+
+
+
+
+ }
+
+ 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)}
+ />
+
+
: '';
+ let groupPosition = this.props.Groups !== undefined
+ && this.props.Groups[this.props.selectedGroups[0]].memberIDs.length != 0
+ && this.state.location === 'inside' ?
+
+
Group position
+
+
+
+ this.onPlacementLocationChange(v)}>
+
+
+
+
+
+
+ this.dropDownChange(v, 'selectedChild')}
+ value={this.props.Groups[this.props.selectedGroups[0]]['memberIDs'][0]}
+ data={this.props.Groups[this.props.selectedGroups[0]]['memberIDs'].map((itemID) => {
+ if (itemID[0] === 'L') {
+ return { value: itemID, text: `Leaf ${this.props.leafIDs.indexOf(itemID) + 1}` }
+ } else if (itemID[0] === 'G') {
+ let groupType = this.props.Groups[itemID].type
+ // quireNumber should be the notation value, which means we have to have
+ // the notation logic here as well
+ let groupNotation = this.groupNotation(this.props.Groups[itemID])
+ return { value: itemID, text: `${groupType} ${groupNotation}` }
+ }
+
+ })}
+ width={250}
+ />
+
+
: '';
+
+
+ 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}
+ {groupPosition}
+ {numberOfGroups}
+ {addLeafsCheckbox}
+ {numberOfLeaves}
+ {conjoinOption}
+ {oddLeaf}
+
+ );
+
+ return (
+
+ {dialog}
+
+ );
+ }
+}
\ No newline at end of file
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/AddTerm.js b/viscoll-app/src/components/infoBox/dialog/AddTerm.js
new file mode 100644
index 00000000..8fd78761
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/dialog/AddTerm.js
@@ -0,0 +1,250 @@
+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 term to an object (leaf, side, or group). This component is used in the visual and tabular edit modes. */
+export default class AddTerm extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ open: false,
+ taxonomy: '',
+ description: '',
+ uri: '',
+ show: false,
+ searchText: '',
+ termID: null,
+ };
+ }
+
+ /** Open this modal component */
+ handleOpen = () => {
+ this.setState({
+ open: true,
+ taxonomy: '',
+ description: '',
+ uri: '',
+ show: false,
+ searchText: '',
+ termID: null,
+ });
+ this.props.togglePopUp(true);
+ };
+
+ /** Close this modal component */
+ handleClose = () => {
+ this.setState({ open: false });
+ this.props.togglePopUp(false);
+ };
+
+ handleUpdateInput = searchText => {
+ this.setState({
+ searchText: searchText,
+ termID: null,
+ });
+ };
+
+ handleNewRequest = request => {
+ // User pressed enter instead of selecting a term in drop down
+ // Look for key associated with user input
+ let termID = null;
+ for (let id in this.props.Terms) {
+ const term = this.props.Terms[id];
+ if (term.title === request) {
+ termID = term.id;
+ }
+ }
+ this.setState({ termID }, () => {
+ if (termID) this.submit();
+ });
+ };
+
+ submit = () => {
+ if (this.state.termID !== null) {
+ // Attach existing term to selected objects
+ this.props.action.linkTerm(this.state.termID);
+ } else {
+ // Check if term exists (in case user types and did not press enter)
+ let termID = null;
+ for (let id in this.props.Terms) {
+ const term = this.props.Terms[id];
+ if (term.title === this.state.searchText) termID = term.id;
+ }
+ if (termID) {
+ this.props.action.linkTerm(termID);
+ } else {
+ // Did not find term, so create and attach new term to object
+ this.props.action.createAndAttachTerm(
+ this.state.searchText,
+ this.state.taxonomy,
+ this.state.description,
+ this.state.uri,
+ this.state.show
+ );
+ }
+ }
+ this.handleClose();
+ };
+
+ termExists = () => {
+ for (let termID in this.props.Terms) {
+ const term = this.props.Terms[termID];
+ if (term.title === this.state.searchText) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ /**
+ * Mapping function to render one term taxonomy menu item
+ */
+ renderTaxonomies = name => {
+ return ;
+ };
+
+ onChange = (name, value) => {
+ this.setState({ [name]: value });
+ };
+
+ getFilteredTermTitlesDropDown = () => {
+ return Object.keys(this.props.Terms).filter(termID => {
+ return !this.props.commonTerms.includes(termID);
+ });
+ };
+
+ render() {
+ const dataSourceConfig = {
+ text: 'textKey',
+ value: 'valueKey',
+ };
+
+ const actions = [
+ ,
+ ,
+ ];
+
+ let newTermForm =
;
+ if (!this.termExists() && this.state.searchText.length > 1) {
+ newTermForm = (
+
+
this.onChange('taxonomy', v)}
+ floatingLabelText="Taxonomy"
+ fullWidth
+ style={{ marginTop: -20 }}
+ >
+ {this.props.Taxonomies.map(this.renderTaxonomies)}
+
+
this.onChange('description', v)}
+ multiLine
+ fullWidth
+ style={{ marginTop: -20 }}
+ />
+ this.onChange('uri', v)}
+ multiLine
+ fullWidth
+ style={{ marginTop: -20 }}
+ />
+
+ Show in diagram (leaves & sides only):
+
+
+ this.onChange('show', !this.state.show)}
+ />
+
+
+ );
+ }
+
+ let dialog = (
+
+ {
+ return {
+ textKey: this.props.Terms[termID].title,
+ valueKey: termID,
+ };
+ })}
+ filter={(searchText, key) => key.indexOf(searchText) !== -1}
+ openOnFocus={true}
+ dataSourceConfig={dataSourceConfig}
+ fullWidth
+ listStyle={{ maxHeight: 300, overflow: 'auto' }}
+ errorText={
+ !this.termExists() && this.state.searchText.length > 0
+ ? "This term doesn't exist. To create and attach it, fill out its taxonomy, description, and URI."
+ : ''
+ }
+ errorStyle={{ color: '#727272' }}
+ floatingLabelFocusStyle={{ color: '#3A4B55' }}
+ />
+ {newTermForm}
+
+ );
+
+ 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/TermDialog.js b/viscoll-app/src/components/infoBox/dialog/TermDialog.js
new file mode 100644
index 00000000..f9119e7b
--- /dev/null
+++ b/viscoll-app/src/components/infoBox/dialog/TermDialog.js
@@ -0,0 +1,113 @@
+import React from 'react';
+import EditTermForm from '../../termsManager/EditTermForm';
+import Dialog from 'material-ui/Dialog';
+import FlatButton from 'material-ui/FlatButton';
+
+export default class TermDialog extends React.Component {
+
+
+ getLinkedGroups = () => {
+ const groupsWithCurrentTerm = Object.keys(this.props.Groups).filter((groupID) => {
+ return (this.props.Groups[groupID].terms.includes(this.props.activeTerm.id))
+ });
+ return groupsWithCurrentTerm.map((value) => {
+ const label = `Group ${this.props.Groups[value].order}`;
+ return {label, value};
+ });
+ }
+
+ getLinkedLeaves = () => {
+ const leafsWithCurrentTerm = Object.keys(this.props.Leafs).filter((leafID) => {
+ return (this.props.Leafs[leafID].terms.includes(this.props.activeTerm.id))
+ });
+ return leafsWithCurrentTerm.map((value)=>{
+ const label = `Leaf ${this.props.Leafs[value].order}`;
+ return {label, value};
+ });
+ }
+
+ getLinkedSides = () => {
+ const rectosWithCurrentTerm = Object.keys(this.props.Rectos).filter((rectoID) => {
+ return (this.props.Rectos[rectoID].terms.includes(this.props.activeTerm.id))
+ });
+ const versosWithCurrentTerm = Object.keys(this.props.Versos).filter((versoID) => {
+ return (this.props.Versos[versoID].terms.includes(this.props.activeTerm.id))
+ });
+ const sidesWithCurrentTerm = [];
+ for (let value of rectosWithCurrentTerm){
+ const leafOrder = this.props.Leafs[this.props.Rectos[value].parentID].order;
+ const label = `Leaf ${leafOrder}: Side Recto}`;
+ sidesWithCurrentTerm.push({label, value})
+ }
+ for (let value of versosWithCurrentTerm){
+ const leafOrder = this.props.Leafs[this.props.Versos[value].parentID].order;
+ const label = `Leaf ${leafOrder}: Side Verso}`;
+ sidesWithCurrentTerm.push({label, value})
+ }
+ return sidesWithCurrentTerm;
+ }
+
+ 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/termsManager/DeleteConfirmation.js b/viscoll-app/src/components/termsManager/DeleteConfirmation.js
new file mode 100644
index 00000000..a1e43ac3
--- /dev/null
+++ b/viscoll-app/src/components/termsManager/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 terms and term taxonomies */
+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==="term") {
+ this.props.action.deleteTerm(this.props.termID);
+ } else {
+ this.props.onDelete(this.props.index)
+ }
+ this.handleClose();
+ }
+
+ render() {
+ const actions = [
+ ,
+ ,
+ ];
+ let deleteIcon =
+
+
+ let message = "This term will be removed from all groups/sides/leaves that have this term.";
+ if (this.props.item==="taxonomy") {
+ deleteIcon =
+
+
+ message = "Any existing terms associated with this term taxonomy will be assigned to the taxonomy 'Unknown'.";
+ }
+ if (this.props.item!=="") {
+ return (
+
+ {deleteIcon}
+
+ {message}
+
+
+ );
+ } else {
+ return
;
+ }
+ }
+}
\ No newline at end of file
diff --git a/viscoll-app/src/components/termsManager/EditTermForm.js b/viscoll-app/src/components/termsManager/EditTermForm.js
new file mode 100644
index 00000000..744e666f
--- /dev/null
+++ b/viscoll-app/src/components/termsManager/EditTermForm.js
@@ -0,0 +1,425 @@
+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 Term tab in the Term Manager */
+export default class EditTermForm extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ id: props.term.id,
+ title: props.term.title,
+ taxonomy: props.term.taxonomy,
+ description: props.term.description,
+ uri: props.term.uri,
+ editing: {
+ title: false,
+ description: false,
+ },
+ errors: {
+ title: '',
+ },
+ };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.setState({
+ id: nextProps.term.id,
+ title: nextProps.term.title,
+ taxonomy: nextProps.term.taxonomy,
+ description: nextProps.term.description,
+ uri: nextProps.term.uri, // added URI
+ editing: {
+ title: false,
+ description: false,
+ },
+ errors: {
+ title: '',
+ },
+ });
+ }
+
+ validateTitle = title => {
+ for (let termID in this.props.Terms) {
+ const term = this.props.Terms[termID];
+ if (term.title === title && term.id !== this.state.id) {
+ this.setState({ errors: { title: 'This term 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 === 'taxonomy') {
+ let editing = {
+ title: this.state.title,
+ taxonomy: value,
+ description: this.state.description,
+ uri: this.state.uri, // added URI
+ };
+ if (this.props.term)
+ this.props.action.updateTerm(this.props.term.id, editing);
+ }
+ };
+
+ update = (event, name) => {
+ event.preventDefault();
+ if (this.props.term) {
+ let editing = {
+ title: this.state.title,
+ taxonomy: this.state.taxonomy,
+ description: this.state.description,
+ uri: this.state.uri, // added URI
+ };
+ this.setState({ editing: { ...this.state.editing, [name]: false } });
+ this.props.action.updateTerm(this.props.term.id, editing);
+ }
+ };
+
+ /**
+ * Reset input field to original value
+ */
+ onCancelUpdate = name => {
+ this.setState({
+ [name]: this.props.term[name],
+ editing: {
+ ...this.state.editing,
+ [name]: false,
+ },
+ errors: {
+ ...this.state.errors,
+ [name]: '',
+ },
+ });
+ };
+
+ renderTaxonomies = name => {
+ return { value: name, text: name };
+ };
+
+ renderSubmitButtons = name => {
+ if (
+ this.state.editing[name] &&
+ this.props.term !== null &&
+ this.props.term !== 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.linkTerm(this.props.term.id, [{ type, id: chip.value }]);
+ };
+
+ handleDeleteChip = (id, index, type) => {
+ this.props.action.unlinkTerm(this.props.term.id, [{ type, id }]);
+ };
+
+ render() {
+ let title = this.props.isReadOnly
+ ? this.props.term.title
+ : 'Edit ' + this.props.term.title;
+ let linkedObjects = '';
+ let deleteButton = '';
+ let sideData = [];
+ for (let i = 0; i < this.props.leafIDs.length; i++) {
+ const leaf = this.props.Leafs[this.props.leafIDs[i]];
+ const recto = this.props.Rectos[leaf.rectoID];
+ const verso = this.props.Versos[leaf.versoID];
+ const rectoFolioNumber =
+ recto.folio_number && recto.folio_number !== ''
+ ? '(' + recto.folio_number + ')'
+ : '';
+ const rectoPageNumber =
+ recto.page_number && recto.page_number !== ''
+ ? '(' + recto.page_number + ')'
+ : '';
+ const versoFolioNumber =
+ verso.folio_number && verso.folio_number !== ''
+ ? '(' + verso.folio_number + ')'
+ : '';
+ const versoPageNumber =
+ verso.page_number && verso.page_number !== ''
+ ? '(' + verso.page_number + ')'
+ : '';
+ sideData.push({
+ value: leaf.rectoID,
+ label:
+ 'L' +
+ (this.props.leafIDs.indexOf(leaf.id) + 1) +
+ ' Recto ' +
+ rectoFolioNumber +
+ ' ' +
+ rectoPageNumber,
+ });
+ sideData.push({
+ value: leaf.versoID,
+ label:
+ 'L' +
+ (this.props.leafIDs.indexOf(leaf.id) + 1) +
+ ' Verso ' +
+ versoFolioNumber +
+ ' ' +
+ versoPageNumber,
+ });
+ }
+ const linkToGroups = (
+
+
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 term"
+ 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 term"
+ 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 term"
+ menuProps={{ maxHeight: 200 }}
+ tabIndex={this.props.tabIndex}
+ />
+
+
+ );
+
+ if (this.props.term) {
+ 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')}
+
+ )}
+
+
+ Taxonomy
+
+
+ {this.props.isReadOnly ? (
+
{this.state.taxonomy}
+ ) : (
+
this.onChange('taxonomy', v)}
+ tabIndex={this.props.tabIndex}
+ data={this.props.Taxonomies.map(this.renderTaxonomies)}
+ />
+ )}
+
+
+ Description
+
+
+ {this.props.isReadOnly ? (
+
{this.state.description}
+ ) : (
+
this.update(e, 'description')}>
+ this.onChange('description', v)}
+ multiLine
+ fullWidth
+ tabIndex={this.props.tabIndex}
+ />
+ {this.renderSubmitButtons('description')}
+
+ )}
+
+ {/* added URI input to view */}
+
+ URI
+
+
+ {this.props.isReadOnly ? (
+
{this.state.uri}
+ ) : (
+
this.update(e, 'uri')}>
+ this.onChange('uri', v)}
+ multiLine
+ fullWidth
+ tabIndex={this.props.tabIndex}
+ />
+ {this.renderSubmitButtons('uri')}
+
+ )}
+
+ {!this.props.isReadOnly && (
+
+
+ Show in diagram (leaves & sides only):
+
+
+
+ this.props.action.updateTerm(this.props.term.id, {
+ title: this.state.title,
+ taxonomy: this.state.taxonomy,
+ description: this.state.description,
+ show: !this.props.term.show,
+ })
+ }
+ tabIndex={this.props.tabIndex}
+ />
+
+
+ )}
+
+ {linkedObjects}
+ {deleteButton}
+
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/termsManager/ManageTerms.js b/viscoll-app/src/components/termsManager/ManageTerms.js
new file mode 100644
index 00000000..eeb03ee8
--- /dev/null
+++ b/viscoll-app/src/components/termsManager/ManageTerms.js
@@ -0,0 +1,260 @@
+import React, { Component } from 'react';
+import RaisedButton from 'material-ui/RaisedButton';
+import EditTermForm from './EditTermForm';
+import NewTermForm from './NewTermForm';
+import Add from 'material-ui/svg-icons/content/add';
+import { btnMd, btnBase } from '../../styles/button';
+
+/** Create New Term tab in the Term Manager */
+export default class ManageTerms extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ activeTerm: null,
+ title: '',
+ taxonomy: '',
+ description: '',
+ };
+ }
+
+ /**
+ * Update state when user clicks on new term item
+ */
+ onItemChange = activeTerm => {
+ this.setState({ activeTerm });
+ };
+
+ componentWillReceiveProps(nextProps) {
+ if (this.state.activeTerm)
+ this.setState({ activeTerm: nextProps.Terms[this.state.activeTerm.id] });
+ }
+
+ /**
+ * Mapping function to render a term thumbnail
+ */
+ renderList = termID => {
+ const term = this.props.Terms[termID];
+ return (
+ this.onItemChange(term)}
+ tabIndex={this.props.tabIndex}
+ key={termID}
+ >
+
+
+ {term.title.length > 80
+ ? term.title.substring(0, 80) + '...'
+ : term.title}
+
+
{term.taxonomy}
+
+
+ );
+ };
+
+ /**
+ * Clear values in the input fields
+ */
+ reset = () => {
+ this.setState({
+ title: '',
+ taxonomy: '',
+ description: '',
+ });
+ };
+
+ deleteTerm = termID => {
+ this.props.action.deleteTerm(termID);
+ this.setState({ activeTerm: null });
+ };
+
+ updateTerm = (termID, term) => {
+ this.props.action.updateTerm(termID, term);
+ };
+
+ linkTerm = (termID, object) => {
+ this.props.action.linkTerm(termID, object);
+ };
+
+ unlinkTerm = (termID, object) => {
+ this.props.action.unlinkTerm(termID, object);
+ };
+
+ linkAndUnlinkTerms = (termID, linkObjects, unlinkObjects) => {
+ this.props.action.linkAndUnlinkTerms(termID, linkObjects, unlinkObjects);
+ };
+
+ getLinkedGroups = () => {
+ const groupsWithCurrentTerm = Object.keys(this.props.Groups).filter(
+ groupID => {
+ return this.props.Groups[groupID].terms.includes(
+ this.state.activeTerm.id
+ );
+ }
+ );
+ return groupsWithCurrentTerm.map(value => {
+ const label = `Group ${this.props.groupIDs.indexOf(value) + 1}`;
+ return { label, value };
+ });
+ };
+
+ getLinkedLeaves = () => {
+ const leafsWithCurrentTerm = Object.keys(this.props.Leafs).filter(
+ leafID => {
+ return this.props.Leafs[leafID].terms.includes(
+ this.state.activeTerm.id
+ );
+ }
+ );
+ return leafsWithCurrentTerm.map(value => {
+ const label = `Leaf ${this.props.leafIDs.indexOf(value) + 1}`;
+ return { label, value };
+ });
+ };
+
+ getLinkedSides = () => {
+ const rectosWithCurrentTerm = Object.keys(this.props.Rectos).filter(
+ rectoID => {
+ return this.props.Rectos[rectoID].terms.includes(
+ this.state.activeTerm.id
+ );
+ }
+ );
+ const versosWithCurrentTerm = Object.keys(this.props.Versos).filter(
+ versoID => {
+ return this.props.Versos[versoID].terms.includes(
+ this.state.activeTerm.id
+ );
+ }
+ );
+ const sidesWithCurrentTerm = [];
+ for (let value of rectosWithCurrentTerm) {
+ 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}`;
+ sidesWithCurrentTerm.push({ label, value });
+ }
+ for (let value of versosWithCurrentTerm) {
+ 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}`;
+ sidesWithCurrentTerm.push({ label, value });
+ }
+ return sidesWithCurrentTerm;
+ };
+
+ getRectosAndVersos = () => {
+ const size = Object.keys(this.props.Rectos).length;
+ let result = {};
+ for (let i = 0; i < size; i++) {
+ const rectoID = Object.keys(this.props.Rectos)[i];
+ const versoID = Object.keys(this.props.Versos)[i];
+ result[rectoID] = this.props.Rectos[rectoID];
+ result[versoID] = this.props.Versos[versoID];
+ }
+ return result;
+ };
+
+ render() {
+ let termForm;
+ if (!this.state.activeTerm) {
+ termForm = (
+
+ );
+ } else {
+ termForm = (
+
+ );
+ }
+
+ return (
+
+
+
+
this.onItemChange(null)}
+ style={{ width: '90%' }}
+ {...btnMd}
+ label="Create New Term"
+ labelStyle={{ ...btnBase().labelStyle }}
+ icon={ }
+ labelColor={'#ffffff'}
+ backgroundColor={'#566476'}
+ tabIndex={this.props.tabIndex}
+ >
+ {Object.keys(this.props.Terms).map(this.renderList)}
+
+
+
+ {termForm}
+
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/termsManager/NewTermForm.js b/viscoll-app/src/components/termsManager/NewTermForm.js
new file mode 100644
index 00000000..b577adec
--- /dev/null
+++ b/viscoll-app/src/components/termsManager/NewTermForm.js
@@ -0,0 +1,226 @@
+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 Term tab in the Term Manager */
+export default class NewTermForm extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ //the state is managed from the component itself
+ title: '', //this is why a class component was used
+ taxonomy: '',
+ description: '',
+ uri: '',
+ show: false,
+ errors: {
+ title: '',
+ },
+ };
+ }
+
+ initialState = {};
+
+ //ensure title validates (is not a duplicate, too long etc)
+ validateTitle = title => {
+ for (let termID in this.props.Terms) {
+ const term = this.props.Terms[termID];
+ if (term.title === title) {
+ this.setState({ errors: { title: 'This term 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 === 'taxonomy') {
+ let editing = {
+ title: this.state.title,
+ taxonomy: value,
+ description: this.state.description,
+ show: this.state.show,
+ };
+ if (this.props.term)
+ this.props.action.updateTerm(this.props.term.id, editing);
+ }
+ };
+
+ create = () => {
+ let term = {
+ project_id: this.props.projectID,
+ title: this.state.title,
+ taxonomy: this.state.taxonomy,
+ description: this.state.description,
+ uri: this.state.uri,
+ show: this.state.show,
+ };
+ this.props.action.addTerm(term);
+ // Reset form
+ this.setState({
+ title: '',
+ taxonomy: '',
+ description: '',
+ uri: '',
+ show: false,
+ errors: {
+ title: '',
+ },
+ });
+ };
+
+ /**
+ * Clear values in the input fields if we are creating a new term
+ * Reset to original values if we are editing an existing term
+ */
+ reset = props => {
+ this.setState({
+ title: '',
+ taxonomy: '',
+ description: '',
+ uri: '',
+ errors: {
+ title: '',
+ taxonomy: '',
+ description: '',
+ uri: '',
+ show: false,
+ },
+ });
+ };
+
+ renderTaxonomies = 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 term';
+ let createButtons = (
+
+ this.reset()}
+ style={{ width: 120 }}
+ />{' '}
+
+ this.create()}
+ disabled={
+ this.state.errors.title !== '' ||
+ this.state.taxonomy === '' ||
+ this.state.title === ''
+ }
+ />
+
+ );
+
+ return (
+
+
{title}
+
+
+ Title
+
+
+ this.onChange('title', v)}
+ fullWidth
+ aria-invalid={this.state.errors.title.length > 0}
+ />
+
+
+ Taxonomy
+
+
+ this.onChange('taxonomy', v)}
+ data={this.props.Taxonomies.map(this.renderTaxonomies)}
+ >
+
+
+ Description
+
+
+ this.onChange('description', v)}
+ multiLine
+ fullWidth
+ />
+
+
+ URI
+
+
+ this.onChange('uri', v)}
+ multiLine
+ fullWidth
+ />
+
+
+ Show in diagram (leaves & sides only):
+
+
+ this.onChange('show', !this.state.show)}
+ />
+
+ {createButtons}
+
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/termsManager/Taxonomy.js b/viscoll-app/src/components/termsManager/Taxonomy.js
new file mode 100644
index 00000000..0deaf597
--- /dev/null
+++ b/viscoll-app/src/components/termsManager/Taxonomy.js
@@ -0,0 +1,234 @@
+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';
+
+/** Taxonomy page to add, edit and delete taxonomies */
+export default class Taxonomy extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ newTaxonomy: '',
+ Taxonomies: [...props.Taxonomies],
+ editing: new Array(props.Taxonomies.length).fill(false),
+ errorNewTaxonomy: '',
+ errorTypes: new Array(props.Taxonomies.length).fill(''),
+ lastSubmitted: -2,
+ };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (
+ this.state.Taxonomies.length !== nextProps.Taxonomies.length ||
+ this.props.Taxonomies !== nextProps.Taxonomies
+ ) {
+ this.setState({ Taxonomies: [...nextProps.Taxonomies] });
+ this.resetEditing();
+ }
+ }
+
+ resetEditing = () => {
+ this.setState({
+ editing: new Array(this.props.Taxonomies.length).fill(false),
+ newTaxonomy: '',
+ });
+ };
+
+ onNewTaxonomyChange = newTaxonomy => {
+ this.setState({ newTaxonomy }, () => {
+ if (!this.isValid(newTaxonomy.trim())) {
+ let errorMessage = `Taxonomy with name ${newTaxonomy} already exists in this project`;
+ if (newTaxonomy.length === 0) errorMessage = '';
+ this.setState({ errorNewTaxonomy: errorMessage });
+ } else {
+ this.setState({ errorNewTaxonomy: '' });
+ }
+ });
+ };
+
+ onChange = (newTaxonomy, index) => {
+ this.setTaxonomy(index, newTaxonomy);
+ this.setEditing(index, true);
+ if (!this.isValid(newTaxonomy)) {
+ let errorMessage = `Taxonomy with name ${newTaxonomy} already exists in this project`;
+ if (newTaxonomy === this.props.Taxonomies[index]) errorMessage = '';
+ if (newTaxonomy.length === 0) errorMessage = `Taxonomy cannot be blank`;
+ this.setError(index, errorMessage);
+ } else {
+ this.setError(index, '');
+ }
+ };
+
+ onUpdate = (event, index) => {
+ event.preventDefault();
+ const newTaxonomy = this.state.Taxonomies[index];
+ if (newTaxonomy !== this.props.Taxonomies[index]) {
+ this.setState({ lastSubmitted: index });
+ let taxonomy = {
+ project_id: this.props.projectID,
+ taxonomy: newTaxonomy,
+ old_taxonomy: this.props.Taxonomies[index],
+ };
+ this.props.action.updateTaxonomy(taxonomy);
+ }
+ this.setEditing(index, false);
+ };
+
+ isValid = newTaxonomy => {
+ return !this.props.Taxonomies.includes(newTaxonomy) && newTaxonomy.length !== 0;
+ };
+
+ onDelete = index => {
+ let taxonomy = {
+ project_id: this.props.projectID,
+ taxonomy: this.state.Taxonomies[index],
+ };
+ let updatedEditing = [...this.state.editing];
+ updatedEditing.splice(index, 1);
+ this.setState({ editing: updatedEditing }, () => {
+ this.props.action.deleteTaxonomy(taxonomy);
+ });
+ };
+
+ onCreate = event => {
+ event.preventDefault();
+ let taxonomy = {
+ project_id: this.props.projectID,
+ taxonomy: this.state.newTaxonomy.trim(),
+ };
+ this.props.action.createTaxonomy(taxonomy);
+ this.resetEditing();
+ this.setState({ lastSubmitted: -1 });
+ };
+
+ onCancelUpdate = index => {
+ this.setTaxonomy(index, this.props.Taxonomies[index]);
+ this.setError(index, '');
+ this.setEditing(index, false);
+ };
+
+ setTaxonomy = (index, value) => {
+ let newTaxonomies = [...this.state.Taxonomies];
+ newTaxonomies[index] = value;
+ this.setState({ Taxonomies: newTaxonomies });
+ };
+
+ 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.Taxonomies[index])}
+ />
+ }
+ style={{ minWidth: '60px', marginLeft: '5px' }}
+ onClick={e => this.onCancelUpdate(index)}
+ />
+
+ );
+ } else {
+ return '';
+ }
+ };
+
+ renderTaxonomy = (taxonomy, 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}
+ />
+ {taxonomy === 'Unknown' ? (
+ ''
+ ) : (
+
+ )}
+ {this.renderSubmitButtons(index)}
+
+
+ );
+ };
+
+ filterTaxonomy = (object, index) => {
+ return object.key !== 'taxonomy_0';
+ };
+
+ render() {
+ return (
+
+
Add a new taxonomy
+
this.onCreate(e)}>
+
+
+ this.onNewTaxonomyChange(v)}
+ errorText={this.state.errorNewTaxonomy}
+ aria-invalid={this.state.errorNewTaxonomy.length > 0}
+ style={{ width: 300 }}
+ tabIndex={this.props.tabIndex}
+ />
+
+
+
+
+
+
+
Your taxonomies
+
+ {this.props.Taxonomies
+ .map(this.renderTaxonomy)
+ .filter(this.filterTaxonomy)}
+
+
+ );
+ }
+}
diff --git a/viscoll-app/src/components/termsManager/TermsFilter.js b/viscoll-app/src/components/termsManager/TermsFilter.js
new file mode 100644
index 00000000..812c0e77
--- /dev/null
+++ b/viscoll-app/src/components/termsManager/TermsFilter.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 terms */
+class TermsFilter 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 TermsFilter;
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..cea9f286
--- /dev/null
+++ b/viscoll-app/src/containers/App.js
@@ -0,0 +1,83 @@
+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'
+import CacheBuster from '../components/global/CacheBuster';
+
+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 (
+
+ {({ loading, isLatestVersion, refreshCacheAndReload }) => {
+ if (loading) return null;
+ if (!loading && !isLatestVersion) {
+ // You can decide how and when you want to force reload
+ refreshCacheAndReload();
+ }
+
+ 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..3adf00f5
--- /dev/null
+++ b/viscoll-app/src/containers/Authentication.js
@@ -0,0 +1,257 @@
+import React, { Component } from 'react';
+import RaisedButton from 'material-ui/RaisedButton';
+import imgCollation from '../assets/collation.png';
+import imgLogo from '../assets/vceditor_logo.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';
+import { API_URL } from '../store/axiosConfig';
+import light from "../styles/light.js";
+
+/** 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');
+ }
+ }
+ // set instance into state
+ let instanceURL = API_URL + '/instance'
+ let instanceRequest = () => {
+ fetch(instanceURL).then(async (response) => {
+ const data = await response.json();
+ this.setState({instance:data.current_instance})
+ })
+ }
+ instanceRequest();
+ }
+
+ 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}
+
+
+
+
+
+ );
+ }
+}
+
+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..38dbbdbb
--- /dev/null
+++ b/viscoll-app/src/containers/CollationManager.js
@@ -0,0 +1,1116 @@
+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 UTLogo from '../assets/ut_logo.png'
+import UPLogo from '../assets/upenn_logo.png'
+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 {
+ updateTerm,
+ deleteTerm,
+ linkTerm,
+ unlinkTerm,
+} from '../actions/backend/termActions';
+import {
+ loadProject,
+ exportProject,
+ updateProject,
+} from '../actions/backend/projectActions';
+import fileDownload from 'js-file-download';
+import TermDialog from '../components/collationManager/dialog/TermDialog';
+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,
+ exportTerms: true,
+ },
+ selectAll: '',
+ leftSideBarOpen: true,
+ showTips: props.preferences.showTips,
+ imageViewerEnabled: false,
+ activeTerm: 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 term
+ const commonTerms = this.getCommonTerms(nextProps);
+ if (
+ this.state.activeTerm !== null &&
+ commonTerms.findIndex(termID => termID === this.state.activeTerm.id) <
+ 0 &&
+ !this.state.clickedFromDiagram
+ ) {
+ // Hide term when term was clicked from infobox and removed from selected object
+ this.setState({activeTerm: null});
+ } else if (this.state.activeTerm) {
+ // Update term object
+ this.setState({
+ activeTerm: nextProps.project.Terms[this.state.activeTerm.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, exportTerms) => {
+ this.setState(
+ {export: {open, type, label, exportCols, exportTerms}},
+ () => {
+ if (this.state.export.open && 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 < canvases.length; i += numCols) {
+ rows.push(canvases.slice(i, i + numCols));
+ }
+ let exportWidth = 0;
+ for (let i = 0; i < numCols; i++) {
+ exportWidth += canvases[i].width;
+ }
+ let exportHeight = 0;
+ for (let i = 0; i < rows.length; i++) {
+ let row = rows[i];
+ let maxRowHeight = 0;
+ for (let j = 0; j < row.length; j++) {
+ maxRowHeight = Math.max(maxRowHeight, row[j].height);
+ }
+ exportHeight += maxRowHeight;
+ }
+ finalCanvas.width = exportWidth;
+ finalCanvas.height = exportHeight;
+
+ // Combine canvases
+ let finalContext = finalCanvas.getContext('2d');
+ let globalY = 0;
+ for (let i = 0; i < rows.length; i++) {
+ let row = rows[i];
+ let x = 0;
+ let currentY = globalY;
+ let currentLargestY = 0;
+ for (let j = 0; j < row.length; j++) {
+ let canvas = row[j];
+ finalContext.drawImage(canvas, x, currentY);
+ x += canvas.width;
+ currentLargestY = Math.max(currentLargestY, canvas.height);
+ }
+ globalY += currentLargestY;
+ }
+ };
+
+ handleDownloadCollationDiagram = () => {
+ 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,
+ });
+ };
+
+ closeTermDialog = () => {
+ this.setState({activeTerm: null, clickedFromDiagram: false}, () =>
+ this.props.togglePopUp(false)
+ );
+ };
+ openTermDialog = (term, clickedFromDiagram = false) => {
+ this.setState({activeTerm: term, clickedFromDiagram}, () =>
+ this.props.togglePopUp(true)
+ );
+ };
+
+ getCommonTerms = (props = this.props) => {
+ // Find the common terms of all currently selected objects
+ const memberType = props.selectedObjects.type;
+ const members = props.selectedObjects.members;
+ let terms = [];
+ if (members.length > 0) {
+ terms = props.project[memberType + 's'][members[0]].terms;
+ for (let id of members) {
+ terms = this.intersect(
+ terms,
+ props.project[memberType + 's'][id].terms
+ );
+ }
+ }
+ return terms;
+ };
+
+ /**
+ * 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);
+ });
+ };
+
+ updateTerm = (termID, term) => {
+ this.props.updateTerm(
+ termID,
+ term,
+ this.props.project.id,
+ this.props.collationManager.filters
+ );
+ };
+
+ linkDialogTerm = (termID, objects) => {
+ this.props.linkTerm(
+ termID,
+ objects,
+ this.props.project.id,
+ this.props.collationManager.filters
+ );
+ };
+
+ linkAndUnlinkTerms = (termID, linkObjects, unlinkObjects) => {
+ this.props.linkAndUnlinkTerms(
+ termID,
+ linkObjects,
+ unlinkObjects,
+ this.props.project.id,
+ this.props.collationManager.filters
+ );
+ };
+
+ unlinkDialogTerm = (termID, objects) => {
+ this.props.unlinkTerm(
+ termID,
+ objects,
+ this.props.project.id,
+ this.props.collationManager.filters
+ );
+ };
+
+ deleteTerm = termID => {
+ this.closeTermDialog();
+ this.props.deleteTerm(
+ termID,
+ 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.",
+ 'Try exporting your collation in a multitude of different formats by selecting "EXPORT" in the 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).',
+ 'To add a subgroup (group inside a group), select a group and click the "ADD" button in the popup to the right of the diagram.',
+ "You can share your collation with others, even if they don't have an account. Click 'Share' and copy the link.",
+ 'You can easily import a set of images from a IIIF manifest. Select "Images" in the sidebar, paste a IIIF link in the "Manifest URL" box, and click "ADD".',
+ 'If you have suggestions, please fill out the feedback form by clicking the "FEEDBACK" button at the bottom of the sidebar.'
+ ];
+ 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}
+ exportTerms={this.state.export.exportTerms}
+ />
+ );
+
+ 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, 'svg', 'SVG');
+ }}
+ tabIndex={this.props.popUpActive ? -1 : 0}
+ />
+
+
+ {
+ 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}
+ />
+
+ Export Collation Formula
+
+ {
+ this.handleExportToggle(true, 'formula', 'Formula');
+ }}
+ tabIndex={this.props.popUpActive ? -1 : 0}
+ />
+
+ Export HTML
+
+ {
+ this.handleExportToggle(true, 'html', 'HTML');
+ }}
+ 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.openTermDialog(term, 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.openTermDialog(term, true)}
+ tabIndex={this.props.popUpActive ? -1 : 0}
+ showTerms={this.state.export.exportTerms}
+ />
+
+ {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));
+ },
+
+ updateTerm: (termID, term, projectID, filters) => {
+ dispatch(updateTerm(termID, term)).then(() =>
+ dispatch(reapplyFilterProject(projectID, filters))
+ );
+ },
+
+ deleteTerm: (termID, projectID, filters) => {
+ dispatch(deleteTerm(termID)).then(() =>
+ dispatch(reapplyFilterProject(projectID, filters))
+ );
+ },
+
+ linkTerm: (termID, object, projectID, filters) => {
+ dispatch(linkTerm(termID, object)).then(() =>
+ dispatch(reapplyFilterProject(projectID, filters))
+ );
+ },
+
+ unlinkTerm: (termID, object, projectID, filters) => {
+ dispatch(unlinkTerm(termID, object)).then(() =>
+ dispatch(reapplyFilterProject(projectID, filters))
+ );
+ },
+
+ linkAndUnlinkTerms: (
+ termID,
+ linkObjects,
+ unlinkObjects,
+ projectID,
+ filters
+ ) => {
+ if (linkObjects.length > 0 && unlinkObjects.length > 0) {
+ dispatch(linkTerm(termID, linkObjects))
+ .then(action => dispatch(unlinkTerm(termID, unlinkObjects)))
+ .then(() => dispatch(reapplyFilterProject(projectID, filters)));
+ } else if (linkObjects.length > 0) {
+ dispatch(linkTerm(termID, linkObjects)).then(() =>
+ dispatch(reapplyFilterProject(projectID, filters))
+ );
+ } else if (unlinkObjects.length > 0) {
+ dispatch(unlinkTerm(termID, 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..1e649b26
--- /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 TermDialog from '../components/collationManager/dialog/TermDialog';
+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,
+ activeTerm: 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,
+ );
+ }
+
+ closeTermDialog = () => {
+ this.setState({ activeTerm: null, clickedFromDiagram: false }, () => this.props.togglePopUp(false));
+ }
+
+ openTermDialog = (term, clickedFromDiagram = false) => {
+ this.setState({ activeTerm: term, clickedFromDiagram }, () => this.props.togglePopUp(true));
+ }
+
+ getCommonTerms = (props = this.props) => {
+ // Find the common terms of all currently selected objects
+ const memberType = props.selectedObjects.type;
+ const members = props.selectedObjects.members;
+ let terms = [];
+ if (members.length > 0) {
+ terms = props.project[memberType + "s"][members[0]].terms;
+ for (let id of members) {
+ terms = this.intersect(terms, props.project[memberType + "s"][id].terms);
+ }
+ }
+ return terms;
+ }
+
+ /**
+ * 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 = (
+
+ { },
+ unlinkTerm: () => { },
+ 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}
+ { },
+ deleteTerm: () => { },
+ linkTerm: () => { },
+ unlinkTerm: () => { },
+ linkAndUnlinkTerms: () => { },
+ }}
+ projectID={this.props.project.id}
+ Taxonomies={this.props.project.Taxonomies}
+ Terms={this.props.project.Terms}
+ 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..0de18dbd
--- /dev/null
+++ b/viscoll-app/src/containers/Dashboard.js
@@ -0,0 +1,316 @@
+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';
+import Panel from '../components/global/Panel';
+import UTLogo from '../assets/ut_logo.png';
+import UPLogo from '../assets/upenn_logo.png';
+
+/** 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..2117f6d8
--- /dev/null
+++ b/viscoll-app/src/containers/Feedback.js
@@ -0,0 +1,206 @@
+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 = {
+ feedbackOpen: false,
+ creditsOpen: false,
+ title: "",
+ feedback: "",
+ }
+ }
+ handleOpen = (type) => {
+ if (type === "feedback") {
+ this.setState({ feedbackOpen: true });
+ } else if (type === "credits") {
+ this.setState({ creditsOpen: true });
+ }
+ }
+ handleClose = (type) => {
+ if (type === "feedback") {
+ this.setState({
+ feedbackOpen: false,
+ title: "",
+ feedback: "",
+ });} else if (type === "credits") {
+ this.setState({ creditsOpen: false });
+ }
+ 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("feedback");
+ }
+ render() {
+ const feedbackActions = [
+ this.handleClose("feedback")}
+ />,
+ this.submit()}
+ />,
+ ];
+ const creditsActions = [
+ this.handleClose("credits")}
+ />
+ ]
+ const openInNewTab = (url) => {
+ const newWindow = window.open(url, '_blank', 'noopener,noreferrer')
+ if (newWindow) newWindow.opener = null
+ }
+ return (
+
+
+ { this.handleOpen("feedback"); this.props.togglePopUp(true) }}
+ backgroundColor="rgba(82, 108, 145, 0.2)"
+ tabIndex={this.props.tabIndex}
+ />
+ { this.handleOpen("credits"); this.props.togglePopUp(true) }}
+ backgroundColor="rgba(82, 108, 145, 0.2)"
+ tabIndex={this.props.tabIndex}
+ />
+ openInNewTab("https://viscoll.org/help/")}
+ 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}
+ />
+
+
+
+
+
+ VCEditor is a tool for building and visualizing the physical structure of manuscripts using the data model
+ provided by VisColl : Modeling and Visualizing the
+ Physical Construction of Codex Manuscripts, a project
+ co-directed by Alberto Campagnolo and Dot Porter.
+
+
+
+ VCEditor is a continuation of development of VisCodex ,
+ developed by the Old Books New Science lab at the University of Toronto, directed by Dr Alexandra Gillespie.
+ The VisCodex development team consisted of Monica Ung, Janarthenan Rajakumar, and Rachel Di Cresce.
+
+
+
+ For VCEditor we have made several
+ modification, particularly to the back end, to ensure that the output
+ validates to the most recent version of the VisColl data model. Development of the XSLT pipelines for
+ VCEditor was done by Gregor Middell, and primary development of VCEditor was done by Patrick Perkins.
+
+
+
+ Supervision of all development was provided by Doug Emery, and organizational support was provided by Lynn
+ Ransom. We would like to thank the Schoenberg Institute for Manuscript Studies and the University of
+ Pennsylvania Libraries for their continued support of the project.
+
+
+
+ );
+ }
+}
+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..31b764b5
--- /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.termMatches.length>0) {
+ let plural = nextProps.termMatches.length>1? "s" : "";
+ matches.push(nextProps.termMatches.length + " term" + 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", "term"].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==="term" && 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,
+ Terms: state.active.project.Terms,
+ 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,
+ Taxonomies: state.active.project.Taxonomies,
+ groupMatches: state.active.collationManager.filters.Groups,
+ leafMatches: state.active.collationManager.filters.Leafs,
+ sideMatches: state.active.collationManager.filters.Sides,
+ termMatches: state.active.collationManager.filters.Terms,
+ 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..345f2840
--- /dev/null
+++ b/viscoll-app/src/containers/InfoBox.js
@@ -0,0 +1,634 @@
+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 { addTerm, linkTerm } from '../actions/backend/termActions';
+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
+ );
+ };
+
+ // if the group is a subgroup, reset tacketing on the parent
+ resetTacket = (groupID) => {
+ let group = {};
+ group['tacketed'] = [];
+ group['sewing'] = []
+ let parentID = this.props.Groups[groupID].parentID
+ this.updateGroup(parentID, group);
+ }
+
+ deleteGroup = groupID => {
+ // reset tacket on parent
+ if (this.props.Groups[groupID].parentID) {
+ this.resetTacket(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);
+ };
+
+ linkTerm = termID => {
+ 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.linkTerm(
+ termID,
+ objects,
+ this.props.projectID,
+ this.props.filters
+ );
+ };
+
+ unlinkTerm = (termID, sideIndex) => {
+ let objects = [];
+ let type = this.props.selectedObjects.type;
+ for (let id of this.props.selectedObjects.members) {
+ objects.push({ id, type });
+ }
+ this.props.action.unlinkTerm(
+ termID,
+ objects,
+ this.props.projectID,
+ this.props.filters
+ );
+ };
+
+ createAndAttachTerm = (termTitle, taxonomy, description, uri, 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 term = {
+ project_id: this.props.projectID,
+ id: id,
+ title: termTitle,
+ taxonomy: taxonomy,
+ description: description,
+ uri: uri,
+ show: show,
+ };
+ this.props.createAndAttachTerm(
+ term,
+ objects,
+ this.props.projectID,
+ this.props.filters
+ );
+ };
+
+ generateFolioNumbers = startNumber => {
+ let leafIDs = this.props.collationManager.selectedObjects.members;
+
+ this.props.generateFolioNumbers(startNumber, leafIDs);
+ };
+
+ 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 termActions = {
+ linkTerm: this.linkTerm,
+ unlinkTerm: this.unlinkTerm,
+ createAndAttachTerm: this.createAndAttachTerm,
+ };
+
+ 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,
+ Terms: state.active.project.Terms,
+ Taxonomies: state.active.project.Taxonomies,
+ selectedObjects: state.active.collationManager.selectedObjects,
+ collationManager: state.active.collationManager,
+ termsManager: state.active.termsManager,
+ 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 })
+ );
+ },
+
+ createAndAttachTerm: (term, objects, projectID, filters) => {
+ dispatch(addTerm(term))
+ .then(() => dispatch(linkTerm(term.id, objects)))
+ .then(() => dispatch(reapplyFilterProject(projectID, filters)));
+ },
+
+ generateFolioNumbers: (startNumber, leafIDs) => {
+ dispatch(generateFolioNumbers(startNumber, leafIDs));
+ },
+
+ 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/Project.js b/viscoll-app/src/containers/Project.js
new file mode 100644
index 00000000..bcc12273
--- /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 TermsManager from './TermsManager';
+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 Terms 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 termsManager = ( );
+ const imageManager = ( );
+ let manager;
+ switch (this.props.managerMode) {
+ case "collationManager":
+ manager = collationManager;
+ break;
+ case "termsManager":
+ manager = termsManager;
+ 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..0e90b114
--- /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 Terms 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/TermsManager.js b/viscoll-app/src/containers/TermsManager.js
new file mode 100644
index 00000000..d4e88f21
--- /dev/null
+++ b/viscoll-app/src/containers/TermsManager.js
@@ -0,0 +1,301 @@
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import TopBar from './TopBar';
+import ManageTerms from '../components/termsManager/ManageTerms';
+import Taxonomy from '../components/termsManager/Taxonomy';
+import { Tabs, Tab } from 'material-ui/Tabs';
+// import Panel from '../components/global/Panel';
+import topbarStyle from '../styles/topbar';
+import {
+ changeManagerMode,
+ changeTermsTab,
+} from '../actions/backend/interactionActions';
+import {
+ addTerm,
+ updateTerm,
+ deleteTerm,
+ createTaxonomy,
+ updateTaxonomy,
+ deleteTaxonomy,
+ linkTerm,
+ unlinkTerm,
+} from '../actions/backend/termActions';
+import { sendFeedback } from '../actions/backend/userActions';
+import ManagersPanel from '../components/global/ManagersPanel';
+
+class TermsManager extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ Terms: props.Terms,
+ value: '',
+ filterTypes: {
+ title: true,
+ taxonomy: true,
+ description: true,
+ // TODO: add URI?
+ },
+ windowWidth: window.innerWidth,
+ };
+ }
+
+ componentDidMount() {
+ window.addEventListener('resize', this.resizeHandler);
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('resize', this.resizeHandler);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.setState({ Terms: nextProps.Terms }, () => this.applyFilter());
+ }
+
+ resizeHandler = () => {
+ this.setState({ windowWidth: window.innerWidth });
+ };
+
+ applyFilter = () => {
+ this.filterTerms(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()
+ );
+ };
+
+ handleAddTerm = term => {
+ const userID = this.props.user.id;
+ const date = Date.now().toString();
+ const IDHash = userID + date;
+ term['id'] = IDHash.substr(IDHash.length - 24);
+ this.props.addTerm(term);
+ };
+
+ filterTerms = (value, filterTypes) => {
+ if (value === '') {
+ this.setState({ Terms: this.props.Terms });
+ } else {
+ let filteredTerms = {};
+ let isNoneSelected = true;
+ for (let type of Object.keys(filterTypes)) {
+ if (filterTypes[type]) {
+ isNoneSelected = false;
+ break;
+ }
+ }
+ if (isNoneSelected)
+ filterTypes = { title: true, taxonomy: true, description: true };
+ for (let termID in this.props.Terms) {
+ const term = this.props.Terms[termID];
+ for (let type of Object.keys(filterTypes)) {
+ if (
+ filterTypes[type] &&
+ term[type].toUpperCase().includes(value.toUpperCase())
+ )
+ if (filteredTerms[termID]) break;
+ else filteredTerms[termID] = term;
+ }
+ }
+ this.setState({ Terms: filteredTerms });
+ }
+ };
+
+ toggleFilterDrawer = () => {
+ this.setState({ filterOpen: !this.state.filterOpen });
+ };
+
+ updateTerm = (termID, term) => {
+ this.props.updateTerm(termID, term, this.props);
+ };
+
+ linkTerm = (termID, object) => {
+ this.props.linkTerm(termID, object, this.props);
+ };
+
+ unlinkTerm = (termID, object) => {
+ this.props.unlinkTerm(termID, object, this.props);
+ };
+
+ linkAndUnlinkTerms = (termID, linkObjects, unlinkObjects) => {
+ this.props.linkAndUnlinkTerms(
+ termID,
+ linkObjects,
+ unlinkObjects,
+ this.props
+ );
+ };
+
+ render() {
+ let content = '';
+
+ if (this.props.activeTab === 'MANAGE') {
+ content = (
+
+ );
+ } else if (this.props.activeTab === 'TYPES') {
+ content = (
+
+ this.props.deleteTaxonomy(taxonomies, 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.changeTermsTab(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,
+ Terms: state.active.project.Terms,
+ Taxonomies: state.active.project.Taxonomies,
+ activeTab: state.active.termsManager.activeTab,
+ termsManager: state.active.termsManager,
+ managerMode: state.active.managerMode,
+ };
+};
+const mapDispatchToProps = dispatch => {
+ return {
+ changeManagerMode: managerMode => {
+ dispatch(changeManagerMode(managerMode));
+ },
+ changeTermsTab: tabName => {
+ dispatch(changeTermsTab(tabName));
+ },
+ addTerm: term => {
+ dispatch(addTerm(term));
+ },
+ updateTerm: (termID, term, props) => {
+ dispatch(updateTerm(termID, term));
+ },
+ deleteTerm: termID => {
+ dispatch(deleteTerm(termID));
+ },
+ createTaxonomy: taxonomy => {
+ dispatch(createTaxonomy(taxonomy));
+ },
+ updateTaxonomy: taxonomy => {
+ dispatch(updateTaxonomy(taxonomy));
+ },
+ deleteTaxonomy: (taxonomy, props) => {
+ dispatch(deleteTaxonomy(taxonomy));
+ },
+ linkTerm: (termID, object, props) => {
+ dispatch(linkTerm(termID, object));
+ },
+ unlinkTerm: (termID, object, props) => {
+ dispatch(unlinkTerm(termID, object));
+ },
+ linkAndUnlinkTerms: (termID, linkObjects, unlinkObjects, props) => {
+ if (linkObjects.length > 0) {
+ dispatch(linkTerm(termID, linkObjects));
+ }
+ if (unlinkObjects.length > 0) {
+ dispatch(unlinkTerm(termID, unlinkObjects));
+ }
+ },
+ sendFeedback: (title, message, userID) => {
+ dispatch(sendFeedback(title, message, userID));
+ },
+ };
+};
+export default connect(mapStateToProps, mapDispatchToProps)(TermsManager);
diff --git a/viscoll-app/src/containers/TopBar.js b/viscoll-app/src/containers/TopBar.js
new file mode 100644
index 00000000..884d20c1
--- /dev/null
+++ b/viscoll-app/src/containers/TopBar.js
@@ -0,0 +1,282 @@
+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 TermsFilter from '../components/termsManager/TermsFilter';
+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/vceditor_logo.svg';
+import { btnBase } from '../styles/button';
+import { connect } from 'react-redux';
+import {
+ logout,
+ updateProfile,
+ deleteProfile,
+} from '../actions/backend/userActions';
+import { API_URL } from '../store/axiosConfig';
+import light from '../styles/light';
+
+/** 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);
+ // set instance into state
+ let instanceURL = API_URL + '/instance'
+ let instanceRequest = () => {
+ fetch(instanceURL).then(async (response) => {
+ const data = await response.json();
+ this.setState({instance:data.current_instance})
+ })
+ }
+ instanceRequest();
+ }
+
+ 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 = (
+
+
+
+
+
+
+
+ }
+ 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.termsFilter ? (
+
+ ) : null}
+
+ {UserMenu}
+
+
+
+
+ );
+ }
+}
+const mapStateToProps = state => {
+ return {
+ user: state.user,
+ terms: state.active.terms,
+ 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..dd407129
--- /dev/null
+++ b/viscoll-app/src/helpers/renderHelper.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import Chip from 'material-ui/Chip';
+
+export function renderTermChip(props, term) {
+ let deleteFn = () => {
+ props.action.unlinkTerm(term.id);
+ };
+ if (props.isReadOnly) deleteFn = null;
+ return (
+ props.openTermDialog(term)}
+ tabIndex={props.tabIndex}
+ labelStyle={{ fontSize: props.windowWidth <= 1024 ? 12 : null }}
+ >
+ {term.title}
+
+ );
+}
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..eabc6c1e
--- /dev/null
+++ b/viscoll-app/src/reducers/dashboardReducer.js
@@ -0,0 +1,72 @@
+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_SUCCESS':
+ state = {
+ projects: action.payload.projects,
+ images: action.payload.images,
+ }
+ break;
+ 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..efd0f74c
--- /dev/null
+++ b/viscoll-app/src/reducers/editCollationReducer.js
@@ -0,0 +1,186 @@
+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,
+ Terms: action.payload.terms,
+ };
+ 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_TERMS_TAB':
+ state.termsManager.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 =
+ // adding more options to this breaks json export
+ action.payload.type === 'xml' ||
+ action.payload.type === 'svg' ||
+ action.payload.type === 'png' ||
+ action.payload.type === 'formula'
+ ? action.payload.data
+ : JSON.stringify(action.payload.Export, null, 4);
+ state.exportedImages = action.payload.Images.exportedImages;
+ break;
+
+ // FRONT-END ACTIONS
+ case 'CREATE_TAXONOMY_FRONTEND':
+ case 'UPDATE_TAXONOMY_FRONTEND':
+ case 'DELETE_TAXONOMY_FRONTEND':
+ case 'UPDATE_TERM_FRONTEND':
+ case 'DELETE_TERM_FRONTEND':
+ case 'LINK_TERM_FRONTEND':
+ case 'UNLINK_TERM_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_TERM_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..45c9b1a7
--- /dev/null
+++ b/viscoll-app/src/reducers/initialStates/active.js
@@ -0,0 +1,214 @@
+export const initialState = {
+ project: {
+ id: '',
+ title: '',
+ shelfmark: '',
+ uri: '',
+ notationStyle: {
+ name: 'notationStyle',
+ displayName: 'Notation Style',
+ options: ['r-v', 'recto-verso', 'a-b'],
+ isDropdown: true,
+ },
+ metadata: {
+ date: '',
+ },
+ manifests: {
+ DIYImages: {
+ id: 'DIYImages',
+ name: 'Uploaded Images',
+ images: [],
+ },
+ },
+ groupIDs: [],
+ leafIDs: [],
+ rectoIDs: [],
+ versoIDs: [],
+ Groups: {},
+ Leafs: {},
+ Rectos: {},
+ Versos: {},
+ Taxonomies: [],
+ Terms: {},
+ 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',
+ 'Sewn',
+ 'Pasted',
+ 'Tipped',
+ 'Drummed',
+ 'Stitched',
+ 'Other',
+ ],
+ isDropdown: true,
+ },
+ {
+ name: 'attached_below',
+ displayName: 'Attached Below',
+ options: [
+ 'None',
+ 'Sewn',
+ 'Pasted',
+ 'Tipped',
+ 'Drummed',
+ 'Stitched',
+ 'Other',
+ ],
+ isDropdown: true,
+ },
+ {
+ name: 'stub',
+ displayName: 'Stub',
+ options: ['No', 'Yes'],
+ isDropdown: true,
+ },
+ {
+ name: 'folio_number',
+ displayName: 'Folio Number',
+ },
+ ],
+ 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: 'script_direction',
+ displayName: 'Script Direction',
+ options: ['None', 'Left-to-Right', 'Right-To-Left', 'Top-To-Bottom'],
+ isDropdown: true,
+ },
+ {
+ name: 'page_number',
+ displayName: 'Page Number',
+ },
+ {
+ name: 'uri',
+ displayName: 'URI',
+ },
+ ],
+ term: [
+ {
+ name: 'title',
+ displayName: 'Title',
+ },
+ {
+ name: 'taxonomy',
+ displayName: 'Taxonomy',
+ isDropdown: true,
+ },
+ {
+ name: 'description',
+ displayName: 'Description',
+ },
+ {
+ name: 'uri',
+ displayName: 'URI',
+ },
+ ],
+ },
+ filters: {
+ filterPanelOpen: false,
+ Groups: [],
+ Leafs: [],
+ Sides: [],
+ Terms: [],
+ GroupsOfMatchingLeafs: [],
+ LeafsOfMatchingSides: [],
+ GroupsOfMatchingSides: [],
+ GroupsOfMatchingTerms: [],
+ LeafsOfMatchingTerms: [],
+ SidesOfMatchingTerms: [],
+ active: false,
+ hideOthers: false,
+ queries: [
+ {
+ type: null,
+ attribute: '',
+ attributeIndex: '',
+ values: [],
+ condition: '',
+ conjunction: '',
+ },
+ ],
+ selection: '',
+ },
+ flashItems: {
+ leaves: [],
+ groups: [],
+ },
+ visualizations: {
+ tacketed: '',
+ sewing: '',
+ },
+ },
+ termsManager: {
+ 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..7dcc73d9
--- /dev/null
+++ b/viscoll-app/src/store/axiosConfig.js
@@ -0,0 +1,57 @@
+import axios from 'axios';
+
+export let API_URL = '';
+
+// 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..c0703769
--- /dev/null
+++ b/viscoll-app/src/store/middleware/frontendBeforeActionsMiddleware.js
@@ -0,0 +1,290 @@
+import { cloneDeep } from 'lodash';
+
+import {
+ updateProject,
+ updatePreferences,
+ deleteProject,
+} from '../../actions/frontend/before/projectActions';
+
+import {
+ createTaxonomy,
+ updateTaxonomy,
+ deleteTaxonomy,
+ createTerm,
+ updateTerm,
+ linkTerm,
+ unlinkTerm,
+ deleteTerm,
+} from '../../actions/frontend/before/termActions';
+
+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,
+ generatePageNumbers,
+ generateFolioNumbers,
+} 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;
+ // Taxonomy Actions
+ case 'CREATE_TAXONOMY_FRONTEND':
+ action.payload.response = createTaxonomy(
+ action,
+ cloneDeep(store.getState().active)
+ );
+ break;
+ case 'UPDATE_TAXONOMY_FRONTEND':
+ action.payload.response = updateTaxonomy(
+ action,
+ cloneDeep(store.getState().active)
+ );
+ break;
+ case 'DELETE_TAXONOMY_FRONTEND':
+ action.payload.response = deleteTaxonomy(
+ action,
+ cloneDeep(store.getState().active)
+ );
+ break;
+ // Tern Actions
+ case 'CREATE_TERM_FRONTEND':
+ action.payload.response = createTerm(
+ action,
+ cloneDeep(store.getState().active)
+ );
+ break;
+ case 'UPDATE_TERM_FRONTEND':
+ action.payload.response = updateTerm(
+ action,
+ cloneDeep(store.getState().active)
+ );
+ break;
+ case 'LINK_TERM_FRONTEND':
+ action.payload.response = linkTerm(
+ action,
+ cloneDeep(store.getState().active)
+ );
+ break;
+ case 'UNLINK_TERM_FRONTEND':
+ action.payload.response = unlinkTerm(
+ action,
+ cloneDeep(store.getState().active)
+ );
+ break;
+ case 'DELETE_TERM_FRONTEND':
+ action.payload.response = deleteTerm(
+ 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 = generateFolioNumbers(
+ action,
+ cloneDeep(store.getState().active)
+ );
+ break;
+ case 'GENERATE_PAGE_NUMBERS_FRONTEND':
+ action.payload.response = generatePageNumbers(
+ action,
+ cloneDeep(store.getState().active)
+ );
+ 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;
diff --git a/viscoll-app/src/store/middleware/undoRedoMiddleware.js b/viscoll-app/src/store/middleware/undoRedoMiddleware.js
new file mode 100644
index 00000000..d47ee06f
--- /dev/null
+++ b/viscoll-app/src/store/middleware/undoRedoMiddleware.js
@@ -0,0 +1,240 @@
+import { cloneDeep } from 'lodash';
+
+import {
+ undoCreateLeaves,
+ undoUpdateLeaf,
+ undoUpdateLeaves,
+ undoDeleteLeaf,
+ undoDeleteLeaves,
+ undoAutoconjoin,
+ undoFolioNumbers,
+ undoPageNumbers,
+} 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 {
+ undoCreateTaxonomy,
+ undoUpdateTaxonomy,
+ undoDeleteTaxonomy,
+ undoLinkTerm,
+ undoUnlinkTerm,
+ undoDeleteTerm,
+} from '../../actions/undoRedo/termHelper';
+
+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 = undoFolioNumbers(action, store.getState().active);
+ break;
+ case 'GENERATE_PAGE_NUMBERS_FRONTEND':
+ historyAction = undoPageNumbers(action, store.getState().active);
+ 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_TAXONOMY_FRONTEND':
+ historyAction = undoUpdateTaxonomy(action, store.getState().active);
+ break;
+ case 'CREATE_TAXONOMY_FRONTEND':
+ historyAction = undoCreateTaxonomy(action, store.getState().active);
+ break;
+ case 'DELETE_TAXONOMY_FRONTEND':
+ historyAction = undoDeleteTaxonomy(action, store.getState().active);
+ break;
+ case 'LINK_TERM_FRONTEND':
+ historyAction = undoLinkTerm(action, store.getState().active);
+ break;
+ case 'UNLINK_TERM_FRONTEND':
+ historyAction = undoUnlinkTerm(action, store.getState().active);
+ break;
+ case 'DELETE_TERM_FRONTEND':
+ historyAction = undoDeleteTerm(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..67c1de1a
--- /dev/null
+++ b/viscoll-app/src/store/store.js
@@ -0,0 +1,55 @@
+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()
+ );
+ if (window.__REDUX_DEVTOOLS_EXTENSION__) {
+ storeEnhancers = compose(
+ storeEnhancers,
+ window.__REDUX_DEVTOOLS_EXTENSION__({trace: true})
+ );
+ }
+} 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..d28a8527
--- /dev/null
+++ b/viscoll-app/src/styles/App.css
@@ -0,0 +1,1072 @@
+.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 {
+ width: 100%;
+ height: 10vh;
+ background: #4ED6CB; }
+ .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 .manager, .sidebar .dashboard {
+ 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 .manager:hover, .sidebar .dashboard:hover {
+ background: rgba(78, 214, 203, 0.1);
+ font-weight: bold; }
+ .sidebar .navigation.active, .sidebar .active.manager, .sidebar .active.dashboard {
+ 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, .projectWorkspace, .dashboardWorkspace, .termsWorkspace, .imageWorkspace {
+ 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%; }
+
+.termsWorkspace, .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%; } }
+.sidebarLogo img {
+ width: 60%;
+ margin: 0;
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ margin-bottom:5%;
+}
+
+.termsManager {
+ height: 100%; }
+ .termsManager .container {
+ padding: 1em 2em 0em 2em; }
+ .termsManager .browse {
+ height: 100%; }
+ .termsManager .browse .termsList {
+ position: fixed;
+ top: 56px;
+ left: 18%;
+ width: 300px;
+ height: calc(100% - 56px);
+ overflow-y: auto;
+ text-align: center;
+ background: #e5e5e5; }
+ .termsManager .browse .termsList .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); }
+ .termsManager .browse .termsList .item:hover {
+ cursor: pointer;
+ background: #FFFFFF; }
+ .termsManager .browse .termsList .item .title {
+ padding-top: 10px;
+ font-weight: 500;
+ color: #4e4e4e; }
+ .termsManager .browse .termsList .item .taxonomy {
+ padding-top: 5px;
+ padding-bottom: 10px;
+ color: #727272;
+ font-style: italic;
+ font-size: 0.9em; }
+ .termsManager .browse .termsList .item.active {
+ background: #4ED6CB; }
+ .termsManager .browse .details {
+ position: relative;
+ width: calc(100% - 320px);
+ left: 300px; }
+ .termsManager .taxonomy {
+ padding: 1em 2em; }
+ .termsManager .taxonomy .items {
+ display: flex;
+ flex-wrap: wrap; }
+ .termsManager .taxonomy .item {
+ width: 220px;
+ margin-right: 1em; }
+ .termsManager .taxonomy .create {
+ display: flex;
+ margin-bottom: 2em; }
+ .termsManager .taxonomy .create .input {
+ margin-right: 1em; }
+
+.termsInfobox {
+ display: flex;
+ flex-wrap: wrap; }
+
+.termSearch {
+ height: 56px; }
+ @media screen and (max-width: 890px) {
+ .termSearch {
+ width: 170px; } }
+ .termSearch .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; }
+
+.termForm {
+ width: 100%;
+ margin-left: 2%;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: flex-start; }
+ .termForm .label {
+ padding-top: 1em;
+ width: 100%; }
+ @media screen and (max-width: 768px) {
+ .termForm .label {
+ font-size: 0.8em; } }
+ .termForm .input {
+ width: 80%; }
+ @media screen and (max-width: 768px) {
+ .termForm .input {
+ padding-left: 5%;
+ width: 75%; } }
+ .termForm .input .textOnly {
+ margin-top: 1em;
+ color: #4e4e4e; }
+ .termForm .buttons {
+ text-align: right;
+ width: 100%;
+ padding-top: 2em; }
+ .termForm .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 .sideBacklog, .imageManager .imageMapper .bottomPanel .imageBacklog {
+ width: 49%;
+ padding: 0em; }
+ .imageManager .imageMapper .bottomPanel .backlog .scrollable, .imageManager .imageMapper .bottomPanel .sideBacklog .scrollable, .imageManager .imageMapper .bottomPanel .imageBacklog .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; }
+.feedbackDialog a:visited {
+ text-decoration: none;
+ color: dodgerblue;
+}
+
+.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..03e30b42
--- /dev/null
+++ b/viscoll-app/src/styles/App.css.map
@@ -0,0 +1,7 @@
+{
+"version": 3,
+"mappings": "AAAA,QAAS;EACP,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,KAAK;EACb,UAAU,ECAE,OAAO;EDCjB,UAAU,EAAE,MAAM;EAEpB,mBAAW;IACT,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,IAAI;IACZ,OAAO,EAAE,IAAI;IACb,WAAW,EAAE,MAAM;IACnB,aAAa,EAAE,MAAM;EAGvB,YAAI;IACF,KAAK,EAAE,IAAI;EAGb,mBAAW;IACT,KAAK,EAAE,GAAG;EAGZ,oBAAY;IACV,KAAK,EAAE,GAAG;IACV,YAAY,EAAE,EAAE;EAGlB,qBAAa;IACX,KAAK,EAAE,IAAI;IACX,MAAM,EAAC,IAAI;IACX,UAAU,EC3BA,OAAO;ED8BnB,WAAG;IACD,MAAM,EAAE,iBAAe;EAGzB,uBAAe;IACb,aAAa,EAAE,KAAK;EAGtB,oBAAY;IACV,UAAU,EAAE,KAAK;EAGnB,UAAE;IACA,KAAK,EC3CK,OAAO;ED8CnB,UAAE;IACA,KAAK,EC/CK,OAAO;IDgDjB,MAAM,EAAE,OAAO;IACf,eAAe,EAAE,SAAS;IAE1B,gBAAQ;MACN,KAAK,EAAE,OAAmB;;AExDhC,QAAS;EACP,QAAQ,EAAE,KAAK;EACf,OAAO,EAAE,KAAK;EACd,GAAG,EAAE,IAAI;EACT,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,iBAAiB;EACzB,UAAU,EDJE,OAAO;ECKnB,OAAO,EAAE,CAAC;ECMV,kBAAkB,EAAE,qBAAiC;EACjD,cAAc,EAAE,qBAAiC;EAC7C,UAAU,EAAE,qBAAiC;EDNrD,UAAU,EAAE,IAAI;EAChB,OAAO,EAAE,IAAI;EAEb,oBAAc;IACZ,OAAO,EAAE,OAAO;EAGlB,eAAS;IACP,OAAO,EAAE,CAAC;EAEZ,WAAG;IACD,MAAM,EAAE,iBAAe;IACvB,MAAM,EAAE,CAAC;EAEX,WAAG;IACD,cAAc,EAAE,SAAS;IACzB,KAAK,EDpBK,OAAO;ICqBjB,SAAS,EAAE,GAAG;IACd,WAAW,EAAE,GAAG;EAElB,WAAG;IACD,KAAK,EDzBK,OAAO;IC0BjB,SAAS,EAAE,KAAK;IAChB,WAAW,EAAE,OAAO;IACpB,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE,GAAG;IACX,cAAc,EAAE,SAAS;IACzB,wBAAe;MACb,WAAW,EAAE,GAAG;IAElB,oCAAuC;MAVzC,WAAG;QAWC,SAAS,EAAE,KAAK;EAIlB,uBAAQ;IACN,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,IAAI;IACb,eAAe,EAAE,aAAa;IAC9B,oCAAuC;MAJzC,uBAAQ;QAKJ,SAAS,EAAE,MAAM;EAGrB,wBAAS;IACP,OAAO,EAAE,GAAG;IACZ,UAAU,EAAE,uBAA6B;IACzC,+BAAS;MACP,OAAO,EAAE,IAAI;EAGjB,0BAAa;IACX,aAAa,EAAE,IAAI;EAGvB,oBAAY;IACV,OAAO,EAAE,eAAe;IACxB,UAAU,EAAE,MAAM;IAkBlB,UAAU,EAAE,OAAoB;IAhBhC,yBAAK;MACH,SAAS,EAAE,IAAI;MACf,KAAK,EDjEG,OAAO;MCkEf,cAAc,EAAE,SAAS;IAE3B,yBAAK;MACH,SAAS,EAAE,KAAK;MAChB,KAAK,EDpEG,OAAO;MCqEf,UAAU,EAAE,IAAI;MAChB,WAAW,EAAE,KAAK;IAEpB,2BAAO;MACL,UAAU,EAAE,KAAK;MACjB,YAAY,EAAE,KAAK;MACnB,UAAU,EAAE,KAAK;EAKrB,4DAAY;IACV,UAAU,EAAE,MAAM;IAClB,MAAM,EAAE,GAAG;IACX,cAAc,EAAE,SAAS;IC5E3B,kBAAkB,EAAE,qBAAiC;IACjD,cAAc,EAAE,qBAAiC;IAC7C,UAAU,EAAE,qBAAiC;ID4EnD,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,CAAC;IACT,UAAU,EAAE,IAAI;IAChB,KAAK,EDzFK,OAAO;IC0FjB,SAAS,EAAE,GAAG;IACd,8EAAQ;MACN,UAAU,EAAE,uBAA0B;MACtC,WAAW,EAAE,IAAI;IAEnB,iFAAS;MACP,UAAU,EDjGF,OAAO;MCkGf,WAAW,EAAE,IAAI;MACjB,KAAK,ED/FG,OAAO;ECkGnB,iBAAS;IAEP,OAAO,EAAE,SAAS;IAClB,oCAAuC;MAHzC,iBAAS;QAIL,SAAS,EAAE,KAAK;EAGpB,mBAAW;IAET,cAAc,EAAE,IAAI;IACpB,OAAO,EAAE,UAAU;IACnB,yBAAQ;MACN,UAAU,EAAE,yBAA2B;IAEzC,0BAAS;MACP,UAAU,EAAE,wBAA2B;MACvC,WAAW,EAAE,IAAI;MACjB,KAAK,EDtHG,OAAO;ICwHjB,oCAAuC;MAZzC,mBAAW;QAaP,SAAS,EAAE,KAAK;EAIpB,gBAAQ;IACJ,WAAW,EAAE,IAAI;;AAIvB,SAAU;EACR,QAAQ,EAAE,KAAK;EACf,MAAM,EAAE,CAAC;EACT,IAAI,EAAE,CAAC;EACP,KAAK,EAAE,GAAG;EACV,OAAO,EAAC,KAAK;EACb,UAAU,EAAE,MAAM;;AAGpB,SAAU;ECnIR,kBAAkB,EAAE,qBAAiC;EACjD,cAAc,EAAE,qBAAiC;EAC7C,UAAU,EAAE,qBAAiC;EDmIrD,UAAU,EAAE,kBAAkB;EAC9B,eAAQ;IACN,UAAU,EAAE,kBAAkB;IAC9B,MAAM,EAAE,OAAO;;AErJnB,iBAAkB;EAChB,OAAO,EAAE,GAAG;EACZ,WAAW,EAAE,GAAG;EAEhB,uBAAM;IACJ,WAAW,EAAE,GAAG;IAChB,SAAS,EAAE,KAAK;IAChB,4BAAK;MACH,KAAK,EAAE,qBAA2B;;ACRxC,QAAS;EACP,QAAQ,EAAE,KAAK;EACf,OAAO,EAAE,YAAY;EACrB,KAAK,EAAE,GAAG;EACV,cAAc,EAAC,GAAG;EAClB,KAAK,EAAE,CAAC;EACR,GAAG,EAAE,IAAI;EACT,UAAU,EAAE,KAAK;EACjB,UAAU,EAAE,GAAG;EACf,UAAU,EAAE,IAAI;EAChB,MAAM,EAAE,WAAW;EFFlB,kBAAkB,EAAE,mCAAO;EAC3B,UAAU,EAAE,mCAAO;EEIpB,eAAO;IACL,OAAO,EAAE,mBAAmB;IAC5B,qCAAsC;MAFxC,eAAO;QAGH,OAAO,EAAE,iBAAiB;IAE5B,oCAAuC;MALzC,eAAO;QAMH,OAAO,EAAE,iBAAiB;IAG5B,oBAAK;MACH,OAAO,EAAE,IAAI;MACb,SAAS,EAAE,IAAI;MACf,eAAe,EAAE,aAAa;MAC9B,WAAW,EAAE,MAAM;IAErB,sBAAO;MACL,KAAK,EAAE,GAAG;IAEZ,sBAAO;MACL,KAAK,EAAE,GAAG;MACV,oCAAuC;QAFzC,sBAAO;UAGH,KAAK,EAAE,GAAG;EAIhB,qBAAa;IACX,MAAM,EAAE,CAAC;IACT,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE,CAAC;IACV,MAAM,EAAE,CAAC;IACT,SAAS,EAAE,GAAG;IAId,QAAQ,EAAE,IAAI;IACd,SAAS,EAAE,GAAG;IAJd,8BAAW;MACT,WAAW,EAAE,KAAK;;AC7CxB,oFAAW;EACP,QAAQ,EAAE,QAAQ;EAClB,IAAI,EAAE,GAAG;EACT,GAAG,EAAE,IAAI;;AAEb,iBAAkB;EAEd,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,EAAE;EACV,8BAAa;IACT,OAAO,EAAE,IAAI;IACb,gDAAkB;MACd,UAAU,EAAE,GAAG;IAEnB,iDAAmB;MACf,UAAU,EAAE,IAAI;;AAK5B,mBAAoB;EAEhB,KAAK,EAAE,GAAG;EHTZ,kBAAkB,EAAE,wCAAiC;EACjD,cAAc,EAAE,wCAAiC;EAC7C,UAAU,EAAE,wCAAiC;EGUnD,oCAAmB;IACf,KAAK,EAAE,GAAG;;AAGlB,gCAAiC;EAE7B,KAAK,EAAE,GAAG;;AC/Bd,eAAgB;EJQb,kBAAkB,EAAE,mCAAO;EAC3B,UAAU,EAAE,mCAAO;EAIpB,kBAAkB,EAAE,wBAAiC;EACjD,cAAc,EAAE,wBAAiC;EAC7C,UAAU,EAAE,wBAAiC;EIJrD,UAAU,ENNE,OAAO;EMOnB,aAAa,EAAE,GAAG;EAClB,MAAM,EAAE,OAAO;EACf,MAAM,EAAE,iBAAgB;EAbxB,qBAAM;IACJ,OAAO,EAAE,CAAC;IACV,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;IACX,UAAU,EAAE,KAAK;IACjB,KAAK,EAAC,KAAK;EAUb,qBAAQ;IACN,MAAM,EAAE,iBAAe;EAEzB,sBAAS;IACP,UAAU,ENhBA,OAAO;EMmBnB,6BAAc;IACZ,OAAO,EAAE,eAAe;IAExB,oCAAS;MACP,OAAO,EAAE,IAAI;;AAKnB,cAAe;EACb,OAAO,EAAE,IAAI;EACb,WAAW,EAAE,OAAO;EACpB,oBAAQ;IACN,eAAe,EAAE,aAAa;IAE9B,kCAAc;MACZ,OAAO,EAAE,IAAI;IAGf,kCAAc;MACZ,KAAK,EAAE,KAAK;;AAIlB,cAAe;EJvCZ,kBAAkB,EAAE,mCAAO;EAC3B,UAAU,EAAE,mCAAO;EIwCpB,aAAa,EAAE,KAAK;EACpB,MAAM,EAAE,OAAO;EACf,UAAU,EN9CE,OAAO;EMgDnB,2BAAa;IACX,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,CAAC;IJ1Cd,kBAAkB,EAAE,wBAAiC;IACjD,cAAc,EAAE,wBAAiC;IAC7C,UAAU,EAAE,wBAAiC;II0CnD,MAAM,EAAE,iBAAgB;IAExB,kCAAS;MACP,UAAU,ENxDF,OAAO;IM2DjB,iCAAQ;MACN,MAAM,EAAE,iBAAe;;AAK7B,SAAU;EACR,KAAK,EAAE,IAAI;EACX,OAAO,EAAE,IAAI;EACb,WAAW,EAAE,MAAM;EACnB,WAAW,EAAE,GAAG;EAChB,YAAY,EAAE,IAAI;EAClB,UAAU,EAAE,IAAI;EAChB,oCAAsC;IAPxC,SAAU;MAQN,SAAS,EAAE,KAAK;;AAGpB,eAAgB;EACd,SAAS,EAAE,CAAC;EACZ,OAAO,EAAE,IAAI;;AAEf,UAAW;EACT,OAAO,EAAE,IAAI;EACb,WAAW,EAAE,MAAM;EACnB,SAAS,EAAE,KAAK;EAChB,OAAO,EAAE,WAAW;EACpB,KAAK,EAAE,qBAA2B;EAClC,WAAW,EAAE,GAAG;EAChB,WAAW,EAAE,gCAAsC;EACnD,QAAQ,EAAE,MAAM;EAChB,aAAa,EAAE,QAAQ;EACvB,WAAW,EAAE,MAAM;EACnB,oCAAsC;IAXxC,UAAW;MAYP,SAAS,EAAE,KAAK;EAGlB,eAAK;IACH,QAAQ,EAAE,MAAM;IAChB,aAAa,EAAE,QAAQ;IACvB,WAAW,EAAE,MAAM;IACnB,SAAS,EAAE,IAAI;IACf,oCAAuC;MALzC,eAAK;QAMD,SAAS,EAAE,IAAI;EAInB,4BAAkB;IAChB,KAAK,EAAE,qBAA2B;IAClC,OAAO,EAAE,KAAK;EAGhB,mCAAkB;IAChB,KAAK,EN3GK,OAAO;EM+GjB,uBAAQ;IACN,KAAK,ENhHG,OAAO;EMkHjB,mCAAkB;IAChB,KAAK,EAAE,qBAA2B;;AAKxC,YAAa;EACX,SAAS,EAAE,CAAC;EACZ,OAAO,EAAE,IAAI;EACb,cAAc,EAAE,MAAM;EACtB,WAAW,EAAE,gCAAsC;EACnD,QAAQ,EAAE,MAAM;EAEhB,kBAAM;IACJ,QAAQ,EAAE,MAAM;IAChB,aAAa,EAAE,QAAQ;IACvB,WAAW,EAAE,MAAM;IACnB,MAAM,EAAE,iBAAgB;IACxB,MAAM,EAAE,OAAO;IAEf,yBAAS;MACP,UAAU,EN3IF,OAAO;IM6IjB,wBAAQ;MACN,MAAM,EAAE,iBAAe;MACvB,KAAK,EAAE,OAAyB;IAGlC,wBAAM;MACJ,KAAK,EAAE,IAAI;MACX,OAAO,EAAE,YAAY;MACrB,OAAO,EAAE,iBAAiB;MAC1B,cAAc,EAAE,GAAG;MACnB,WAAW,EAAE,MAAM;MACnB,YAAY,EAAE,gCAAsC;IAEtD,6BAAW;MACT,OAAO,EAAE,YAAY;MACrB,cAAc,EAAE,GAAG;MACnB,OAAO,EAAE,QAAQ;MACjB,WAAW,EAAE,GAAG;MAChB,YAAY,EAAE,gCAAsC;MACpD,KAAK,EAAE,qBAA2B;MJvJtC,kBAAkB,EAAE,uBAAiC;MACjD,cAAc,EAAE,uBAAiC;MAC7C,UAAU,EAAE,uBAAiC;MIuJjD,SAAS,EAAE,IAAI;MAEf,kCAAK;QACH,WAAW,EAAE,MAAM;QACnB,KAAK,EAAE,qBAA2B;MAEpC,mCAAQ;QACN,KAAK,EAAE,OAAyB;MAElC,oCAAS;QACP,KAAK,EAAE,OAAyB;IAGpC,8BAAc;MACZ,aAAa,EAAE,+BAAsC;MACrD,oCAAQ;QACR,aAAa,EAAE,iBAAe;;AAMpC,WAAY;EACV,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;EACZ,WAAW,EAAE,gCAAsC;EACnD,MAAM,EAAE,OAAO;EACf,iBAAM;IACJ,OAAO,EAAE,KAAK;IACd,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,IAAI;IACZ,WAAW,EAAE,GAAG;IAChB,UAAU,EAAE,MAAM;IAClB,KAAK,EAAE,qBAA2B;IAClC,MAAM,EAAE,iBAAgB;IAExB,6BAAc;MACZ,aAAa,EAAE,gCAAsC;IAEvD,wBAAS;MACP,UAAU,EN1MF,OAAO;IM4MjB,uBAAQ;MACN,MAAM,EAAE,iBAAe;MACvB,KAAK,EAAE,OAAyB;;AAKtC,MAAO;EACL,cAAc,EAAE,QAAQ;EACxB,kBAAkB,EAAE,EAAE;;AJjLtB,2BAEC;EImLD,EAAK;IAAE,MAAM,EAAE,eAAgC;EAC/C,GAAI;IAAE,MAAM,EAAE,iBAA+B;EAE7C,GAAI;IAAE,MAAM,EAAE,iBAA+B;EAC7C,IAAO;IAAE,MAAM,EAAE,eAAgC;AJtLjD,wBAEC;EIgLD,EAAK;IAAE,MAAM,EAAE,eAAgC;EAC/C,GAAI;IAAE,MAAM,EAAE,iBAA+B;EAE7C,GAAI;IAAE,MAAM,EAAE,iBAA+B;EAC7C,IAAO;IAAE,MAAM,EAAE,eAAgC;AJnLjD,uBAEC;EI6KD,EAAK;IAAE,MAAM,EAAE,eAAgC;EAC/C,GAAI;IAAE,MAAM,EAAE,iBAA+B;EAE7C,GAAI;IAAE,MAAM,EAAE,iBAA+B;EAC7C,IAAO;IAAE,MAAM,EAAE,eAAgC;AJhLjD,mBAEC;EI0KD,EAAK;IAAE,MAAM,EAAE,eAAgC;EAC/C,GAAI;IAAE,MAAM,EAAE,iBAA+B;EAE7C,GAAI;IAAE,MAAM,EAAE,iBAA+B;EAC7C,IAAO;IAAE,MAAM,EAAE,eAAgC;ACjOnD,OAAQ;EACN,QAAQ,EAAE,KAAK;EACf,IAAI,EAAC,GAAG;EACR,KAAK,EAAE,IAAI;EACX,OAAO,EAAE,IAAI;ELIZ,kBAAkB,EAAE,mCAAO;EAC3B,UAAU,EAAE,mCAAO;EKFpB,mBAAc;IACZ,OAAO,EAAE,IAAI;EAGf,aAAM;IACJ,KAAK,EAAC,IAAI;IACV,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,IAAI;IACZ,UAAU,EAAE,MAAM;IAClB,QAAQ,EAAE,QAAQ;IAClB,UAAU,EPfA,OAAO;IOgBjB,iBAAI;MACF,KAAK,EAAE,GAAG;MACV,MAAM,EAAE,CAAC;MACT,QAAQ,EAAE,QAAQ;MAClB,GAAG,EAAE,GAAG;MACR,IAAI,EAAE,GAAG;MACT,aAAa,EAAE,qBAAqB;MACpC,SAAS,EAAE,qBAAqB;MAChC,oCAAuC;QARzC,iBAAI;UASA,KAAK,EAAE,GAAG;;AC3BlB,aAAc;EACZ,MAAM,EAAE,IAAI;EACZ,wBAAW;IACT,OAAO,EAAE,eAAe;EAG1B,qBAAQ;IACN,MAAM,EAAE,IAAI;IACZ,gCAAW;MA8BT,QAAQ,EAAE,KAAK;MACf,GAAG,EAAC,IAAI;MACR,IAAI,EAAC,GAAG;MACR,KAAK,EAAE,KAAK;MACZ,MAAM,EAAE,iBAAiB;MACzB,UAAU,EAAE,IAAI;MAChB,UAAU,EAAE,MAAM;MAClB,UAAU,EAAE,OAAiB;MApC7B,sCAAM;QACJ,MAAM,EAAE,iBAAiB;QACzB,QAAQ,EAAE,MAAM;QNEtB,kBAAkB,EAAE,4BAAiC;QACjD,cAAc,EAAE,4BAAiC;QAC7C,UAAU,EAAE,4BAAiC;QMF/C,KAAK,EAAE,GAAG;QACV,SAAS,EAAE,GAAG;QACd,UAAU,ERTJ,OAAO;QQUb,MAAM,EAAE,GAAG;QNRhB,kBAAkB,EAAE,kCAAO;QAC3B,UAAU,EAAE,kCAAO;QMSd,4CAAQ;UACN,MAAM,EAAE,OAAO;UACf,UAAU,ERfN,OAAO;QQiBb,6CAAO;UACL,WAAW,EAAE,IAAI;UACjB,WAAW,EAAE,GAAG;UAChB,KAAK,ERjBD,OAAO;QQmBb,4CAAM;UACJ,WAAW,EAAE,GAAG;UAChB,cAAc,EAAE,IAAI;UACpB,KAAK,ERvBD,OAAO;UQwBX,UAAU,EAAE,MAAM;UAClB,SAAS,EAAE,KAAK;QAElB,6CAAS;UACP,UAAU,ER/BN,OAAO;IQ2CjB,8BAAS;MACP,QAAQ,EAAE,QAAQ;MAClB,KAAK,EAAE,kBAAkB;MACzB,IAAI,EAAE,KAAK;EAGf,uBAAU;IACR,OAAO,EAAE,OAAO;IAChB,8BAAO;MACL,OAAO,EAAE,IAAI;MACb,SAAS,EAAE,IAAI;IAEjB,6BAAM;MACJ,KAAK,EAAE,KAAK;MACZ,YAAY,EAAE,GAAG;IAEnB,+BAAQ;MACN,OAAO,EAAE,IAAI;MACb,aAAa,EAAE,GAAG;MAClB,sCAAO;QACL,YAAY,EAAE,GAAG;;AAKzB,aAAc;EACZ,OAAO,EAAE,IAAI;EACb,SAAS,EAAE,IAAI;;AAEjB,WAAY;EACV,MAAM,EAAE,IAAI;EACZ,oCAAqC;IAFvC,WAAY;MAGR,KAAK,EAAE,KAAK;EAGd,0BAAe;IACb,WAAW,EAAE,GAAG;;AAGpB,cAAe;EACb,UAAU,EAAE,MAAM;EAClB,OAAO,EAAE,CAAC;EACV,UAAU,ERpFE,OAAO;EQqFnB,KAAK,EAAE,KAAK;ENzFZ,qBAAqB,EM0FE,eAAe;ENzFnC,kBAAkB,EMyFE,eAAe;ENxFlC,iBAAiB,EMwFE,eAAe;ENvF9B,aAAa,EMuFE,eAAe;EN9EtC,kBAAkB,EAAE,qBAAiC;EACjD,cAAc,EAAE,qBAAiC;EAC7C,UAAU,EAAE,qBAAiC;EMmFrD,OAAO,EAAE,OAAO;EAJhB,oCAAqC;IARvC,cAAe;MASX,KAAK,EAAE,KAAK;EAId,qBAAQ;IACN,UAAU,EAAE,OAAO;IACnB,OAAO,EAAE,CAAC;;AAGd,SAAU;EACR,KAAK,EAAE,IAAI;EACX,WAAW,EAAE,EAAE;EACf,OAAO,EAAE,IAAI;EACb,SAAS,EAAE,IAAI;EACf,WAAW,EAAE,UAAU;EAEvB,gBAAO;IACL,WAAW,EAAC,GAAG;IACf,KAAK,EAAE,GAAG;IACV,oCAAuC;MAHzC,gBAAO;QAIH,SAAS,EAAE,KAAK;EAGpB,gBAAO;IACL,KAAK,EAAE,GAAG;IACV,oCAAuC;MAFzC,gBAAO;QAGH,YAAY,EAAE,EAAE;QAChB,KAAK,EAAE,GAAG;IAEZ,0BAAU;MACR,UAAU,EAAE,GAAG;MACf,KAAK,ERtHG,OAAO;EQyHnB,kBAAS;IACP,UAAU,EAAE,KAAK;IACjB,KAAK,EAAE,IAAI;IACX,WAAW,EAAE,GAAG;EAElB,4BAAmB;IACjB,KAAK,EAAE,IAAI;;ACvIf,OAAQ;EACN,KAAK,EAAE,IAAI;EACX,UAAU,EAAE,GAAG;EACf,QAAQ,EAAE,QAAQ;EAClB,OAAO,EAAE,CAAC;EACV,IAAI,EAAE,CAAC;EACP,OAAO,EAAE,IAAI;EACb,eAAe,EAAE,QAAQ;EPMzB,kBAAkB,EAAE,oBAAiC;EACjD,cAAc,EAAE,oBAAiC;EAC7C,UAAU,EAAE,oBAAiC;;AOLvD,gBAAiB;EACf,UAAU,EAAC,iBAAe;EAC1B,QAAQ,EAAE,KAAK;EACf,KAAK,EAAE,GAAG;EACV,UAAU,EAAE,GAAG;EACf,UAAU,ETVE,OAAO;ESWnB,cAAc,EAAE,IAAI;EACpB,QAAQ,EAAE,IAAI;EPJd,kBAAkB,EAAE,wCAAiC;EACjD,cAAc,EAAE,wCAAiC;EAC7C,UAAU,EAAE,wCAAiC;EAPpD,kBAAkB,EAAE,kCAAO;EAC3B,UAAU,EAAE,kCAAO;;AOYtB,UAAW;EACT,OAAO,EAAE,IAAI;EACb,WAAW,EAAE,UAAU;EACvB,eAAe,EAAE,MAAM;EACvB,uBAAe;IACf,UAAU,EAAE,KAAK;EAGjB,uBAAa;IACX,KAAK,EAAE,KAAK;IACZ,WAAW,EAAE,IAAI;IACjB,mCAAc;MACZ,WAAW,EAAC,IAAI;IAElB,kCAAa;MACX,KAAK,EAAE,KAAK;MACZ,UAAU,EAAE,MAAM;;AAIxB,cAAe;EACb,cAAc,EAAE,SAAS;EACzB,SAAS,EAAE,KAAK;EAChB,WAAW,EAAE,GAAG;EAChB,KAAK,ETtCO,OAAO;;AUPrB,WAAY;EACV,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,KAAK;EACb,UAAU,EVDE,OAAO;EUEnB,OAAO,EAAE,IAAI;EACb,WAAW,EAAE,MAAM;EACnB,sBAAW;IACT,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE,IAAI;IACb,cAAc,EAAE,MAAM;IACtB,eAAe,EAAE,MAAM;IACvB,WAAW,EAAE,MAAM;IACnB,UAAU,EAAE,GAAG;EAEjB,iBAAM;IACJ,KAAK,EAAE,GAAG;IACV,SAAS,EAAE,KAAK;IAChB,aAAa,EAAE,GAAG;;ACjBtB,WAAY;EACV,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,KAAK;EACb,UAAU,EXDE,OAAO;EWEnB,OAAO,EAAE,IAAI;EACb,WAAW,EAAE,MAAM;EACnB,sBAAW;IACT,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE,IAAI;IACb,cAAc,EAAE,MAAM;IACtB,eAAe,EAAE,MAAM;IACvB,WAAW,EAAE,MAAM;IACnB,UAAU,EAAE,IAAI;IAChB,yBAAG;MACD,SAAS,EAAE,GAAG;MACd,KAAK,EXVG,OAAO;MWWf,cAAc,EAAE,CAAC;MACjB,aAAa,EAAE,IAAI;IAErB,wBAAE;MACA,KAAK,EXfG,OAAO;MWgBf,aAAa,EAAE,IAAI;;ACpBvB,iBAAQ;EACN,OAAO,EAAE,IAAI;EACb,OAAO,EAAE,OAAO;EAChB,SAAS,EAAE,KAAK;EAChB,qBAAI;IACF,KAAK,EAAE,GAAG;AAGd,gBAAO;EACL,KAAK,EAAE,IAAI;EACX,OAAO,EAAE,IAAI;EACb,UAAU,EAAE,IAAI;EAChB,WAAW,EAAE,kCAAqC;EAClD,YAAY,EAAE,kCAAqC;EACnD,UAAU,EAAE,kCAAyC;EACrD,aAAa,EAAE,kCAAqC;EACpD,OAAO,EAAE,OAAO;EAChB,UAAU,EAAE,wBAA2B;EACvC,SAAS,EAAE,KAAK;EAChB,KAAK,EZZK,OAAO;EYajB,MAAM,EAAE,OAAO;EVRjB,kBAAkB,EAAE,4BAAiC;EACjD,cAAc,EAAE,4BAAiC;EAC7C,UAAU,EAAE,4BAAiC;EUSnD,sBAAQ;IACN,UAAU,EZpBF,OAAO;EYsBjB,sBAAQ;IACN,aAAa,EAAE,KAAK;IACpB,aAAa,EAAE,uBAA0B;IACzC,aAAa,EAAE,GAAG;IAClB,MAAM,EAAE,iBAAe;EAEzB,yBAAW;IACT,UAAU,EZ9BF,OAAO;EYiCjB,+BAAiB;IACf,OAAO,EAAE,CAAC;EAGZ,oBAAI;IACF,KAAK,EAAE,GAAG;;ACxCZ,wBAAK;EACH,OAAO,EAAE,IAAI;EACb,+BAAO;IACL,WAAW,EAAC,GAAG;IACf,SAAS,EAAE,KAAK;EAElB,+BAAO;IACL,SAAS,EAAE,CAAC;AAIlB,8BAAiB;EACf,OAAO,EAAE,eAAe;EACxB,iCAAG;IACD,SAAS,EAAE,KAAK;IAChB,OAAO,EAAE,eAAe;IACxB,MAAM,EAAC,GAAG;IACV,KAAK,EbXG,OAAO;EaajB,4CAAc;IACZ,OAAO,EAAE,IAAI;IACb,eAAe,EAAE,aAAa;IAC9B,SAAS,EAAE,IAAI;IACf,OAAO,EAAE,qBAAqB;IAE9B,iDAAK;MACH,SAAS,EAAE,GAAG;MACd,WAAW,EAAE,MAAM;MACnB,KAAK,EAAE,qBAA2B;IAEpC,kDAAM;MACJ,SAAS,EAAE,CAAC;IAEd,wDAAY;MACV,UAAU,EAAE,KAAK;EAGrB,yCAAW;IACT,OAAO,EAAE,IAAI;IACb,+CAAM;MACJ,KAAK,EAAE,GAAG;MAEV,OAAO,EAAE,qBAAqB;MAC9B,UAAU,EbxCJ,OAAO;MEGlB,kBAAkB,EAAE,mCAAO;MAC3B,UAAU,EAAE,mCAAO;MWwCd,2DAAc;QACZ,YAAY,EAAE,KAAK;MAErB,4DAAe;QACb,WAAW,EAAE,KAAK;AAMxB,wCAAc;EACZ,MAAM,EAAE,IAAI;EACZ,UAAU,EbxDF,OAAO;EayDf,YAAY,EAAE,eAAe;EAC7B,YAAY,EAAE,KAAK;EACnB,YAAY,Eb1DJ,OAAO;Ea2Df,MAAM,EAAE,OAAO;EXhEnB,qBAAqB,EWiEM,GAAG;EXhE3B,kBAAkB,EWgEM,GAAG;EX/D1B,iBAAiB,EW+DM,GAAG;EX9DtB,aAAa,EW8DM,GAAG;EAC1B,YAAY,EAAE,KAAK;EACnB,OAAO,EAAE,IAAI;EACb,eAAe,EAAE,aAAa;EAC9B,WAAW,EAAE,MAAM;EAEnB,8CAAM;IACJ,KAAK,EbjEC,OAAO;IakEb,YAAY,EAAE,KAAK;IACnB,oCAAuC;MAHzC,8CAAM;QAIF,SAAS,EAAE,KAAK;IAElB,qDAAO;MACL,SAAS,EAAE,KAAK;EAGpB,mDAAW;IACT,OAAO,EAAE,GAAG;IXtElB,kBAAkB,EAAE,qBAAiC;IACjD,cAAc,EAAE,qBAAiC;IAC7C,UAAU,EAAE,qBAAiC;IWuE/C,yDAAQ;MACN,OAAO,EAAE,CAAC;MACV,MAAM,EAAE,OAAO;AAKrB,qCAAW;EACT,OAAO,EAAE,IAAI;EACb,eAAe,EAAE,YAAY;EAC7B,UAAU,Eb3FF,OAAO;Ea4Ff,MAAM,EAAE,SAAS;EACjB,OAAO,EAAE,SAAS;EAClB,qCAAsC;IANxC,qCAAW;MAOP,MAAM,EAAE,SAAS;AAIrB,oCAAU;EACR,UAAU,EbpGF,OAAO;EaqGf,KAAK,EAAE,IAAI;EACX,OAAO,EAAE,IAAI;EACb,eAAe,EAAE,aAAa;EAC9B,WAAW,EAAE,MAAM;EACnB,aAAa,EAAE,iBAAe;EAC9B,MAAM,EAAE,IAAI;EACZ,2CAAO;IACL,YAAY,EAAE,GAAG;IACjB,cAAc,EAAE,SAAS;IACzB,KAAK,Eb3GC,OAAO;Ia4Gb,WAAW,EAAE,IAAI;EAEnB,4CAAQ;IACN,aAAa,EAAE,KAAK;AAGxB,oCAAU;EACR,SAAS,EAAE,CAAC;EACZ,OAAO,EAAE,IAAI;EACb,cAAc,EAAE,MAAM;EACtB,KAAK,EAAE,GAAG;EACV,UAAU,EAAE,GAAG;EACf,WAAW,EAAE,GAAG;EAChB,UAAU,EAAE,OAAgB;EAC5B,KAAK,Eb1HG,OAAO;Ea2Hf,0CAAM;IAEJ,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,GAAG;EAGb,4CAAQ;IACN,OAAO,EAAE,IAAI;IACb,KAAK,EAAE,IAAI;IACX,UAAU,EAAE,IAAI;IAChB,kDAAM;MACJ,KAAK,EAAE,GAAG;EAGd,6CAAS;IACP,cAAc,EAAE,SAAS;IACzB,UAAU,EAAE,MAAM;IAClB,OAAO,EAAC,IAAI;IACZ,WAAW,EAAE,MAAM;IACnB,eAAe,EAAE,MAAM;IACvB,KAAK,EAAE,eAAe;IACtB,MAAM,EAAE,IAAI;AAGhB,uCAAa;EACX,SAAS,EAAE,CAAC;EACZ,OAAO,EAAE,IAAI;EACb,eAAe,EAAE,aAAa;EAC9B,KAAK,EAAE,GAAG;EAEV,WAAW,EAAE,GAAG;EAChB,6JAAS;IACP,KAAK,EAAE,GAAG;IACV,OAAO,EAAE,GAAG;IAEZ,iMAAY;MACV,UAAU,EAAE,IAAI;EAKlB,gEAAY;IACV,UAAU,EAAE,IAAI;IAChB,MAAM,EAAE,IAAI;EAGhB,qDAAc;IAEZ,MAAM,EAAE,IAAI;IACZ,wEAAmB;MAEjB,MAAM,EAAE,IAAI;MACZ,OAAO,EAAE,OAAO;MAChB,OAAO,EAAE,IAAI;MACb,eAAe,EAAE,YAAY;MAC7B,aAAa,EAAE,iBAAe;MAC9B,UAAU,EbvLN,OAAO;MawLX,KAAK,EAAE,IAAI;MACX,+EAAO;QACL,KAAK,EAAC,IAAI;QACV,WAAW,EAAE,IAAI;QACjB,aAAa,EAAE,IAAI;QACnB,KAAK,Eb1LH,OAAO;Ma4LX,8EAAM;QACJ,SAAS,EAAE,CAAC;IAIhB,iEAAY;MACV,MAAM,EAAE,IAAI;AAIlB,uCAAa;EAGX,UAAU,EAAE,KAAK;EACjB,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;EACZ,OAAO,EAAE,IAAI;EACb,eAAe,EAAE,aAAa;EAC9B,WAAW,EAAE,MAAM;EACnB,UAAU,EblNF,OAAO;EEGlB,kBAAkB,EAAE,mCAAO;EAC3B,UAAU,EAAE,mCAAO;EWgNhB,gDAAS;IACP,YAAY,EAAE,GAAG;IACjB,cAAc,EAAE,SAAS;IACzB,KAAK,EbpNC,OAAO;IaqNb,SAAS,EAAE,KAAK;EAElB,gDAAS;IACP,aAAa,EAAC,GAAG;;AChOzB,YAAa;EZQV,kBAAkB,EAAE,mCAAO;EAC3B,UAAU,EAAE,mCAAO;EYPpB,UAAU,EdGE,OAAO;EcFnB,MAAM,EAAE,iBAAiB;EACzB,OAAO,EAAE,IAAI;EACb,eAAe,EAAE,aAAa;;ACH9B,iBAAO;EACL,KAAK,EfKK,OAAO;AeFnB,aAAG;EACD,aAAa,EAAE,cAAc;AAG/B,aAAG;EACD,KAAK,EfHK,OAAO;EeIjB,UAAU,EAAE,GAAG;EACf,aAAa,EAAE,GAAG;EAClB,WAAW,EAAC,GAAG;AAGjB,iBAAO;EACL,KAAK,EAAE,KAAK;EACZ,OAAO,EAAC,YAAY;AAEtB,iBAAO;EACL,KAAK,EAAE,KAAK;EACZ,OAAO,EAAC,YAAY;EACpB,UAAU,EAAE,KAAK;;AAInB,iBAAE;EACA,KAAK,EfrBK,OAAO;AeuBnB,sBAAO;EACL,KAAK,EfxBK,OAAO;EeyBjB,KAAK,EAAE,KAAK;EACZ,OAAO,EAAC,YAAY;EACpB,cAAc,EAAE,GAAG;AAErB,sBAAO;EACL,KAAK,EAAE,KAAK;EACZ,OAAO,EAAC,YAAY;EACpB,UAAU,EAAE,KAAK;;AAInB,mBAAE;EACA,KAAK,EfrCK,OAAO;AeuCnB,oBAAG;EACD,WAAW,EAAE,MAAM;EACnB,cAAc,EAAE,OAAO;AAEzB,oBAAG;EACD,SAAS,EAAE,GAAG;EACd,UAAU,EAAE,GAAG;AAEjB,0BAAS;EACP,WAAW,EAAE,GAAG;AAGhB,0DAAoB;EAClB,KAAK,EAAE,IAAI;EACX,UAAU,EfxDF,OAAO;EeyDf,MAAM,EAAE,CAAC;EbtDZ,kBAAkB,EAAE,mCAAO;EAC3B,UAAU,EAAE,mCAAO;EauDhB,aAAa,EAAE,GAAG;EAClB,OAAO,EAAE,CAAC;EAEV,kIAAiB;IACf,aAAa,EAAE,KAAK;IACpB,aAAa,EAAE,KAAK;IACpB,aAAa,EAAE,uBAA0B;IACzC,MAAM,EAAE,OAAO;AAGnB,kDAAY;EACV,OAAO,EAAE,IAAI;EACb,OAAO,EAAE,SAAS;EAElB,wDAAM;IACJ,KAAK,EAAE,IAAI;IACX,OAAO,EAAC,QAAQ;EAElB,wDAAM;IACJ,WAAW,EAAE,KAAK;IAClB,0EAAkB;MAChB,UAAU,EAAE,IAAI;MAChB,SAAS,EAAE,KAAK;MAChB,OAAO,EAAE,KAAK;MACd,KAAK,EfhFD,OAAO;MeiFX,KAAK,EAAE,IAAI;IAEb,0EAAkB;MAChB,SAAS,EAAE,KAAK;MAChB,KAAK,EAAE,OAAmB;MAC1B,KAAK,EAAE,IAAI;MACX,OAAO,EAAE,KAAK;;AC/FxB,QAAS;EACP,QAAQ,EAAE,QAAQ;EAClB,OAAO,EAAE,YAAY;EACrB,KAAK,EAAE,IAAI;EAEX,cAAM;IACJ,UAAU,EAAE,MAAM;IAClB,KAAK,EAAE,KAAK;IACZ,WAAW,EAAE,GAAG;IAChB,UAAU,EAAE,qBAAwC;IACpD,KAAK,EAAE,IAAI;IACX,UAAU,EAAE,MAAM;IdVpB,qBAAqB,EcWI,GAAG;IdVzB,kBAAkB,EcUI,GAAG;IdTxB,iBAAiB,EcSI,GAAG;IdRpB,aAAa,EcQI,GAAG;IAC1B,OAAO,EAAE,KAAK;IACd,MAAM,EAAE,GAAG;IACX,SAAS,EAAE,KAAK;IAChB,OAAO,EAAE,CAAC;IdHZ,kBAAkB,EAAE,qBAAiC;IACjD,cAAc,EAAE,qBAAiC;IAC7C,UAAU,EAAE,qBAAiC;IcInD,0BAA0B;IAC1B,QAAQ,EAAE,QAAQ;IAClB,OAAO,EAAE,GAAG;IAEZ,qBAAS;MACP,OAAO,EAAE,GAAG;MACZ,QAAQ,EAAE,QAAQ;MAClB,MAAM,EAAE,IAAI;MAAG,+BAA+B;MAC9C,IAAI,EAAE,GAAG;MACT,WAAW,EAAE,IAAI;MACjB,YAAY,EAAE,GAAG;MACjB,YAAY,EAAE,KAAK;MACnB,YAAY,EAAE,yDAA4E;EAK5F,wBAAM;IACJ,KAAK,EAAE,GAAG;IACV,+BAAS;MACP,UAAU,EAAE,OAAO;MACnB,OAAO,EAAE,CAAC;IAEZ,+BAAS;MACP,IAAI,EAAE,GAAG;EAKf,kBAAY;IACV,KAAK,EAAE,OAAO;IACd,wBAAM;MACJ,IAAI,EAAE,GAAG;MACT,WAAW,EAAE,CAAC;MACd,KAAK,EAAE,KAAK;MAEZ,+BAAS;QACP,MAAM,EAAE,IAAI;QAAG,+BAA+B;QAC9C,IAAI,EAAE,GAAG;QACT,WAAW,EAAE,IAAI;MAEnB,+BAAS;QACP,UAAU,EAAE,OAAO;QACnB,OAAO,EAAE,CAAC;;AC9DlB,QAAS;EACP,MAAM,EAAE,iBAAiB;EACzB,KAAK,EAAE,IAAI;EACX,SAAS,EAAE,GAAG;EACd,cAAQ;IACN,aAAa,EjBDH,OAAO;;AkBJrB,EAAG;EACD,SAAS,EAAE,KAAK;EAChB,WAAW,EAAE,IAAI;EACjB,KAAK,ElBKO,OAAO;EkBJnB,oCAAuC;IAJzC,EAAG;MAKC,SAAS,EAAE,KAAK;;AAGpB,EAAG;EACD,KAAK,ElBFO,OAAO;EkBGnB,WAAW,EAAE,KAAK;EAClB,SAAS,EAAE,MAAM;EACjB,oCAAuC;IAJzC,EAAG;MAKC,SAAS,EAAE,GAAG;;AAIhB,oCAAuC;EADzC,EAAG;IAEC,SAAS,EAAE,GAAG;;ACGlB,UAAW;EACT,UAAU,EAAE,OAAO",
+"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/_terms.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": [],
+"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..36afdef7
--- /dev/null
+++ b/viscoll-app/src/styles/light.js
@@ -0,0 +1,42 @@
+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: {
+ stagingColor: '#B81616',
+ 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
diff --git a/viscoll-xproc/.dockerignore b/viscoll-xproc/.dockerignore
new file mode 100644
index 00000000..b956cf2d
--- /dev/null
+++ b/viscoll-xproc/.dockerignore
@@ -0,0 +1,4 @@
+*
+!deps.edn
+!src/
+!xpl/
diff --git a/viscoll-xproc/.gitignore b/viscoll-xproc/.gitignore
new file mode 100644
index 00000000..8f889796
--- /dev/null
+++ b/viscoll-xproc/.gitignore
@@ -0,0 +1,4 @@
+.cpcache/
+.nrepl-port
+
+htdocs/
diff --git a/viscoll-xproc/Dockerfile b/viscoll-xproc/Dockerfile
new file mode 100644
index 00000000..cf761b95
--- /dev/null
+++ b/viscoll-xproc/Dockerfile
@@ -0,0 +1,11 @@
+FROM clojure:tools-deps
+
+COPY deps.edn /app/deps.edn
+WORKDIR /app
+
+# Pull dependencies
+RUN clojure -Stree
+
+COPY . /app
+
+ENTRYPOINT ["/usr/local/bin/clojure", "-M", "-m", "vceditor.xproc"]
diff --git a/viscoll-xproc/deps.edn b/viscoll-xproc/deps.edn
new file mode 100644
index 00000000..c3f614d5
--- /dev/null
+++ b/viscoll-xproc/deps.edn
@@ -0,0 +1,10 @@
+{:deps {babashka/fs {:mvn/version "0.5.26"}
+ com.taoensso/telemere {:mvn/version "1.2.1"}
+ com.taoensso/telemere-slf4j {:mvn/version "1.2.1"}
+ com.xmlcalabash/xmlcalabash {:mvn/version "3.0.47"
+ :exclusions [ch.qos.logback/logback-classic]}
+ jarohen/chime {:mvn/version "0.3.3"}
+ metosin/reitit {:mvn/version "0.10.1"}
+ ring/ring-defaults {:mvn/version "0.7.0"}
+ ring/ring-jetty-adapter {:mvn/version "1.15.4"}
+ tick/tick {:mvn/version "1.0"}}}
diff --git a/viscoll-xproc/src/vceditor/xproc.clj b/viscoll-xproc/src/vceditor/xproc.clj
new file mode 100644
index 00000000..f09578e2
--- /dev/null
+++ b/viscoll-xproc/src/vceditor/xproc.clj
@@ -0,0 +1,400 @@
+(ns vceditor.xproc
+ (:require
+ [babashka.fs :as fs]
+ [chime.core :as chime]
+ [clojure.java.io :as io]
+ [clojure.stacktrace :refer [print-cause-trace]]
+ [clojure.string :as str]
+ [muuntaja.core :as m]
+ [reitit.coercion.malli :as rcm]
+ [reitit.core :as r]
+ [reitit.ring :as ring]
+ [reitit.ring.coercion :as coercion]
+ [reitit.ring.middleware.exception :as exception]
+ [reitit.ring.middleware.muuntaja :as muuntaja]
+ [ring.adapter.jetty :as jetty]
+ [ring.middleware.defaults :as defaults]
+ [ring.util.io :as ring-io]
+ [ring.util.request :as req]
+ [ring.util.response :as resp]
+ [taoensso.telemere :as tel]
+ [tick.core :as t])
+ (:import
+ (com.xmlcalabash XmlCalabash XmlCalabashBuilder)
+ (java.io File)
+ (java.lang AutoCloseable)
+ (java.net URL)
+ (java.nio.charset Charset)
+ (java.time Duration)
+ (java.util UUID)
+ (java.util.zip ZipEntry ZipOutputStream)
+ (net.sf.saxon.s9api QName XdmAtomicValue)
+ (org.eclipse.jetty.server Server)))
+
+(defn getenv
+ [k dv]
+ (or (some-> (System/getenv (str "VCEDITOR_XPROC_" k)) (str/trim) (not-empty))
+ dv))
+
+(def http-port
+ (parse-long (getenv "HTTP_PORT" "2000")))
+
+(def http-context-path
+ (getenv "HTTP_CONTEXT_PATH" "/xproc"))
+
+(def http-doc-root
+ (doto (io/file (getenv "HTTP_DOC_ROOT" "htdocs")) (.mkdirs)))
+
+(def http-job-max-age
+ (Duration/parse (getenv "HTTP_JOB_MAX_AGE" "PT168H")))
+
+(def xpl-dir
+ (io/file (getenv "XPL_DIR" "xpl")))
+
+(defn wrap-job-coordinates
+ [handler]
+ (fn
+ [{{:keys [pipeline job path]} :path-params :as request} respond raise]
+ (let [coords {::pipeline pipeline ::job job ::path path}
+ request (merge request coords)]
+ (tel/with-ctx+ {::job coords}
+ (handler
+ request
+ (fn [response]
+ (let [{:keys [::pipeline ::job]} (merge request response)]
+ (respond
+ (cond-> response
+ pipeline (resp/header "X-Idrovora-Pipeline" pipeline)
+ job (resp/header "X-Idrovora-Job" job)))))
+ raise)))))
+
+(defn wrap-resource
+ [handler]
+ (fn
+ [{::keys [pipeline job path] :as request} respond raise]
+ (let [f (apply io/file (remove nil? [http-doc-root pipeline job path]))]
+ (if (fs/starts-with? f http-doc-root)
+ (tel/with-ctx+ {::resource f}
+ (handler (assoc request ::file f) respond raise))
+ (respond (resp/not-found {}))))))
+
+(defn absolute-url
+ [request url]
+ (str (URL. (URL. (req/request-url request)) url)))
+
+(defn self-url
+ [request]
+ (req/request-url request))
+
+(defn index-url
+ [{:keys [::r/router] :as request}]
+ (->>
+ (r/match-by-name router ::index-request)
+ (r/match->path)
+ (absolute-url request)))
+
+(defn pipeline-url
+ [{:keys [::r/router] :as request} pipeline]
+ (->>
+ {:pipeline pipeline}
+ (r/match-by-name router ::job-request)
+ (r/match->path)
+ (absolute-url request)))
+
+(defn job-url
+ [{::r/keys [router] :as request} pipeline job]
+ (->>
+ {:pipeline pipeline :job job :path ""}
+ (r/match-by-name router ::resource-request)
+ (r/match->path)
+ (absolute-url request)))
+
+(defn link
+ ([href] (link href :self))
+ ([href rel] {rel {:href href}}))
+
+(defn wrap-links
+ [handler]
+ (fn [{::keys [pipeline job] :as request} respond raise]
+ (handler
+ request
+ (fn [{:keys [body status] p ::pipeline j ::job
+ :or {p pipeline j job} :as resp}]
+ (respond
+ (if-not (and (map? body) (< status 400))
+ resp
+ (->>
+ (merge (link (self-url request))
+ (link (index-url request) :index)
+ (when p (link (pipeline-url request p) :pipeline))
+ (when j (link (job-url request p j) :job)))
+ (assoc-in resp [:body :_links])))))
+ raise)))
+
+(defn pipeline-names
+ []
+ (into []
+ (comp (filter fs/regular-file?)
+ (filter #(= "xpl" (fs/extension %)))
+ (map (comp fs/strip-ext fs/file-name)))
+ (fs/list-dir xpl-dir)))
+
+(defn handle-index-request
+ [request respond _]
+ (->>
+ (for [p (pipeline-names)] {:id p :_links (link (pipeline-url request p))})
+ (assoc-in {} [:_embedded :pipelines])
+ (resp/response)
+ (respond)))
+
+(defn xpl-file
+ [pipeline]
+ (let [f (fs/file xpl-dir (str pipeline ".xpl"))]
+ (when (fs/regular-file? f) f)))
+
+(defn wrap-xpl
+ [handler]
+ (fn [{:keys [::pipeline] :as request} respond raise]
+ (if-let [xpl (xpl-file pipeline)]
+ (tel/with-ctx+ {::xpl xpl}
+ (handler (assoc request ::xpl xpl) respond raise))
+ (respond (resp/not-found {:pipeline pipeline})))))
+
+(defn resource
+ [f]
+ {:id (fs/file-name f)
+ :modified (str (. (fs/last-modified-time f) (toMillis)))})
+
+(defn job-dirs
+ [pipeline]
+ (filter fs/directory? (fs/list-dir http-doc-root pipeline)))
+
+(defn handle-pipeline-request
+ [{:keys [::pipeline] :as request} respond _]
+ (let [jobs (->> (map resource (job-dirs pipeline))
+ (sort-by :modified #(compare %2 %1)))]
+ (->>
+ (for [{:keys [id] :as job} (take 100 jobs)]
+ (assoc job :_links (link (absolute-url request (str id "/")))))
+ (assoc-in {:id pipeline :total_jobs (count jobs)} [:_embedded :jobs])
+ (resp/response)
+ (respond))))
+
+(defn file-param?
+ [[_ {:keys [tempfile]}]]
+ tempfile)
+
+(defn zip-file-param?
+ [[_ {:keys [content-type filename] :or {filename ""}} :as param]]
+ (and (file-param? param)
+ (or (= "application/zip" content-type)
+ (-> filename str/lower-case (str/ends-with? ".zip")))))
+
+(defn string-param?
+ [[_ v]]
+ (string? v))
+
+(defn param-key->filename
+ [k]
+ (let [filename (name k)
+ has-ext? (str/last-index-of filename ".")]
+ (if has-ext? filename (str filename ".xml"))))
+
+(defn unzip-param
+ [source-dir [_k {:keys [tempfile]}]]
+ (fs/unzip tempfile source-dir))
+
+(defn copy-param
+ [source-dir [k {:keys [tempfile]}]]
+ (io/copy tempfile (fs/file source-dir (param-key->filename k))))
+
+(defn spit-param
+ [source-dir [k v]]
+ (spit (fs/file source-dir (param-key->filename k)) v :encoding "UTF-8"))
+
+(defn wrap-job
+ [handler]
+ (fn
+ [{:keys [::pipeline :params] :as request} respond raise]
+ (tel/with-ctx+ {::pipeline pipeline}
+ (try
+ (let [job (str (UUID/randomUUID))
+ job-dir (doto (fs/file http-doc-root pipeline job) (.mkdirs))
+ file-params (filter file-param? params)
+ zip-file-params (filter zip-file-param? file-params)
+ file-params (remove zip-file-param? file-params)
+ string-params (filter string-param? params)]
+ (doseq [p zip-file-params] (unzip-param job-dir p))
+ (doseq [p file-params] (copy-param job-dir p))
+ (doseq [p string-params] (spit-param job-dir p))
+ (handler
+ (assoc request ::job job ::job-dir job-dir)
+ #(respond (assoc % ::job job))
+ raise))
+ (catch Throwable t
+ (tel/error! ::job-request t)
+ (respond (resp/bad-request {:pipeline pipeline})))))))
+
+(def ^XmlCalabash xml-calabash
+ (.. (XmlCalabashBuilder.) (build)))
+
+(defn handle-job-request
+ [{::keys [job job-dir pipeline xpl] :as request} respond _raise]
+ (tel/with-ctx+ {::job request}
+ (tel/event! ::resource :trace)
+ (future
+ (try
+ (let [pipeline (.. xml-calabash
+ (newXProcParser)
+ (parse (. ^File xpl (toURI)))
+ (getExecutable))
+ job-dir-uri (.. (fs/file job-dir) (toURI) (toString))]
+ (. pipeline (option (QName. "job-dir") (XdmAtomicValue. job-dir-uri)))
+ (. pipeline (run)))
+ (->
+ (resp/created (job-url request pipeline job) (resource job-dir))
+ (respond))
+ (catch Throwable t
+ (tel/error! ::job-error t)
+ (->
+ (resp/response {:error (with-out-str (print-cause-trace t))})
+ (resp/status 502)
+ (respond)))))))
+
+(defn handle-resource-request
+ [{^File f ::file :as request} respond _raise]
+ (tel/event! ::resource :trace)
+ (respond
+ (cond
+ ;; files are delivered as-is
+ (fs/regular-file? f)
+ (resp/response f)
+ ;; a directory's representation can be negotiated
+ (fs/directory? f)
+ (cond
+ ;; directories can be delivered as ZIP archives, if requested
+ (some-> (m/get-response-format-and-charset request)
+ :raw-format #{"application/zip"})
+ (-> (fn [os]
+ (with-open [zip (ZipOutputStream. os (Charset/forName "UTF-8"))]
+ (doseq [fc (fs/list-dir f)]
+ (with-open [fis (io/input-stream (fs/file fc))]
+ (. zip (putNextEntry (ZipEntry. (fs/file-name fc))))
+ (io/copy fis zip)
+ (. zip (closeEntry))))))
+ (ring-io/piped-input-stream)
+ (resp/response)
+ (resp/content-type "application/zip"))
+ ;; otherwise return directory listing
+ :else
+ (->>
+ (for [c (fs/list-dir f)]
+ (let [n (str (fs/file-name c) (if (fs/directory? c) "/" ""))]
+ (assoc (resource c) :_links (link (absolute-url request n)))))
+ (assoc-in {} [:_embedded :resources])
+ (resp/response)))
+ ;; fallback
+ :else
+ (resp/not-found {}))))
+
+(defn handle-job-removal
+ [{^File f ::file ::keys [pipeline job path] :as request} respond _]
+ (tel/event! ::job-removal :trace)
+ (respond
+ (if (and pipeline job (empty? path) (fs/directory? f))
+ (do (fs/delete-tree f) (resp/redirect (pipeline-url request pipeline)))
+ (resp/not-found {}))))
+
+(def handlers
+ [""
+ {:middleware [wrap-job-coordinates wrap-resource wrap-links]}
+ ["/"
+ {:name ::index-request
+ :handler handle-index-request}]
+ ["/:pipeline/"
+ {:name ::job-request
+ :handler handle-pipeline-request
+ :middleware [wrap-xpl]
+ :parameters {:path [:map [:pipeline :string]]}
+ :post {:handler handle-job-request
+ :middleware [wrap-job]}}]
+ ["/:pipeline/:job/*path"
+ {:name ::resource-request
+ :handler handle-resource-request
+ :parameters {:path [:map [:pipeline :string] [:job :string] [:path :string]]}
+ :delete {:handler handle-job-removal}}]])
+
+(defn log-exceptions
+ [handler ^Throwable e request]
+ (when-not (some-> e ex-data :type #{::ring/response}) (tel/error! ::ring e))
+ (handler e request))
+
+(def exception-middleware
+ (exception/create-exception-middleware
+ (assoc exception/default-handlers ::exception/wrap log-exceptions)))
+
+(def handler-options
+ {:coercion rcm/coercion
+ :muuntaja m/instance
+ :middleware [{:name ::defaults
+ :wrap #(defaults/wrap-defaults
+ % (-> defaults/secure-site-defaults
+ (assoc-in [:proxy] true)
+ (assoc-in [:session] false)
+ (assoc-in [:cookies] false)
+ (assoc-in [:security :ssl-redirect] false)
+ (assoc-in [:security :anti-forgery] false)))}
+ muuntaja/format-negotiate-middleware
+ muuntaja/format-request-middleware
+ muuntaja/format-response-middleware
+ exception-middleware
+ coercion/coerce-exceptions-middleware
+ coercion/coerce-request-middleware
+ coercion/coerce-response-middleware]})
+
+(defn cleanup-jobs!
+ [& _]
+ (doseq [pipeline (filter fs/directory? (fs/list-dir http-doc-root))
+ job (filter fs/directory? (fs/list-dir pipeline))
+ :let [age (- (System/currentTimeMillis)
+ (. (fs/last-modified-time job) (toMillis)))]
+ :when (pos? (compare (Duration/ofMillis age) http-job-max-age))]
+ (tel/with-ctx+ {::job job}
+ (tel/event! ::cleanup)
+ (fs/delete-tree job))))
+
+(defn schedule-job-cleanup
+ []
+ (-> (t/truncate (t/offset-date-time) :days)
+ (t/instant)
+ (chime/periodic-seq (t/of-days 1))
+ (chime/without-past-times)
+ (chime/chime-at cleanup-jobs!
+ {:error-handler #(tel/error! ::cleanup-error %)})))
+
+(defn stop-server!
+ [^AutoCloseable schedule ^Server server]
+ (.close schedule)
+ (.stop server))
+
+(defn start-server!
+ [& _]
+ (partial
+ stop-server!
+ (schedule-job-cleanup)
+ (jetty/run-jetty
+ (ring/ring-handler
+ (ring/router [http-context-path handler-options handlers])
+ (ring/routes
+ (ring/redirect-trailing-slash-handler)
+ (ring/create-default-handler)))
+ {:port http-port
+ :join? false
+ :async? true})))
+
+(defn -main
+ [& _]
+ (tel/uncaught->error!)
+ (let [stop-server! (start-server!)]
+ (. (Runtime/getRuntime) (addShutdownHook (Thread. ^Runnable stop-server!)))
+ @(promise)))
diff --git a/viscoll-xproc/xpl/copy.xpl b/viscoll-xproc/xpl/copy.xpl
new file mode 100644
index 00000000..d9426f36
--- /dev/null
+++ b/viscoll-xproc/xpl/copy.xpl
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/viscoll-xproc/xpl/css/collation.css b/viscoll-xproc/xpl/css/collation.css
new file mode 100644
index 00000000..77f2f0f6
--- /dev/null
+++ b/viscoll-xproc/xpl/css/collation.css
@@ -0,0 +1,86 @@
+/* style.css */
+
+
+@font-face{
+ font-family: 'Archivo Narrow', sans-serif;
+ src: url('https://fonts.googleapis.com/css?family=Archivo+Narrow&display=swap');
+}
+
+@font-face{
+ font-family: 'Archivo', sans-serif;
+ src: url('https://fonts.googleapis.com/css2?family=Archivo&display=swap');
+}
+
+.gatheringNumber {
+ font-family: 'Archivo', sans-serif;
+ font-size: 6px;
+ alignment-baseline:hanging;
+}
+
+.gatheringNumber_L-R {
+ text-anchor: end;
+}
+
+.folioNumber {
+ font-family: 'Archivo Narrow', sans-serif;
+ font-size: 5px;
+ alignment-baseline:middle;
+}
+
+.folioNumber_R-L {
+ text-anchor: end;
+}
+
+.base {
+stroke-width:1pt;
+fill:none;
+stroke-linecap:round;
+}
+
+.leaf {
+ stroke:#000000;
+}
+
+.current{
+stroke:red;
+}
+
+.goatskin {
+stroke:#CC6D68;
+}
+
+.sheepskin {
+stroke:#DBBD68;
+}
+
+.calfskin {
+stroke:#4F81BD;
+}
+
+.missingLeaf{
+ stroke-width:0.8pt;
+ stroke:#000000;
+ stroke-dasharray:1,2;
+}
+
+.replacedLeaf{
+ stroke:#DCDCDC;
+}
+
+.addedLeaf{
+ stroke:#000000;
+ stroke-dasharray:0.25, 3, 4, 3;
+}
+
+.fleshside {
+}
+
+.hairside{
+ stroke-dasharray:0.5,2;
+}
+
+.glued{
+ stroke:grey;
+ stroke-width:0.5;
+ stroke-linecap:round;
+}
diff --git a/viscoll-xproc/xpl/css/collation2.css b/viscoll-xproc/xpl/css/collation2.css
new file mode 100644
index 00000000..db51dad2
--- /dev/null
+++ b/viscoll-xproc/xpl/css/collation2.css
@@ -0,0 +1,60 @@
+/* style.css */
+
+
+@font-face{
+ font-family: 'Archivo Narrow', sans-serif;
+ src: url('https://fonts.googleapis.com/css?family=Archivo+Narrow&display=swap');
+}
+
+.folioNumber {
+ font-family: 'Archivo Narrow', sans-serif;
+ font-size: 0.5em;
+ alignment-baseline:middle;
+}
+
+.leaf{
+ stroke:red;
+ stroke-width:1pt;
+ fill:none;
+ stroke-linecap:round;
+}
+
+.current{
+ stroke:red;
+}
+
+.missingLeaf{
+ stroke:none;
+ stroke-width:1pt;
+ fill:none;
+ stroke-linecap:round;
+}
+
+.replacedLeaf{
+ stroke:yellow;
+ stroke-dasharray:0.25, 3, 4, 3;
+ stroke-width:1pt;
+ fill:none;
+ stroke-linecap:round;
+}
+
+.addedLeaf{
+ stroke:green;
+ stroke-dasharray:0.8, 2;
+ stroke-width:0.8pt;
+ fill:none;
+ stroke-linecap:square;
+}
+
+.addedLeaf2{
+ stroke:orange;
+ stroke-dasharray:0.8, 2;
+ stroke-width:1.8pt;
+ fill:none;
+}
+
+.glued{
+ stroke:teal;
+ stroke-width:0.5;
+ stroke-linecap:round;
+}
\ No newline at end of file
diff --git a/viscoll-xproc/xpl/rng/viscoll-2.0.rng b/viscoll-xproc/xpl/rng/viscoll-2.0.rng
new file mode 100755
index 00000000..fc1fd1a6
--- /dev/null
+++ b/viscoll-xproc/xpl/rng/viscoll-2.0.rng
@@ -0,0 +1,643 @@
+
+
+
+
+
+
+
+ 2.1.0
+
+
+
+
+
+ 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 fol.*, agenda 4to*, 4to*, 8vo*, 12mo, long 12mo, 16mo*,
+ 18mo, 24mo, long 24mo, 32mo*, 48mo, 64mo*, 72mo, 96mo, 128mo
+ based on the DCRM(B) standard, the national standard for
+ cataloging rare books in library collections, generally adopted
+ by English-speaking countries and some non-English-speaking
+ countries, with the addition of "agenda quarto" (Needham, Paul.
+ 2017. “Format and Paper Size in Fifteenth-Century Printing.” In
+ Materielle Aspekte in Der Inkunabelforschung , edited
+ by Christoph Reske and Wolfgang Schmitz, 59–108. Wolfenbüttel
+ Writings on the History of Bookkeeping 49. Wiesbaden:
+ Harrassowitz Verlag in Kommission.). * indicates formats
+ considered by the Needham Calculator
+ (http://www.needhamcalculator.net/), see paper size below. In
+ case of mixed formats, e.g., F° and 4°, or other options being
+ used in one textblock, the information should be mapped directly
+ to the sheets instead.
+
+
+
+ fol.
+ agenda 4to
+ 4to
+ 8vo
+ 12mo
+ long 12mo
+ 16mo
+ 18mo
+ 24mo
+ long 24mo
+ 32mo
+ 48mo
+ 64mo
+ 72mo
+ 96mo
+ 128mo
+ mixed format
+ other
+
+
+
+
+
+
+
+
+
+
+
+ Refers to the category list in the "Table of Fifteenth-Century
+ Paper Flavors" for the Needham Calculator
+ (http://www.needhamcalculator.net/). For more information see
+ also: Needham, Paul. 2017. “Format and Paper Size in
+ Fifteenth-Century Printing.” In Materielle Aspekte in Der
+ Inkunabelforschung , edited by Christoph Reske and
+ Wolfgang Schmitz, 59–108. Wolfenbüttel Writings on the History
+ of Bookkeeping 49. Wiesbaden: Harrassowitz Verlag in Kommission.
+ In case of mixed sizes or other options being used in one
+ textblock, the information should be mapped directly to the
+ sheets instead.
+
+ Papal: 56 x 38.5 cm (half sheet)
+ Imperial: 48 x 34 cm
+ Super-Royal (Reale Bolognese): 45 x 30 cm
+ Royal (Reale, Regal): 42 x 30 cm
+ Super-Median (Mezzana Grande?): 37 x 25 cm
+ Median (Mezzana): 34.5/35 x 25 cm
+ Super-Chancery: 33 x 23 cm
+ Chancery (Reçute, Comune, Kanzleiformat): 31/31.5 x 23
+ cm
+ Half-Median: 25 x 17.5 cm
+
+
+
+
+ papal
+ imperial
+ super-royal
+ royal
+ super-median
+ median
+ super-chancery
+ chancery
+ half-median
+ mixed_sizes
+ other
+
+
+
+
+
+
+
+
+
+ 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
+ node 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). Stitching
+ goes through the margin instead of through the fold.
+ Stitching will have one @target identifying the exit
+ point. Tacketing works in a similar manner to sewing
+ through the fold.
+
+
+
+
+
+
+ sewn
+ pasted
+ tipped
+ drummed
+ stitched
+ tacketed
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ notes
+
+
+
+
+
+
+
+ mapping
+
+
+ map
+
+
+ The @side attribute permits the mapping of features on one
+ of the two surfaces of a sheet. The possible values are
+ 'left' and 'right'.
+
+ The place on the left of the centre of a book opened as
+ if to be read. All the components or features of a
+ binding on this side of the book can therefore be
+ described as left (e.g. left board, left endleaves,
+ etc.). This removes any confusion about what is the
+ front board on books written in arabic or latin, for
+ instance
+ (http://w3id.org/lob/concept/2947).
+ left
+ The place on the right of the centre of a book opened
+ as if to be read. All the components or features of a
+ binding on this side of the book can therefore be
+ described as right (e.g. right board, right endleaves,
+ etc.). This removes any confusion about what is the back
+ board on books written in arabic or latin, for instance
+ (http://w3id.org/lob/concept/3004).
+ right
+
+
+
+
+
+
+ The location attribute permits the mapping of features on
+ the surface of a page. The model provides a list of
+ possible values, but the user can add values too. If
+ more than one location is to be selected, the values
+ should be separated by a space, for this reason, spaces
+ are not allowed in the location values, use camelCasing,
+ _underscore, or similar instead.
+ Suggested values, as defined in the Language of Bindings
+ or the Getty's AAT, are:
+ head: the top of a bound book as placed
+ vertically on a shelf. http://w3id.org/lob/concept/3803
+ tail: the bottom of a bound book as placed
+ vertically on a shelf. http://w3id.org/lob/concept/3805
+ fore-edge: the edge of a codex-form book from
+ which it is opened, opposite the spine. http://w3id.org/lob/concept/3808
+ inner: the edge of a codex-form book closer to
+ the inside of the binding. http://w3id.org/lob/concept/3812
+ margins: the areas of a page between the
+ printed, written, or illustrative matter (text
+ area) and the edges of the leaf. The four margins
+ are usually called the 'head' (or top);
+ 'fore-edge' (or outer, outside, side); 'tail' (or
+ bottom foot); and 'inner' (or gutter). http://w3id.org/lob/concept/4579
+ gutters: the adjoining inner margins of two
+ facing pages, i.e. the margin at the sewn fold of
+ a section. http://w3id.org/lob/concept/4577
+ corner: the meeting-place of converging sides or
+ edges of a book, forming an angular extremity or
+ projection. On books, they can be defined
+ differently according to where they are found. The
+ sides of a binding therefore will have upper and
+ lower corners on either the inner (spine) or outer
+ (fore-edge) edges whereas the spines or fore-edges
+ of a book will have upper and lower corners at
+ either the left or right sides. http://w3id.org/lob/concept/4543
+ text_area: The area of a page containing the
+ printed, written, or illustrative matter, within
+ the four margins. http://w3id.org/lob/concept/4583
+ upper: situated or passing above the level,
+ surface, or base of measurement of something else.
+ http://vocab.getty.edu/page/aat/300010291
+ lower: situated or passing below the level,
+ surface, or base of measurement of something else.
+ http://vocab.getty.edu/page/aat/300010283
+ central: positioned at or near the middle of
+ something. http://vocab.getty.edu/page/aat/300010273
+ projecting: jutting or sticking out beyond the
+ general surface or adjacent parts. http://vocab.getty.edu/page/aat/300010286
+ parallel: extending in the same direction and
+ everywhere equidistant; usually said of lines and
+ linear objects and structures. http://vocab.getty.edu/page/aat/300010284
+ vertical: parallel relationships to a vertical
+ axis in designs for graphic works, objects, and
+ structures. http://vocab.getty.edu/page/aat/300056325
+ horizontal: parallel relationships to a
+ horizontal axis in designs for graphic works,
+ objects, and structures. http://vocab.getty.edu/page/aat/300065463
+ oblique: in form or position, a deviation from a
+ parallel state, or perpendicularity. http://vocab.getty.edu/page/aat/300265773
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ term
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ values for @certainty: 1 = very certain, 2 = fairly certain, 3 = not certain
+
+ 1
+ 2
+ 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ For
+ printed books, the alphanumerical code printed (or derived) for the
+ gathering.
+
+
+
+
+
+ id
+
+
+
+
+
+
+
+
+
+
+
+
+
+ certainty
+
+
+
+
+ id
+
+
+
+
+
+
diff --git a/viscoll-xproc/xpl/viscoll2formulas.xpl b/viscoll-xproc/xpl/viscoll2formulas.xpl
new file mode 100644
index 00000000..80410433
--- /dev/null
+++ b/viscoll-xproc/xpl/viscoll2formulas.xpl
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/viscoll-xproc/xpl/viscoll2html.xpl b/viscoll-xproc/xpl/viscoll2html.xpl
new file mode 100644
index 00000000..881f0ed4
--- /dev/null
+++ b/viscoll-xproc/xpl/viscoll2html.xpl
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/viscoll-xproc/xpl/viscoll2svg.xpl b/viscoll-xproc/xpl/viscoll2svg.xpl
new file mode 100644
index 00000000..18319b8e
--- /dev/null
+++ b/viscoll-xproc/xpl/viscoll2svg.xpl
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/viscoll-xproc/xpl/xsl/processed2html.xsl b/viscoll-xproc/xpl/xsl/processed2html.xsl
new file mode 100644
index 00000000..c4f0d582
--- /dev/null
+++ b/viscoll-xproc/xpl/xsl/processed2html.xsl
@@ -0,0 +1,1490 @@
+
+
+
+
+
+
+
+
+
+
+ Created on: July 2, 2014
+ Author: Dot Porter
+ Modified on: April 29, 2016
+ Modified by: Dot Porter
+ Modified on: 2019-06-06
+ Modified by: Alberto Campagnolo
+ Modified on: 2022-01-26
+ Modified by: Alberto Campagnolo
+ This document takes as its input the output from
+ viscoll_mod2_XML_to_processed-XML.xsl
+ It generates:
+ several HTML files that make up a collation site for a manuscript
+ a single HTML document (with -diagrams appended) containing diagrams for
+ each quire
+ a single HTML document containing digrams and bifolia view for the entire
+ manuscript
+ a single HTML containing collation formulas
+
+ Congratulations! You are done!
+
+
+
+
+
+
+
+ X
+
+
+
+
+ STUB
+
+
+
+
+ https://cdn.rawgit.com/leoba/VisColl/master/data/support/images/x.jpg
+
+
+
+
+
+ STUB
+
+
+
+
+ Root template
+
+
+
+
+
+
+
+
+ Template to create the HTML files.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Collation of
+
+ : gathering
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Template to generate HTML page with collation formulas.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Gathering structure of
+
+ .
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Collation Formula for
+
+
+
+
+
+ Formula 1:
+
+
+
+ Formula 2:
+
+
+
+
+
+
+
+
+ Single page HTML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ begin set
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ One webpage per leaf according to the visualization structure
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Quire
+
+ , Unit
+
+ .
+
+ ,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ begin set
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Gathering structure of
+
+ .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Select a unit to display/hide, or
+
+ Show All Units
+
+
+ Hide All Units
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
,
+
+
+ Select a Gathering
+
+
+ Gathering
+
+
+ :
+
+
+
+
+ ( )
+
+
+
+ Gathering
+
+
+ :
+
+
+
+ ( )
+
+
+ begin set
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ If you would like to include the ability to show and hide gatherings, uncomment the code below
+
+
+
Gathering
+
+
, Unit
+
+
,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ svg
+
+
+
+
+
+
+ image/svg+xml
+
+ Error: SVG file not found
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Top division
+
+
+
+
+
+
+
+
+
+
+
+ Record date and time of transformation
+
+
+
+
+
+ HTML file generated on:
+
+ using
+
+ version
+
+
+
+
+
+
+
+ HTML information
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Template containing the javascript code to be inserted to highlight the current
+ leaf in the svg diagrams.
+
+
+
+
+
+
+
+
diff --git a/viscoll-xproc/xpl/xsl/viscoll2formulas.xsl b/viscoll-xproc/xpl/xsl/viscoll2formulas.xsl
new file mode 100644
index 00000000..a3d3c569
--- /dev/null
+++ b/viscoll-xproc/xpl/xsl/viscoll2formulas.xsl
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+ Created on: 2019-06-10
+ Author: Alberto Campagnolo
+ Based on templates created by: Dot Porter, created on:
+ 2014-07-02 Modified on: May 5, 2015
+ Modified by: Dot Porter
+
+ This document takes as its input the output from the Collation Modeler. It
+ generates two text files with two styles of simple collation formulas
+
+
+
+
+
+ Collation formulas are created in separate text files.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Collation formula generator.
+ Format: 1(8, -4, +3)
+
+
+
+
+
+
+
+
+ Formula 1
+
+
+ 1(8, -4, +3)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (
+
+
+ , -
+
+
+
+ , +
+
+
+
+ , leaf in position
+
+ has been replaced
+
+ )
+
+
+ ,
+
+
+
+
+
+
+
+
+
+
+ Collation formula generator.
+ Format: 1(8, leaf missing between fol. X and fol. Y, leaf added after fol.
+ X)
+
+
+
+
+
+
+
+
+ Formula 2
+
+
+ 1(8, leaf missing between f.X and f.Y, leaf added after f.X)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
+
+ leaves missing after fol.
+
+
+
+
+
+
+ , leaf missing after fol.
+
+
+
+
+
+ , first leaf is missing
+
+
+
+
+
+
+ , leaf added after fol.
+
+
+
+ , first leaf is added
+
+
+
+
+
+
+ , leaf replaced after fol.
+
+
+
+ , first leaf is replaced
+
+
+
+ )
+
+
+ ,
+
+
+
+
+
+
+
+
+
diff --git a/viscoll-xproc/xpl/xsl/viscoll2processed.xsl b/viscoll-xproc/xpl/xsl/viscoll2processed.xsl
new file mode 100644
index 00000000..65ba2789
--- /dev/null
+++ b/viscoll-xproc/xpl/xsl/viscoll2processed.xsl
@@ -0,0 +1,471 @@
+
+
+
+
+
+ Created on: 2019-06-5
+ Author: Alberto Campagnolo
+ Modified by: Alberto Campagnolo
+ Modified on: 2020-08-14
+ Based on templates created by: Dot Porter, created on:
+ 2014-07-02 Modified on: May 5, 2015
+ Modified by: Dot Porter
+ Modified on: May 21, 2015
+ Modified by: Conal
+
+ This document takes as its input the output from the Collation Modeler. It
+ generates the collation frame for the bifolio visualization.
+
+
+
+
+
+
+
+
+
+ X
+
+
+
+
+ STUB
+
+
+
+
+ https://cdn.rawgit.com/leoba/VisColl/master/data/support/images/x.jpg
+
+
+
+
+
+ STUB
+
+
+
+
+
+
+ Main template. Creates an XML file for each textblock. Leaves are grouped into
+ gatherings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This template divides leaves into (one for each bifolio)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This template sets the data for each leaf representation, linking to the image
+ listed in the imageList file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ https://raw.githubusercontent.com/leoba/VisColl/master/data/support/images/x.jpg
+
+
+
+
+
+ https://raw.githubusercontent.com/leoba/VisColl/master/data/support/images/x.jpg
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/viscoll-xproc/xpl/xsl/viscoll2svg.xsl b/viscoll-xproc/xpl/xsl/viscoll2svg.xsl
new file mode 100644
index 00000000..675ba64c
--- /dev/null
+++ b/viscoll-xproc/xpl/xsl/viscoll2svg.xsl
@@ -0,0 +1,3707 @@
+
+
+
+
+
+ Created on: 2018-01-06
+ Author: Alberto Campagnolo
+ Modified on: 2019-06-05
+ Modified by: Alberto Campagnolo
+ Modified on: 2020-08-14
+ Modified by: Alberto Campagnolo
+ Modified on: 2020-12-04
+ Modified by: Alberto Campagnolo
+ Modified on: 2021-01-09
+ Modified by: Alberto Campagnolo
+ Modified on: 2021-01-15
+ Modified by: Alberto Campagnolo
+ This document takes as its input the output from the Collation Modeler. It
+ generates one SVG diagram per gathering. A general parameter permits to insert the
+ CSS information directly into the SVG file.
+
+
+
+
+
+ This stylesheet outputs a series of SVG files, one for each detected
+ gathering
+
+
+
+
+
+
+
+
+
+
+
+ This parameter permits selecting a choice between writing all page numbers in the
+ SVG file (1) or only the first and last (0).
+ 0 = no; 1 = yes
+
+
+
+
+
+
+ This parameter permits selecting a choice between writing the gathering number in
+ the SVG file (1) or not (0)
+ 0 = no; 1 = yes
+
+
+
+
+
+
+ Some visualizations require two paths per folio to show differences in each side,
+ e.g., for parchment, hair and flesh side.
+ This variable looks for a specific taxonomy terms
+ ('hairside' or 'fleshside' ) to select whether double paths are
+ needed.
+ 0 = no; 1 = yes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Some visualizations require two paths per folio to show differences in each side,
+ e.g., for parchment, hair and flesh side.
+ This variable sets the displacement value for the second path.
+
+
+
+
+
+ Biocodicology visualizations assign a colour to each parchment species.
+ This variable checks if a mapping for the animal species was encoded by looking for a
+ specific taxonomy ID //taxonomy/@xml:id = 'id-species' and caters for
+ this.
+ 0 = no; 1 = yes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This variable creates a temporary tree to store all the targets of each map The
+ tree looks like this:
+
+
+
+
+
+
+ ...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This variable holds the relative path to the CSS file
+
+
+
+
+
+
+ X reference value - i.e. the registration for the whole diagram, changing this
+ value, the whole diagram can be moved (left-right)
+
+
+
+
+
+ Y reference value - i.e. the registration for the whole diagram, changing this
+ value, the whole diagram can be moved (top-bottom)
+
+
+
+
+
+
+ Cx does not need to be parametric, so it can be established at this level, but it
+ is called in the Cx template to keep Cx and Cy together
+
+
+
+
+
+
+ Value to determine the Y value of distance between the different components of the
+ gathering
+
+
+
+
+
+
+ Variable to determine the minimum length of the leaves in the diagram
+
+
+
+
+
+
+ Variable to determine the maximum number of regular bifolia in a gathering in the
+ whole model (even if there are more textblocks)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Variable to determine the max number of figures needed to write the leaf
+ numbers.
+
+
+
+
+
+
+
+
+ Variable to determine the maximum number of letters for a roman numeral utilized
+ to count the gatherings.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Main template to start generating files for each textblock: one item could be
+ composed of more than one textblock and if coded in this manner the system will
+ treat each textblock as independent (adding a number to the ID)
+
+
+
+
+
+ Warning: Old schema, "manuscript" should be replaced with
+ "textblock".
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This template groups the leaves into gatherings and analyses the structure of the
+ codex into a series of parameters containing the relevant information.
+
+
+ Parameter to pass on the textblock ID generated from the shelfmark. Only adds
+ textblock number for more than one textblock in bookblock.
+
+
+ The item's shelfmark
+
+
+ A copy of the whole textblock to manage inter-quire references.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This template initiates the SVG pipeline. The parameters necessary to draw the
+ gatherings are passed from the previous template.
+
+
+ Parameter to pass on the textblock ID generated from the shelfmark. Only adds
+ textblock number for more than one textblock in bookblock.
+
+
+ The gathering number.
+
+
+ The total number of leaves in the gathering.
+
+
+ The item's shelfmark.
+
+
+ A copy of the whole textblock to manage inter-quire references.
+
+
+ Parameter to manage subquires.
+
+
+ Variable to count the number of singletons in the quire
+ Singletons are folios with the following pattern:
+ /viscoll/textblock/leaves/leaf/q/single/@val="yes" . Whilst folios
+ whose cognate has @mode with value 'missing' are technically
+ singletons they are not counted here as they do not alter the symmetry of the
+ diagram.
+
+
+ Parameter to count the number of leaves in subquires.
+
+
+ Variable to count how many bifolia should be drawn.
+ If the total number of positions is an even number the components are the total
+ number of positions/2, if odd = (the total number of positions - total number of
+ singletons)/2
+
+
+ Refined parameter that only counts regular bifolia in the main gathering.
+
+
+ Parameter to find the left regular inner leaf position: it avoids singletons
+ (inside complex gatherings) and leaves belonging to subquires.
+
+
+ Parameter to check for subquires in the middle of the quire that belong to the
+ left half of the gathering.
+
+
+ First leaf in the gathering (that is not 'missing' or is not a stub)
+
+
+ Last leaf in the gathering (that is not 'missing' or is not a stub).
+
+
+ Parameter to store the end of line x value to write folio numbers (in R-L
+ direction).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SVG file generated on:
+
+ using
+
+ version
+
+
+
+
+
+
+ Collation diagram of quire for composed of
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This template prepares the data for the drawing of the regular bifolia. The
+ parameters necessary to draw the gatherings are passed from the previous
+ template.
+
+
+ A copy of the whole textblock to manage inter-quire references.
+
+
+ The total number of leaves in the gathering.
+
+
+ Parameter to manage subquires.
+
+
+ The gathering number.
+
+
+ Variable to count the number of singletons in the quire
+ Singletons are folios with the following pattern:
+ /viscoll/textblock/leaves/leaf/q/single/@val="yes" . Whilst folios
+ whose cognate has @mode with value 'missing' are technically
+ singletons they are not counted here as they do not alter the symmetry of the
+ diagram.
+
+
+ Parameter to count the number of leaves in subquires.
+
+
+ Variable to count how many bifolia should be drawn.
+ If the total number of positions is an even number the components are the total
+ number of positions/2, if odd = (the total number of positions - total number of
+ singletons)/2
+
+
+ Refined parameter that only counts regular bifolia in the main gathering.
+
+
+ Parameter to find the left regular inner leaf position: it avoids singletons
+ (inside complex gatherings) and leaves belonging to subquires.
+
+
+ Parameter to check for subquires in the middle of the quire that belong to the
+ left half of the gathering.
+
+
+ First leaf in the gathering (that is not 'missing' or is not a stub)
+
+
+ Last leaf in the gathering (that is not 'missing' or is not a stub).
+
+
+ Parameter to store the end of line x value to write folio numbers (in R-L
+ direction).
+
+
+ Writing direction.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Folio #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rotate diagrams if r-l.
+
+ Writing direction.
+
+
+ Parameter to determine where the template is called.
+ text = 0 (main mirroring of the whole diagram for R-L);
+ text = 1 (folio numbers);
+ text = 2 (gathering numbers).
+
+
+ Parameter to check for subquires in the middle of the quire that belong to the
+ left half of the gathering.
+
+
+ Parameter to store the end of line x value to write folio numbers (in R-L
+ direction).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ matrix(-1 0 0 1
+
+ 0)
+
+
+ translate(
+
+ 0) scale(-1 1)
+
+
+ translate(
+
+ 0) scale(-1 1)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Confirms the value of Cx
+
+
+
+
+
+
+
+
+ Calculates Cy
+
+
+ Parameter to find the left regular inner leaf position: it avoids singletons
+ (inside complex gatherings) and leaves belonging to subquires.
+
+
+
+
+
+
+
+
+
+ This template finds the position of the conjoined leaf.
+ If there is a conjoined leaf, then the position is returned, otherwise 0
+
+
+ Whether the leaf has a conjoin:
+
+ q[1]/conjoin
+
+
+
+ The position of the conjoined leaf.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameter to generate a unique ID that puts together conjoined leaf positions in
+ the correct order.
+
+
+ The position of the conjoined leaf.
+
+
+ The position of the current leaf.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameter to determine if the current folio is in the left or the right half of
+ the gathering.
+
+
+ If there is a single leaf
+ q[1]/single/@val
+
+
+ If the leaf is in a subquire
+ contains(q[1]/@n, '.')
+
+
+ If the leaf is in the left half of the subquire
+ (xs:integer(parent::tp:subquire/vc:leaf[1]/vc:q[1]/@position) - xs:integer($centralLeftLeafPos)) le 1
+
+
+ The position of the current leaf.
+
+
+ Parameter to find the left regular inner leaf position: it avoids singletons
+ (inside complex gatherings) and leaves belonging to subquires.
+
+
+ The current leaf's attachment method.
+
+
+ The ID of the preceding leaf.
+
+
+ If the current leaf is in a subquire
+ 0 = no; 1 = yes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Template to count how many folios the current one is wrapped around.
+
+
+ If the current folio is in the left or the right half of the gathering.
+
+
+ Parameter to find the left regular inner leaf position: it avoids singletons
+ (inside complex gatherings) and leaves belonging to subquires.
+
+
+ The position of the current leaf.
+
+
+ Parameter to check for subquires in the middle of the quire that belong to the
+ left half of the gathering.
+
+
+ If the current leaf is in a subquire
+ 0 = no; 1 = yes
+
+
+ If the current folio is in the left or the right half of the subquire.
+
+
+ Parameter to find the left regular inner leaf position within subquires.
+
+
+ The position of the current leaf within the subquire.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Template to count how many regular bifolia the current folio is wrapped
+ around.
+
+
+ If the current folio is in the left or the right half of the gathering.
+
+
+ The number of regular components to the left of the current leaf.
+
+
+ The number of regular components to the right of the current leaf.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This template draws regular bifolia. The parameters necessary to draw the
+ gatherings are passed from the previous template.
+
+
+ The value of Cx
+
+
+ The value of Cy
+
+
+ A copy of the whole textblock to manage inter-quire references.
+
+
+ Variable to count how many bifolia should be drawn.
+ If the total number of positions is an even number the components are the total
+ number of positions/2, if odd = (the total number of positions - total number of
+ singletons)/2
+
+
+ Refined parameter that only counts regular bifolia in the main gathering.
+
+
+ Parameter to count the number of leaves in subquires.
+
+
+ Parameter to count how many folios the current one is wrapped around.
+
+
+ Parameter to count how many regular bifolia the current folio is wrapped
+ around.
+
+
+ Parameter to find the left regular inner leaf position: it avoids singletons
+ (inside complex gatherings) and leaves belonging to subquires.
+
+
+ If the current folio is in the left or the right half of the gathering.
+
+
+ The position of the conjoined leaf.
+
+
+ The ID of the conjoined leaf.
+
+
+ The total number of leaves in the gathering.
+
+
+ First leaf in the gathering (that is not 'missing' or is not a stub)
+
+
+ Last leaf in the gathering (that is not 'missing' or is not a stub).
+
+
+ Writing direction.
+
+
+ Parameter to store the end of line x value to write folio numbers (in R-L
+ direction).
+
+
+ The gathering number.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Folio #
+
+ : arc
+
+
+
+
+
+
+ M
+
+ ,
+
+ Q
+
+ ,
+
+
+
+ ,
+
+
+
+
+
+
+
+
+ M
+
+ ,
+
+ Q
+
+ ,
+
+
+
+
+ ,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Folio #
+
+ : line
+
+
+
+
+
+
+ M
+
+ ,
+
+ L
+
+ ,
+
+
+
+
+
+
+
+
+
+ M
+
+ ,
+
+ L
+
+ ,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Template to write the gathering number in Roman numerals in the SVG.
+
+
+ First leaf in the gathering (that is not 'missing' or is not a stub)
+
+
+ Writing direction.
+
+
+ Variable to count how many bifolia should be drawn.
+ If the total number of positions is an even number the components are the total
+ number of positions/2, if odd = (the total number of positions - total number of
+ singletons)/2
+
+
+ Refined parameter that only counts regular bifolia in the main gathering.
+
+
+ Parameter to store the end of line x value to write folio numbers (in R-L
+ direction).
+
+
+ The gathering number.
+
+
+ Parameter to count the number of leaves in the current subquire.
+
+
+ The value of Cx
+
+
+ The value of Cy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This template draws the folio. When double paths are needed to encode
+ visualizations for leaf sides, this draws the left side or right side depending on
+ the position of the leaf (left or right of the centre).
+
+
+ FolioID for JS highlighting
+
+
+ Line path. When double paths are needed to encode visualizations for leaf sides, a
+ second path is drawn and it corresponds to the left side or right side depending on
+ the position of the leaf (left or right of the centre)
+
+
+ Parameter to define the visualization of the mode of the folio. Regularly, these
+ are coded in the XML model, however, for parchment leaves, the visualization
+ requires to indicate flesh and hair sides, modes (missing, replaced, added) cannot
+ also be visualized.
+
+
+ Whether the current leaf is in the main gathering or in a subquire.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Template to determine and visualize the uncertainty of the path.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Template to determine and visualize the uncertainty of the path within
+ subquires.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This template draws the folio. When double paths are needed to encode
+ visualizations for leaf sides, this draws the left side or right side depending on
+ the position of the leaf (left or right of the centre)
+
+
+ FolioID for JS highlighting
+
+
+ Arc path. When double paths are needed to encode visualizations for leaf sides, a
+ second path is drawn and it corresponds to the left side or right side depending on
+ the position of the leaf (left or right of the centre)
+
+
+ Parameter to define the visualization of the mode of the folio. Regularly, these
+ are coded in the XML model, however, for parchment leaves, the visualization
+ requires to indicate flesh and hair sides, modes (missing, replaced, added) cannot
+ also be visualized.
+
+
+ Whether the current leaf is in the main gathering or in a subquire.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This template prepares the data for the drawing of the irregular bifolia. The
+ parameters necessary to draw the gatherings are passed from the previous
+ template.
+
+
+ Parameter to manage subquires.
+
+
+ A copy of the whole textblock to manage inter-quire references.
+
+
+ Counter for recursive template
+
+
+ Variable to count how many bifolia should be drawn.
+ If the total number of positions is an even number the components are the total
+ number of positions/2, if odd = (the total number of positions - total number of
+ singletons)/2
+
+
+ Refined parameter that only counts regular bifolia in the main gathering.
+
+
+ Parameter to count the number of leaves in subquires.
+
+
+ Parameter to find the left regular inner leaf position: it avoids singletons
+ (inside complex gatherings) and leaves belonging to subquires.
+
+
+ Number of subquires, i.e. the number of iterations
+
+
+ First leaf in the gathering (that is not 'missing' or is not a stub)
+
+
+ Last leaf in the gathering (that is not 'missing' or is not a stub).
+
+
+ Parameter to store the end of line x value to write folio numbers (in R-L
+ direction).
+
+
+ The gathering number.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Folio #
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This template checks if there are subquires with position greater than the central
+ left leaf but still in the left half.
+
+
+ Parameter to manage subquires.
+
+
+ Parameter to find the left regular inner leaf position: it avoids singletons
+ (inside complex gatherings) and leaves belonging to subquires.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Template to determine if the current folio is in the left or the right half of the
+ subquire.
+
+
+ The position of the current leaf within the subquire.
+
+
+ Parameter to find the left regular inner leaf position within subquires.
+
+
+ Counter for recursive template.
+
+
+ The number of the subquire.
+
+
+ Parameter to find the left regular inner leaf position: it avoids singletons
+ (inside complex gatherings) and leaves belonging to subquires.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Template to find the left regular inner leaf position: it avoids singletons and
+ leaves belonging to other subquires
+
+
+ Parameter to manage subquires.
+
+
+ Counter for recursive template.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This template draws irregular bifolia. The parameters necessary to draw the
+ gatherings are passed from the previous template.
+
+
+ A copy of the whole textblock to manage inter-quire references.
+
+
+ The number of the subquire.
+
+
+ The value of Cx for the subquire
+
+
+ The value of Cy for the subquire
+
+
+ Variable to count how many bifolia should be drawn.
+ If the total number of positions is an even number the components are the total
+ number of positions/2, if odd = (the total number of positions - total number of
+ singletons)/2
+
+
+ Refined parameter that only counts regular bifolia in the main gathering.
+
+
+ Parameter to count the number of leaves in subquires.
+
+
+ Parameter to count how many folios the current one is wrapped around.
+
+
+ Parameter to count how many folios the current one is wrapped around within the
+ subquire.
+
+
+ Parameter to count how many regular bifolia the current folio is wrapped around
+ within the subquire.
+
+
+ If the current folio is in the left or the right half of the subquire.
+
+
+ If the current folio is in the left or the right half of the gathering.
+
+
+ Parameter to count the number of leaves in the current subquire.
+
+
+ Checks how many leaves before within the subquire.
+
+
+ Parameter to find the left regular inner leaf position within subquires.
+
+
+ The position of the conjoined leaf.
+
+
+ The ID of the conjoined leaf.
+
+
+ First leaf in the gathering (that is not 'missing' or is not a stub)
+
+
+ Last leaf in the gathering (that is not 'missing' or is not a stub).
+
+
+ Writing direction.
+
+
+ Parameter to store the end of line x value to write folio numbers (in R-L
+ direction).
+
+
+ The gathering number.
+
+
+ Parameter to count the number of singletons in the subquire.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Folio #
+
+ : arc
+
+
+
+
+
+
+ M
+
+ ,
+
+ Q
+
+ ,
+
+
+
+ ,
+
+
+
+
+
+
+
+
+ M
+
+ ,
+
+ Q
+
+ ,
+
+
+
+
+ ,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Folio #
+
+ : line
+
+
+
+
+
+
+ M
+
+ ,
+
+ L
+
+ ,
+
+
+
+
+
+
+
+
+
+ M
+
+ ,
+
+ L
+
+ ,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This template adds the folio numbers to the SVG.
+
+
+ First leaf in the gathering (that is not 'missing' or is not a stub)
+
+
+ The value of Cy.
+
+
+ Parametric Y values for each leaf: positive or negative depending on whether the
+ leaf is in the left or right half pf the quire
+
+
+ The value of Cx.
+
+
+ Variable to count how many bifolia should be drawn.
+ If the total number of positions is an even number the components are the total
+ number of positions/2, if odd = (the total number of positions - total number of
+ singletons)/2
+
+
+ The line length varies for stubs, and is lengthened to gatherings with less leaves
+ than the maximum in the textblock (so that the diagrams, if aligned, are all the
+ same width.
+
+
+ Last leaf in the gathering (that is not 'missing' or is not a stub).
+
+
+ The numbering of the current folio
+
+
+ Writing direction.
+
+
+ Parameter to store the end of line x value to write folio numbers (in R-L
+ direction).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ FolioID for JS highlighting.
+
+
+ Checks if folio is missing.
+
+
+ Standard ID text
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ CSS class attribute template.
+
+
+ Parameter to define the visualization of the mode of the folio. Regularly, these
+ are coded in the XML model, however, for parchment leaves, the visualization
+ requires to indicate flesh and hair sides, modes (missing, replaced, added) cannot
+ also be visualized.
+
+
+ FolioID for JS highlighting
+ One value for bifolium
+
+
+
+
+
+
+
+
+ leaf
+
+
+ missingLeaf
+
+
+ replacedLeaf
+
+
+ addedLeaf
+
+
+
+ fleshside
+
+
+ hairside
+
+
+
+
+
+
+
+
+
+ calfskin
+
+
+ sheepskin
+
+
+ goatskin
+
+
+
+
+
+
+ base
+
+
+
+
+
+
+
+
+
+ leaf
+
+
+
+
+
+
+
+
+ This template draws the attachment methods. The parameters necessary to draw the
+ gatherings are passed from the previous template.
+
+
+ The value of Cx for the attachment method.
+
+
+ The value of Cy for the attachment method.
+
+
+ Variable to count how many bifolia should be drawn.
+ If the total number of positions is an even number the components are the total
+ number of positions/2, if odd = (the total number of positions - total number of
+ singletons)/2
+
+
+ Refined parameter that only counts regular bifolia in the main gathering.
+
+
+ Parameter to count the number of leaves in the current subquire.
+
+
+ The line length varies for stubs, and is lengthened to gatherings with less leaves
+ than the maximum in the textblock (so that the diagrams, if aligned, are all the
+ same width.
+
+
+ Parametric Y values for each leaf: positive or negative depending on whether the
+ leaf is in the left or right half pf the quire
+
+
+ Checks the deviation between the leaf and its attachment target: usually this
+ would be the leaf before or after.
+
+
+ Certainty value.
+
+
+ Writing direction.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ leaf
+
+
+ Attachment method -
+
+
+
+
+
+
+
+
+ M
+
+ ,
+
+ L
+
+ ,
+
+
+
+
+
+
+
+ M
+
+ ,
+
+ L
+
+ ,
+
+
+
+
+
+
+
+
+
+ M
+
+ ,
+
+ Q
+
+ ,
+
+
+
+ ,
+
+ M
+
+ ,
+
+ L
+
+ ,
+
+
+
+
+
+ M
+
+ ,
+
+ Q
+
+ ,
+
+
+
+ ,
+
+ M
+
+ ,
+
+ L
+
+ ,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ M
+
+ ,
+
+ L
+
+ ,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SVG definitions' template
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Glue pattern
+
+
+
+
+
+
+
+
+ Uncertainty template
+
+
+ Certainty value.
+
+
+
+
+
+
+
+
+
+
+
+
+
+