From a825f598aec1826975b74253584e7286c36ffd83 Mon Sep 17 00:00:00 2001 From: Zafarelli Date: Tue, 28 Jun 2022 13:19:48 +0200 Subject: [PATCH 001/180] Upgrade Rails to V7 --- Gemfile | 30 +- Gemfile.lock | 313 ++++++++++-------- app/assets/config/manifest.js | 2 + config/initializers/assets.rb | 4 +- config/initializers/new_framework_defaults.rb | 2 +- db/schema.rb | 101 +++--- 6 files changed, 234 insertions(+), 218 deletions(-) diff --git a/Gemfile b/Gemfile index 3fa4d348..ed000dc5 100644 --- a/Gemfile +++ b/Gemfile @@ -7,30 +7,20 @@ end # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '~> 5.0.1' +gem 'rails', '~> 7.0.2' # Use sqlite3 as the database for Active Record -gem 'sqlite3', '~> 1.3', '< 1.4' +gem 'sqlite3', '~> 1.4.2' # Use Puma as the app server gem 'puma', '~> 3.0' # Use SCSS for stylesheets gem 'sass-rails', '~> 5.0' -# Use Uglifier as compressor for JavaScript assets -gem 'uglifier', '>= 1.3.0' -# Use CoffeeScript for .coffee assets and views -gem 'coffee-rails', '~> 4.2' -# See https://github.com/rails/execjs#readme for more supported runtimes -# gem 'therubyracer', platforms: :ruby - -# Use jquery as the JavaScript library -gem 'jquery-rails' + + # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks gem 'turbolinks', '~> 5' -# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder -gem 'jbuilder', '~> 2.5' -# Use Redis adapter to run Action Cable in production -# gem 'redis', '~> 3.0' + # Use ActiveModel has_secure_password - gem 'bcrypt', '~> 3.1.7' +gem 'bcrypt', '~> 3.1.7' # Use Capistrano for deployment gem 'capistrano-rails' @@ -44,10 +34,7 @@ end group :development do # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. gem 'web-console', '>= 3.3.0' - gem 'listen', '~> 3.0.5' - # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring - gem 'spring' - gem 'spring-watcher-listen', '~> 2.0.0' + gem 'listen', '~> 3.0.5' end group :production do @@ -60,4 +47,5 @@ gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] #Additional gems: gem 'rubyzip' gem 'bootstrap-sass', '~> 3.3.7' -gem 'visjs-rails', '~> 4.21.0.0' \ No newline at end of file +gem "importmap-rails", "~> 1.1" +gem "js-routes" diff --git a/Gemfile.lock b/Gemfile.lock index 6c62fc67..da4f5611 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,191 +1,221 @@ GEM remote: https://rubygems.org/ specs: - actioncable (5.0.7.1) - actionpack (= 5.0.7.1) - nio4r (>= 1.2, < 3.0) - websocket-driver (~> 0.6.1) - actionmailer (5.0.7.1) - actionpack (= 5.0.7.1) - actionview (= 5.0.7.1) - activejob (= 5.0.7.1) + actioncable (7.0.3) + actionpack (= 7.0.3) + activesupport (= 7.0.3) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailbox (7.0.3) + actionpack (= 7.0.3) + activejob (= 7.0.3) + activerecord (= 7.0.3) + activestorage (= 7.0.3) + activesupport (= 7.0.3) + mail (>= 2.7.1) + net-imap + net-pop + net-smtp + actionmailer (7.0.3) + actionpack (= 7.0.3) + actionview (= 7.0.3) + activejob (= 7.0.3) + activesupport (= 7.0.3) mail (~> 2.5, >= 2.5.4) + net-imap + net-pop + net-smtp rails-dom-testing (~> 2.0) - actionpack (5.0.7.1) - actionview (= 5.0.7.1) - activesupport (= 5.0.7.1) - rack (~> 2.0) - rack-test (~> 0.6.3) + actionpack (7.0.3) + actionview (= 7.0.3) + activesupport (= 7.0.3) + rack (~> 2.0, >= 2.2.0) + rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.7.1) - activesupport (= 5.0.7.1) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actiontext (7.0.3) + actionpack (= 7.0.3) + activerecord (= 7.0.3) + activestorage (= 7.0.3) + activesupport (= 7.0.3) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (7.0.3) + activesupport (= 7.0.3) builder (~> 3.1) - erubis (~> 2.7.0) + erubi (~> 1.4) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.0.7.1) - activesupport (= 5.0.7.1) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activejob (7.0.3) + activesupport (= 7.0.3) globalid (>= 0.3.6) - activemodel (5.0.7.1) - activesupport (= 5.0.7.1) - activerecord (5.0.7.1) - activemodel (= 5.0.7.1) - activesupport (= 5.0.7.1) - arel (~> 7.0) - activesupport (5.0.7.1) + activemodel (7.0.3) + activesupport (= 7.0.3) + activerecord (7.0.3) + activemodel (= 7.0.3) + activesupport (= 7.0.3) + activestorage (7.0.3) + actionpack (= 7.0.3) + activejob (= 7.0.3) + activerecord (= 7.0.3) + activesupport (= 7.0.3) + marcel (~> 1.0) + mini_mime (>= 1.1.0) + activesupport (7.0.3) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - airbrussh (1.3.1) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + airbrussh (1.4.0) sshkit (>= 1.6.1, != 1.7.0) - arel (7.1.4) - autoprefixer-rails (9.4.7) - execjs - bcrypt (3.1.12) - bindex (0.5.0) + autoprefixer-rails (10.4.7.0) + execjs (~> 2) + bcrypt (3.1.18) + bindex (0.8.1) bootstrap-sass (3.3.7) autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) - builder (3.2.3) - byebug (11.0.0) - capistrano (3.11.0) + builder (3.2.4) + byebug (11.1.3) + capistrano (3.17.0) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) sshkit (>= 1.9.0) - capistrano-bundler (1.5.0) + capistrano-bundler (2.1.0) capistrano (~> 3.1) - capistrano-passenger (0.2.0) + capistrano-passenger (0.2.1) capistrano (~> 3.0) - capistrano-rails (1.4.0) + capistrano-rails (1.6.2) capistrano (~> 3.1) - capistrano-bundler (~> 1.1) - coffee-rails (4.2.2) - coffee-script (>= 2.2.0) - railties (>= 4.0.0) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.12.2) - concurrent-ruby (1.1.4) - crass (1.0.4) - erubis (2.7.0) - execjs (2.7.0) - ffi (1.10.0) - globalid (0.4.2) - activesupport (>= 4.2.0) - i18n (1.5.3) + capistrano-bundler (>= 1.1, < 3) + concurrent-ruby (1.1.10) + crass (1.0.6) + digest (3.1.0) + erubi (1.10.0) + execjs (2.8.1) + ffi (1.15.5) + globalid (1.0.0) + activesupport (>= 5.0) + i18n (1.10.0) concurrent-ruby (~> 1.0) - jbuilder (2.8.0) - activesupport (>= 4.2.0) - multi_json (>= 1.2) - jquery-rails (4.3.3) - rails-dom-testing (>= 1, < 3) - railties (>= 4.2.0) - thor (>= 0.14, < 2.0) + importmap-rails (1.1.2) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + js-routes (2.2.4) + railties (>= 4) listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - loofah (2.2.3) + loofah (2.18.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) mini_mime (>= 0.1.1) - method_source (0.9.2) - mini_mime (1.0.1) - mini_portile2 (2.4.0) - minitest (5.11.3) - multi_json (1.13.1) - mysql2 (0.5.2) - net-scp (1.2.1) - net-ssh (>= 2.6.5) - net-ssh (5.1.0) - nio4r (2.3.1) - nokogiri (1.10.1) - mini_portile2 (~> 2.4.0) - puma (3.12.0) - rack (2.0.6) - rack-test (0.6.3) - rack (>= 1.0) - rails (5.0.7.1) - actioncable (= 5.0.7.1) - actionmailer (= 5.0.7.1) - actionpack (= 5.0.7.1) - actionview (= 5.0.7.1) - activejob (= 5.0.7.1) - activemodel (= 5.0.7.1) - activerecord (= 5.0.7.1) - activesupport (= 5.0.7.1) - bundler (>= 1.3.0) - railties (= 5.0.7.1) - sprockets-rails (>= 2.0.0) + marcel (1.0.2) + method_source (1.0.0) + mini_mime (1.1.2) + minitest (5.16.1) + mysql2 (0.5.4) + net-imap (0.2.3) + digest + net-protocol + strscan + net-pop (0.1.1) + digest + net-protocol + timeout + net-protocol (0.1.3) + timeout + net-scp (3.0.0) + net-ssh (>= 2.6.5, < 7.0.0) + net-smtp (0.3.1) + digest + net-protocol + timeout + net-ssh (6.1.0) + nio4r (2.5.8) + nokogiri (1.13.6-x86_64-linux) + racc (~> 1.4) + puma (3.12.6) + racc (1.6.0) + rack (2.2.3.1) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (7.0.3) + actioncable (= 7.0.3) + actionmailbox (= 7.0.3) + actionmailer (= 7.0.3) + actionpack (= 7.0.3) + actiontext (= 7.0.3) + actionview (= 7.0.3) + activejob (= 7.0.3) + activemodel (= 7.0.3) + activerecord (= 7.0.3) + activestorage (= 7.0.3) + activesupport (= 7.0.3) + bundler (>= 1.15.0) + railties (= 7.0.3) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.0.4) - loofah (~> 2.2, >= 2.2.2) - railties (5.0.7.1) - actionpack (= 5.0.7.1) - activesupport (= 5.0.7.1) + rails-html-sanitizer (1.4.3) + loofah (~> 2.3) + railties (7.0.3) + actionpack (= 7.0.3) + activesupport (= 7.0.3) method_source - rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - rake (12.3.2) - rb-fsevent (0.10.3) - rb-inotify (0.10.0) + rake (>= 12.2) + thor (~> 1.0) + zeitwerk (~> 2.5) + rake (13.0.6) + rb-fsevent (0.11.1) + rb-inotify (0.10.1) ffi (~> 1.0) - rubyzip (1.2.2) - sass (3.7.3) + rubyzip (2.3.2) + sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sass-rails (5.0.7) - railties (>= 4.0.0, < 6) + sass-rails (5.1.0) + railties (>= 5.2.0) sass (~> 3.1) sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) - spring (2.0.2) - activesupport (>= 4.2) - spring-watcher-listen (2.0.1) - listen (>= 2.7, < 4.0) - spring (>= 1.2, < 3.0) sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) sprockets (>= 3.0.0) - sqlite3 (1.3.13) - sshkit (1.18.2) + sqlite3 (1.4.4) + sshkit (1.21.2) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) - thor (0.20.3) - thread_safe (0.3.6) - tilt (2.0.9) - turbolinks (5.2.0) + strscan (3.0.3) + thor (1.2.1) + tilt (2.0.10) + timeout (0.3.0) + turbolinks (5.2.1) turbolinks-source (~> 5.2) turbolinks-source (5.2.0) - tzinfo (1.2.5) - thread_safe (~> 0.1) - uglifier (4.1.20) - execjs (>= 0.3.0, < 3) - visjs-rails (4.21.0.0) - web-console (3.7.0) - actionview (>= 5.0) - activemodel (>= 5.0) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) bindex (>= 0.4.0) - railties (>= 5.0) - websocket-driver (0.6.5) + railties (>= 6.0.0) + websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.3) + websocket-extensions (0.1.5) + zeitwerk (2.6.0) PLATFORMS - ruby + x86_64-linux DEPENDENCIES bcrypt (~> 3.1.7) @@ -193,23 +223,18 @@ DEPENDENCIES byebug capistrano-passenger capistrano-rails - coffee-rails (~> 4.2) - jbuilder (~> 2.5) - jquery-rails + importmap-rails (~> 1.1) + js-routes listen (~> 3.0.5) mysql2 puma (~> 3.0) - rails (~> 5.0.1) + rails (~> 7.0.2) rubyzip sass-rails (~> 5.0) - spring - spring-watcher-listen (~> 2.0.0) - sqlite3 (~> 1.3, < 1.4) + sqlite3 (~> 1.4.2) turbolinks (~> 5) tzinfo-data - uglifier (>= 1.3.0) - visjs-rails (~> 4.21.0.0) web-console (>= 3.3.0) BUNDLED WITH - 2.0.1 + 2.3.16 diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index b16e53d6..ab693a12 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1,3 +1,5 @@ //= link_tree ../images //= link_directory ../javascripts .js //= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index d2c8b4c1..6214fc69 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -8,4 +8,6 @@ # Precompile additional assets. # application.js, application.css.sass, and all non-JS/CSS in app/assets folder are already added. -Rails.application.config.assets.precompile += %w( timeline/* network/* jquery.simplecolorpicker.*) \ No newline at end of file +Rails.application.config.assets.precompile += %w( timeline/* network/* jquery.simplecolorpicker.* ) +Rails.application.config.assets.precompile += %w( ConceptMap.js) +Rails.application.config.assets.precompile += %w( entrypoint.js application.js Intro.js) diff --git a/config/initializers/new_framework_defaults.rb b/config/initializers/new_framework_defaults.rb index 671abb69..33693339 100644 --- a/config/initializers/new_framework_defaults.rb +++ b/config/initializers/new_framework_defaults.rb @@ -18,7 +18,7 @@ Rails.application.config.active_record.belongs_to_required_by_default = true # Do not halt callback chains when a callback returns false. Previous versions had true. -ActiveSupport.halt_callback_chains_on_return_false = false +#ActiveSupport.halt_callback_chains_on_return_false = false # Configure SSL options to enable HSTS with subdomains. Previous versions had false. Rails.application.config.ssl_options = { hsts: { subdomains: true } } diff --git a/db/schema.rb b/db/schema.rb index 641bd16c..c1e0c387 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -2,79 +2,78 @@ # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20190213154714) do - +ActiveRecord::Schema[7.0].define(version: 2019_02_13_154714) do create_table "concept_maps", force: :cascade do |t| - t.string "code" - t.integer "accesses" - t.integer "survey_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "code" + t.integer "accesses" + t.integer "survey_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false end create_table "concepts", force: :cascade do |t| - t.string "label" - t.float "x" - t.float "y" - t.integer "concept_map_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.text "color" + t.string "label" + t.float "x" + t.float "y" + t.integer "concept_map_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.text "color" end create_table "links", force: :cascade do |t| - t.integer "start_id" - t.integer "end_id" - t.string "label" - t.integer "concept_map_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "start_id" + t.integer "end_id" + t.string "label" + t.integer "concept_map_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false end create_table "projects", force: :cascade do |t| - t.string "name" - t.text "description" - t.integer "user_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "name" + t.text "description" + t.integer "user_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false end create_table "surveys", force: :cascade do |t| - t.string "name" - t.text "description" - t.string "code" - t.text "introduction" - t.text "association_labels" - t.text "concept_labels" - t.text "initial_map" - t.date "start_date" - t.date "end_date" - t.integer "project_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "name" + t.text "description" + t.string "code" + t.text "introduction" + t.text "association_labels" + t.text "concept_labels" + t.text "initial_map" + t.date "start_date" + t.date "end_date" + t.integer "project_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false end create_table "users", force: :cascade do |t| - t.string "email" - t.string "password_digest" - t.string "capabilities" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "email" + t.string "password_digest" + t.string "capabilities" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false end create_table "versions", force: :cascade do |t| - t.integer "concept_map_id" - t.text "map" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "concept_map_id" + t.text "map" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false end end From 3fcf30a62f9f7f5cb91ee54f39cb917215c231b5 Mon Sep 17 00:00:00 2001 From: Zafarelli Date: Thu, 30 Jun 2022 09:10:09 +0200 Subject: [PATCH 002/180] Migrate to Rails 7 and accompanying changes --- Dockerfile | 30 ++ Gemfile | 1 + Gemfile.lock | 4 + .../{application.js => application.js.bak} | 3 - app/assets/stylesheets/application.css.sass | 2 +- app/javascript/ConceptMap.js | 456 ++++++++++++++++++ app/javascript/application.js | 2 + app/javascript/entrypoint.js | 29 ++ app/models/link.rb | 4 +- app/models/survey.rb | 2 +- app/views/concept_maps/_form.html.erb | 3 +- app/views/concept_maps/_form_import.html.erb | 9 +- app/views/concept_maps/_index.html.erb | 3 +- app/views/concept_maps/_preview.html.erb | 4 +- app/views/concept_maps/edit.html.erb | 381 +-------------- .../concept_maps/editor/_colorpicker.html.erb | 36 +- app/views/concept_maps/editor/_intro.html.erb | 30 +- app/views/concepts/_concept.html.erb | 21 +- app/views/concepts/_concept.json.erb | 1 + app/views/layouts/application.html.erb | 1 + app/views/layouts/login.html.erb | 6 + app/views/links/_link.json.erb | 1 + app/views/projects/_form.html.erb | 3 +- app/views/projects/_form_import.html.erb | 3 +- app/views/projects/_new.html.erb | 2 +- app/views/projects/new.html.erb | 1 + app/views/surveys/_form_basic.html.erb | 17 +- app/views/surveys/_form_import.html.erb | 9 +- app/views/versions/_show.html.erb | 18 +- app/views/versions/_timeline.html.erb | 5 +- app/views/versions/show.js.erb | 5 +- bin/importmap | 4 + config/importmap.rb | 19 + config/routes.rb | 2 +- docker-compose.yml | 16 + entrypoint.sh | 8 + vendor/javascript/.keep | 0 37 files changed, 688 insertions(+), 453 deletions(-) create mode 100644 Dockerfile rename app/assets/javascripts/{application.js => application.js.bak} (92%) create mode 100644 app/javascript/ConceptMap.js create mode 100644 app/javascript/application.js create mode 100644 app/javascript/entrypoint.js create mode 100644 app/views/concepts/_concept.json.erb create mode 100644 app/views/links/_link.json.erb create mode 100644 app/views/projects/new.html.erb create mode 100755 bin/importmap create mode 100644 config/importmap.rb create mode 100644 docker-compose.yml create mode 100755 entrypoint.sh create mode 100644 vendor/javascript/.keep diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..e44a0bc8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +FROM ruby:3.1.2-slim + +RUN apt-get update -qq && apt-get install -yq --no-install-recommends \ + build-essential \ + gnupg2 \ + git \ + libmariadb-dev \ + sqlite3 \ + libsqlite3-dev \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + + + +ENV LANG=C.UTF-8 \ + BUNDLE_JOBS=4 \ + BUNDLE_RETRY=3 + +WORKDIR /usr/src/app +COPY Gemfile /Gemfile +COPY Gemfile.lock /Gemfile.lock + +RUN gem update --system && gem install bundler && bundle install + +COPY entrypoint.sh /usr/bin/ +RUN chmod +x /usr/bin/entrypoint.sh +ENTRYPOINT ["sh", "/usr/bin/entrypoint.sh"] + +EXPOSE 3000 + +CMD ["bundle", "exec", "rails", "s", "-b", "0.0.0.0"] diff --git a/Gemfile b/Gemfile index ed000dc5..617e76f2 100644 --- a/Gemfile +++ b/Gemfile @@ -49,3 +49,4 @@ gem 'rubyzip' gem 'bootstrap-sass', '~> 3.3.7' gem "importmap-rails", "~> 1.1" gem "js-routes" +gem 'mime-types', '~> 3.1' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index da4f5611..89c48eb0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -114,6 +114,9 @@ GEM mini_mime (>= 0.1.1) marcel (1.0.2) method_source (1.0.0) + mime-types (3.4.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2022.0105) mini_mime (1.1.2) minitest (5.16.1) mysql2 (0.5.4) @@ -226,6 +229,7 @@ DEPENDENCIES importmap-rails (~> 1.1) js-routes listen (~> 3.0.5) + mime-types (~> 3.1) mysql2 puma (~> 3.0) rails (~> 7.0.2) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js.bak similarity index 92% rename from app/assets/javascripts/application.js rename to app/assets/javascripts/application.js.bak index e039c882..f7e2ee4e 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js.bak @@ -10,9 +10,6 @@ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details // about supported directives. // -//= require jquery -//= require jquery_ujs //= require turbolinks -//= require vis //= require bootstrap-sprockets //= require_tree . diff --git a/app/assets/stylesheets/application.css.sass b/app/assets/stylesheets/application.css.sass index 86105ed8..bc19535c 100644 --- a/app/assets/stylesheets/application.css.sass +++ b/app/assets/stylesheets/application.css.sass @@ -16,4 +16,4 @@ @import "bootstrap-sprockets" @import "bootstrap" -@import "vis" + diff --git a/app/javascript/ConceptMap.js b/app/javascript/ConceptMap.js new file mode 100644 index 00000000..dc77e4e6 --- /dev/null +++ b/app/javascript/ConceptMap.js @@ -0,0 +1,456 @@ +import "vis-network" +import "jquery" + +class ConceptMap { + + static none = 0 + static addNode = 1 + static editNode = 2 + static addEdge = 3 + static editEdge = 4 + static dragNode = 5 + + constructor({ edgeData, nodeData, conceptsPath, linksPath, dialogTexts }) { + + this.edges = new vis.DataSet(edgeData) + this.nodes = new vis.DataSet(nodeData) + this.conceptsPath = conceptsPath + this.linksPath = linksPath + this.dialogTexts = dialogTexts + + this.data = { + nodes: this.nodes, + edges: this.edges + } + + this.container = $('#map-canvas')[0] + + this.mode = ConceptMap.none + this.canvasX = 0 + this.canvasY = 0 + this.id = 0 + + this.options = { + autoResize: true, + height: '100%', + width: '100%', + edges: { + arrows: { + to: { + enabled: true, + scaleFactor: 0.75 + }, + }, + smooth: false + }, + physics: { + enabled: false + }, + interaction: { + hover: true, + navigationButtons: true, + selectConnectedEdges: false, + hoverConnectedEdges: false, + } + + } + + this.network = new vis.Network(this.container, this.data, this.options) + + $('#context-help-text').html($('#ch_normal').html()) + + // close dialog on escape + $("#entry_concept").on('keyup', function (e) { + if (e.keyCode == 27) + hideForm() + }) + + // close dialog on escape + $("#entry_link").on('keyup', function (e) { + if (e.keyCode == 27) + hideForm() + }) + + this.network.on("hoverNode", () => { + if (this.mode == ConceptMap.none) { + $('#context-help-text').html($('#ch_hovernode').html()) + } + this.network.canvas.body.container.style.cursor = 'pointer' + }) + this.network.on("hoverEdge", () => { + if (this.mode == ConceptMap.none) { + $('#context-help-text').html($('#ch_hoveredge').html()) + } + this.network.canvas.body.container.style.cursor = 'pointer' + }) + + this.network.on("blurNode", () => { + if (this.mode == ConceptMap.none) { + $('#context-help-text').html($('#ch_normal').html()) + } + this.network.canvas.body.container.style.cursor = 'default' + }) + this.network.on("blurEdge", () => { + if (this.mode == ConceptMap.none) { + $('#context-help-text').html($('#ch_normal').html()) + } + this.network.canvas.body.container.style.cursor = 'default' + }) + + + /********************************* + * Network drag: save new node position + ********************************/ + this.network.on("dragStart", (params) => { + if (params.nodes.length > 0) { + this.id = params.nodes[0] + this.mode = ConceptMap.dragNode + } + }) + + this.network.on("dragEnd", (params) => { + switch (this.mode) { + case ConceptMap.dragNode: + $.ajax({ + type: "PUT", + url: this.conceptsPath + "/" + this.id, + headers: { + 'Accept': 'text/javascript', + 'X-Requested-With': 'XMLHttpRequest', + 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content') + }, + data: { "concept": { 'label': this.nodes.get(this.id).label, 'x': params.pointer.canvas.x, 'y': params.pointer.canvas.y } } + }).always(() => { + this.mode = ConceptMap.none + this.id = undefined + }) + this.hideForm() + } + }) + + /********************************* + * Network release: edit node or edge + ********************************/ + this.network.on("release", (params) => { + if (this.mode === ConceptMap.none) { + if (params.edges.length) { + this.editEdge(params) + } else if (params.nodes.length) { + this.editNode(params) + } + + } + }) + + /********************************* + * Network click: create an edge if a node was previously held, or cancel edge creation + ********************************/ + this.network.on("click", params => { + if (this.mode === ConceptMap.addEdge && params.nodes.length) { + this.createEdge(params) + } else if (this.mode === ConceptMap.addEdge && !params.nodes.length) { + this.mode = ConceptMap.none + this.id = undefined + } + }) + + /********************************* + * Network hold: start edge creation or create a node + ********************************/ + this.network.on("hold", (params) => { + if (this.mode == ConceptMap.none) { + if (params.nodes.length > 0) { + this.id = params.nodes[0] + this.mode = ConceptMap.addEdge + } else { + this.createNode(params) + } + } + }) + } + + /********************************* + * create node + ********************************/ + createNode = (params) => { + const canvasX = params.pointer.canvas.x + const canvasY = params.pointer.canvas.y + this.mode = ConceptMap.addNode + this.showForm(canvasX, canvasY) + } + + /********************************* + * edit node + ********************************/ + editNode = (params) => { + let canvasX + let canvasY + + this.id = params.nodes[0] + + const currentNode = this.nodes.get(this.id) + if (currentNode && currentNode.label !== "") { + canvasX = currentNode.x + canvasY = currentNode.y + this.mode = ConceptMap.editNode + this.showForm(canvasX, canvasY) + } + } + + /********************************* + * create edge + ********************************/ + createEdge = (params) => { + if (params.nodes.length > 0) { + $('#start').val(this.id) + $('#end').val(params.nodes[0]) + $('#context-help-text').html($('#ch_addedge').html()).removeClass("hidden") + const startNode = this.nodes.get(this.id) + const endNode = this.nodes.get(params.nodes[0]) + const canvasX = Math.min(startNode.x, endNode.x) + Math.abs(startNode.x - endNode.x) / 2 + const canvasY = Math.min(startNode.y, endNode.y) + Math.abs(startNode.y - endNode.y) / 2 + + this.showForm(canvasX, canvasY) + } + else { + this.hideForm() + } + } + + /********************************* + * edit edge + ********************************/ + editEdge = (params) => { + if (params.edges.length > 0) { + this.id = params.edges[0] + const canvasX = params.pointer.canvas.x + const canvasY = params.pointer.canvas.y + this.mode = ConceptMap.editEdge + this.showForm(canvasX, canvasY) + } + } + + /********************************* + * delete nodes or edges + ********************************/ + destroy = () => { + switch (this.mode) { + case ConceptMap.editNode: + $.ajax({ + headers: { + 'Accept': 'text/javascript', + 'X-Requested-With': 'XMLHttpRequest', + 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content') + }, + type: "DELETE", url: this.conceptsPath + "/" + this.id + }).always(() => { + this.mode = ConceptMap.none + this.id = undefined + }) + break + case ConceptMap.editEdge: + $.ajax({ + headers: { + 'Accept': 'text/javascript', + 'X-Requested-With': 'XMLHttpRequest', + 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content') + }, + type: "DELETE", url: this.linksPath + "/" + this.id + }).always(() => { + this.mode = ConceptMap.none + this.id = undefined + }) + break + } + this.hideForm() + } + + /********************************* + * validate inputs: check for duplicated node names + ********************************/ + validateForm = () => { + if (this.mode == ConceptMap.addNode || this.mode == ConceptMap.editNode) { + var t = $('#entry_concept').val() + + const node = this.nodes.get({ + filter: function (item) { + return (item.label.toLocaleLowerCase() === t.toLocaleLowerCase()) + } + }) + if (node == null || node.length == 0 || node[0].id == this.id) + return true + else { + this.network.focus(node[0].id) + return false + } + } else { + return true + } + + } + + /********************************* + * focus element + ********************************/ + focus = (id) => { + setTimeout(function () { + $(id).focus() + }, 100) + } + + /********************************* + * put or post after editing/adding a node or an edge + ********************************/ + onSubmit = () => { + const postObj = {} + let method = "put" + let path + + switch (this.mode) { + + case ConceptMap.addNode: + method = "post" + case ConceptMap.editNode: + postObj["concept[x]"] = $("#x").val() + postObj["concept[y]"] = $("#y").val() + postObj["concept[label]"] = $("#entry_concept").val() + postObj["concept[color]"] = $("#color").val() + path = this.conceptsPath + break + case ConceptMap.addEdge: + method = "post" + postObj["link[start]"] = $("#start").val() + postObj["link[end]"] = $("#end").val() + case ConceptMap.editEdge: + postObj["link[label]"] = $("#entry_link").val() + path = this.linksPath + break + default: + // Nothing to submit, really. Shouldn't happen, but you never know. + return + } + + $.ajax({ + headers: { + 'Accept': 'text/javascript', + 'X-Requested-With': 'XMLHttpRequest', + 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content') + }, + type: method, + url: path + (method === "put" ? "/" + this.id : ""), + data: postObj + }).always(() => { + this.hideForm() + }) + } + + /********************************* + * color picker stuff + ********************************/ + standardizeColor = (color) => { + var ctx = document.createElement('canvas').getContext('2d') + ctx.fillStyle = color + return ctx.fillStyle + } + + changeColor = (id) => { + $('#currentColor').css('background-color', $('#color' + id).css('background-color')) + var color = $('#currentColor').css('background-color') + $("#color").attr("value", this.standardizeColor(color)) + for (var i = 1; i <= 6; ++i) + $('#color' + i).html("") + $('#color' + id).html("") + $("#entry_concept").focus() + $("#colorSelect").css("display", "none") + } + + selectColor = (color) => { + for (var i = 1; i <= 6; ++i) { + if (this.standardizeColor($('#color' + i).css('background-color')) === color) { + this.changeColor(i) + } + } + } + + + initNodeInputs = (canvasX, canvasY) => { + $("#entry_concept").removeClass("hidden") + $("#colorpicker").removeClass("hidden") + $("#entry_link").addClass("hidden") + this.mode === ConceptMap.addNode && $("#delete").addClass("hidden") + this.mode === ConceptMap.editNode && $("#delete").removeClass("hidden") + $("#x").attr("value", canvasX) + $("#y").attr("value", canvasY) + this.focus("#entry_concept") + } + + initEdgeInputs = (canvasX, canvasY) => { + $("#entry_link").removeClass("hidden") + $("#entry_concept").addClass("hidden") + $("#colorpicker").addClass("hidden") + this.mode === ConceptMap.addEdge && $("#delete").addClass("hidden") + this.mode === ConceptMap.editEdge && $("#delete").removeClass("hidden") + $("#x").attr("value", canvasX) + $("#y").attr("value", canvasY) + this.focus("#entry_link") + } + + showForm = (canvasX, canvasY) => { + // attach close handler for clicks elsewhere + this.network.once("click", (params) => { + if (!params.nodes.length && !params.edges.length) { + this.hideForm() + } + }) + + $("#panel") + .removeClass("hidden") + .attr("style", "z-index: 1; position:absolute;left:" + ($("#map-canvas").offset().left + this.network.canvasToDOM({ x: canvasX, y: canvasY }).x - $("#form").width() / 2) + "px;top:" + ($("#map-canvas").offset().top + this.network.canvasToDOM({ x: canvasX, y: canvasY }).y - $("#form").height() / 2) + "px;") + switch (this.mode) { + case ConceptMap.addNode: + $('#context-help-text').html($('#ch_new').html()) + $('#action').html(this.dialogTexts.addNode) + $("#entry_concept").val("") + this.initNodeInputs(canvasX, canvasY) + break + case ConceptMap.editNode: + $('#context-help-text').html($('#ch_edit').html()) + $('#action').html(this.dialogTexts.editNode) + $("#entry_concept").val(this.nodes.get(this.id).label) + this.initNodeInputs(canvasX, canvasY) + this.selectColor(this.nodes.get(this.id).color.background) + break + case ConceptMap.editEdge: + $('#context-help-text').html($('#ch_edit').html()) + $('#action').html(this.dialogTexts.editEdge) + $("#entry_link").val(this.edges.get(this.id).label) + this.initEdgeInputs(canvasX, canvasY) + break + case ConceptMap.addEdge: + $('#context-help-text').html($('#ch_new').html()) + $('#action').html(this.dialogTexts.addEdge) + $("#entry_link").val("") + this.initEdgeInputs(canvasX, canvasY) + } + } + + //Edit/Create Aktion beenden + hideForm = () => { + $("#panel").addClass("hidden") + $("#panel").focusout() + this.network.unselectAll() + $('#context-help-text').html($('#ch_normal').html()) + //Zoom-Out by Mobilgeräten veranlassen + const viewport = document.querySelector('meta[name="viewport"]') + if (viewport) { + viewport.content = 'initial-scale=1' + viewport.content = 'width=device-width' + viewport.content = 'maximum-scale=1' + } + + this.mode = ConceptMap.none + } +} + + +export default ConceptMap \ No newline at end of file diff --git a/app/javascript/application.js b/app/javascript/application.js new file mode 100644 index 00000000..e5b0549b --- /dev/null +++ b/app/javascript/application.js @@ -0,0 +1,2 @@ +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +//import "@rails/ujs" \ No newline at end of file diff --git a/app/javascript/entrypoint.js b/app/javascript/entrypoint.js new file mode 100644 index 00000000..7764b886 --- /dev/null +++ b/app/javascript/entrypoint.js @@ -0,0 +1,29 @@ +import ConceptMap from "./ConceptMap" +import "jquery" + +import "vis-network" +import "bootstrap" + +export let cm + +export const init = (edgeData, nodeData) => { + cm = new ConceptMap(edgeData, nodeData) + + // expose handlers needed for DOM events + window.hideForm = cm.hideForm + window.validateForm = cm.validateForm + window.destroy = cm.destroy + window.submitChanges = (event) => { + event.preventDefault() + event.stopPropagation() + cm.onSubmit() + return false + } + window.edges = cm.edges + window.nodes = cm.nodes + window.changeColor = cm.changeColor +} + +export const initJQuery = () => { + return $ +} diff --git a/app/models/link.rb b/app/models/link.rb index dc5ebecd..8055c253 100644 --- a/app/models/link.rb +++ b/app/models/link.rb @@ -2,6 +2,6 @@ class Link < ApplicationRecord validates :label, presence: true belongs_to :concept_map - belongs_to :start, class_name: Concept - belongs_to :end, class_name: Concept + belongs_to :start, class_name: Concept.to_s + belongs_to :end, class_name: Concept.to_s end diff --git a/app/models/survey.rb b/app/models/survey.rb index 34fa2c19..087fc328 100644 --- a/app/models/survey.rb +++ b/app/models/survey.rb @@ -2,7 +2,7 @@ class Survey < ApplicationRecord validates :name, presence: true - validates :code, uniqueness: true, if: 'code.present?' + validates :code, uniqueness: true, if: -> {code.present?} belongs_to :project has_many :concept_maps, dependent: :destroy diff --git a/app/views/concept_maps/_form.html.erb b/app/views/concept_maps/_form.html.erb index 580e010f..93fe0352 100644 --- a/app/views/concept_maps/_form.html.erb +++ b/app/views/concept_maps/_form.html.erb @@ -16,7 +16,8 @@ - \ No newline at end of file diff --git a/app/views/concept_maps/_index.html.erb b/app/views/concept_maps/_index.html.erb index d71908c1..83753564 100644 --- a/app/views/concept_maps/_index.html.erb +++ b/app/views/concept_maps/_index.html.erb @@ -10,7 +10,8 @@ <%= render 'concept_maps/maps' %> - diff --git a/app/views/concept_maps/editor/_colorpicker.html.erb b/app/views/concept_maps/editor/_colorpicker.html.erb index 63ecc8d1..ebb88172 100644 --- a/app/views/concept_maps/editor/_colorpicker.html.erb +++ b/app/views/concept_maps/editor/_colorpicker.html.erb @@ -1,8 +1,8 @@ -