├── .gitignore
├── .gitlab-ci.yml
├── Dockerfile
├── README.md
├── docker-compose.yml
├── exclude.txt
├── sonar-project.properties
├── viscoll-api
├── .rspec
├── Gemfile
├── Gemfile.lock
├── Guardfile
├── README.md
├── Rakefile
├── app
│ ├── channels
│ │ └── application_cable
│ │ │ ├── channel.rb
│ │ │ └── connection.rb
│ ├── controllers
│ │ ├── application_controller.rb
│ │ ├── concerns
│ │ │ ├── .keep
│ │ │ └── rails_jwt_auth
│ │ │ │ └── warden_helper.rb
│ │ ├── confirmations_controller.rb
│ │ ├── export_controller.rb
│ │ ├── feedback_controller.rb
│ │ ├── filter_controller.rb
│ │ ├── groups_controller.rb
│ │ ├── images_controller.rb
│ │ ├── import_controller.rb
│ │ ├── leafs_controller.rb
│ │ ├── notes_controller.rb
│ │ ├── projects_controller.rb
│ │ ├── registrations_controller.rb
│ │ ├── sessions_controller.rb
│ │ ├── sides_controller.rb
│ │ └── users_controller.rb
│ ├── helpers
│ │ ├── controller_helper
│ │ │ ├── export_helper.rb
│ │ │ ├── filter_helper.rb
│ │ │ ├── groups_helper.rb
│ │ │ ├── import_helper.rb
│ │ │ ├── import_json_helper.rb
│ │ │ ├── import_mapping_helper.rb
│ │ │ ├── import_xml_helper.rb
│ │ │ ├── leafs_helper.rb
│ │ │ └── projects_helper.rb
│ │ └── validation_helper
│ │ │ ├── group_validation_helper.rb
│ │ │ ├── leaf_validation_helper.rb
│ │ │ └── project_validation_helper.rb
│ ├── jobs
│ │ └── application_job.rb
│ ├── mailers
│ │ ├── account_approval_mailer.rb
│ │ ├── application_mailer.rb
│ │ ├── feedback_mailer.rb
│ │ └── mailer.rb
│ ├── models
│ │ ├── concerns
│ │ │ └── .keep
│ │ ├── group.rb
│ │ ├── image.rb
│ │ ├── leaf.rb
│ │ ├── note.rb
│ │ ├── project.rb
│ │ ├── side.rb
│ │ └── user.rb
│ └── views
│ │ ├── account_approval_mailer
│ │ └── sendApprovalStatus.html.erb
│ │ ├── exports
│ │ └── show.json.jbuilder
│ │ ├── feedback_mailer
│ │ └── sendFeedback.html.erb
│ │ ├── filter
│ │ └── show.json.jbuilder
│ │ ├── layouts
│ │ ├── mailer.html.erb
│ │ └── mailer.text.erb
│ │ ├── projects
│ │ ├── index.json.jbuilder
│ │ └── show.json.jbuilder
│ │ ├── rails_jwt_auth
│ │ └── mailer
│ │ │ ├── confirmation_instructions.html.erb
│ │ │ ├── reset_password_instructions.html.erb
│ │ │ └── set_password_instructions.html.erb
│ │ ├── sessions
│ │ └── index.json.jbuilder
│ │ └── users
│ │ └── show.json.jbuilder
├── bin
│ ├── bundle
│ ├── rails
│ ├── rake
│ ├── setup
│ ├── spring
│ └── update
├── config.ru
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── cable.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── application_controller_renderer.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── cors.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── mime_types.rb
│ │ ├── mongoid.rb
│ │ ├── new_framework_defaults.rb
│ │ ├── rails_jwt_auth.rb
│ │ └── wrap_parameters.rb
│ ├── locales
│ │ └── en.yml
│ ├── mongoid.yml
│ ├── puma.rb
│ ├── routes.rb
│ ├── secrets.yml
│ ├── shrine.rb
│ └── spring.rb
├── db
│ └── seeds.rb
├── lib
│ └── tasks
│ │ └── .keep
├── log
│ └── .keep
├── public
│ ├── docs
│ │ ├── index.html
│ │ └── viscoll_api.yaml
│ ├── viscoll-datamodel2.0.rng
│ └── viscoll-datamodel2.rng
├── spec
│ ├── factories
│ │ ├── groups.rb
│ │ ├── images.rb
│ │ ├── leafs.rb
│ │ ├── notes.rb
│ │ ├── projects.rb
│ │ ├── sides.rb
│ │ └── users.rb
│ ├── fixtures
│ │ ├── base64zip.txt
│ │ ├── dots_exported.zip
│ │ ├── pixel.png
│ │ ├── sample_import_json.json
│ │ ├── sample_import_xml.xml
│ │ ├── shibainu.jpg
│ │ ├── uoft_hollar.json
│ │ ├── villanova_boston.json
│ │ └── viscoll.png
│ ├── helpers
│ │ ├── controller_helper
│ │ │ ├── export_helper_spec.rb
│ │ │ ├── filter_helper_spec.rb
│ │ │ ├── groups_helper_spec.rb
│ │ │ ├── import_helper_spec.rb
│ │ │ ├── import_json_helper_spec.rb
│ │ │ ├── import_mapping_helper_spec.rb
│ │ │ ├── import_xml_helper_spec.rb
│ │ │ ├── leafs_helper_spec.rb
│ │ │ └── projects_helper_spec.rb
│ │ └── validation_helper
│ │ │ ├── group_validation_helper_spec.rb
│ │ │ ├── leaf_validation_helper_spec.rb
│ │ │ └── project_validation_helper_spec.rb
│ ├── mailers
│ │ ├── feedback_spec.rb
│ │ └── previews
│ │ │ └── feedback_preview.rb
│ ├── models
│ │ ├── group_spec.rb
│ │ ├── grouping_spec.rb
│ │ ├── image_spec.rb
│ │ ├── leaf_spec.rb
│ │ ├── note_spec.rb
│ │ ├── project_spec.rb
│ │ └── side_spec.rb
│ ├── rails_helper.rb
│ ├── requests
│ │ ├── authentication
│ │ │ ├── delete_session_spec.rb
│ │ │ ├── post_password_spec.rb
│ │ │ ├── post_registration_spec.rb
│ │ │ ├── post_session_spec.rb
│ │ │ ├── put_confirmation_spec.rb
│ │ │ └── put_password_spec.rb
│ │ ├── feedback
│ │ │ └── create_feedback_spec.rb
│ │ ├── groups
│ │ │ ├── groups_create_spec.rb
│ │ │ ├── groups_destroy_multiple_spec.rb
│ │ │ ├── groups_destroy_spec.rb
│ │ │ ├── groups_update_multiple_spec.rb
│ │ │ └── groups_update_spec.rb
│ │ ├── images
│ │ │ ├── destroy_images_spec.rb
│ │ │ ├── link_images_spec.rb
│ │ │ ├── show_images_spec.rb
│ │ │ ├── unlink_images_spec.rb
│ │ │ ├── upload_images_spec.rb
│ │ │ └── zip_images_spec.rb
│ │ ├── leafs
│ │ │ ├── leafs_conjoin_spec.rb
│ │ │ ├── leafs_create_spec.rb
│ │ │ ├── leafs_destroy_multiple_spec.rb
│ │ │ ├── leafs_destroy_spec.rb
│ │ │ ├── leafs_update_multiple_spec.rb
│ │ │ └── leafs_update_spec.rb
│ │ ├── notes
│ │ │ ├── notes_create_spec.rb
│ │ │ ├── notes_create_type_spec.rb
│ │ │ ├── notes_delete_type_spec.rb
│ │ │ ├── notes_destroy_spec.rb
│ │ │ ├── notes_link_spec.rb
│ │ │ ├── notes_unlink_spec.rb
│ │ │ ├── notes_update_spec.rb
│ │ │ └── notes_update_type_spec.rb
│ │ ├── projects
│ │ │ ├── create_manifest_projects_spec.rb
│ │ │ ├── create_projects_spec.rb
│ │ │ ├── delete_manifest_projects_spec.rb
│ │ │ ├── destroy_projects_spec.rb
│ │ │ ├── export_projects_spec.rb
│ │ │ ├── filter_projects_spec.rb
│ │ │ ├── import_projects_spec.rb
│ │ │ ├── index_projects_spec.rb
│ │ │ ├── show_projects_spec.rb
│ │ │ ├── update_manifest_projects_spec.rb
│ │ │ └── update_projects_spec.rb
│ │ ├── sides
│ │ │ ├── sides_generateFolio_spec.rb
│ │ │ ├── sides_generatePage_spec.rb
│ │ │ ├── sides_updateMultiple_spec.rb
│ │ │ └── sides_update_spec.rb
│ │ └── users
│ │ │ ├── delete_users_userID_spec.rb
│ │ │ ├── get_users_userID_spec.rb
│ │ │ └── put_users_userID_spec.rb
│ └── spec_helper.rb
└── tmp
│ └── .keep
└── viscoll-app
├── README.md
├── __test__
├── actions
│ └── frontendBeforeActions
│ │ ├── groupActions.spec.js
│ │ ├── helperActions.spec.js
│ │ ├── imageActions.spec.js
│ │ ├── leafActions.spec.js
│ │ ├── manifestActions.spec.js
│ │ ├── noteActions.spec.js
│ │ ├── projectActions.spec.js
│ │ └── sideActions.spec.js
└── testData
│ ├── dashboardState001.js
│ ├── membersStructure01.js
│ ├── projectState001.js
│ └── state001.js
├── assetsTransformer.js
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── sass
├── components
│ ├── _dialog.scss
│ ├── _textarea.scss
│ └── _tooltip.scss
├── index.scss
├── layout
│ ├── _404.scss
│ ├── _dashboard.scss
│ ├── _filter.scss
│ ├── _imageCollection.scss
│ ├── _imageManager.scss
│ ├── _infobox.scss
│ ├── _landing.scss
│ ├── _loading.scss
│ ├── _notes.scss
│ ├── _projectPanel.scss
│ ├── _sidebar.scss
│ ├── _tabular.scss
│ ├── _topbar.scss
│ └── _workspace.scss
├── lib
│ ├── _mixins.scss
│ └── _variables.scss
└── typography.scss
└── src
├── actions
├── backend
│ ├── filterActions.js
│ ├── groupActions.js
│ ├── imageActions.js
│ ├── interactionActions.js
│ ├── leafActions.js
│ ├── manifestActions.js
│ ├── noteActions.js
│ ├── projectActions.js
│ ├── sideActions.js
│ └── userActions.js
├── frontend
│ ├── after
│ │ └── imageActions.js
│ └── before
│ │ ├── groupActions.js
│ │ ├── helperActions.js
│ │ ├── imageActions.js
│ │ ├── leafActions.js
│ │ ├── manifestActions.js
│ │ ├── noteActions.js
│ │ ├── projectActions.js
│ │ └── sideActions.js
└── undoRedo
│ ├── groupHelper.js
│ ├── imageHelper.js
│ ├── leafHelper.js
│ ├── manifestHelper.js
│ ├── noteHelper.js
│ └── sideHelper.js
├── assets
├── blank_page.png
├── collation.png
├── logo_white.png
├── logo_white.svg
├── viscoll_loading.gif
└── visualMode
│ ├── PaperGroup.js
│ ├── PaperLeaf.js
│ ├── PaperManager.js
│ └── export
│ ├── PaperGroup.js
│ ├── PaperLeaf.js
│ └── PaperManager.js
├── components
├── authentication
│ ├── Login.js
│ ├── Register.js
│ ├── ResendConfirmation.js
│ ├── ResetPassword.js
│ └── ResetPasswordRequest.js
├── collationManager
│ ├── ExportMode.js
│ ├── TabularMode.js
│ ├── ViewingMode.js
│ ├── VisualMode.js
│ ├── dialog
│ │ └── NoteDialog.js
│ └── tabularMode
│ │ ├── Group.js
│ │ ├── Leaf.js
│ │ └── Side.js
├── dashboard
│ ├── CloneProject.js
│ ├── EditProjectForm.js
│ ├── ImageCollections.js
│ ├── ImportProject.js
│ ├── ListView.js
│ ├── NewProjectChoice.js
│ ├── NewProjectContainer.js
│ ├── NewProjectSelection.js
│ ├── ProjectDetails.js
│ ├── ProjectOptions.js
│ └── ProjectStructure.js
├── export
│ └── Export.js
├── filter
│ └── FilterRow.js
├── global
│ ├── AppLoadingScreen.js
│ ├── ImageViewer.js
│ ├── LoadingScreen.js
│ ├── ManagersPanel.js
│ ├── NetworkErrorScreen.js
│ ├── Notification.js
│ ├── PageNotFound.js
│ ├── Panel.js
│ ├── SelectField.js
│ └── ServerErrorScreen.js
├── imageManager
│ ├── AddManifest.js
│ ├── DeleteManifest.js
│ ├── EditManifest.js
│ ├── ManageManifests.js
│ ├── MapImages.js
│ ├── RemoveImageConfirmation.js
│ ├── UploadImages.js
│ └── mapImages
│ │ ├── ImageBacklog.js
│ │ ├── MapBoard.js
│ │ └── SideBacklog.js
├── infoBox
│ ├── GroupInfoBox.js
│ ├── LeafInfoBox.js
│ ├── SideInfoBox.js
│ └── dialog
│ │ ├── AddGroupDialog.js
│ │ ├── AddLeafDialog.js
│ │ ├── AddNote.js
│ │ ├── DeleteConfirmationDialog.js
│ │ ├── FolioNumberDialog.js
│ │ ├── NoteDialog.js
│ │ └── VisualizationDialog.js
├── notesManager
│ ├── DeleteConfirmation.js
│ ├── EditNoteForm.js
│ ├── ManageNotes.js
│ ├── NewNoteForm.js
│ ├── NoteType.js
│ └── NotesFilter.js
└── topbar
│ └── UserProfileForm.js
├── containers
├── App.js
├── Authentication.js
├── CollationManager.js
├── CollationManagerViewOnly.js
├── Dashboard.js
├── Feedback.js
├── Filter.js
├── ImageManager.js
├── InfoBox.js
├── NotesManager.js
├── Project.js
├── ProjectViewOnly.js
└── TopBar.js
├── helpers
├── MultiSelectAutoComplete.js
├── getLeafsOfGroup.js
├── getMemberOrder.js
└── renderHelper.js
├── index.js
├── reducers
├── dashboardReducer.js
├── editCollationReducer.js
├── globalReducer.js
├── historyReducer.js
├── initialStates
│ ├── active.js
│ ├── global.js
│ ├── history.js
│ ├── projects.js
│ └── user.js
└── userReducer.js
├── registerServiceWorker.js
├── store
├── axiosConfig.js
├── middleware
│ ├── frontendAfterActionsMiddleware.js
│ ├── frontendBeforeActionsMiddleware.js
│ └── undoRedoMiddleware.js
└── store.js
└── styles
├── App.css
├── App.css.map
├── button.js
├── checkbox.js
├── index.css
├── infobox.js
├── light.js
├── sidebar.js
├── tabular.js
├── textfield.js
└── topbar.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore all logfiles and tempfiles.
11 | */log/*
12 | */tmp/*
13 | !/log/.keep
14 | !/tmp/.keep
15 |
16 | # Ignore Byebug command history file.
17 | .byebug_history
18 |
19 | # Ignore Mac files
20 | *.DS_Store
21 |
22 | # Ignore Visual Studio files
23 | .vscode
24 |
25 | # Ignore caches
26 | *.sass-cache
27 |
28 | /public/swagger/
29 |
30 | *.idea
31 |
32 | # React app stuff
33 |
34 | # dependencies
35 | */node_modules
36 |
37 | # testing
38 | */coverage
39 |
40 | # production
41 | */build
42 |
43 | # documentation
44 | styleguide
45 |
46 | # misc
47 | *.DS_Store
48 |
49 | *.env.local
50 | *.env.development.local
51 | *.env.test.local
52 | *.env.production.local
53 |
54 | *npm-debug.log*
55 | *yarn-debug.log*
56 | *yarn-error.log*
57 |
58 | coverage
59 | jest_0
60 | test.log
61 | *.xml
62 | !/viscoll-api/spec/fixtures/*.xml
63 |
64 | # DIY images
65 | viscoll-api/uploads/*
66 | viscoll-api/public/uploads*
67 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:16.04
2 |
3 | # Install tools & libs to compile everything
4 | RUN apt-get update && \
5 | apt-get install -y curl tzdata build-essential libssl-dev libreadline-dev wget && \
6 | apt-get clean
7 |
8 | # Install nodejs
9 | RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
10 | RUN apt-get install -y nodejs && apt-get clean
11 |
12 | # Install ruby-build
13 | RUN apt-get install -y git-core && apt-get clean
14 | RUN git clone https://github.com/sstephenson/ruby-build.git && cd ruby-build && ./install.sh
15 |
16 | # Install ruby 2.4.1
17 | ENV CONFIGURE_OPTS --disable-install-rdoc
18 | RUN ruby-build 2.6.4 /usr/local
19 | RUN gem install bundler
20 | RUN gem install tzinfo-data
21 |
22 | # Clean up downloaded packages
23 | RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 |
3 | services:
4 | api:
5 | build: .
6 | image: viscoll
7 | container_name: viscoll-api
8 | volumes:
9 | - ./viscoll-api:/app
10 | working_dir: /app
11 | command: bash -c "rm -f tmp/pids/server.pid && bundle i && bundle exec rails s -p 3001 -b '0.0.0.0'"
12 | ports:
13 | - 3001:3001
14 | depends_on:
15 | - mongo
16 | - mongo-express
17 |
18 | app:
19 | build: .
20 | image: viscoll
21 | container_name: viscoll-app
22 | volumes:
23 | - ./viscoll-app:/app
24 | working_dir: /app
25 | command: bash -c "npm install && npm start"
26 | ports:
27 | - 3000:3000
28 | depends_on:
29 | - api
30 |
31 | mongo:
32 | container_name: viscoll-mongo
33 | image: mongo:4.0
34 | volumes:
35 | - mongo:/data/db
36 |
37 | mongo-express:
38 | image: mongo-express
39 | container_name: viscoll-mongo-express
40 | ports:
41 | - 127.0.0.1:3002:8081
42 | depends_on:
43 | - mongo
44 | environment:
45 | ME_CONFIG_MONGODB_SERVER: mongo
46 | depends_on:
47 | - mongo
48 |
49 | volumes:
50 | mongo:
51 |
--------------------------------------------------------------------------------
/exclude.txt:
--------------------------------------------------------------------------------
1 | viscoll-api
2 | viscoll-app
3 | test_report
4 | .gitignore
5 | .git
6 | viscoll.tar.gz
7 | viscoll-testing.tar.gz
8 | viscoll-dev.tar.gz
9 | spec
10 | exclude.txt
11 | install_nvm.sh
12 | rspec_test_results.xml
13 | spec
14 | test_report
15 | mongodb*
16 | nodesource*
17 | coverage
18 | data/db
19 | test.log
20 | viscoll-api.tar.gz
21 | viscoll-app.tar.gz
22 | utlviscoll-api.tar.gz
23 | utlviscoll-app.tar.gz
24 | testing.tar.gz
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | # must be unique in a given SonarQube instance
2 | sonar.projectKey=viscoll
3 | # this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1.
4 | sonar.projectName=VisColl
5 | sonar.projectVersion=0.6.0
6 |
7 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
8 | # This property is optional if sonar.modules is set.
9 | sonar.sources=viscoll-app/src
10 |
11 | # Encoding of the source code. Default is default system encoding
12 | #sonar.sourceEncoding=UTF-8
--------------------------------------------------------------------------------
/viscoll-api/.rspec:
--------------------------------------------------------------------------------
1 | --require spec_helper
2 | --color
3 | --format documentation
4 |
--------------------------------------------------------------------------------
/viscoll-api/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | git_source(:github) do |repo_name|
4 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
5 | "https://github.com/#{repo_name}.git"
6 | end
7 |
8 |
9 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
10 | gem 'rails', '5.2.5.0'
11 | # Use Puma as the app server
12 | gem 'puma', '~> 3.0'
13 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
14 | gem 'jbuilder', '~> 2.7'
15 | # Use Redis adapter to run Action Cable in production
16 | # gem 'redis', '~> 3.0'
17 | # Use ActiveModel has_secure_password
18 | # gem 'bcrypt', '~> 3.1.7'
19 |
20 | # Use Capistrano for deployment
21 | # gem 'capistrano-rails', group: :development
22 |
23 | group :development, :test do
24 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console
25 | gem 'byebug', platform: :mri
26 | gem 'rspec-rails', '~> 3.8.2'
27 | gem 'factory_girl_rails', '~> 4.8'
28 | gem 'shoulda-matchers', '~> 3.1', '>= 3.1.1'
29 | gem 'faker', '~> 1.7', '>= 1.7.3'
30 | gem 'database_cleaner', '~> 1.6', '>= 1.6.1'
31 | gem 'simplecov', :require => false
32 | gem 'mongoid-rspec', github: 'mongoid-rspec/mongoid-rspec'
33 | gem 'guard-rspec'
34 | gem 'rspec_junit_formatter', '~> 0.3.0'
35 | gem 'webmock', '~> 3.1.0'
36 | end
37 |
38 | group :development do
39 | gem 'listen', '~> 3.0.5'
40 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
41 | gem 'spring'
42 | gem 'spring-watcher-listen', '~> 2.0.0'
43 | end
44 |
45 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
46 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
47 |
48 | gem 'mongoid', '~> 6.2'
49 | gem 'rails_jwt_auth', '0.16.1'
50 | gem "shrine"
51 | gem 'rubyzip', '1.3.0'
52 |
53 | # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
54 | gem 'rack-cors', '1.1.1'
55 |
--------------------------------------------------------------------------------
/viscoll-api/Guardfile:
--------------------------------------------------------------------------------
1 | guard :rspec, cmd: "bundle exec rspec" do
2 | require "guard/rspec/dsl"
3 | dsl = Guard::RSpec::Dsl.new(self)
4 |
5 | # RSpec files
6 | rspec = dsl.rspec
7 | watch(rspec.spec_files)
8 |
9 | # Rails files
10 | watch(%r{^app/controllers/*}) { rspec.spec_dir }
11 | watch(%r{^app/models/*}) { rspec.spec_dir }
12 | watch(%r{^app/views/*}) { rspec.spec_dir }
13 | watch(%r{^app/config/*}) { rspec.spec_dir }
14 | end
15 |
--------------------------------------------------------------------------------
/viscoll-api/README.md:
--------------------------------------------------------------------------------
1 | # VisColl (Rails API Back-End)
2 |
3 | ## Introduction
4 |
5 | This is the the Rails-driven back-end for Viscoll.
6 |
7 | ## System Requirements
8 |
9 | - `rvm` (>= 1.29.1)
10 | - `ruby` (>= 2.4.1)
11 |
12 | ### Additional Requirements for Development:
13 |
14 | - [`mailcatcher`](https://mailcatcher.me/) (>= 0.6.5)
15 |
16 | ## Setup
17 |
18 | Run the following commands to install the dependencies:
19 | ```
20 | rvm --ruby-version use 2.4.1@viscollobns
21 | bundle install
22 | ```
23 |
24 | Set the admin email address in two locations:
25 |
26 | `viscoll-api/app/mailers/mailer.rb` on line 18:
27 |
28 | ```
29 | toEmail = Rails.application.secrets.admin_email || "dummy-admin@library.utoronto.ca"
30 | ```
31 |
32 | and `viscoll-api/app/mailers/feedback_mailer.rb` on line 10:
33 |
34 | ```
35 | to:"utlviscoll@library.utoronto.ca",
36 | ```
37 |
38 | Then run this to start the API server:
39 | ```
40 | rails s -p 3001
41 | ```
42 |
43 | If you wish to receive confirmation and password reset emails while developing, also start the mailcatcher daemon:
44 | ```
45 | mailcatcher
46 | ```
47 |
48 | ## Testing
49 |
50 | Run this command to test once:
51 | ```
52 | rspec
53 | ```
54 |
55 | Alternatively, run this command to test continually while monitoring for changes:
56 | ```
57 | guard
58 | ```
59 |
--------------------------------------------------------------------------------
/viscoll-api/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative 'config/application'
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/viscoll-api/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/viscoll-api/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/viscoll-api/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::API
2 | before_action :set_base_api_url
3 | def set_base_api_url
4 | @base_api_url = Rails.application.secrets.api_url ? Rails.application.secrets.api_url : 'https://dummy.library.utoronto.ca/api'
5 | end
6 |
7 | include RailsJwtAuth::WardenHelper
8 | include ControllerHelper::ProjectsHelper
9 | include ControllerHelper::GroupsHelper
10 | include ControllerHelper::LeafsHelper
11 | include ControllerHelper::FilterHelper
12 | include ControllerHelper::ImportJsonHelper
13 | include ControllerHelper::ImportXmlHelper
14 | include ControllerHelper::ImportMappingHelper
15 | include ControllerHelper::ExportHelper
16 | include ValidationHelper::ProjectValidationHelper
17 | include ValidationHelper::GroupValidationHelper
18 | include ValidationHelper::LeafValidationHelper
19 | end
20 |
--------------------------------------------------------------------------------
/viscoll-api/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utlib/VisualCollation/5004b087ad5bf31ece78ad663ba1bc66efcf178d/viscoll-api/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/viscoll-api/app/controllers/concerns/rails_jwt_auth/warden_helper.rb:
--------------------------------------------------------------------------------
1 | module RailsJwtAuth
2 | module WardenHelper
3 | def signed_in?
4 | !current_user.nil?
5 | end
6 |
7 | def current_user
8 | warden.user
9 | end
10 |
11 | def warden
12 | request.env['warden']
13 | end
14 |
15 | def authenticate!
16 | begin
17 | warden.authenticate!(store: false)
18 | rescue Exception => e
19 | render json: {error: "Authorization Token: "+e.message}, status: :bad_request
20 | return false
21 | end
22 | end
23 |
24 | def authenticateDestroy!
25 | warden.authenticate!(store: false)
26 | end
27 |
28 | def self.included(base)
29 | return unless Rails.env.test? && base.name == 'ApplicationController'
30 |
31 | base.send(:rescue_from, RailsJwtAuth::Spec::NotAuthorized) do
32 | render json: {}, status: 401
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/viscoll-api/app/controllers/confirmations_controller.rb:
--------------------------------------------------------------------------------
1 | class ConfirmationsController < ApplicationController
2 | def update
3 | if params[:confirmation_token].blank?
4 | return render_422(confirmation_token: [I18n.t('rails_jwt_auth.errors.not_found')])
5 | end
6 | user = RailsJwtAuth.model.where(confirmation_token: params[:confirmation_token]).first
7 | return render_422(confirmation_token: [I18n.t('rails_jwt_auth.errors.not_found')]) unless user
8 | if user.confirm!
9 | AccountApprovalMailer.sendApprovalStatus(user).deliver_now
10 | render_204
11 | else
12 | render_422(user.errors)
13 | end
14 | end
15 |
16 | def render_204
17 | render json: {}, status: 204
18 | end
19 |
20 | def render_422(errors)
21 | render json: {errors: errors}, status: 422
22 | end
23 |
24 | end
25 |
--------------------------------------------------------------------------------
/viscoll-api/app/controllers/export_controller.rb:
--------------------------------------------------------------------------------
1 | require 'zip'
2 |
3 | class ExportController < ApplicationController
4 | before_action :authenticate!
5 | before_action :set_project, only: [:show]
6 |
7 | # GET /projects/:id/export/:format
8 | def show
9 | # Zip all DIY images and provide the link to download the file
10 | begin
11 | @zipFilePath = nil
12 | images = []
13 | current_user.images.all.each do |image|
14 | if image.projectIDs.include? @project.id.to_s
15 | images.push(image)
16 | end
17 | end
18 | if !images.empty?
19 | basePath = "#{Rails.root}/public/uploads/"
20 | zipFilename = "#{basePath}#{@project.id.to_s}_images.zip"
21 | File.delete(zipFilename) if File.exist?(zipFilename)
22 | ::Zip::File.open(zipFilename, Zip::File::CREATE) do |zipFile|
23 | images.each do |image|
24 | fileExtension = image.metadata['mime_type'].split('/')[1]
25 | filenameOnly = image.filename.rpartition(".")[0]
26 | zipFile.add("#{filenameOnly}_#{image.fileID}.#{fileExtension}", "#{basePath}#{image.fileID}")
27 | end
28 | end
29 | @zipFilePath = "#{@base_api_url}/images/zip/#{@project.id.to_s}"
30 | end
31 | rescue Exception => e
32 | end
33 |
34 | begin
35 | case @format
36 | when "xml"
37 | exportData = buildDotModel(@project)
38 | xml = Nokogiri::XML(exportData)
39 | schema = Nokogiri::XML::RelaxNG(File.open("public/viscoll-datamodel2.rng"))
40 | errors = schema.validate(xml)
41 | if errors.empty?
42 | render json: {data: exportData, type: @format, Images: {exportedImages:@zipFilePath ? @zipFilePath : false}}, status: :ok and return
43 | else
44 | render json: {data: errors, type: @format}, status: :unprocessable_entity and return
45 | end
46 | when "json"
47 | @data = buildJSON(@project)
48 | render :'exports/show', status: :ok and return
49 | else
50 | render json: {error: "Export format must be one of [json, xml]"}, status: :unprocessable_entity and return
51 | end
52 | rescue Exception => e
53 | render json: {error: e.message}, status: :internal_server_error and return
54 | end
55 | end
56 |
57 | private
58 | def set_project
59 | begin
60 | @project = Project.find(params[:id])
61 | if (@project.user_id!=current_user.id)
62 | render json: {error: ""}, status: :unauthorized and return
63 | end
64 | @format = params[:format]
65 | rescue Exception => e
66 | render json: {error: "project not found with id "+params[:id]}, status: :not_found and return
67 | end
68 | end
69 |
70 | end
71 |
--------------------------------------------------------------------------------
/viscoll-api/app/controllers/feedback_controller.rb:
--------------------------------------------------------------------------------
1 | class FeedbackController < ApplicationController
2 | before_action :authenticate!
3 |
4 | # POST /feedback
5 | def create
6 | begin
7 | if not current_user
8 | render json: {}, status: :unprocessable_entity and return
9 | end
10 | @title = feedback_params[:title]
11 | @message = feedback_params[:message]
12 | @browserInformation = feedback_params[:browserInformation]
13 | @projectJSONExport = feedback_params[:project]
14 | if @title.blank? or @message.blank?
15 | render json: {error: "[title] and [message] params required."}, status: :unprocessable_entity and return
16 | end
17 | FeedbackMailer.sendFeedback(
18 | @title,
19 | @message,
20 | @browserInformation,
21 | @projectJSONExport,
22 | current_user
23 | ).deliver_now
24 | render json: {}, status: :ok and return
25 | rescue Exception => e
26 | render json: {error: e.message}, status: :unprocessable_entity and return
27 | end
28 | end
29 |
30 | private
31 | def feedback_params
32 | params.require(:feedback).permit(:title, :message, :browserInformation, :project)
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/viscoll-api/app/controllers/import_controller.rb:
--------------------------------------------------------------------------------
1 | class ImportController < ApplicationController
2 | before_action :authenticate!
3 |
4 | # PUT /projects/import
5 | def index
6 | errorMessage = "Sorry, the imported data cannot be validated. Please check your file for errors and make sure the correct import format is selected above."
7 | importData = imported_data.to_h[:importData]
8 | importFormat = imported_data.to_h[:importFormat]
9 | imageData = imported_data.to_h[:imageData]
10 | begin
11 | case importFormat
12 | when "json"
13 | handleJSONImport(JSON.parse(importData))
14 | when "xml"
15 | xml = Nokogiri::XML(importData)
16 | schema = Nokogiri::XML::RelaxNG(File.open("public/viscoll-datamodel2.rng"))
17 | schema2 = Nokogiri::XML::RelaxNG(File.open("public/viscoll-datamodel2.0.rng"))
18 | errors = schema.validate(xml)
19 | errors2 = schema2.validate(xml)
20 | if errors.empty? || errors2.empty?
21 | handleXMLImport(xml)
22 | else
23 | render json: {error: errors+errors2}, status: :unprocessable_entity and return
24 | end
25 | end
26 | newProject = current_user.projects.order_by(:updated_at => 'desc').first
27 | handleMappingImport(newProject, imageData, current_user)
28 | current_user.reload
29 | @projects = current_user.projects.order_by(:updated_at => 'desc')
30 | @images = current_user.images
31 | render :'projects/index', status: :ok and return
32 | rescue Exception => e
33 | render json: {error: errorMessage}, status: :unprocessable_entity and return
34 | ensure
35 | end
36 | end
37 |
38 |
39 |
40 | private
41 | # Never trust parameters from the scary Internet, only allow the white list through.
42 | def imported_data
43 | params.permit(:importData, :importFormat, :imageData)
44 | end
45 |
46 | end
47 |
--------------------------------------------------------------------------------
/viscoll-api/app/controllers/registrations_controller.rb:
--------------------------------------------------------------------------------
1 | class RegistrationsController < RailsJwtAuth::RegistrationsController
2 |
3 | def create
4 | begin
5 | user = RailsJwtAuth.model.new(registration_create_params)
6 | user.save ? render_registration(user) : render_422(user.errors)
7 | rescue Exception => e
8 | render json: {error: e.message}, status: :unprocessable_entity and return
9 | end
10 |
11 | end
12 |
13 | private
14 | def registration_create_params
15 | params.require(RailsJwtAuth.model_name.underscore).permit(
16 | RailsJwtAuth.auth_field_name, :password, :password_confirmation, :name
17 | )
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/viscoll-api/app/controllers/sessions_controller.rb:
--------------------------------------------------------------------------------
1 | class SessionsController < RailsJwtAuth::SessionsController
2 |
3 | def create
4 | user = RailsJwtAuth.model.where(RailsJwtAuth.auth_field_name =>
5 | session_create_params[RailsJwtAuth.auth_field_name].to_s.downcase).first
6 |
7 | if !user
8 | render_422 session: [create_session_error]
9 | elsif user.respond_to?('confirmed?') && !user.confirmed?
10 | render_422 session: [I18n.t('rails_jwt_auth.errors.unconfirmed')]
11 | elsif user.authenticate(session_create_params[:password])
12 | @userProjects = []
13 | begin
14 | @userProjects = user.projects
15 | rescue
16 | end
17 | @userToken = get_jwt(user)
18 | @user = user
19 | render :index, status: :ok, location: {
20 | userProjects: @userProjects,
21 | userToken: @userToken,
22 | user: @user
23 | }
24 | else
25 | render_422 session: [create_session_error]
26 | end
27 | end
28 |
29 | def destroy
30 | begin
31 | authenticateDestroy!
32 | current_user.destroy_auth_token Jwt::Request.new(request).auth_token
33 | render_204
34 | rescue Exception => e
35 | render json: {error: "Authorization Header: "+e.message}, status: :unprocessable_entity
36 | end
37 | end
38 | end
39 |
40 |
41 |
42 | module Jwt
43 | class Request
44 | def initialize(request)
45 | return unless request.env['HTTP_AUTHORIZATION']
46 | @jwt = request.env['HTTP_AUTHORIZATION'].split.last
47 |
48 | begin
49 | @jwt_info = RailsJwtAuth::Jwt::Manager.decode(@jwt)
50 | rescue JWT::ExpiredSignature, JWT::VerificationError
51 | @jwt_info = false
52 | end
53 | end
54 |
55 | def valid?
56 | @jwt && @jwt_info && RailsJwtAuth::Jwt::Manager.valid_payload?(payload)
57 | end
58 |
59 | def payload
60 | @jwt_info ? @jwt_info[0] : nil
61 | end
62 |
63 | def header
64 | @jwt_info ? @jwt_info[1] : nil
65 | end
66 |
67 | def auth_token
68 | payload ? payload['auth_token'] : nil
69 | end
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/viscoll-api/app/controllers/users_controller.rb:
--------------------------------------------------------------------------------
1 | class UsersController < ApplicationController
2 | before_action :authenticate!
3 | before_action :set_user, only: [:show, :update, :destroy]
4 |
5 | # GET /users/1
6 | def show
7 | end
8 |
9 | # PATCH/PUT /users/1
10 | def update
11 | if user_params_with_password[:password] != nil
12 | action = current_user.update_with_password(user_params_with_password)
13 | else
14 | action = current_user.update_attributes(user_params)
15 | end
16 | if action
17 | @user = User.find(params[:id])
18 | render :show, status: :ok and return
19 | else
20 | render json: current_user.errors, status: :unprocessable_entity and return
21 | end
22 |
23 | end
24 |
25 | # DELETE /users/1
26 | def destroy
27 | @user.destroy
28 | end
29 |
30 | private
31 | # Use callbacks to share common setup or constraints between actions.
32 | def set_user
33 | begin
34 | @user = User.find(params[:id])
35 | if (@user!=current_user)
36 | render json: {error: ""}, status: :unauthorized and return
37 | end
38 | rescue Exception => e
39 | render json: {error: "user not found with id "+params[:id]}, status: :not_found and return
40 | end
41 | end
42 |
43 | # Only allow a trusted parameter "white list" through.
44 | def user_params
45 | params.require(:user).permit(:email, :name)
46 | end
47 |
48 | # Only allow a trusted parameter "white list" through.
49 | def user_params_with_password
50 | params.require(:user).permit(:email, :name, :current_password, :password)
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/viscoll-api/app/helpers/controller_helper/groups_helper.rb:
--------------------------------------------------------------------------------
1 | module ControllerHelper
2 | module GroupsHelper
3 | include ControllerHelper::LeafsHelper
4 |
5 | def addLeavesInside(project_id, group, noOfLeafs, conjoin, oddMemberLeftOut, leafIDs=false, sideIDs=false)
6 | begin
7 | if (leafIDs and sideIDs)
8 | Leaf.skip_callback(:create, :before, :create_sides)
9 | end
10 | newlyAddedLeafs = []
11 | newlyAddedLeafIDs = []
12 | sideIDIndex = 0
13 | noOfLeafs.times do |leafIDIndex|
14 | leaf = Leaf.new({project_id: project_id, parentID:group.id.to_s, nestLevel: group.nestLevel})
15 | if (leafIDs and sideIDs)
16 | leaf.id = leafIDs[leafIDIndex]
17 | end
18 | leaf.save()
19 | newlyAddedLeafs.push(leaf)
20 | newlyAddedLeafIDs.push(leaf.id.to_s)
21 | # Create new sides for this leaf with given SideIDs
22 | if (leafIDs and sideIDs)
23 | recto = Side.new({parentID: leaf.id.to_s, project: leaf.project, texture: "Hair", id: sideIDs[sideIDIndex]})
24 | verso = Side.new({parentID: leaf.id.to_s, project: leaf.project, texture: "Flesh", id: sideIDs[sideIDIndex+1] })
25 | recto.id = "Recto_"+recto.id.to_s
26 | verso.id = "Verso_"+verso.id.to_s
27 | recto.save
28 | verso.save
29 | leaf.rectoID = recto.id
30 | leaf.versoID = verso.id
31 | leaf.save
32 | end
33 | sideIDIndex += 2
34 | end
35 | # Add newly created leaves to this group
36 | group.add_members(newlyAddedLeafIDs, 1)
37 | # Auto-Conjoin newly added leaves in this group
38 | if conjoin
39 | autoConjoinLeaves(newlyAddedLeafs, oddMemberLeftOut)
40 | end
41 | rescue
42 | ensure
43 | if (leafIDs and sideIDs)
44 | Leaf.set_callback(:create, :before, :create_sides)
45 | end
46 | end
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/viscoll-api/app/helpers/validation_helper/project_validation_helper.rb:
--------------------------------------------------------------------------------
1 | module ValidationHelper
2 | module ProjectValidationHelper
3 | def validateProjectCreateGroupsParams(allGroups)
4 | @group_errors = []
5 | if not allGroups
6 | allGroups = []
7 | end
8 | allGroups.each_with_index do |group, index|
9 | haveGroupError = false
10 | @group_error = {groupID: (index+1)}
11 | @group_error[:leaves] = []
12 | @group_error[:oddLeaf] = []
13 | @group_error[:conjoin] = []
14 | leaves = group["leaves"]
15 | oddLeaf = group["oddLeaf"]
16 | conjoin = group["conjoin"]
17 | if (!leaves.is_a?(Integer))
18 | @group_error[:leaves].push("should be an Integer")
19 | haveGroupError = true
20 | elsif (leaves < 1)
21 | @group_error[:leaves].push("should be greater than 0")
22 | haveGroupError = true
23 | end
24 | if (leaves.is_a?(Integer) and leaves.odd?)
25 | if (!oddLeaf.is_a?(Integer))
26 | @group_error[:oddLeaf].push("should be an Integer")
27 | haveGroupError = true
28 | else
29 | if (oddLeaf < 1)
30 | @group_error[:oddLeaf].push("should be greater than 0")
31 | haveGroupError = true
32 | end
33 | if (oddLeaf > leaves)
34 | @group_error[:oddLeaf].push("cannot be greater than leaves")
35 | haveGroupError = true
36 | end
37 | end
38 | end
39 | if (!conjoin.is_a?(Boolean))
40 | @group_error[:conjoin].push("should be a Boolean")
41 | haveGroupError = true
42 | end
43 | if (haveGroupError)
44 | @group_errors.push(@group_error)
45 | end
46 | end
47 | return {status: @group_errors.empty?, errors: @group_errors}
48 | end
49 | end
50 | end
--------------------------------------------------------------------------------
/viscoll-api/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | end
3 |
--------------------------------------------------------------------------------
/viscoll-api/app/mailers/account_approval_mailer.rb:
--------------------------------------------------------------------------------
1 | class AccountApprovalMailer < ApplicationMailer
2 | default from: RailsJwtAuth.mailer_sender
3 |
4 | def sendApprovalStatus(user)
5 | @user = User.find(user)
6 | mail(
7 | subject: "VisColl Account Approval",
8 | to: @user.email,
9 | )
10 | end
11 | end
--------------------------------------------------------------------------------
/viscoll-api/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'utlviscoll@library.utoronto.ca'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/viscoll-api/app/mailers/feedback_mailer.rb:
--------------------------------------------------------------------------------
1 | class FeedbackMailer < ApplicationMailer
2 | def sendFeedback(title, message, browserInformation, projectJSONExport, current_user)
3 | @title = title
4 | @message = message
5 | @browserInformation = browserInformation
6 | @projectJSONExport = projectJSONExport
7 | @user = User.find(current_user)
8 | mail(
9 | subject: title,
10 | to:"utlviscoll@library.utoronto.ca",
11 | )
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/viscoll-api/app/mailers/mailer.rb:
--------------------------------------------------------------------------------
1 | if defined?(ActionMailer)
2 | class RailsJwtAuth::Mailer < ApplicationMailer
3 | default from: RailsJwtAuth.mailer_sender
4 |
5 | def confirmation_instructions(user)
6 | @user = user
7 | if RailsJwtAuth.confirmation_url
8 | url, params = RailsJwtAuth.confirmation_url.split('?')
9 | params = params ? params.split('&') : []
10 | params.push("confirmation_token=#{@user.confirmation_token}")
11 |
12 | @confirmation_url = "#{url}?#{params.join('&')}"
13 | else
14 | @confirmation_url = confirmation_url(confirmation_token: @user.confirmation_token)
15 | end
16 | subject = I18n.t('rails_jwt_auth.mailer.confirmation_instructions.subject')
17 | # mail(to: @user.unconfirmed_email || @user.email, subject: subject)
18 | toEmail = "dummy@library.utoronto.ca"
19 | mail(to: toEmail, subject: subject)
20 | end
21 |
22 | def reset_password_instructions(user)
23 | @user = user
24 |
25 | if RailsJwtAuth.reset_password_url
26 | url, params = RailsJwtAuth.reset_password_url.split('?')
27 | params = params ? params.split('&') : []
28 | params.push("reset_password_token=#{@user.reset_password_token}")
29 |
30 | @reset_password_url = "#{url}?#{params.join('&')}"
31 | else
32 | @reset_password_url = password_url(reset_password_token: @user.reset_password_token)
33 | end
34 |
35 | subject = I18n.t('rails_jwt_auth.mailer.reset_password_instructions.subject')
36 | mail(to: @user.email, subject: subject)
37 | end
38 |
39 | def set_password_instructions(user)
40 | @user = user
41 |
42 | if RailsJwtAuth.set_password_url
43 | url, params = RailsJwtAuth.set_password_url.split('?')
44 | params = params ? params.split('&') : []
45 | params.push("reset_password_token=#{@user.reset_password_token}")
46 |
47 | @reset_password_url = "#{url}?#{params.join('&')}"
48 | else
49 | @reset_password_url = password_url(reset_password_token: @user.reset_password_token)
50 | end
51 |
52 | subject = I18n.t('rails_jwt_auth.mailer.set_password_instructions.subject')
53 | mail(to: @user.email, subject: subject)
54 | end
55 | end
56 | end
--------------------------------------------------------------------------------
/viscoll-api/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utlib/VisualCollation/5004b087ad5bf31ece78ad663ba1bc66efcf178d/viscoll-api/app/models/concerns/.keep
--------------------------------------------------------------------------------
/viscoll-api/app/models/group.rb:
--------------------------------------------------------------------------------
1 | class Group
2 | include Mongoid::Document
3 | include Mongoid::Timestamps
4 |
5 | # Fields
6 | field :title, type: String, default: "None"
7 | field :type, type: String, default: "Quire"
8 | field :direction, type: String
9 | field :tacketed, type: Array, default: []
10 | field :sewing, type: Array, default: []
11 | field :nestLevel, type: Integer, default: 1
12 | field :parentID, type: String
13 | field :memberIDs, type: Array, default: [] # eg [ id1, id2, ... ]
14 |
15 | # Relations
16 | belongs_to :project
17 | has_and_belongs_to_many :notes, inverse_of: nil
18 |
19 | # Callbacks
20 | before_create :edit_ID
21 | before_destroy :unlink_notes, :unlink_project, :unlink_group, :destroy_members
22 |
23 |
24 | def edit_ID
25 | self.id = "Group_"+self.id.to_s unless self.id.to_s[0] == "G"
26 | end
27 |
28 | # Add new members to this group
29 | def add_members(memberIDs, startOrder, save=true)
30 | if self.memberIDs.length==0
31 | self.memberIDs = memberIDs
32 | elsif
33 | self.memberIDs.insert(startOrder-1, *memberIDs)
34 | end
35 | if save
36 | self.save
37 | end
38 | return self
39 | end
40 |
41 | def remove_members(ids)
42 | newList = self.memberIDs.reject{|id| ids.include?(id)}
43 | self.memberIDs = newList
44 | self.save
45 | end
46 |
47 | # If linked to note(s), remove link from the note(s)'s side
48 | def unlink_notes
49 | if self.notes
50 | self.notes.each do | note |
51 | note.objects[:Group].delete(self.id.to_s)
52 | note.save
53 | end
54 | end
55 | end
56 |
57 | # Remove itself from project
58 | def unlink_project
59 | self.project.remove_groupID(self.id.to_s)
60 | end
61 |
62 | # Remove itself from parent group (if nested)
63 | def unlink_group
64 | if self.parentID != nil
65 | Group.find(self.parentID).remove_members([self.id.to_s])
66 | end
67 | end
68 |
69 | def destroy_members
70 | self.memberIDs.each do | memberID |
71 | if memberID[0] === "G"
72 | Group.find(memberID).destroy
73 | elsif memberID[0] === "L"
74 | Leaf.find(memberID).destroy
75 | end
76 | end
77 | end
78 |
79 | end
80 |
--------------------------------------------------------------------------------
/viscoll-api/app/models/image.rb:
--------------------------------------------------------------------------------
1 | class Image
2 | include Mongoid::Document
3 |
4 | # Fields
5 | field :filename, type: String
6 | field :fileID, type: String
7 | field :metadata, type: Hash
8 | field :projectIDs, type: Array, default: [] # List of projectIDs this image belongs to
9 | field :sideIDs, type: Array, default: [] # List of sideIDs this image is mapped to
10 |
11 | # Relations
12 | belongs_to :user, inverse_of: :images
13 |
14 | # Callbacks
15 | before_destroy :unlink_sides_before_delete, :delete_file
16 | validates_uniqueness_of :filename, :message => "Image with filename: '%{value}', already exists.", scope: :user
17 |
18 | protected
19 | # If linked to side(s), remove link from the side(s)
20 | def unlink_sides_before_delete
21 | self.sideIDs.each do |sideID|
22 | if side = Side.where(:id => sideID).first
23 | side.image = {}
24 | side.save
25 | end
26 | end
27 | end
28 |
29 | def delete_file
30 | path = "#{Rails.root}/public/uploads/#{self.fileID}"
31 | if File.file?(path)
32 | File.delete(path)
33 | end
34 | end
35 |
36 | end
37 |
--------------------------------------------------------------------------------
/viscoll-api/app/models/note.rb:
--------------------------------------------------------------------------------
1 | class Note
2 | include Mongoid::Document
3 | include Mongoid::Timestamps
4 |
5 | # Fields
6 | field :title, type: String, default: "None"
7 | field :type, type: String, default: ""
8 | field :description, type: String, default: ""
9 | field :objects, type: Hash, default: {Group: [], Leaf: [], Recto: [], Verso: []}
10 | field :show, type: Boolean, default: false
11 |
12 | # Relations
13 | belongs_to :project, inverse_of: :notes
14 |
15 | # Validations
16 | validates_presence_of :title, :message => "Note title is required."
17 | validates_uniqueness_of :title, :message => "Note title should be unique.", scope: :project
18 | validates_presence_of :type, :message => "Note type is required."
19 |
20 | # Callbacks
21 | before_destroy :update_objects_before_delete
22 |
23 | def update_objects_before_delete
24 | self.objects[:Group].each do |groupID|
25 | if group = Group.where(:id => groupID).first
26 | group.notes.delete(self)
27 | group.save
28 | end
29 | end
30 | self.objects[:Leaf].each do |leafID|
31 | if leaf = Leaf.where(:id => leafID).first
32 | leaf.notes.delete(self)
33 | leaf.save
34 | end
35 | end
36 | self.objects[:Recto].each do |sideID|
37 | if side = Side.where(:id => sideID).first
38 | side.notes.delete(self)
39 | side.save
40 | end
41 | end
42 | self.objects[:Verso].each do |sideID|
43 | if side = Side.where(:id => sideID).first
44 | side.notes.delete(self)
45 | side.save
46 | end
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/viscoll-api/app/models/project.rb:
--------------------------------------------------------------------------------
1 | class Project
2 | include Mongoid::Document
3 | include Mongoid::Timestamps
4 |
5 | # Fields
6 | field :title, type: String
7 | field :shelfmark, type: String # (eg) "MS 1754"
8 | field :metadata, type: Hash, default: lambda { { } } # (eg) {date: "19th century"}
9 | field :manifests, type: Hash, default: lambda { { } } # (eg) { "1234556": { id: "123456, url: ""} }
10 | field :noteTypes, type: Array, default: ["Unknown"] # custom notetypes
11 | field :preferences, type: Hash, default: lambda { { :showTips => true } }
12 | field :groupIDs, type: Array, default: []
13 |
14 | # Relations
15 | belongs_to :user, inverse_of: :projects
16 | has_many :groups, dependent: :delete
17 | has_many :leafs, dependent: :delete
18 | has_many :sides, dependent: :delete
19 | has_many :notes, dependent: :delete
20 |
21 | # Callbacks
22 | before_destroy :unlink_images_before_delete
23 |
24 | # Validations
25 | validates_presence_of :title, :message => "Project title is required."
26 | validates_uniqueness_of :title, :message => "Project title: '%{value}', must be unique.", scope: :user
27 |
28 | def add_groupIDs(groupIDs, index)
29 | if self.groupIDs.length == 0
30 | self.groupIDs = groupIDs
31 | else
32 | self.groupIDs.insert(index, *groupIDs)
33 | end
34 | self.save()
35 | end
36 |
37 | def remove_groupID(groupID)
38 | self.groupIDs.delete(groupID)
39 | self.save()
40 | end
41 |
42 | def unlink_images_before_delete
43 | Image.where(:user_id => self.user.id).each do |image|
44 | # Unlink All Sides that belongs to this Project that has this Image mapped to it.
45 | image.sideIDs.each do |sideID|
46 | side = self.sides.where(:id => sideID).first
47 | if side
48 | side.image = {}
49 | side.save
50 | image.sideIDs.include?(sideID) ? image.sideIDs.delete(sideID) : nil
51 | end
52 | end
53 | image.projectIDs.include?(self.id.to_s) ? image.projectIDs.delete(self.id.to_s) : nil
54 | image.save
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/viscoll-api/app/models/side.rb:
--------------------------------------------------------------------------------
1 | class Side
2 | include Mongoid::Document
3 | include Mongoid::Timestamps
4 |
5 | # Fields
6 | field :folio_number, type: String, default: nil
7 | field :page_number, type: String, default: nil
8 | field :texture, type: String, default: "None"
9 | field :script_direction, type: String, default: "None"
10 | field :image, type: Hash, default: lambda { { } } # {manifestID: 123, label: "bla, " url: "https://iiif.library.utoronto.ca/image/v2/hollar:Hollar_a_3002_0001"}
11 | field :parentID, type: String
12 |
13 | # Relations
14 | belongs_to :project
15 | has_and_belongs_to_many :notes, inverse_of: nil
16 |
17 | # Callbacks
18 | before_destroy :unlink_notes, :unlink_image
19 |
20 | protected
21 | # If linked to note(s), remove link from the note(s)'s side
22 | def unlink_notes
23 | self.notes.each do | note |
24 | note.objects[:Recto].delete(self.id.to_s)
25 | note.objects[:Verso].delete(self.id.to_s)
26 | note.save
27 | end
28 | end
29 |
30 | # If linked to image, remove link from the image's sides list
31 | def unlink_image
32 | if not self.image.empty?
33 | if (image = Image.where(:id => self.image[:url].split("/")[-1].split("_", 2)[0]).first)
34 | image.sideIDs.delete(self.id.to_s)
35 | image.save
36 | end
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/viscoll-api/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User
2 | include Mongoid::Document
3 | include RailsJwtAuth::Authenticatable
4 | include RailsJwtAuth::Confirmable
5 | include RailsJwtAuth::Recoverable
6 | include RailsJwtAuth::Trackable
7 |
8 | field :name, type: String, default: ""
9 |
10 | has_many :images, dependent: :destroy
11 | has_many :projects, dependent: :destroy
12 |
13 | end
14 |
--------------------------------------------------------------------------------
/viscoll-api/app/views/account_approval_mailer/sendApprovalStatus.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
8 |
Hi <%= @user.name %>,
9 |
Congratulations! Your request to join VisCodex has been successsfully approved.
10 |
You can now log in with the credentials that you used to register.
11 |
12 |
--------------------------------------------------------------------------------
/viscoll-api/app/views/exports/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.set! 'Export' do
2 | json.project @data[:project]
3 | json.Groups @data[:groups]
4 | json.Leafs @data[:leafs]
5 | json.Rectos @data[:rectos]
6 | json.Versos @data[:versos]
7 | json.Notes @data[:notes]
8 | end
9 |
10 | json.set! 'Images' do
11 | if @zipFilePath
12 | json.exportedImages @zipFilePath
13 | else
14 | json.exportedImages ""
15 | end
16 | end
--------------------------------------------------------------------------------
/viscoll-api/app/views/feedback_mailer/sendFeedback.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
8 |
Feedback from: <%= @user.email %>
9 |
Message: <%= @message %>
10 |
11 |
Browser Information:
12 | <%= @browserInformation %>
13 |
14 |
15 | <% if @projectJSONExport!=nil %>
16 |
Project JSON Export:
17 | <%= @projectJSONExport %>
18 |
19 | <% end %>
20 |
21 |
--------------------------------------------------------------------------------
/viscoll-api/app/views/filter/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.Groups @groups
2 | json.Leafs @leafs
3 | json.Sides @sides
4 | json.Notes @notes
5 | json.GroupsOfMatchingLeafs @groupsOfMatchingLeafs
6 | json.LeafsOfMatchingSides @leafsOfMatchingSides
7 | json.GroupsOfMatchingSides @groupsOfMatchingSides
8 | json.GroupsOfMatchingNotes @groupsOfMatchingNotes
9 | json.LeafsOfMatchingNotes @leafsOfMatchingNotes
10 | json.SidesOfMatchingNotes @sidesOfMatchingNotes
11 | json.visibleAttributes @visibleAttributes
12 |
--------------------------------------------------------------------------------
/viscoll-api/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/viscoll-api/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/viscoll-api/app/views/projects/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.set! "projects" do
2 | json.array!(@projects.desc(:updated_at)) do | project |
3 | json.extract! project, :id, :title, :shelfmark, :metadata, :created_at, :updated_at
4 | end
5 | end
6 |
7 | json.set! "images" do
8 | json.array!(@images) do | image |
9 | json.extract! image, :id, :projectIDs, :sideIDs
10 | json.url @base_api_url+"/images/"+image.id.to_s+"_"+image.filename
11 | json.label image.filename
12 | end
13 | end
--------------------------------------------------------------------------------
/viscoll-api/app/views/projects/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.set! "active" do
2 | json.id @data[:project][:id]
3 | json.title @data[:project][:title]
4 | json.shelfmark @data[:project][:shelfmark]
5 | json.metadata @data[:project][:metadata]
6 | json.preferences @data[:project][:preferences]
7 | json.noteTypes @data[:project][:noteTypes]
8 |
9 | json.set! "manifests" do
10 | json.set! "DIYImages" do
11 | json.id "DIYImages"
12 | json.images @diyImages
13 | json.name "Uploaded Images"
14 | end
15 | json.merge! @data[:project][:manifests]
16 | end
17 |
18 | json.groupIDs @data[:groupIDs]
19 | json.leafIDs @data[:leafIDs]
20 | json.rectoIDs @data[:rectoIDs]
21 | json.versoIDs @data[:versoIDs]
22 |
23 | json.Groups @data[:groups]
24 | json.Leafs @data[:leafs]
25 | json.Rectos @data[:rectos]
26 | json.Versos @data[:versos]
27 | json.Notes @data[:notes]
28 | end
29 |
30 | json.set! "dashboard" do
31 | json.set! "projects" do
32 | json.array!(@projects.desc(:updated_at)) do | project |
33 | json.extract! project, :id, :title, :shelfmark, :metadata, :created_at, :updated_at
34 | end
35 | end
36 |
37 | json.set! "images" do
38 | json.array!(@images) do | image |
39 | json.extract! image, :id, :projectIDs, :sideIDs
40 | json.url @base_api_url+"/images/"+image.id.to_s+"_"+image.filename
41 | json.label image.filename
42 | end
43 | end
44 | end
--------------------------------------------------------------------------------
/viscoll-api/app/views/rails_jwt_auth/mailer/confirmation_instructions.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
8 |
Hello Admin,
9 |
The following user has requested to join VisColl. You can confirm the account through the link below.
10 |
Once successfully confirmed, the user will be notified by email.
11 |
12 |
13 |
14 |
Name: <%= @user.name %>
15 |
Email: <%= @user.email %>
16 |
17 |
<%= link_to 'Confirm Account', @confirmation_url.html_safe, {:style => 'color: #4ED6CB'} %>
18 |
19 |
--------------------------------------------------------------------------------
/viscoll-api/app/views/rails_jwt_auth/mailer/reset_password_instructions.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
Hi <%= @user.name %>!
8 |
Someone has requested a link to change your password. You can do this through the link below.
9 |
10 |
<%= link_to 'Change my password', @reset_password_url.html_safe, {:style => 'color: #4ED6CB'} %>
11 |
12 |
If you didn't request this, please ignore this email.
13 |
Your password won't change until you access the link above and create a new one.
14 |
15 |
16 |
--------------------------------------------------------------------------------
/viscoll-api/app/views/rails_jwt_auth/mailer/set_password_instructions.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
Hi <%= @user.name %>!
8 |
9 |
You need to define your password to complete registration. You can do this through the link below.
10 |
11 |
<%= link_to 'Set my password', @reset_password_url.html_safe, {:style => 'color: #4ED6CB'} %>
12 |
13 |
--------------------------------------------------------------------------------
/viscoll-api/app/views/sessions/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.session do
2 | json.jwt @userToken
3 | json.id @user.id
4 | json.email @user.email
5 | json.name @user.name
6 | json.lastLoggedIn @user.last_sign_in_at
7 |
8 | json.projects(@userProjects) do | project |
9 | json.id project.id
10 | json.merge! project.attributes.except("_id", "user_id")
11 | end
12 |
13 | end
14 |
--------------------------------------------------------------------------------
/viscoll-api/app/views/users/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! @user, :id, :name, :email
2 | json.projects(@user.projects) do | project |
3 | json.id project.id
4 | json.merge! project.attributes.except("_id", "user_id")
5 | end
--------------------------------------------------------------------------------
/viscoll-api/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/viscoll-api/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | APP_PATH = File.expand_path('../config/application', __dir__)
8 | require_relative '../config/boot'
9 | require 'rails/commands'
10 |
--------------------------------------------------------------------------------
/viscoll-api/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | require_relative '../config/boot'
8 | require 'rake'
9 | Rake.application.run
10 |
--------------------------------------------------------------------------------
/viscoll-api/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a starting point to setup your application.
15 | # Add necessary setup steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | # puts "\n== Copying sample files =="
22 | # unless File.exist?('config/database.yml')
23 | # cp 'config/database.yml.sample', 'config/database.yml'
24 | # end
25 |
26 | puts "\n== Preparing database =="
27 | system! 'bin/rails db:setup'
28 |
29 | puts "\n== Removing old logs and tempfiles =="
30 | system! 'bin/rails log:clear tmp:clear'
31 |
32 | puts "\n== Restarting application server =="
33 | system! 'bin/rails restart'
34 | end
35 |
--------------------------------------------------------------------------------
/viscoll-api/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # This file loads spring without using Bundler, in order to be fast.
4 | # It gets overwritten when you run the `spring binstub` command.
5 |
6 | unless defined?(Spring)
7 | require 'rubygems'
8 | require 'bundler'
9 |
10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" }
12 | if spring
13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
14 | gem 'spring', spring.version
15 | require 'spring/binstub'
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/viscoll-api/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a way to update your development environment automatically.
15 | # Add necessary update steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | puts "\n== Updating database =="
22 | system! 'bin/rails db:migrate'
23 |
24 | puts "\n== Removing old logs and tempfiles =="
25 | system! 'bin/rails log:clear tmp:clear'
26 |
27 | puts "\n== Restarting application server =="
28 | system! 'bin/rails restart'
29 | end
30 |
--------------------------------------------------------------------------------
/viscoll-api/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative 'config/environment'
4 |
5 | run Rails.application
6 |
--------------------------------------------------------------------------------
/viscoll-api/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative 'boot'
2 | require_relative 'shrine'
3 |
4 | require "rails"
5 | # Pick the frameworks you want:
6 | require "active_model/railtie"
7 | require "active_job/railtie"
8 | # require "active_record/railtie"
9 | require "action_controller/railtie"
10 | require "action_mailer/railtie"
11 | require "action_view/railtie"
12 | require "action_cable/engine"
13 | # require "sprockets/railtie"
14 | # require "rails/test_unit/railtie"
15 |
16 | # Require the gems listed in Gemfile, including any gems
17 | # you've limited to :test, :development, or :production.
18 | Bundler.require(*Rails.groups)
19 |
20 | module ViscollApi
21 | class Application < Rails::Application
22 | # Settings in config/environments/* take precedence over those specified here.
23 | # Application configuration should go into files in config/initializers
24 | # -- all .rb files in that directory are automatically loaded.
25 |
26 | # Only loads a smaller set of middleware suitable for API only apps.
27 | # Middleware like session, flash, cookies can be added back manually.
28 | # Skip views, helpers and assets when generating a new resource.
29 | config.api_only = true
30 |
31 | Mongo::Logger.logger.level = Logger::FATAL
32 | config.log_level = :warn
33 |
34 | # Rack CORS for handling Cross-Origin Resource Sharing (CORS)
35 | config.middleware.use Rack::Cors do
36 | allow do
37 | origins '*'
38 | resource '*',
39 | :headers => :any,
40 | :expose => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
41 | :methods => [:get, :patch, :put, :delete, :post, :options]
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/viscoll-api/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
2 |
3 | require 'bundler/setup' # Set up gems listed in the Gemfile.
4 |
--------------------------------------------------------------------------------
/viscoll-api/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: redis
9 | url: redis://localhost:6379/1
10 |
--------------------------------------------------------------------------------
/viscoll-api/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative 'application'
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/viscoll-api/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports.
13 | config.consider_all_requests_local = true
14 |
15 | # Enable/disable caching. By default caching is disabled.
16 | if Rails.root.join('tmp/caching-dev.txt').exist?
17 | config.action_controller.perform_caching = true
18 |
19 | config.cache_store = :memory_store
20 | config.public_file_server.headers = {
21 | 'Cache-Control' => 'public, max-age=172800'
22 | }
23 | else
24 | config.action_controller.perform_caching = false
25 |
26 | config.cache_store = :null_store
27 | end
28 |
29 | # Don't care if the mailer can't send.
30 | config.action_mailer.raise_delivery_errors = false
31 |
32 | config.action_mailer.perform_caching = false
33 | config.action_mailer.delivery_method = :smtp
34 | # config.action_mailer.default_url_options = { :host => "localhost", :port => 3000 }
35 | config.action_mailer.smtp_settings = {
36 | :address => 'smtp.ethereal.email',
37 | :port => 587,
38 | :user_name => 'gonzalo.hahn@ethereal.email',
39 | :password => 'yK6He1aP38xCrEEarn'
40 | }
41 |
42 | # Print deprecation notices to the Rails logger.
43 | config.active_support.deprecation = :log
44 |
45 |
46 | # Raises error for missing translations
47 | # config.action_view.raise_on_missing_translations = true
48 |
49 | # Use an evented file watcher to asynchronously detect changes in source code,
50 | # routes, locales, etc. This feature depends on the listen gem.
51 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
52 |
53 | config.middleware.insert_before 0, Rack::Cors do
54 | allow do
55 | origins '*'
56 | resource '*', :headers => :any, :methods => [:get, :post, :put, :patch, :options, :delete]
57 | end
58 | end
59 |
60 | end
61 |
--------------------------------------------------------------------------------
/viscoll-api/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure public file server for tests with Cache-Control for performance.
16 | config.public_file_server.enabled = true
17 | config.public_file_server.headers = {
18 | 'Cache-Control' => 'public, max-age=3600'
19 | }
20 |
21 | # Show full error reports and disable caching.
22 | config.consider_all_requests_local = true
23 | config.action_controller.perform_caching = false
24 |
25 | # Raise exceptions instead of rendering exception templates.
26 | config.action_dispatch.show_exceptions = false
27 |
28 | # Disable request forgery protection in test environment.
29 | config.action_controller.allow_forgery_protection = false
30 | config.action_mailer.perform_caching = false
31 |
32 | # Tell Action Mailer not to deliver emails to the real world.
33 | # The :test delivery method accumulates sent emails in the
34 | # ActionMailer::Base.deliveries array.
35 | config.action_mailer.delivery_method = :test
36 | config.action_mailer.default_url_options = { :host => "localhost", :port => 3000 }
37 |
38 | # Print deprecation notices to the stderr.
39 | config.active_support.deprecation = :stderr
40 |
41 | # Raises error for missing translations
42 | # config.action_view.raise_on_missing_translations = true
43 | end
44 |
--------------------------------------------------------------------------------
/viscoll-api/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ApplicationController.renderer.defaults.merge!(
4 | # http_host: 'example.org',
5 | # https: false
6 | # )
7 |
--------------------------------------------------------------------------------
/viscoll-api/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/viscoll-api/config/initializers/cors.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Avoid CORS issues when API is called from the frontend app.
4 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.
5 |
6 | # Read more: https://github.com/cyu/rack-cors
7 |
8 | # Rails.application.config.middleware.insert_before 0, Rack::Cors do
9 | # allow do
10 | # origins 'example.com'
11 | #
12 | # resource '*',
13 | # headers: :any,
14 | # methods: [:get, :post, :put, :patch, :delete, :options, :head]
15 | # end
16 | # end
17 |
--------------------------------------------------------------------------------
/viscoll-api/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/viscoll-api/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/viscoll-api/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/viscoll-api/config/initializers/mongoid.rb:
--------------------------------------------------------------------------------
1 | module BSON
2 | class ObjectId
3 | def to_json(*args)
4 | to_s.to_json
5 | end
6 |
7 | def as_json(*args)
8 | to_s.as_json
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/viscoll-api/config/initializers/new_framework_defaults.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 | #
3 | # This file contains migration options to ease your Rails 5.0 upgrade.
4 | #
5 | # Read the Guide for Upgrading Ruby on Rails for more info on each option.
6 |
7 | Rails.application.config.raise_on_unfiltered_parameters = true
8 |
9 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`.
10 | # Previous versions had false.
11 | ActiveSupport.to_time_preserves_timezone = true
12 |
13 | # Do not halt callback chains when a callback returns false. Previous versions had true.
14 | # DEPRECATION WARNING: ActiveSupport.halt_callback_chains_on_return_false= is deprecated
15 | # and will be removed in Rails 5.2.
16 | # ActiveSupport.halt_callback_chains_on_return_false = false
17 |
18 | # Configure SSL options to enable HSTS with subdomains. Previous versions had false.
19 | Rails.application.config.ssl_options = { hsts: { subdomains: true } }
20 |
--------------------------------------------------------------------------------
/viscoll-api/config/initializers/rails_jwt_auth.rb:
--------------------------------------------------------------------------------
1 | RailsJwtAuth.setup do |config|
2 | # authentication model class name
3 | #config.model_name = 'User'
4 |
5 | # field name used to authentication with password
6 | #config.auth_field_name = 'email'
7 |
8 | # set to true to validate auth_field email format
9 | #config.auth_field_email = true
10 |
11 | # expiration time for generated tokens
12 | #config.jwt_expiration_time = 7.days
13 |
14 | # the "iss" (issuer) claim identifies the principal that issued the JWT
15 | #config.jwt_issuer = 'RailsJwtAuth'
16 |
17 | # number of simultaneously sessions for an user
18 | #config.simultaneously_sessions = 3
19 |
20 | # mailer sender
21 | config.mailer_sender = 'noreply-dummy@library.utoronto.ca'
22 |
23 | # url used to create email link with confirmation token
24 | config.confirmation_url = if Rails.env.production? then 'https://dummy.library.utoronto.ca/confirmation' else 'http://127.0.0.1:3000/confirmation' end
25 |
26 | # expiration time for confirmation tokens
27 | #config.confirmation_expiration_time = 1.day
28 |
29 | # url used to create email link with reset password token
30 | config.reset_password_url = if Rails.env.production? then 'https://dummy.library.utoronto.ca/password' else 'http://127.0.0.1:3000/password' end
31 |
32 |
33 | # expiration time for reset password tokens
34 | #config.reset_password_expiration_time = 1.day
35 |
36 | # uses deliver_later to send emails instead of deliver method
37 | #config.deliver_later = false
38 | end
39 |
--------------------------------------------------------------------------------
/viscoll-api/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
--------------------------------------------------------------------------------
/viscoll-api/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/viscoll-api/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum, this matches the default thread size of Active Record.
6 | #
7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
8 | threads threads_count, threads_count
9 |
10 | # Specifies the `port` that Puma will listen on to receive requests, default is 3000.
11 | #
12 | port ENV.fetch("PORT") { 3000 }
13 |
14 | # Specifies the `environment` that Puma will run in.
15 | #
16 | environment ENV.fetch("RAILS_ENV") { "development" }
17 |
18 | # Specifies the number of `workers` to boot in clustered mode.
19 | # Workers are forked webserver processes. If using threads and workers together
20 | # the concurrency of the application would be max `threads` * `workers`.
21 | # Workers do not work on JRuby or Windows (both of which do not support
22 | # processes).
23 | #
24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
25 |
26 | # Use the `preload_app!` method when specifying a `workers` number.
27 | # This directive tells Puma to first boot the application and load code
28 | # before forking the application. This takes advantage of Copy On Write
29 | # process behavior so workers use less memory. If you use this option
30 | # you need to make sure to reconnect any threads in the `on_worker_boot`
31 | # block.
32 | #
33 | # preload_app!
34 |
35 | # The code in the `on_worker_boot` will be called if you are using
36 | # clustered mode by specifying a number of `workers`. After each worker
37 | # process is booted this block will be run, if you are using `preload_app!`
38 | # option you will want to use this block to reconnect to any threads
39 | # or connections that may have been created at application boot, Ruby
40 | # cannot share connections between processes.
41 | #
42 | # on_worker_boot do
43 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
44 | # end
45 |
46 | # Allow puma to be restarted by `rails restart` command.
47 | plugin :tmp_restart
48 |
--------------------------------------------------------------------------------
/viscoll-api/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rails secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development:
14 | secret_key_base: 98eb3bcbe406c141ad93c58e5f5ff08ab7348c82d688b78ee1fb1a30559d7104081e0dd9bf97c8e080a54f1d408f7f9d22710439c44cbbddc332861994b1c531
15 | admin_email: 'smtp://localhost:1025'
16 | api_url: 'http://localhost:3001'
17 |
18 | test:
19 | secret_key_base: a8986cd44e89b6547fe0c8ebd320706dc2dbd6aa617e42c5625b9420fa8ab8347beeedb0690138e178e79583b0f7c71b45fac99409d96653030c6a94c14d9d9d
20 |
21 | # Do not keep production secrets in the repository,
22 | # instead read values from the environment.
23 | production:
24 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
25 |
--------------------------------------------------------------------------------
/viscoll-api/config/shrine.rb:
--------------------------------------------------------------------------------
1 | require "shrine"
2 | require "shrine/storage/file_system"
3 | Shrine.storages = {
4 | cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), # temporary
5 | store: Shrine::Storage::FileSystem.new("public", prefix: "uploads"), # permanent
6 | }
7 | Shrine.plugin :data_uri
8 |
--------------------------------------------------------------------------------
/viscoll-api/config/spring.rb:
--------------------------------------------------------------------------------
1 | %w(
2 | .ruby-version
3 | .rbenv-vars
4 | tmp/restart.txt
5 | tmp/caching-dev.txt
6 | ).each { |path| Spring.watch(path) }
7 |
--------------------------------------------------------------------------------
/viscoll-api/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
7 | # Character.create(name: 'Luke', movie: movies.first)
8 |
--------------------------------------------------------------------------------
/viscoll-api/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utlib/VisualCollation/5004b087ad5bf31ece78ad663ba1bc66efcf178d/viscoll-api/lib/tasks/.keep
--------------------------------------------------------------------------------
/viscoll-api/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utlib/VisualCollation/5004b087ad5bf31ece78ad663ba1bc66efcf178d/viscoll-api/log/.keep
--------------------------------------------------------------------------------
/viscoll-api/public/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | API Docs
7 |
8 |
9 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/viscoll-api/spec/factories/groups.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 | sequence :quire_title do |n|
3 | "Quire #{n}"
4 | end
5 | sequence :booklet_title do |n|
6 | "Booklet #{n}"
7 | end
8 | sequence :group_title do |n|
9 | "Group #{n}"
10 | end
11 | factory :group, class: Group do
12 | transient do
13 | members []
14 | end
15 | after(:create) do |group, evaluator|
16 | group.nestLevel ||= 1
17 | unless evaluator.members.blank?
18 | newmembers = evaluator.members.each do |member|
19 | if member.is_a?(Group)
20 | member.nestLevel = group.nestLevel+1
21 | else
22 | member.nestLevel = group.nestLevel
23 | end
24 | member.save
25 | end
26 | group.add_members(newmembers.collect { |member| member.id.to_s }, 1)
27 | end
28 | group.save
29 | end
30 | title { generate(:group_title) }
31 | type "Quire"
32 | end
33 |
34 | factory :quire, class: Group do
35 | transient do
36 | leafs 0
37 | conjoined true
38 | leaf_properties { {} }
39 | start_page 1
40 | end
41 | after(:create) do |group, evaluator|
42 | group.nestLevel ||= 1
43 | unless evaluator.leafs <= 0
44 | newleafprops = evaluator.leaf_properties.merge({
45 | project_id: group.project_id,
46 | parentID: group.id.to_s,
47 | nestLevel: group.nestLevel
48 | })
49 | newleafs = evaluator.leafs.times.collect { |n|
50 | FactoryGirl.build(:leaf, newleafprops.merge({ folio_number: evaluator.start_page+n }))
51 | }
52 | if evaluator.conjoined
53 | evaluator.leafs.times.each do |n|
54 | unless evaluator.leafs.odd? and n == evaluator.leafs >> 1
55 | conjoin_id = newleafs[-1-n].id.to_s
56 | newleafs[n].conjoined_to = if conjoin_id[0..4] == 'Leaf_' then conjoin_id else "Leaf_#{conjoin_id}" end
57 | end
58 | newleafs[n].save
59 | end
60 | end
61 | group.add_members(newleafs.collect { |newleaf| newleaf.id.to_s }, 1)
62 | end
63 | end
64 | title { generate(:quire_title) }
65 | type "Quire"
66 | end
67 |
68 | factory :booklet, parent: :quire do
69 | title { generate(:booklet_title) }
70 | type "Booklet"
71 | leafs 0
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/viscoll-api/spec/factories/images.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 | sequence :image_filename do |n|
3 | "Image #{n}"
4 | end
5 |
6 | sequence :image_fileid do |n|
7 | "#{n}"
8 | end
9 |
10 | sequence :image_original_filename do |n|
11 | "image_#{n}"
12 | end
13 |
14 | factory :image do
15 | filename { generate(:image_filename) }
16 |
17 | factory :pixel do
18 | filename { 'pixel.png'}
19 | fileID { 'pixel'}
20 | metadata { {
21 | "filename": "pixel.png",
22 | "size": 20470,
23 | "mime_type": "image/png"
24 | } }
25 | end
26 |
27 | factory :shiba_inu do
28 | filename { 'shiba_inu.png'}
29 | fileID { 'shiba_inu'}
30 | metadata { {
31 | "filename": "shiba_inu.png",
32 | "size": 20470,
33 | "mime_type": "image/png"
34 | } }
35 | end
36 |
37 | factory :viscoll_logo do
38 | filename { 'viscoll_logo.png'}
39 | fileID { 'viscoll_logo'}
40 | metadata { {
41 | "filename": "viscoll_logo.png",
42 | "size": 20470,
43 | "mime_type": "image/png"
44 | } }
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/viscoll-api/spec/factories/leafs.rb:
--------------------------------------------------------------------------------
1 | include ActionDispatch::TestProcess
2 | FactoryGirl.define do
3 | factory :leaf do
4 | transient {
5 | folio_number nil
6 | }
7 | after(:create) do |leaf, evaluator|
8 | unless evaluator.folio_number.blank?
9 | Side.find(leaf.rectoID).update(folio_number: "#{evaluator.folio_number}R")
10 | Side.find(leaf.versoID).update(folio_number: "#{evaluator.folio_number}V")
11 | end
12 | end
13 | material "Paper"
14 | type "Original"
15 |
16 | factory :parchment do
17 | material "Parchment"
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/viscoll-api/spec/factories/notes.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 | sequence :note_title do |n|
3 | "Note #{n}"
4 | end
5 | sequence :note_text do |n|
6 | "Blah #{n}"
7 | end
8 |
9 | factory :note do
10 | transient do
11 | attachments []
12 | end
13 | before(:build) do |note, evaluator|
14 | myobjects = {Group: [], Leaf: [], Recto: [], Verso: []}
15 | evaluator.attachments.each do |attachment|
16 | if attachment.is_a? Group
17 | myobjects[:Group] << attachment
18 | elsif attachment.is_a? Leaf
19 | myobjects[:Leaf] << attachment
20 | elsif attachment.is_a? Side
21 | if attachment.id.to_s[0..5] == 'Verso_'
22 | myobjects[:Verso] << attachment
23 | else
24 | myobjects[:Recto] << attachment
25 | end
26 | else
27 | raise Exception('Notes can only be attached to groups, leafs and sides')
28 | end
29 | end
30 | end
31 | title { generate(:note_title) }
32 | type "Unknown"
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/viscoll-api/spec/factories/projects.rb:
--------------------------------------------------------------------------------
1 | include ActionDispatch::TestProcess
2 | FactoryGirl.define do
3 | sequence :title do |n|
4 | "Project #{n}"
5 | end
6 | sequence :manifest_id do |n|
7 | "Manifest_#{n}"
8 | end
9 | sequence :manifest_name do |n|
10 | "Manifest #{n}"
11 | end
12 | sequence :manifest_url do |n|
13 | "https://iiif.example.org/#{n}/manifest.json"
14 | end
15 |
16 | factory :empty_project, class: Project do
17 | title { generate(:title) }
18 | user_id { FactoryGirl.create(:user) }
19 | end
20 |
21 | factory :project do
22 | transient do
23 | with_members []
24 | with_manifests []
25 | end
26 | before(:build) do |project, evaluator|
27 | evaluator.with_manifests.each do |manifest|
28 | mid = evaluator.generate(:manifest_id)
29 | manifest[:id] = mid
30 | project.manifests[mid] = manifest
31 | end
32 | end
33 | after(:create) do |project, evaluator|
34 | evaluator.with_members.each do |member|
35 | member.project_id = project.id
36 | member.nestLevel ||= 1
37 | member.save
38 | end
39 | unless evaluator.with_members.blank?
40 | project.add_groupIDs(evaluator.with_members.collect { |member| member.id.to_s }, 1)
41 | end
42 | end
43 | title { generate(:title) }
44 | user_id { FactoryGirl.create(:user) }
45 | end
46 |
47 | factory :codex_project, parent: :project do
48 | transient do
49 | manifest_count 0
50 | quire_structure { [[4, 6]] }
51 | end
52 | before(:build) do |project, evaluator|
53 | evaluator.manifest_count.times do
54 | manifest = FactoryGirl.build(:manifest)
55 | project.manifests[manifest[:id]] = manifest
56 | end
57 | end
58 | after(:create) do |project, evaluator|
59 | start_page = 1
60 | members = []
61 | evaluator.quire_structure.each do |qs|
62 | qs[0].times do
63 | members << FactoryGirl.create(:quire, project_id: project.id, leafs: qs[1], start_page: start_page, nestLevel: 1)
64 | start_page += qs[1]
65 | end
66 | end
67 | unless members.blank?
68 | project.add_groupIDs(members.collect { |member| member.id.to_s }, 1)
69 | end
70 | end
71 | end
72 |
73 | factory :manifest, class: Hash do
74 | id { generate(:manifest_id) }
75 | url { generate(:manifest_url) }
76 | name { generate(:manifest_name) }
77 | initialize_with { attributes }
78 | to_create { }
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/viscoll-api/spec/factories/sides.rb:
--------------------------------------------------------------------------------
1 | include ActionDispatch::TestProcess
2 | FactoryGirl.define do
3 | factory :side do
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/viscoll-api/spec/factories/users.rb:
--------------------------------------------------------------------------------
1 | include ActionDispatch::TestProcess
2 | FactoryGirl.define do
3 | factory :user do
4 | name {Faker::Name.name}
5 | email {Faker::Internet.email}
6 | password {Faker::Internet.password}
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/viscoll-api/spec/fixtures/base64zip.txt:
--------------------------------------------------------------------------------
1 | data:application/zip;base64,UEsDBBQACAAIAMdVrU4AAAAAAAAAAAAAAAAfABAAMVJfNWEyODIyMWVjMTk5ODYwZTdhMmY1ZmQxLnBuZ1VYDADtkdlcNoPZXPcBFADrDPBz5+WS4mJgYOD19HAJAtKMIMzBBiTlRY90giVcHEMqbiX/+T9/oRwDexNTve0Jj/dACQZPVz+XdU4JTQBQSwcIbxz83T8AAABGAAAAUEsDBBQACAAIAMdVrU4AAAAAAAAAAAAAAAAVABAAMlJfMmY1ZmQxc2hpYmFpbnUucG5nVVgMAEGD2Vw2g9lc9wEUAOsM8HPn5ZLiYmBg4PX0cAkC0owgzMEGJOVFj3SCJVwcQypuJf/5P3+hHAN7E1O97QmP90AJBk9XP5d1TglNAFBLBwhvHPzdPwAAAEYAAABQSwMEFAAIAAgAx1WtTgAAAAAAAAAAAAAAAB8AEAAxVl81YTI4MjIxZWMxOTk4NjBlN2EyZjVmZDEucG5nVVgMAEGD2Vw2g9lc9wEUAOsM8HPn5ZLiYmBg4PX0cAkC0owgzMEGJOVFj3SCJVwcQypuJf/5P3+hHAN7E1O97QmP90AJBk9XP5d1TglNAFBLBwhvHPzdPwAAAEYAAABQSwECFQMUAAgACADHVa1Obxz83T8AAABGAAAAHwAMAAAAAAAAAABApIEAAAAAMVJfNWEyODIyMWVjMTk5ODYwZTdhMmY1ZmQxLnBuZ1VYCADtkdlcNoPZXFBLAQIVAxQACAAIAMdVrU5vHPzdPwAAAEYAAAAVAAwAAAAAAAAAAECkgZwAAAAyUl8yZjVmZDFzaGliYWludS5wbmdVWAgAQYPZXDaD2VxQSwECFQMUAAgACADHVa1Obxz83T8AAABGAAAAHwAMAAAAAAAAAABApIEuAQAAMVZfNWEyODIyMWVjMTk5ODYwZTdhMmY1ZmQxLnBuZ1VYCABBg9lcNoPZXFBLBQYAAAAAAwADAAEBAADKAQAAAAA=
--------------------------------------------------------------------------------
/viscoll-api/spec/fixtures/dots_exported.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utlib/VisualCollation/5004b087ad5bf31ece78ad663ba1bc66efcf178d/viscoll-api/spec/fixtures/dots_exported.zip
--------------------------------------------------------------------------------
/viscoll-api/spec/fixtures/pixel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utlib/VisualCollation/5004b087ad5bf31ece78ad663ba1bc66efcf178d/viscoll-api/spec/fixtures/pixel.png
--------------------------------------------------------------------------------
/viscoll-api/spec/fixtures/shibainu.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utlib/VisualCollation/5004b087ad5bf31ece78ad663ba1bc66efcf178d/viscoll-api/spec/fixtures/shibainu.jpg
--------------------------------------------------------------------------------
/viscoll-api/spec/fixtures/viscoll.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utlib/VisualCollation/5004b087ad5bf31ece78ad663ba1bc66efcf178d/viscoll-api/spec/fixtures/viscoll.png
--------------------------------------------------------------------------------
/viscoll-api/spec/helpers/controller_helper/groups_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe ControllerHelper::GroupsHelper, type: :helper do
4 | before :each do
5 | @project = FactoryGirl.create(:project)
6 | @group = FactoryGirl.create(:group, project: @project)
7 | end
8 |
9 | describe 'addLeavesInside' do
10 | it 'adds unconjoined leaves' do
11 | addLeavesInside(@project.id.to_s, @group, 4, false, nil)
12 | expect(@project.leafs.count).to eq 4
13 | expect(@group.memberIDs.count).to eq 4
14 | expect(@project.leafs.all? { |leaf| leaf.conjoined_to.blank? }).to be true
15 | end
16 | it 'adds conjoined leaves' do
17 | addLeavesInside(@project.id.to_s, @group, 4, true, nil)
18 | expect(@project.leafs.count).to eq 4
19 | expect(@group.memberIDs.count).to eq 4
20 | 4.times.each do |i|
21 | expect(@project.leafs[i].conjoined_to).to eq @project.leafs[3-i].id.to_s
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/viscoll-api/spec/helpers/validation_helper/project_validation_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe ValidationHelper::ProjectValidationHelper, type: :helper do
4 | describe "validateProjectCreateGroupsParams" do
5 | before :each do
6 | @params = [
7 | { 'leaves' => 3, 'oddLeaf' => 1, 'conjoin' => false },
8 | { 'leaves' => 6, 'oddLeaf' => 0, 'conjoin' => true }
9 | ]
10 | end
11 |
12 | it 'should allow nil' do
13 | result = validateProjectCreateGroupsParams(nil)
14 | expect(result[:errors]).to be_empty
15 | expect(result[:status]).to be true
16 | end
17 |
18 | it 'should allow the standard params' do
19 | result = validateProjectCreateGroupsParams(@params)
20 | expect(result[:errors]).to be_empty
21 | expect(result[:status]).to be true
22 | end
23 |
24 | describe "Leaf count" do
25 | it 'should be integers only' do
26 | @params[0]['leaves'] = 'waahoo'
27 | result = validateProjectCreateGroupsParams(@params)
28 | expect(result[:errors][0][:leaves]).to include 'should be an Integer'
29 | expect(result[:status]).to be false
30 | end
31 |
32 | it 'should be positive' do
33 | @params[1]['leaves'] = 0
34 | result = validateProjectCreateGroupsParams(@params)
35 | expect(result[:errors][0][:leaves]).to include 'should be greater than 0'
36 | expect(result[:status]).to be false
37 | end
38 | end
39 |
40 | describe "Odd leaf parity" do
41 | it 'should be integers only' do
42 | @params[0]['oddLeaf'] = 'waahoo'
43 | result = validateProjectCreateGroupsParams(@params)
44 | expect(result[:errors][0][:oddLeaf]).to include 'should be an Integer'
45 | expect(result[:status]).to be false
46 | end
47 |
48 | it 'should be positive' do
49 | @params[0]['oddLeaf'] = 0
50 | result = validateProjectCreateGroupsParams(@params)
51 | expect(result[:errors][0][:oddLeaf]).to include 'should be greater than 0'
52 | expect(result[:status]).to be false
53 | end
54 |
55 | it 'should not be greater than leaves' do
56 | @params[0]['oddLeaf'] = 7
57 | result = validateProjectCreateGroupsParams(@params)
58 | expect(result[:errors][0][:oddLeaf]).to include 'cannot be greater than leaves'
59 | expect(result[:status]).to be false
60 | end
61 | end
62 |
63 | describe "Conjoin" do
64 | it 'should be Boolean' do
65 | @params[1]['conjoin'] = 'waahoo'
66 | result = validateProjectCreateGroupsParams(@params)
67 | expect(result[:errors][0][:conjoin]).to include 'should be a Boolean'
68 | expect(result[:status]).to be false
69 | end
70 | end
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/viscoll-api/spec/mailers/feedback_spec.rb:
--------------------------------------------------------------------------------
1 | require "rails_helper"
2 |
3 | RSpec.describe FeedbackMailer, type: :mailer do
4 | context 'user submits a feedback' do
5 | before do
6 | @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
7 | end
8 |
9 | let(:mail) { FeedbackMailer.sendFeedback("Title of feedback", "My message", nil, nil, @user.id)}
10 |
11 | it "should send email" do
12 | expect(mail.subject).to eq("Title of feedback")
13 | expect(mail.to).to eq(["utlviscoll@library.utoronto.ca"])
14 | end
15 |
16 | it "should render body" do
17 | expect(mail.body.raw_source).to include("My message")
18 | expect(mail.body.raw_source).to include(@user.name)
19 | expect(mail.body.raw_source).to include(@user.email)
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/viscoll-api/spec/mailers/previews/feedback_preview.rb:
--------------------------------------------------------------------------------
1 | # Preview all emails at http://localhost:3000/rails/mailers/feedback
2 | class FeedbackPreview < ActionMailer::Preview
3 |
4 | end
5 |
--------------------------------------------------------------------------------
/viscoll-api/spec/models/grouping_spec.rb:
--------------------------------------------------------------------------------
1 | # require 'rails_helper'
2 |
3 | # RSpec.describe Grouping, type: :model do
4 | # it { is_expected.to be_mongoid_document }
5 | # it { is_expected.to have_field(:order).of_type(Integer) }
6 | # it { is_expected.to belong_to(:group).with_foreign_key(:group_id) }
7 | # it { is_expected.to belong_to(:member).with_foreign_key(:member_id) }
8 |
9 | # before(:each) do
10 | # @project = FactoryGirl.create(:project)
11 | # @leaf = FactoryGirl.create(:leaf, project: @project)
12 | # @group = FactoryGirl.create(:quire)
13 | # end
14 |
15 | # it "can delete a member" do
16 | # @group.add_member(@leaf, 1)
17 | # @leaf.destroy
18 | # expect(@group.get_members.size).to eq 0
19 | # end
20 |
21 | # end
--------------------------------------------------------------------------------
/viscoll-api/spec/models/image_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Image, type: :model do
4 | it { is_expected.to be_mongoid_document }
5 |
6 | it { is_expected.to have_field(:filename).of_type(String) }
7 | it { is_expected.to have_field(:projectIDs).of_type(Array) }
8 | it { is_expected.to have_field(:sideIDs).of_type(Array) }
9 |
10 | it { is_expected.to belong_to(:user) }
11 |
12 | before(:each) do
13 | @user = FactoryGirl.create(:user)
14 | @project = FactoryGirl.create(:codex_project, user: @user, quire_structure: [[1, 2]])
15 | @image = FactoryGirl.create(:image, user: @user)
16 | end
17 |
18 | describe 'Validations' do
19 | it 'should be valid to start with' do
20 | expect(@image).to be_valid
21 | end
22 |
23 | it 'should not be valid with a duplicate file name' do
24 | duplicate_image = FactoryGirl.build(:image, user: @user, filename: @image.filename)
25 | expect(duplicate_image).not_to be_valid
26 | end
27 | end
28 |
29 | describe 'Side unlinking hook' do
30 | before do
31 | @side = @project.sides[1]
32 | @side.update(image: {
33 | manifestID: 'DIYImages',
34 | label: 'hello.png',
35 | url: 'http://127.0.0.1:3001/pixel.png'
36 | })
37 | @image.update(sideIDs: [@side.id.to_s])
38 | end
39 |
40 | it 'should unhook the side upon deletion' do
41 | @image.destroy
42 | @side.reload
43 | expect(@side.image).to be_blank
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/viscoll-api/spec/models/leaf_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Leaf, type: :model do
4 | it { is_expected.to be_mongoid_document }
5 |
6 | it { is_expected.to have_field(:material).of_type(String) }
7 | it { is_expected.to have_field(:type).of_type(String) }
8 | it { is_expected.to have_field(:conjoined_to).of_type(String) }
9 | it { is_expected.to have_field(:attached_above).of_type(String) }
10 | it { is_expected.to have_field(:attached_below).of_type(String) }
11 | it { is_expected.to have_field(:stubType).of_type(String) }
12 | it { is_expected.to have_field(:parentID).of_type(String) }
13 | it { is_expected.to have_field(:nestLevel).of_type(Integer) }
14 | it { is_expected.to have_field(:rectoID).of_type(String) }
15 | it { is_expected.to have_field(:versoID).of_type(String) }
16 |
17 | it { is_expected.to belong_to(:project) }
18 | it { is_expected.to have_and_belong_to_many(:notes) }
19 |
20 | before(:each) do
21 | @project = FactoryGirl.create(:project)
22 | @leaf = FactoryGirl.create(:leaf, project: @project)
23 | @group = FactoryGirl.create(:group, project: @project)
24 | @group.add_members([@leaf.id.to_s], 0)
25 | @leaf.parentID = @group.id
26 | @leaf.save
27 | end
28 |
29 | describe "Initialization" do
30 | it "should have a prefixed ID" do
31 | expect(@leaf.id.to_s[0..4]).to eq "Leaf_"
32 | end
33 |
34 | it "should add two sides" do
35 | expect(Side.where(id: @leaf.rectoID).exists?).to be true
36 | expect(Side.where(id: @leaf.versoID).exists?).to be true
37 | end
38 | end
39 |
40 | it "should be able to unlink itself from a group" do
41 | @group = FactoryGirl.create(:group, project: @project)
42 | @group.add_members([@leaf.id.to_s], 0)
43 | @leaf.parentID = @group.id
44 | @leaf.save
45 | expect(@group.memberIDs).to include(@leaf.id.to_s)
46 | @leaf.remove_from_group
47 | @group.reload
48 | expect(@group.memberIDs).not_to include(@leaf.id.to_s)
49 | end
50 |
51 | describe "Destruction" do
52 | it "should unlink its notes" do
53 | subnote = FactoryGirl.create(:note, project: @project, objects: {Group: [], Leaf: [@leaf.id], Recto: [], Verso: []})
54 | @leaf.notes << subnote
55 | @leaf.save
56 | @leaf.destroy
57 | expect(subnote.objects[:Leaf]).to be_empty
58 | end
59 |
60 | it "should destroy its sides" do
61 | rectoId = @leaf.rectoID
62 | versoId = @leaf.versoID
63 | @leaf.destroy
64 | expect(Side.where(id: rectoId).exists?).to be false
65 | expect(Side.where(id: versoId).exists?).to be false
66 | end
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/viscoll-api/spec/models/side_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Side, type: :model do
4 | it { is_expected.to be_mongoid_document }
5 |
6 | it { is_expected.to have_field(:folio_number).of_type(String) }
7 | it { is_expected.to have_field(:texture).of_type(String) }
8 | it { is_expected.to have_field(:script_direction).of_type(String) }
9 | it { is_expected.to have_field(:image).of_type(Hash) }
10 | it { is_expected.to have_field(:parentID).of_type(String) }
11 |
12 | it { is_expected.to belong_to(:project) }
13 | it { is_expected.to have_and_belong_to_many(:notes) }
14 |
15 | before :each do
16 | @user = FactoryGirl.create(:user)
17 | @project = FactoryGirl.create(:project, user: @user)
18 | @leaf = FactoryGirl.create(:leaf, project: @project)
19 | @side = Side.find(id: @leaf.rectoID)
20 | end
21 |
22 | describe "Destruction hooks" do
23 | it "should unlink attached notes" do
24 | note = FactoryGirl.create(:note, project: @project, objects: {Group: [], Leaf: [], Recto: [@side.id.to_s], Verso: []} )
25 | note2 = FactoryGirl.create(:note, project: @project, objects: {Group: [], Leaf: [], Recto: [], Verso: [@side.id.to_s]} )
26 | @side.notes << [note, note2]
27 | @side.save
28 | expect(@side.notes).to include note
29 | expect(@side.notes).to include note2
30 | @side.destroy
31 | expect(note.objects[:Recto]).to be_empty
32 | expect(note2.objects[:Verso]).to be_empty
33 | end
34 |
35 | it "should unlink attached image" do
36 | image = FactoryGirl.create(:pixel, user: @user, filename: 'pixel.png', projectIDs: [@project.id.to_s], sideIDs: [@side.id.to_s])
37 | @side.update(image: { url: "http://127.0.0.1:12345/images/#{image.id}_pixel.png", label: 'Pixel', manifestID: 'DIYImages' })
38 | @side.destroy
39 | image.reload
40 | expect(image.sideIDs).to be_empty
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/viscoll-api/spec/requests/authentication/delete_session_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe "DELETE /session", :type => :request do
4 | context 'without token in header' do
5 | before do
6 | @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
7 | @user.confirmation_token = nil
8 | @user.confirmed_at = "2017-07-12T16:08:25.278Z"
9 | @user.save
10 | delete '/session'
11 | end
12 |
13 | it 'returns an unauthorized action error' do
14 | expect(response).to have_http_status(:unauthorized)
15 | end
16 | end
17 |
18 | context 'with token in header' do
19 | before do
20 | @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
21 | @user.confirmation_token = nil
22 | @user.confirmed_at = "2017-07-12T16:08:25.278Z"
23 | @user.save
24 | end
25 |
26 | context 'and token is invalid' do
27 | before do
28 | post '/session', params: {:session => { :email=> "user@mail.com", :password => "user" }}
29 | authToken = JSON.parse(response.body)['session']['jwt']+"someInvalidStuff"
30 | delete '/session', params: '', headers: {'Authorization' => authToken}
31 | end
32 |
33 | it 'returns an unprocessable_entity status' do
34 | expect(response).to have_http_status(:unprocessable_entity)
35 | end
36 |
37 | it 'returns an appropriate error message' do
38 | expect(JSON.parse(response.body)['error']).to eq('Authorization Header: Signature verification raised')
39 | end
40 | end
41 |
42 | context 'and token format is wrong' do
43 | before do
44 | post '/session', params: {:session => { :email=> "user@mail.com", :password => "user" }}
45 | delete '/session', params: '', headers: {'Authorization' => "invalidTokenFormat"}
46 | end
47 |
48 | it 'returns an unprocessable_entity status' do
49 | expect(response).to have_http_status(:unprocessable_entity)
50 | end
51 |
52 | it 'returns an appropriate error message' do
53 | expect(JSON.parse(response.body)['error']).to eq('Authorization Header: Not enough or too many segments')
54 | end
55 | end
56 |
57 | context 'and token is valid' do
58 | before do
59 | post '/session', params: {:session => { :email=> "user@mail.com", :password => "user" }}
60 | authToken = JSON.parse(response.body)['session']['jwt']
61 | delete '/session', params: '', headers: {'Authorization' => authToken}
62 | end
63 |
64 | it 'returns 204 no content response' do
65 | expect(response).to have_http_status(:no_content)
66 | end
67 |
68 | it 'clears the auth tokens of the user' do
69 | expect(User.find(@user.id).auth_tokens).to be_empty
70 | end
71 | end
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/viscoll-api/spec/requests/authentication/post_password_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe "POST /password", :type => :request do
4 | context 'with valid params' do
5 | before do
6 | @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
7 | @user.confirmation_token = nil
8 | @user.confirmed_at = "2017-07-12T16:08:25.278Z"
9 | @user.save
10 | post '/password', params: {:password => {:email => "user@mail.com"}}
11 | end
12 |
13 | it 'returns a successful no_content response' do
14 | expect(response).to have_http_status(:no_content)
15 | end
16 |
17 | it 'creates fields for reset_password in user record' do
18 | expect(User.find(@user.id).reset_password_token).not_to eq(nil)
19 | expect(User.find(@user.id).reset_password_sent_at).not_to eq(nil)
20 | end
21 | end
22 |
23 | context 'with invalid params' do
24 | context 'and unconfirmed user' do
25 | before do
26 | @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
27 | post '/password', params: {:password => {:email => "user@mail.com"}}
28 | end
29 |
30 | it 'returns an unprocessable_entity status' do
31 | expect(response).to have_http_status(:unprocessable_entity)
32 | end
33 |
34 | it 'returns an appropriate error message' do
35 | expect(JSON.parse(response.body)['errors']['email']).to eq(['unconfirmed email'])
36 | end
37 |
38 | it 'doest not create fields for reset_password in user record' do
39 | expect(User.find(@user.id).reset_password_token).to eq(nil)
40 | expect(User.find(@user.id).reset_password_sent_at).to eq(nil)
41 | end
42 | end
43 |
44 | context 'and no valid user' do
45 | before do
46 | post '/password', params: {:password => {:email => "user@mail.com"}}
47 | end
48 |
49 | it 'returns an unprocessable_entity status' do
50 | expect(response).to have_http_status(:unprocessable_entity)
51 | end
52 |
53 | it 'returns an appropriate error message' do
54 | expect(JSON.parse(response.body)['errors']['email']).to eq(['not found'])
55 | end
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/viscoll-api/spec/requests/authentication/put_confirmation_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe "PUT /confirmation", :type => :request do
4 | context 'with invalid token' do
5 | before do
6 | put '/confirmation', params: {:confirmation_token => "invalidToken"}
7 | end
8 |
9 | it 'returns an invalid token message' do
10 | expect(JSON.parse(response.body)['errors']['confirmation_token']).to eq(['not found'])
11 | end
12 |
13 | it 'returns an unprocessable_entity status' do
14 | expect(response).to have_http_status(:unprocessable_entity)
15 | end
16 | end
17 |
18 | context 'with valid token' do
19 | before do
20 | @user = User.create(:name => "user", :email => "user@mail.com", :password => "user")
21 | put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
22 | end
23 |
24 | it 'returns successful response code' do
25 | expect(response).to have_http_status(:no_content)
26 | end
27 |
28 | it 'clears the confirmation token in user record' do
29 | expect(User.find(@user.id).confirmation_token).to eq(nil)
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/viscoll-api/spec/requests/images/show_images_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe "GET /images/:id", :type => :request do
4 | before do
5 | @user = FactoryGirl.create(:user, {:password => "user"})
6 | put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
7 | post '/session', params: {:session => { :email => @user.email, :password => "user" }}
8 | @authToken = JSON.parse(response.body)['session']['jwt']
9 | end
10 |
11 | before :each do
12 | @project = FactoryGirl.create(:codex_project, user: @user, quire_structure: [[1, 2]])
13 | @image1 = FactoryGirl.create(:pixel, user: @user)
14 | @image2 = FactoryGirl.create(:shiba_inu, user: @user)
15 | end
16 |
17 | before :all do
18 | imagePath = "#{Rails.root}/public/uploads"
19 | File.new(imagePath+'/pixel', 'w')
20 | end
21 |
22 | after :all do
23 | imagePath = "#{Rails.root}/public/uploads"
24 | File.delete(imagePath+'/pixel')
25 | end
26 |
27 | context 'and valid authorization' do
28 | context 'and valid image' do
29 | before do
30 | get "/images/#{@image1.id}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json'}
31 | end
32 |
33 | it 'returns 200' do
34 | expect(response).to have_http_status(:ok)
35 | end
36 |
37 | it 'shows the right image' do
38 | expect(response.body).to eq(File.open("#{Rails.root}/public/uploads/pixel", 'rb') { |file| file.read })
39 | end
40 | end
41 |
42 | context 'and missing image' do
43 | before do
44 | get "/images/#{@image1.id}missing", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json'}
45 | @body = JSON.parse(response.body)
46 | end
47 |
48 | it 'returns 404' do
49 | expect(response).to have_http_status(:not_found)
50 | end
51 |
52 | it 'returns the error message' do
53 | expect(@body['error']).to eq("image not found with id #{@image1.id.to_str}missing")
54 | end
55 | end
56 |
57 | context 'and uncaught exception' do
58 | before do
59 | allow(Image).to receive(:find).and_raise("Exception")
60 | get "/images/#{@image1.id}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json'}
61 | @body = JSON.parse(response.body)
62 | end
63 |
64 | it 'returns 422' do
65 | expect(response).to have_http_status(:unprocessable_entity)
66 | @body = JSON.parse(response.body)
67 | end
68 |
69 | it 'returns the error message' do
70 | expect(@body['error']).to eq "Exception"
71 | end
72 | end
73 | end
74 | end
75 |
--------------------------------------------------------------------------------
/viscoll-api/spec/requests/images/zip_images_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe "GET /images/zip/:imageid_:projectid", :type => :request do
4 | before do
5 | @user = FactoryGirl.create(:user, {:password => "user"})
6 | put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
7 | post '/session', params: {:session => { :email => @user.email, :password => "user" }}
8 | @authToken = JSON.parse(response.body)['session']['jwt']
9 | @zipPath = "#{Rails.root}/public/uploads"
10 | end
11 |
12 | before :each do
13 | @project = FactoryGirl.create(:codex_project, user: @user, quire_structure: [[1, 2]])
14 | @image1 = FactoryGirl.create(:pixel, user: @user)
15 | @image2 = FactoryGirl.create(:shiba_inu, user: @user)
16 | end
17 |
18 | context 'and valid authorization' do
19 | context 'and valid image' do
20 | before do
21 |
22 | File.open("#{@zipPath}/#{@project.id}_images.zip", 'w+') { |file| file.write('testcontent') }
23 | get "/images/zip/#{@project.id}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json'}
24 | end
25 | after do
26 | File.delete("#{@zipPath}/#{@project.id}_images.zip")
27 | end
28 |
29 | it 'returns 200' do
30 | expect(response).to have_http_status(:ok)
31 | end
32 |
33 | it 'sends the zip file' do
34 | expect(response.body).to eq('testcontent')
35 | end
36 | end
37 |
38 | context 'and missing image' do
39 | before do
40 | get "/images/zip/#{@image1.id}missing_#{@project.id}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json'}
41 | @body = JSON.parse(response.body)
42 | end
43 |
44 | it 'returns 422' do
45 | expect(response).to have_http_status(:unprocessable_entity)
46 | end
47 | end
48 |
49 | context 'and uncaught exception' do
50 | before do
51 | allow(Image).to receive(:find).and_raise("Exception")
52 | get "/images/zip/#{@image1.id}_#{@project.id}", headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json'}
53 | @body = JSON.parse(response.body)
54 | end
55 |
56 | it 'returns 422' do
57 | expect(response).to have_http_status(:unprocessable_entity)
58 | @body = JSON.parse(response.body)
59 | end
60 |
61 | it 'returns the error message' do
62 | expect(@body['error']).to include "Cannot read file"
63 | end
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/viscoll-api/spec/requests/sides/sides_generateFolio_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe "PUT /sides/generateFolio", :type => :request do
4 | before do
5 | @user = FactoryGirl.create(:user, {:password => "user"})
6 | put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
7 | post '/session', params: {:session => { :email => @user.email, :password => "user" }}
8 | @authToken = JSON.parse(response.body)['session']['jwt']
9 | end
10 |
11 | before :each do
12 | @project = FactoryGirl.create(:project, {user: @user})
13 | @defaultGroup = FactoryGirl.create(:quire, project: @project)
14 | @project.add_groupIDs([@defaultGroup.id.to_s], 0)
15 | @leaf1 = FactoryGirl.create(:leaf, {project: @project})
16 | @leaf2 = FactoryGirl.create(:leaf, {project: @project})
17 | @defaultGroup.add_members([@leaf1.id.to_s, @leaf2.id.to_s], 1)
18 | @parameters = {
19 | rectoIDs: [@leaf1.rectoID, @leaf2.rectoID],
20 | versoIDs: [@leaf1.versoID, @leaf2.versoID],
21 | startNumber: 9,
22 | }
23 | end
24 |
25 | context 'generate folio number' do
26 | before do
27 | put '/sides/generateFolio', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
28 | end
29 |
30 | it 'returns 204' do
31 | expect(response).to have_http_status(:no_content)
32 | end
33 |
34 | it 'Updates the side folio numbers' do
35 | side1R = @project.sides.find(@leaf1.rectoID)
36 | side1V = @project.sides.find(@leaf1.versoID)
37 | side2R = @project.sides.find(@leaf2.rectoID)
38 | side2V = @project.sides.find(@leaf2.versoID)
39 | expect(side1R.folio_number).to eq "9R"
40 | expect(side1V.folio_number).to eq "9V"
41 | expect(side2R.folio_number).to eq "10R"
42 | expect(side2V.folio_number).to eq "10V"
43 | end
44 | end
45 | end
--------------------------------------------------------------------------------
/viscoll-api/spec/requests/sides/sides_generatePage_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe "PUT /sides/generatePageNumber", :type => :request do
4 | before do
5 | @user = FactoryGirl.create(:user, {:password => "user"})
6 | put '/confirmation', params: {:confirmation_token => @user.confirmation_token}
7 | post '/session', params: {:session => { :email => @user.email, :password => "user" }}
8 | @authToken = JSON.parse(response.body)['session']['jwt']
9 | end
10 |
11 | before :each do
12 | @project = FactoryGirl.create(:project, {user: @user})
13 | @defaultGroup = FactoryGirl.create(:quire, project: @project)
14 | @project.add_groupIDs([@defaultGroup.id.to_s], 0)
15 | @leaf1 = FactoryGirl.create(:leaf, {project: @project})
16 | @leaf2 = FactoryGirl.create(:leaf, {project: @project})
17 | @defaultGroup.add_members([@leaf1.id.to_s, @leaf2.id.to_s], 1)
18 | @parameters = {
19 | rectoIDs: [@leaf1.rectoID, @leaf2.rectoID],
20 | versoIDs: [@leaf1.versoID, @leaf2.versoID],
21 | startNumber: 3,
22 | }
23 | end
24 |
25 | context 'generate page number' do
26 | before do
27 | put '/sides/generatePageNumber', params: @parameters.to_json, headers: {'Authorization' => @authToken, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
28 | end
29 |
30 | it 'returns 204' do
31 | expect(response).to have_http_status(:no_content)
32 | end
33 |
34 | it 'Updates the side page numbers' do
35 | side1R = @project.sides.find(@leaf1.rectoID)
36 | side1V = @project.sides.find(@leaf1.versoID)
37 | side2R = @project.sides.find(@leaf2.rectoID)
38 | side2V = @project.sides.find(@leaf2.versoID)
39 | expect(side1R.page_number).to eq "3"
40 | expect(side1V.page_number).to eq "4"
41 | expect(side2R.page_number).to eq "5"
42 | expect(side2V.page_number).to eq "6"
43 | end
44 | end
45 | end
--------------------------------------------------------------------------------
/viscoll-api/tmp/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utlib/VisualCollation/5004b087ad5bf31ece78ad663ba1bc66efcf178d/viscoll-api/tmp/.keep
--------------------------------------------------------------------------------
/viscoll-app/README.md:
--------------------------------------------------------------------------------
1 | # VisColl (Redux Front-End)
2 |
3 | ## Introduction
4 |
5 | This is the the Redux-driven user interface for Viscoll.
6 |
7 | ## System Requirements
8 |
9 | - `node` (>= 6.11.4)
10 | - `npm` (>= 3.10.10)
11 |
12 | ### Additional Requirements for Development:
13 |
14 | - [Redux DevTools for Firefox or Chrome](https://github.com/zalmoxisus/redux-devtools-extension) (>= 2.15.1)
15 |
16 | ## Setup
17 |
18 | Run this to install the dependencies:
19 | ```
20 | npm install
21 | ```
22 |
23 | Then run the dev server which brings up a browser window serving the user interface:
24 | ```
25 | npm start
26 | ```
27 |
28 | ## Testing
29 |
30 | Run this command to test once:
31 | ```
32 | npm test
33 | ```
34 |
35 | Alternatively, run this command to test continually while monitoring for changes:
36 | ```
37 | npm test -- --watch
38 | ```
39 |
40 | ## Building
41 |
42 | Before building the app, edit line 3 in `viscoll-app/src/store/axiosConfig.js` to contain the correct root endpoint of the VisColl API:
43 |
44 | ```Javascript
45 | export let API_URL = '/api';
46 |
47 | ```
48 |
49 | Build the app with:
50 | ```
51 | npm build
52 | ```
53 |
--------------------------------------------------------------------------------
/viscoll-app/__test__/actions/frontendBeforeActions/helperActions.spec.js:
--------------------------------------------------------------------------------
1 | import {
2 | getLeafMembers,
3 | } from '../../../src/actions/frontend/before/helperActions';
4 |
5 | import {projectState001} from '../../testData/projectState001';
6 |
7 | import {cloneDeep} from 'lodash';
8 |
9 | describe('>>>A C T I O N --- Test helper actions', () => {
10 |
11 | it('+++ actionCreator getLeafMembers', () => {
12 | // Test getLeafMembers of 2nd group (Group_5a57825a4cfad13070870df5)
13 | const memberIDs = [
14 | 'Group_5a57825a4cfad13070870df6',
15 | 'Leaf_5a57825a4cfad13070870de2',
16 | 'Leaf_5a57825a4cfad13070870de5',
17 | 'Leaf_5a57825a4cfad13070870de8',
18 | 'Leaf_5a57825a4cfad13070870deb',
19 | 'Leaf_5a57825a4cfad13070870dee',
20 | 'Leaf_5a57825a4cfad13070870df1'
21 | ];
22 | const leafIDs = [];
23 | const projectState = cloneDeep(projectState001);
24 | const expectedLeafIDs = [
25 | 'Leaf_5a57825a4cfad13070870dd6',
26 | 'Leaf_5a57825a4cfad13070870dd9',
27 | 'Leaf_5a57825a4cfad13070870ddc',
28 | 'Leaf_5a57825a4cfad13070870ddf',
29 | 'Leaf_5a57825a4cfad13070870de2',
30 | 'Leaf_5a57825a4cfad13070870de5',
31 | 'Leaf_5a57825a4cfad13070870de8',
32 | 'Leaf_5a57825a4cfad13070870deb',
33 | 'Leaf_5a57825a4cfad13070870dee',
34 | 'Leaf_5a57825a4cfad13070870df1',
35 | ]
36 | getLeafMembers(memberIDs, projectState, leafIDs);
37 | expect(leafIDs).toEqual(expectedLeafIDs);
38 | })
39 | it('+++ actionCreator getLeafMembers', () => {
40 | // Test getLeafMembers of an empty group
41 | const memberIDs = [];
42 | const leafIDs = [];
43 | const projectState = cloneDeep(projectState001);
44 | const expectedLeafIDs = []
45 | getLeafMembers(memberIDs, projectState, leafIDs);
46 | expect(leafIDs).toEqual(expectedLeafIDs);
47 | })
48 | })
--------------------------------------------------------------------------------
/viscoll-app/__test__/actions/frontendBeforeActions/manifestActions.spec.js:
--------------------------------------------------------------------------------
1 | import {
2 | updateManifest,
3 | deleteManifest,
4 | } from '../../../src/actions/frontend/before/manifestActions';
5 |
6 | import {projectState001} from '../../testData/projectState001';
7 |
8 | import {cloneDeep} from 'lodash';
9 |
10 | describe('>>>A C T I O N --- Test manifest actions', () => {
11 |
12 | it('+++ actionCreator updateManifest', () => {
13 | const manifestPayload = {
14 | payload: {
15 | request : {
16 | url: `/projects/5a57825a4cfad13070870dc3/manifests`,
17 | method: 'put',
18 | data: {
19 | "manifest": {
20 | "id": "5a25b0703b0eb7478b415bd4",
21 | "name": "new manifest name",
22 | }
23 | },
24 | successMessage: "Successfully updated the manifest",
25 | errorMessage: "Ooops! Something went wrong"
26 | }
27 | }
28 | }
29 | const beforeState = cloneDeep(projectState001);
30 | let expectedState = cloneDeep(projectState001);
31 |
32 | expectedState.project.manifests["5a25b0703b0eb7478b415bd4"].name = "new manifest name";
33 |
34 | const gotState = updateManifest(manifestPayload, beforeState);
35 | expect(gotState).toEqual(expectedState);
36 | })
37 |
38 | it('+++ actionCreator deleteManifest', () => {
39 | const manifestPayload = {
40 | payload: {
41 | request : {
42 | url: `/projects/5a57825a4cfad13070870dc3/manifests`,
43 | method: 'delete',
44 | data: {
45 | "manifest": {
46 | "id": "5a25b0703b0eb7478b415bd4",
47 | }
48 | },
49 | successMessage: "Successfully deleted the manifest",
50 | errorMessage: "Ooops! Something went wrong"
51 | }
52 | }
53 | }
54 | const beforeState = cloneDeep(projectState001);
55 | let expectedState = cloneDeep(projectState001);
56 |
57 | delete expectedState.project.manifests["5a25b0703b0eb7478b415bd4"];
58 | expectedState.project.Rectos["Recto_5a57825a4cfad13070870dc5"].image = {};
59 | expectedState.project.Rectos["Recto_5a57825a4cfad13070870dc8"].image = {};
60 | expectedState.project.Rectos["Recto_5a57825a4cfad13070870dcb"].image = {};
61 | expectedState.project.Rectos["Recto_5a57825a4cfad13070870dce"].image = {};
62 | expectedState.project.Rectos["Recto_5a57825a4cfad13070870dd1"].image = {};
63 | expectedState.project.Rectos["Recto_5a57825a4cfad13070870dd4"].image = {};
64 |
65 | const gotState = deleteManifest(manifestPayload, beforeState);
66 | expect(gotState).toEqual(expectedState);
67 | })
68 |
69 | })
--------------------------------------------------------------------------------
/viscoll-app/__test__/testData/dashboardState001.js:
--------------------------------------------------------------------------------
1 | export const dashboardState001 = {
2 | projects: [
3 | {
4 | id: '5a57825a4cfad13070870dc3',
5 | title: 'my prject',
6 | shelfmark: 'mss 568',
7 | metadata: {
8 | date: '19th century'
9 | },
10 | created_at: '2018-01-12T19:05:20.803Z',
11 | updated_at: '2018-01-12T19:05:21.175Z'
12 | },
13 | ],
14 | images: [
15 | {
16 | id: '5a5cc9594cfad17bed092f4a',
17 | projectIDs: [
18 | '5a57825a4cfad13070870dc3'
19 | ],
20 | sideIDs: ['Verso_5a57825a4cfad13070870dc6'],
21 | url: 'http://localhost:3001/images/5a5cc9594cfad17bed092f4a_cguk1l0u4aeewdf.jpeg',
22 | label: 'cguk1l0u4aeewdf.jpeg'
23 | },
24 | {
25 | id: '5a5cc9594cfad17bed092f4b',
26 | projectIDs: [
27 | '5a57825a4cfad13070870dc3'
28 | ],
29 | sideIDs: ['Verso_5a57825a4cfad13070870dc9'],
30 | url: 'http://localhost:3001/images/5a5cc9594cfad17bed092f4b_3c17f2b4127b1a5a8bcfc76ba9de9c9f_chiba_inu_dogs_shiba_inu_funny.jpeg',
31 | label: '3c17f2b4127b1a5a8bcfc76ba9de9c9f_chiba_inu_dogs_shiba_inu_funny.jpeg'
32 | },
33 | {
34 | id: '5a5cc9594cfad17bed092f4c',
35 | projectIDs: [
36 | '5a57825a4cfad13070870dc3'
37 | ],
38 | sideIDs: ['Verso_5a57825a4cfad13070870dcc'],
39 | url: 'http://localhost:3001/images/5a5cc9594cfad17bed092f4c_1_105.jpeg',
40 | label: '1_105.jpeg'
41 | },
42 | {
43 | id: '5a5783154cfad13070870e0e',
44 | projectIDs: [
45 | '5a57825a4cfad13070870dc3'
46 | ],
47 | sideIDs: [],
48 | url: 'http://localhost:3001/images/5a5783154cfad13070870e0e_shiba_inu_taiki.jpeg',
49 | label: 'shiba_inu_taiki.jpeg'
50 | },
51 | {
52 | id: '5a5cc95a4cfad17bed092f4e',
53 | projectIDs: [
54 | '5a57825a4cfad13070870dc3'
55 | ],
56 | sideIDs: [],
57 | url: 'http://localhost:3001/images/5a5cc95a4cfad17bed092f4e_cnrvtp6vaaamulm.png',
58 | label: 'cnrvtp6vaaamulm.png'
59 | },
60 | {
61 | id: '5a5783154cfad13070870e13',
62 | projectIDs: [
63 | '5a57825a4cfad13070870dc3'
64 | ],
65 | sideIDs: [],
66 | url: 'http://localhost:3001/images/5a5783154cfad13070870e13_shiba_inu_3jpg.jpeg',
67 | label: 'shiba_inu_3jpg.jpeg'
68 | },
69 | {
70 | id: '5a5783154cfad16535870e13',
71 | projectIDs: [],
72 | sideIDs: [],
73 | url: 'http://localhost:3001/images/5a5783154cfad16535870e13_103496018.jpeg',
74 | label: '103496018.jpeg'
75 | }
76 | ],
77 | importStatus: null
78 | }
--------------------------------------------------------------------------------
/viscoll-app/assetsTransformer.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | process(src, filename, config, options) {
5 | return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/viscoll-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "viscoll-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "axios": "^0.19.0",
7 | "babel-polyfill": "^6.26.0",
8 | "clientjs": "^0.1.11",
9 | "copy-to-clipboard": "^3.2.0",
10 | "es6-promise": "^4.1.0",
11 | "file-saver": "^2.0.2",
12 | "fuse.js": "^3.4.5",
13 | "immutability-helper": "^2.4.0",
14 | "js-file-download": "^0.4.7",
15 | "jszip": "^3.2.1",
16 | "localforage": "^1.5.0",
17 | "lodash": "^4.17.15",
18 | "material-ui": "^0.19.4",
19 | "material-ui-chip-input": "^0.18.3",
20 | "material-ui-superselectfield": "^1.5.6",
21 | "openseadragon": "^2.3.1",
22 | "paper": "^0.11.4",
23 | "react": "^15.6.1",
24 | "react-detect-offline": "^1.0.6",
25 | "react-dom": "^15.6.1",
26 | "react-redux": "^5.0.5",
27 | "react-router-dom": "^4.1.1",
28 | "react-tap-event-plugin": "^2.0.1",
29 | "react-tiny-virtual-list": "^2.1.4",
30 | "react-virtualized": "^9.21.1",
31 | "redux": "^3.7.1",
32 | "redux-axios-middleware": "^4.0.0",
33 | "redux-persist": "^4.8.2"
34 | },
35 | "devDependencies": {
36 | "babel-preset-es2015": "^6.24.1",
37 | "babel-preset-react": "^6.24.1",
38 | "babel-preset-stage-2": "^6.24.1",
39 | "enzyme": "^2.9.1",
40 | "react-addons-test-utils": "^15.6.0",
41 | "react-scripts": "^3.4.0",
42 | "react-test-renderer": "^15.6.1",
43 | "redux-devtools-extension": "^2.13.2",
44 | "redux-mock-store": "^1.2.3",
45 | "regenerator-runtime": "^0.11.0"
46 | },
47 | "scripts": {
48 | "start": "react-scripts start",
49 | "build": "react-scripts build",
50 | "eject": "react-scripts eject"
51 | },
52 | "babel": {
53 | "presets": [
54 | "react",
55 | "es2015",
56 | "stage-2"
57 | ]
58 | },
59 | "browserslist": {
60 | "production": [
61 | ">0.2%",
62 | "not dead",
63 | "not op_mini all"
64 | ],
65 | "development": [
66 | "last 1 chrome version",
67 | "last 1 firefox version",
68 | "last 1 safari version"
69 | ]
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/viscoll-app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utlib/VisualCollation/5004b087ad5bf31ece78ad663ba1bc66efcf178d/viscoll-app/public/favicon.ico
--------------------------------------------------------------------------------
/viscoll-app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | Viscodex
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/viscoll-app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/viscoll-app/sass/components/_dialog.scss:
--------------------------------------------------------------------------------
1 | .addDialog {
2 |
3 | .title {
4 | color: $black;
5 |
6 | }
7 | h3 {
8 | border-bottom: 1px solid #ddd;
9 | }
10 |
11 | h4 {
12 | color: $black;
13 | margin-top: 2em;
14 | margin-bottom: 0em;
15 | font-weight:600;
16 | }
17 |
18 | .label {
19 | width: 200px;
20 | display:inline-block;
21 | }
22 | .input {
23 | width: 200px;
24 | display:inline-block;
25 | text-align: right;
26 | }
27 | }
28 | .feedbackDialog {
29 | p {
30 | color: $black;
31 | }
32 | .label {
33 | color: $black;
34 | width: 100px;
35 | display:inline-block;
36 | vertical-align: top;
37 | }
38 | .input {
39 | width: 250px;
40 | display:inline-block;
41 | text-align: right;
42 | }
43 | }
44 | .newProjectDialog {
45 | p {
46 | color: $black;
47 | }
48 | h1 {
49 | font-weight: normal;
50 | text-transform: inherit;
51 | }
52 | h3 {
53 | font-size: 1em;
54 | margin-top: 0em;
55 | }
56 | .section {
57 | margin-left: 1em;
58 | }
59 | .newProjectSelection {
60 | button.btnSelection {
61 | width: 100%;
62 | background: $white;
63 | border: 0;
64 | @include box-shadow(0px 2px 10px 0px rgba(0,0,0,0.2));
65 | margin-bottom: 1em;
66 | outline: 0;
67 |
68 | &:focus, &:hover {
69 | outline-style: solid;
70 | outline-width: 0.5em;
71 | outline-color: transparentize($teal, 0.5);
72 | cursor: pointer;
73 | }
74 | }
75 | .selectItem {
76 | display: flex;
77 | padding: 2.5em 0em;
78 |
79 | .icon {
80 | width: 50px;
81 | padding:0px 15px;
82 | }
83 | .text {
84 | line-height: 2.5em;
85 | span:nth-child(1) {
86 | text-align: left;
87 | font-size: 2.5em;
88 | display: block;
89 | color: $black;
90 | width: 100%;
91 | }
92 | span:nth-child(2) {
93 | font-size: 1.3em;
94 | color: lighten($black, 15);
95 | width: 100%;
96 | display: block;
97 | }
98 | }
99 | }
100 | }
101 | }
--------------------------------------------------------------------------------
/viscoll-app/sass/components/_textarea.scss:
--------------------------------------------------------------------------------
1 | textarea {
2 | border: 1px solid #e0e0e0;
3 | width: 100%;
4 | font-size: 1em;
5 | &:focus {
6 | outline-color: $teal;
7 | }
8 | }
--------------------------------------------------------------------------------
/viscoll-app/sass/components/_tooltip.scss:
--------------------------------------------------------------------------------
1 | .tooltip {
2 | position: relative;
3 | display: inline-block;
4 | width: 100%;
5 |
6 | .text {
7 | visibility: hidden;
8 | width: 210px;
9 | font-weight: 300;
10 | background: transparentize(darken($black, 15%), 0.1);
11 | color: #fff;
12 | text-align: center;
13 | @include border-radius(6px);
14 | padding: 0.5em;
15 | margin: 1em;
16 | font-size: 0.9em;
17 | opacity: 0;
18 | @include transition(all, 200ms, ease-in-out);
19 |
20 | /* Position the tooltip */
21 | position: absolute;
22 | z-index: 100;
23 |
24 | &::after {
25 | content: " ";
26 | position: absolute;
27 | bottom: 100%; /* At the top of the tooltip */
28 | left: 50%;
29 | margin-left: -5px;
30 | border-width: 5px;
31 | border-style: solid;
32 | border-color: transparent transparent transparentize(darken($black, 15%), 0.1) transparent;
33 | }
34 | }
35 |
36 | &.addDialog {
37 | .text {
38 | width: 70%;
39 | &.active {
40 | visibility: visible;
41 | opacity: 1;
42 | }
43 | &::after {
44 | left: 20%;
45 | }
46 | }
47 | }
48 |
49 | &.eyeToggle {
50 | width: initial;
51 | .text {
52 | left: -5%;
53 | margin-left: 0;
54 | width: 100px;
55 |
56 | &::after {
57 | bottom: 100%; /* At the top of the tooltip */
58 | left: 15%;
59 | margin-left: -5px;
60 | }
61 | &.active {
62 | visibility: visible;
63 | opacity: 1;
64 | }
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/viscoll-app/sass/index.scss:
--------------------------------------------------------------------------------
1 | @import 'lib/variables';
2 | @import 'lib/mixins';
3 | @import 'layout/landing';
4 | @import 'layout/sidebar';
5 | @import 'layout/projectPanel';
6 | @import 'layout/infobox';
7 | @import 'layout/workspace';
8 | @import 'layout/tabular';
9 | @import 'layout/topbar';
10 | @import 'layout/notes';
11 | @import 'layout/filter';
12 | @import 'layout/loading';
13 | @import 'layout/404';
14 | @import 'layout/dashboard';
15 | @import 'layout/imageManager';
16 | @import 'layout/imageCollection';
17 | @import 'components/dialog';
18 | @import 'components/tooltip';
19 | @import 'components/textarea';
20 | @import 'typography';
21 |
22 | html, body {
23 | background: #F2F2F2;
24 | }
--------------------------------------------------------------------------------
/viscoll-app/sass/layout/_404.scss:
--------------------------------------------------------------------------------
1 | .fourOhFour {
2 | width: 100vw;
3 | height: 100vh;
4 | background: $bg_blue;
5 | display: flex;
6 | align-items: center;
7 | .container {
8 | width: 100vw;
9 | display: flex;
10 | flex-direction: column;
11 | justify-content: center;
12 | align-items: center;
13 | margin-top: -10%;
14 | h1 {
15 | font-size: 8em;
16 | color: $white;
17 | padding-bottom: 0;
18 | margin-bottom: 10px;
19 | }
20 | p {
21 | color: $white;
22 | margin-bottom: 30px;
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/viscoll-app/sass/layout/_dashboard.scss:
--------------------------------------------------------------------------------
1 | #listView {
2 | .header {
3 | display: flex;
4 | padding: 1em 2em;
5 | font-size: 0.8em;
6 | div {
7 | width: 50%;
8 | }
9 | }
10 | button {
11 | width: 100%;
12 | display: flex;
13 | text-align: left;
14 | border-left: 1px solid transparentize($white, 0.5);
15 | border-right: 1px solid transparentize($white, 0.5);
16 | border-top: 1px solid transparentize($dark_gray, 0.8);
17 | border-bottom: 1px solid transparentize($white, 0.5);
18 | padding: 1em 2em;
19 | background: transparentize($white, 0.5);
20 | font-size: 0.9em;
21 | color: $black;
22 | cursor: pointer;
23 | @include transition(background, 100ms, ease-in-out);
24 |
25 | &:hover {
26 | background: $white;
27 | }
28 | &:focus {
29 | outline-style: solid;
30 | outline-color: transparentize($teal, 0.5);
31 | outline-width: 2px;
32 | border: 1px solid $teal;
33 | }
34 | &.selected {
35 | background: $teal;
36 | }
37 |
38 | &.selected:focus {
39 | outline: 0;
40 | }
41 |
42 | div {
43 | width: 50%;
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/viscoll-app/sass/layout/_filter.scss:
--------------------------------------------------------------------------------
1 | .filter {
2 | width: 100%;
3 | max-height: 45%;
4 | position: relative;
5 | z-index: 2;
6 | left: 0;
7 | display: flex;
8 | justify-content: flex-end;
9 | @include transition(opacity, 200ms, linear);
10 | }
11 | .filterContainer {
12 | border-top:1px solid $gray;
13 | position: fixed;
14 | width: 82%;
15 | max-height: 45%;
16 | background: $white;
17 | padding-bottom: 10px;
18 | overflow: auto;
19 | @include transition(top, 450ms, cubic-bezier(0.23, 1, 0.32, 1));
20 | @include box-shadow(0px 2px 8px 0px rgba(0,0,0,0.3));
21 | }
22 | .filterRow {
23 | display: flex;
24 | align-items: flex-start;
25 | justify-content: center;
26 | & + .filterRow {
27 | margin-top: -20px;
28 | }
29 |
30 | .filterField {
31 | width: 230px;
32 | margin-left: 10px;
33 | &:first-child {
34 | margin-left:20px;
35 | }
36 | &:last-child {
37 | width: 160px;
38 | text-align: center;
39 | }
40 | }
41 | }
42 | .filterMessage {
43 | text-transform: uppercase;
44 | font-size: 0.9em;
45 | font-weight: 500;
46 | color: $dark_gray;
47 | }
--------------------------------------------------------------------------------
/viscoll-app/sass/layout/_imageCollection.scss:
--------------------------------------------------------------------------------
1 | .imageFilter {
2 | @include box-shadow(0px 2px 10px 0px rgba(0,0,0,0.1));
3 | background: $white;
4 | margin: 0em 0em 0.5em 0em;
5 | display: flex;
6 | justify-content: space-between;
7 | }
--------------------------------------------------------------------------------
/viscoll-app/sass/layout/_infobox.scss:
--------------------------------------------------------------------------------
1 | .infoBox {
2 | position: fixed;
3 | display: inline-block;
4 | width: 22%;
5 | vertical-align:top;
6 | right: 0;
7 | top: 56px;
8 | background: white;
9 | max-height: 90%;
10 | overflow-y: auto;
11 | margin: 2% 2% 0% 0%;
12 | @include box-shadow(0px 2px 10px 0px rgba(0,0,0,0.1));
13 |
14 | .inner {
15 | padding: 10px 20px 15px 20px;
16 | @media screen and (max-width: $small) {
17 | padding: 5px 15px 7px 15px;
18 | }
19 | @media screen and (max-width: $xsmall) {
20 | padding: 5px 10px 7px 10px;
21 | }
22 |
23 | .row {
24 | display: flex;
25 | flex-wrap: wrap;
26 | justify-content: space-between;
27 | align-items: center;
28 | }
29 | .label {
30 | width: 35%;
31 | }
32 | .input {
33 | width: 55%;
34 | @media screen and (max-width: $xsmall) {
35 | width: 50%;
36 | }
37 | }
38 | }
39 | button.image {
40 | border: 0;
41 | background: none;
42 | padding: 0;
43 | margin: 0;
44 | font-size: 1em;
45 | & + button {
46 | margin-left: 0.5em;
47 | }
48 | overflow: none;
49 | max-width: 40%;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/viscoll-app/sass/layout/_landing.scss:
--------------------------------------------------------------------------------
1 | .landing {
2 | width: 100vw;
3 | height: 100vh;
4 | background: $bg_blue2;
5 | text-align: center;
6 |
7 | .container {
8 | margin: 0 auto;
9 | width: 80vw;
10 | height: 90vh;
11 | display: flex;
12 | align-items: center;
13 | align-content: center;
14 | }
15 |
16 | img {
17 | width: 100%;
18 | }
19 |
20 | .panelLogo {
21 | width: 55%;
22 | }
23 |
24 | .panelLogin {
25 | width: 40%;
26 | padding-left: 5%;
27 | }
28 |
29 | .panelBottom {
30 | display: table;
31 | width: 100%;
32 | height:10vh;
33 | background: $teal;
34 |
35 | div {
36 | display: table-cell;
37 | vertical-align: middle;
38 | }
39 |
40 | span:first-child {
41 | font-weight: bold;
42 | }
43 |
44 | span {
45 | color: #2b4352;
46 | font-size: 1.1em;
47 | display: block;
48 | margin-bottom: .5em;
49 | }
50 | }
51 |
52 | hr {
53 | border: 1px solid $teal;
54 | }
55 |
56 | .spacingBottom {
57 | margin-bottom: 1.5em;
58 | }
59 |
60 | .spacingTop {
61 | margin-top: 1.5em;
62 | }
63 |
64 | p {
65 | color: $teal;
66 | }
67 |
68 | a {
69 | color: $teal;
70 | cursor: pointer;
71 | text-decoration: underline;
72 |
73 | &:hover {
74 | color: lighten($teal, 20%);
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/viscoll-app/sass/layout/_loading.scss:
--------------------------------------------------------------------------------
1 | .appLoading {
2 | width: 100vw;
3 | height: 100vh;
4 | background: $bg_blue;
5 | display: flex;
6 | align-items: center;
7 | .container {
8 | width: 100vw;
9 | display: flex;
10 | flex-direction: column;
11 | justify-content: center;
12 | align-items: center;
13 | margin-top: -5%;
14 | }
15 | .logo {
16 | width: 30%;
17 | max-width: 300px;
18 | margin-bottom: 1em;
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/viscoll-app/sass/layout/_projectPanel.scss:
--------------------------------------------------------------------------------
1 | .projectPanelInfo {
2 | padding: 1em;
3 | line-height: 2em;
4 |
5 | .info {
6 | padding-top: 1em;
7 | font-size: 0.9em;
8 | span {
9 | color: transparentize($black, 0.2);
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/viscoll-app/sass/layout/_topbar.scss:
--------------------------------------------------------------------------------
1 | .topbar {
2 | position: fixed;
3 | left:0px;
4 | width: 100%;
5 | z-index: 2200;
6 | @include box-shadow(0px 1px 2px 1px rgba(0,0,0,0.05));
7 |
8 | &.lowerZIndex {
9 | z-index: 1500;
10 | }
11 |
12 | .logo {
13 | float:left;
14 | width: 18%;
15 | height: 55px;
16 | text-align: center;
17 | position: relative;
18 | background: $bg_blue;
19 | img {
20 | width: 60%;
21 | margin: 0;
22 | position: absolute;
23 | top: 50%;
24 | left: 50%;
25 | -ms-transform: translate(-50%, -50%);
26 | transform: translate(-50%, -50%);
27 | @media screen and (max-width: $xsmall) {
28 | width: 85%;
29 | }
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/viscoll-app/sass/layout/_workspace.scss:
--------------------------------------------------------------------------------
1 | .workspace {
2 | position: absolute;
3 | left: 18%;
4 | top: 56px;
5 | }
6 | .projectWorkspace {
7 | @extend .workspace;
8 | width: 54%;
9 | margin: 2%;
10 | .viewingMode {
11 | display: flex;
12 | &>div:first-child {
13 | margin-top: 4px;
14 | }
15 | &>div:nth-child(2) {
16 | margin-top: 14px;
17 |
18 | }
19 | }
20 | }
21 | .dashboardWorkspace {
22 | @extend .workspace;
23 | width: 82%;
24 | @include transition(all, 450ms, cubic-bezier(0.23, 1, 0.32, 1));
25 |
26 | &.projectPanelOpen {
27 | width: 70%;
28 | }
29 | }
30 | .notesWorkspace, .imageWorkspace {
31 | @extend .workspace;
32 | width: 82%;
33 | }
--------------------------------------------------------------------------------
/viscoll-app/sass/lib/_mixins.scss:
--------------------------------------------------------------------------------
1 | @mixin border-radius($radius) {
2 | -webkit-border-radius: $radius;
3 | -moz-border-radius: $radius;
4 | -ms-border-radius: $radius;
5 | border-radius: $radius;
6 | }
7 |
8 | @mixin box-shadow($shadow...) {
9 | -webkit-box-shadow: $shadow;
10 | box-shadow: $shadow;
11 | }
12 |
13 | @mixin transition($target, $duration, $transitionType) {
14 | -webkit-transition: $target $duration $transitionType;
15 | -ms-transition: $target $duration $transitionType;
16 | transition: $target $duration $transitionType;
17 | }
18 |
19 | @mixin breakpoint($point) {
20 | @media(nth($point, 1): nth($point, 2)) { @content; }
21 | }
22 |
23 | @mixin centerize($x_percent, $y_percent) {
24 | position: relative;
25 | top: $y_percent;
26 | left: $x_percent;
27 | transform: translateY(-$y_percent) translateX(-$x_percent);
28 | }
29 |
30 | @mixin user-select($value) {
31 | -webkit-touch-callout: $value; /* iOS Safari */
32 | -webkit-user-select: $value; /* Safari */
33 | -khtml-user-select: $value; /* Konqueror HTML */
34 | -moz-user-select: $value; /* Firefox */
35 | -ms-user-select: $value; /* Internet Explorer/Edge */
36 | user-select: $value; /* Non-prefixed version, currently
37 | supported by Chrome and Opera */
38 | }
39 |
40 | @mixin keyframes($name) {
41 | @-webkit-keyframes #{$name} {
42 | @content;
43 | }
44 | @-moz-keyframes #{$name} {
45 | @content;
46 | }
47 | @-ms-keyframes #{$name} {
48 | @content;
49 | }
50 | @keyframes #{$name} {
51 | @content;
52 | }
53 | }
--------------------------------------------------------------------------------
/viscoll-app/sass/lib/_variables.scss:
--------------------------------------------------------------------------------
1 | // Palette
2 | $fg_blue: #526C91;
3 | $bg_blue: #3A4B55;
4 | $bg_blue2: #2B4352;
5 | $teal: #4ED6CB;
6 | $white: #FFFFFF;
7 | $gray: #F2F2F2;
8 | $dark_gray: #727272;
9 | $black: #4e4e4e;
10 | $error: #bd4a4a;
11 | $success: #34A251;
12 | $warning: #E37A05;
13 |
14 | // Breakpoints
15 | $medium: 1200px;
16 | $small: 1024px;
17 | $xsmall: 768px;
18 |
--------------------------------------------------------------------------------
/viscoll-app/sass/typography.scss:
--------------------------------------------------------------------------------
1 | h1 {
2 | font-size: 1.6em;
3 | font-weight: bold;
4 | color: $black;
5 | @media screen and (max-width: $xsmall) {
6 | font-size: 1.3em;
7 | }
8 | }
9 | h2 {
10 | color: $dark_gray;
11 | padding-top: 0.8em;
12 | font-size: 1.15em;
13 | @media screen and (max-width: $xsmall) {
14 | font-size: 1em;
15 | }
16 | }
17 | h3 {
18 | @media screen and (max-width: $xsmall) {
19 | font-size: 1em;
20 | }
21 | }
--------------------------------------------------------------------------------
/viscoll-app/src/actions/backend/groupActions.js:
--------------------------------------------------------------------------------
1 |
2 | export function addGroups(group, additional) {
3 | return {
4 | types: ['CREATE_GROUPS_FRONTEND','CREATE_GROUPS_SUCCESS_BACKEND','CREATE_GROUPS_FAILED_BACKEND'],
5 | payload: {
6 | request : {
7 | url: `/groups`,
8 | method: 'post',
9 | data: {group, additional},
10 | successMessage: "Successfully added the groups" ,
11 | errorMessage: "Ooops! Something went wrong"
12 | }
13 | },
14 | isUndoable: true,
15 | };
16 | }
17 |
18 | export function updateGroup(groupID, group) {
19 | return {
20 | types: ['UPDATE_GROUP_FRONTEND','UPDATE_GROUP_SUCCESS_BACKEND','UPDATE_GROUP_FAILED_BACKEND'],
21 | payload: {
22 | request : {
23 | url: `/groups/${groupID}`,
24 | method: 'put',
25 | data: {group},
26 | successMessage: "Successfully updated the group" ,
27 | errorMessage: "Ooops! Something went wrong"
28 | }
29 | },
30 | isUndoable: true,
31 | };
32 | }
33 |
34 | export function updateGroups(groups) {
35 | return {
36 | types: ['UPDATE_GROUPS_FRONTEND','UPDATE_GROUPS_SUCCESS_BACKEND','UPDATE_GROUPS_FAILED_BACKEND'],
37 | payload: {
38 | request : {
39 | url: `/groups`,
40 | method: 'put',
41 | data: {groups},
42 | successMessage: "Successfully updated the groups" ,
43 | errorMessage: "Ooops! Something went wrong"
44 | }
45 | },
46 | isUndoable: true,
47 | };
48 | }
49 |
50 | export function deleteGroup(groupID) {
51 | return {
52 | types: ['DELETE_GROUP_FRONTEND','DELETE_GROUP_SUCCESS_BACKEND','DELETE_GROUP_FAILED_BACKEND'],
53 | payload: {
54 | request : {
55 | url: `/groups/${groupID}`,
56 | method: 'delete',
57 | successMessage: "Successfully deleted the group" ,
58 | errorMessage: "Ooops! Something went wrong"
59 | }
60 | },
61 | isUndoable: true,
62 | };
63 | }
64 |
65 | export function deleteGroups(groups, projectID) {
66 | return {
67 | types: ['DELETE_GROUPS_FRONTEND','DELETE_GROUPS_SUCCESS_BACKEND','DELETE_GROUPS_FAILED_BACKEND'],
68 | payload: {
69 | request : {
70 | url: `/groups`,
71 | method: 'delete',
72 | data: {...groups, projectID},
73 | successMessage: "Successfully deleted the groups" ,
74 | errorMessage: "Ooops! Something went wrong"
75 | }
76 | },
77 | isUndoable: true,
78 | };
79 | }
80 |
--------------------------------------------------------------------------------
/viscoll-app/src/actions/backend/manifestActions.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | export function createManifest(projectID, manifest) {
5 | return {
6 | types: ['SHOW_LOADING','CREATE_MANIFEST_SUCCESS','CREATE_MANIFEST_FAILED'],
7 | payload: {
8 | request : {
9 | url: `/projects/${projectID}/manifests`,
10 | method: 'post',
11 | data: manifest,
12 | successMessage: "You have successfully created the manifest" ,
13 | errorMessage: "Ooops! Something went wrong"
14 | }
15 | }
16 | };
17 | }
18 |
19 |
20 | export function updateManifest(projectID, manifest) {
21 | return {
22 | types: ['UPDATE_MANIFEST_FRONTEND','UPDATE_MANIFEST_SUCCESS_BACKEND','UPDATE_MANIFEST_FAILED_BACKEND'],
23 | payload: {
24 | request : {
25 | url: `/projects/${projectID}/manifests`,
26 | method: 'put',
27 | data: manifest,
28 | successMessage: "You have successfully updated the manifest" ,
29 | errorMessage: "Ooops! Something went wrong"
30 | }
31 | },
32 | isUndoable: true,
33 | };
34 | }
35 |
36 |
37 | export function deleteManifest(projectID, manifest) {
38 | return {
39 | types: ['DELETE_MANIFEST_FRONTEND','DELETE_MANIFEST_SUCCESS_BACKEND','DELETE_MANIFEST_FAILED_BACKEND'],
40 | payload: {
41 | request : {
42 | url: `/projects/${projectID}/manifests`,
43 | method: 'delete',
44 | data: manifest,
45 | successMessage: "You have successfully deleted the manifest" ,
46 | errorMessage: "Ooops! Something went wrong"
47 | }
48 | },
49 | isUndoable: true,
50 | };
51 | }
52 |
53 | export function cancelCreateManifest(){
54 | return {type: "CANCEL_CREATE_MANIFEST"}
55 | }
56 |
57 |
58 | export function cloneProjectImagesMapping(projectID) {
59 | return {
60 | types: ['NO_LOADING','CLONE_PROJECT_MAPPING_SUCCESS','CLONE_PROJECT_MAPPING_FAILED'],
61 | payload: {
62 | request : {
63 | url: `/projects/${projectID}/cloneImageMapping`,
64 | method: 'get',
65 | successMessage: "" ,
66 | errorMessage: "Ooops! Something went wrong"
67 | }
68 | }
69 | };
70 | }
--------------------------------------------------------------------------------
/viscoll-app/src/actions/backend/sideActions.js:
--------------------------------------------------------------------------------
1 | export function updateSide(sideID, side) {
2 | return {
3 | types: ['UPDATE_SIDE_FRONTEND','UPDATE_SIDE_SUCCESS_BACKEND','UPDATE_SIDE_FAILED_BACKEND'],
4 | payload: {
5 | request : {
6 | url: `/sides/${sideID}`,
7 | method: 'put',
8 | data: {side},
9 | successMessage: "Successfully updated the side" ,
10 | errorMessage: "Ooops! Something went wrong"
11 | }
12 | },
13 | isUndoable: true,
14 | };
15 | }
16 |
17 | export function updateSides(sides) {
18 | return {
19 | types: ['UPDATE_SIDES_FRONTEND','UPDATE_SIDES_SUCCESS_BACKEND','UPDATE_SIDES_FAILED_BACKEND'],
20 | payload: {
21 | request : {
22 | url: `/sides`,
23 | method: 'put',
24 | data: {sides},
25 | successMessage: "Successfully updated the sides" ,
26 | errorMessage: "Ooops! Something went wrong"
27 | }
28 | },
29 | isUndoable: true,
30 | };
31 | }
--------------------------------------------------------------------------------
/viscoll-app/src/actions/frontend/after/imageActions.js:
--------------------------------------------------------------------------------
1 | export function updateImagesAfterUpload(action, dashboard, active) {
2 | const newDIYImages = action.payload.images
3 | dashboard.images = [...dashboard.images, ...newDIYImages]
4 | if (active.project.id !== ""){
5 | // Update the active project's DIYManifest images list
6 | active.project.manifests.DIYImages.images = [
7 | ...active.project.manifests.DIYImages.images,
8 | ...newDIYImages.map(image => {
9 | return { label: image.label, url: image.url, manifestID: "DIYImages" }
10 | })
11 | ]
12 | }
13 | return {dashboard, active}
14 | }
15 |
--------------------------------------------------------------------------------
/viscoll-app/src/actions/frontend/before/helperActions.js:
--------------------------------------------------------------------------------
1 | export function getLeafMembers(memberIDs, state, leafIDs=[]) {
2 | for (let memberID of memberIDs){
3 | if (memberID.charAt(0)==="G"){
4 | getLeafMembers(state.project.Groups[memberID].memberIDs, state, leafIDs)
5 | } else {
6 | leafIDs.push(memberID)
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/viscoll-app/src/actions/frontend/before/manifestActions.js:
--------------------------------------------------------------------------------
1 | export function updateManifest(action, state) {
2 | const updatedManifest = action.payload.request.data.manifest
3 | // Only manifest name is allowed to be updated
4 | state.project.manifests[updatedManifest.id].name = updatedManifest.name
5 | return state
6 | }
7 |
8 | export function deleteManifest(action, state) {
9 | const deletedManifest = action.payload.request.data.manifest
10 | // Delete the manifest with id deletedManifest.id from the active project's manifests
11 | delete state.project.manifests[deletedManifest.id]
12 | // Update all sides that have an image mapped from deletedManifest
13 | for (let rectoID of [...Object.keys(state.project.Rectos)]){
14 | const rectoSide = state.project.Rectos[rectoID]
15 | if (rectoSide.image.hasOwnProperty('manifestID') && rectoSide.image.manifestID===deletedManifest.id)
16 | state.project.Rectos[rectoID].image = {}
17 | }
18 | for (let versoID of [...Object.keys(state.project.Versos)]) {
19 | const versoSide = state.project.Versos[versoID]
20 | if (versoSide.image.hasOwnProperty('manifestID') && versoSide.image.manifestID === deletedManifest.id)
21 | state.project.Versos[versoID].image = {}
22 | }
23 | return state
24 | }
25 |
--------------------------------------------------------------------------------
/viscoll-app/src/actions/frontend/before/projectActions.js:
--------------------------------------------------------------------------------
1 | export function updateProject(action, state){
2 | const updatedProject = action.payload.request.data.project;
3 | const updatedProjectID = action.payload.request.url.split("/").pop();
4 | const projectIndex = state.projects.findIndex(project => project.id === updatedProjectID)
5 | state.projects[projectIndex] = {...state.projects[projectIndex], ...updatedProject};
6 | return state
7 | }
8 |
9 | export function updatePreferences(action, state) {
10 | const preferences = action.payload.request.data.project.preferences;
11 | state.project.preferences = {
12 | group:{...state.project.preferences.group, ...preferences.group},
13 | leaf:{...state.project.preferences.leaf, ...preferences.leaf},
14 | side:{...state.project.preferences.side,...preferences.side}
15 | };
16 | return state;
17 | }
18 |
19 | export function deleteProject(action, state) {
20 | const deletedProjectID = action.payload.request.url.split("/").pop();
21 | const deletedProjectIndex = state.projects.findIndex(project => project.id === deletedProjectID);
22 | state.projects.splice(deletedProjectIndex, 1)
23 | // Remove deletedProjectID from all images. If image has no projects linked, delete the image.
24 | for (let image of [...state.images]){
25 | const projectIDIndex = image.projectIDs.indexOf(deletedProjectID)
26 | const imageIndex = state.images.findIndex(DIYImage => DIYImage.id === image.id)
27 | if (projectIDIndex !== -1) {
28 | state.images[imageIndex].projectIDs.splice(projectIDIndex, 1)
29 | }
30 | // Remove the image if its not linked to any other projects
31 | if (projectIDIndex!==-1 && image.projectIDs.length===0 && action.payload.request.data.deleteUnlinkedImages) {
32 | state.images.splice(imageIndex, 1)
33 | }
34 | }
35 | return state
36 | }
37 |
--------------------------------------------------------------------------------
/viscoll-app/src/actions/undoRedo/imageHelper.js:
--------------------------------------------------------------------------------
1 | import {
2 | linkImages,
3 | unlinkImages,
4 | } from '../backend/imageActions';
5 |
6 | export function undoLinkImages(action) {
7 | const projectIDs = action.payload.request.data.projectIDs;
8 | const imageIDs = action.payload.request.data.imageIDs;
9 | const historyAction = unlinkImages(projectIDs, imageIDs);
10 | return [historyAction];
11 | }
12 |
13 | export function undoUnlinkImages(action) {
14 | const projectIDs = action.payload.request.data.projectIDs;
15 | const imageIDs = action.payload.request.data.imageIDs;
16 | const historyAction = linkImages(projectIDs, imageIDs);
17 | return [historyAction];
18 | }
--------------------------------------------------------------------------------
/viscoll-app/src/actions/undoRedo/manifestHelper.js:
--------------------------------------------------------------------------------
1 | import {
2 | createManifest,
3 | updateManifest,
4 | } from '../backend/manifestActions';
5 |
6 | import {
7 | updateSides,
8 | } from '../backend/sideActions';
9 |
10 | export function undoUpdateManifest(action, state) {
11 | const manifest = {
12 | id: action.payload.request.data.manifest.id,
13 | name: state.project.manifests[action.payload.request.data.manifest.id].name,
14 | }
15 | const historyAction = updateManifest(state.project.id, {manifest});
16 | return [historyAction];
17 | }
18 |
19 | export function undoDeleteManifest(action, state) {
20 | const historyActions = [];
21 | const manifestID = action.payload.request.data.manifest.id;
22 | // Create manifest
23 | const manifest = {
24 | id: manifestID,
25 | url: state.project.manifests[manifestID].url,
26 | }
27 | const createAction = createManifest(state.project.id, {manifest});
28 | historyActions.push(createAction);
29 |
30 | // Relink sides linked to images in this manifest
31 | const sides = [];
32 | for (const rectoID of state.project.rectoIDs) {
33 | const recto = state.project.Rectos[rectoID];
34 | if (recto.image.manifestID && recto.image.manifestID === manifestID) {
35 | const item = {
36 | id: recto.id,
37 | attributes: {
38 | image: {...recto.image},
39 | }
40 | }
41 | sides.push(item);
42 | }
43 | }
44 | for (const versoID of state.project.versoIDs) {
45 | const verso = state.project.Versos[versoID];
46 | if (verso.image.manifestID && verso.image.manifestID === manifestID) {
47 | const item = {
48 | id: verso.id,
49 | attributes: {
50 | image: {...verso.image},
51 | }
52 | };
53 | sides.push(item);
54 | }
55 | }
56 | if (sides.length>0) {
57 | const mapAction = updateSides(sides);
58 | historyActions.push(mapAction);
59 | }
60 | return historyActions;
61 | }
--------------------------------------------------------------------------------
/viscoll-app/src/actions/undoRedo/sideHelper.js:
--------------------------------------------------------------------------------
1 | import {
2 | updateSide,
3 | updateSides,
4 | } from '../backend/sideActions';
5 |
6 | export function undoUpdateSide(action, state) {
7 | const sideID = action.payload.request.url.split("/").pop();
8 | const attribute = Object.keys(action.payload.request.data.side)[0];
9 | const sideType = sideID.split("_")[0] + "s";
10 | const side = {
11 | [attribute]: state.project[sideType][sideID][attribute],
12 | };
13 | const historyAction = updateSide(sideID, side);
14 | return [historyAction];
15 | }
16 |
17 | export function undoUpdateSides(action, state) {
18 | const requests = action.payload.request.data.sides;
19 | const sides = [];
20 |
21 | for (const request of requests) {
22 | const sideType = request.id.split("_")[0] + "s";
23 | const side = state.project[sideType][request.id];
24 | const item = {
25 | id: request.id,
26 | attributes: {},
27 | }
28 | for (const attribute in request.attributes) {
29 | if (!request.attributes.hasOwnProperty(attribute)) continue;
30 | item.attributes[attribute] = side[attribute];
31 | }
32 | sides.push(item);
33 | }
34 | const historyActions = updateSides(sides);
35 | return [historyActions];
36 | }
--------------------------------------------------------------------------------
/viscoll-app/src/assets/blank_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utlib/VisualCollation/5004b087ad5bf31ece78ad663ba1bc66efcf178d/viscoll-app/src/assets/blank_page.png
--------------------------------------------------------------------------------
/viscoll-app/src/assets/collation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utlib/VisualCollation/5004b087ad5bf31ece78ad663ba1bc66efcf178d/viscoll-app/src/assets/collation.png
--------------------------------------------------------------------------------
/viscoll-app/src/assets/logo_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utlib/VisualCollation/5004b087ad5bf31ece78ad663ba1bc66efcf178d/viscoll-app/src/assets/logo_white.png
--------------------------------------------------------------------------------
/viscoll-app/src/assets/logo_white.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/viscoll-app/src/assets/viscoll_loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/utlib/VisualCollation/5004b087ad5bf31ece78ad663ba1bc66efcf178d/viscoll-app/src/assets/viscoll_loading.gif
--------------------------------------------------------------------------------
/viscoll-app/src/components/authentication/ResendConfirmation.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import TextField from 'material-ui/TextField';
3 | import RaisedButton from 'material-ui/RaisedButton';
4 | import FlatButton from 'material-ui/FlatButton';
5 | import { btnLg, btnAuthCancel } from '../../styles/button';
6 | import {floatFieldDark} from '../../styles/textfield';
7 |
8 | /**
9 | * Resend confirmation form.
10 | */
11 | class ResendConfirmation extends Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | message: props.message,
16 | email: props.email,
17 | submitted: false,
18 | };
19 | }
20 |
21 | /**
22 | * Update state when user inputs new value in a text field
23 | */
24 | onChange(v, type) {
25 | this.setState({[type]: v});
26 | }
27 |
28 | /**
29 | * Send confirmation email
30 | */
31 | resendConfirmation = () => {
32 | this.props.action.resendConfirmation(this.state.email);
33 | this.setState({submitted:true, message: "An email confirmation has been resent to " + this.state.email});
34 | }
35 |
36 | render() {
37 | let form = this.state.submitted? "": ;
48 |
49 | let cancel = this.props.tapCancel()}
52 | label="Go back"
53 | {...btnAuthCancel}
54 | />;
55 |
56 | if (this.state.submitted) {
57 | cancel = this.props.tapCancel()}
63 | />;
64 | }
65 |
66 | return (
67 |
68 |
{this.state.message}
69 | {form}
70 |
71 | {cancel}
72 |
73 |
);
74 | }
75 | }
76 | export default ResendConfirmation;
--------------------------------------------------------------------------------
/viscoll-app/src/components/authentication/ResetPassword.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import TextField from 'material-ui/TextField';
3 | import RaisedButton from 'material-ui/RaisedButton';
4 | import { btnLg } from '../../styles/button';
5 | import {floatFieldDark} from '../../styles/textfield';
6 | /**
7 | * Contains the form to update password when user forgets password.
8 | */
9 | class ResetPassword extends Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | password: "",
14 | passwordConfirm: "",
15 | resetMessage: ""
16 | };
17 | }
18 |
19 | /**
20 | * Update state when user inputs new value in a text field
21 | */
22 | onInputChange = (v, type) => {
23 | this.setState({ [type]: v });
24 | }
25 |
26 | /**
27 | * Validate password input and submit password change
28 | */
29 | submit = (e) => {
30 | if (e) e.preventDefault();
31 | let resetMessage = ""
32 | if (!this.state.password || !this.state.passwordConfirm) {
33 | resetMessage = "Error: Both Password & Password Confirmation must be filled";
34 | } else if (this.state.password !== this.state.passwordConfirm) {
35 | resetMessage = "Error: Both Password & Password Confirmation must match";
36 | }
37 | else {
38 | const reset_password_token = this.props.reset_password_token;
39 | const password = {
40 | password: this.state.password,
41 | password_confirmation: this.state.passwordConfirm
42 | };
43 | this.props.resetPassword(reset_password_token, password);
44 | this.props.handleResetPasswordSuccess();
45 | }
46 | this.setState({ resetMessage })
47 | };
48 |
49 | render() {
50 | return (
51 |
66 | );
67 | }
68 | }
69 | export default ResetPassword;
70 |
--------------------------------------------------------------------------------
/viscoll-app/src/components/collationManager/TabularMode.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import update from 'immutability-helper';
3 | import Group from './tabularMode/Group';
4 |
5 |
6 | /** Tabular mode - shows collation as table rows */
7 | export default class TabularMode extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | focusGroupID: [],
12 | focusLeafID: null,
13 | }
14 | }
15 |
16 | toggleFocusGroup = (id) => {
17 | let newList = [];
18 | if (id!==null) {
19 | // Push to array
20 | newList = update(this.state.focusGroupID, {$push: [id]});
21 | } else {
22 | // Pop the array
23 | newList = update(this.state.focusGroupID, {$splice: [[this.state.focusGroupID.length-1, 1]]});
24 | }
25 | this.setState({focusGroupID: newList});
26 | }
27 |
28 | toggleFocusLeaf = (id) => {
29 | this.setState({focusLeafID: id, focusGroupID: [this.state.focusGroupID[this.state.focusGroupID.length-1]]});
30 | }
31 |
32 | render() {
33 | let group_components = [];
34 | for (let groupID of this.props.project.groupIDs){
35 | const group = this.props.project.Groups[groupID]
36 | if (group.nestLevel === 1)
37 | group_components.push(
38 | 0? this.state.focusGroupID[this.state.focusGroupID.length-1] : null}
48 | focusLeafID={this.state.focusLeafID}
49 | handleObjectPress={this.props.handleObjectPress}
50 | tabIndex={this.props.tabIndex}
51 | leafIDs={this.props.leafIDs}
52 | />
53 | );
54 | }
55 |
56 | let emptyResults = false;
57 | const activeFiltersLength = this.props.collationManager.filters.Groups.length + this.props.collationManager.filters.Leafs.length + this.props.collationManager.filters.Sides.length + this.props.collationManager.filters.Notes.length;
58 | if (activeFiltersLength===0)
59 | emptyResults = true && this.props.collationManager.filters.hideOthers && this.props.collationManager.filters.active
60 |
61 | return (
62 |
63 | {emptyResults ?
No objects match the query
: group_components}
64 |
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/viscoll-app/src/components/dashboard/CloneProject.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import RaisedButton from 'material-ui/RaisedButton';
3 | import FlatButton from 'material-ui/FlatButton';
4 | import SelectField from '../global/SelectField';
5 |
6 | /** New Project dialog - clone existing project */
7 | export default class CloneProject extends React.Component {
8 |
9 | constructor(props) {
10 | super(props);
11 | this.state = {
12 | projectIndex: -1
13 | }
14 | }
15 |
16 | onChange = (projectIndex) => {
17 | this.setState({ projectIndex });
18 | }
19 |
20 | submit = (event) => {
21 | if (event) event.preventDefault();
22 | this.props.cloneProject(this.props.allProjects[this.state.projectIndex].id);
23 | this.props.reset();
24 | this.props.close();
25 | }
26 |
27 | render(){
28 | if (this.props.allProjects.length>0) {
29 | const data = this.props.allProjects.map((project, index)=>{
30 | return (
31 | {text: project.title, value:index}
32 | );
33 | });
34 | return (
35 |
36 |
Clone Existing Collation
37 |
61 |
62 | );
63 | } else {
64 | return
65 |
Clone Existing Collation
66 |
You do not have any projects to clone.
67 |
68 | this.props.previousStep()}
72 | />
73 |
74 |
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/viscoll-app/src/components/dashboard/ListView.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | /**
4 | * List the projects in a table format
5 | */
6 | const ListView = ({selectedProjectID, selectProject, allProjects=[], doubleClick, tabIndex}) => {
7 | const viewDate = {year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'}
8 | const ariaDate = {year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit'}
9 | const projectsList = allProjects.map((project, i) => {
10 | return (
11 |
21 | );
22 | });
23 | return (
24 |
27 |
28 |
Name
29 |
Date Modified
30 |
31 |
32 | {projectsList}
33 |
34 |
35 | );
36 | };
37 | export default ListView;
38 |
--------------------------------------------------------------------------------
/viscoll-app/src/components/dashboard/NewProjectChoice.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import RaisedButton from 'material-ui/RaisedButton';
3 | import {btnMd} from '../../styles/button';
4 |
5 | /** New Project dialog - select between starting with an empty project or pre-poulating it */
6 | const NewProjectChoice = (props) => {
7 | return
8 |
15 |
16 | OR
17 |
18 |
25 |
26 | }
27 | export default NewProjectChoice;
--------------------------------------------------------------------------------
/viscoll-app/src/components/dashboard/NewProjectSelection.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AddIcon from 'material-ui/svg-icons/content/add';
3 | import CopyIcon from 'material-ui/svg-icons/content/content-copy';
4 | import ImportIcon from 'material-ui/svg-icons/action/system-update-alt';
5 |
6 | /** New Project dialog - select between creating new, importing and cloning project */
7 | const NewProjectSelection = (props) => {
8 | return (
9 |
10 |
29 |
30 |
49 |
50 |
69 |
70 | );
71 | }
72 | export default NewProjectSelection;
--------------------------------------------------------------------------------
/viscoll-app/src/components/dashboard/ProjectDetails.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {floatFieldLight} from '../../styles/textfield';
3 | import TextField from 'material-ui/TextField';
4 | import RaisedButton from 'material-ui/RaisedButton';
5 | import FlatButton from 'material-ui/FlatButton';
6 |
7 | /** New Project dialog - panel with a form to fill out project details */
8 | const ProjectDetails = (props) => {
9 | let submit = (event) => {
10 | if (event) event.preventDefault();
11 | if(!props.doErrorsExist()) props.nextStep()
12 | }
13 | return (
14 |
15 |
Object Details
16 |
64 |
65 | );
66 | }
67 | export default ProjectDetails;
68 |
--------------------------------------------------------------------------------
/viscoll-app/src/components/global/AppLoadingScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import logoImg from '../../assets/logo_white.png';
3 | import CircularProgress from 'material-ui/CircularProgress';
4 |
5 | /** Stateless functional component for the app loading screen */
6 | const AppLoadingScreen = (props) => {
7 | const logo =
;
8 | if (props.loading) {
9 | return
10 |
11 |
12 | {logo}
13 |
14 |
15 |
19 |
20 |
21 |
;
22 | } else {
23 | return
24 | }
25 | }
26 | export default AppLoadingScreen;
27 |
--------------------------------------------------------------------------------
/viscoll-app/src/components/global/LoadingScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Dialog from 'material-ui/Dialog';
3 | import loadingImg from '../../assets/viscoll_loading.gif';
4 |
5 | /** Stateless functional component for the loading screen */
6 | const LoadingScreen = (props) => {
7 | const logo =
;
8 | return (
9 |
18 | );
19 | }
20 | export default LoadingScreen;
21 |
--------------------------------------------------------------------------------
/viscoll-app/src/components/global/ManagersPanel.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Panel from './Panel';
3 |
4 | /** Stateless functional component for the Managers panel in sidebar of project edit page */
5 | const ManagersPanel = (props) => {
6 | return (
7 |
8 |
16 |
24 |
32 |
33 | );
34 | }
35 | export default ManagersPanel;
--------------------------------------------------------------------------------
/viscoll-app/src/components/global/Notification.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Snackbar from 'material-ui/Snackbar';
3 |
4 | /** Stateless functional component for snackbar notification */
5 | const Notification = (props) => {
6 | return (
7 |
13 | );
14 | }
15 | export default Notification;
16 |
--------------------------------------------------------------------------------
/viscoll-app/src/components/global/PageNotFound.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import RaisedButton from 'material-ui/RaisedButton';
3 |
4 | /** 404 page */
5 | export default class PageNotFound extends Component {
6 | render() {
7 | return
19 | }
20 | }
--------------------------------------------------------------------------------
/viscoll-app/src/components/global/Panel.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import IconButton from 'material-ui/IconButton';
3 | import ExpandMore from 'material-ui/svg-icons/navigation/expand-more';
4 | import ExpandLess from 'material-ui/svg-icons/navigation/expand-less';
5 |
6 | /** Expandable panel component for the project sidebar. Panel examples: Filter, export.. */
7 | export default class Panel extends Component {
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | open: props.defaultOpen,
12 | }
13 | }
14 |
15 | handleChange = (type, value) => {
16 | this.setState({[type]: value});
17 | }
18 |
19 | render() {
20 | let paddingStyle = this.props.noPadding?{padding:0}:{};
21 | return (
22 |
23 |
24 |
{this.props.title}
25 | {e.preventDefault(); e.stopPropagation(); this.handleChange("open", !this.state.open)}}
27 | aria-label={this.state.open?"Hide " + this.props.title + " panel" : "Expand " + this.props.title + " panel"}
28 | iconStyle={{color:"white"}}
29 | tooltip={this.state.open?"Hide panel":"Expand panel"}
30 | style={{padding:0,width:"inherit",height:"inherit"}}
31 | tooltipPosition="bottom-left"
32 | tabIndex={this.props.tabIndex}
33 | >
34 | {this.state.open? : }
35 |
36 |
37 |
38 | {this.props.children}
39 |
40 |
41 | )
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/viscoll-app/src/components/global/ServerErrorScreen.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import Dialog from 'material-ui/Dialog';
3 | import FlatButton from 'material-ui/FlatButton';
4 | import { connect } from "react-redux";
5 |
6 | /** Dialog for server error */
7 | class ServerErrorScreen extends Component {
8 |
9 | render() {
10 |
11 | const actions = [
12 | this.props.logout()}
17 | />,
18 | ];
19 |
20 | return (
21 |
30 | );
31 | }
32 | }
33 |
34 | const mapStateToProps = (state) => {
35 | return {
36 | serverError: state.global.serverError
37 | };
38 | };
39 |
40 | const mapDispatchToProps = (dispatch) => {
41 | return {
42 | logout: () => {
43 | dispatch({type: "LOGOUT_SUCCESS"})
44 | }
45 | };
46 | };
47 |
48 | export default connect(mapStateToProps, mapDispatchToProps)(ServerErrorScreen);
49 |
--------------------------------------------------------------------------------
/viscoll-app/src/components/imageManager/DeleteManifest.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import Dialog from 'material-ui/Dialog';
3 | import FlatButton from 'material-ui/FlatButton';
4 | import RaisedButton from 'material-ui/RaisedButton';
5 |
6 | /** Confirmation dialog to delete manifest */
7 | export default class DeleteManifest extends Component {
8 | render() {
9 | const actions = [
10 | ,
16 | {this.props.handleClose(); this.props.deleteManifest()}}
19 | backgroundColor="#b53c3c"
20 | labelColor="#ffffff"
21 | />,
22 | ];
23 |
24 | if (this.props.open) {
25 | return (
26 |
27 |
35 |
36 | );
37 | } else {
38 | return ;
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/viscoll-app/src/components/imageManager/mapImages/SideBacklog.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import IconButton from 'material-ui/IconButton';
3 | import Add from 'material-ui/svg-icons/content/add-circle-outline';
4 | import VirtualList from 'react-tiny-virtual-list';
5 |
6 | /** Panel for unmapped sides */
7 | export default class SideBacklog extends Component {
8 |
9 | renderSideItem = (index, style) => {
10 | const sideID = this.props.sideIDs[index];
11 | const sideType = sideID.charAt(0)==="R"? "recto" : "verso";
12 | const side = sideType === "recto" ? this.props.Rectos[sideID] : this.props.Versos[sideID];
13 | const folioNumber = side.folio_number && side.folio_number!=="" ? "("+side.folio_number+")" : "";
14 | const pageNumber = side.page_number && side.page_number!=="" ? "("+side.page_number+")" : "";
15 | const leafOrder = this.props.leafIDs.indexOf(side.parentID)+1;
16 | let actionButtons = (
17 | event.stopPropagation()}>
18 |
this.props.moveItemsToMap([sideID], "sideMapBoard", "sideBacklog")}
22 | tabIndex={this.props.tabIndex}
23 | >
24 |
25 |
26 |
27 | );
28 | let activeStyle = {};
29 | if (this.props.selectedObjects.members.includes(sideID))
30 | activeStyle = {backgroundColor: "#4ED6CB"}
31 | return (
32 | this.props.handleObjectClick(this.props.id, sideID, event)}>
33 |
34 | {"Leaf " + leafOrder + " " + side.memberType + " " + folioNumber + " " + pageNumber}
35 |
36 | {actionButtons}
37 |
38 | );
39 | }
40 |
41 | render() {
42 | if (this.props.id==="sideMapBoard") {
43 | return (
44 |
45 | this.renderSideItem(index, style)}
51 | overscanCount={10}
52 | estimatedItemSize={400}
53 | />
54 |
55 | );
56 | }
57 |
58 | // sideBacklog
59 | return (
60 | this.renderSideItem(index, style)}
66 | overscanCount={10}
67 | estimatedItemSize={400}
68 | />
69 | );
70 |
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/viscoll-app/src/components/notesManager/NotesFilter.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import TextField from 'material-ui/TextField';
3 | import Checkbox from 'material-ui/Checkbox';
4 | import {floatFieldLight} from '../../styles/textfield';
5 |
6 | /** Filter notes */
7 | class NotesFilter extends Component {
8 |
9 | constructor(props) {
10 | super(props);
11 | this.state = {
12 | value: "",
13 | }
14 | }
15 |
16 | render() {
17 | return (
18 |
19 |
20 | {this.setState({value});this.props.onValueChange(e,value)}}
25 | style={window.innerWidth<=890?{marginLeft:10,marginRight:10, width:150}:{marginLeft:10,marginRight:10, width:200}}
26 | value={this.state.value}
27 | {...floatFieldLight}
28 | tabIndex={this.props.tabIndex}
29 | />
30 |
31 |
0)?"searchOptions active":"searchOptions"}>
32 | this.props.onTypeChange("title", !this.props.filterTypes["title"])}
39 | tabIndex={this.props.tabIndex}
40 | />
41 | this.props.onTypeChange("type", !this.props.filterTypes["type"])}
48 | tabIndex={this.props.tabIndex}
49 | />
50 | this.props.onTypeChange("description", !this.props.filterTypes["description"])}
57 | tabIndex={this.props.tabIndex}
58 | />
59 |
60 |
61 | );
62 | }
63 | }
64 |
65 |
66 | export default NotesFilter;
67 |
--------------------------------------------------------------------------------
/viscoll-app/src/containers/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import injectTapEventPlugin from 'react-tap-event-plugin';
3 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
4 | import getMuiTheme from 'material-ui/styles/getMuiTheme';
5 | import light from '../styles/light';
6 | import '../styles/App.css';
7 | import Authentication from './Authentication';
8 | import Dashboard from './Dashboard';
9 | import Project from './Project';
10 | import ProjectViewOnly from './ProjectViewOnly'
11 | import {persistStore} from 'redux-persist'
12 | import store from "../store/store";
13 | import {Provider} from "react-redux";
14 | import AppLoadingScreen from "../components/global/AppLoadingScreen";
15 | import localForage from 'localforage'
16 | import PageNotFound from '../components/global/PageNotFound';
17 | import {
18 | BrowserRouter as Router,
19 | Route,
20 | Switch
21 | } from 'react-router-dom'
22 |
23 | injectTapEventPlugin();
24 |
25 | /** Main app */
26 | class App extends Component {
27 |
28 | constructor() {
29 | super()
30 | this.state = { rehydrated: false }
31 | }
32 |
33 | componentWillMount(){
34 | persistStore(store, {storage: localForage, whitelist:['user']}, () => {
35 | setTimeout(()=>{this.setState({ rehydrated: true })}, 500);
36 | })
37 | }
38 |
39 | render() {
40 | if (!this.state.rehydrated) {
41 | return (
42 |
43 |
44 |
45 | )
46 | }
47 | return (
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | );
64 | }
65 | }
66 |
67 | export default App;
68 |
--------------------------------------------------------------------------------
/viscoll-app/src/containers/ProjectViewOnly.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import { connect } from "react-redux";
3 | import CollationManager from './CollationManagerViewOnly'
4 | import LoadingScreen from "../components/global/LoadingScreen";
5 | import Notification from "../components/global/Notification";
6 | import NetworkErrorScreen from "../components/global/NetworkErrorScreen";
7 | import { loadProjectViewOnly } from "../actions/backend/projectActions";
8 |
9 | /** Container for 'Manager (Collation or Notes or Image)', `LoadingScreen`, and `Notification`. */
10 | class Project extends Component {
11 |
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | popUpActive: false,
16 | };
17 | }
18 |
19 | componentWillMount() {
20 | const projectID = this.props.location.pathname.split("/")[2];
21 | this.props.loadProjectViewOnly(projectID);
22 | }
23 |
24 | togglePopUp = (value) => {
25 | this.setState({popUpActive: value});
26 | }
27 |
28 | render() {
29 | return (
30 |
31 |
36 |
37 |
38 |
39 |
40 | )
41 | }
42 | }
43 |
44 | const mapStateToProps = (state) => {
45 | return {
46 | user: state.user,
47 | loading: state.global.loading,
48 | notification: state.global.notification,
49 | projectID: state.active.project.id,
50 | };
51 | };
52 |
53 | const mapDispatchToProps = (dispatch) => {
54 | return {
55 | loadProjectViewOnly: (projectID) => {
56 | dispatch(loadProjectViewOnly(projectID))
57 | },
58 | };
59 | };
60 |
61 |
62 | export default connect(mapStateToProps, mapDispatchToProps)(Project);
63 |
64 |
--------------------------------------------------------------------------------
/viscoll-app/src/helpers/MultiSelectAutoComplete.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SuperSelectField from 'material-ui-superselectfield';
3 |
4 | const selectionsRenderer = (values) => {
5 | if (values.length===1) return "1 item selected"
6 | if (values.length>1) return `${values.length} items selected`
7 | return "Select item(s)..."
8 | }
9 |
10 | const MultiSelectAutoComplete = (props) => {
11 | return (
12 |
22 | {props.children}
23 |
24 | );
25 | }
26 |
27 | export default MultiSelectAutoComplete;
28 |
--------------------------------------------------------------------------------
/viscoll-app/src/helpers/getLeafsOfGroup.js:
--------------------------------------------------------------------------------
1 | export function getLeafsOfGroup(group, Leafs, includeNone=true){
2 | let leafMembersOfCurrentGroup = [];
3 | if (includeNone) {
4 | leafMembersOfCurrentGroup.push({
5 | order: "None",
6 | id: "None",
7 | })
8 | }
9 | for (let memberID of group.memberIDs) {
10 | if (memberID.charAt(0)==="L") leafMembersOfCurrentGroup.push(Leafs[memberID]);
11 | }
12 | return leafMembersOfCurrentGroup;
13 | }
14 |
--------------------------------------------------------------------------------
/viscoll-app/src/helpers/getMemberOrder.js:
--------------------------------------------------------------------------------
1 | export function getMemberOrder(member, Groups, groupIDs) {
2 | const parentID = member.parentID
3 | if (parentID){
4 | return Groups[parentID].memberIDs.indexOf(member.id)+1
5 | } else {
6 | // member is a root Group. Find member order from groupIDs
7 | return groupIDs.indexOf(member.id)+1
8 | }
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/viscoll-app/src/helpers/renderHelper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Chip from 'material-ui/Chip';
3 |
4 | export function renderNoteChip(props, note) {
5 | let deleteFn = () => {props.action.unlinkNote(note.id)};
6 | if (props.isReadOnly) deleteFn = null;
7 | return props.openNoteDialog(note)}
12 | tabIndex={props.tabIndex}
13 | labelStyle={{fontSize:props.windowWidth<=1024?12:null}}
14 | >
15 | {note.title}
16 |
17 | }
--------------------------------------------------------------------------------
/viscoll-app/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './containers/App';
4 | import registerServiceWorker from './registerServiceWorker';
5 | import './styles/index.css';
6 |
7 |
8 | ReactDOM.render(
9 | ,
10 | document.getElementById('root')
11 | );
12 | registerServiceWorker();
13 |
14 |
--------------------------------------------------------------------------------
/viscoll-app/src/reducers/dashboardReducer.js:
--------------------------------------------------------------------------------
1 | import { initialState } from './initialStates/projects';
2 |
3 | export default function dashboardReducer(state=initialState, action) {
4 | try {
5 | if (action.error) {
6 | action = {type: action.type, payload: action.error.response.data}
7 | }
8 | } catch (e) {}
9 |
10 | switch(action.type) {
11 | case "LOAD_PROJECT_SUCCESS":
12 | state = action.payload.dashboard
13 | break;
14 | case "LOAD_PROJECTS_SUCCESS":
15 | case "CREATE_PROJECT_SUCCESS":
16 | case "CLONE_PROJECT_IMPORT_SUCCESS":
17 | case "IMPORT_MANIFEST_SUCCESS":
18 | state = action.payload;
19 | break;
20 | case "IMPORT_PROJECT_SUCCESS":
21 | state = {
22 | projects: action.payload.projects,
23 | images: action.payload.images,
24 | importStatus: "SUCCESS"
25 | }
26 | break;
27 | case "IMPORT_PROJECT_SUCCESS_CALLBACK":
28 | state = {...state, importStatus: null}
29 | break;
30 | case "LOGOUT_SUCCESS":
31 | case "DELETE_PROFILE_SUCCESS":
32 | state = initialState
33 | break;
34 | case "IMPORT_PROJECT_FAILED":
35 | state = {
36 | ...state,
37 | importStatus: action.payload.error
38 | }
39 | break;
40 | case "CREATE_PROJECT_FAILED":
41 | case "LOAD_PROJECTS_FAILED":
42 | case "CLONE_PROJECT_IMPORT_FAILED":
43 | break;
44 |
45 |
46 | // FRONT-END ACTIONS
47 | case "UPDATE_PROJECT_FRONTEND":
48 | case "DELETE_PROJECT_FRONTEND":
49 | state = action.payload.response
50 | break;
51 | case "LINK_IMAGES_FRONTEND":
52 | case "UNLINK_IMAGES_FRONTEND":
53 | case "DELETE_IMAGES_FRONTEND":
54 | case "MAP_SIDES_FRONTEND":
55 | state = action.payload.response.dashboard
56 | break;
57 | case "UPLOAD_IMAGES_SUCCESS_BACKEND":
58 | state = action.payload.response.dashboard
59 | break;
60 | case "DELETE_PROJECT_SUCCESS_BACKEND":
61 | state = action.payload
62 | break;
63 | default:
64 | break;
65 | }
66 | return state;
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/viscoll-app/src/reducers/globalReducer.js:
--------------------------------------------------------------------------------
1 | import { initialState } from './initialStates/global';
2 |
3 | export default function globalReducer(state=initialState, action) {
4 | if (action.error && action.error.status===0) {
5 | state = {...state, serverError: true}
6 | }
7 | switch(action.type) {
8 | case "SHOW_LOADING":
9 | state = {...state, loading: true}
10 | break;
11 | case "HIDE_LOADING":
12 | state = {...state, loading: false}
13 | break;
14 | case "SHOW_NOTIFICATION":
15 | state = {...state, notification: action.payload}
16 | break;
17 | case "HIDE_NOTIFICATION":
18 | state = {...state, notification: ""}
19 | break;
20 | case "UPDATE_LOADING_COUNT":
21 | state = {...state, loadingRequestCount: action.payload};
22 | break;
23 | case "DELETE_PROFILE_SUCCESS":
24 | case "LOGOUT_SUCCESS":
25 | state = initialState
26 | break;
27 | case "BACKEND_SERVER_ERROR":
28 | state = {...state, serverError: true}
29 | break;
30 | default:
31 | break;
32 | }
33 | return state;
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/viscoll-app/src/reducers/historyReducer.js:
--------------------------------------------------------------------------------
1 | import { initialState } from './initialStates/history';
2 |
3 | export default function historyReducer(state=initialState, action) {
4 | switch(action.type) {
5 | case "UNDO":
6 | case "PUSH_UNDO":
7 | state.undo = action.payload;
8 | break;
9 | case "REDO":
10 | case "PUSH_REDO":
11 | state.redo = action.payload;
12 | break;
13 | case "CLEAR_ACTION_HISTORY":
14 | state.undo = [];
15 | state.redo = [];
16 | break;
17 | default:
18 | break;
19 | }
20 | return state;
21 | }
--------------------------------------------------------------------------------
/viscoll-app/src/reducers/initialStates/global.js:
--------------------------------------------------------------------------------
1 | export const initialState = {
2 | loading: false,
3 | loadingRequestCount: 0,
4 | notification: "",
5 | serverError: false,
6 | unauthorizedError: false,
7 | };
8 |
9 | export default initialState;
10 |
--------------------------------------------------------------------------------
/viscoll-app/src/reducers/initialStates/history.js:
--------------------------------------------------------------------------------
1 | export const initialState = {
2 | undo: [],
3 | redo: [],
4 | }
5 | export default initialState;
--------------------------------------------------------------------------------
/viscoll-app/src/reducers/initialStates/projects.js:
--------------------------------------------------------------------------------
1 | export const initialState = {
2 | projects: [],
3 | images: [],
4 | importStatus: null
5 | };
6 |
7 | export default initialState;
8 |
--------------------------------------------------------------------------------
/viscoll-app/src/reducers/initialStates/user.js:
--------------------------------------------------------------------------------
1 | export const initialState = {
2 | authenticated: false,
3 | token: "",
4 | errors: {
5 | login: {errorMessage: ""},
6 | register: {email: "", password: ""},
7 | update: {password: "", current_password: "", email: ""},
8 | confirmation: "",
9 | }
10 | }
11 |
12 | export default initialState;
13 |
--------------------------------------------------------------------------------
/viscoll-app/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | export default function register() {
12 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
13 | window.addEventListener('load', () => {
14 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
15 | navigator.serviceWorker
16 | .register(swUrl)
17 | .then(registration => {
18 | registration.onupdatefound = () => {
19 | const installingWorker = registration.installing;
20 | installingWorker.onstatechange = () => {
21 | if (installingWorker.state === 'installed') {
22 | if (navigator.serviceWorker.controller) {
23 | // At this point, the old content will have been purged and
24 | // the fresh content will have been added to the cache.
25 | // It's the perfect time to display a "New content is
26 | // available; please refresh." message in your web app.
27 | console.log('New content is available; please refresh.');
28 | } else {
29 | // At this point, everything has been precached.
30 | // It's the perfect time to display a
31 | // "Content is cached for offline use." message.
32 | console.log('Content is cached for offline use.');
33 | }
34 | }
35 | };
36 | };
37 | })
38 | .catch(error => {
39 | console.error('Error during service worker registration:', error);
40 | });
41 | });
42 | }
43 | }
44 |
45 | export function unregister() {
46 | if ('serviceWorker' in navigator) {
47 | navigator.serviceWorker.ready.then(registration => {
48 | registration.unregister();
49 | });
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/viscoll-app/src/store/axiosConfig.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | export let API_URL = '/api';
4 |
5 | // IN DEVELOPMENT
6 | if (process.env.NODE_ENV === 'development') {
7 | API_URL = 'http://localhost:3001'
8 | }
9 | export const client = axios.create({
10 | baseURL: API_URL,
11 | responseType: 'json'
12 | });
13 |
14 | export const clientOptions = {
15 | interceptors: {
16 | request: [
17 | ({getState, dispatch, getSourceAction}, request) => {
18 | if (getState().user.token) request.headers['Authorization'] = getState().user.token
19 | return request;
20 | }
21 | ],
22 | response: [{
23 | success: function ({getState, dispatch, getSourceAction}, response) {
24 | if (getState().global.loading) {
25 | if (getState().global.loadingRequestCount>0)
26 | dispatch({type:"UPDATE_LOADING_COUNT", payload:getState().global.loadingRequestCount-1})
27 | if (getState().global.loadingRequestCount<=1)
28 | dispatch({ type: "HIDE_LOADING" });
29 | }
30 | return Promise.resolve(response.data);
31 | },
32 | error: function ({getState, dispatch, getSourceAction}, error) {
33 | if (getState().global.loading) dispatch({ type: "HIDE_LOADING" });
34 | if (error.config.errorMessage) {
35 | dispatch({ type: "SHOW_NOTIFICATION", payload: error.config.errorMessage });
36 | setTimeout(()=>dispatch({type: "HIDE_NOTIFICATION"}), 4000);
37 | }
38 | if (error.response && (error.response.status===401 || error.response.status!==422)) {
39 | dispatch({type: "BACKEND_SERVER_ERROR"});
40 | }
41 | return Promise.reject(error);
42 | }
43 | }]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/viscoll-app/src/store/middleware/frontendAfterActionsMiddleware.js:
--------------------------------------------------------------------------------
1 | import { cloneDeep } from 'lodash';
2 | import {
3 | updateImagesAfterUpload,
4 | } from '../../actions/frontend/after/imageActions';
5 |
6 | const frontendAfterActionsMiddleware = store => next => action => {
7 | switch (action.type) {
8 | // Image Actions
9 | case "UPLOAD_IMAGES_SUCCESS_BACKEND":
10 | action.payload.response = updateImagesAfterUpload(action, cloneDeep(store.getState().dashboard), cloneDeep(store.getState().active))
11 | break;
12 | default:
13 | break;
14 | }
15 | next(action);
16 | }
17 |
18 |
19 | export default frontendAfterActionsMiddleware;
--------------------------------------------------------------------------------
/viscoll-app/src/store/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, combineReducers, compose, applyMiddleware } from "redux";
2 | import { autoRehydrate } from 'redux-persist'
3 | import user from "../reducers/userReducer";
4 | import dashboard from "../reducers/dashboardReducer";
5 | import active from "../reducers/editCollationReducer";
6 | import global from "../reducers/globalReducer";
7 | import history from "../reducers/historyReducer";
8 | import axiosMiddleware from 'redux-axios-middleware';
9 | import { client, clientOptions } from './axiosConfig';
10 | import frontendBeforeActionsMiddleware from './middleware/frontendBeforeActionsMiddleware';
11 | import frontendAfterActionsMiddleware from './middleware/frontendAfterActionsMiddleware';
12 | import undoRedoMiddleware from "./middleware/undoRedoMiddleware";
13 |
14 | let storeEnhancers;
15 | if (process.env.NODE_ENV === 'development') {
16 | storeEnhancers = compose(
17 | applyMiddleware(
18 | axiosMiddleware(client, clientOptions),
19 | undoRedoMiddleware,
20 | frontendBeforeActionsMiddleware,
21 | frontendAfterActionsMiddleware,
22 | ),
23 | autoRehydrate(),
24 | // window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
25 | )
26 | } else {
27 | storeEnhancers = compose(
28 | applyMiddleware(
29 | axiosMiddleware(client, clientOptions),
30 | undoRedoMiddleware,
31 | frontendBeforeActionsMiddleware,
32 | frontendAfterActionsMiddleware,
33 | ),
34 | autoRehydrate()
35 | )
36 | }
37 |
38 | const store = createStore(
39 | combineReducers({
40 | user,
41 | dashboard,
42 | active,
43 | global,
44 | history
45 | }),
46 | {},
47 | storeEnhancers
48 | );
49 |
50 | export default store;
51 |
--------------------------------------------------------------------------------
/viscoll-app/src/styles/button.js:
--------------------------------------------------------------------------------
1 | export let btnLg = {
2 | buttonStyle: {
3 | height: 60,
4 | },
5 | labelStyle: {
6 | fontSize: window.innerWidth<=768?18:20,
7 | },
8 | overlayStyle: {
9 | paddingTop: 12,
10 | height: 48,
11 | }
12 | }
13 |
14 |
15 | export let btnMd = {
16 | buttonStyle: {
17 | height: 50,
18 | },
19 | labelStyle: {
20 | fontSize: window.innerWidth<=768?16:18,
21 | },
22 | overlayStyle: {
23 | paddingTop: 8,
24 | height: 42,
25 | }
26 | }
27 |
28 | export let btnAuthCancel = {
29 | labelStyle: {
30 | color: "#a5bde0",
31 | }
32 | }
33 |
34 |
35 | export let btnBase = () => {
36 | let fontSize = "0.9em";
37 | if (window.innerWidth<=1024) {
38 | fontSize = "0.8em";
39 | }
40 | if (window.innerWidth<=768) {
41 | fontSize = "0.7em";
42 | }
43 | return {
44 | labelStyle:{
45 | fontSize,
46 | },
47 | buttonStyle: {
48 | lineHeight: window.innerWidth<=768?"32px":"36px",
49 | },
50 | style: {
51 | minWidth: window.innerWidth<=1024?"30px":"78px",
52 | },
53 | }
54 | }
55 |
56 | export let radioBtnDark = () => {
57 | return {
58 | labelStyle: {
59 | color:"#ffffff",
60 | fontSize:window.innerWidth<=768?"0.6em":"0.9em",
61 | width:window.innerWidth<=768?"inherit":"",
62 | lineHeight: window.innerWidth<=768?"inherit":null,
63 | paddingTop:window.innerWidth<=768?5:null,
64 | },
65 | iconStyle: {
66 | fill:"#4ED6CB",
67 | marginRight:window.innerWidth<=768?"10px":"12px",
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/viscoll-app/src/styles/checkbox.js:
--------------------------------------------------------------------------------
1 | export let checkboxStyle = () => {
2 | let fontSize = null;
3 | if (window.innerWidth<=1024) {
4 | fontSize = "14px";
5 | }
6 | if (window.innerWidth<=768) {
7 | fontSize = "12px";
8 | }
9 | return {
10 | iconStyle:{
11 | height:window.innerWidth<=1024?15:20,
12 | width:window.innerWidth<=1024?15:20,
13 | marginTop:window.innerWidth<=1024?"2px":null,
14 | marginRight:window.innerWidth<=1024?"5px":"10px",
15 | },
16 | labelStyle: {
17 | fontSize,
18 | lineHeight:"21px",
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/viscoll-app/src/styles/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/viscoll-app/src/styles/infobox.js:
--------------------------------------------------------------------------------
1 | let fontSize = null;
2 | if (window.innerWidth<=768) {
3 | fontSize = "12px";
4 | } else if (window.innerWidth<=1024) {
5 | fontSize = "14px";
6 | }
7 |
8 | let infoBoxStyle = {
9 | tab: {
10 | color: '#6A6A6A',
11 | fontSize,
12 | },
13 | }
14 | export default infoBoxStyle;
--------------------------------------------------------------------------------
/viscoll-app/src/styles/light.js:
--------------------------------------------------------------------------------
1 | Object.defineProperty(exports, "__esModule", {
2 | value: true
3 | });
4 |
5 | var _colors = require('material-ui/styles/colors');
6 |
7 | var _colorManipulator = require('material-ui/utils/colorManipulator');
8 |
9 | var _spacing = require('material-ui/styles/spacing');
10 |
11 | var _spacing2 = _interopRequireDefault(_spacing);
12 |
13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
14 |
15 | exports.default = {
16 | spacing: _spacing2.default,
17 | fontFamily: 'Roboto, sans-serif',
18 | borderRadius: 2,
19 | palette: {
20 | primary1Color: '#526C91',
21 | primary2Color: '#3A4B55',
22 | primary3Color: _colors.grey400,
23 | accent1Color: '#4ED6CB',
24 | accent2Color: _colors.grey100,
25 | accent3Color: _colors.grey500,
26 | textColor: "#4e4e4e",
27 | secondaryTextColor: (0, _colorManipulator.fade)(_colors.darkBlack, 0.54),
28 | alternateTextColor: _colors.white,
29 | canvasColor: _colors.white,
30 | borderColor: _colors.grey300,
31 | disabledColor: (0, _colorManipulator.fade)(_colors.darkBlack, 0.3),
32 | pickerHeaderColor: _colors.cyan500,
33 | clockCircleColor: (0, _colorManipulator.fade)(_colors.darkBlack, 0.07),
34 | shadowColor: _colors.fullBlack
35 | },
36 | tableRow: {
37 | selectedColor: '#fff',
38 | },
39 | }; /**
40 | * NB: If you update this file, please also update `docs/src/app/customization/Themes.js`
41 | */
--------------------------------------------------------------------------------
/viscoll-app/src/styles/sidebar.js:
--------------------------------------------------------------------------------
1 | import light from "./light.js";
2 |
3 | let sidebarStyle = {
4 | panel: {
5 | main: {
6 | background: light.palette.primary2Color,
7 | boxShadow: "none",
8 |
9 | },
10 | title: {
11 | textTransform: "uppercase",
12 | fontSize: "1.1em"
13 | },
14 | text: {
15 | background: "rgba(82, 108, 145, 0.2)",
16 | overflowY: "auto",
17 | maxHeight: "40vh",
18 |
19 | }
20 | },
21 | }
22 | export default sidebarStyle;
--------------------------------------------------------------------------------
/viscoll-app/src/styles/tabular.js:
--------------------------------------------------------------------------------
1 | let tabularStyle = {
2 | group: {
3 | card: {
4 | marginBottom: 10,
5 | boxShadow: "0px 1px 2px 1px rgba(0,0,0,0.1)",
6 | paddingLeft: 0,
7 | border: "2px solid white"
8 | },
9 | cardHeader: {
10 | padding: 0,
11 | overflow: "hidden",
12 | },
13 | containerStyle: {
14 | paddingBottom: 0,
15 | paddingTop: 0
16 | },
17 | cardTextStyle: {
18 | paddingTop: 0,
19 | paddingBottom: 0
20 | }
21 | },
22 | leaf: {
23 | card: {
24 | marginBottom: 5,
25 | overflow: "hidden",
26 | textOverflow: "ellipsis",
27 | boxShadow: "0px 1px 1px 1px rgba(0,0,0,0.1)",
28 | border: "2px solid white"
29 | }
30 | }
31 | }
32 | export default tabularStyle;
--------------------------------------------------------------------------------
/viscoll-app/src/styles/textfield.js:
--------------------------------------------------------------------------------
1 | const floatFieldDark = {
2 | floatingLabelStyle: {
3 | color: "#a5bde0",
4 | },
5 | underlineStyle: {
6 | border: "1px solid #526C91",
7 | },
8 | underlineFocusStyle: {
9 | border: "1px solid #4ED6CB",
10 | },
11 | inputStyle: {
12 | color: "white",
13 | }
14 | }
15 | const floatFieldLight = {
16 | floatingLabelShrinkStyle: {color: "#526C91"},
17 | floatingLabelStyle: {color: "#6E6E6E"},
18 | hintStyle: {color: "#6E6E6E"},
19 | }
20 |
21 | export {
22 | floatFieldDark,
23 | floatFieldLight,
24 | }
--------------------------------------------------------------------------------
/viscoll-app/src/styles/topbar.js:
--------------------------------------------------------------------------------
1 | let topbarStyle = () => {
2 | let width = 200;
3 | if (window.innerWidth<=870) {
4 | width = 120;
5 | } else if (window.innerWidth<=1024) {
6 | width = 150;
7 | }
8 | return {
9 | tab: {
10 | width,
11 | height: 55,
12 | color: '#6A6A6A',
13 | fontSize: window.innerWidth<=870?"12px":null,
14 | },
15 | }
16 | }
17 | export default topbarStyle;
--------------------------------------------------------------------------------