├── .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? "":
38 | this.onChange(v,"email")} name="email" floatingLabelText="E-mail" {...floatFieldDark} /> 39 |

40 | this.resendConfirmation()} 46 | /> 47 | ; 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 |
52 |

{this.state.resetMessage}

53 | this.onInputChange(v, "password")} name="password" type="password" floatingLabelText="New Password" {...floatFieldDark} /> 54 | this.onInputChange(v, "passwordConfirm")} name="passwordConfirm" type="password" floatingLabelText="Confirm New Password" {...floatFieldDark} /> 55 |

56 | this.submit(null)} 64 | /> 65 | 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 |
38 | 45 |
46 | this.props.previousStep()} 50 | /> 51 | this.submit(null)} 58 | /> 59 |
60 | 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 |
17 |
18 | 0} 25 | onChange={(event, newValue) => props.set("title", newValue)} 26 | fullWidth 27 | /> 28 | 0} 35 | onChange={(event, newValue) => props.set("shelfmark", newValue)} 36 | fullWidth 37 | /> 38 | 0} 45 | onChange={(event, newValue) => props.set("date", newValue)} 46 | fullWidth 47 | /> 48 |
49 |
50 | props.previousStep()} 53 | aria-label="Back" 54 | /> 55 | 62 |
63 |
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 = 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 = logo ; 8 | return ( 9 | 16 | {logo} 17 | 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
8 |
9 |

404!

10 |

Well, this isn't where you parked your car.

11 | 12 | this.props.history.push("/dashboard")} 16 | /> 17 |
18 |
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 | 27 | Something has gone wrong likely having to do with internets and tubes.
28 | Re-login into your account to get back to business.
29 |
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 | 34 | 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; --------------------------------------------------------------------------------