From 9271fdb3c359b7f9ce94bf3f553655f5c3b87106 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Fri, 10 Feb 2023 16:09:44 +0100 Subject: [PATCH 001/291] Created migrations for annotations. --- .../20221015114100_create_annotations.rb | 15 +++++++++++++ ...124508_add_annotations_status_to_medium.rb | 5 +++++ ...24528_add_annotations_status_to_lecture.rb | 5 +++++ db/schema.rb | 21 +++++++++++++++++-- 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100755 db/migrate/20221015114100_create_annotations.rb create mode 100755 db/migrate/20230117124508_add_annotations_status_to_medium.rb create mode 100755 db/migrate/20230117124528_add_annotations_status_to_lecture.rb diff --git a/db/migrate/20221015114100_create_annotations.rb b/db/migrate/20221015114100_create_annotations.rb new file mode 100755 index 000000000..355b2ece7 --- /dev/null +++ b/db/migrate/20221015114100_create_annotations.rb @@ -0,0 +1,15 @@ +class CreateAnnotations < ActiveRecord::Migration[7.0] + def change + create_table :annotations do |t| + t.references :medium, null: false, foreign_key: true + t.references :user, null: false, foreign_key: true + t.string :timestamp + t.text :comment + t.integer :category + t.boolean :visible_for_teacher + t.string :color + + t.timestamps + end + end +end diff --git a/db/migrate/20230117124508_add_annotations_status_to_medium.rb b/db/migrate/20230117124508_add_annotations_status_to_medium.rb new file mode 100755 index 000000000..6de139be6 --- /dev/null +++ b/db/migrate/20230117124508_add_annotations_status_to_medium.rb @@ -0,0 +1,5 @@ +class AddAnnotationsStatusToMedium < ActiveRecord::Migration[7.0] + def change + add_column :media, :annotations_status, :integer + end +end diff --git a/db/migrate/20230117124528_add_annotations_status_to_lecture.rb b/db/migrate/20230117124528_add_annotations_status_to_lecture.rb new file mode 100755 index 000000000..3039866d7 --- /dev/null +++ b/db/migrate/20230117124528_add_annotations_status_to_lecture.rb @@ -0,0 +1,5 @@ +class AddAnnotationsStatusToLecture < ActiveRecord::Migration[7.0] + def change + add_column :lectures, :annotations_status, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index a0d5c9e3c..c14c66685 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,12 +10,25 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2022_01_25_162730) do - +ActiveRecord::Schema[7.0].define(version: 2023_01_17_124528) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" + create_table "annotations", force: :cascade do |t| + t.bigint "medium_id", null: false + t.bigint "user_id", null: false + t.string "timestamp" + t.text "comment" + t.integer "category" + t.boolean "visible_for_teacher" + t.string "color" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["medium_id"], name: "index_annotations_on_medium_id" + t.index ["user_id"], name: "index_annotations_on_user_id" + end + create_table "announcements", force: :cascade do |t| t.bigint "lecture_id" t.bigint "announcer_id" @@ -273,6 +286,7 @@ t.integer "submission_max_team_size" t.integer "submission_grace_period", default: 15 t.boolean "legacy_seminar", default: false + t.integer "annotations_status" t.index ["teacher_id"], name: "index_lectures_on_teacher_id" t.index ["term_id"], name: "index_lectures_on_term_id" end @@ -351,6 +365,7 @@ t.datetime "released_at", precision: nil t.text "publisher" t.datetime "file_last_edited", precision: nil + t.integer "annotations_status" t.index ["quizzable_type", "quizzable_id"], name: "index_media_on_quizzable_type_and_quizzable_id" t.index ["teachable_type", "teachable_id"], name: "index_media_on_teachable_type_and_teachable_id" end @@ -898,6 +913,8 @@ t.index ["watchlist_entry_id"], name: "index_watchlists_on_watchlist_entry_id" end + add_foreign_key "annotations", "media" + add_foreign_key "annotations", "users" add_foreign_key "announcements", "lectures" add_foreign_key "announcements", "users", column: "announcer_id" add_foreign_key "assignments", "lectures" From a5530ff2c2398061715c13028bf40ce4a6a4a4d0 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Fri, 10 Feb 2023 16:12:00 +0100 Subject: [PATCH 002/291] Added/changed abilities for annotations. Changed permission of migrations to 100644. --- app/abilities/annotation_ability.rb | 8 ++++++++ app/abilities/medium_ability.rb | 5 +++-- db/migrate/20221015114100_create_annotations.rb | 0 .../20230117124508_add_annotations_status_to_medium.rb | 0 .../20230117124528_add_annotations_status_to_lecture.rb | 0 5 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 app/abilities/annotation_ability.rb mode change 100755 => 100644 db/migrate/20221015114100_create_annotations.rb mode change 100755 => 100644 db/migrate/20230117124508_add_annotations_status_to_medium.rb mode change 100755 => 100644 db/migrate/20230117124528_add_annotations_status_to_lecture.rb diff --git a/app/abilities/annotation_ability.rb b/app/abilities/annotation_ability.rb new file mode 100644 index 000000000..4028eba8d --- /dev/null +++ b/app/abilities/annotation_ability.rb @@ -0,0 +1,8 @@ +class AnnotationAbility + include CanCan::Ability + + def initialize(user) + can :create, Annotation + end + +end diff --git a/app/abilities/medium_ability.rb b/app/abilities/medium_ability.rb index 1a39a4d36..95ca643dd 100644 --- a/app/abilities/medium_ability.rb +++ b/app/abilities/medium_ability.rb @@ -5,7 +5,7 @@ def initialize(user) user ||= User.new clear_aliased_actions - can [:index, :new, :search], Medium + can [:index, :new, :check_annotation_visibility], Medium can [:show, :show_comments], Medium do |medium| medium.visible_for_user?(user) && @@ -50,4 +50,5 @@ def initialize(user) !user.new_record? end end -end \ No newline at end of file +end + diff --git a/db/migrate/20221015114100_create_annotations.rb b/db/migrate/20221015114100_create_annotations.rb old mode 100755 new mode 100644 diff --git a/db/migrate/20230117124508_add_annotations_status_to_medium.rb b/db/migrate/20230117124508_add_annotations_status_to_medium.rb old mode 100755 new mode 100644 diff --git a/db/migrate/20230117124528_add_annotations_status_to_lecture.rb b/db/migrate/20230117124528_add_annotations_status_to_lecture.rb old mode 100755 new mode 100644 From 03777ce9807abb0b1d39cc357552bf34579f0768 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Fri, 10 Feb 2023 16:15:18 +0100 Subject: [PATCH 003/291] Added/changed asset files for the annotation-tool. --- app/assets/javascripts/annotations.coffee | 3 + app/assets/javascripts/thyme.coffee | 327 ++++++++++++++++++++-- app/assets/stylesheets/annotations.scss | 0 app/assets/stylesheets/media.scss | 17 +- app/assets/stylesheets/thyme.scss | 116 +++++++- 5 files changed, 432 insertions(+), 31 deletions(-) create mode 100644 app/assets/javascripts/annotations.coffee create mode 100644 app/assets/stylesheets/annotations.scss diff --git a/app/assets/javascripts/annotations.coffee b/app/assets/javascripts/annotations.coffee new file mode 100644 index 000000000..24f83d18b --- /dev/null +++ b/app/assets/javascripts/annotations.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/thyme.coffee b/app/assets/javascripts/thyme.coffee index 65f8a7a8d..7a461824c 100644 --- a/app/assets/javascripts/thyme.coffee +++ b/app/assets/javascripts/thyme.coffee @@ -1,9 +1,49 @@ +# a boolean that helps to deactivate all key listeners +# for the time the annotation modal opens and the user +# has to write text into the command box +lockKeyListeners = false + +# when callig the updateMarkers() method this will be used to save an +# array containing all annotations +annotations = null + +# helps to find the annotation that is currently shown in the +# annotation area +activeAnnotationId = 0 + +# if the window width (in px) gets over this threshold value, hide the control bar +# (default value) +hideControlBarThreshold = + x: 850 + y: 500 + # convert time in seconds to string of the form H:MM:SS secondsToTime = (seconds) -> date = new Date(null) date.setSeconds seconds return date.toISOString().substr(12, 7) +# convert time in H:MM:SS to seconds +timeToSeconds = (time) -> + array = time.split(':') + return (+array[0]) * 3600 + (+array[1]) * 60 + (+array[2]) + +# lightens up a given color (given in a string in hexadecimal +# representation "#xxyyzz") such that e.g. black becomes dark grey. +# The higher the value of "factor" the brighter the colors become. +lightenUp = (color, factor) -> + red = Math.floor(((factor - 1) * 255 + Number("0x" + color.substr(5, 2))) / factor) + green = Math.floor(((factor - 1) * 255 + Number("0x" + color.substr(3, 2))) / factor) + blue = Math.floor(((factor - 1) * 255 + Number("0x" + color.substr(1, 2))) / factor) + return "#" + blue.toString(16) + green.toString(16) + red.toString(16) + +sortAnnotations = -> + if annotations == null + return + annotations.sort (ann1, ann2) -> + timeToSeconds(ann1.timestamp) - timeToSeconds(ann2.timestamp) + return + # return the start time of the next chapter relative to a given time in seconds nextChapterStart = (seconds) -> chapters = document.getElementById('chapters') @@ -390,6 +430,8 @@ $(document).on 'turbolinks:load', -> nextChapterButton = document.getElementById('next-chapter') previousChapterButton = document.getElementById('previous-chapter') backButton = document.getElementById('back-button') + emergencyButton = document.getElementById('emergency-button') + annotationsToggle = document.getElementById('annotations-toggle-check') # Sliders seekBar = document.getElementById('seek-bar') volumeBar = document.getElementById('volume-bar') @@ -401,6 +443,21 @@ $(document).on 'turbolinks:load', -> # ControlBar videoControlBar = document.getElementById('video-controlBar') + # User is teacher/editor for the given medium? + #-> show toggle annotations button + mediumId = thyme.dataset.medium + $.ajax Routes.check_annotation_visibility_path(mediumId), + type: 'GET' + dataType: 'json' + success: (isPermitted) -> + if isPermitted + $('#volume-controls').css('left', '66%') + $('#speed-control').css('left', '77%') + $('#emergency-button').css('left', '86%') + hideControlBarThreshold.x = 960 + updateControlBarType() + return + # resizes the thyme container to the window dimensions, taking into account # whether the interactive area is displayed or hidden resizeContainer = -> @@ -418,6 +475,10 @@ $(document).on 'turbolinks:load', -> $('#thyme-container').css('width', width + 'px') $('#thyme-container').css('top', top + 'px') $('#thyme-container').css('left', left + 'px') + #iaHeight = $('#annotation-caption').css('height') + #commentHeight = Number(iaHeight.substr(0, iaHeight.length - 2)) - 110 + #$('#annotation-comment').css('height', commentHeight + 'px') + updateMarkers() return # detect IE/edge and inform user that they are not suppported if necessary, @@ -425,6 +486,7 @@ $(document).on 'turbolinks:load', -> if document.documentMode || /Edge/.test(navigator.userAgent) alert($('body').data('badbrowser')) $('#caption').hide() + $('#annotation-caption').hide() $('#video-controlBar').hide() video.style.width = '100%' video.controls = true @@ -438,6 +500,7 @@ $(document).on 'turbolinks:load', -> # on small mobile display, fall back to standard browser player mobileDisplay = -> $('#caption').hide() + $('#annotation-caption').hide() $('#video-controlBar').hide() video.controls = true video.style.width = '100%' @@ -447,50 +510,81 @@ $(document).on 'turbolinks:load', -> largeDisplay = -> video.controls = false $('#caption').show() + $('#annotation-caption').show() $('#video-controlBar').show() video.style.width = '82%' - # directly closes the IA again, if the IA-button status is "-" if iaButton.dataset.status == 'false' iaButton.innerHTML = 'remove_from_queue' $('#caption').hide() + $('#annotation-caption').hide() video.style.width = '100%' $('#video-controlBar').css('width', '100%') $(window).trigger('resize') return - # display native control bar if screen is very small - if window.matchMedia("screen and (max-width: 767px)").matches - mobileDisplay() - - if window.matchMedia("screen and (max-device-width: 767px)").matches - mobileDisplay() - - # mediaQuery listener for very small screens - match_verysmall = window.matchMedia("screen and (max-width: 767px)") - match_verysmall.addListener (result) -> - if result.matches + updateControlBarType = -> + # display native control bar if screen is very small + if window.matchMedia("screen and (max-width: " + hideControlBarThreshold.x + "px)").matches or + window.matchMedia("screen and (max-height: " + hideControlBarThreshold.y + "px)").matches mobileDisplay() - return - match_verysmalldevice = window.matchMedia("screen and (max-device-width: 767px)") - match_verysmalldevice.addListener (result) -> - if result.matches + if window.matchMedia("screen and (max-device-width: " + hideControlBarThreshold.x + "px)").matches or + window.matchMedia("screen and (max-device-height: " + hideControlBarThreshold.y + "px)").matches mobileDisplay() - return - # mediaQuery listener for normal screens - match_normal = window.matchMedia("screen and (min-width: 768px)") - match_normal.addListener (result) -> - if result.matches - largeDisplay() - return + # mediaQuery listener for very small screens + match_verysmall_x = window.matchMedia("screen and (max-width: " + hideControlBarThreshold.x + "px)") + match_verysmall_x.addListener (result) -> + if result.matches + mobileDisplay() + return + match_verysmall_y = window.matchMedia("screen and (max-height: " + hideControlBarThreshold.y + "px)") + match_verysmall_y.addListener (result) -> + if result.matches + mobileDisplay() + return - match_normal = window.matchMedia("screen and (min-device-width: 768px)") - match_normal.addListener (result) -> - if result.matches - largeDisplay() + match_verysmalldevice_x = window.matchMedia("screen and (max-device-width: " + hideControlBarThreshold.x + "px)") + match_verysmalldevice_x.addListener (result) -> + if result.matches + mobileDisplay() + return + match_verysmalldevice_y = window.matchMedia("screen and (max-device-height: " + hideControlBarThreshold.y + "px)") + match_verysmalldevice_y.addListener (result) -> + if result.matches + mobileDisplay() + return + + # mediaQuery listener for normal screens + match_normal_x = window.matchMedia("screen and (min-width: " + (hideControlBarThreshold.x + 1) + "px)") + match_normal_x.addListener (result) -> + match_normal_y = window.matchMedia("screen and (min-height: " + (hideControlBarThreshold.y + 1) + "px)") + if result.matches && match_normal_y.matches + largeDisplay() + return + match_normal_y = window.matchMedia("screen and (min-height: " + (hideControlBarThreshold.y + 1) + "px)") + match_normal_y.addListener (result) -> + match_normal_x = window.matchMedia("screen and (min-width: " + (hideControlBarThreshold.x + 1) + "px)") + if result.matches && match_normal_x.matches + largeDisplay() + return + + match_normaldevice_x = window.matchMedia("screen and (min-device-width: " + (hideControlBarThreshold.x + 1) + "px)") + match_normaldevice_x.addListener (result) -> + match_normaldevice_y = window.matchMedia("screen and (min-device-height: " + (hideControlBarThreshold.y + 1) + "px)") + if result.matches && match_normal_y.matches + largeDisplay() + return + match_normaldevice_y = window.matchMedia("screen and (min-device-height: " + (hideControlBarThreshold.y + 1) + "px)") + match_normaldevice_y.addListener (result) -> + match_normaldevice_x = window.matchMedia("screen and (min-device-width: " + (hideControlBarThreshold.x + 1) + "px)") + if result.matches && match_normal_x.matches + largeDisplay() + return return + updateControlBarType() + window.onresize = resizeContainer video.onloadedmetadata = resizeContainer @@ -547,6 +641,57 @@ $(document).on 'turbolinks:load', -> video.currentTime = previousChapterStart(video.currentTime) if previous? return + # Event handler for the emergency button + emergencyButton.addEventListener 'click', -> + video.pause() + # round time to full seconds + time = video.currentTime + intTime = Math.floor(time) + roundTime = intTime + mediumId = thyme.dataset.medium + $.ajax Routes.new_annotation_path(), + type: 'GET' + dataType: 'script' + data: { + timestamp: secondsToTime(roundTime) + mediumId: mediumId + } + # When the modal opens, all key listeners must be + # deactivated until the modal gets closed again + lockKeyListeners = true + $('#annotation-modal').on('hidden.bs.modal', -> + lockKeyListeners = false + ) + return + + if annotationsToggle != null + annotationsToggle.addEventListener 'click', -> + updateMarkers() + + # Update annotations after submitting the annotations form + $(document).on 'click', '#submit-button', -> + # NOTE: + # Updating might take some time on the backend, + # so I added a slight delay. + # I couldn't think of an easy way to let the script + # wait for the update to complete (as with the delete button), + # but it might be possible! + setTimeout(updateMarkers, 500) + + # Update annotations after deleting an annotation + $(document).on 'click', '#delete-button', -> + annotationId = Number(document.getElementById('annotation_id').textContent) + $.ajax Routes.annotation_path(annotationId), + type: 'DELETE' + dataType: 'json' + data: { + annotationId: annotationId + } + success: -> + updateMarkers() + $('#annotation-close-button').click() + return + # Event handler for speed speed selector speedSelector.addEventListener 'change', -> if video.preservesPitch? @@ -564,6 +709,7 @@ $(document).on 'turbolinks:load', -> iaButton.innerHTML = 'remove_from_queue' iaButton.dataset.status = 'false' $('#caption').hide() + $('#annotation-caption').hide() video.style.width = '100%' $('#video-controlBar').css('width', '100%') $(window).trigger('resize') @@ -573,6 +719,7 @@ $(document).on 'turbolinks:load', -> video.style.width = '82%' $('#video-controlBar').css('width', '82%') $('#caption').show() + $('#annotation-caption').show() $(window).trigger('resize') return @@ -729,6 +876,8 @@ $(document).on 'turbolinks:load', -> # m - mute # i - toggle interactive area window.addEventListener 'keydown', (evt) -> + if lockKeyListeners == true + return key = evt.key if key == ' ' if video.paused == true @@ -754,4 +903,126 @@ $(document).on 'turbolinks:load', -> else if key == 'i' $(iaButton).trigger('click') return - return + + # updates the annotation markers + updateMarkers = -> + mediumId = thyme.dataset.medium + toggled = $('#annotations-toggle-check').is(":checked") + $.ajax Routes.update_markers_path(), + type: 'GET' + dataType: 'json' + data: { + mediumId: mediumId + toggled: toggled + } + success: (annots) -> + annotations = annots + sortAnnotations() + $('#markers').empty() + if annotations == null + return + flag = false + for annotation in annotations + createMarker(annotation) + if annotation.id == activeAnnotationId + updateAnnotationArea(annotation) + flag = true + if flag == false && $('#annotation-caption').is(":visible") == true + $('#annotation-caption').hide() + $('#caption').show() + return + return + + # an auxiliary method for "updateMarkers()" creating a single marker + createMarker = (annotation) -> + # create marker + markerStr = ' + + + ' + $('#markers').append(markerStr) + # set the correct position for the marker + marker = $('#marker-' + annotation.id) + size = seekBar.clientWidth - 13 + ratio = timeToSeconds(annotation.timestamp) / video.duration + offset = marker.parent().offset().left + ratio * size + 3 + marker.offset({ left: offset }) + marker.on 'click', -> + updateAnnotationArea(annotation) + $('#caption').hide() + $('#annotation-caption').show() + return + + updateAnnotationArea = (annotation) -> + activeAnnotationId = annotation.id + comment = annotation.comment.replaceAll('\n', '
') + headColor = lightenUp(annotation.color, 2) + backgroundColor = lightenUp(annotation.color, 3) + $('#annotation-infobar').empty().append(annotation.category) + $('#annotation-infobar').css('background-color', headColor) + $('#annotation-infobar').css('text-align', 'center') + $('#annotation-comment').empty().append(comment) + $('#annotation-caption').css('background-color', backgroundColor) + # remove old listeners + $('#annotation-previous-button').off 'click' + $('#annotation-next-button').off 'click' + $('#annotation-goto-button').off 'click' + $('#annotation-edit-button').off 'click' + $('#annotation-close-button').off 'click' + # previous annotation listener + $('#annotation-previous-button').on 'click', -> + for i in [0 .. annotations.length - 1] + if i != 0 && annotations[i] == annotation + updateAnnotationArea(annotations[i - 1]) + # next annotation Listener + $('#annotation-next-button').on 'click', -> + for i in [0 .. annotations.length - 1] + if i != annotations.length - 1 && annotations[i] == annotation + updateAnnotationArea(annotations[i + 1]) + # goto listener + $('#annotation-goto-button').on 'click', -> + video.currentTime = timeToSeconds(annotation.timestamp) + # edit listener + $('#annotation-edit-button').on 'click', -> + lockKeyListeners = true + $.ajax Routes.edit_annotation_path(annotation.id), + type: 'GET' + dataType: 'script' + data: { + annotationId: annotation.id + } + # close listener + $('#annotation-close-button').on 'click', -> + activeAnnotationId = 0 + $('#annotation-caption').hide() + $('#caption').show() + # LaTex + renderMathInElement document.getElementById('annotation-comment'), + delimiters: [ + { + left: '$$' + right: '$$' + display: true + } + { + left: '$' + right: '$' + display: false + } + { + left: '\\(' + right: '\\)' + display: false + } + { + left: '\\[' + right: '\\]' + display: true + } + ] + throwOnError: false + return + + return \ No newline at end of file diff --git a/app/assets/stylesheets/annotations.scss b/app/assets/stylesheets/annotations.scss new file mode 100644 index 000000000..e69de29bb diff --git a/app/assets/stylesheets/media.scss b/app/assets/stylesheets/media.scss index ca56d63ec..9ea3b2dfe 100644 --- a/app/assets/stylesheets/media.scss +++ b/app/assets/stylesheets/media.scss @@ -328,4 +328,19 @@ .tab-content > .active { visibility: visible; -} \ No newline at end of file +} + +/* annotation modal */ +#annotation-modal { + #annotation_comment { + height: 150px; + resize: none; + } + #annotation_category_text { + width: 200px; + } + #annotation_visible_for_teacher { + width: 18px; + height: 18px; + } +} diff --git a/app/assets/stylesheets/thyme.scss b/app/assets/stylesheets/thyme.scss index 6fbed3b35..a1f5d4967 100644 --- a/app/assets/stylesheets/thyme.scss +++ b/app/assets/stylesheets/thyme.scss @@ -125,7 +125,7 @@ position: absolute; padding: 3px; font-size: 12px; - left: 81%; + left: 79%; } #volume-bar { @@ -135,6 +135,84 @@ margin-left: 4px; } +#emergency-button { + position: absolute; + display: flex; + left: 89%; +} + +#annotations-toggle { + position: absolute; + display: flex; + left: 89%; + top: 50%; + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} + +/* The switch - the box around the slider */ +.switch { + position: relative; + display: inline-block; + width: 30px; + height: 17px; +} + +/* Hide default HTML checkbox */ +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +/* The slider */ +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 13px; + width: 13px; + left: 2px; + bottom: 2px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +input:checked + .slider { + background-color: #2196F3; +} + +input:focus + .slider { + box-shadow: 0 0 1px #2196F3; +} + +input:checked + .slider:before { + -webkit-transform: translateX(13px); + -ms-transform: translateX(13px); + transform: translateX(13px); +} + +/* Rounded sliders */ +.slider.round { + border-radius: 17px; +} + +.slider.round:before { + border-radius: 50%; +} + #size-buttons { position: absolute; right: 0%; @@ -325,7 +403,7 @@ figure { .replay { position: absolute; top: 0; - right:0; + right: 0; padding: 3px; cursor: pointer; -webkit-user-select: none; @@ -341,3 +419,37 @@ figure { { background-color: black; } + + +#markers { + position: relative; + top: -2px; + width: 0; + height: 0; + display: flex; + cursor: pointer; +} + +#annotation-infobar { + height: 2em; + color: black; + overflow-y: scroll; + padding: 3px; + border-left: 1px solid darkgray; + border-right: 1px solid darkgray; + border-top: 1px solid darkgray; +} + +#annotation-comment { + margin-left: 3px; + margin-right: 3px; + overflow-y: scroll; + height: 85%; +} + +#annotation-buttons { + display: flex; + justify-content: space-between; + margin-left: 10%; + margin-right: 10%; +} \ No newline at end of file From f9d0bb88b6b2cb0ddc9dc443f88383d3ad72d522 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Fri, 10 Feb 2023 16:19:03 +0100 Subject: [PATCH 004/291] Added/changed controllers for annotation-tool. --- app/controllers/annotations_controller.rb | 79 +++++++++++++++++++++++ app/controllers/lectures_controller.rb | 7 ++ app/controllers/media_controller.rb | 7 ++ 3 files changed, 93 insertions(+) create mode 100644 app/controllers/annotations_controller.rb diff --git a/app/controllers/annotations_controller.rb b/app/controllers/annotations_controller.rb new file mode 100644 index 000000000..7d1993ef7 --- /dev/null +++ b/app/controllers/annotations_controller.rb @@ -0,0 +1,79 @@ +class AnnotationsController < ApplicationController + + def create + @annotation = Annotation.new(annotation_params) + @annotation.user_id = current_user.id + @annotation.category = category_translation + @annotation.save + end + + def edit + @annotation = Annotation.find(params[:annotationId]) + @timestamp = @annotation.timestamp + @medium_id = @annotation.medium_id + end + + def new + @annotation = Annotation.new + @timestamp = params[:timestamp] + @medium_id = params[:mediumId] + end + + def show + @annotation = Annotation.find(params[:id]) + end + + def update + @annotation = Annotation.find(params[:id]) + @annotation.update(annotation_params) + end + + def destroy + Annotation.find(params[:annotationId]).destroy + render json: [] + end + + def update_markers + medium = Medium.find_by_id(params[:mediumId]) + toggled = params[:toggled] + + if medium.annotations_visible?(current_user) && toggled == "true" + annots = Annotation.where(medium: medium, + visible_for_teacher: true).or( + Annotation.where(medium: medium, + user: current_user)) + else + annots = Annotation.where(medium: medium, + user: current_user) + end + + render json: annots + end + + + + private + + def annotation_params + params.require(:annotation).permit( + :color, :comment, :medium_id, + :timestamp, :visible_for_teacher) + end + + def category_translation + category = params[:annotation][:category_text] + case category + when 'Need help!' + return Annotation.categories[:help] + when 'Found a mistake' + return Annotation.categories[:mistake] + when 'Give a comment' + return Annotation.categories[:comment] + when 'Note' + return Annotation.categories[:note] + when 'Other' + return Annotation.categories[:other] + end + end + +end diff --git a/app/controllers/lectures_controller.rb b/app/controllers/lectures_controller.rb index bf7a63ffb..88ba2bf2c 100644 --- a/app/controllers/lectures_controller.rb +++ b/app/controllers/lectures_controller.rb @@ -228,6 +228,12 @@ def search_examples def close_comments @lecture.close_comments!(current_user) + # disable emergency button + @lecture.update(annotations_status: -1) + @lecture.media.update(annotations_status: 0) + @lecture.lessons.each do |lesson| + lesson.media.update(annotations_status: 0) + end redirect_to edit_lecture_path(@lecture) end @@ -310,6 +316,7 @@ def lecture_params :comments_disabled, :submission_max_team_size, :submission_grace_period, + :annotation_status, editor_ids: []) end diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb index 3f4c03d8a..6477bad9e 100644 --- a/app/controllers/media_controller.rb +++ b/app/controllers/media_controller.rb @@ -496,6 +496,12 @@ def fill_reassign_modal @no_rights = params[:rights] == 'none' end + def check_annotation_visibility + medium = Medium.find_by_id(params[:id]) + isPermitted = medium.annotations_visible?(current_user) + render json: isPermitted + end + private def medium_params @@ -505,6 +511,7 @@ def medium_params :teachable_type, :teachable_id, :released, :text, :locale, :content, :boost, + :annotations_status, editor_ids: [], tag_ids: [], linked_medium_ids: []) From 7bab1a1dcf6ca65b1badb575452789f1a126f86b Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Fri, 10 Feb 2023 16:24:58 +0100 Subject: [PATCH 005/291] Added/changed models for the annotation-tool. Added (empty) annotation helper. --- app/helpers/annotations_helper.rb | 2 ++ app/models/annotation.rb | 5 +++++ app/models/medium.rb | 29 ++++++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 app/helpers/annotations_helper.rb create mode 100644 app/models/annotation.rb diff --git a/app/helpers/annotations_helper.rb b/app/helpers/annotations_helper.rb new file mode 100644 index 000000000..442c10bc0 --- /dev/null +++ b/app/helpers/annotations_helper.rb @@ -0,0 +1,2 @@ +module AnnotationsHelper +end diff --git a/app/models/annotation.rb b/app/models/annotation.rb new file mode 100644 index 000000000..720643d29 --- /dev/null +++ b/app/models/annotation.rb @@ -0,0 +1,5 @@ +class Annotation < ApplicationRecord + belongs_to :medium + belongs_to :user + enum category: { other: 0, note: 1, comment: 2, mistake: 3, help: 4 } +end diff --git a/app/models/medium.rb b/app/models/medium.rb index 687e24a37..a4c31de54 100644 --- a/app/models/medium.rb +++ b/app/models/medium.rb @@ -1044,7 +1044,34 @@ def subscribed_users Lecture.find_by(id: teachable.lecture_id).user_ids end - + + def get_annotations_status + if annotations_status != 0 + return annotations_status + else + return lecture.annotations_status + end + end + + def annotations_visible?(user) + # find lecture to which this medium is associated (might be nil) + if lesson != nil + lecture = lesson.lecture + end + + # If the medium is associated to a lecture/course + # and the user is a teacher/editor of this lecture, + # return true + if lecture&.teacher == user || + lecture&.editors&.include?(user) || + editors&.include?(user) || + course&.editors&.include?(user) + return true + else + return false + end + end + private # media of type kaviar associated to a lesson and script do not require From 306a7b9781b90fdf59cf1ef64a9c642f32b77b8a Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Fri, 10 Feb 2023 16:30:38 +0100 Subject: [PATCH 006/291] Added/edited view files for the annotation-tool. --- .../annotations/_annotation_modal.html.erb | 21 ++++++ app/views/annotations/_form.html.erb | 71 +++++++++++++++++++ app/views/annotations/create.coffee | 1 + app/views/annotations/edit.coffee | 3 + app/views/annotations/get_marker.coffee | 1 + app/views/annotations/new.coffee | 3 + app/views/annotations/update.coffee | 1 + app/views/lectures/edit/_comments.html.erb | 27 ++++++- app/views/media/_basics.html.erb | 36 ++++++++++ app/views/media/play.html.erb | 61 +++++++++++++++- 10 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 app/views/annotations/_annotation_modal.html.erb create mode 100644 app/views/annotations/_form.html.erb create mode 100644 app/views/annotations/create.coffee create mode 100644 app/views/annotations/edit.coffee create mode 100644 app/views/annotations/get_marker.coffee create mode 100644 app/views/annotations/new.coffee create mode 100644 app/views/annotations/update.coffee diff --git a/app/views/annotations/_annotation_modal.html.erb b/app/views/annotations/_annotation_modal.html.erb new file mode 100644 index 000000000..b9e0eefeb --- /dev/null +++ b/app/views/annotations/_annotation_modal.html.erb @@ -0,0 +1,21 @@ + + +
+ Enable Emergency Button? +
+
+ <%= f.radio_button :annotations_status, + 1, + class: 'custom-control-input' %> + <%= f.label :annotations_status, + 'yes', + value: 1, + class: 'custom-control-label' %> +
+
+ <%= f.radio_button :annotations_status, + -1, + class: 'custom-control-input' %> + <%= f.label :annotations_status, + 'no', + value: -1, + class: 'custom-control-label' %> +
+
+
+
@@ -58,4 +83,4 @@ <% end %>
-
\ No newline at end of file + diff --git a/app/views/media/_basics.html.erb b/app/views/media/_basics.html.erb index 848a80ccb..ce8ddba99 100644 --- a/app/views/media/_basics.html.erb +++ b/app/views/media/_basics.html.erb @@ -182,6 +182,42 @@ class: 'form-control' %> <% end %> + + <% if medium.video.present? %> +
+ Enable Emergency Button? +
+
+ <%= f.radio_button :annotations_status, + '0', + class: 'custom-control-input' %> + <%= f.label :annotations_status, + 'inherit from lecture', + value: '0', + class: 'custom-control-label' %> +
+
+ <%= f.radio_button :annotations_status, + '1', + class: 'custom-control-input' %> + <%= f.label :annotations_status, + 'yes', + value: '1', + class: 'custom-control-label' %> +
+
+ <%= f.radio_button :annotations_status, + '-1', + class: 'custom-control-input' %> + <%= f.label :annotations_status, + 'no', + value: '-1', + class: 'custom-control-label' %> +
+
+
+ <% end %> + <%= f.hidden_field :teachable_id, value: medium.teachable_id %> <%= f.hidden_field :teachable_type, value: medium.teachable_type %> diff --git a/app/views/media/play.html.erb b/app/views/media/play.html.erb index 382e1fb44..e6862bd77 100644 --- a/app/views/media/play.html.erb +++ b/app/views/media/play.html.erb @@ -1,8 +1,10 @@ <% content_for :title, "THymE - \"#{@medium.caption}\"" %>
-
+
+
+
+ +
+ + bookmark_add + +
+ + <% if @medium.annotations_visible?(current_user) %> +
+ +
+ <% end %>
@@ -71,6 +91,42 @@
+
+
+ +
+
+ +
+ +
+ + + + + + + + + + +
+
+ +<%= render partial: "annotations/annotation_modal" %> +<%#= javascript_include_tag('markers.js') %> From 40ca2fbb4fcb80c43e82937a9681aed585db3c81 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Fri, 10 Feb 2023 16:32:42 +0100 Subject: [PATCH 007/291] Added routes for the annotation-tool. --- config/routes.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 8aee5a21f..e16fdc2e0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -38,7 +38,15 @@ get '/administration/classification', to: 'administration#classification', as: 'classification' + + # annotation routes + get 'annotations/update_markers', + to: 'annotations#update_markers', + as: 'update_markers' + + resources :annotations, only: [:new, :create, :edit, :update, :destroy] + # announcements routes post 'announcements/:id/propagate', @@ -390,7 +398,11 @@ get 'media/:id/fill_reassign_modal', to: 'media#fill_reassign_modal', as: 'fill_reassign_modal' - + + get 'media/:id/check_annotation_visibility', + to: 'media#check_annotation_visibility', + as: 'check_annotation_visibility' + resources :media # notifications controller From 89e0384dfd351ebe4931de377e9aabf96751062a Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sun, 12 Feb 2023 16:29:50 +0100 Subject: [PATCH 008/291] Improved the CSS of the annotation buttons in the interactive area. --- app/assets/stylesheets/thyme.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/assets/stylesheets/thyme.scss b/app/assets/stylesheets/thyme.scss index a1f5d4967..0233a1817 100644 --- a/app/assets/stylesheets/thyme.scss +++ b/app/assets/stylesheets/thyme.scss @@ -430,6 +430,12 @@ figure { cursor: pointer; } +#annotation-caption { + display: flex; + flex-direction: column; + justify-content: space-between; +} + #annotation-infobar { height: 2em; color: black; @@ -452,4 +458,6 @@ figure { justify-content: space-between; margin-left: 10%; margin-right: 10%; + margin-bottom: 15px; + margin-top: auto; } \ No newline at end of file From d043fd2ffd647617520b4a2e7c1309005fdf4414 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sun, 12 Feb 2023 17:16:56 +0100 Subject: [PATCH 009/291] Fixed a bug which caused the annotation's category not to be updated when editing the annotation. --- app/controllers/annotations_controller.rb | 24 ++++++------------- app/helpers/annotations_helper.rb | 29 +++++++++++++++++++++++ app/views/annotations/_form.html.erb | 2 +- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/app/controllers/annotations_controller.rb b/app/controllers/annotations_controller.rb index 7d1993ef7..5ccb39171 100644 --- a/app/controllers/annotations_controller.rb +++ b/app/controllers/annotations_controller.rb @@ -3,7 +3,8 @@ class AnnotationsController < ApplicationController def create @annotation = Annotation.new(annotation_params) @annotation.user_id = current_user.id - @annotation.category = category_translation + @annotation.category = helpers.category_text_to_int( + params[:annotation][:category_text]) @annotation.save end @@ -11,6 +12,9 @@ def edit @annotation = Annotation.find(params[:annotationId]) @timestamp = @annotation.timestamp @medium_id = @annotation.medium_id + # A variable that helps to assign the correct text to + # the given category, e.g. "Need help!" for the category 'help'. + @category_text = helpers.category_token_to_text(@annotation.category) end def new @@ -25,6 +29,8 @@ def show def update @annotation = Annotation.find(params[:id]) + @annotation.category = helpers.category_text_to_int( + params[:annotation][:category_text]) @annotation.update(annotation_params) end @@ -60,20 +66,4 @@ def annotation_params :timestamp, :visible_for_teacher) end - def category_translation - category = params[:annotation][:category_text] - case category - when 'Need help!' - return Annotation.categories[:help] - when 'Found a mistake' - return Annotation.categories[:mistake] - when 'Give a comment' - return Annotation.categories[:comment] - when 'Note' - return Annotation.categories[:note] - when 'Other' - return Annotation.categories[:other] - end - end - end diff --git a/app/helpers/annotations_helper.rb b/app/helpers/annotations_helper.rb index 442c10bc0..f7f3babb0 100644 --- a/app/helpers/annotations_helper.rb +++ b/app/helpers/annotations_helper.rb @@ -1,2 +1,31 @@ module AnnotationsHelper + def category_text_to_int(text) + case text + when 'Need help!' + return Annotation.categories[:help] + when 'Found a mistake' + return Annotation.categories[:mistake] + when 'Give a comment' + return Annotation.categories[:comment] + when 'Note' + return Annotation.categories[:note] + when 'Other' + return Annotation.categories[:other] + end + end + + def category_token_to_text(token) + case token + when 'help' + return 'Need help!' + when 'mistake' + return 'Found a mistake' + when 'comment' + return 'Give a comment' + when 'note' + return "Note" + when 'other' + return 'Other' + end + end end diff --git a/app/views/annotations/_form.html.erb b/app/views/annotations/_form.html.erb index c891820db..51b0d992e 100644 --- a/app/views/annotations/_form.html.erb +++ b/app/views/annotations/_form.html.erb @@ -29,7 +29,7 @@ <%= f.select :category_text, ['Need help!', 'Found a mistake', 'Give a comment', 'Note', 'Other'], - {}, + { selected: @category_text }, { class: 'form-control' } %>

From 618b0114be0a34dc06ca33bafb71170a996167df Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Mon, 13 Feb 2023 20:53:47 +0100 Subject: [PATCH 010/291] Added ":search" in line 8 of the file medium_ability.rb which was accidently overwritten. --- app/abilities/medium_ability.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/abilities/medium_ability.rb b/app/abilities/medium_ability.rb index 95ca643dd..b28694ef0 100644 --- a/app/abilities/medium_ability.rb +++ b/app/abilities/medium_ability.rb @@ -5,7 +5,7 @@ def initialize(user) user ||= User.new clear_aliased_actions - can [:index, :new, :check_annotation_visibility], Medium + can [:index, :new, :search, :check_annotation_visibility], Medium can [:show, :show_comments], Medium do |medium| medium.visible_for_user?(user) && From 84567fbe65e91b60fef8af9704913b0fe2e23b1d Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sat, 18 Feb 2023 15:18:58 +0100 Subject: [PATCH 011/291] Changed marker offset such that it fits better for larger videos. --- app/assets/javascripts/thyme.coffee | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/thyme.coffee b/app/assets/javascripts/thyme.coffee index 7a461824c..a00e5f7c3 100644 --- a/app/assets/javascripts/thyme.coffee +++ b/app/assets/javascripts/thyme.coffee @@ -646,8 +646,7 @@ $(document).on 'turbolinks:load', -> video.pause() # round time to full seconds time = video.currentTime - intTime = Math.floor(time) - roundTime = intTime + roundTime = Math.floor(time) mediumId = thyme.dataset.medium $.ajax Routes.new_annotation_path(), type: 'GET' @@ -945,7 +944,7 @@ $(document).on 'turbolinks:load', -> $('#markers').append(markerStr) # set the correct position for the marker marker = $('#marker-' + annotation.id) - size = seekBar.clientWidth - 13 + size = seekBar.clientWidth - 15 ratio = timeToSeconds(annotation.timestamp) / video.duration offset = marker.parent().offset().left + ratio * size + 3 marker.offset({ left: offset }) From e7dbf2dd5d4cb49c1d4ed85996bac781b1fb2c5b Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Mon, 6 Mar 2023 20:34:21 +0100 Subject: [PATCH 012/291] Changed 'annotation_status' in the lectures controller to the correct term 'annotations_status' and added default value 0. --- app/controllers/lectures_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/lectures_controller.rb b/app/controllers/lectures_controller.rb index 88ba2bf2c..b7aa87699 100644 --- a/app/controllers/lectures_controller.rb +++ b/app/controllers/lectures_controller.rb @@ -91,6 +91,7 @@ def new # info to the lecture @lecture.course = Course.find_by_id(params[:course]) I18n.locale = @lecture.course.locale + @lecture.annotations_status = 0 end def create @@ -316,7 +317,7 @@ def lecture_params :comments_disabled, :submission_max_team_size, :submission_grace_period, - :annotation_status, + :annotations_status, editor_ids: []) end From e2b3fdf8089e9f97b7d6432b03ae5b30b9956d67 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Mon, 13 Mar 2023 20:38:56 +0100 Subject: [PATCH 013/291] Replaced the standard color picker of the annotation form by a selection of 15 colors. --- app/assets/stylesheets/thyme.scss | 68 +++++++++++++++++++++++++++- app/helpers/annotations_helper.rb | 36 +++++++++++++++ app/views/annotations/_form.html.erb | 16 ++++++- 3 files changed, 118 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/thyme.scss b/app/assets/stylesheets/thyme.scss index 0233a1817..a7162c60d 100644 --- a/app/assets/stylesheets/thyme.scss +++ b/app/assets/stylesheets/thyme.scss @@ -460,4 +460,70 @@ figure { margin-right: 10%; margin-bottom: 15px; margin-top: auto; -} \ No newline at end of file +} + + +/* A collection of all colors. +Be aware that color changes must also be written in the +AnnotationHelper! */ + +$annotation_colors: ( + annotation_color1: #DB2828, + annotation_color2: #F2711C, + annotation_color3: #FBBD08, + annotation_color4: #B5CC18, + annotation_color5: #21BA45, + annotation_color6: #00B5AD, + annotation_color7: #2185D0, + annotation_color8: #6435C9, + annotation_color9: #A333C8, + annotation_color10: #E03997, + annotation_color11: #d05d41, + annotation_color12: #924129, + annotation_color13: #444444, + annotation_color14: #999999, + annotation_color15: #eeeeee, +); + +* { + box-sizing: border-box; +} + +input[type="radio"] { + display: none; + &:checked + label { + span { transform: scale(1.25); } + @each $name, $value in $annotation_colors { + .#{$name} { + border: 2px solid black; + } + } + } +} + +#annotation-color-picker { + text-align: center; + label { + display: inline-block; + width: 25px; + height: 25px; + margin-right: 2px; + cursor: pointer; + &:hover { + span { + transform: scale(1.25); + } + } + span { + display: block; + width: 100%; + height: 100%; + transition: transform .2s ease-in-out; + @each $name, $value in $annotation_colors { + &.#{$name} { + background: $value; + } + } + } + } +} diff --git a/app/helpers/annotations_helper.rb b/app/helpers/annotations_helper.rb index f7f3babb0..4e830f8b4 100644 --- a/app/helpers/annotations_helper.rb +++ b/app/helpers/annotations_helper.rb @@ -28,4 +28,40 @@ def category_token_to_text(token) return 'Other' end end + + def annotation_color(int) + case int + when 1 + return '#DB2828' + when 2 + return '#F2711C' + when 3 + return '#FBBD08' + when 4 + return '#B5CC18' + when 5 + return '#21BA45' + when 6 + return '#00B5AD' + when 7 + return '#2185D0' + when 8 + return '#6435C9' + when 9 + return '#A333C8' + when 10 + return '#E03997' + when 11 + return '#d05d41' + when 12 + return '#924129' + when 13 + return '#444444' + when 14 + return '#999999' + when 15 + return '#eeeeee' + end + end + end diff --git a/app/views/annotations/_form.html.erb b/app/views/annotations/_form.html.erb index 51b0d992e..3f598ccf9 100644 --- a/app/views/annotations/_form.html.erb +++ b/app/views/annotations/_form.html.erb @@ -19,8 +19,22 @@

+ Color: +
+ <% for i in 1..15 do %> + <%= f.radio_button :color, + annotation_color(i), + id: "annotation_color#{i}" %> + <%= f.label :color, + "annotation_color#{i}", + for: "annotation_color#{i}" do %> + + <% end %> + <% end %> +
+

From ff4f51ad3ae60c889fc6cf49ba2521c979a5e652 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Wed, 29 Mar 2023 19:15:45 +0200 Subject: [PATCH 014/291] Replaced cases with hash in the AnnotationsHelper. --- app/helpers/annotations_helper.rb | 50 +++++++++++-------------------- 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/app/helpers/annotations_helper.rb b/app/helpers/annotations_helper.rb index 4e830f8b4..62a0f31c7 100644 --- a/app/helpers/annotations_helper.rb +++ b/app/helpers/annotations_helper.rb @@ -30,38 +30,24 @@ def category_token_to_text(token) end def annotation_color(int) - case int - when 1 - return '#DB2828' - when 2 - return '#F2711C' - when 3 - return '#FBBD08' - when 4 - return '#B5CC18' - when 5 - return '#21BA45' - when 6 - return '#00B5AD' - when 7 - return '#2185D0' - when 8 - return '#6435C9' - when 9 - return '#A333C8' - when 10 - return '#E03997' - when 11 - return '#d05d41' - when 12 - return '#924129' - when 13 - return '#444444' - when 14 - return '#999999' - when 15 - return '#eeeeee' - end + color_map = { + 1 => '#DB2828', + 2 => '#F2711C', + 3 => '#FBBD08', + 4 => '#B5CC18', + 5 => '#21BA45', + 6 => '#00B5AD', + 7 => '#2185D0', + 8 => '#6435C9', + 9 => '#A333C8', + 10 => '#E03997', + 11 => '#d05d41', + 12 => '#924129', + 13 => '#444444', + 14 => '#999999', + 15 => '#eeeeee' + } + color_map[int] || '#000000' end end From fd61649bfe6383565654c07d1c86f2016d52101e Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Tue, 11 Apr 2023 11:02:48 +0200 Subject: [PATCH 015/291] The timestamp of an annotation is now properly saved as a TimeStamp object and the annotation categories have been replaced. --- app/assets/javascripts/thyme.coffee | 20 +++--- app/controllers/annotations_controller.rb | 12 ++-- app/helpers/annotations_helper.rb | 32 ++++----- app/models/annotation.rb | 6 +- app/models/time_stamp.rb | 2 +- app/views/annotations/_form.html.erb | 83 ++++++++++++++++++++--- 6 files changed, 108 insertions(+), 47 deletions(-) diff --git a/app/assets/javascripts/thyme.coffee b/app/assets/javascripts/thyme.coffee index a00e5f7c3..31872044d 100644 --- a/app/assets/javascripts/thyme.coffee +++ b/app/assets/javascripts/thyme.coffee @@ -26,7 +26,11 @@ secondsToTime = (seconds) -> # convert time in H:MM:SS to seconds timeToSeconds = (time) -> array = time.split(':') - return (+array[0]) * 3600 + (+array[1]) * 60 + (+array[2]) + return (+array[0]) * 3600 + (+array[1]) * 60 + (+array[2]) + (+array[3]) * .0001 + +# converts a json timestamp to a double containing the absolute count of millitseconds +timestampToMillis = (timestamp) -> + return 3600 * timestamp.hours + 60 * timestamp.minutes + timestamp.seconds + 0.001 * timestamp.milliseconds # lightens up a given color (given in a string in hexadecimal # representation "#xxyyzz") such that e.g. black becomes dark grey. @@ -41,7 +45,7 @@ sortAnnotations = -> if annotations == null return annotations.sort (ann1, ann2) -> - timeToSeconds(ann1.timestamp) - timeToSeconds(ann2.timestamp) + timestampToMillis(ann1.timestamp) - timestampToMillis(ann2.timestamp) return # return the start time of the next chapter relative to a given time in seconds @@ -644,16 +648,12 @@ $(document).on 'turbolinks:load', -> # Event handler for the emergency button emergencyButton.addEventListener 'click', -> video.pause() - # round time to full seconds - time = video.currentTime - roundTime = Math.floor(time) - mediumId = thyme.dataset.medium $.ajax Routes.new_annotation_path(), type: 'GET' dataType: 'script' data: { - timestamp: secondsToTime(roundTime) - mediumId: mediumId + total_seconds: video.currentTime + mediumId: thyme.dataset.medium } # When the modal opens, all key listeners must be # deactivated until the modal gets closed again @@ -945,7 +945,7 @@ $(document).on 'turbolinks:load', -> # set the correct position for the marker marker = $('#marker-' + annotation.id) size = seekBar.clientWidth - 15 - ratio = timeToSeconds(annotation.timestamp) / video.duration + ratio = timestampToMillis(annotation.timestamp) / video.duration offset = marker.parent().offset().left + ratio * size + 3 marker.offset({ left: offset }) marker.on 'click', -> @@ -982,7 +982,7 @@ $(document).on 'turbolinks:load', -> updateAnnotationArea(annotations[i + 1]) # goto listener $('#annotation-goto-button').on 'click', -> - video.currentTime = timeToSeconds(annotation.timestamp) + video.currentTime = timestampToMillis(annotation.timestamp) # edit listener $('#annotation-edit-button').on 'click', -> lockKeyListeners = true diff --git a/app/controllers/annotations_controller.rb b/app/controllers/annotations_controller.rb index 5ccb39171..3ce458870 100644 --- a/app/controllers/annotations_controller.rb +++ b/app/controllers/annotations_controller.rb @@ -2,6 +2,7 @@ class AnnotationsController < ApplicationController def create @annotation = Annotation.new(annotation_params) + @annotation.timestamp = TimeStamp.new(total_seconds: params[:annotation][:total_seconds]) @annotation.user_id = current_user.id @annotation.category = helpers.category_text_to_int( params[:annotation][:category_text]) @@ -10,7 +11,7 @@ def create def edit @annotation = Annotation.find(params[:annotationId]) - @timestamp = @annotation.timestamp + @total_seconds = @annotation.timestamp.total_seconds @medium_id = @annotation.medium_id # A variable that helps to assign the correct text to # the given category, e.g. "Need help!" for the category 'help'. @@ -19,7 +20,7 @@ def edit def new @annotation = Annotation.new - @timestamp = params[:timestamp] + @total_seconds = params[:total_seconds] @medium_id = params[:mediumId] end @@ -50,9 +51,9 @@ def update_markers user: current_user)) else annots = Annotation.where(medium: medium, - user: current_user) + user: current_user) end - + render json: annots end @@ -62,8 +63,7 @@ def update_markers def annotation_params params.require(:annotation).permit( - :color, :comment, :medium_id, - :timestamp, :visible_for_teacher) + :color, :comment, :medium_id, :visible_for_teacher) end end diff --git a/app/helpers/annotations_helper.rb b/app/helpers/annotations_helper.rb index 62a0f31c7..dd7fa80b9 100644 --- a/app/helpers/annotations_helper.rb +++ b/app/helpers/annotations_helper.rb @@ -1,31 +1,27 @@ module AnnotationsHelper def category_text_to_int(text) case text - when 'Need help!' - return Annotation.categories[:help] - when 'Found a mistake' - return Annotation.categories[:mistake] - when 'Give a comment' - return Annotation.categories[:comment] - when 'Note' + when 'I want to create a personal note.' return Annotation.categories[:note] - when 'Other' - return Annotation.categories[:other] + when 'I have a problem with understanding the content.' + return Annotation.categories[:content] + when 'I found a mistake.' + return Annotation.categories[:mistake] + when 'I can\'t read everything in this part.' + return Annotation.categories[:presentation] end end def category_token_to_text(token) case token - when 'help' - return 'Need help!' - when 'mistake' - return 'Found a mistake' - when 'comment' - return 'Give a comment' when 'note' - return "Note" - when 'other' - return 'Other' + return 'I want to create a personal note.' + when 'content' + return 'I have a problem with understanding the content.' + when 'mistake' + return 'I found a mistake.' + when 'presentation' + return 'I can\'t read everything in this part.' end end diff --git a/app/models/annotation.rb b/app/models/annotation.rb index 720643d29..d899bae12 100644 --- a/app/models/annotation.rb +++ b/app/models/annotation.rb @@ -1,5 +1,9 @@ class Annotation < ApplicationRecord belongs_to :medium belongs_to :user - enum category: { other: 0, note: 1, comment: 2, mistake: 3, help: 4 } + + # the timestamp for the annotation position is serialized as text in the db + serialize :timestamp, TimeStamp + + enum category: { note: 0, content: 1, mistake: 2, presentation: 3 } end diff --git a/app/models/time_stamp.rb b/app/models/time_stamp.rb index d5232735c..61d1b05d7 100644 --- a/app/models/time_stamp.rb +++ b/app/models/time_stamp.rb @@ -5,7 +5,7 @@ class TimeStamp validates :milliseconds, presence: true attr_reader :hours, :minutes, :seconds, :milliseconds - + # extract from YAML def self.load(text) YAML.safe_load(text, permitted_classes: [TimeStamp, diff --git a/app/views/annotations/_form.html.erb b/app/views/annotations/_form.html.erb index 3f598ccf9..79f070e76 100644 --- a/app/views/annotations/_form.html.erb +++ b/app/views/annotations/_form.html.erb @@ -1,11 +1,11 @@ <%= form_with model: @annotation do |f| %> - Time:   <%= @timestamp %> + Time:   <%= TimeStamp.new(total_seconds: @total_seconds).hms_colon_string %>

<%= f.hidden_field :medium_id, value: @medium_id %> - <%= f.hidden_field :timestamp, value: @timestamp %> + <%= f.hidden_field :total_seconds, value: @total_seconds %>
- Enable Emergency Button? + <%= t('admin.lecture.enable_emergency_button') %>
<%= f.radio_button :annotations_status, 1, class: 'custom-control-input' %> <%= f.label :annotations_status, - 'yes', + t('basics.yes_lc'), value: 1, class: 'custom-control-label' %>
@@ -60,7 +60,7 @@ -1, class: 'custom-control-input' %> <%= f.label :annotations_status, - 'no', + t('basics.no_lc'), value: -1, class: 'custom-control-label' %>
diff --git a/app/views/lectures/edit/_preferences.html.erb b/app/views/lectures/edit/_preferences.html.erb index 891613b9a..91a6c440f 100644 --- a/app/views/lectures/edit/_preferences.html.erb +++ b/app/views/lectures/edit/_preferences.html.erb @@ -152,14 +152,14 @@ <% if lecture.annotations_status == 1 %>
- Link for emergency button + <%= t('admin.lecture.emergency_link') %> @@ -177,7 +177,7 @@ :direct_link, class: 'custom-control-input' %> <%= f.label :emergency_link_status, - 'direct link', + t('admin.lecture.emergency_link_direct_link'), value: :direct_link, class: 'custom-control-label' %>
@@ -185,14 +185,14 @@
<% end %> - \ No newline at end of file + + +<%= render partial: "annotations/annotation_locales" %> \ No newline at end of file diff --git a/app/views/media/play.html.erb b/app/views/media/play.html.erb index 610fa2189..0be25a50f 100644 --- a/app/views/media/play.html.erb +++ b/app/views/media/play.html.erb @@ -159,3 +159,4 @@ <%= render partial: "annotations/annotation_modal" %> +<%= render partial: "annotations/annotation_locales" %> \ No newline at end of file diff --git a/config/locales/de.yml b/config/locales/de.yml index 84703afb0..b7a3a75fa 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -322,6 +322,45 @@ de: title: 'Titel' teacher: 'DozentIn' editors: 'EditorInnen' + annotation: + inherit_from_lecture: 'von Vorlesung erben' + annotations: 'Annotationen' + time: 'Zeit:' + comment: 'Kommentar' + color: 'Farbe' + category: 'Kategorie' + note: 'Notiz' + content: 'Inhalt' + mistake: 'Fehler' + presentation: 'Darstellung' + where_problem: > + Wo liegt das Problem? + definition: 'Definition' + argument: 'Argument' + strategy: 'Beweisstrategie' + further_help: 'Du brauchst weitere Hilfe? Dann klicke auf:' + warning_message: + publishing: > + Jeder, der diese Vorlesung abonniert hat, wird deinen Kommentar + lesen können, sobald du auf "Speichern" klickst. + Falls du das nicht möchtest, entferne bitte wieder den Haken + dieser Checkbox. + mistake: > + Bevor du diesen Kommentar abschickst, überprüfe außerdem bitte, + ob bereits ein Kommentar zu dem von dir gefundenen Fehler existiert. + one_close_annotation: > + In der Nähe deiner Annotation gibt es bereits eine veröffentlichte + Fehler-Annotation. + multiple_close_annotations_1: "In der Nähe deiner Annotation gibt es bereits " + multiple_close_annotations_2: " veröffentliche Fehler-Annotationen." + permission: "Dir fehlen die Rechte, um diese Annotation zu bearbeiten." + visible_for_teacher: 'Für DozentIn sichtbar?' + post_as_comment: 'Als Kommentar veröffentlichen?' + toggle: + note: "Notiz-Annotationen ein-/ausblenden" + content: "Inhalt-Annotationen ein-/ausblenden" + mistake: "Fehler-Annotationen ein-/ausblenden" + presentation: "Darstellung-Annotationen ein-/ausblenden" announcement: help: > Hier kannst Du den Text eingeben. Du kannst LaTeX benutzen @@ -371,6 +410,7 @@ de: no_imported_media: 'Es sind keine importierten Medien vorhanden.' comments_disabled: > Kommentare für neu veröffentlichte Medien sind standardmäßig deaktiviert + enable_emergency_button: 'Emergency-Button aktivieren?' new_notifications: 'neue Benachrichtigungen' new_media: 'neue Medien' new_posts: 'neue Forenbeiträge' @@ -620,6 +660,12 @@ de: script_based: > unter Verwendung eines Veranstaltungsskriptes, das mit dem MaMpf LaTeX-Paket erstellt wurde + emergency_link: 'Link für den Emergency-Button' + emergency_link_no_link: 'kein Link' + emergency_link_lecture_link: 'Vorlesungslink' + emergency_link_direct_link: 'direkter Link' + enter_emergency_link: 'Emergency-Link eingeben:' + select_helpdesk: 'Helpdesk auswählen:' no_chapters: 'Es sind noch keine Kapitel vorhanden.' no_talks: Es sind noch keine Vorträge vorhanden. orphaned_lessons: 'Verwaiste Sitzungen' diff --git a/config/locales/en.yml b/config/locales/en.yml index 037f67f4b..7c3d5680d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -323,6 +323,44 @@ en: title: 'Title' teacher: 'Teacher' editors: 'Editors' + annotation: + inherit_from_lecture: 'inherit from lecture' + annotations: 'Annotations' + time: 'Time:' + comment: 'Comment' + color: 'Color' + category: 'Category' + note: 'note' + content: 'content' + mistake: 'mistake' + presentation: 'presentation' + where_problem: > + Where's the problem? + definition: 'definition' + argument: 'argument' + strategy: 'proof strategy' + further_help: 'You need further help? Then click on:' + warning_message: + publishing: > + Everyone who subscribed this lecture will be able to read + your comment once you click "Save". + If this is not what you intended to do, + please unselect this checkbox. + mistake: > + Before submitting, please also check, if someone already posted + a comment concerning this mistake. + one_close_annotation: > + There already is a public mistake annotation that is close to yours. + multiple_close_annotations_1: "There are already " + multiple_close_annotations_2: " mistake annotations that are close to yours." + permission: "You don't have the permission to edit this annotation." + visible_for_teacher: 'Visible for teacher?' + post_as_comment: 'Post as comment?' + toggle: + note: "toggle note annotations" + content: "toggle note content" + mistake: "toggle note mistake" + presentation: "toggle note presentation" announcement: help: > Here you can enter a text. You may use LaTeX (by putting the @@ -372,6 +410,7 @@ en: close_comments: 'Close all threads for related media' comments_disabled: > Comments for newly published media are disabled by default + enable_emergency_button: 'Enable emergency button?' new_notifications: 'new notifications' new_media: 'new media' new_posts: 'new forum posts' @@ -590,6 +629,12 @@ en: content_mode: 'Content determination' video_based: 'media based' script_based: 'using a manuscript generated by the MaMpf LaTeX package' + emergency_link: 'Link for the emergency button' + emergency_link_no_link: 'no link' + emergency_link_lecture_link: 'lecture link' + emergency_link_direct_link: 'direct link' + enter_emergency_link: 'Enter emergency link:' + select_helpdesk: 'Select helpdesk:' no_chapters: 'There are no chapters yet.' no_talks: 'There are no talks yet.' orphaned_lessons: 'Orphaned Sessions' From 885a731115e1cae014b9e534cb7ee19f74983cbb Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Tue, 27 Jun 2023 12:15:37 +0200 Subject: [PATCH 032/291] Combined new and edit scripts for the annotations controller (as they basically share the same code). --- app/controllers/annotations_controller.rb | 1 + app/views/annotations/edit.coffee | 19 ++-- app/views/annotations/new.coffee | 108 ---------------------- 3 files changed, 11 insertions(+), 117 deletions(-) delete mode 100644 app/views/annotations/new.coffee diff --git a/app/controllers/annotations_controller.rb b/app/controllers/annotations_controller.rb index 66b971c0f..f2a34312e 100644 --- a/app/controllers/annotations_controller.rb +++ b/app/controllers/annotations_controller.rb @@ -46,6 +46,7 @@ def new @annotation = Annotation.new(category: :note, color: helpers.annotation_color(1)) @total_seconds = params[:total_seconds] @medium_id = params[:mediumId] + render :edit end def show diff --git a/app/views/annotations/edit.coffee b/app/views/annotations/edit.coffee index 97ea2448b..0cf37c9cc 100644 --- a/app/views/annotations/edit.coffee +++ b/app/views/annotations/edit.coffee @@ -6,14 +6,11 @@ submitButton = document.getElementById('submit-button') postAsComment = document.getElementById('post-as-comment') postAsComment.addEventListener 'change', (evt) -> - message = "Everyone who subscribed this lecture will be able to read your comment once " + - "you click \"Create Annotation\". " + - "If this is not what you intended to do, please unselect this checkbox." + message = document.getElementById('warning').dataset.publishing if this.checked mistakeRadio = document.getElementById('annotation_category_mistake') if mistakeRadio.checked - message += "\n\nBefore submitting, please also check, if someone already posted " + - " a comment concerning this mistake. " + message += "\n" + document.getElementById('warning').dataset.mistake medId = thyme.dataset.medium rad = 60 # annotations that are inside this radius are considered as "near". $.ajax Routes.near_mistake_annotations_path(), @@ -27,9 +24,11 @@ postAsComment.addEventListener 'change', (evt) -> success: (c) -> if c != undefined && c != 0 if c == 1 - message += "There already is a (public) mistake annotation that is close to yours." + message += document.getElementById('warning').dataset.oneCloseAnnotation else - message += "There are already " + c + " (public) mistake annotations that are close to yours." + message += document.getElementById('warning').dataset.multipleCloseAnnotations1 + + "" + c + + document.getElementById('warning').dataset.multipleCloseAnnotations2 alert message else alert message @@ -110,7 +109,8 @@ postComment = (boolean) -> -# select correct subcategory (this is not automatically done by the rails form +# If this script is rendered by the edit method of the annotation controller: +# Select correct subcategory (this is not automatically done by the rails form # as the content is dynamically rendered). contentRadio = document.getElementById('annotation_category_content') if contentRadio.checked @@ -120,4 +120,5 @@ if contentRadio.checked switch subtext when "definition" then document.getElementById('content-category-definition').checked = true when "argument" then document.getElementById('content-category-argument').checked = true - when "strategy" then document.getElementById('content-category-strategy').checked = true \ No newline at end of file + when "strategy" then document.getElementById('content-category-strategy').checked = true + return \ No newline at end of file diff --git a/app/views/annotations/new.coffee b/app/views/annotations/new.coffee deleted file mode 100644 index 73b6962d4..000000000 --- a/app/views/annotations/new.coffee +++ /dev/null @@ -1,108 +0,0 @@ -$('#annotation-modal-content').empty() - .append('<%= j render partial: "annotations/form"%>') -$('#annotation-modal').modal('show') - -submitButton = document.getElementById('submit-button') -postAsComment = document.getElementById('post-as-comment') - -postAsComment.addEventListener 'change', (evt) -> - message = document.getElementById('warning').dataset.publishing - if this.checked - mistakeRadio = document.getElementById('annotation_category_mistake') - if mistakeRadio.checked - message += "\n" + document.getElementById('warning').dataset.mistake - medId = thyme.dataset.medium - rad = 60 # annotations that are inside this radius are considered as "near". - $.ajax Routes.near_mistake_annotations_path(), - type: 'GET' - dataType: 'json' - data: { - mediumId: medId - timestamp: video.currentTime - radius: rad - } - success: (c) -> - if c != undefined && c != 0 - if c == 1 - message += document.getElementById('warning').dataset.oneCloseAnnotation - else - message += document.getElementById('warning').dataset.multipleCloseAnnotations1 + - "" + c + - document.getElementById('warning').dataset.multipleCloseAnnotations2 - alert message - else - alert message - return - - - -# CATEGORY - -categoryRadios = document.getElementById('category-radios') - -categoryRadios.addEventListener 'click', (evt) -> - if evt.target && event.target.matches("input[type='radio']") - switch evt.target.value - when "note" then note() - when "content" then content() - when "mistake" then mistake() - when "presentation" then presentation() - return - -note = -> - $('#specific').empty() - submitButton.disabled = false - visibleForTeacher(false) - postComment(false) - return - -content = -> - $('#specific').empty().append('<%= j render partial: "annotations/form_content"%>') - submitButton.disabled = true # disable submit button until the content category is selected - visibleForTeacher(true) - postComment(false) - contentCategoryRadios = document.getElementById('content-category-radios') - contentCategoryRadios.addEventListener 'click', (evt) -> - if evt.target && event.target.matches("input[type='radio']") - submitButton.disabled = false - switch evt.target.value - when "definition" then definition() - when "argument" then argument() - when "strategy" then strategy() - return - definition = -> - $('#content-specific').empty().append('<%= j render partial: "annotations/form_content_definition"%>') - return - argument = -> - $('#content-specific').empty() - return - strategy = -> - $('#content-specific').empty() - return - return - -mistake = -> - $('#specific').empty() - submitButton.disabled = false - visibleForTeacher(true) - postComment(true) - return - -presentation = -> - $('#specific').empty() - submitButton.disabled = false - visibleForTeacher(true) - postComment(false) - return - - - -# Auxiliary methods - -visibleForTeacher = (boolean) -> - $('#annotation_visible_for_teacher').prop("checked", boolean) - return - -postComment = (boolean) -> - $('#annotation_post_as_comment').prop("checked", boolean) - return \ No newline at end of file From 1011f97a2ee08c2fd5c8ac815235bc60e27eda99 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Tue, 27 Jun 2023 13:26:47 +0200 Subject: [PATCH 033/291] Fixed bug which caused the annotation area to overlap with the video player, if one clicks the iaButton and then click on an annotation. --- app/assets/javascripts/thyme.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/thyme.coffee b/app/assets/javascripts/thyme.coffee index f51794832..1b406f327 100644 --- a/app/assets/javascripts/thyme.coffee +++ b/app/assets/javascripts/thyme.coffee @@ -952,8 +952,10 @@ $(document).on 'turbolinks:load', -> offset = marker.parent().offset().left + ratio * size + 3 marker.offset({ left: offset }) marker.on 'click', -> - updateAnnotationArea(annotation) + if iaButton.dataset.status == "false" + $(iaButton).trigger('click') $('#caption').hide() + updateAnnotationArea(annotation) $('#annotation-caption').show() return From 34d28bdb033d16217d62bab27943f17897a02288 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sat, 1 Jul 2023 09:27:15 +0200 Subject: [PATCH 034/291] Manually set the ranges of the thyme feedback player to a default value on reload (time to 0, volume to 1). This still doesn't solve the bug which causes the ranges to be white (instead of blue) until the user manually changes their values. --- app/assets/javascripts/thyme_feedback.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/javascripts/thyme_feedback.coffee b/app/assets/javascripts/thyme_feedback.coffee index 60d062bca..1c5d4f811 100644 --- a/app/assets/javascripts/thyme_feedback.coffee +++ b/app/assets/javascripts/thyme_feedback.coffee @@ -113,6 +113,9 @@ $(document).on 'turbolinks:load', -> toggleContentAnnotations = document.getElementById('toggle-content-annotations-check') toggleMistakeAnnotations = document.getElementById('toggle-mistake-annotations-check') togglePresentationAnnotations = document.getElementById('toggle-presentation-annotations-check') + # set seek bar to 0 and volume bar to 1 + seekBar.value = 0 + volumeBar.value = 1 # resizes the thyme container to the window dimensions resizeContainer = -> From 088050741ba2c9a305531ad4d7200b4c2b94c893 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sat, 8 Jul 2023 14:16:27 +0200 Subject: [PATCH 035/291] Updated all emergency button forms to bootstrap 5 standards. Changed a CSS paragraph which caused all radio buttons to disappear. Fixed a bug in the annotation form which caused unwanted behavior of the publish-button. --- app/assets/stylesheets/thyme.scss | 19 +++---- .../annotations/_annotation_modal.html.erb | 11 ++-- app/views/annotations/_form.html.erb | 48 ++++++++-------- app/views/annotations/_form_content.html.erb | 20 +++---- app/views/annotations/edit.coffee | 57 ++++++++++--------- app/views/lectures/edit/_comments.html.erb | 14 ++--- app/views/lectures/edit/_preferences.html.erb | 20 +++---- app/views/media/_basics.html.erb | 22 +++---- app/views/media/feedback.html.erb | 12 ++-- app/views/media/play.html.erb | 14 ++--- config/locales/de.yml | 3 +- config/locales/en.yml | 3 +- 12 files changed, 124 insertions(+), 119 deletions(-) diff --git a/app/assets/stylesheets/thyme.scss b/app/assets/stylesheets/thyme.scss index 1474a4254..9b9e43f8f 100644 --- a/app/assets/stylesheets/thyme.scss +++ b/app/assets/stylesheets/thyme.scss @@ -507,19 +507,18 @@ $annotation_colors: ( box-sizing: border-box; } -input[type="radio"] { - display: none; - &:checked + label { - span { transform: scale(1.25); } - @each $name, $value in $annotation_colors { - .#{$name} { - border: 2px solid black; +#annotation-color-picker { + input[type="radio"] { + display: none; + &:checked + label { + span { transform: scale(1.25); } + @each $name, $value in $annotation_colors { + .#{$name} { + border: 2px solid black; + } } } } -} - -#annotation-color-picker { text-align: center; label { display: inline-block; diff --git a/app/views/annotations/_annotation_modal.html.erb b/app/views/annotations/_annotation_modal.html.erb index b9e0eefeb..79c119c53 100644 --- a/app/views/annotations/_annotation_modal.html.erb +++ b/app/views/annotations/_annotation_modal.html.erb @@ -2,20 +2,19 @@ \ No newline at end of file + diff --git a/app/views/annotations/_form.html.erb b/app/views/annotations/_form.html.erb index a17224abc..bef853d08 100644 --- a/app/views/annotations/_form.html.erb +++ b/app/views/annotations/_form.html.erb @@ -4,26 +4,26 @@

- + <%= f.hidden_field :medium_id, value: @medium_id %> <%= f.hidden_field :total_seconds, value: @total_seconds %> - + <%= t('admin.annotation.comment') %> <%= f.text_area :comment, class: 'form-control' %>
- + <%= t('admin.annotation.color') %>
<% for i in 1..15 do %> @@ -37,62 +37,62 @@ <% end %> <% end %>
-
- + <%= t('admin.annotation.category') %>
-
+
<%= f.radio_button :category, :note, - class: 'custom-control-input' %> + class: 'form-check-input' %> <%= f.label :category, t('admin.annotation.note'), value: :note, - class: 'custom-control-label' %> + class: 'form-check-label' %>
-
+
<%= f.radio_button :category, :content, - class: 'custom-control-input' %> + class: 'form-check-input' %> <%= f.label :category, t('admin.annotation.content'), value: :content, - class: 'custom-control-label' %> + class: 'form-check-label' %>
-
+
<%= f.radio_button :category, :mistake, - class: 'custom-control-input' %> + class: 'form-check-input' %> <%= f.label :category, t('admin.annotation.mistake'), value: :mistake, - class: 'custom-control-label' %> + class: 'form-check-label' %>
-
+
<%= f.radio_button :category, :presentation, - class: 'custom-control-input' %> + class: 'form-check-input' %> <%= f.label :category, t('admin.annotation.presentation'), value: :presentation, - class: 'custom-control-label' %> + class: 'form-check-label' %>

- +

- + <% annotations_status = Medium.find_by(id: @medium_id).get_annotations_status %> <% if annotations_status == 1 %> <%= f.label :visible_for_teacher, class: "checkbox inline" do %> @@ -105,7 +105,7 @@ <% end %> <%= t('admin.annotation.post_as_comment') %>   - <%= f.check_box :post_as_comment, id: "post-as-comment" %> + <%= f.check_box :post_as_comment %>
@@ -115,12 +115,12 @@
<% end %> @@ -132,4 +132,4 @@ data-one-close-annotation="<%= t('admin.annotation.warning_message.one_close_annotation') %>" data-multiple-close-annotations1="<%= t('admin.annotation.warning_message.multiple_close_annotations_1') %>" data-multiple-close-annotations2="<%= t('admin.annotation.warning_message.multiple_close_annotations_2') %>"> -
\ No newline at end of file +
diff --git a/app/views/annotations/_form_content.html.erb b/app/views/annotations/_form_content.html.erb index 49cf18bbe..bac4a39b2 100644 --- a/app/views/annotations/_form_content.html.erb +++ b/app/views/annotations/_form_content.html.erb @@ -3,35 +3,35 @@
-
+
-
-
+
-
-
+
- @@ -41,5 +41,5 @@
- +
diff --git a/app/views/annotations/edit.coffee b/app/views/annotations/edit.coffee index 0cf37c9cc..b250d3a56 100644 --- a/app/views/annotations/edit.coffee +++ b/app/views/annotations/edit.coffee @@ -3,35 +3,39 @@ $('#annotation-modal-content').empty() $('#annotation-modal').modal('show') submitButton = document.getElementById('submit-button') -postAsComment = document.getElementById('post-as-comment') +postAsComment = document.getElementById('annotation_post_as_comment') postAsComment.addEventListener 'change', (evt) -> - message = document.getElementById('warning').dataset.publishing if this.checked - mistakeRadio = document.getElementById('annotation_category_mistake') - if mistakeRadio.checked - message += "\n" + document.getElementById('warning').dataset.mistake - medId = thyme.dataset.medium - rad = 60 # annotations that are inside this radius are considered as "near". - $.ajax Routes.near_mistake_annotations_path(), - type: 'GET' - dataType: 'json' - data: { - mediumId: medId - timestamp: video.currentTime - radius: rad - } - success: (c) -> - if c != undefined && c != 0 - if c == 1 - message += document.getElementById('warning').dataset.oneCloseAnnotation - else - message += document.getElementById('warning').dataset.multipleCloseAnnotations1 + - "" + c + - document.getElementById('warning').dataset.multipleCloseAnnotations2 - alert message - else - alert message + warningMessage() + return + +warningMessage = () -> + message = document.getElementById('warning').dataset.publishing + mistakeRadio = document.getElementById('annotation_category_mistake') + if mistakeRadio.checked + message += "\n" + document.getElementById('warning').dataset.mistake + medId = thyme.dataset.medium + rad = 60 # annotations that are inside this radius (in seconds) are considered as "near". + $.ajax Routes.near_mistake_annotations_path(), + type: 'GET' + dataType: 'json' + data: { + mediumId: medId + timestamp: video.currentTime + radius: rad + } + success: (c) -> + if c != undefined && c != 0 + if c == 1 + message += document.getElementById('warning').dataset.oneCloseAnnotation + else + message += document.getElementById('warning').dataset.multipleCloseAnnotations1 + + "" + c + + document.getElementById('warning').dataset.multipleCloseAnnotations2 + alert message + else + alert message return @@ -86,6 +90,7 @@ mistake = -> submitButton.disabled = false visibleForTeacher(true) postComment(true) + warningMessage() return presentation = -> diff --git a/app/views/lectures/edit/_comments.html.erb b/app/views/lectures/edit/_comments.html.erb index 1fe0bd2c0..ac3c59510 100644 --- a/app/views/lectures/edit/_comments.html.erb +++ b/app/views/lectures/edit/_comments.html.erb @@ -57,27 +57,27 @@
- +
<%= t('admin.lecture.enable_emergency_button') %>
-
+
<%= f.radio_button :annotations_status, 1, - class: 'custom-control-input' %> + class: 'form-check-input' %> <%= f.label :annotations_status, t('basics.yes_lc'), value: 1, - class: 'custom-control-label' %> + class: 'form-check-label' %>
-
+
<%= f.radio_button :annotations_status, -1, - class: 'custom-control-input' %> + class: 'form-check-input' %> <%= f.label :annotations_status, t('basics.no_lc'), value: -1, - class: 'custom-control-label' %> + class: 'form-check-label' %>
diff --git a/app/views/lectures/edit/_preferences.html.erb b/app/views/lectures/edit/_preferences.html.erb index 609c3215e..1d79ba00a 100644 --- a/app/views/lectures/edit/_preferences.html.erb +++ b/app/views/lectures/edit/_preferences.html.erb @@ -158,37 +158,37 @@
<% end %> - + <% if lecture.annotations_status == 1 %>
<%= t('admin.lecture.emergency_link') %> -
-
- -
-
- -
- -
- - - - - - - - - - -
-
+ <%= render partial: "annotations/annotation_area" %>
diff --git a/app/views/media/feedback.html.erb b/app/views/media/feedback.html.erb index 2fce12e03..f6553671a 100644 --- a/app/views/media/feedback.html.erb +++ b/app/views/media/feedback.html.erb @@ -52,25 +52,25 @@
From 526bc087b59c35f0d8c4949d6f09c7dcf03dacfb Mon Sep 17 00:00:00 2001 From: Splines Date: Sun, 22 Oct 2023 18:11:06 +0200 Subject: [PATCH 161/291] Merge together multiple annotation-related migrations --- db/migrate/20221015114100_create_annotations.rb | 2 ++ .../20230117124508_add_annotations_status_to_medium.rb | 2 +- .../20230117124528_add_annotations_status_to_lecture.rb | 2 +- ...62844_add_public_comment_id_and_subtext_to_annotation.rb | 6 ------ db/migrate/20231001065026_remove_subtext_from_annotation.rb | 5 ----- db/migrate/20231001065435_add_subcategory_to_annotation.rb | 5 ----- ...ange_annotations_status_default_in_media_and_lectures.rb | 6 ------ 7 files changed, 4 insertions(+), 24 deletions(-) delete mode 100644 db/migrate/20230523062844_add_public_comment_id_and_subtext_to_annotation.rb delete mode 100644 db/migrate/20231001065026_remove_subtext_from_annotation.rb delete mode 100644 db/migrate/20231001065435_add_subcategory_to_annotation.rb delete mode 100644 db/migrate/20231021100348_change_annotations_status_default_in_media_and_lectures.rb diff --git a/db/migrate/20221015114100_create_annotations.rb b/db/migrate/20221015114100_create_annotations.rb index c30220336..02bbd65bc 100644 --- a/db/migrate/20221015114100_create_annotations.rb +++ b/db/migrate/20221015114100_create_annotations.rb @@ -8,6 +8,8 @@ def change t.integer :category t.boolean :visible_for_teacher t.string :color + t.integer :public_comment_id + t.integer :subcategory t.timestamps end diff --git a/db/migrate/20230117124508_add_annotations_status_to_medium.rb b/db/migrate/20230117124508_add_annotations_status_to_medium.rb index 6de139be6..ca48b67d7 100644 --- a/db/migrate/20230117124508_add_annotations_status_to_medium.rb +++ b/db/migrate/20230117124508_add_annotations_status_to_medium.rb @@ -1,5 +1,5 @@ class AddAnnotationsStatusToMedium < ActiveRecord::Migration[7.0] def change - add_column :media, :annotations_status, :integer + add_column :media, :annotations_status, :integer, default: 0 end end diff --git a/db/migrate/20230117124528_add_annotations_status_to_lecture.rb b/db/migrate/20230117124528_add_annotations_status_to_lecture.rb index 3039866d7..e829fcc1a 100644 --- a/db/migrate/20230117124528_add_annotations_status_to_lecture.rb +++ b/db/migrate/20230117124528_add_annotations_status_to_lecture.rb @@ -1,5 +1,5 @@ class AddAnnotationsStatusToLecture < ActiveRecord::Migration[7.0] def change - add_column :lectures, :annotations_status, :integer + add_column :lectures, :annotations_status, :integer, default: -1 end end diff --git a/db/migrate/20230523062844_add_public_comment_id_and_subtext_to_annotation.rb b/db/migrate/20230523062844_add_public_comment_id_and_subtext_to_annotation.rb deleted file mode 100644 index 8a49b3d38..000000000 --- a/db/migrate/20230523062844_add_public_comment_id_and_subtext_to_annotation.rb +++ /dev/null @@ -1,6 +0,0 @@ -class AddPublicCommentIdAndSubtextToAnnotation < ActiveRecord::Migration[7.0] - def change - add_column :annotations, :public_comment_id, :integer - add_column :annotations, :subtext, :text - end -end diff --git a/db/migrate/20231001065026_remove_subtext_from_annotation.rb b/db/migrate/20231001065026_remove_subtext_from_annotation.rb deleted file mode 100644 index 2cb796b87..000000000 --- a/db/migrate/20231001065026_remove_subtext_from_annotation.rb +++ /dev/null @@ -1,5 +0,0 @@ -class RemoveSubtextFromAnnotation < ActiveRecord::Migration[7.0] - def change - remove_column :annotations, :subtext, :text - end -end diff --git a/db/migrate/20231001065435_add_subcategory_to_annotation.rb b/db/migrate/20231001065435_add_subcategory_to_annotation.rb deleted file mode 100644 index 62e2f8d4b..000000000 --- a/db/migrate/20231001065435_add_subcategory_to_annotation.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddSubcategoryToAnnotation < ActiveRecord::Migration[7.0] - def change - add_column :annotations, :subcategory, :integer - end -end diff --git a/db/migrate/20231021100348_change_annotations_status_default_in_media_and_lectures.rb b/db/migrate/20231021100348_change_annotations_status_default_in_media_and_lectures.rb deleted file mode 100644 index 52a2e7c21..000000000 --- a/db/migrate/20231021100348_change_annotations_status_default_in_media_and_lectures.rb +++ /dev/null @@ -1,6 +0,0 @@ -class ChangeAnnotationsStatusDefaultInMediaAndLectures < ActiveRecord::Migration[7.0] - def change - change_column_default :media, :annotations_status, 0 - change_column_default :lectures, :annotations_status, -1 - end -end From 735585f970e4744d0d65e4973720c58ba0abe8dd Mon Sep 17 00:00:00 2001 From: Splines Date: Sun, 22 Oct 2023 18:54:16 +0200 Subject: [PATCH 162/291] Improve code comment --- .../javascripts/thyme/display_manager.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/thyme/display_manager.js b/app/assets/javascripts/thyme/display_manager.js index 7bb011e55..7fac4e32f 100644 --- a/app/assets/javascripts/thyme/display_manager.js +++ b/app/assets/javascripts/thyme/display_manager.js @@ -9,8 +9,8 @@ class DisplayManager { elements = An array containing JQuery references on the HTML elements that should be hidden, when the display is too small. - onEnlarge = A reference on a function that is called when the display - changes from small to large. Use this for player specific behaviour. + onEnlarge = A reference to a function that is called when the display + changes from small to large. Use this for player specific behavior. */ this.elements = elements; this.onEnlarge = onEnlarge; @@ -41,15 +41,15 @@ class DisplayManager { const dm = this; if (window.matchMedia("screen and (max-width: " + - thymeAttributes.hideControlBarThreshold.x + "px)").matches || - window.matchMedia("screen and (max-height: " + + thymeAttributes.hideControlBarThreshold.x + "px)").matches || + window.matchMedia("screen and (max-height: " + thymeAttributes.hideControlBarThreshold.y + "px)").matches) { dm.adaptToSmallDisplay(); } if (window.matchMedia("screen and (max-device-width: " + - thymeAttributes.hideControlBarThreshold.x + "px)").matches || - window.matchMedia("screen and (max-device-height: " + + thymeAttributes.hideControlBarThreshold.x + "px)").matches || + window.matchMedia("screen and (max-device-height: " + thymeAttributes.hideControlBarThreshold.y + "px)").matches) { dm.adaptToSmallDisplay(); } @@ -57,14 +57,14 @@ class DisplayManager { // mediaQuery listener for very small screens const matchVerysmallX = window.matchMedia("screen and (max-width: " + thymeAttributes.hideControlBarThreshold.x + "px)"); - matchVerysmallX.addListener(function(result) { + matchVerysmallX.addListener(function (result) { if (result.matches) { dm.adaptToSmallDisplay(); } }); const matchVerysmallY = window.matchMedia("screen and (max-height: " + thymeAttributes.hideControlBarThreshold.y + "px)"); - matchVerysmallY.addListener(function(result) { + matchVerysmallY.addListener(function (result) { if (result.matches) { dm.adaptToSmallDisplay(); } @@ -72,14 +72,14 @@ class DisplayManager { const matchVerysmalldeviceX = window.matchMedia("screen and (max-device-width: " + thymeAttributes.hideControlBarThreshold.x + "px)"); - matchVerysmalldeviceX.addListener(function(result) { + matchVerysmalldeviceX.addListener(function (result) { if (result.matches) { dm.adaptToSmallDisplay(); } }); const matchVerysmalldeviceY = window.matchMedia("screen and (max-device-height: " + thymeAttributes.hideControlBarThreshold.y + "px)"); - matchVerysmalldeviceY.addListener(function(result) { + matchVerysmalldeviceY.addListener(function (result) { if (result.matches) { dm.adaptToSmallDisplay(); } @@ -88,7 +88,7 @@ class DisplayManager { // mediaQuery listener for normal screens let matchNormalX = window.matchMedia("screen and (min-width: " + (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); - matchNormalX.addListener(function(result) { + matchNormalX.addListener(function (result) { let matchNormalY; matchNormalY = window.matchMedia("screen and (min-height: " + (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); @@ -98,7 +98,7 @@ class DisplayManager { }); const matchNormalY = window.matchMedia("screen and (min-height: " + (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); - matchNormalY.addListener(function(result) { + matchNormalY.addListener(function (result) { matchNormalX = window.matchMedia("screen and (min-width: " + (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); if (result.matches && matchNormalX.matches) { @@ -109,7 +109,7 @@ class DisplayManager { let matchNormaldeviceX = window.matchMedia("screen and (min-device-width: " + (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); let matchNormaldeviceY; - matchNormaldeviceX.addListener(function(result) { + matchNormaldeviceX.addListener(function (result) { matchNormaldeviceY = window.matchMedia("screen and (min-device-height: " + (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); if (result.matches && matchNormalY.matches) { @@ -118,7 +118,7 @@ class DisplayManager { }); matchNormaldeviceY = window.matchMedia("screen and (min-device-height: " + (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); - matchNormaldeviceY.addListener(function(result) { + matchNormaldeviceY.addListener(function (result) { matchNormaldeviceX = window.matchMedia("screen and (min-device-width: " + (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); if (result.matches && matchNormalX.matches) { From 759d99c368dd9e3dbc119279c629a17f26afd2cb Mon Sep 17 00:00:00 2001 From: Splines Date: Sun, 22 Oct 2023 18:56:24 +0200 Subject: [PATCH 163/291] Remove renaming of "this" in JS class --- .../javascripts/thyme/display_manager.js | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/thyme/display_manager.js b/app/assets/javascripts/thyme/display_manager.js index 7fac4e32f..65beb2037 100644 --- a/app/assets/javascripts/thyme/display_manager.js +++ b/app/assets/javascripts/thyme/display_manager.js @@ -38,20 +38,18 @@ class DisplayManager { // Check screen size and trigger the right method updateControlBarType() { - const dm = this; - if (window.matchMedia("screen and (max-width: " + thymeAttributes.hideControlBarThreshold.x + "px)").matches || window.matchMedia("screen and (max-height: " + thymeAttributes.hideControlBarThreshold.y + "px)").matches) { - dm.adaptToSmallDisplay(); + this.adaptToSmallDisplay(); } if (window.matchMedia("screen and (max-device-width: " + thymeAttributes.hideControlBarThreshold.x + "px)").matches || window.matchMedia("screen and (max-device-height: " + thymeAttributes.hideControlBarThreshold.y + "px)").matches) { - dm.adaptToSmallDisplay(); + this.adaptToSmallDisplay(); } // mediaQuery listener for very small screens @@ -59,14 +57,14 @@ class DisplayManager { thymeAttributes.hideControlBarThreshold.x + "px)"); matchVerysmallX.addListener(function (result) { if (result.matches) { - dm.adaptToSmallDisplay(); + this.adaptToSmallDisplay(); } }); const matchVerysmallY = window.matchMedia("screen and (max-height: " + thymeAttributes.hideControlBarThreshold.y + "px)"); matchVerysmallY.addListener(function (result) { if (result.matches) { - dm.adaptToSmallDisplay(); + this.adaptToSmallDisplay(); } }); @@ -74,14 +72,14 @@ class DisplayManager { thymeAttributes.hideControlBarThreshold.x + "px)"); matchVerysmalldeviceX.addListener(function (result) { if (result.matches) { - dm.adaptToSmallDisplay(); + this.adaptToSmallDisplay(); } }); const matchVerysmalldeviceY = window.matchMedia("screen and (max-device-height: " + thymeAttributes.hideControlBarThreshold.y + "px)"); matchVerysmalldeviceY.addListener(function (result) { if (result.matches) { - dm.adaptToSmallDisplay(); + this.adaptToSmallDisplay(); } }); @@ -93,7 +91,7 @@ class DisplayManager { matchNormalY = window.matchMedia("screen and (min-height: " + (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); if (result.matches && matchNormalY.matches) { - dm.adaptToLargeDisplay(); + this.adaptToLargeDisplay(); } }); const matchNormalY = window.matchMedia("screen and (min-height: " + @@ -102,7 +100,7 @@ class DisplayManager { matchNormalX = window.matchMedia("screen and (min-width: " + (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); if (result.matches && matchNormalX.matches) { - dm.adaptToLargeDisplay(); + this.adaptToLargeDisplay(); } }); @@ -113,7 +111,7 @@ class DisplayManager { matchNormaldeviceY = window.matchMedia("screen and (min-device-height: " + (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); if (result.matches && matchNormalY.matches) { - dm.adaptToLargeDisplay(); + this.adaptToLargeDisplay(); } }); matchNormaldeviceY = window.matchMedia("screen and (min-device-height: " + @@ -122,7 +120,7 @@ class DisplayManager { matchNormaldeviceX = window.matchMedia("screen and (min-device-width: " + (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); if (result.matches && matchNormalX.matches) { - dm.adaptToLargeDisplay(); + this.adaptToLargeDisplay(); } }); } From 80924242df26999306fa2e829bf6a81ab5a651dd Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 23 Oct 2023 19:41:29 +0200 Subject: [PATCH 164/291] Redesign "post as comment" checkbox according to Bootstrap here: https://getbootstrap.com/docs/5.3/forms/checks-radios/#checks --- app/views/annotations/_form.html.erb | 14 ++++++++------ app/views/annotations/edit.js.erb | 2 ++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/views/annotations/_form.html.erb b/app/views/annotations/_form.html.erb index db29f991c..e1686e951 100644 --- a/app/views/annotations/_form.html.erb +++ b/app/views/annotations/_form.html.erb @@ -121,12 +121,14 @@ <%= f.hidden_field :visible_for_teacher, value: false %> <% end %> - <%= f.label :post_as_comment, class: "checkbox inline" do %> - - <%= t('admin.annotation.post_as_comment') %>   - - <%= f.check_box :post_as_comment %> - <% end %> + +
+ <%= f.check_box :post_as_comment, class: "form-check-input" %> + <%= f.label :post_as_comment, + t('admin.annotation.post_as_comment'), + class: "form-check-label" %> + <%= helpdesk(t('admin.annotation.post_as_comment_tooltip'), false) %> +

diff --git a/app/views/annotations/edit.js.erb b/app/views/annotations/edit.js.erb index b99bedac4..7eddd9931 100644 --- a/app/views/annotations/edit.js.erb +++ b/app/views/annotations/edit.js.erb @@ -243,3 +243,5 @@ if (<%= @posted %>) { postComment(true); postAsComment.disabled = true; } + +initBootstrapPopovers(); From afadcff6d69bc283b152260e542ff20887d37471 Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 23 Oct 2023 19:54:21 +0200 Subject: [PATCH 165/291] Restructure cancel/close buttons --- app/views/annotations/_form.html.erb | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/app/views/annotations/_form.html.erb b/app/views/annotations/_form.html.erb index e1686e951..3dc1235d3 100644 --- a/app/views/annotations/_form.html.erb +++ b/app/views/annotations/_form.html.erb @@ -133,18 +133,27 @@
<% end %> -
\ No newline at end of file +
From dcc0d1c31a3d02a89c8f5ef95e4985765d133c7a Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Thu, 26 Oct 2023 10:04:34 +0200 Subject: [PATCH 166/291] Improve annotation commontator::comment association --- .../thyme/annotations/annotation_area.js | 2 +- .../thyme/components/emergency_button.js | 2 +- .../thyme/display_manager (Kopie).js | 130 ++++++++++++++++++ app/assets/javascripts/thyme/thyme_player.js | 2 +- app/controllers/annotations_controller.rb | 129 +++++++++-------- app/models/annotation.rb | 9 ++ app/views/commontator/comments/_body.html.erb | 11 ++ app/views/media/comments/_comments.html.erb | 2 +- app/views/media/show_comments.html.erb | 2 +- config/initializers/commontator.rb | 2 +- lib/extensions/commontator/comment.rb | 7 +- 11 files changed, 228 insertions(+), 70 deletions(-) create mode 100644 app/assets/javascripts/thyme/display_manager (Kopie).js diff --git a/app/assets/javascripts/thyme/annotations/annotation_area.js b/app/assets/javascripts/thyme/annotations/annotation_area.js index c1ddaf734..44bae5107 100644 --- a/app/assets/javascripts/thyme/annotations/annotation_area.js +++ b/app/assets/javascripts/thyme/annotations/annotation_area.js @@ -149,7 +149,7 @@ class AnnotationArea { type: 'GET', dataType: 'script', data: { - annotationId: annotation.id + annotation_id: annotation.id }, success: function(permitted) { if (permitted === "false") { diff --git a/app/assets/javascripts/thyme/components/emergency_button.js b/app/assets/javascripts/thyme/components/emergency_button.js index 6bb646612..1d1e4987c 100644 --- a/app/assets/javascripts/thyme/components/emergency_button.js +++ b/app/assets/javascripts/thyme/components/emergency_button.js @@ -12,7 +12,7 @@ class EmergencyButton extends Component { dataType: 'script', data: { total_seconds: video.currentTime, - mediumId: thymeAttributes.mediumId + medium_id: thymeAttributes.mediumId } }); // When the modal opens, all key listeners must be diff --git a/app/assets/javascripts/thyme/display_manager (Kopie).js b/app/assets/javascripts/thyme/display_manager (Kopie).js new file mode 100644 index 000000000..7bb011e55 --- /dev/null +++ b/app/assets/javascripts/thyme/display_manager (Kopie).js @@ -0,0 +1,130 @@ +/** + * A DisplayManager helps to switch between the full thyme player and + * the native HTML player shown on small devices. + */ +class DisplayManager { + + constructor(elements, onEnlarge) { + /* + elements = An array containing JQuery references on the HTML elements + that should be hidden, when the display is too small. + + onEnlarge = A reference on a function that is called when the display + changes from small to large. Use this for player specific behaviour. + */ + this.elements = elements; + this.onEnlarge = onEnlarge; + } + + + + // on small display, fall back to standard browser player + adaptToSmallDisplay() { + for (let e of this.elements) { + e.hide(); + } + thymeAttributes.video.style.width = '100%'; + thymeAttributes.video.controls = true; + } + + // on large display, use anything thyme has to offer, disable native player + adaptToLargeDisplay() { + thymeAttributes.video.controls = false; + for (let e of this.elements) { + e.show(); + } + this.onEnlarge(); + } + + // Check screen size and trigger the right method + updateControlBarType() { + const dm = this; + + if (window.matchMedia("screen and (max-width: " + + thymeAttributes.hideControlBarThreshold.x + "px)").matches || + window.matchMedia("screen and (max-height: " + + thymeAttributes.hideControlBarThreshold.y + "px)").matches) { + dm.adaptToSmallDisplay(); + } + + if (window.matchMedia("screen and (max-device-width: " + + thymeAttributes.hideControlBarThreshold.x + "px)").matches || + window.matchMedia("screen and (max-device-height: " + + thymeAttributes.hideControlBarThreshold.y + "px)").matches) { + dm.adaptToSmallDisplay(); + } + + // mediaQuery listener for very small screens + const matchVerysmallX = window.matchMedia("screen and (max-width: " + + thymeAttributes.hideControlBarThreshold.x + "px)"); + matchVerysmallX.addListener(function(result) { + if (result.matches) { + dm.adaptToSmallDisplay(); + } + }); + const matchVerysmallY = window.matchMedia("screen and (max-height: " + + thymeAttributes.hideControlBarThreshold.y + "px)"); + matchVerysmallY.addListener(function(result) { + if (result.matches) { + dm.adaptToSmallDisplay(); + } + }); + + const matchVerysmalldeviceX = window.matchMedia("screen and (max-device-width: " + + thymeAttributes.hideControlBarThreshold.x + "px)"); + matchVerysmalldeviceX.addListener(function(result) { + if (result.matches) { + dm.adaptToSmallDisplay(); + } + }); + const matchVerysmalldeviceY = window.matchMedia("screen and (max-device-height: " + + thymeAttributes.hideControlBarThreshold.y + "px)"); + matchVerysmalldeviceY.addListener(function(result) { + if (result.matches) { + dm.adaptToSmallDisplay(); + } + }); + + // mediaQuery listener for normal screens + let matchNormalX = window.matchMedia("screen and (min-width: " + + (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); + matchNormalX.addListener(function(result) { + let matchNormalY; + matchNormalY = window.matchMedia("screen and (min-height: " + + (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); + if (result.matches && matchNormalY.matches) { + dm.adaptToLargeDisplay(); + } + }); + const matchNormalY = window.matchMedia("screen and (min-height: " + + (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); + matchNormalY.addListener(function(result) { + matchNormalX = window.matchMedia("screen and (min-width: " + + (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); + if (result.matches && matchNormalX.matches) { + dm.adaptToLargeDisplay(); + } + }); + + let matchNormaldeviceX = window.matchMedia("screen and (min-device-width: " + + (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); + let matchNormaldeviceY; + matchNormaldeviceX.addListener(function(result) { + matchNormaldeviceY = window.matchMedia("screen and (min-device-height: " + + (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); + if (result.matches && matchNormalY.matches) { + dm.adaptToLargeDisplay(); + } + }); + matchNormaldeviceY = window.matchMedia("screen and (min-device-height: " + + (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); + matchNormaldeviceY.addListener(function(result) { + matchNormaldeviceX = window.matchMedia("screen and (min-device-width: " + + (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); + if (result.matches && matchNormalX.matches) { + dm.adaptToLargeDisplay(); + } + }); + } + +}; diff --git a/app/assets/javascripts/thyme/thyme_player.js b/app/assets/javascripts/thyme/thyme_player.js index ebed8a5b1..882738d3e 100644 --- a/app/assets/javascripts/thyme/thyme_player.js +++ b/app/assets/javascripts/thyme/thyme_player.js @@ -101,7 +101,7 @@ $(document).on('turbolinks:load', function() { type: 'DELETE', dataType: 'json', data: { - annotationId: annotationId + annotation_id: annotationId }, success: function() { annotationManager.updateAnnotations(); diff --git a/app/controllers/annotations_controller.rb b/app/controllers/annotations_controller.rb index 76cb84f97..2785e2981 100644 --- a/app/controllers/annotations_controller.rb +++ b/app/controllers/annotations_controller.rb @@ -1,66 +1,73 @@ class AnnotationsController < ApplicationController - before_action :update_comments, only: [:edit, :update_annotations] - authorize_resource def new @annotation = Annotation.new(category: :note, color: Annotation.colors[1]) + @total_seconds = params[:total_seconds] - @medium_id = params[:mediumId] - @posted = (@annotation.public_comment_id != nil) + @medium_id = params[:medium_id] + @posted = false render :edit end def edit - @annotation = Annotation.find(params[:annotationId]) + @annotation = Annotation.find(params[:annotation_id]) + # only allow editing, if the current user created the annotation if @annotation.user_id != current_user.id render json: false return end + @total_seconds = @annotation.timestamp.total_seconds @medium_id = @annotation.medium_id @posted = (@annotation.public_comment_id != nil) - end - def update - @annotation = Annotation.find(params[:id]) - comment_id = @annotation.public_comment_id - if (comment_id == nil) - @annotation.public_comment_id = post_comment - else - medium = Medium.find_by_id(params[:annotation][:medium_id]) - total_seconds = params[:annotation][:total_seconds] - comment = params[:annotation][:comment] - commontator_comment = Commontator::Comment.find_by(id: comment_id) - commontator_comment.update(editor: current_user, - body: comment_html(medium, total_seconds, comment)) - end - @annotation.update(annotation_params) + # if this annotation has an associated commontator comment, + # we have to call the "get_comment" method in order to get + # the text. + @annotation.comment = @annotation.get_comment end def create @annotation = Annotation.new(annotation_params) @annotation.user_id = current_user.id - @total_seconds = params[:annotation][:total_seconds] + @total_seconds = annotation_auxiliary_params[:total_seconds] @annotation.timestamp = TimeStamp.new(total_seconds: @total_seconds) - @annotation.public_comment_id = post_comment + @annotation.public_comment_id = post_comment(@annotation) @annotation.save render :update end + def update + @annotation = Annotation.find(params[:id]) + @annotation.update(annotation_params) + + @annotation.public_comment_id = post_comment(@annotation) + @annotation.save + end + def show @annotation = Annotation.find(params[:id]) end def destroy - annotation = Annotation.find(params[:annotationId]) - comment = Commontator::Comment.find_by(id: annotation.public_comment_id) - comment.update(deleted_at: DateTime.now) + annotation = Annotation.find(params[:annotation_id]) + + # only the owner of the annotation is allowed to delete it + return unless annotation.user == current_user + + # delete associated commontator comment + unless annotation.public_comment_id.nil? + commontator_comment = Commontator::Comment.find_by(id: annotation.public_comment_id) + commontator_comment.update(deleted_at: DateTime.now) + end + annotation.destroy + render json: [] end @@ -78,14 +85,21 @@ def update_annotations user: current_user) end + # If annotation is associated to a comment, + # the field "comment" is empty -> get it from the commontator comment + annotations.each do |a| + a.comment = a.get_comment + end + # Convert to JSON (for easier hash operations) annotations = annotations.as_json # Filter attributes and add boolean "belongs_to_current_user". annotations.each do |a| + public_comment_id = a['public_comment_id'] a['belongs_to_current_user'] = (current_user.id == a['user_id']) - a.slice!('category', 'color', 'comment', 'id', - 'belongs_to_current_user', 'timestamp', 'subcategory') + a.slice!('belongs_to_current_user', 'category', 'color', 'comment', + 'id', 'subcategory', 'timestamp') end render json: annotations @@ -117,44 +131,43 @@ def current_ability def annotation_params params.require(:annotation).permit( - :category, :color, :comment, :medium_id, :subcategory, :visible_for_teacher) + :category, :color, :medium_id, :subcategory, :visible_for_teacher + ) end - def post_comment - if params[:annotation][:post_as_comment] == "1" - medium = Medium.find_by_id(params[:annotation][:medium_id]) - total_seconds = params[:annotation][:total_seconds] - commontator_thread = medium.commontator_thread - commontator_comment = Commontator::Comment.new( - thread: commontator_thread, - creator: current_user, - body: comment_html = comment_html(medium, - total_seconds, - annotation_params[:comment]) - ) - commontator_comment.save - return commontator_comment.id - end + def annotation_auxiliary_params + params.require(:annotation).permit( + :total_seconds, :post_as_comment, :comment + ) end - def comment_html(medium, total_seconds, comment) - timestamp = TimeStamp.new(total_seconds: total_seconds).hms_colon_string - return comment + "\n" + - 'Thymestamp: ' + timestamp + '' - end + def post_comment(annotation) + public_comment_id = annotation.public_comment_id + aux_params = annotation_auxiliary_params + + # return if checkbox "post_as_comment" is not checked and if there is no comment to update + return if aux_params[:post_as_comment] != "1" && public_comment_id.nil? - def update_comments - Annotation.where(medium: params[:mediumId]).each do |annotation| - # find comment - unless annotation.public_comment.nil? - comment = annotation.public_comment.body + comment = aux_params[:comment] - # remove "Thymestamp: H:MM:SS" at the end of the string - index = comment.rindex("\nThymestamp") - annotation.update(comment: comment[0 .. index - 1]) unless index == nil - end + if public_comment_id.nil? # comment doesn't exist yet -> create one + medium = annotation.medium + commontator_comment = Commontator::Comment.create( + thread: medium.commontator_thread, + creator: current_user, + body: comment + ) + commontator_comment.annotation = annotation + else # comment already exists -> update it + commontator_comment = Commontator::Comment.find_by(id: public_comment_id) + commontator_comment.update(editor: current_user, + body: comment) end + + # delete comment as it is already saved in the commontator comment model + annotation.comment = nil + + return commontator_comment.id end end diff --git a/app/models/annotation.rb b/app/models/annotation.rb index eff5f2ee0..73147eb4f 100644 --- a/app/models/annotation.rb +++ b/app/models/annotation.rb @@ -10,6 +10,15 @@ class Annotation < ApplicationRecord enum category: { note: 0, content: 1, mistake: 2, presentation: 3 } enum subcategory: { definition: 0, argument: 1, strategy: 2 } + def get_comment + if self.public_comment_id.nil? + return self.comment + else + commontator_comment = Commontator::Comment.find_by(id: public_comment_id) + return commontator_comment.body + end + end + def self.colors color_map = { 1 => '#DB2828', diff --git a/app/views/commontator/comments/_body.html.erb b/app/views/commontator/comments/_body.html.erb index 6ec78b6ee..75da4696f 100644 --- a/app/views/commontator/comments/_body.html.erb +++ b/app/views/commontator/comments/_body.html.erb @@ -4,3 +4,14 @@ %> <%= commontator_simple_format comment.body %> + + +<% annotation = comment.annotation %> +<% unless annotation.nil? %> + <% medium = annotation.medium %> + <% timestamp = annotation.timestamp %> +
+ Thymestamp: + + <%= timestamp.hms_colon_string %> +<% end %> diff --git a/app/views/media/comments/_comments.html.erb b/app/views/media/comments/_comments.html.erb index 0fe39b445..e77225b82 100644 --- a/app/views/media/comments/_comments.html.erb +++ b/app/views/media/comments/_comments.html.erb @@ -19,4 +19,4 @@ style="max-height: 30vh; overflow-y: scroll;"> <%= commontator_thread(medium) %>
-
\ No newline at end of file + diff --git a/app/views/media/show_comments.html.erb b/app/views/media/show_comments.html.erb index 20e1f4a6c..e38819cb4 100644 --- a/app/views/media/show_comments.html.erb +++ b/app/views/media/show_comments.html.erb @@ -13,4 +13,4 @@ <%= commontator_thread(@medium) %> - \ No newline at end of file + diff --git a/config/initializers/commontator.rb b/config/initializers/commontator.rb index 1039f4048..ba0a7970e 100644 --- a/config/initializers/commontator.rb +++ b/config/initializers/commontator.rb @@ -320,4 +320,4 @@ end rescue ActiveRecord::NoDatabaseError end -end \ No newline at end of file +end diff --git a/lib/extensions/commontator/comment.rb b/lib/extensions/commontator/comment.rb index 0508d109c..3da558ce4 100644 --- a/lib/extensions/commontator/comment.rb +++ b/lib/extensions/commontator/comment.rb @@ -5,16 +5,11 @@ module Comment included do has_one :annotation, foreign_key: :public_comment_id - after_update :update_annotation def medium thread.commontable end end - - def update_annotation - pp 'we can do something here' - end end end -end \ No newline at end of file +end From a9682758d77e5cff3732c91d48d38d2c2d182b4c Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Thu, 26 Oct 2023 10:18:05 +0200 Subject: [PATCH 167/291] Code clean up --- .../thyme/annotations/annotation.js | 2 +- .../thyme/annotations/annotation_area.js | 34 +++++++++---------- .../thyme/annotations/annotation_manager.js | 8 ++--- .../components/annotation_category_toggle.js | 4 +-- .../thyme/components/mute_button.js | 2 +- .../thyme/components/play_button.js | 2 +- .../javascripts/thyme/control_bar_hider.js | 2 +- app/assets/javascripts/thyme/heatmap.js | 6 ++-- app/assets/javascripts/thyme/key_shortcuts.js | 8 ++--- .../javascripts/thyme/metadata_manager.js | 10 +++--- app/assets/javascripts/thyme/thyme_editor.js | 2 +- .../javascripts/thyme/thyme_feedback.js | 4 +-- app/assets/javascripts/thyme/thyme_player.js | 4 +-- app/assets/javascripts/thyme/utility.js | 2 +- 14 files changed, 45 insertions(+), 45 deletions(-) diff --git a/app/assets/javascripts/thyme/annotations/annotation.js b/app/assets/javascripts/thyme/annotations/annotation.js index 36cea9140..c13e901e2 100644 --- a/app/assets/javascripts/thyme/annotations/annotation.js +++ b/app/assets/javascripts/thyme/annotations/annotation.js @@ -4,7 +4,7 @@ class Annotation { constructor(json) { - // We only save attributes that are needed in the thyme related JavaScripts in the asset pipeline! + // We only save attributes that are needed in the thyme related JavaScripts! this.category = Category.getByName(json.category); this.color = json.color; this.comment = json.comment; diff --git a/app/assets/javascripts/thyme/annotations/annotation_area.js b/app/assets/javascripts/thyme/annotations/annotation_area.js index 44bae5107..149a6f52d 100644 --- a/app/assets/javascripts/thyme/annotations/annotation_area.js +++ b/app/assets/javascripts/thyme/annotations/annotation_area.js @@ -6,29 +6,29 @@ class AnnotationArea { static DISABLED_BUTTON_OPACITY = 0.2; /* - fancyStyle = If true, all buttons are shown, if false, - only previous, goto and next are shown. + hasFancyStyle = If true, all buttons are shown, if false, + only previous, goto and next are shown. - colorFunc = A function for colorizing the annotation area - which takes an annotation as argument and gives - back a color. + colorFunc = A function for colorizing the annotation area + which takes an annotation as argument and gives + back a color. - onClose = A function that is executed when closing the - annotation area. + onClose = A function that is executed when closing the + annotation area. - isValid = A function which takes an annotation as argument - and returns true, if and only if the annotation is - "valid", i.e. should be visualized in the annotation - area. (This is needed to skip unwanted annotations in - the previous/next button listeners.) + isValid = A function which takes an annotation as argument + and returns true, if and only if the annotation is + "valid", i.e. should be visualized in the annotation + area. (This is needed to skip unwanted annotations in + the previous/next button listeners.) */ - constructor(fancyStyle, colorFunc, onClose, isValid) { + constructor(hasFancyStyle, colorFunc, onClose, isValid) { this.isActive = false; this.annotation = null; // the current annotation this.colorFunc = colorFunc; this.onClose = onClose; this.isValid = isValid; - this.fancyStyle = fancyStyle; + this.hasFancyStyle = hasFancyStyle; this.caption = $('#annotation-caption'); this.inforBar = $('#annotation-infobar'); @@ -41,7 +41,7 @@ class AnnotationArea { this.localesId = 'annotation-locales'; - if (fancyStyle === false) { + if (!hasFancyStyle) { this.editButton.hide(); this.closeButton.hide(); } @@ -79,7 +79,7 @@ class AnnotationArea { this.#updatePreviousButton(annotation); this.#updateNextButton(annotation); this.#updateGotoButton(annotation); - if (this.fancyStyle == true) { + if (this.hasFancyStyle) { this.#updateEditButton(annotation); this.#updateCloseButton(annotation); } @@ -212,4 +212,4 @@ class AnnotationArea { return null; } -} \ No newline at end of file +} diff --git a/app/assets/javascripts/thyme/annotations/annotation_manager.js b/app/assets/javascripts/thyme/annotations/annotation_manager.js index 7171e3607..19b43f0d8 100644 --- a/app/assets/javascripts/thyme/annotations/annotation_manager.js +++ b/app/assets/javascripts/thyme/annotations/annotation_manager.js @@ -52,7 +52,7 @@ class AnnotationManager { function onClick() { annotationManager.onClick(a); } - if (this.sizeFunc !== null && this.sizeFunc(a) === true) { + if (this.sizeFunc !== null && this.sizeFunc(a)) { a.createBigMarker(this.colorFunc(a), this.strokeColorFunc(a), onClick); } else { a.createMarker(this.colorFunc(a), this.strokeColorFunc(a), onClick); @@ -82,7 +82,7 @@ class AnnotationManager { success: function(annotations) { // update the annotation field in thymeAttributes thymeAttributes.annotations = []; - if (annotations === null) { + if (!annotations) { return; } for (let a of annotations) { @@ -96,7 +96,7 @@ class AnnotationManager { /* sorts all annotations according to their timestamp */ static sortAnnotations() { - if (thymeAttributes.annotations === null) { + if (!thymeAttributes.annotations) { return; } thymeAttributes.annotations.sort(function(ann1, ann2) { @@ -138,4 +138,4 @@ class AnnotationManager { return undefined; } -} \ No newline at end of file +} diff --git a/app/assets/javascripts/thyme/components/annotation_category_toggle.js b/app/assets/javascripts/thyme/components/annotation_category_toggle.js index 3c7014fa1..bfbcf9824 100644 --- a/app/assets/javascripts/thyme/components/annotation_category_toggle.js +++ b/app/assets/javascripts/thyme/components/annotation_category_toggle.js @@ -16,14 +16,14 @@ class AnnotationCategoryToggle extends Component { const category = this.category; const check = document.getElementById(this.element.id + '-check'); const heatmap = this.heatmap; - if (heatmap != null) { + if (heatmap) { heatmap.addCategory(category); // add category when adding the button } check.addEventListener('click', function() { thymeAttributes.annotationManager.updateAnnotations(); if (heatmap) { - if (toggle.getValue() === true) { + if (toggle.getValue()) { heatmap.addCategory(category); } else { heatmap.removeCategory(category); diff --git a/app/assets/javascripts/thyme/components/mute_button.js b/app/assets/javascripts/thyme/components/mute_button.js index c1676b440..8b138d854 100644 --- a/app/assets/javascripts/thyme/components/mute_button.js +++ b/app/assets/javascripts/thyme/components/mute_button.js @@ -5,7 +5,7 @@ class MuteButton extends Component { const element = this.element; element.addEventListener('click', function() { - if (video.muted === false) { + if (video.muted) { video.muted = true; element.innerHTML = 'volume_off'; } else { diff --git a/app/assets/javascripts/thyme/components/play_button.js b/app/assets/javascripts/thyme/components/play_button.js index 3864580fc..a02d70746 100644 --- a/app/assets/javascripts/thyme/components/play_button.js +++ b/app/assets/javascripts/thyme/components/play_button.js @@ -5,7 +5,7 @@ class PlayButton extends Component { const element = this.element; element.addEventListener('click', function() { - if (video.paused === true) { + if (video.paused) { video.play(); } else { video.pause(); diff --git a/app/assets/javascripts/thyme/control_bar_hider.js b/app/assets/javascripts/thyme/control_bar_hider.js index f221b8773..e02c38901 100644 --- a/app/assets/javascripts/thyme/control_bar_hider.js +++ b/app/assets/javascripts/thyme/control_bar_hider.js @@ -46,7 +46,7 @@ class ControlBarHider { function resetTimer() { clearTimeout(t); t = setTimeout(function() { - if (controlBarHider.hideBlocker === true) { + if (controlBarHider.hideBlocker) { return; } controlBarHider.hideControlBar(); diff --git a/app/assets/javascripts/thyme/heatmap.js b/app/assets/javascripts/thyme/heatmap.js index 2be1adddc..7f9338f88 100644 --- a/app/assets/javascripts/thyme/heatmap.js +++ b/app/assets/javascripts/thyme/heatmap.js @@ -18,7 +18,7 @@ class Heatmap { draw() { - if (thymeAttributes.annotations === null) { + if (thymeAttributes.annotations == null) { return; } this.heatmap.empty(); @@ -48,7 +48,7 @@ class Heatmap { */ for (const a of thymeAttributes.annotations) { const valid = this.#isValidCategory(a.category); - if (valid === true) { + if (valid) { colors.push(a.category.color); } const time = a.seconds; @@ -56,7 +56,7 @@ class Heatmap { for (let x = position - Heatmap.RADIUS; x <= position + Heatmap.RADIUS; x++) { let y = Heatmap.#sinX(x, position, Heatmap.RADIUS); pixelsAll[x + Heatmap.RADIUS] += y; - if (valid === true) { + if (valid) { pixels[x + Heatmap.RADIUS] += y; } } diff --git a/app/assets/javascripts/thyme/key_shortcuts.js b/app/assets/javascripts/thyme/key_shortcuts.js index 1dffae8c0..65476542d 100644 --- a/app/assets/javascripts/thyme/key_shortcuts.js +++ b/app/assets/javascripts/thyme/key_shortcuts.js @@ -15,12 +15,12 @@ const thymeKeyShortcuts = { const video = document.getElementById('video'); window.addEventListener('keydown', function(evt) { - if (thymeAttributes.lockKeyListeners === true) { + if (thymeAttributes.lockKeyListeners) { return; } const key = evt.key; if (key === ' ') { - if (video.paused === true) { + if (video.paused) { video.play(); } else { video.pause(); @@ -51,7 +51,7 @@ const thymeKeyShortcuts = { */ addPlayerShortcuts() { window.addEventListener('keydown', function(evt) { - if (thymeAttributes.lockKeyListeners === true) { + if (thymeAttributes.lockKeyListeners) { return; } const key = evt.key; @@ -80,7 +80,7 @@ const thymeKeyShortcuts = { */ addFeedbackShortcuts() { window.addEventListener('keydown', function(evt) { - if (thymeAttributes.lockKeyListeners === true) { + if (thymeAttributes.lockKeyListeners) { return; } const key = evt.key; diff --git a/app/assets/javascripts/thyme/metadata_manager.js b/app/assets/javascripts/thyme/metadata_manager.js index dcdcc48ff..d5c3b2677 100644 --- a/app/assets/javascripts/thyme/metadata_manager.js +++ b/app/assets/javascripts/thyme/metadata_manager.js @@ -104,7 +104,7 @@ class MetadataManager { target: '_blank' }); $videoRef.append($videoIcon); - if (meta.video === null) { + if (!meta.video) { $videoRef.hide(); } const $manIcon = $('', { @@ -116,7 +116,7 @@ class MetadataManager { target: '_blank' }); $manRef.append($manIcon); - if (meta.manuscript === null) { + if (!meta.manuscript) { $manRef.hide(); } const $scriptIcon = $('', { @@ -128,7 +128,7 @@ class MetadataManager { target: '_blank' }); $scriptRef.append($scriptIcon); - if (meta.script === null) { + if (!meta.script) { $scriptRef.hide(); } const $quizIcon = $('', { @@ -140,7 +140,7 @@ class MetadataManager { target: '_blank' }); $quizRef.append($quizIcon); - if (meta.quiz === null) { + if (!meta.quiz) { $quizRef.hide(); } const $extIcon = $('', { @@ -152,7 +152,7 @@ class MetadataManager { target: '_blank' }); $extRef.append($extIcon); - if (meta.link === null) { + if (!meta.link) { $extRef.hide(); } const $description = $('
', { diff --git a/app/assets/javascripts/thyme/thyme_editor.js b/app/assets/javascripts/thyme/thyme_editor.js index 41721d16e..f540bbb86 100644 --- a/app/assets/javascripts/thyme/thyme_editor.js +++ b/app/assets/javascripts/thyme/thyme_editor.js @@ -4,7 +4,7 @@ $(document).on('turbolinks:load', function() { */ // exit script if the current page has no thyme player const thymeEdit = document.getElementById('thyme-edit'); - if (thymeEdit === null) { + if (!thymeEdit) { return; } // initialize attributes diff --git a/app/assets/javascripts/thyme/thyme_feedback.js b/app/assets/javascripts/thyme/thyme_feedback.js index ebd7d5b1d..3331b3019 100644 --- a/app/assets/javascripts/thyme/thyme_feedback.js +++ b/app/assets/javascripts/thyme/thyme_feedback.js @@ -5,7 +5,7 @@ $(document).on('turbolinks:load', function() { */ // exit script if the current page has no thyme player const thymeContainer = document.getElementById('thyme-feedback'); - if (thymeContainer === null) { + if (!thymeContainer) { return; } @@ -63,7 +63,7 @@ $(document).on('turbolinks:load', function() { } function isValid(annotation) { for (let toggle of annotationCategoryToggles) { - if (annotation.category === toggle.category && toggle.getValue() === true) { + if (annotation.category === toggle.category && toggle.getValue()) { return true; } } diff --git a/app/assets/javascripts/thyme/thyme_player.js b/app/assets/javascripts/thyme/thyme_player.js index 882738d3e..5b5191836 100644 --- a/app/assets/javascripts/thyme/thyme_player.js +++ b/app/assets/javascripts/thyme/thyme_player.js @@ -4,7 +4,7 @@ $(document).on('turbolinks:load', function() { */ // exit script if the current page has no thyme player const thymeContainer = document.getElementById('thyme-container'); - if (thymeContainer === null) { + if (!thymeContainer) { return; } @@ -152,7 +152,7 @@ $(document).on('turbolinks:load', function() { function resizeContainer() { const factor = $('#caption').is(':hidden') && $('#annotation-caption').is(':hidden') ? 1 : 1 / 0.82; resize.resizeContainer(thymeContainer, factor); - if (thymeAttributes.annotations === null) { + if (!thymeAttributes.annotations) { annotationManager.updateAnnotations(); } else { annotationManager.updateMarkers(); diff --git a/app/assets/javascripts/thyme/utility.js b/app/assets/javascripts/thyme/utility.js index 46dfec71c..2ec0f45f0 100644 --- a/app/assets/javascripts/thyme/utility.js +++ b/app/assets/javascripts/thyme/utility.js @@ -61,7 +61,7 @@ const thymeUtility = { playOnClick() { const video = thymeAttributes.video; video.addEventListener('click', function() { - if (video.paused === true) { + if (video.paused) { video.play(); } else { video.pause(); From b23f5e1444fbbe164498ec592683cb78bc6f99ba Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Fri, 27 Oct 2023 18:22:44 +0200 Subject: [PATCH 168/291] Code clean-up --- app/abilities/annotation_ability.rb | 2 +- app/assets/javascripts/thyme/metadata_manager.js | 4 ++-- app/controllers/annotations_controller.rb | 11 +++++------ app/controllers/lectures_controller.rb | 6 +++--- app/views/annotations/_form.html.erb | 8 +++++--- app/views/annotations/edit.js.erb | 2 +- config/routes.rb | 6 +++--- 7 files changed, 20 insertions(+), 19 deletions(-) diff --git a/app/abilities/annotation_ability.rb b/app/abilities/annotation_ability.rb index 416245fe7..afa54597c 100644 --- a/app/abilities/annotation_ability.rb +++ b/app/abilities/annotation_ability.rb @@ -6,7 +6,7 @@ def initialize(user) annotation.user == user end - can [:new, :create, :update_annotations, :num_nearby_mistake_annotations], Annotation + can [:new, :create, :update_annotations, :num_nearby_posted_mistake_annotations], Annotation end end diff --git a/app/assets/javascripts/thyme/metadata_manager.js b/app/assets/javascripts/thyme/metadata_manager.js index d5c3b2677..e78875fcf 100644 --- a/app/assets/javascripts/thyme/metadata_manager.js +++ b/app/assets/javascripts/thyme/metadata_manager.js @@ -203,8 +203,8 @@ class MetadataManager { $('#' + metadataListId + ' li').removeClass('current'); while (j < this.activeCues.length) { const activeStart = this.activeCues[j].startTime; - let metalink; - if (metalink = document.getElementById('m-' + activeStart)) { + let metalink = document.getElementById('m-' + activeStart); + if (metalink) { $(metalink).show(); $(metalink).addClass('current'); } diff --git a/app/controllers/annotations_controller.rb b/app/controllers/annotations_controller.rb index 2785e2981..662eb1128 100644 --- a/app/controllers/annotations_controller.rb +++ b/app/controllers/annotations_controller.rb @@ -105,18 +105,17 @@ def update_annotations render json: annotations end - def num_nearby_mistake_annotations + def num_nearby_posted_mistake_annotations annotations = Annotation.where(medium: params[:mediumId]) # the time (!) radius in which annotation are considered as "nearby" radius = params[:radius].to_i timestamp = params[:timestamp].to_i counter = 0 for annotation in annotations - if annotation.category == "mistake" && - (annotation.timestamp.total_seconds - timestamp).abs() < radius && - annotation.public_comment_id != nil - counter += 1 - end + next unless annotation.category == "mistake" && + (annotation.timestamp.total_seconds - timestamp).abs() < radius && + !annotation.public_comment_id.nil? + counter += 1 end render json: counter end diff --git a/app/controllers/lectures_controller.rb b/app/controllers/lectures_controller.rb index 446fb9f33..02fbd76a7 100644 --- a/app/controllers/lectures_controller.rb +++ b/app/controllers/lectures_controller.rb @@ -25,11 +25,11 @@ def edit end # emergency link -> prefill form - status = @lecture.emergency_link_status + status = @lecture.emergency_link_status_for_database link = @lecture.emergency_link - if status == "lecture_link" + if status == Lecture.emergency_link_statuses[:lecture_link] @linked_lecture = Lecture.find_by_id(link.tr("^[0-9]", "")) - elsif status == "direct_link" + elsif status == Lecture.emergency_link_statuses[:direct_link] @direct_link = link end end diff --git a/app/views/annotations/_form.html.erb b/app/views/annotations/_form.html.erb index db29f991c..4e2a8b030 100644 --- a/app/views/annotations/_form.html.erb +++ b/app/views/annotations/_form.html.erb @@ -137,12 +137,14 @@ id="delete-button" class="btn btn-secondary" data-bs-dismiss="modal"> - <%= t('buttons.delete') %> + <%= t('buttons.delete') %> + <% end %>
<% end %> - \ No newline at end of file + diff --git a/app/views/annotations/edit.js.erb b/app/views/annotations/edit.js.erb index b99bedac4..99409a399 100644 --- a/app/views/annotations/edit.js.erb +++ b/app/views/annotations/edit.js.erb @@ -25,7 +25,7 @@ function alertMistakeWarningMessage(message) { message += "\n" + document.getElementById('annotation-locales').dataset.warningMistake; var mediumId = thyme.dataset.medium; var radius = 60; // annotations that are inside this radius (in seconds) are considered as "near". - $.ajax(Routes.num_nearby_mistake_annotations_path(), { + $.ajax(Routes.num_nearby_posted_mistake_annotations_path(), { type: 'GET', dataType: 'json', data: { diff --git a/config/routes.rb b/config/routes.rb index e7957d085..bea8ce156 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -45,9 +45,9 @@ to: 'annotations#update_annotations', as: 'update_annotations' - get 'annotations/num_nearby_mistake_annotations', - to: 'annotations#num_nearby_mistake_annotations', - as: 'num_nearby_mistake_annotations' + get 'annotations/num_nearby_posted_mistake_annotations', + to: 'annotations#num_nearby_posted_mistake_annotations', + as: 'num_nearby_posted_mistake_annotations' resources :annotations, only: [:new, :create, :edit, :update, :destroy] From 03c7d31df793f7567893fe385f42caa14d62b2d0 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Fri, 27 Oct 2023 20:52:02 +0200 Subject: [PATCH 169/291] Replace name "emergency button" by "annotation button" --- app/assets/javascripts/application.js | 2 +- ...ergency_button.js => annotation_button.js} | 4 ++-- .../thyme/components/annotations_toggle.js | 2 +- app/assets/javascripts/thyme/thyme_player.js | 2 +- app/assets/stylesheets/annotations.scss | 4 ++-- app/controllers/lectures_controller.rb | 2 +- app/models/lecture.rb | 2 +- .../annotations/_annotation_area.html.erb | 2 +- .../_form_content_further_help.html.erb | 6 +++--- app/views/annotations/edit.js.erb | 20 +++++++++---------- app/views/lectures/edit/_comments.html.erb | 6 +++--- app/views/lectures/edit/_preferences.html.erb | 2 +- app/views/media/_basics.html.erb | 10 +++++----- app/views/media/play.html.erb | 6 +++--- config/locales/de.yml | 6 +++--- config/locales/en.yml | 6 +++--- 16 files changed, 41 insertions(+), 41 deletions(-) rename app/assets/javascripts/thyme/components/{emergency_button.js => annotation_button.js} (88%) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index d9ff40064..bb88a5a4d 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -67,7 +67,7 @@ //= require thyme/components/add_screenshot_button //= require thyme/components/annotation_category_toggle //= require thyme/components/annotations_toggle -//= require thyme/components/emergency_button +//= require thyme/components/annotation_button //= require thyme/components/full_screen_button //= require thyme/components/ia_button //= require thyme/components/ia_back_button diff --git a/app/assets/javascripts/thyme/components/emergency_button.js b/app/assets/javascripts/thyme/components/annotation_button.js similarity index 88% rename from app/assets/javascripts/thyme/components/emergency_button.js rename to app/assets/javascripts/thyme/components/annotation_button.js index 1d1e4987c..19c7c6d4d 100644 --- a/app/assets/javascripts/thyme/components/emergency_button.js +++ b/app/assets/javascripts/thyme/components/annotation_button.js @@ -1,10 +1,10 @@ -class EmergencyButton extends Component { +class AnnotationButton extends Component { add() { const video = thymeAttributes.video; const element = this.element; - // Event handler for the emergency button + // Event handler for the annotation button element.addEventListener('click', function() { video.pause(); $.ajax(Routes.new_annotation_path(), { diff --git a/app/assets/javascripts/thyme/components/annotations_toggle.js b/app/assets/javascripts/thyme/components/annotations_toggle.js index 142497fe6..684e990b1 100644 --- a/app/assets/javascripts/thyme/components/annotations_toggle.js +++ b/app/assets/javascripts/thyme/components/annotations_toggle.js @@ -44,7 +44,7 @@ class AnnotationsToggle extends Component { show() { $('#volume-controls').css('left', '66%'); $('#speed-control').css('left', '77%'); - $('#emergency-button').css('left', '86%'); + $('#annotation-button').css('left', '86%'); thymeAttributes.hideControlBarThreshold.x = 960; this.div.show(); } diff --git a/app/assets/javascripts/thyme/thyme_player.js b/app/assets/javascripts/thyme/thyme_player.js index 5b5191836..21b7e9e50 100644 --- a/app/assets/javascripts/thyme/thyme_player.js +++ b/app/assets/javascripts/thyme/thyme_player.js @@ -26,7 +26,7 @@ $(document).on('turbolinks:load', function() { */ const annotationsToggle = new AnnotationsToggle('annotations-toggle'); annotationsToggle.add(); - (new EmergencyButton('emergency-button')).add(); + (new AnnotationButton('annotation-button')).add(); (new FullScreenButton('full-screen', thymeContainer)).add(); (new MuteButton('mute')).add(); (new NextChapterButton('next-chapter')).add(); diff --git a/app/assets/stylesheets/annotations.scss b/app/assets/stylesheets/annotations.scss index 3998e0b2c..5abd00213 100644 --- a/app/assets/stylesheets/annotations.scss +++ b/app/assets/stylesheets/annotations.scss @@ -1,5 +1,5 @@ /* buttons */ -#emergency-button { +#annotation-button { position: absolute; display: flex; left: 89%; @@ -83,7 +83,7 @@ height: 85%; } -#annotation-buttons { +#annotation-area-buttons { display: flex; justify-content: space-between; margin-left: 10%; diff --git a/app/controllers/lectures_controller.rb b/app/controllers/lectures_controller.rb index 02fbd76a7..946d171a3 100644 --- a/app/controllers/lectures_controller.rb +++ b/app/controllers/lectures_controller.rb @@ -248,7 +248,7 @@ def search_examples def close_comments @lecture.close_comments!(current_user) - # disable emergency button + # disable annotation button @lecture.update(annotations_status: -1) @lecture.media.update(annotations_status: 0) @lecture.lessons.each do |lesson| diff --git a/app/models/lecture.rb b/app/models/lecture.rb index d43b7bd89..9552b1461 100644 --- a/app/models/lecture.rb +++ b/app/models/lecture.rb @@ -59,7 +59,7 @@ class Lecture < ApplicationRecord # in the erdbeere database serialize :structure_ids, Array - # if the emergency button is enabled, one can add different types of links + # if the annotation button is enabled, one can add different types of links # that e.g. bring students to the helpdesk enum emergency_link_status: { no_link: 0, lecture_link: 1, direct_link: 2 } diff --git a/app/views/annotations/_annotation_area.html.erb b/app/views/annotations/_annotation_area.html.erb index 4c53be4f0..1c345adb4 100644 --- a/app/views/annotations/_annotation_area.html.erb +++ b/app/views/annotations/_annotation_area.html.erb @@ -6,7 +6,7 @@ -
+
<% lecture = Medium.find_by_id(@medium_id).lecture %> - <% status = lecture.emergency_link_status %> + <% status = lecture.emergency_link_status_for_database %> <% link = lecture.emergency_link %> - <% if status == "lecture_link" %> + <% if status == Lecture.emergency_link_statuses[:lecture_link] %> <% link_name = Lecture.find_by_id(link.tr("^[0-9]", "")).title %> - <% elsif status == "direct_link" %> + <% elsif status == Lecture.emergency_link_statuses[:direct_link] %> <% link_name = link %> <% end %> <% unless link_name.blank? %> diff --git a/app/views/annotations/edit.js.erb b/app/views/annotations/edit.js.erb index 3d1d925b4..8d5fb1803 100644 --- a/app/views/annotations/edit.js.erb +++ b/app/views/annotations/edit.js.erb @@ -63,16 +63,16 @@ var categoryRadios = document.getElementById('category-radios'); categoryRadios.addEventListener('click', function(evt) { if (evt.target && event.target.matches("input[type='radio']")) { switch(evt.target.value) { - case "note": + case Category.NOTE.name: note(); break; - case "content": + case Category.CONTENT.name: content(); break; - case "mistake": + case Category.MISTAKE.name: mistake(); break; - case "presentation": + case Category.PRESENTATION.name: presentation(); break; } @@ -97,13 +97,13 @@ function content() { if (evt.target && event.target.matches("input[type='radio']")) { submitButton.disabled = false; switch (evt.target.value) { - case "definition": + case Subcategory.DEFINITION.name: definition(); break; - case "argument": + case Subcategory.ARGUMENT.name: argument(); break; - case "strategy": + case Subcategory.STRATEGY.name: strategy(); break; } @@ -208,13 +208,13 @@ if (contentRadio.checked) { submitButton.disabled = false; var subcategory = document.getElementById('annotation_subcategory').textContent.replace(/[^a-z]/g, ''); switch (subcategory) { - case "definition": + case Subcategory.DEFINITION.name: document.getElementById('content-category-definition').checked = true; break; - case "argument": + case Subcategory.ARGUMENT.name: document.getElementById('content-category-argument').checked = true; break; - case "strategy": + case Subcategory.STRATEGY.name: document.getElementById('content-category-strategy').checked = true; break; } diff --git a/app/views/lectures/edit/_comments.html.erb b/app/views/lectures/edit/_comments.html.erb index 713409721..cdc6345c5 100644 --- a/app/views/lectures/edit/_comments.html.erb +++ b/app/views/lectures/edit/_comments.html.erb @@ -57,10 +57,10 @@
- +
- <%= t('admin.lecture.enable_emergency_button') %> - <%= helpdesk(t('admin.lecture.enable_emergency_button_helpdesk'), + <%= t('admin.lecture.enable_annotation_button') %> + <%= helpdesk(t('admin.lecture.enable_annotation_button_helpdesk'), false) %>
diff --git a/app/views/lectures/edit/_preferences.html.erb b/app/views/lectures/edit/_preferences.html.erb index 0d8d516f1..4ace30803 100644 --- a/app/views/lectures/edit/_preferences.html.erb +++ b/app/views/lectures/edit/_preferences.html.erb @@ -158,7 +158,7 @@
<% end %> - + <% if lecture.annotations_status == 1 %>
<%= t('admin.lecture.emergency_link') %> diff --git a/app/views/media/_basics.html.erb b/app/views/media/_basics.html.erb index 360d11f5a..2e6cdd877 100644 --- a/app/views/media/_basics.html.erb +++ b/app/views/media/_basics.html.erb @@ -173,12 +173,12 @@ class: 'form-control' %>
<% end %> - + <% if medium.video.present? %>
- <%= t('admin.lecture.enable_emergency_button') %> - <%= helpdesk(t('admin.lecture.enable_emergency_button_helpdesk') + - t('admin.lecture.enable_emergency_button_inherit_helpdesk'), + <%= t('admin.lecture.enable_annotation_button') %> + <%= helpdesk(t('admin.lecture.enable_annotation_button_helpdesk') + + t('admin.lecture.enable_annotation_button_inherit_helpdesk'), false) %>
@@ -211,7 +211,7 @@
<% end %> - + <%= f.hidden_field :teachable_id, value: medium.teachable_id %> <%= f.hidden_field :teachable_type, value: medium.teachable_type %>
diff --git a/app/views/media/play.html.erb b/app/views/media/play.html.erb index 01335c005..baa3d8432 100644 --- a/app/views/media/play.html.erb +++ b/app/views/media/play.html.erb @@ -61,9 +61,9 @@
- -
- + +
+ bookmark_add
diff --git a/config/locales/de.yml b/config/locales/de.yml index 1ded94019..69f38d347 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -443,14 +443,14 @@ de: no_imported_media: 'Es sind keine importierten Medien vorhanden.' comments_disabled: > Kommentare für neu veröffentlichte Medien sind standardmäßig deaktiviert - enable_emergency_button: 'Dürfen Studierende Annotationen für Sie sichtbar machen?' - enable_emergency_button_helpdesk: > + enable_annotation_button: 'Dürfen Studierende Annotationen für Sie sichtbar machen?' + enable_annotation_button_helpdesk: > Wenn Sie "ja" anwählen, dann haben Studierende die Möglichkeit, ihre Annotationen für den/die Dozent*in bzw. alle Editor*innen der Vorlesung sichtbar machen. Sie können diese dann entweder im normalen Thyme-Player oder im Thyme-Feedback-Player einsehen und dadurch Rückmeldung zu Ihren Vorlesungsvideos erhalten. - enable_emergency_button_inherit_helpdesk: > + enable_annotation_button_inherit_helpdesk: > Ist die Option "von Vorlesung erben" ausgewählt, dann wird die globale Option aus den Vorlesungseinstellungen (Kommentare) übernommen. new_notifications: 'neue Benachrichtigungen' diff --git a/config/locales/en.yml b/config/locales/en.yml index 3306f3c5c..14caee1c6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -443,14 +443,14 @@ en: close_comments: 'Close all threads for related media' comments_disabled: > Comments for newly published media are disabled by default - enable_emergency_button: 'Are students allowed to make annotations visible for you?' - enable_emergency_button_helpdesk: > + enable_annotation_button: 'Are students allowed to make annotations visible for you?' + enable_annotation_button_helpdesk: > If you select "yes", students have the possibility to make their annotations visible for the teacher resp. all editors of the lecture. You can then see them either in the normal thyme player or in the thyme feedback player to get feedback for your lecture videos. - enable_emergency_button_inherit_helpdesk: > + enable_annotation_button_inherit_helpdesk: > If the option "inherit from lecture" is selected, the global option of the lecture preferences (comments) is taken. new_notifications: 'new notifications' From 480c3b559012a7bdf09a1ed5d2a4f75989d1237e Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sat, 28 Oct 2023 12:31:41 +0200 Subject: [PATCH 170/291] Add color check --- app/controllers/annotations_controller.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/controllers/annotations_controller.rb b/app/controllers/annotations_controller.rb index 662eb1128..2ec06676b 100644 --- a/app/controllers/annotations_controller.rb +++ b/app/controllers/annotations_controller.rb @@ -33,6 +33,8 @@ def edit def create @annotation = Annotation.new(annotation_params) + return unless is_valid_color(@annotation.color) + @annotation.user_id = current_user.id @total_seconds = annotation_auxiliary_params[:total_seconds] @annotation.timestamp = TimeStamp.new(total_seconds: @total_seconds) @@ -44,7 +46,8 @@ def create def update @annotation = Annotation.find(params[:id]) - @annotation.update(annotation_params) + @annotation.assign_attributes(annotation_params) + return unless is_valid_color(@annotation.color) @annotation.public_comment_id = post_comment(@annotation) @annotation.save @@ -139,6 +142,10 @@ def annotation_auxiliary_params :total_seconds, :post_as_comment, :comment ) end + + def is_valid_color(color) + return color&.match?(/\A#([0-9]|[A-F]){6}\z/) + end def post_comment(annotation) public_comment_id = annotation.public_comment_id From 8cb24a735f9d8d516b965a688271f51ab63e01ea Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sat, 28 Oct 2023 13:26:29 +0200 Subject: [PATCH 171/291] Fix comment is nil bug --- app/controllers/annotations_controller.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/controllers/annotations_controller.rb b/app/controllers/annotations_controller.rb index 2ec06676b..2d6d7f889 100644 --- a/app/controllers/annotations_controller.rb +++ b/app/controllers/annotations_controller.rb @@ -33,12 +33,13 @@ def edit def create @annotation = Annotation.new(annotation_params) + return unless is_valid_color(@annotation.color) + @annotation.public_comment_id = post_comment(@annotation) @annotation.user_id = current_user.id @total_seconds = annotation_auxiliary_params[:total_seconds] @annotation.timestamp = TimeStamp.new(total_seconds: @total_seconds) - @annotation.public_comment_id = post_comment(@annotation) @annotation.save render :update @@ -47,9 +48,10 @@ def create def update @annotation = Annotation.find(params[:id]) @annotation.assign_attributes(annotation_params) + return unless is_valid_color(@annotation.color) - @annotation.public_comment_id = post_comment(@annotation) + @annotation.save end @@ -133,13 +135,13 @@ def current_ability def annotation_params params.require(:annotation).permit( - :category, :color, :medium_id, :subcategory, :visible_for_teacher + :category, :color, :comment, :medium_id, :subcategory, :visible_for_teacher ) end def annotation_auxiliary_params params.require(:annotation).permit( - :total_seconds, :post_as_comment, :comment + :total_seconds, :post_as_comment ) end @@ -149,12 +151,11 @@ def is_valid_color(color) def post_comment(annotation) public_comment_id = annotation.public_comment_id - aux_params = annotation_auxiliary_params # return if checkbox "post_as_comment" is not checked and if there is no comment to update - return if aux_params[:post_as_comment] != "1" && public_comment_id.nil? + return if annotation_auxiliary_params[:post_as_comment] != "1" && public_comment_id.nil? - comment = aux_params[:comment] + comment = annotation_params[:comment] if public_comment_id.nil? # comment doesn't exist yet -> create one medium = annotation.medium From 0f6e562d37ad1b9999ee022441164024c6ff369d Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sat, 28 Oct 2023 13:46:58 +0200 Subject: [PATCH 172/291] Code clean-up --- app/controllers/annotations_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/annotations_controller.rb b/app/controllers/annotations_controller.rb index 2d6d7f889..b13a78241 100644 --- a/app/controllers/annotations_controller.rb +++ b/app/controllers/annotations_controller.rb @@ -48,7 +48,7 @@ def create def update @annotation = Annotation.find(params[:id]) @annotation.assign_attributes(annotation_params) - + return unless is_valid_color(@annotation.color) @annotation.public_comment_id = post_comment(@annotation) @@ -77,7 +77,7 @@ def destroy end def update_annotations - medium = Medium.find_by_id(params[:mediumId]) + medium = Medium.find_by(id: params[:mediumId]) # Get the right annotations if medium.annotations_visible?(current_user) From e39fcb514eb400f58a2854d9951af066c1eb23bf Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sat, 28 Oct 2023 13:47:39 +0200 Subject: [PATCH 173/291] Add non-null constraint for category --- ...28113531_add_category_not_null_contraint_to_annotation.rb | 5 +++++ db/schema.rb | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20231028113531_add_category_not_null_contraint_to_annotation.rb diff --git a/db/migrate/20231028113531_add_category_not_null_contraint_to_annotation.rb b/db/migrate/20231028113531_add_category_not_null_contraint_to_annotation.rb new file mode 100644 index 000000000..f815562cb --- /dev/null +++ b/db/migrate/20231028113531_add_category_not_null_contraint_to_annotation.rb @@ -0,0 +1,5 @@ +class AddCategoryNotNullContraintToAnnotation < ActiveRecord::Migration[7.0] + def change + change_column_null :annotations, :category, false + end +end diff --git a/db/schema.rb b/db/schema.rb index a110a592f..ffedf812f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_10_21_100348) do +ActiveRecord::Schema[7.0].define(version: 2023_10_28_113531) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -20,7 +20,7 @@ t.bigint "user_id", null: false t.text "timestamp", null: false t.text "comment" - t.integer "category" + t.integer "category", null: false t.boolean "visible_for_teacher" t.string "color" t.datetime "created_at", null: false From 3123db5da2fb43475700a9862f57fae9e7f4e147 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sat, 28 Oct 2023 15:36:50 +0200 Subject: [PATCH 174/291] CSS improvement and bugfix --- .../thyme/display_manager (Kopie).js | 130 ------------------ .../javascripts/thyme/display_manager.js | 22 +-- app/assets/javascripts/thyme/resize.js | 6 +- app/assets/stylesheets/thyme_feedback.scss | 17 ++- app/views/media/feedback.html.erb | 67 +++++---- app/views/media/play.html.erb | 2 +- 6 files changed, 67 insertions(+), 177 deletions(-) delete mode 100644 app/assets/javascripts/thyme/display_manager (Kopie).js diff --git a/app/assets/javascripts/thyme/display_manager (Kopie).js b/app/assets/javascripts/thyme/display_manager (Kopie).js deleted file mode 100644 index 7bb011e55..000000000 --- a/app/assets/javascripts/thyme/display_manager (Kopie).js +++ /dev/null @@ -1,130 +0,0 @@ -/** - * A DisplayManager helps to switch between the full thyme player and - * the native HTML player shown on small devices. - */ -class DisplayManager { - - constructor(elements, onEnlarge) { - /* - elements = An array containing JQuery references on the HTML elements - that should be hidden, when the display is too small. - - onEnlarge = A reference on a function that is called when the display - changes from small to large. Use this for player specific behaviour. - */ - this.elements = elements; - this.onEnlarge = onEnlarge; - } - - - - // on small display, fall back to standard browser player - adaptToSmallDisplay() { - for (let e of this.elements) { - e.hide(); - } - thymeAttributes.video.style.width = '100%'; - thymeAttributes.video.controls = true; - } - - // on large display, use anything thyme has to offer, disable native player - adaptToLargeDisplay() { - thymeAttributes.video.controls = false; - for (let e of this.elements) { - e.show(); - } - this.onEnlarge(); - } - - // Check screen size and trigger the right method - updateControlBarType() { - const dm = this; - - if (window.matchMedia("screen and (max-width: " + - thymeAttributes.hideControlBarThreshold.x + "px)").matches || - window.matchMedia("screen and (max-height: " + - thymeAttributes.hideControlBarThreshold.y + "px)").matches) { - dm.adaptToSmallDisplay(); - } - - if (window.matchMedia("screen and (max-device-width: " + - thymeAttributes.hideControlBarThreshold.x + "px)").matches || - window.matchMedia("screen and (max-device-height: " + - thymeAttributes.hideControlBarThreshold.y + "px)").matches) { - dm.adaptToSmallDisplay(); - } - - // mediaQuery listener for very small screens - const matchVerysmallX = window.matchMedia("screen and (max-width: " + - thymeAttributes.hideControlBarThreshold.x + "px)"); - matchVerysmallX.addListener(function(result) { - if (result.matches) { - dm.adaptToSmallDisplay(); - } - }); - const matchVerysmallY = window.matchMedia("screen and (max-height: " + - thymeAttributes.hideControlBarThreshold.y + "px)"); - matchVerysmallY.addListener(function(result) { - if (result.matches) { - dm.adaptToSmallDisplay(); - } - }); - - const matchVerysmalldeviceX = window.matchMedia("screen and (max-device-width: " + - thymeAttributes.hideControlBarThreshold.x + "px)"); - matchVerysmalldeviceX.addListener(function(result) { - if (result.matches) { - dm.adaptToSmallDisplay(); - } - }); - const matchVerysmalldeviceY = window.matchMedia("screen and (max-device-height: " + - thymeAttributes.hideControlBarThreshold.y + "px)"); - matchVerysmalldeviceY.addListener(function(result) { - if (result.matches) { - dm.adaptToSmallDisplay(); - } - }); - - // mediaQuery listener for normal screens - let matchNormalX = window.matchMedia("screen and (min-width: " + - (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); - matchNormalX.addListener(function(result) { - let matchNormalY; - matchNormalY = window.matchMedia("screen and (min-height: " + - (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); - if (result.matches && matchNormalY.matches) { - dm.adaptToLargeDisplay(); - } - }); - const matchNormalY = window.matchMedia("screen and (min-height: " + - (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); - matchNormalY.addListener(function(result) { - matchNormalX = window.matchMedia("screen and (min-width: " + - (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); - if (result.matches && matchNormalX.matches) { - dm.adaptToLargeDisplay(); - } - }); - - let matchNormaldeviceX = window.matchMedia("screen and (min-device-width: " + - (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); - let matchNormaldeviceY; - matchNormaldeviceX.addListener(function(result) { - matchNormaldeviceY = window.matchMedia("screen and (min-device-height: " + - (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); - if (result.matches && matchNormalY.matches) { - dm.adaptToLargeDisplay(); - } - }); - matchNormaldeviceY = window.matchMedia("screen and (min-device-height: " + - (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); - matchNormaldeviceY.addListener(function(result) { - matchNormaldeviceX = window.matchMedia("screen and (min-device-width: " + - (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); - if (result.matches && matchNormalX.matches) { - dm.adaptToLargeDisplay(); - } - }); - } - -}; diff --git a/app/assets/javascripts/thyme/display_manager.js b/app/assets/javascripts/thyme/display_manager.js index 65beb2037..7fac4e32f 100644 --- a/app/assets/javascripts/thyme/display_manager.js +++ b/app/assets/javascripts/thyme/display_manager.js @@ -38,18 +38,20 @@ class DisplayManager { // Check screen size and trigger the right method updateControlBarType() { + const dm = this; + if (window.matchMedia("screen and (max-width: " + thymeAttributes.hideControlBarThreshold.x + "px)").matches || window.matchMedia("screen and (max-height: " + thymeAttributes.hideControlBarThreshold.y + "px)").matches) { - this.adaptToSmallDisplay(); + dm.adaptToSmallDisplay(); } if (window.matchMedia("screen and (max-device-width: " + thymeAttributes.hideControlBarThreshold.x + "px)").matches || window.matchMedia("screen and (max-device-height: " + thymeAttributes.hideControlBarThreshold.y + "px)").matches) { - this.adaptToSmallDisplay(); + dm.adaptToSmallDisplay(); } // mediaQuery listener for very small screens @@ -57,14 +59,14 @@ class DisplayManager { thymeAttributes.hideControlBarThreshold.x + "px)"); matchVerysmallX.addListener(function (result) { if (result.matches) { - this.adaptToSmallDisplay(); + dm.adaptToSmallDisplay(); } }); const matchVerysmallY = window.matchMedia("screen and (max-height: " + thymeAttributes.hideControlBarThreshold.y + "px)"); matchVerysmallY.addListener(function (result) { if (result.matches) { - this.adaptToSmallDisplay(); + dm.adaptToSmallDisplay(); } }); @@ -72,14 +74,14 @@ class DisplayManager { thymeAttributes.hideControlBarThreshold.x + "px)"); matchVerysmalldeviceX.addListener(function (result) { if (result.matches) { - this.adaptToSmallDisplay(); + dm.adaptToSmallDisplay(); } }); const matchVerysmalldeviceY = window.matchMedia("screen and (max-device-height: " + thymeAttributes.hideControlBarThreshold.y + "px)"); matchVerysmalldeviceY.addListener(function (result) { if (result.matches) { - this.adaptToSmallDisplay(); + dm.adaptToSmallDisplay(); } }); @@ -91,7 +93,7 @@ class DisplayManager { matchNormalY = window.matchMedia("screen and (min-height: " + (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); if (result.matches && matchNormalY.matches) { - this.adaptToLargeDisplay(); + dm.adaptToLargeDisplay(); } }); const matchNormalY = window.matchMedia("screen and (min-height: " + @@ -100,7 +102,7 @@ class DisplayManager { matchNormalX = window.matchMedia("screen and (min-width: " + (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); if (result.matches && matchNormalX.matches) { - this.adaptToLargeDisplay(); + dm.adaptToLargeDisplay(); } }); @@ -111,7 +113,7 @@ class DisplayManager { matchNormaldeviceY = window.matchMedia("screen and (min-device-height: " + (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); if (result.matches && matchNormalY.matches) { - this.adaptToLargeDisplay(); + dm.adaptToLargeDisplay(); } }); matchNormaldeviceY = window.matchMedia("screen and (min-device-height: " + @@ -120,7 +122,7 @@ class DisplayManager { matchNormaldeviceX = window.matchMedia("screen and (min-device-width: " + (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); if (result.matches && matchNormalX.matches) { - this.adaptToLargeDisplay(); + dm.adaptToLargeDisplay(); } }); } diff --git a/app/assets/javascripts/thyme/resize.js b/app/assets/javascripts/thyme/resize.js index 2328f9703..d93e17db6 100644 --- a/app/assets/javascripts/thyme/resize.js +++ b/app/assets/javascripts/thyme/resize.js @@ -1,5 +1,5 @@ /** - Use the methods here to resize thyme players. + Use the method here to resize thyme players. */ const resize = { resizeContainer: function(container, factor) { @@ -7,7 +7,9 @@ const resize = { const $container = $(container); let height = $(window).height(); - let width = Math.floor((video.videoWidth * $(window).height() / video.videoHeight) * factor); + const vWidth = video.videoWidth; + const vHeight = video.videoHeight; + let width = Math.floor((vWidth * $(window).height() / vHeight) * factor); if (width > $(window).width()) { const shrink = $(window).width() / width; height = Math.floor(height * shrink); diff --git a/app/assets/stylesheets/thyme_feedback.scss b/app/assets/stylesheets/thyme_feedback.scss index 91febb2d9..4d2a4a64d 100644 --- a/app/assets/stylesheets/thyme_feedback.scss +++ b/app/assets/stylesheets/thyme_feedback.scss @@ -42,6 +42,7 @@ } #below-area { + display: flex; bottom: 0; padding: 10px; background: #eeefff; @@ -51,24 +52,32 @@ } #toggle-mistake-annotations { + position: relative; + left: 5%; input:checked + .slider { background-color: #C23939; } } -#toggle-content-annotations { +#toggle-presentation-annotations { + position: relative; + left: 11%; input:checked + .slider { - background-color: #D5D841; + background-color: #D9892E; } } -#toggle-presentation-annotations { +#toggle-content-annotations { + position: relative; + left: 17%; input:checked + .slider { - background-color: #D9892E; + background-color: #D5D841; } } #toggle-note-annotations { + position: relative; + left: 23%; input:checked + .slider { background-color: #66B748; } diff --git a/app/views/media/feedback.html.erb b/app/views/media/feedback.html.erb index f6553671a..dedd8ddbe 100644 --- a/app/views/media/feedback.html.erb +++ b/app/views/media/feedback.html.erb @@ -2,6 +2,8 @@
+ +
+ + <%= render partial: "annotations/annotation_area" %> - -
-
- - - - - - - - - - - - + + +
+
+ + + + + + + + + + + + +
-
+ +
diff --git a/app/views/media/play.html.erb b/app/views/media/play.html.erb index baa3d8432..9e53f981b 100644 --- a/app/views/media/play.html.erb +++ b/app/views/media/play.html.erb @@ -22,7 +22,7 @@ 0:00:00 - + From f74cdc7b25946ac7fa6ee35edcd276903a5e3759 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sat, 28 Oct 2023 15:37:03 +0200 Subject: [PATCH 175/291] Correct locales --- config/locales/en.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 14caee1c6..ebf84ef03 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -391,9 +391,9 @@ en: comment for this video. toggle: note: "toggle note annotations" - content: "toggle note content" - mistake: "toggle note mistake" - presentation: "toggle note presentation" + content: "toggle content annotations" + mistake: "toggle mistake annotations" + presentation: "toggle presentation annotations" announcement: help: > Here you can enter a text. You may use LaTeX (by putting the From 63c15eaf25629a3de6c860e290fd01be45e6567b Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 30 Oct 2023 17:07:29 +0100 Subject: [PATCH 176/291] Add null constraint to category in original migration --- db/migrate/20221015114100_create_annotations.rb | 2 +- ...28113531_add_category_not_null_contraint_to_annotation.rb | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 db/migrate/20231028113531_add_category_not_null_contraint_to_annotation.rb diff --git a/db/migrate/20221015114100_create_annotations.rb b/db/migrate/20221015114100_create_annotations.rb index 02bbd65bc..e22f4365d 100644 --- a/db/migrate/20221015114100_create_annotations.rb +++ b/db/migrate/20221015114100_create_annotations.rb @@ -5,7 +5,7 @@ def change t.references :user, null: false, foreign_key: true t.text :timestamp, null: false t.text :comment - t.integer :category + t.integer :category, null: false t.boolean :visible_for_teacher t.string :color t.integer :public_comment_id diff --git a/db/migrate/20231028113531_add_category_not_null_contraint_to_annotation.rb b/db/migrate/20231028113531_add_category_not_null_contraint_to_annotation.rb deleted file mode 100644 index f815562cb..000000000 --- a/db/migrate/20231028113531_add_category_not_null_contraint_to_annotation.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddCategoryNotNullContraintToAnnotation < ActiveRecord::Migration[7.0] - def change - change_column_null :annotations, :category, false - end -end From 9c81e0ad74923784b0c6214754ee589469149390 Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 30 Oct 2023 17:21:57 +0100 Subject: [PATCH 177/291] Rearrange save button --- app/views/annotations/_form.html.erb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/annotations/_form.html.erb b/app/views/annotations/_form.html.erb index 4f603b015..e25155275 100644 --- a/app/views/annotations/_form.html.erb +++ b/app/views/annotations/_form.html.erb @@ -133,6 +133,11 @@
<% end %> From b6c1327ef0f60e220e042fe6658a37b268ea6d07 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sat, 4 Nov 2023 14:20:45 +0100 Subject: [PATCH 178/291] Quick fix thyme feedback resize. --- .../thyme/components/full_screen_button.js | 2 +- app/assets/javascripts/thyme/resize.js | 8 ++++---- app/assets/javascripts/thyme/thyme_feedback.js | 6 +++--- app/assets/javascripts/thyme/thyme_player.js | 2 +- app/assets/stylesheets/thyme_feedback.scss | 11 ++++++++++- app/views/media/feedback.html.erb | 4 ++-- 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/thyme/components/full_screen_button.js b/app/assets/javascripts/thyme/components/full_screen_button.js index ff2894fed..30fa04e30 100644 --- a/app/assets/javascripts/thyme/components/full_screen_button.js +++ b/app/assets/javascripts/thyme/components/full_screen_button.js @@ -60,7 +60,7 @@ class FullScreenButton extends Component { window.onresize is triggered twice(!), the second time with incorrect window height data, which results in a video area not quite filling the whole window. The next line resizes the container again. */ - setTimeout(resize.resizeContainer($(this.container), 1), 20); + setTimeout(resize.resizeContainer($(this.container), 1, 0), 20); } } diff --git a/app/assets/javascripts/thyme/resize.js b/app/assets/javascripts/thyme/resize.js index d93e17db6..cc3b7e73c 100644 --- a/app/assets/javascripts/thyme/resize.js +++ b/app/assets/javascripts/thyme/resize.js @@ -2,20 +2,20 @@ Use the method here to resize thyme players. */ const resize = { - resizeContainer: function(container, factor) { + resizeContainer: function(container, factor, offset) { const video = document.getElementById('video'); const $container = $(container); let height = $(window).height(); const vWidth = video.videoWidth; const vHeight = video.videoHeight; - let width = Math.floor((vWidth * $(window).height() / vHeight) * factor); + let width = Math.floor((vWidth * $(window).height() / vHeight) * factor) - offset; if (width > $(window).width()) { const shrink = $(window).width() / width; height = Math.floor(height * shrink); - width = $(window).width(); + width = $(window).width(); } - const top = Math.floor(0.5 * ($(window).height() - height)); + const top = Math.floor(0.5 * ($(window).height() - height)); const left = Math.floor(0.5 * ($(window).width() - width)); $container.css('height', height + 'px'); $container.css('width', width + 'px'); diff --git a/app/assets/javascripts/thyme/thyme_feedback.js b/app/assets/javascripts/thyme/thyme_feedback.js index 3331b3019..b7eaae1e9 100644 --- a/app/assets/javascripts/thyme/thyme_feedback.js +++ b/app/assets/javascripts/thyme/thyme_feedback.js @@ -4,7 +4,7 @@ $(document).on('turbolinks:load', function() { VIDEO INITIALIZATION */ // exit script if the current page has no thyme player - const thymeContainer = document.getElementById('thyme-feedback'); + const thymeContainer = document.getElementById('thyme-feedback-container'); if (!thymeContainer) { return; } @@ -15,7 +15,7 @@ $(document).on('turbolinks:load', function() { // initialize attributes const video = document.getElementById('video'); thymeAttributes.video = video; - thymeAttributes.mediumId = document.getElementById('thyme').dataset.medium; + thymeAttributes.mediumId = document.getElementById('thyme-feedback').dataset.medium; thymeAttributes.markerBarId = 'feedback-markers'; @@ -105,7 +105,7 @@ $(document).on('turbolinks:load', function() { // resizes the thyme container to the window dimensions function resizeContainer() { - resize.resizeContainer(thymeContainer, 1); + resize.resizeContainer(thymeContainer, 1.22, 70); if (!thymeAttributes.annotations) { annotationManager.updateAnnotations(); } else { diff --git a/app/assets/javascripts/thyme/thyme_player.js b/app/assets/javascripts/thyme/thyme_player.js index 21b7e9e50..18e02ec27 100644 --- a/app/assets/javascripts/thyme/thyme_player.js +++ b/app/assets/javascripts/thyme/thyme_player.js @@ -151,7 +151,7 @@ $(document).on('turbolinks:load', function() { // whether the interactive area is displayed or hidden function resizeContainer() { const factor = $('#caption').is(':hidden') && $('#annotation-caption').is(':hidden') ? 1 : 1 / 0.82; - resize.resizeContainer(thymeContainer, factor); + resize.resizeContainer(thymeContainer, factor, 0); if (!thymeAttributes.annotations) { annotationManager.updateAnnotations(); } else { diff --git a/app/assets/stylesheets/thyme_feedback.scss b/app/assets/stylesheets/thyme_feedback.scss index 4d2a4a64d..47e0d0aa7 100644 --- a/app/assets/stylesheets/thyme_feedback.scss +++ b/app/assets/stylesheets/thyme_feedback.scss @@ -1,4 +1,4 @@ -#thyme-feedback { +#thyme-feedback-container { position: absolute; top: 0; width: 100%; @@ -6,6 +6,15 @@ font-family: 'Roboto'; } +#thyme-feedback { + position: absolute; + min-width: 100%; + min-height: 100%; + width: auto; + height: auto; + background: black; +} + #feedback-timeline { position: absolute; display: flex; diff --git a/app/views/media/feedback.html.erb b/app/views/media/feedback.html.erb index dedd8ddbe..11d492870 100644 --- a/app/views/media/feedback.html.erb +++ b/app/views/media/feedback.html.erb @@ -1,6 +1,6 @@ <% content_for :title, "THymE - \"#{@medium.caption}\"" %> -
-
+
+
From 854470fe1aedcf9784e189c9f60e5055b7f688d1 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sat, 4 Nov 2023 14:47:56 +0100 Subject: [PATCH 179/291] Add non-nil constraint --- .../20221015114100_create_annotations.rb | 8 +++---- ...tegory_not_null_contraint_to_annotation.rb | 5 ---- db/schema.rb | 23 +++++-------------- 3 files changed, 10 insertions(+), 26 deletions(-) delete mode 100644 db/migrate/20231028113531_add_category_not_null_contraint_to_annotation.rb diff --git a/db/migrate/20221015114100_create_annotations.rb b/db/migrate/20221015114100_create_annotations.rb index 02bbd65bc..ae6fae3e3 100644 --- a/db/migrate/20221015114100_create_annotations.rb +++ b/db/migrate/20221015114100_create_annotations.rb @@ -5,11 +5,11 @@ def change t.references :user, null: false, foreign_key: true t.text :timestamp, null: false t.text :comment - t.integer :category - t.boolean :visible_for_teacher - t.string :color - t.integer :public_comment_id + t.string :color, null: false + t.integer :category, null: false t.integer :subcategory + t.boolean :visible_for_teacher, null: false + t.integer :public_comment_id t.timestamps end diff --git a/db/migrate/20231028113531_add_category_not_null_contraint_to_annotation.rb b/db/migrate/20231028113531_add_category_not_null_contraint_to_annotation.rb deleted file mode 100644 index f815562cb..000000000 --- a/db/migrate/20231028113531_add_category_not_null_contraint_to_annotation.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddCategoryNotNullContraintToAnnotation < ActiveRecord::Migration[7.0] - def change - change_column_null :annotations, :category, false - end -end diff --git a/db/schema.rb b/db/schema.rb index ffedf812f..34b39a078 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_10_28_113531) do +ActiveRecord::Schema[7.0].define(version: 2023_06_08_093848) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -20,13 +20,13 @@ t.bigint "user_id", null: false t.text "timestamp", null: false t.text "comment" + t.string "color", null: false t.integer "category", null: false - t.boolean "visible_for_teacher" - t.string "color" + t.integer "subcategory" + t.boolean "visible_for_teacher", null: false + t.integer "public_comment_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "public_comment_id" - t.integer "subcategory" t.index ["medium_id"], name: "index_annotations_on_medium_id" t.index ["user_id"], name: "index_annotations_on_user_id" end @@ -205,16 +205,6 @@ t.index ["editable_id", "editable_type"], name: "polymorphic_editable_idx" end - create_table "feedbacks", force: :cascade do |t| - t.text "title" - t.text "feedback" - t.boolean "can_contact", default: false - t.bigint "user_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["user_id"], name: "index_feedbacks_on_user_id" - end - create_table "friendly_id_slugs", force: :cascade do |t| t.string "slug", null: false t.integer "sluggable_id", null: false @@ -379,8 +369,8 @@ t.datetime "released_at", precision: nil t.text "publisher" t.datetime "file_last_edited", precision: nil - t.text "external_link_description" t.integer "annotations_status", default: 0 + t.text "external_link_description" t.index ["quizzable_type", "quizzable_id"], name: "index_media_on_quizzable_type_and_quizzable_id" t.index ["teachable_type", "teachable_id"], name: "index_media_on_teachable_type_and_teachable_id" end @@ -938,7 +928,6 @@ add_foreign_key "commontator_subscriptions", "commontator_threads", column: "thread_id", on_update: :cascade, on_delete: :cascade add_foreign_key "course_self_joins", "courses" add_foreign_key "divisions", "programs" - add_foreign_key "feedbacks", "users" add_foreign_key "imports", "media" add_foreign_key "items", "media" add_foreign_key "items", "sections" From d9674e439cc52e55ba94fab15de3b561bd1969d7 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sat, 4 Nov 2023 15:09:13 +0100 Subject: [PATCH 180/291] Add constraint to AnnotationsController --- app/controllers/annotations_controller.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controllers/annotations_controller.rb b/app/controllers/annotations_controller.rb index b13a78241..d9f545c07 100644 --- a/app/controllers/annotations_controller.rb +++ b/app/controllers/annotations_controller.rb @@ -35,6 +35,8 @@ def create @annotation = Annotation.new(annotation_params) return unless is_valid_color(@annotation.color) + return if @annotation.category_for_database == Annotation.categories[:content] and + @annotation.subcategory.nil? @annotation.public_comment_id = post_comment(@annotation) @annotation.user_id = current_user.id @@ -50,6 +52,8 @@ def update @annotation.assign_attributes(annotation_params) return unless is_valid_color(@annotation.color) + return if @annotation.category_for_database == Annotation.categories[:content] and + @annotation.subcategory.nil? @annotation.public_comment_id = post_comment(@annotation) @annotation.save From 9cb902133a798b1a8b9daca512ccd41d8b91b031 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sun, 5 Nov 2023 10:05:40 +0100 Subject: [PATCH 181/291] Improve code style --- app/controllers/annotations_controller.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/annotations_controller.rb b/app/controllers/annotations_controller.rb index d9f545c07..cb8f34a5d 100644 --- a/app/controllers/annotations_controller.rb +++ b/app/controllers/annotations_controller.rb @@ -23,7 +23,7 @@ def edit @total_seconds = @annotation.timestamp.total_seconds @medium_id = @annotation.medium_id - @posted = (@annotation.public_comment_id != nil) + @posted = !@annotation.public_comment_id.nil? # if this annotation has an associated commontator comment, # we have to call the "get_comment" method in order to get @@ -34,7 +34,7 @@ def edit def create @annotation = Annotation.new(annotation_params) - return unless is_valid_color(@annotation.color) + return unless valid_color?(@annotation.color) return if @annotation.category_for_database == Annotation.categories[:content] and @annotation.subcategory.nil? @annotation.public_comment_id = post_comment(@annotation) @@ -51,7 +51,7 @@ def update @annotation = Annotation.find(params[:id]) @annotation.assign_attributes(annotation_params) - return unless is_valid_color(@annotation.color) + return unless valid_color?(@annotation.color) return if @annotation.category_for_database == Annotation.categories[:content] and @annotation.subcategory.nil? @annotation.public_comment_id = post_comment(@annotation) @@ -149,15 +149,15 @@ def annotation_auxiliary_params ) end - def is_valid_color(color) - return color&.match?(/\A#([0-9]|[A-F]){6}\z/) + def valid_color?(color) + color&.match?(/\A#([0-9]|[A-F]){6}\z/) end def post_comment(annotation) public_comment_id = annotation.public_comment_id # return if checkbox "post_as_comment" is not checked and if there is no comment to update - return if annotation_auxiliary_params[:post_as_comment] != "1" && public_comment_id.nil? + return if annotation_auxiliary_params[:post_as_comment] != '1' && public_comment_id.nil? comment = annotation_params[:comment] @@ -178,7 +178,7 @@ def post_comment(annotation) # delete comment as it is already saved in the commontator comment model annotation.comment = nil - return commontator_comment.id + commontator_comment.id end end From 4c02f5a1c47ad1e9f3d0603ca7b8ac6f781f9110 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Tue, 7 Nov 2023 19:25:57 +0100 Subject: [PATCH 182/291] Rename variables to proper camelCase --- .../javascripts/thyme/display_manager.js | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/thyme/display_manager.js b/app/assets/javascripts/thyme/display_manager.js index 7fac4e32f..3f774a574 100644 --- a/app/assets/javascripts/thyme/display_manager.js +++ b/app/assets/javascripts/thyme/display_manager.js @@ -55,31 +55,31 @@ class DisplayManager { } // mediaQuery listener for very small screens - const matchVerysmallX = window.matchMedia("screen and (max-width: " + + const matchVerySmallX = window.matchMedia("screen and (max-width: " + thymeAttributes.hideControlBarThreshold.x + "px)"); - matchVerysmallX.addListener(function (result) { + matchVerySmallX.addListener(function (result) { if (result.matches) { dm.adaptToSmallDisplay(); } }); - const matchVerysmallY = window.matchMedia("screen and (max-height: " + + const matchVerySmallY = window.matchMedia("screen and (max-height: " + thymeAttributes.hideControlBarThreshold.y + "px)"); - matchVerysmallY.addListener(function (result) { + matchVerySmallY.addListener(function (result) { if (result.matches) { dm.adaptToSmallDisplay(); } }); - const matchVerysmalldeviceX = window.matchMedia("screen and (max-device-width: " + + const matchVerySmallDeviceX = window.matchMedia("screen and (max-device-width: " + thymeAttributes.hideControlBarThreshold.x + "px)"); - matchVerysmalldeviceX.addListener(function (result) { + matchVerySmallDeviceX.addListener(function (result) { if (result.matches) { dm.adaptToSmallDisplay(); } }); - const matchVerysmalldeviceY = window.matchMedia("screen and (max-device-height: " + + const matchVerySmallDeviceY = window.matchMedia("screen and (max-device-height: " + thymeAttributes.hideControlBarThreshold.y + "px)"); - matchVerysmalldeviceY.addListener(function (result) { + matchVerySmallDeviceY.addListener(function (result) { if (result.matches) { dm.adaptToSmallDisplay(); } @@ -106,20 +106,20 @@ class DisplayManager { } }); - let matchNormaldeviceX = window.matchMedia("screen and (min-device-width: " + + let matchNormalDeviceX = window.matchMedia("screen and (min-device-width: " + (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); - let matchNormaldeviceY; - matchNormaldeviceX.addListener(function (result) { - matchNormaldeviceY = window.matchMedia("screen and (min-device-height: " + + let matchNormalDeviceY; + matchNormalDeviceX.addListener(function (result) { + matchNormalDeviceY = window.matchMedia("screen and (min-device-height: " + (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); if (result.matches && matchNormalY.matches) { dm.adaptToLargeDisplay(); } }); - matchNormaldeviceY = window.matchMedia("screen and (min-device-height: " + + matchNormalDeviceY = window.matchMedia("screen and (min-device-height: " + (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); - matchNormaldeviceY.addListener(function (result) { - matchNormaldeviceX = window.matchMedia("screen and (min-device-width: " + + matchNormalDeviceY.addListener(function (result) { + matchNormalDeviceX = window.matchMedia("screen and (min-device-width: " + (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); if (result.matches && matchNormalX.matches) { dm.adaptToLargeDisplay(); From e08812f2f8a52d8e02b9956c2a97590498351ccd Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Tue, 7 Nov 2023 19:27:52 +0100 Subject: [PATCH 183/291] Improve code style --- app/assets/javascripts/thyme/utility.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/thyme/utility.js b/app/assets/javascripts/thyme/utility.js index 2ec0f45f0..9128c9758 100644 --- a/app/assets/javascripts/thyme/utility.js +++ b/app/assets/javascripts/thyme/utility.js @@ -58,7 +58,7 @@ const thymeUtility = { /* Installs a listener which lets the video play/pause when clicked. */ - playOnClick() { + playOnClick: function() { const video = thymeAttributes.video; video.addEventListener('click', function() { if (video.paused) { @@ -112,7 +112,7 @@ const thymeUtility = { (In order to make this work, we have to wait for the video's metadata to be loaded.) */ - setUpMaxTime(maxTimeId) { + setUpMaxTime: function(maxTimeId) { const video = thymeAttributes.video; video.addEventListener('loadedmetadata', function() { const maxTime = document.getElementById(maxTimeId); @@ -135,8 +135,8 @@ const thymeUtility = { Converts a given integer between 0 and 255 into a hexadecimal, s.t. e.g. "15" becomes "0f" (instead of just "f") -> needed for correct format. */ - toHexaDecimal: function(int) { - return int.toString(16).padStart(2, '0'); + toHexaDecimal: function(integer) { + return integer.toString(16).padStart(2, '0'); }, }; From 55e893a12206a66b8423753cd1f983b156bc349c Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Tue, 7 Nov 2023 19:34:39 +0100 Subject: [PATCH 184/291] Revert time_stamp.rb --- app/models/time_stamp.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/time_stamp.rb b/app/models/time_stamp.rb index 94d3c5d86..1924e36bb 100644 --- a/app/models/time_stamp.rb +++ b/app/models/time_stamp.rb @@ -5,7 +5,7 @@ class TimeStamp validates :milliseconds, presence: true attr_reader :hours, :minutes, :seconds, :milliseconds - + # extract from YAML def self.load(text) YAML.safe_load(text, permitted_classes: [TimeStamp, From ec696e974c9c3b396d9c574aae56af4bcb794436 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Tue, 7 Nov 2023 19:48:40 +0100 Subject: [PATCH 185/291] Remove ==/!= null statements --- .../javascripts/thyme/annotations/annotation.js | 2 +- .../thyme/annotations/annotation_area.js | 2 +- .../thyme/annotations/annotation_manager.js | 6 +++--- .../thyme/components/full_screen_button.js | 2 +- .../thyme/components/ia_back_button.js | 2 +- .../components/next_chapter_button (Kopie).js | 16 ---------------- .../thyme/components/next_chapter_button.js | 2 +- .../thyme/components/previous_chapter_button.js | 2 +- .../javascripts/thyme/components/seek_bar.js | 2 +- .../thyme/components/speed_selector.js | 6 +++--- app/assets/javascripts/thyme/heatmap.js | 2 +- app/assets/javascripts/thyme/thyme_player.js | 2 +- app/assets/javascripts/thyme/utility.js | 2 +- 13 files changed, 16 insertions(+), 32 deletions(-) delete mode 100644 app/assets/javascripts/thyme/components/next_chapter_button (Kopie).js diff --git a/app/assets/javascripts/thyme/annotations/annotation.js b/app/assets/javascripts/thyme/annotations/annotation.js index c13e901e2..348bb80d3 100644 --- a/app/assets/javascripts/thyme/annotations/annotation.js +++ b/app/assets/javascripts/thyme/annotations/annotation.js @@ -82,7 +82,7 @@ class Annotation { categoryLocale() { const c = this.category; const s = this.subcategory; - return s == null ? c.locale() : c.locale() + " (" + s.locale() + ")"; + return s ? c.locale() + " (" + s.locale() + ")" : c.locale(); } /* diff --git a/app/assets/javascripts/thyme/annotations/annotation_area.js b/app/assets/javascripts/thyme/annotations/annotation_area.js index 149a6f52d..fcaf1bfa6 100644 --- a/app/assets/javascripts/thyme/annotations/annotation_area.js +++ b/app/assets/javascripts/thyme/annotations/annotation_area.js @@ -69,7 +69,7 @@ class AnnotationArea { Update the annotation area with the content of the given annotation. */ update(annotation) { - if (annotation == null) { + if (!annotation) { return; } this.annotation = annotation; diff --git a/app/assets/javascripts/thyme/annotations/annotation_manager.js b/app/assets/javascripts/thyme/annotations/annotation_manager.js index 19b43f0d8..2ccb0856c 100644 --- a/app/assets/javascripts/thyme/annotations/annotation_manager.js +++ b/app/assets/javascripts/thyme/annotations/annotation_manager.js @@ -52,7 +52,7 @@ class AnnotationManager { function onClick() { annotationManager.onClick(a); } - if (this.sizeFunc !== null && this.sizeFunc(a)) { + if (this.sizeFunc && this.sizeFunc(a)) { a.createBigMarker(this.colorFunc(a), this.strokeColorFunc(a), onClick); } else { a.createMarker(this.colorFunc(a), this.strokeColorFunc(a), onClick); @@ -110,7 +110,7 @@ class AnnotationManager { */ static find(id) { const annotations = thymeAttributes.annotations; - if (annotations == null) { + if (!annotations) { return null; } for (let a of annotations) { @@ -127,7 +127,7 @@ class AnnotationManager { */ static findIndex(id) { const annotations = thymeAttributes.annotations; - if (annotations == null) { + if (!annotations) { return undefined; } for (let i = 0; i < annotations.length; i++) { diff --git a/app/assets/javascripts/thyme/components/full_screen_button.js b/app/assets/javascripts/thyme/components/full_screen_button.js index 30fa04e30..8575dd7f4 100644 --- a/app/assets/javascripts/thyme/components/full_screen_button.js +++ b/app/assets/javascripts/thyme/components/full_screen_button.js @@ -46,7 +46,7 @@ class FullScreenButton extends Component { } #fullscreenChange() { - if (document.fullscreenElement !== null) { + if (document.fullscreenElement) { this.element.innerHTML = 'fullscreen_exit'; this.element.dataset.status = 'true'; /* Set height to 100vh in fullscreen mode as it otherwise diff --git a/app/assets/javascripts/thyme/components/ia_back_button.js b/app/assets/javascripts/thyme/components/ia_back_button.js index d75b958cd..0de777ac4 100644 --- a/app/assets/javascripts/thyme/components/ia_back_button.js +++ b/app/assets/javascripts/thyme/components/ia_back_button.js @@ -26,7 +26,7 @@ class IaBackButton extends Component { const currentChapter = $('#' + this.chapterListId + ' .current'); if (currentChapter.length > 0) { let backInfo = currentChapter.data('text').split(':', 1)[0]; - if ((backInfo != null) && backInfo.length > 20) { + if (backInfo && backInfo.length > 20) { backInfo = this.element.dataset.back; } else { backInfo = this.element.dataset.backto + backInfo; diff --git a/app/assets/javascripts/thyme/components/next_chapter_button (Kopie).js b/app/assets/javascripts/thyme/components/next_chapter_button (Kopie).js deleted file mode 100644 index 7e4db886e..000000000 --- a/app/assets/javascripts/thyme/components/next_chapter_button (Kopie).js +++ /dev/null @@ -1,16 +0,0 @@ -class NextChapterButton extends Component { - - add() { - const video = thymeAttributes.video; - const element = this.element; - - // Event handler for the nextChapter button - element.addEventListener('click', function() { - const next = thymeAttributes.chapterManager.nextChapterStart(); - if (next != null) { - video.currentTime = thymeAttributes.chapterManager.nextChapterStart(); - } - }); - } - -} diff --git a/app/assets/javascripts/thyme/components/next_chapter_button.js b/app/assets/javascripts/thyme/components/next_chapter_button.js index 7e4db886e..977fc8e09 100644 --- a/app/assets/javascripts/thyme/components/next_chapter_button.js +++ b/app/assets/javascripts/thyme/components/next_chapter_button.js @@ -7,7 +7,7 @@ class NextChapterButton extends Component { // Event handler for the nextChapter button element.addEventListener('click', function() { const next = thymeAttributes.chapterManager.nextChapterStart(); - if (next != null) { + if (next) { video.currentTime = thymeAttributes.chapterManager.nextChapterStart(); } }); diff --git a/app/assets/javascripts/thyme/components/previous_chapter_button.js b/app/assets/javascripts/thyme/components/previous_chapter_button.js index 3cde02063..6d778a1b4 100644 --- a/app/assets/javascripts/thyme/components/previous_chapter_button.js +++ b/app/assets/javascripts/thyme/components/previous_chapter_button.js @@ -7,7 +7,7 @@ class PreviousChapterButton extends Component { // Event handler for the previousChapter button element.addEventListener('click', function() { const previous = thymeAttributes.chapterManager.previousChapterStart(); - if (previous != null) { + if (previous) { video.currentTime = thymeAttributes.chapterManager.previousChapterStart(); } }); diff --git a/app/assets/javascripts/thyme/components/seek_bar.js b/app/assets/javascripts/thyme/components/seek_bar.js index f2b9b8912..aca8efd19 100644 --- a/app/assets/javascripts/thyme/components/seek_bar.js +++ b/app/assets/javascripts/thyme/components/seek_bar.js @@ -17,7 +17,7 @@ class SeekBar extends Component { // if videomedtadata have been loaded, set up seek bar video.addEventListener('loadedmetadata', function() { - if (video.dataset.time != null) { + if (video.dataset.time) { const time = video.dataset.time; element.value = video.currentTime / video.duration * 100; } else { diff --git a/app/assets/javascripts/thyme/components/speed_selector.js b/app/assets/javascripts/thyme/components/speed_selector.js index 4bab67024..743902101 100644 --- a/app/assets/javascripts/thyme/components/speed_selector.js +++ b/app/assets/javascripts/thyme/components/speed_selector.js @@ -11,11 +11,11 @@ class SpeedSelector extends Component { const element = this.element; element.addEventListener('click', function() { - if (video.preservesPitch != null) { + if (video.preservesPitch) { video.preservesPitch = true; - } else if (video.mozPreservesPitch != null) { + } else if (video.mozPreservesPitch) { video.mozPreservesPitch = true; - } else if (video.webkitPreservesPitch != null) { + } else if (video.webkitPreservesPitch) { video.webkitPreservesPitch = true; } video.playbackRate = this.options[this.selectedIndex].value; diff --git a/app/assets/javascripts/thyme/heatmap.js b/app/assets/javascripts/thyme/heatmap.js index 7f9338f88..0dfbc7f3e 100644 --- a/app/assets/javascripts/thyme/heatmap.js +++ b/app/assets/javascripts/thyme/heatmap.js @@ -18,7 +18,7 @@ class Heatmap { draw() { - if (thymeAttributes.annotations == null) { + if (!thymeAttributes.annotations) { return; } this.heatmap.empty(); diff --git a/app/assets/javascripts/thyme/thyme_player.js b/app/assets/javascripts/thyme/thyme_player.js index 18e02ec27..a6b220496 100644 --- a/app/assets/javascripts/thyme/thyme_player.js +++ b/app/assets/javascripts/thyme/thyme_player.js @@ -76,7 +76,7 @@ $(document).on('turbolinks:load', function() { /* update might change the annotation which is currently shown in the annotation area -> find the updated annotation in the annotation array and update the area. */ - if (annotationArea.annotation != null) { + if (annotationArea.annotation) { const id = annotationArea.annotation.id; annotationArea.update(AnnotationManager.find(id)); } diff --git a/app/assets/javascripts/thyme/utility.js b/app/assets/javascripts/thyme/utility.js index 9128c9758..88b46a59c 100644 --- a/app/assets/javascripts/thyme/utility.js +++ b/app/assets/javascripts/thyme/utility.js @@ -117,7 +117,7 @@ const thymeUtility = { video.addEventListener('loadedmetadata', function() { const maxTime = document.getElementById(maxTimeId); maxTime.innerHTML = thymeUtility.secondsToTime(video.duration); - if (video.dataset.time != null) { + if (video.dataset.time) { const time = video.dataset.time; video.currentTime = time; } From 0770f9887842cd7daad18cae50060f0f9903d59b Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Tue, 7 Nov 2023 19:51:04 +0100 Subject: [PATCH 186/291] Add newline after/before function --- app/assets/javascripts/thyme/thyme_feedback.js | 8 ++++++++ app/assets/javascripts/thyme/thyme_player.js | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/app/assets/javascripts/thyme/thyme_feedback.js b/app/assets/javascripts/thyme/thyme_feedback.js index b7eaae1e9..eacc51982 100644 --- a/app/assets/javascripts/thyme/thyme_feedback.js +++ b/app/assets/javascripts/thyme/thyme_feedback.js @@ -61,6 +61,7 @@ $(document).on('turbolinks:load', function() { function colorFunc(annotation) { return annotation.category.color; } + function isValid(annotation) { for (let toggle of annotationCategoryToggles) { if (annotation.category === toggle.category && toggle.getValue()) { @@ -69,20 +70,26 @@ $(document).on('turbolinks:load', function() { } return false; } + const annotationArea = new AnnotationArea(false, colorFunc, null, isValid); thymeAttributes.annotationArea = annotationArea; + function strokeColorFunc(annotation) { return annotation.category === Category.MISTAKE ? 'darkred' : 'black'; } + function sizeFunc(annotation) { return annotation.category === Category.MISTAKE; } + function onClick(annotation) { annotationArea.update(annotation); } + function onUpdate() { heatmap.draw(); } + const annotationManager = new AnnotationManager(colorFunc, strokeColorFunc, sizeFunc, onClick, onUpdate, isValid); thymeAttributes.annotationManager = annotationManager; @@ -112,6 +119,7 @@ $(document).on('turbolinks:load', function() { annotationManager.updateMarkers(); } }; + window.onresize = resizeContainer; video.onloadedmetadata = resizeContainer; diff --git a/app/assets/javascripts/thyme/thyme_player.js b/app/assets/javascripts/thyme/thyme_player.js index a6b220496..656909694 100644 --- a/app/assets/javascripts/thyme/thyme_player.js +++ b/app/assets/javascripts/thyme/thyme_player.js @@ -52,26 +52,33 @@ $(document).on('turbolinks:load', function() { function colorFunc(annotation) { return annotation.color; } + function onClose() { iaButton.minus(); } + function isValid(annotation) { return (!annotationsToggle.getValue() && !annotation.belongsToCurrentUser ? false : true); } + const annotationArea = new AnnotationArea(true, colorFunc, onClose, isValid); thymeAttributes.annotationArea = annotationArea; + function strokeColorFunc(annotation) { return 'black'; } + function sizeFunc(annotation) { return false; } + function onClick(annotation) { iaButton.minus(); annotationArea.update(annotation); annotationArea.show(); $('#caption').hide(); } + function onUpdate() { /* update might change the annotation which is currently shown in the annotation area -> find the updated annotation in the annotation array @@ -81,6 +88,7 @@ $(document).on('turbolinks:load', function() { annotationArea.update(AnnotationManager.find(id)); } } + const annotationManager = new AnnotationManager(colorFunc, strokeColorFunc, sizeFunc, onClick, onUpdate, isValid); thymeAttributes.annotationManager = annotationManager; @@ -144,6 +152,7 @@ $(document).on('turbolinks:load', function() { function onEnlarge() { iaButton.plus(); } + const elements = [$('#caption'), $('#annotation-caption'), $('#video-controlBar')]; const displayManager = new DisplayManager(elements, onEnlarge); @@ -158,6 +167,7 @@ $(document).on('turbolinks:load', function() { annotationManager.updateMarkers(); } }; + window.onresize = resizeContainer; video.onloadedmetadata = resizeContainer; From 53320d7fb78d058e7b9a97481c7fad17ca04db63 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Tue, 7 Nov 2023 19:57:04 +0100 Subject: [PATCH 187/291] Add comment --- app/models/medium.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/medium.rb b/app/models/medium.rb index ac903bcee..835acf45c 100644 --- a/app/models/medium.rb +++ b/app/models/medium.rb @@ -1126,6 +1126,10 @@ def subscribed_users Lecture.find_by(id: teachable.lecture_id).user_ids end + # Returns either the annotations status (1 = activated, -1 = deactivated) + # of this medium or the annotations status of the associated lecture + # if "inherit from lecture" was selected (i.e. if the annotations status of + # this medium is 0). def get_annotations_status return annotations_status if annotations_status != 0 lecture.annotations_status From 4182d2025d28539f3c9b58dc9877463614419a2f Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Wed, 8 Nov 2023 09:40:23 +0100 Subject: [PATCH 188/291] Clean up code/fix bug --- app/controllers/annotations_controller.rb | 43 +++++++++++++---------- app/models/annotation.rb | 14 ++++---- app/views/annotations/edit.js.erb | 4 ++- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/app/controllers/annotations_controller.rb b/app/controllers/annotations_controller.rb index cb8f34a5d..3add73a44 100644 --- a/app/controllers/annotations_controller.rb +++ b/app/controllers/annotations_controller.rb @@ -34,10 +34,7 @@ def edit def create @annotation = Annotation.new(annotation_params) - return unless valid_color?(@annotation.color) - return if @annotation.category_for_database == Annotation.categories[:content] and - @annotation.subcategory.nil? - @annotation.public_comment_id = post_comment(@annotation) + create_and_update_shared(@annotation) @annotation.user_id = current_user.id @total_seconds = annotation_auxiliary_params[:total_seconds] @@ -51,10 +48,7 @@ def update @annotation = Annotation.find(params[:id]) @annotation.assign_attributes(annotation_params) - return unless valid_color?(@annotation.color) - return if @annotation.category_for_database == Annotation.categories[:content] and - @annotation.subcategory.nil? - @annotation.public_comment_id = post_comment(@annotation) + create_and_update_shared(@annotation) @annotation.save end @@ -115,17 +109,11 @@ def update_annotations end def num_nearby_posted_mistake_annotations - annotations = Annotation.where(medium: params[:mediumId]) + annotations = Annotation.where(medium: params[:mediumId], category: 'mistake').commented # the time (!) radius in which annotation are considered as "nearby" radius = params[:radius].to_i timestamp = params[:timestamp].to_i - counter = 0 - for annotation in annotations - next unless annotation.category == "mistake" && - (annotation.timestamp.total_seconds - timestamp).abs() < radius && - !annotation.public_comment_id.nil? - counter += 1 - end + counter = annotations.to_a.count { |annotation| annotation.nearby?(timestamp, radius) } render json: counter end @@ -153,11 +141,28 @@ def valid_color?(color) color&.match?(/\A#([0-9]|[A-F]){6}\z/) end + # resets the subcategory to "nil" if the selected category isn't "content" + def subcategory_nil(annotation) + if annotation.category_for_database != Annotation.categories[:content] + annotation.subcategory = nil + end + end + + # common code for the create and update method + def create_and_update_shared(annotation) + return unless valid_color?(annotation.color) + return if annotation.category_for_database == Annotation.categories[:content] and + annotation.subcategory.nil? + subcategory_nil(annotation) + annotation.public_comment_id = post_comment(annotation) + end + + # Run all the Commontator::Comment related code here def post_comment(annotation) public_comment_id = annotation.public_comment_id # return if checkbox "post_as_comment" is not checked and if there is no comment to update - return if annotation_auxiliary_params[:post_as_comment] != '1' && public_comment_id.nil? + return if annotation_auxiliary_params[:post_as_comment] != '1' and public_comment_id.nil? comment = annotation_params[:comment] @@ -166,9 +171,9 @@ def post_comment(annotation) commontator_comment = Commontator::Comment.create( thread: medium.commontator_thread, creator: current_user, - body: comment + body: comment, + annotation: annotation ) - commontator_comment.annotation = annotation else # comment already exists -> update it commontator_comment = Commontator::Comment.find_by(id: public_comment_id) commontator_comment.update(editor: current_user, diff --git a/app/models/annotation.rb b/app/models/annotation.rb index 73147eb4f..645696d92 100644 --- a/app/models/annotation.rb +++ b/app/models/annotation.rb @@ -4,6 +4,8 @@ class Annotation < ApplicationRecord belongs_to :public_comment, class_name: 'Commontator::Comment', optional: true + scope :commented, -> { where.not(public_comment_id: nil) } + # the timestamp for the annotation position is serialized as text in the db serialize :timestamp, TimeStamp @@ -11,12 +13,12 @@ class Annotation < ApplicationRecord enum subcategory: { definition: 0, argument: 1, strategy: 2 } def get_comment - if self.public_comment_id.nil? - return self.comment - else - commontator_comment = Commontator::Comment.find_by(id: public_comment_id) - return commontator_comment.body - end + return self.comment if self.public_comment_id.nil? + commontator_comment = Commontator::Comment.find_by(id: public_comment_id).body + end + + def nearby?(other_timestamp, radius) + (timestamp.total_seconds - other_timestamp).abs() < radius end def self.colors diff --git a/app/views/annotations/edit.js.erb b/app/views/annotations/edit.js.erb index 8d5fb1803..2bdd7ab7f 100644 --- a/app/views/annotations/edit.js.erb +++ b/app/views/annotations/edit.js.erb @@ -4,6 +4,7 @@ $('#annotation-modal').modal('show'); var submitButton = document.getElementById('annotation-modal-submit-button'); var postAsComment = document.getElementById('annotation_post_as_comment'); +var posted = <%= @posted %>; postAsComment.addEventListener('change', function() { if (this.checked) { @@ -179,6 +180,7 @@ function visibleForTeacher(boolean) { } function postComment(boolean) { + boolean = posted ? true : boolean; $('#annotation_post_as_comment').prop("checked", boolean); } @@ -239,7 +241,7 @@ previewToggle.addEventListener('change', function() { }); // disable post comment checkbox if annotation was already posted -if (<%= @posted %>) { +if (posted) { postComment(true); postAsComment.disabled = true; } From e272579a8205769f5dee42a6ff8bf2ab9678b84c Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Wed, 8 Nov 2023 09:58:44 +0100 Subject: [PATCH 189/291] Check timestamp in backend --- app/controllers/annotations_controller.rb | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/app/controllers/annotations_controller.rb b/app/controllers/annotations_controller.rb index 3add73a44..4172525b6 100644 --- a/app/controllers/annotations_controller.rb +++ b/app/controllers/annotations_controller.rb @@ -34,12 +34,12 @@ def edit def create @annotation = Annotation.new(annotation_params) - create_and_update_shared(@annotation) - @annotation.user_id = current_user.id @total_seconds = annotation_auxiliary_params[:total_seconds] @annotation.timestamp = TimeStamp.new(total_seconds: @total_seconds) + return unless create_and_update_shared(@annotation) + @annotation.save render :update end @@ -48,7 +48,7 @@ def update @annotation = Annotation.find(params[:id]) @annotation.assign_attributes(annotation_params) - create_and_update_shared(@annotation) + return unless create_and_update_shared(@annotation) @annotation.save end @@ -141,20 +141,29 @@ def valid_color?(color) color&.match?(/\A#([0-9]|[A-F]){6}\z/) end + def valid_time?(annotation) + time = annotation.timestamp.total_seconds + time >= 0 and time <= annotation.medium.video["duration"] + end + + # checks that the subcategory is non-nil if the category is "content" and # resets the subcategory to "nil" if the selected category isn't "content" def subcategory_nil(annotation) + return if annotation.category_for_database == Annotation.categories[:content] and + annotation.subcategory.nil? if annotation.category_for_database != Annotation.categories[:content] annotation.subcategory = nil end + return true end # common code for the create and update method def create_and_update_shared(annotation) - return unless valid_color?(annotation.color) - return if annotation.category_for_database == Annotation.categories[:content] and - annotation.subcategory.nil? - subcategory_nil(annotation) + return unless valid_color?(annotation.color) and + valid_time?(annotation) and + subcategory_nil(annotation) annotation.public_comment_id = post_comment(annotation) + return true end # Run all the Commontator::Comment related code here From 50bb82be7b9c3cb65aee4da8f1731e872c561023 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Wed, 8 Nov 2023 17:54:28 +0100 Subject: [PATCH 190/291] Add annotations status check + update --- app/controllers/lectures_controller.rb | 2 ++ app/controllers/media_controller.rb | 5 +++++ app/models/lecture.rb | 4 ++++ app/models/medium.rb | 4 ++++ .../20230117124508_add_annotations_status_to_medium.rb | 2 +- .../20230117124528_add_annotations_status_to_lecture.rb | 2 +- ...1_add_default_annotations_status_to_lecture_and_media.rb | 6 ++++++ db/schema.rb | 2 +- 8 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20231108164701_add_default_annotations_status_to_lecture_and_media.rb diff --git a/app/controllers/lectures_controller.rb b/app/controllers/lectures_controller.rb index 946d171a3..54fa4dad4 100644 --- a/app/controllers/lectures_controller.rb +++ b/app/controllers/lectures_controller.rb @@ -35,6 +35,8 @@ def edit end def update + return unless @lecture.valid_annotations_status? + editor_ids = lecture_params[:editor_ids] if editor_ids != nil # removes the empty String "" in the NEW array of editor ids diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb index 9174711b2..5b00c2b5e 100644 --- a/app/controllers/media_controller.rb +++ b/app/controllers/media_controller.rb @@ -66,6 +66,8 @@ def update @errors = @medium.errors return unless @errors.empty? + return unless @medium.valid_annotations_status? + # make sure the medium is touched # (it will not be touched automatically in some cases (e.g. if you only # update the associated tags), causing trouble for caching) @@ -130,6 +132,9 @@ def update def create @medium = Medium.new(medium_params) + + return unless @medium.valid_annotations_status? + @medium.locale = @medium.teachable&.locale @medium.editors = [current_user] if @medium.teachable.class.to_s == 'Lesson' diff --git a/app/models/lecture.rb b/app/models/lecture.rb index 9552b1461..35515faad 100644 --- a/app/models/lecture.rb +++ b/app/models/lecture.rb @@ -922,4 +922,8 @@ def only_one_lecture errors.add(:course, :already_present) end + + def valid_annotations_status? + [-1, 1].include?(self.annotations_status) + end end diff --git a/app/models/medium.rb b/app/models/medium.rb index 835acf45c..4cd63cdfc 100644 --- a/app/models/medium.rb +++ b/app/models/medium.rb @@ -1315,4 +1315,8 @@ def answers_count becomes(Question).answers.count end + + def valid_annotations_status? + [-1, 0, 1].include?(self.annotations_status) + end end diff --git a/db/migrate/20230117124508_add_annotations_status_to_medium.rb b/db/migrate/20230117124508_add_annotations_status_to_medium.rb index ca48b67d7..6942012d0 100644 --- a/db/migrate/20230117124508_add_annotations_status_to_medium.rb +++ b/db/migrate/20230117124508_add_annotations_status_to_medium.rb @@ -1,5 +1,5 @@ class AddAnnotationsStatusToMedium < ActiveRecord::Migration[7.0] def change - add_column :media, :annotations_status, :integer, default: 0 + add_column :media, :annotations_status, :integer, default: 0, null: false end end diff --git a/db/migrate/20230117124528_add_annotations_status_to_lecture.rb b/db/migrate/20230117124528_add_annotations_status_to_lecture.rb index e829fcc1a..469ce474f 100644 --- a/db/migrate/20230117124528_add_annotations_status_to_lecture.rb +++ b/db/migrate/20230117124528_add_annotations_status_to_lecture.rb @@ -1,5 +1,5 @@ class AddAnnotationsStatusToLecture < ActiveRecord::Migration[7.0] def change - add_column :lectures, :annotations_status, :integer, default: -1 + add_column :lectures, :annotations_status, :integer, default: -1, null: false end end diff --git a/db/migrate/20231108164701_add_default_annotations_status_to_lecture_and_media.rb b/db/migrate/20231108164701_add_default_annotations_status_to_lecture_and_media.rb new file mode 100644 index 000000000..735c2e053 --- /dev/null +++ b/db/migrate/20231108164701_add_default_annotations_status_to_lecture_and_media.rb @@ -0,0 +1,6 @@ +class AddDefaultAnnotationsStatusToLectureAndMedia < ActiveRecord::Migration[7.0] + def change + Lecture.update_all(annotations_status: -1) + Medium.update_all(annotations_status: 0) + end +end diff --git a/db/schema.rb b/db/schema.rb index 34b39a078..dd43d1536 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_06_08_093848) do +ActiveRecord::Schema[7.0].define(version: 2023_11_08_164701) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" From 5143f208f0d2cf429d1510c330001f708c1d9fd3 Mon Sep 17 00:00:00 2001 From: Frodo161 Date: Sat, 11 Nov 2023 19:23:01 +0100 Subject: [PATCH 191/291] Fix user authorization --- .../thyme/annotations/annotation_manager.js | 4 ++ app/assets/javascripts/thyme/attributes.js | 3 + .../thyme/components/annotations_toggle.js | 6 +- .../javascripts/thyme/components/ia_button.js | 55 +++++++++---------- app/assets/javascripts/thyme/thyme_player.js | 10 +++- app/models/lecture.rb | 11 ++-- app/models/medium.rb | 17 +++--- app/views/media/play.html.erb | 18 +++--- 8 files changed, 73 insertions(+), 51 deletions(-) diff --git a/app/assets/javascripts/thyme/annotations/annotation_manager.js b/app/assets/javascripts/thyme/annotations/annotation_manager.js index 2ccb0856c..7b541fe3c 100644 --- a/app/assets/javascripts/thyme/annotations/annotation_manager.js +++ b/app/assets/javascripts/thyme/annotations/annotation_manager.js @@ -72,6 +72,10 @@ class AnnotationManager { successfully updated. */ updateAnnotations() { + if (!thymeAttributes.annotationFeatureActive) { + return; + } + const annotationManager = this; $.ajax(Routes.update_annotations_path(), { type: 'GET', diff --git a/app/assets/javascripts/thyme/attributes.js b/app/assets/javascripts/thyme/attributes.js index 85207f8ed..3882d4a5b 100644 --- a/app/assets/javascripts/thyme/attributes.js +++ b/app/assets/javascripts/thyme/attributes.js @@ -10,6 +10,9 @@ const thymeAttributes = { /* Saves a reference on the annotation area. */ annotationArea: null, + /* Use this to check if the annotation feature is activated. */ + annotationFeatureActive: false, + /* When callig the updateMarkers() method this will be used to save an array containing all annotations. */ annotations: null, diff --git a/app/assets/javascripts/thyme/components/annotations_toggle.js b/app/assets/javascripts/thyme/components/annotations_toggle.js index 684e990b1..4e9b868dc 100644 --- a/app/assets/javascripts/thyme/components/annotations_toggle.js +++ b/app/assets/javascripts/thyme/components/annotations_toggle.js @@ -11,7 +11,11 @@ class AnnotationsToggle extends Component { add() { const toggle = this; const $check = this.$check; - + + if (!thymeAttributes.annotationFeatureActive) { + return; + } + /* User is teacher/editor for the given medium and visible_for_teacher ist activated? -> add toggle annotations button */ $.ajax(Routes.check_annotation_visibility_path(thymeAttributes.mediumId), { diff --git a/app/assets/javascripts/thyme/components/ia_button.js b/app/assets/javascripts/thyme/components/ia_button.js index 429b47c92..542274acf 100644 --- a/app/assets/javascripts/thyme/components/ia_button.js +++ b/app/assets/javascripts/thyme/components/ia_button.js @@ -6,18 +6,18 @@ class IaButton extends Component { /* - toHide = An array consisting of all the components that - should be hidden/shown when this button is clicked. - These components must provide a show() and hide() - method, but they havn't to be a JQuery reference - on a HTML element. + toHide = An array consisting of all the components that + should be hidden/shown when this button is clicked. + These components must provide a show() and hide() + method, but they havn't to be a JQuery reference + on a HTML element. - toShrink = An array consisting of JQuery references of all - the components that should grow/shrink when this - button is clicked. + toShrink = An array consisting of JQuery references of all + the components that should grow/shrink when this + button is clicked. - shrink = The percentage telling how much the elements of toShrink - should shrink when the components of toHide are shown. + shrink = The percentage telling how much the elements of toShrink + should shrink when the components of toHide are shown. */ constructor(element, toHide, toShrink, shrink) { super(element); @@ -27,10 +27,8 @@ class IaButton extends Component { } add() { - const video = thymeAttributes.video; const element = this.element; const button = this; - const shrink = this.shrink; element.addEventListener('click', function() { if (element.dataset.status === 'true') { @@ -46,16 +44,7 @@ class IaButton extends Component { toHide elements and shrinks all toShrink elements. */ plus() { - this.element.dataset.status = 'false'; - this.element.innerHTML = 'remove_from_queue'; - for (let e of this.toHide) { - e.hide(); - } - for (let e of this.toShrink) { - e.css('width', '100%'); - } - $(window).trigger('resize'); - thymeAttributes.annotationManager.updateMarkers(); + this.#aux('false', 'remove_from_queue', false, '100%'); } /* @@ -63,20 +52,26 @@ class IaButton extends Component { toHide elements and enlarges all toShrink elements. */ minus() { - this.element.dataset.status = 'true'; - this.element.innerHTML = 'add_to_queue'; + this.#aux('true', 'add_to_queue', true, this.shrink); + } + + getStatus() { + return this.element.dataset.status === 'true'; + } + + + + #aux(status, innerHTML, sh, size) { + this.element.dataset.status = status; + this.element.innerHTML = innerHTML; for (let e of this.toHide) { - e.show(); + sh ? e.show() : e.hide(); } for (let e of this.toShrink) { - e.css('width', this.shrink); + e.css('width', size); } $(window).trigger('resize'); thymeAttributes.annotationManager.updateMarkers(); } - getStatus() { - return this.element.dataset.status === 'true'; - } - } diff --git a/app/assets/javascripts/thyme/thyme_player.js b/app/assets/javascripts/thyme/thyme_player.js index 656909694..5193e3ee4 100644 --- a/app/assets/javascripts/thyme/thyme_player.js +++ b/app/assets/javascripts/thyme/thyme_player.js @@ -20,13 +20,19 @@ $(document).on('turbolinks:load', function() { - /* COMPONENTS */ + // annotation components + const annotationFeatureActive = (document.getElementById('annotation-button') != null); + thymeAttributes.annotationFeatureActive = annotationFeatureActive; + if (annotationFeatureActive) { + (new AnnotationButton('annotation-button')).add(); + } const annotationsToggle = new AnnotationsToggle('annotations-toggle'); annotationsToggle.add(); - (new AnnotationButton('annotation-button')).add(); + + // regular components (new FullScreenButton('full-screen', thymeContainer)).add(); (new MuteButton('mute')).add(); (new NextChapterButton('next-chapter')).add(); diff --git a/app/models/lecture.rb b/app/models/lecture.rb index 35515faad..51ce0e501 100644 --- a/app/models/lecture.rb +++ b/app/models/lecture.rb @@ -819,6 +819,12 @@ def stale? older_than?(1.year) end + def valid_annotations_status? + [-1, 1].include?(self.annotations_status) + end + + + private # used for after save callback @@ -922,8 +928,5 @@ def only_one_lecture errors.add(:course, :already_present) end - - def valid_annotations_status? - [-1, 1].include?(self.annotations_status) - end + end diff --git a/app/models/medium.rb b/app/models/medium.rb index 4cd63cdfc..3bb081a17 100644 --- a/app/models/medium.rb +++ b/app/models/medium.rb @@ -1125,7 +1125,7 @@ def subscribed_users Lecture.find_by(id: teachable.lecture_id).user_ids end - + # Returns either the annotations status (1 = activated, -1 = deactivated) # of this medium or the annotations status of the associated lecture # if "inherit from lecture" was selected (i.e. if the annotations status of @@ -1134,14 +1134,20 @@ def get_annotations_status return annotations_status if annotations_status != 0 lecture.annotations_status end - + def annotations_visible?(user) lecture = lesson.lecture unless lesson.nil? is_teacher = edited_with_inheritance_by?(user) is_activated = (get_annotations_status == 1) return is_teacher && is_activated end - + + def valid_annotations_status? + [-1, 0, 1].include?(self.annotations_status) + end + + + private # media of type kaviar associated to a lesson and script do not require @@ -1315,8 +1321,5 @@ def answers_count becomes(Question).answers.count end - - def valid_annotations_status? - [-1, 0, 1].include?(self.annotations_status) - end + end diff --git a/app/views/media/play.html.erb b/app/views/media/play.html.erb index 9e53f981b..8c07891b4 100644 --- a/app/views/media/play.html.erb +++ b/app/views/media/play.html.erb @@ -62,11 +62,13 @@
-
- - bookmark_add - -
+ <% if user_signed_in? %> +
+ + bookmark_add + +
+ <% end %>
-<%= render partial: "annotations/annotation_modal" %> -<%= render partial: "annotations/annotation_locales" %> +<% if user_signed_in? %> + <%= render partial: "annotations/annotation_modal" %> + <%= render partial: "annotations/annotation_locales" %> +<% end %> \ No newline at end of file From 8cd43416be24806421d569a50216054173021abc Mon Sep 17 00:00:00 2001 From: Splines Date: Sun, 12 Nov 2023 16:19:37 +0100 Subject: [PATCH 192/291] Reduce complexity of small/large display checks Also removed deprecated device-width, see: https://stackoverflow.com/a/18500871/ --- .../javascripts/thyme/display_manager.js | 100 +++--------------- 1 file changed, 15 insertions(+), 85 deletions(-) diff --git a/app/assets/javascripts/thyme/display_manager.js b/app/assets/javascripts/thyme/display_manager.js index 7fac4e32f..a65985242 100644 --- a/app/assets/javascripts/thyme/display_manager.js +++ b/app/assets/javascripts/thyme/display_manager.js @@ -16,8 +16,6 @@ class DisplayManager { this.onEnlarge = onEnlarge; } - - // on small display, fall back to standard browser player adaptToSmallDisplay() { for (let e of this.elements) { @@ -38,93 +36,25 @@ class DisplayManager { // Check screen size and trigger the right method updateControlBarType() { - const dm = this; - - if (window.matchMedia("screen and (max-width: " + - thymeAttributes.hideControlBarThreshold.x + "px)").matches || - window.matchMedia("screen and (max-height: " + - thymeAttributes.hideControlBarThreshold.y + "px)").matches) { - dm.adaptToSmallDisplay(); - } + const manager = this; - if (window.matchMedia("screen and (max-device-width: " + - thymeAttributes.hideControlBarThreshold.x + "px)").matches || - window.matchMedia("screen and (max-device-height: " + - thymeAttributes.hideControlBarThreshold.y + "px)").matches) { - dm.adaptToSmallDisplay(); - } - - // mediaQuery listener for very small screens - const matchVerysmallX = window.matchMedia("screen and (max-width: " + - thymeAttributes.hideControlBarThreshold.x + "px)"); - matchVerysmallX.addListener(function (result) { - if (result.matches) { - dm.adaptToSmallDisplay(); - } - }); - const matchVerysmallY = window.matchMedia("screen and (max-height: " + - thymeAttributes.hideControlBarThreshold.y + "px)"); - matchVerysmallY.addListener(function (result) { - if (result.matches) { - dm.adaptToSmallDisplay(); - } - }); + const matchSmallMediaQuery = window.matchMedia(` + screen and ( + (max-width: ${thymeAttributes.hideControlBarThreshold.x}px) + or (max-height: ${thymeAttributes.hideControlBarThreshold.y}px) + ) + `); - const matchVerysmalldeviceX = window.matchMedia("screen and (max-device-width: " + - thymeAttributes.hideControlBarThreshold.x + "px)"); - matchVerysmalldeviceX.addListener(function (result) { - if (result.matches) { - dm.adaptToSmallDisplay(); + function handleSizeChange(event) { + if (event.matches) { + manager.adaptToSmallDisplay(); + } else { + manager.adaptToLargeDisplay(); } - }); - const matchVerysmalldeviceY = window.matchMedia("screen and (max-device-height: " + - thymeAttributes.hideControlBarThreshold.y + "px)"); - matchVerysmalldeviceY.addListener(function (result) { - if (result.matches) { - dm.adaptToSmallDisplay(); - } - }); - - // mediaQuery listener for normal screens - let matchNormalX = window.matchMedia("screen and (min-width: " + - (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); - matchNormalX.addListener(function (result) { - let matchNormalY; - matchNormalY = window.matchMedia("screen and (min-height: " + - (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); - if (result.matches && matchNormalY.matches) { - dm.adaptToLargeDisplay(); - } - }); - const matchNormalY = window.matchMedia("screen and (min-height: " + - (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); - matchNormalY.addListener(function (result) { - matchNormalX = window.matchMedia("screen and (min-width: " + - (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); - if (result.matches && matchNormalX.matches) { - dm.adaptToLargeDisplay(); - } - }); + } - let matchNormaldeviceX = window.matchMedia("screen and (min-device-width: " + - (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); - let matchNormaldeviceY; - matchNormaldeviceX.addListener(function (result) { - matchNormaldeviceY = window.matchMedia("screen and (min-device-height: " + - (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); - if (result.matches && matchNormalY.matches) { - dm.adaptToLargeDisplay(); - } - }); - matchNormaldeviceY = window.matchMedia("screen and (min-device-height: " + - (thymeAttributes.hideControlBarThreshold.y + 1) + "px)"); - matchNormaldeviceY.addListener(function (result) { - matchNormaldeviceX = window.matchMedia("screen and (min-device-width: " + - (thymeAttributes.hideControlBarThreshold.x + 1) + "px)"); - if (result.matches && matchNormalX.matches) { - dm.adaptToLargeDisplay(); - } - }); + matchSmallMediaQuery.addListener(handleSizeChange); + handleSizeChange(matchSmallMediaQuery); // initial call } }; From 958af8fdd9da3f8840ecdb8164e2a23d6a6ff85b Mon Sep 17 00:00:00 2001 From: Splines Date: Sun, 12 Nov 2023 16:24:47 +0100 Subject: [PATCH 193/291] Implement locking to prevent unnecessary DB calls The AnnotationManager should handle its internal state on its own. Therefore, updateMarkers() now checks if annotations are null and if they are, it calls updateAnnotations() accordingly, but only if no other method has already called updateMarkers recently, e.g. when multiple resize events are fired in a small period of time. Resource is freed in updateAnnotations. --- .../thyme/annotations/annotation_manager.js | 25 +++++++++++++++---- .../javascripts/thyme/thyme_feedback.js | 6 +---- app/assets/javascripts/thyme/thyme_player.js | 6 +---- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/thyme/annotations/annotation_manager.js b/app/assets/javascripts/thyme/annotations/annotation_manager.js index 19b43f0d8..3e5ab05d0 100644 --- a/app/assets/javascripts/thyme/annotations/annotation_manager.js +++ b/app/assets/javascripts/thyme/annotations/annotation_manager.js @@ -33,10 +33,9 @@ class AnnotationManager { this.onClick = onClick; this.onUpdate = onUpdate; this.isValid = isValid; + this.isDbCalledForFreshAnnotations = false; } - - /* Updates the markers on the timeline, i.e. the visual represention of the annotations. This method is e.g. used for rearranging the markers when the window is being resized. @@ -44,9 +43,19 @@ class AnnotationManager { in the database. */ updateMarkers() { + // In case the annotations have not been loaded yet, do nothing. + // This situation might occur during the initial page load. + if (!thymeAttributes.annotations) { + if (!this.isDbCalledForFreshAnnotations) { + this.updateAnnotations(); + } + return; + } + const annotationManager = this; $('#' + thymeAttributes.markerBarId).empty(); AnnotationManager.sortAnnotations(); + for (const a of thymeAttributes.annotations) { if (this.isValid(a)) { function onClick() { @@ -72,14 +81,16 @@ class AnnotationManager { successfully updated. */ updateAnnotations() { - const annotationManager = this; + this.isDbCalledForFreshAnnotations = true; // Lock resource + + const manager = this; $.ajax(Routes.update_annotations_path(), { type: 'GET', dataType: 'json', data: { mediumId: thymeAttributes.mediumId, }, - success: function(annotations) { + success: (annotations) => { // update the annotation field in thymeAttributes thymeAttributes.annotations = []; if (!annotations) { @@ -89,7 +100,11 @@ class AnnotationManager { thymeAttributes.annotations.push(new Annotation(a)); } // update visual representation on the seek bar - annotationManager.updateMarkers(); + manager.updateMarkers(); + }, + always: () => { + // Free resource + manager.isDbCalledForFreshAnnotations = false; } }); } diff --git a/app/assets/javascripts/thyme/thyme_feedback.js b/app/assets/javascripts/thyme/thyme_feedback.js index b7eaae1e9..3f4e5e326 100644 --- a/app/assets/javascripts/thyme/thyme_feedback.js +++ b/app/assets/javascripts/thyme/thyme_feedback.js @@ -106,11 +106,7 @@ $(document).on('turbolinks:load', function() { // resizes the thyme container to the window dimensions function resizeContainer() { resize.resizeContainer(thymeContainer, 1.22, 70); - if (!thymeAttributes.annotations) { - annotationManager.updateAnnotations(); - } else { - annotationManager.updateMarkers(); - } + annotationManager.updateMarkers(); }; window.onresize = resizeContainer; video.onloadedmetadata = resizeContainer; diff --git a/app/assets/javascripts/thyme/thyme_player.js b/app/assets/javascripts/thyme/thyme_player.js index 18e02ec27..07c3c2cb3 100644 --- a/app/assets/javascripts/thyme/thyme_player.js +++ b/app/assets/javascripts/thyme/thyme_player.js @@ -152,11 +152,7 @@ $(document).on('turbolinks:load', function() { function resizeContainer() { const factor = $('#caption').is(':hidden') && $('#annotation-caption').is(':hidden') ? 1 : 1 / 0.82; resize.resizeContainer(thymeContainer, factor, 0); - if (!thymeAttributes.annotations) { - annotationManager.updateAnnotations(); - } else { - annotationManager.updateMarkers(); - } + annotationManager.updateMarkers(); }; window.onresize = resizeContainer; video.onloadedmetadata = resizeContainer; From 8fcc2389dd4d3c946a328bfd2789b002285ce277 Mon Sep 17 00:00:00 2001 From: Splines Date: Sun, 12 Nov 2023 17:08:08 +0100 Subject: [PATCH 194/291] Rewrite handling of annotation update --- app/assets/javascripts/thyme/thyme_player.js | 9 --------- app/views/annotations/_annotation_modal.html.erb | 4 +--- app/views/annotations/update.js | 3 ++- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/thyme/thyme_player.js b/app/assets/javascripts/thyme/thyme_player.js index 7a80c6f26..f1a05f9cb 100644 --- a/app/assets/javascripts/thyme/thyme_player.js +++ b/app/assets/javascripts/thyme/thyme_player.js @@ -99,15 +99,6 @@ $(document).on('turbolinks:load', function() { onClick, onUpdate, isValid); thymeAttributes.annotationManager = annotationManager; - // Update annotations after submitting the annotations form - $('#annotation-update').on('DOMSubtreeModified', function() { - const updateDataset = document.querySelector('#annotation-update').dataset; - if (updateDataset.update === "updated") { - updateDataset.update = ""; - annotationManager.updateAnnotations(); - } - }); - // Update annotations after deleting an annotation $(document).on('click', '#delete-button', function() { const annotationId = Number(document.getElementById('annotation_id').textContent); diff --git a/app/views/annotations/_annotation_modal.html.erb b/app/views/annotations/_annotation_modal.html.erb index 4cd7e63e8..b44abe9c3 100644 --- a/app/views/annotations/_annotation_modal.html.erb +++ b/app/views/annotations/_annotation_modal.html.erb @@ -24,7 +24,5 @@
+ - - \ No newline at end of file diff --git a/app/views/annotations/update.js b/app/views/annotations/update.js index 3df69e3ef..2cc509f01 100644 --- a/app/views/annotations/update.js +++ b/app/views/annotations/update.js @@ -1,3 +1,4 @@ $('#annotation-modal').modal("hide"); -document.querySelector('#annotation-update').dataset.update = "updated"; \ No newline at end of file +// Update annotations after submitting the annotations form +thymeAttributes.annotationManager.updateAnnotations(); From 741267f6e728d6eb450531ada30405db59d70e13 Mon Sep 17 00:00:00 2001 From: Splines Date: Sun, 12 Nov 2023 17:45:12 +0100 Subject: [PATCH 195/291] Use gender-neutral pronoun --- config/locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index ebf84ef03..cc1609f40 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -384,7 +384,7 @@ en: visible_for_teacher: 'Visible for teacher?' visible_for_teacher_helpdesk: > If this checkbox is selected your teacher can see this annotation - (without your name!) in his own thyme player. + (without your name!) in their own thyme player. post_as_comment: 'Post as comment?' post_as_comment_helpdesk: > If this checkbox is selected, this annotation will be published as From 0a426d1b17b9e6b833bc75befa1dfd3769664a6a Mon Sep 17 00:00:00 2001 From: Splines Date: Mon, 13 Nov 2023 19:20:38 +0100 Subject: [PATCH 196/291] Replace alert() by Bootstrap alert Used for "Post as comment" warnings. --- app/views/annotations/_form.html.erb | 3 ++ app/views/annotations/edit.js.erb | 65 +++++++++++++++------------- config/locales/de.yml | 4 +- config/locales/en.yml | 6 +-- 4 files changed, 43 insertions(+), 35 deletions(-) diff --git a/app/views/annotations/_form.html.erb b/app/views/annotations/_form.html.erb index e25155275..0716d9325 100644 --- a/app/views/annotations/_form.html.erb +++ b/app/views/annotations/_form.html.erb @@ -130,6 +130,9 @@ <%= helpdesk(t('admin.annotation.post_as_comment_helpdesk'), false) %> + +