mumuki-laboratory 9.16.0 → 9.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/mumuki_laboratory/application/csrf-token.js +5 -5
  3. data/app/assets/javascripts/mumuki_laboratory/application/discussions.js +1 -0
  4. data/app/assets/javascripts/mumuki_laboratory/application/exercise.js +17 -2
  5. data/app/assets/javascripts/mumuki_laboratory/application/timer.js +2 -5
  6. data/app/controllers/application_controller.rb +1 -1
  7. data/app/controllers/exam_authorization_requests_controller.rb +22 -17
  8. data/app/controllers/exam_registrations_controller.rb +1 -1
  9. data/app/controllers/exercises_controller.rb +0 -1
  10. data/app/helpers/exams_helper.rb +33 -0
  11. data/app/helpers/exercise_input_helper.rb +0 -4
  12. data/app/helpers/menu_bar_helper.rb +5 -4
  13. data/app/helpers/notifications_helper.rb +1 -1
  14. data/app/helpers/time_zone_helper.rb +5 -1
  15. data/app/views/book/show.html.erb +4 -2
  16. data/app/views/discussions/_actions.html.erb +1 -0
  17. data/app/views/discussions/_basic_actions.html.erb +20 -0
  18. data/app/views/discussions/_message.html.erb +4 -0
  19. data/app/views/discussions/new.html.erb +7 -1
  20. data/app/views/discussions/show.html.erb +1 -18
  21. data/app/views/exam_registrations/show.html.erb +1 -1
  22. data/app/views/exercises/_read_only.html.erb +54 -60
  23. data/app/views/exercises/show.html.erb +2 -1
  24. data/app/views/guides/_guide.html.erb +1 -1
  25. data/app/views/layouts/_timer.html.erb +1 -1
  26. data/lib/mumuki/laboratory/controllers/validate_access_mode.rb +5 -0
  27. data/lib/mumuki/laboratory/locales/en.yml +8 -2
  28. data/lib/mumuki/laboratory/locales/es-CL.yml +8 -2
  29. data/lib/mumuki/laboratory/locales/es.yml +8 -2
  30. data/lib/mumuki/laboratory/locales/pt.yml +8 -2
  31. data/lib/mumuki/laboratory/version.rb +1 -1
  32. data/spec/controllers/exam_authorization_requests_controller_spec.rb +16 -2
  33. data/spec/controllers/exercises_controller_spec.rb +32 -0
  34. data/spec/dummy/db/schema.rb +2 -1
  35. data/spec/dummy/db/seeds.rb +16 -8
  36. data/spec/features/read_only_flow_spec.rb +14 -16
  37. metadata +107 -102
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0973e0c9ab8333b9b2f4207d9cc40e3c88199c67fcee8f35415b802d0494a85a'
4
- data.tar.gz: 7a7c28856ecb1751c25a053fe0db0eaf0607a8c1dbaa22c70071eeb26617d2ba
3
+ metadata.gz: 74909716a7b8701adb5f410a2fded18a19779547b107635965361cf2247c7595
4
+ data.tar.gz: ac32359e5add8298400567f10d1d1fcf191db92eeacaa258d33387b6d9433de9
5
5
  SHA512:
6
- metadata.gz: 9db401ba5aa815447c273ab82597ee16c98cd0d13ca5f68720f3190114fa69f40f9195ac479d5f4fc234ef3cdfcc7fe7c2e11c8b6d2cd59a9be9194b596d2096
7
- data.tar.gz: b06e42055b04fa8116ce5053e842d63a6c56e8a38c1c38d8b3ed2c23688ae0f0925f8208ece9c765d8987d0f3e6963cb8dcc8faa5ab11dc4e544c95af444d4a3
6
+ metadata.gz: 33cac73574865ca539d574765c2b85c57da593b11078e92f2c17cd47b51382f33840083458c396893ac3ed0227f068fdfab9e1fc83ab7556186ca901c2c6ba50
7
+ data.tar.gz: dae03d18dfd66681d899d3546bc98aa36b88e1d85947202f032f1a7972c8cc3adb33e4cda12795a46719445530fb1aeff4d2783cae4234836f234f163a45cacf
@@ -1,12 +1,12 @@
1
1
  mumuki.CsrfToken = (() => {
2
2
  class CsrfToken {
3
- constructor() {
4
- this.value = $('meta[name="csrf-token"]').attr('content');
3
+ get token() {
4
+ return $('meta[name="csrf-token"]').attr('content');
5
5
  }
6
+
6
7
  newRequest(data) {
7
- var self = this;
8
- data.beforeSend = function (xhr) {
9
- xhr.setRequestHeader('X-CSRF-Token', self.value);
8
+ data.beforeSend = (xhr) => {
9
+ xhr.setRequestHeader('X-CSRF-Token', this.token);
10
10
  };
11
11
  return data;
12
12
  }
@@ -70,6 +70,7 @@ mumuki.load(() => {
70
70
  discussionMessageToggleApprove: function (url, elem) {
71
71
  Forum.discussionPost(url).done(function () {
72
72
  elem.toggleClass("selected");
73
+ elem.attr('data-bs-original-title', '');
73
74
  });
74
75
  },
75
76
  discussionMessageToggleNotActuallyAQuestion: function (url, elem) {
@@ -1,12 +1,20 @@
1
1
  (() => {
2
+ function shouldSkipChangesCheck() {
3
+ return mumuki.isKidsExercise() || mumuki.exercise.isReadOnly;
4
+ }
5
+
2
6
  function solutionChangedSinceLastSubmission() {
3
7
  return mumuki.exercise.id &&
4
8
  mumuki.SubmissionsStore.getLastSubmissionAndResult(mumuki.exercise.id) &&
5
9
  !mumuki.SubmissionsStore.getSubmissionResultFor(mumuki.exercise.id, mumuki.editors.getSubmission());
6
10
  }
7
11
 
12
+ function shouldWarnOfChanges() {
13
+ return !shouldSkipChangesCheck() && solutionChangedSinceLastSubmission();
14
+ }
15
+
8
16
  window.addEventListener("beforeunload", (event) => {
9
- if (solutionChangedSinceLastSubmission()) {
17
+ if (shouldWarnOfChanges()) {
10
18
  event.returnValue = 'unsaved_progress';
11
19
  } else {
12
20
  delete event['returnValue'];
@@ -14,7 +22,7 @@
14
22
  });
15
23
 
16
24
  window.addEventListener("turbolinks:before-visit", (event) => {
17
- if (solutionChangedSinceLastSubmission() && !confirm(mumuki.I18n.t('unsaved_progress'))) event.preventDefault();
25
+ if (shouldWarnOfChanges() && !confirm(mumuki.I18n.t('unsaved_progress'))) event.preventDefault();
18
26
  });
19
27
  })();
20
28
 
@@ -66,6 +74,13 @@ mumuki.exercise = {
66
74
  return this._current;
67
75
  },
68
76
 
77
+ /**
78
+ * @type {Boolean?}
79
+ */
80
+ get isReadOnly() {
81
+ return $('#mu-exercise-read-only').val() === 'true';
82
+ },
83
+
69
84
  /**
70
85
  * Set global current exercise information
71
86
  */
@@ -1,9 +1,6 @@
1
1
  mumuki.startTimer = (() => {
2
- function startTimer(endDate) {
3
- var endTime = new Date(endDate).getTime();
4
- var currentTime = Date.now();
5
- var diffTime = endTime - currentTime;
6
- var duration = moment.duration(diffTime, 'milliseconds');
2
+ function startTimer(seconds) {
3
+ var duration = moment.duration(seconds, 'seconds');
7
4
  var intervalDuration = 1000;
8
5
 
9
6
  var interval = mumuki.setInterval(function () {
@@ -68,7 +68,7 @@ class ApplicationController < ActionController::Base
68
68
  # ensures contents are accessible to current user
69
69
  def validate_accessible!
70
70
  return if current_user&.teacher_here?
71
- accessible_subject.validate_accessible_for! current_user
71
+ accessible_subject&.validate_accessible_for! current_user
72
72
  end
73
73
 
74
74
  def validate_active_organization!
@@ -1,36 +1,41 @@
1
1
  class ExamAuthorizationRequestsController < ApplicationController
2
2
 
3
- before_action :verify_registration_opened!, on: [:create, :update]
3
+ before_action :set_registration!
4
+ before_action :set_exam!
5
+ before_action :verify_registration_opened!
4
6
 
5
7
  def create
6
- authorization_request = ExamAuthorizationRequest.find_or_create_by! create_authorization_request_params do |it|
7
- it.assign_attributes authorization_request_params
8
- end
9
- current_user.read_notification! authorization_request.exam_registration
10
- flash.notice = I18n.t :exam_authorization_request_created
11
- redirect_to root_path
8
+ authorization_request = @registration.request_authorization! current_user, @exam
9
+ current_user.read_notification! @registration
10
+ flash.notice = friendly_flash_notice(:exam_authorization_request_created)
11
+ redirect_to exam_authorizations_user_path
12
12
  end
13
13
 
14
14
  def update
15
- ExamAuthorizationRequest.update params[:id], authorization_request_params
16
- flash.notice = I18n.t :exam_authorization_request_saved
17
- redirect_to root_path
15
+ @registration.update_authorization_request_by_id! params[:id], @exam
16
+ flash.notice = friendly_flash_notice(:exam_authorization_request_saved)
17
+ redirect_to exam_authorizations_user_path
18
18
  end
19
19
 
20
20
  private
21
21
 
22
- def create_authorization_request_params
23
- authorization_request_params.slice :exam_registration_id, :user, :organization
22
+ def friendly_flash_notice(key)
23
+ I18n.t key, friendly_date: helpers.local_time(@exam.start_time)
24
24
  end
25
25
 
26
26
  def authorization_request_params
27
- params
28
- .require(:exam_authorization_request).permit(:exam_id, :exam_registration_id)
29
- .merge(user: current_user, organization: Organization.current)
27
+ params.require(:exam_authorization_request).permit(:exam_id, :exam_registration_id)
28
+ end
29
+
30
+ def set_registration!
31
+ @registration = Organization.current.exam_registrations.find(authorization_request_params[:exam_registration_id])
32
+ end
33
+
34
+ def set_exam!
35
+ @exam = @registration.exams.find(authorization_request_params[:exam_id])
30
36
  end
31
37
 
32
38
  def verify_registration_opened!
33
- exam_registration = ExamRegistration.find(authorization_request_params[:exam_registration_id])
34
- raise Mumuki::Domain::GoneError if exam_registration.ended?
39
+ raise Mumuki::Domain::GoneError if @registration.ended?
35
40
  end
36
41
  end
@@ -1,6 +1,6 @@
1
1
  class ExamRegistrationsController < ApplicationController
2
2
  def show
3
- @registration = ExamRegistration.find(params[:id])
3
+ @registration = Organization.current.exam_registrations.find(params[:id])
4
4
  @authorization_request = @registration.authorization_request_for(current_user)
5
5
  end
6
6
  end
@@ -6,7 +6,6 @@ class ExercisesController < ApplicationController
6
6
 
7
7
  before_action :set_guide!, only: :show
8
8
  before_action :set_assignment!, only: :show, if: :current_user?
9
- before_action :validate_accessible!, only: :show
10
9
  before_action :start!, only: :show
11
10
 
12
11
  def show
@@ -0,0 +1,33 @@
1
+ module ExamsHelper
2
+ def exam_information_for(user, exam)
3
+ %Q{
4
+ #{course_information_for(user, exam)}
5
+ #{date_information_for(exam)}
6
+ #{duration_information_for(exam)}
7
+ }.html_safe
8
+ end
9
+
10
+ private
11
+
12
+ def course_information_for(user, exam)
13
+ if user.teacher_here?
14
+ "#{fa_icon('graduation-cap', class: 'fa-fw',
15
+ text: "<strong>#{t :course}:</strong> #{exam.course.canonical_code}".html_safe)}
16
+ <br>"
17
+ end
18
+ end
19
+
20
+ def date_information_for(exam)
21
+ "#{fa_icon('calendar-alt', class: 'fa-fw',
22
+ text: "<strong>#{t :date_and_time}:</strong> #{local_time_without_time_zone(exam.start_time)} - #{local_time(exam.end_time)}".html_safe)}
23
+ <br>"
24
+ end
25
+
26
+ def duration_information_for(exam)
27
+ if exam.duration?
28
+ "#{fa_icon(:stopwatch, class: 'fa-fw',
29
+ text: "<strong>#{t :available_time}:</strong> #{t :time_in_minutes, time: exam.duration}".html_safe)}
30
+ <br>"
31
+ end
32
+ end
33
+ end
@@ -35,10 +35,6 @@ module ExerciseInputHelper
35
35
  should_render_exercise_tabs?(exercise) { exercise.has_messages_for? user }
36
36
  end
37
37
 
38
- def should_render_read_only_exercise_tabs?(discussion)
39
- should_render_exercise_tabs?(discussion.exercise) { discussion.has_submission? }
40
- end
41
-
42
38
  def should_render_message_input?(exercise, organization = Organization.current)
43
39
  exercise.is_a?(Problem) && !exercise.hidden? && organization.raise_hand_enabled?
44
40
  end
@@ -38,12 +38,13 @@ module MenuBarHelper
38
38
  li_tag menu_item('sign-out-alt', :sign_out, logout_path(origin: url_for, organization: Organization.current))
39
39
  end
40
40
 
41
- def menu_item(icon, name, url, css_class = nil, **translation_params)
42
- menu_text_item(icon, t(name, translation_params), url, css_class)
41
+ def menu_item(icon, name, url, css_class = nil, translation_params = {}, options = {})
42
+ menu_text_item(icon, t(name, translation_params), url, css_class, **options)
43
43
  end
44
44
 
45
- def menu_text_item(icon, text, url, css_class = nil)
46
- link_to fixed_fa_icon(icon, text: text), url, role: 'menuitem', tabindex: '-1', class: "dropdown-item #{css_class}"
45
+ def menu_text_item(icon, text, url, css_class = nil, **options)
46
+ link_options = { role: 'menuitem', tabindex: '-1', class: "dropdown-item #{css_class}" }.merge(options)
47
+ link_to fixed_fa_icon(icon, text: text), url, link_options
47
48
  end
48
49
 
49
50
  def any_menu_bar_links?
@@ -4,7 +4,7 @@ module NotificationsHelper
4
4
  end
5
5
 
6
6
  def notification_preview_item(icon, name, url, **translation_params)
7
- menu_item icon, name, url, 'mu-notification-preview', **translation_params
7
+ menu_item icon, name, url, 'mu-notification-preview', translation_params
8
8
  end
9
9
 
10
10
  def notification_text_preview_item(icon, text, url)
@@ -1,5 +1,9 @@
1
1
  module TimeZoneHelper
2
2
  def local_time(time, time_zone = Time.zone.name)
3
- "#{l(time.in_time_zone(time_zone), format: :long)} (#{time_zone})"
3
+ "#{local_time_without_time_zone(time, time_zone)} (#{time_zone})"
4
+ end
5
+
6
+ def local_time_without_time_zone(time, time_zone = Time.zone.name)
7
+ l(time.in_time_zone(time_zone), format: :long)
4
8
  end
5
9
  end
@@ -45,8 +45,10 @@
45
45
  <h2><%= t(:exams) %></h2>
46
46
  <% @exams.each_with_index do |it, index| %>
47
47
  <div class="chapter">
48
- <h3> <%= index + 1 %>. <%= link_to_path_element it, mode: :plain %></h3>
49
-
48
+ <h3><%= link_to_path_element it, mode: :plain %></h3>
49
+ <div class="fs-7">
50
+ <%= exam_information_for(current_user, it) %>
51
+ </div>
50
52
  <div class="text-box">
51
53
  <%= it.guide.description_teaser_html %>
52
54
  </div>
@@ -0,0 +1 @@
1
+ <%= render partial: 'discussions/basic_actions' %>
@@ -0,0 +1,20 @@
1
+ <% if @discussion.can_toggle_responsible?(current_user) %>
2
+ <div class="discussion-responsible me-1">
3
+ <%= btn_toggle responsible_icon, not_responsible_icon, @discussion.any_responsible?, class: 'btn-sm',
4
+ onclick: "mumuki.Forum.discussionResponsible('#{responsible_discussion_url(@discussion)}')" %>
5
+ </div>
6
+ <% end %>
7
+
8
+ <% if @discussion.subscribable? %>
9
+ <div class="discussion-subscription me-1">
10
+ <%= btn_toggle subscription_icon, unsubscription_icon, current_user.subscribed_to?(@discussion), class: 'btn-sm',
11
+ onclick: "mumuki.Forum.discussionSubscription('#{subscription_discussion_url(@discussion)}')" %>
12
+ </div>
13
+ <% end %>
14
+
15
+ <% if @discussion.solved? %>
16
+ <div class="discussion-upvote">
17
+ <%= btn_toggle upvote_icon, undo_upvote_icon, current_user.upvoted?(@discussion), class: 'btn-sm',
18
+ onclick: "mumuki.Forum.discussionUpvote('#{upvote_discussion_url(@discussion)}')" %>
19
+ </div>
20
+ <% end %>
@@ -13,6 +13,10 @@
13
13
  <% if message.authorized?(current_user) && !message.deleted? %>
14
14
  <% if current_user&.moderator_here? %>
15
15
  <a class="discussion-message-approved <%= 'selected' if message.approved? %>"
16
+ <% if message.approved? && current_user&.forum_supervisor_here? %>
17
+ data-bs-toggle="tooltip" data-bs-placement="left"
18
+ title="<%= t :approved_by, validator: message.approved_by.name, date: local_time(message.approved_at) %>"
19
+ <% end %>
16
20
  onclick="mumuki.Forum.discussionMessageToggleApprove('<%= approve_discussion_message_url(@discussion, message) %>', $(this))">
17
21
  <%= fa_icon(:check, class: 'fa-lg') %>
18
22
  </a>
@@ -22,12 +22,18 @@
22
22
  <div class="container-fluid">
23
23
  <div class="row">
24
24
  <div class="discussion-new-message-content">
25
- <%= f.editor :description, '', {id: 'discussion-new-message', class: 'form-control', placeholder: t(:discussion_description_placeholder)} %>
25
+ <%= spell_checked_editor 'discussion[description]',
26
+ { id: 'discussion-new-message', placeholder: t(:discussion_description_placeholder) } %>
26
27
  </div>
27
28
  </div>
28
29
  </div>
29
30
  </div>
30
31
  </div>
32
+ <% if faqs_enabled_here? %>
33
+ <div class="fs-7">
34
+ <%= fa_icon('exclamation-triangle', text: t(:only_forum_questions_on_forum, link: link_to_faqs).html_safe) %>
35
+ </div>
36
+ <% end %>
31
37
  <%= f.submit t(:publish_discussion), class: 'btn btn-complementary w-100 discussion-new-message-button' %>
32
38
  <% end %>
33
39
  <% end %>
@@ -15,24 +15,7 @@
15
15
  <h3 class="flex-grow-1 me-3"><%= t :messages %></h3>
16
16
  <% if current_user && @discussion.persisted? && current_access_mode.show_discussion_element? %>
17
17
  <span class="d-flex">
18
- <% if @discussion.can_toggle_responsible?(current_user) %>
19
- <div class="discussion-responsible me-1">
20
- <%= btn_toggle responsible_icon, not_responsible_icon, @discussion.any_responsible?, class: 'btn-sm',
21
- onclick: "mumuki.Forum.discussionResponsible('#{responsible_discussion_url(@discussion)}')" %>
22
- </div>
23
- <% end %>
24
- <% if @discussion.subscribable? %>
25
- <div class="discussion-subscription me-1">
26
- <%= btn_toggle subscription_icon, unsubscription_icon, current_user.subscribed_to?(@discussion), class: 'btn-sm',
27
- onclick: "mumuki.Forum.discussionSubscription('#{subscription_discussion_url(@discussion)}')" %>
28
- </div>
29
- <% end %>
30
- <% if @discussion.solved? %>
31
- <div class="discussion-upvote">
32
- <%= btn_toggle upvote_icon, undo_upvote_icon, current_user.upvoted?(@discussion), class: 'btn-sm',
33
- onclick: "mumuki.Forum.discussionUpvote('#{upvote_discussion_url(@discussion)}')" %>
34
- </div>
35
- <% end %>
18
+ <%= render partial: 'discussions/actions' %>
36
19
  </span>
37
20
  <% end %>
38
21
  </div>
@@ -25,7 +25,7 @@
25
25
  <%= f.hidden_field :exam_registration_id, value: @registration.id %>
26
26
  <div class="mb-4">
27
27
  <%= f.label :exam_id, t(:exam_registration_choose_exam) %>
28
- <% @registration.exams.each do |exam| %>
28
+ <% @registration.available_exams.each do |exam| %>
29
29
  <div class="form-check">
30
30
  <%= f.radio_button(:exam_id, exam.id, id: exam.id, required: true, class: 'form-check-input mu-read-only-input',
31
31
  checked: @authorization_request.exam_id == exam.id) %>
@@ -45,82 +45,76 @@
45
45
  <div class="accordion-body accordion-collapse collapse show" id="mu-discussion-accordion-body"
46
46
  aria-labelledby="#mu-discussion-accordion-header" data-bs-parent="#mu-discussion-accordion">
47
47
 
48
- <% if should_render_read_only_exercise_tabs?(@discussion) %>
49
- <ul class="nav nav-tabs discussion-tabs" role="tablist">
50
- <li role="presentation">
51
- <a class="editor-tab nav-link active" data-bs-target="#solution" aria-controls="solution" role="tab" data-bs-toggle="tab">
52
- <%= t :solution %>
53
- </a>
54
- </li>
55
- <li role="presentation">
56
- <a class="editor-tab nav-link" data-bs-target="#results" aria-controls="results" role="tab" data-bs-toggle="tab">
57
- <%= t :results %>
58
- </a>
59
- </li>
60
- <li role="presentation">
61
- <a class="editor-tab nav-link" data-bs-target="#content" aria-controls="content" role="tab" data-bs-toggle="tab">
62
- <%= t :description %>
63
- </a>
64
- </li>
48
+ <ul class="nav nav-tabs discussion-tabs" role="tablist">
49
+ <li role="presentation">
50
+ <a class="editor-tab nav-link active" data-bs-target="#solution" aria-controls="solution" role="tab" data-bs-toggle="tab">
51
+ <%= t :solution %>
52
+ </a>
53
+ </li>
54
+ <li role="presentation">
55
+ <a class="editor-tab nav-link" data-bs-target="#results" aria-controls="results" role="tab" data-bs-toggle="tab">
56
+ <%= t :results %>
57
+ </a>
58
+ </li>
59
+ <li role="presentation">
60
+ <a class="editor-tab nav-link" data-bs-target="#content" aria-controls="content" role="tab" data-bs-toggle="tab">
61
+ <%= t :description %>
62
+ </a>
63
+ </li>
65
64
 
66
- <% if exercise.extra_visible? %>
67
- <%= extra_code_tab %>
68
- <% end %>
65
+ <% if exercise.extra_visible? %>
66
+ <%= extra_code_tab %>
67
+ <% end %>
69
68
 
70
- <% if exercise.queriable? %>
71
- <%= console_tab %>
72
- <% end %>
73
- </ul>
69
+ <% if exercise.queriable? %>
70
+ <%= console_tab %>
71
+ <% end %>
72
+ </ul>
74
73
 
75
- <div class="tab-content">
76
- <div role="tabpanel" class="tab-pane mu-input-panel fade show active" id="solution">
77
- <div class="mu-tab-body">
78
- <div class="mu-read-only-editor">
79
- <%= render_exercise_read_only_editor exercise, @discussion.solution %>
80
- </div>
74
+ <div class="tab-content">
75
+ <div role="tabpanel" class="tab-pane mu-input-panel fade show active" id="solution">
76
+ <div class="mu-tab-body">
77
+ <div class="mu-read-only-editor">
78
+ <%= render_exercise_read_only_editor exercise, @discussion.solution %>
81
79
  </div>
82
80
  </div>
81
+ </div>
83
82
 
84
- <div role="tabpanel" class="tab-pane fade" id="results">
85
- <div class="mu-tab-body">
86
- <%= render layout: 'exercise_solutions/contextualization_results_container', locals: { contextualization: @discussion } do %>
87
- <div class="row">
88
- <div class="col-md-12 submission-results">
89
- <%= render partial: 'exercise_solutions/contextualization_results_body',
90
- locals: { contextualization: @discussion, guide_finished_by_solution: false } %>
91
- </div>
83
+ <div role="tabpanel" class="tab-pane fade" id="results">
84
+ <div class="mu-tab-body">
85
+ <%= render layout: 'exercise_solutions/contextualization_results_container', locals: { contextualization: @discussion } do %>
86
+ <div class="row">
87
+ <div class="col-md-12 submission-results">
88
+ <%= render partial: 'exercise_solutions/contextualization_results_body',
89
+ locals: { contextualization: @discussion, guide_finished_by_solution: false } %>
92
90
  </div>
93
- <% end %>
94
- </div>
95
- </div>
96
-
97
- <div role="tabpanel" class="tab-pane fade" id="content">
98
- <div class="mu-tab-body">
99
- <div class="exercise-assignment">
100
- <%= render partial: 'exercises/exercise_assignment', locals: { exercise: exercise } %>
101
91
  </div>
102
- </div>
92
+ <% end %>
103
93
  </div>
94
+ </div>
104
95
 
105
- <div role="tabpanel" class="tab-pane mu-input-panel fade" id="console">
106
- <div class="mu-overlapped-container">
107
- <div class="console">
108
- </div>
109
- <div class="mu-overlapped">
110
- <%= restart_icon %>
111
- </div>
96
+ <div role="tabpanel" class="tab-pane fade" id="content">
97
+ <div class="mu-tab-body">
98
+ <div class="exercise-assignment">
99
+ <%= render partial: 'exercises/exercise_assignment', locals: { exercise: exercise } %>
112
100
  </div>
113
101
  </div>
102
+ </div>
114
103
 
115
- <div role="tabpanel" class="tab-pane mu-input-panel fade mu-elipsis" id="visible-extra">
116
- <%= @discussion.extra_preview_html %>
104
+ <div role="tabpanel" class="tab-pane mu-input-panel fade" id="console">
105
+ <div class="mu-overlapped-container">
106
+ <div class="console">
107
+ </div>
108
+ <div class="mu-overlapped">
109
+ <%= restart_icon %>
110
+ </div>
117
111
  </div>
118
112
  </div>
119
- <% else %>
120
- <div class="exercise-assignment">
121
- <%= render partial: 'exercises/exercise_assignment', locals: { exercise: exercise } %>
113
+
114
+ <div role="tabpanel" class="tab-pane mu-input-panel fade mu-elipsis" id="visible-extra">
115
+ <%= @discussion.extra_preview_html %>
122
116
  </div>
123
- <% end %>
117
+ </div>
124
118
  </div>
125
119
  </div>
126
120
  </div>
@@ -9,7 +9,7 @@
9
9
  <% @stats = @exercise.stats_for(current_user) %>
10
10
 
11
11
  <% if @exercise.navigable_parent.timed? && !current_user.teacher? %>
12
- <%= render partial: 'layouts/timer', locals: {end_time: @exercise.navigable_parent.real_end_time(current_user)} %>
12
+ <%= render partial: 'layouts/timer', locals: { duration: @exercise.navigable_parent.time_left(current_user) } %>
13
13
  <% end %>
14
14
 
15
15
  <% unless @exercise.input_kids? %>
@@ -45,6 +45,7 @@
45
45
  <%= hidden_field_tag "mu-exercise-id", @exercise.id %>
46
46
  <%= hidden_field_tag "mu-exercise-layout", @exercise.layout %>
47
47
  <%= hidden_field_tag "mu-exercise-settings", @exercise.settings.to_json %>
48
+ <%= hidden_field_tag "mu-exercise-read-only", current_access_mode.read_only? %>
48
49
 
49
50
  <div class="d-none" id="processing-template">
50
51
  <div class="bs-callout bs-callout-info">
@@ -3,7 +3,7 @@
3
3
  <%= render partial: 'layouts/authoring', locals: {guide: @guide} %>
4
4
 
5
5
  <% if subject.timed? && @stats.started? && !current_user.teacher? %>
6
- <%= render partial: 'layouts/timer', locals: {end_time: subject.real_end_time(current_user)} %>
6
+ <%= render partial: 'layouts/timer', locals: { duration: subject.time_left(current_user) } %>
7
7
  <% end %>
8
8
 
9
9
  <%= yield if block_given? %>
@@ -4,7 +4,7 @@
4
4
  <i class="timer timer-text text-center"><%= t :time_left %></i>
5
5
  <i id="timer" class="timer timer-text text-center"></i>
6
6
  <script>
7
- mumuki.startTimer('<%= end_time&.iso8601 %>');
7
+ mumuki.startTimer(<%= duration %>);
8
8
  </script>
9
9
  </div>
10
10
  </div>
@@ -7,9 +7,14 @@ module Mumuki::Laboratory::Controllers::ValidateAccessMode
7
7
 
8
8
  def validate_accessible!
9
9
  current_access_mode.validate_content_here! subject_container
10
+ super
10
11
  end
11
12
 
12
13
  def subject_container
13
14
  subject
14
15
  end
16
+
17
+ def accessible_subject
18
+ nil
19
+ end
15
20
  end
@@ -13,6 +13,7 @@ en:
13
13
  all: All
14
14
  appendix: Appendix
15
15
  appendix_teaser: Do you want to learn more? <a href="%{link}">Check this chapter's appendix</a>"
16
+ approved_by: Validated by %{validator} at %{date}
16
17
  approved_message: Validated by mentor
17
18
  approved_messages:
18
19
  other: validated
@@ -30,6 +31,7 @@ en:
30
31
  authoring: Authoring
31
32
  authoring_note_html: This guide's content was developed by %{authors} under the terms of <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">Creative Commons License Share-Alike, 4.0</a>
32
33
  authoring_note_with_collaborators_html: This guide's content was developed by %{authors} and <a href="%{collaborators}" target="_blank">many others</a>, under the terms of <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">Creative Commons License Share-Alike, 4.0</a>
34
+ available_time: Available time
33
35
  back_to_mumuki: Back to Mumuki!
34
36
  bibliotheca_ui: Bibliotheca
35
37
  birthdate: Birthdate
@@ -67,9 +69,11 @@ en:
67
69
  continue_practicing: Continue practicing!
68
70
  corollary: To think about
69
71
  correct_answer: The answer is correct!
72
+ course: Course
70
73
  create_submission: Submit
71
74
  created_at: Created at
72
75
  date: Date
76
+ date_and_time: Date and time
73
77
  deleted_by: Deleted by %{deleter}
74
78
  deleted_message_warning: If you repeateadly violate these or other rules, you may be prohibited from accessing the forum or you may suffer more severe consequences, such as being expulsed from the course.
75
79
  deletion_motive:
@@ -117,9 +121,9 @@ en:
117
121
  errored: Oops, your solution didn't work
118
122
  exam_authorization_pending_explanation_html: You have until <strong>%{end_time}</strong> (%{location} time) to register. After that date, your request will be processed and you will receive a confirmation. <br> Don't forget to check the notifications!
119
123
  exam_authorization_request_approved_html: Your request was approved, don't forget to log in at <strong>%{date}</strong> (%{location} time). Good luck!
120
- exam_authorization_request_created: Your registration to the exam has been saved!
124
+ exam_authorization_request_created: You registered for the exam on %{friendly_date}
121
125
  exam_authorization_request_rejected: Your request was rejected because you didn't meet the requirements.
122
- exam_authorization_request_saved: Your registration to the exam has been updated!
126
+ exam_authorization_request_saved: You've changed your registration to the exam to %{friendly_date}
123
127
  exam_authorization_request_updated: Your registration to %{description} has been updated
124
128
  exam_registration_choose_exam: Choose date and time to attend to the exam
125
129
  exam_registration_explanation_html: You have until <strong>%{date}</strong> (%{location} time) to register. After that date, your request will be processed and you will receive a confirmation. <br> Don't forget to check the notifications!
@@ -272,6 +276,7 @@ en:
272
276
  notifications_will_be_here: Notifications will be here
273
277
  notify_problem_with_exercise: Report a bug
274
278
  office: Office
279
+ only_forum_questions_on_forum: Remember that only exercise-related questions are answered on the forum. For any other kind of inquiries, please check the %{link}.
275
280
  only_landscape_support: Please, rotate your tablet or cellphone to continue practicing
276
281
  opened: Open
277
282
  opened_count: '%{count} opened'
@@ -360,6 +365,7 @@ en:
360
365
  terms_and_conditions_continue_disclaimer: By continuing you agree to %{terms_link}
361
366
  terms_and_conditions_must_be_accepted: You must accept the terms and conditions
362
367
  test_results: Test results
368
+ time_in_minutes: "%{time} minutes"
363
369
  time_since: "%{time} ago"
364
370
  time_left: You have
365
371
  title: Title