mumuki-laboratory 7.10.5 → 8.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +26 -7
- data/Rakefile +9 -2
- data/app/assets/javascripts/mumuki_laboratory/application/bridge.js +26 -5
- data/app/assets/javascripts/mumuki_laboratory/application/characters.js +3 -1
- data/app/assets/javascripts/mumuki_laboratory/application/gamification.js +85 -0
- data/app/assets/javascripts/mumuki_laboratory/application/kids.js +160 -334
- data/app/assets/javascripts/mumuki_laboratory/application/kindergarten.js +200 -0
- data/app/assets/javascripts/mumuki_laboratory/application/primary.js +257 -0
- data/app/assets/javascripts/mumuki_laboratory/application/profile.js +31 -16
- data/app/assets/javascripts/mumuki_laboratory/application/submission.js +1 -0
- data/app/assets/javascripts/mumuki_laboratory/application/submissions-store.js +19 -2
- data/app/assets/stylesheets/mumuki_laboratory/application/_errors.scss +1 -3
- data/app/assets/stylesheets/mumuki_laboratory/application/_modules.scss +3 -1
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_avatar.scss +21 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/{_chapter_show.scss → _content_show.scss} +0 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kindergarten.scss +421 -12
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_medal.scss +48 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_terms.scss +44 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_user_profile.scss +9 -0
- data/app/controllers/api/courses_controller.rb +1 -2
- data/app/controllers/api/organizations_controller.rb +2 -3
- data/app/controllers/api/users_controller.rb +2 -4
- data/app/controllers/application_controller.rb +15 -8
- data/app/controllers/book_discussions_controller.rb +4 -0
- data/app/controllers/invitations_controller.rb +1 -2
- data/app/controllers/users_controller.rb +8 -2
- data/app/helpers/avatar_helper.rb +11 -3
- data/app/helpers/contextualization_result_helper.rb +9 -1
- data/app/helpers/kindergarten_helper.rb +5 -0
- data/app/helpers/links_helper.rb +8 -0
- data/app/helpers/medal_helper.rb +36 -0
- data/app/helpers/open_graph_helper.rb +2 -2
- data/app/helpers/organization_list_helper.rb +1 -1
- data/app/helpers/overlapped_buttons_helper.rb +1 -1
- data/app/helpers/page_title_helper.rb +2 -2
- data/app/helpers/profile_helper.rb +9 -1
- data/app/views/book/_header.html.erb +17 -0
- data/app/views/book/show.html.erb +1 -18
- data/app/views/discussions/terms.html.erb +10 -0
- data/app/views/exercise_solutions/_kids_results.html.erb +1 -1
- data/app/views/exercises/show.html.erb +1 -0
- data/app/views/invitations/_invitation_form.html.erb +1 -0
- data/app/views/layouts/_discussions.html.erb +4 -0
- data/app/views/layouts/_error.html.erb +3 -6
- data/app/views/layouts/_guide.html.erb +10 -3
- data/app/views/layouts/_kindergarten.html.erb +38 -0
- data/app/views/layouts/_main.html.erb +3 -1
- data/app/views/layouts/_organization_chooser.html.erb +0 -7
- data/app/views/layouts/_terms_acceptance_disclaimer.html.erb +6 -0
- data/app/views/layouts/application.html.erb +3 -2
- data/app/views/layouts/exercise_inputs/layouts/_input_kindergarten.html.erb +27 -27
- data/app/views/layouts/modals/_guide_corollary.html.erb +9 -0
- data/app/views/layouts/modals/_kindergarten_context.html.erb +30 -0
- data/app/views/layouts/modals/_kindergarten_results.html.erb +23 -0
- data/app/views/layouts/modals/_kindergarten_results_aborted.html.erb +27 -0
- data/app/views/users/_avatar_list.html.erb +6 -2
- data/app/views/users/_edit_user_form.html.erb +9 -4
- data/app/views/users/_term.html.erb +10 -0
- data/app/views/users/_user_form.html.erb +5 -1
- data/app/views/users/terms.html.erb +18 -0
- data/config/routes.rb +2 -0
- data/lib/mumuki/laboratory.rb +1 -1
- data/lib/mumuki/laboratory/controllers/current_organization.rb +1 -1
- data/lib/mumuki/laboratory/controllers/results_rendering.rb +3 -2
- data/lib/mumuki/laboratory/events/events.rb +0 -24
- data/lib/mumuki/laboratory/locales/en.yml +16 -5
- data/lib/mumuki/laboratory/locales/es-CL.yml +5 -4
- data/lib/mumuki/laboratory/locales/es.yml +17 -6
- data/lib/mumuki/laboratory/locales/pt.yml +16 -5
- data/lib/mumuki/laboratory/version.rb +1 -1
- data/spec/capybara_helper.rb +99 -0
- data/spec/controllers/exercise_solutions_controller_spec.rb +3 -4
- data/spec/dummy/db/schema.rb +37 -1
- data/spec/dummy/public/medal/outline.svg +1089 -0
- data/spec/features/choose_organization_spec.rb +12 -30
- data/spec/features/disable_user_flow_spec.rb +3 -5
- data/spec/features/disabled_organization_flow_spec.rb +9 -14
- data/spec/features/exercise_flow_spec.rb +2 -2
- data/spec/features/guide_reset_spec.rb +1 -1
- data/spec/features/guides_flow_spec.rb +1 -1
- data/spec/features/home_private_flow_spec.rb +1 -3
- data/spec/features/home_public_flow_spec.rb +6 -12
- data/spec/features/invitations_flow_spec.rb +2 -2
- data/spec/features/login_flow_spec.rb +2 -2
- data/spec/features/not_found_private_flow_spec.rb +4 -4
- data/spec/features/not_found_public_flow_spec.rb +1 -6
- data/spec/features/profile_flow_spec.rb +1 -1
- data/spec/helpers/page_title_helper_spec.rb +3 -3
- data/spec/javascripts/editors-spec.js +23 -0
- data/spec/javascripts/gamification-spec.js +58 -0
- data/spec/javascripts/submissions-store-spec.js +139 -6
- data/spec/spec_helper.rb +2 -0
- data/spec/teaspoon_env.rb +24 -6
- metadata +41 -7
@@ -0,0 +1,48 @@
|
|
1
|
+
.mu-medal {
|
2
|
+
&.outline {
|
3
|
+
position: absolute;
|
4
|
+
|
5
|
+
&.content {
|
6
|
+
height: 75px;
|
7
|
+
|
8
|
+
margin-left: 10px;
|
9
|
+
}
|
10
|
+
|
11
|
+
&.corollary {
|
12
|
+
height: 150px;
|
13
|
+
|
14
|
+
margin-left: auto;
|
15
|
+
margin-right: auto;
|
16
|
+
left: 0;
|
17
|
+
right: 0;
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
&.inlay {
|
22
|
+
position: relative;
|
23
|
+
border-radius: 50%;
|
24
|
+
|
25
|
+
&.content {
|
26
|
+
height: 55px;
|
27
|
+
|
28
|
+
left: 20px;
|
29
|
+
top: 17px;
|
30
|
+
|
31
|
+
margin-right: 30px;
|
32
|
+
margin-bottom: 15px;
|
33
|
+
}
|
34
|
+
|
35
|
+
&.corollary {
|
36
|
+
height: 107px;
|
37
|
+
|
38
|
+
display: block;
|
39
|
+
top: 35px;
|
40
|
+
|
41
|
+
margin: 0 auto 20px;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
&.unacquired {
|
46
|
+
filter: grayscale(100%);
|
47
|
+
}
|
48
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
summary.terms-summary {
|
2
|
+
font-size: 29px;
|
3
|
+
outline: 0;
|
4
|
+
cursor: pointer;
|
5
|
+
.terms-summary-title {
|
6
|
+
display: inline;
|
7
|
+
}
|
8
|
+
}
|
9
|
+
|
10
|
+
.terms-card {
|
11
|
+
position: relative;
|
12
|
+
display: -ms-flexbox;
|
13
|
+
display: flex;
|
14
|
+
-ms-flex-direction: column;
|
15
|
+
flex-direction: column;
|
16
|
+
min-width: 0;
|
17
|
+
word-wrap: break-word;
|
18
|
+
background-color: #fff;
|
19
|
+
background-clip: border-box;
|
20
|
+
border: 1px solid rgba(0,0,0,.125);
|
21
|
+
border-radius: .25rem;
|
22
|
+
&:not(:first-child) {
|
23
|
+
border-top: none;
|
24
|
+
}
|
25
|
+
.terms-card-header {
|
26
|
+
cursor: pointer;
|
27
|
+
font-weight: bold;
|
28
|
+
padding: .75rem 1.25rem;
|
29
|
+
margin-bottom: 0;
|
30
|
+
background-color: rgba(0,0,0,.03);
|
31
|
+
border-bottom: 1px solid rgba(0,0,0,.125);
|
32
|
+
}
|
33
|
+
&:first-child .terms-card-header{
|
34
|
+
border-radius: calc(.25rem - 1px) calc(.25rem - 1px) 0 0;
|
35
|
+
}
|
36
|
+
}
|
37
|
+
.terms-card-body {
|
38
|
+
padding: 1.25rem;
|
39
|
+
}
|
40
|
+
|
41
|
+
.terms-acceptance-btn {
|
42
|
+
margin-top: 10px;
|
43
|
+
display: block
|
44
|
+
}
|
@@ -14,13 +14,12 @@ module Api
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def create
|
17
|
-
@organization.
|
17
|
+
@organization.save!
|
18
18
|
render json: @organization.to_resource_h
|
19
19
|
end
|
20
20
|
|
21
21
|
def update
|
22
|
-
@organization.
|
23
|
-
|
22
|
+
@organization.update! organization_params
|
24
23
|
render json: @organization.to_resource_h
|
25
24
|
end
|
26
25
|
end
|
@@ -4,14 +4,12 @@ module Api
|
|
4
4
|
before_action :authorize_janitor!
|
5
5
|
|
6
6
|
def create
|
7
|
-
@user.
|
8
|
-
@user.notify!
|
7
|
+
@user.save_and_notify!
|
9
8
|
render json: @user.to_resource_h
|
10
9
|
end
|
11
10
|
|
12
11
|
def update
|
13
|
-
@user.
|
14
|
-
@user.notify!
|
12
|
+
@user.update_and_notify! user_params.except([:email, :permissions, :uid])
|
15
13
|
render json: @user.to_resource_h
|
16
14
|
end
|
17
15
|
end
|
@@ -19,7 +19,7 @@ class ApplicationController < ActionController::Base
|
|
19
19
|
before_action :ensure_user_enabled!, if: :current_user?
|
20
20
|
before_action :validate_active_organization!
|
21
21
|
|
22
|
-
before_action :
|
22
|
+
before_action :redirect_to_proper_context!, if: :immersive_context_wrong?
|
23
23
|
|
24
24
|
before_action :authorize_if_private!
|
25
25
|
before_action :validate_active_organization!
|
@@ -39,18 +39,20 @@ class ApplicationController < ActionController::Base
|
|
39
39
|
:theme_stylesheet_url,
|
40
40
|
:extension_javascript_url
|
41
41
|
|
42
|
-
def
|
43
|
-
|
42
|
+
def immersive_context_wrong?
|
43
|
+
current_immersive_context != Organization.current
|
44
44
|
end
|
45
45
|
|
46
|
-
def
|
47
|
-
|
46
|
+
def redirect_to_proper_context!
|
47
|
+
# TODO: redirect to subject (if it exists on the immersive context)
|
48
|
+
redirect_to current_immersive_context.url_for('/')
|
48
49
|
end
|
49
50
|
|
50
51
|
def should_choose_organization?
|
51
|
-
current_user?
|
52
|
-
|
53
|
-
|
52
|
+
return false unless current_user?
|
53
|
+
|
54
|
+
# TODO: replace `nil` with `subject` to consider exercise, guide, etc
|
55
|
+
current_user.immersive_organizations_at(nil).size > 1
|
54
56
|
end
|
55
57
|
|
56
58
|
# ensures contents are accessible to current user
|
@@ -82,6 +84,11 @@ class ApplicationController < ActionController::Base
|
|
82
84
|
|
83
85
|
private
|
84
86
|
|
87
|
+
def current_immersive_context
|
88
|
+
# TODO: replace `nil` with `subject` to consider exercise, guide, etc
|
89
|
+
current_user&.current_immersive_context_at(nil) || Organization.current
|
90
|
+
end
|
91
|
+
|
85
92
|
def from_sessions?
|
86
93
|
params['controller'] == 'login'
|
87
94
|
end
|
@@ -15,8 +15,7 @@ class InvitationsController < ApplicationController
|
|
15
15
|
|
16
16
|
def join
|
17
17
|
current_user.accept_invitation! @invitation
|
18
|
-
current_user.
|
19
|
-
current_user.notify!
|
18
|
+
current_user.update_and_notify! user_params
|
20
19
|
redirect_to_organization!
|
21
20
|
end
|
22
21
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class UsersController < ApplicationController
|
2
2
|
include WithUserParams
|
3
3
|
|
4
|
-
before_action :authenticate
|
4
|
+
before_action :authenticate!, except: :terms
|
5
5
|
before_action :set_user!
|
6
6
|
|
7
7
|
def show
|
@@ -11,9 +11,14 @@ class UsersController < ApplicationController
|
|
11
11
|
|
12
12
|
def update
|
13
13
|
current_user.update_and_notify! user_params
|
14
|
+
current_user.accept_profile_terms!
|
14
15
|
redirect_to user_path, notice: I18n.t(:user_data_updated)
|
15
16
|
end
|
16
17
|
|
18
|
+
def terms
|
19
|
+
@profile_terms ||= Term.profile_terms_for(current_user)
|
20
|
+
end
|
21
|
+
|
17
22
|
def unsubscribe
|
18
23
|
user_id = User.unsubscription_verifier.verify(params[:id])
|
19
24
|
User.find(user_id).unsubscribe_from_reminders!
|
@@ -22,7 +27,7 @@ class UsersController < ApplicationController
|
|
22
27
|
end
|
23
28
|
|
24
29
|
def permissible_params
|
25
|
-
super << :avatar_id
|
30
|
+
super << [:avatar_id, :avatar_type]
|
26
31
|
end
|
27
32
|
|
28
33
|
private
|
@@ -33,4 +38,5 @@ class UsersController < ApplicationController
|
|
33
38
|
def set_user!
|
34
39
|
@user = current_user
|
35
40
|
end
|
41
|
+
|
36
42
|
end
|
@@ -1,9 +1,17 @@
|
|
1
1
|
module AvatarHelper
|
2
2
|
def avatars_for(user)
|
3
|
-
(Avatar.with_current_audience_for(user) + [user.avatar]).compact.uniq
|
3
|
+
(Avatar.with_current_audience_for(user) + user.acquired_medals + [user.avatar]).compact.uniq
|
4
4
|
end
|
5
5
|
|
6
|
-
def
|
7
|
-
|
6
|
+
def locked_avatars_for(user)
|
7
|
+
user.unacquired_medals.compact.uniq
|
8
|
+
end
|
9
|
+
|
10
|
+
def show_locked_avatar_item(item)
|
11
|
+
show_avatar_item(item, class: 'mu-avatar-item mu-locked')
|
12
|
+
end
|
13
|
+
|
14
|
+
def show_avatar_item(item, **options)
|
15
|
+
avatar_image(item.image_url, alt: item.description, 'mu-avatar-id': item.id, class: 'mu-avatar-item', type: item.class.name, **options)
|
8
16
|
end
|
9
17
|
end
|
@@ -8,7 +8,7 @@ module ContextualizationResultHelper
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def t_contextualization_status(contextualization)
|
11
|
-
t contextualization_status
|
11
|
+
t contextualization_status(contextualization)
|
12
12
|
end
|
13
13
|
|
14
14
|
def contextualization_status(contextualization)
|
@@ -16,6 +16,14 @@ module ContextualizationResultHelper
|
|
16
16
|
:hidden_done
|
17
17
|
elsif contextualization.exercise.choice?
|
18
18
|
contextualization.solved? ? :correct_answer : :wrong_answer
|
19
|
+
else
|
20
|
+
contextualization_status_translation_key contextualization
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def contextualization_status_translation_key(contextualization)
|
25
|
+
if contextualization.exercise.input_kindergarten?
|
26
|
+
"kindergarten_#{contextualization.submission_status}"
|
19
27
|
else
|
20
28
|
contextualization.submission_status
|
21
29
|
end
|
data/app/helpers/links_helper.rb
CHANGED
@@ -51,6 +51,14 @@ module LinksHelper
|
|
51
51
|
body: permissions_help_email_body(current_user)
|
52
52
|
end
|
53
53
|
|
54
|
+
def link_to_profile_terms
|
55
|
+
link_to t(:terms_and_conditions).downcase, user_terms_path, target: '_blank'
|
56
|
+
end
|
57
|
+
|
58
|
+
def link_to_forum_terms
|
59
|
+
link_to t(:forum_terms).downcase, discussions_terms_path, target: '_blank'
|
60
|
+
end
|
61
|
+
|
54
62
|
def turbolinks_enable_for(exercise)
|
55
63
|
%Q{data-turbolinks="#{!exercise.input_kids?}"}.html_safe
|
56
64
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module MedalHelper
|
2
|
+
def should_display_medal?(content, organization)
|
3
|
+
content.medal.present? && organization.gamification_enabled?
|
4
|
+
end
|
5
|
+
|
6
|
+
def content_medal_for(content, user)
|
7
|
+
medal_for content, 'content', completion_class_for(content, user)
|
8
|
+
end
|
9
|
+
|
10
|
+
def corollary_medal_for(content)
|
11
|
+
medal_for content, 'corollary'
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def completion_class_for(content, user)
|
17
|
+
content.once_completed_for?(user, Organization.current) ? '' : 'unacquired'
|
18
|
+
end
|
19
|
+
|
20
|
+
def medal_for(content, view, completion='')
|
21
|
+
%Q{
|
22
|
+
<div class="mu-medal #{completion}">
|
23
|
+
#{outline_image(view)}
|
24
|
+
#{inlay_image_for(content, view)}
|
25
|
+
</div>
|
26
|
+
}.html_safe
|
27
|
+
end
|
28
|
+
|
29
|
+
def outline_image(clazz)
|
30
|
+
image_tag '/medal/outline.svg', class: "mu-medal outline #{clazz}", alt: I18n.t(:medal)
|
31
|
+
end
|
32
|
+
|
33
|
+
def inlay_image_for(content, clazz)
|
34
|
+
image_tag content.medal.image_url, class: "mu-medal inlay #{clazz}", alt: content.medal.description
|
35
|
+
end
|
36
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module OpenGraphHelper
|
2
2
|
def open_graph_tags(subject)
|
3
3
|
%Q{
|
4
|
-
<meta property="og:site_name" content="#{Organization.current.
|
4
|
+
<meta property="og:site_name" content="#{Organization.current.display_name}" />
|
5
5
|
<meta property="og:title" content="#{h page_title(subject)}"/>
|
6
|
-
<meta property="og:description" content="#{Organization.current.
|
6
|
+
<meta property="og:description" content="#{Organization.current.description}"/>
|
7
7
|
<meta property="og:type" content="website"/>
|
8
8
|
<meta property="og:image" content="#{Organization.current.open_graph_image_url}"/>
|
9
9
|
<meta property="og:url" content="#{request.original_url}"/>
|
@@ -8,7 +8,7 @@ module OverlappedButtonsHelper
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def restart_guide_link(guide)
|
11
|
-
link_to restart_icon, guide_progress_path(guide), class: 'mu-content-toolbar-item', data: {confirm: t(:confirm_restart)}, method: :delete
|
11
|
+
link_to restart_icon, guide_progress_path(guide), class: 'mu-content-toolbar-item mu-restart-guide', data: {confirm: t(:confirm_restart)}, method: :delete
|
12
12
|
end
|
13
13
|
|
14
14
|
def overlapped_button_icon(key, icon)
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module PageTitleHelper
|
2
2
|
def page_title(subject)
|
3
|
-
name =
|
3
|
+
name = Organization.current.page_name
|
4
4
|
|
5
5
|
if subject && !subject.new_record?
|
6
6
|
"#{subject.friendly} - #{name}"
|
7
7
|
else
|
8
|
-
"#{name}
|
8
|
+
"#{name}"
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
@@ -1,5 +1,13 @@
|
|
1
1
|
module ProfileHelper
|
2
2
|
def edit_profile_button
|
3
|
-
link_to t(:edit_profile),
|
3
|
+
link_to t(:edit_profile), :edit_user, class: 'btn btn-success'
|
4
|
+
end
|
5
|
+
|
6
|
+
def cancel_edit_profile_button
|
7
|
+
link_to t(:cancel), :user, class: 'btn btn-default' if current_user.profile_completed?
|
8
|
+
end
|
9
|
+
|
10
|
+
def save_edit_profile_button(form)
|
11
|
+
form.submit t(:save), disabled: true, class: 'btn btn-success mu-edit-profile-btn'
|
4
12
|
end
|
5
13
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<% @organization = Organization.current %>
|
2
|
+
<div class="book-header <%= current_user? ? '' : 'logged' %>">
|
3
|
+
<%#
|
4
|
+
H1 is not actually displayed.
|
5
|
+
This weird title structure is kept for backward compatibility only
|
6
|
+
%>
|
7
|
+
<h1><%= @organization.page_name %></h1>
|
8
|
+
|
9
|
+
<img src="<%= @organization.banner_url %>" alt="<%= @organization.display_name %>">
|
10
|
+
<h2><%= @organization.page_name %></h2>
|
11
|
+
|
12
|
+
<h3><small><%= @organization.description %></small></h3>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<div class="text-box">
|
16
|
+
<%= @organization.page_description_html %>
|
17
|
+
</div>
|