mumuki-laboratory 7.8.0 → 7.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +70 -0
  3. data/app/assets/javascripts/mumuki_laboratory/application/events.js +51 -0
  4. data/app/assets/javascripts/mumuki_laboratory/application/exercise.js +45 -9
  5. data/app/assets/javascripts/mumuki_laboratory/application/profile.js +71 -0
  6. data/app/assets/stylesheets/mumuki_laboratory/application.scss +1 -1
  7. data/app/assets/stylesheets/mumuki_laboratory/application/_modules.scss +19 -17
  8. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_avatar.scss +41 -0
  9. data/app/assets/stylesheets/mumuki_laboratory/application/modules/{guide-corollary.scss → _guide_corollary.scss} +0 -0
  10. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kids.scss +1 -1
  11. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kindergarten.scss +2 -1
  12. data/app/assets/stylesheets/mumuki_laboratory/application/modules/{popover.scss → _popover.scss} +0 -0
  13. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_user_profile.scss +36 -0
  14. data/app/controllers/application_controller.rb +1 -1
  15. data/app/controllers/users_controller.rb +5 -1
  16. data/app/helpers/application_helper.rb +6 -4
  17. data/app/helpers/avatar_helper.rb +9 -0
  18. data/app/helpers/discussions_helper.rb +2 -2
  19. data/app/helpers/profile_helper.rb +5 -0
  20. data/app/mailers/user_mailer.rb +6 -6
  21. data/app/views/exercises/show.html.erb +1 -0
  22. data/app/views/layouts/_runner_assets.html.erb +1 -2
  23. data/app/views/layouts/application.html.erb +1 -1
  24. data/app/views/layouts/modals/_avatar_picker.html.erb +16 -0
  25. data/app/views/users/_avatar_list.html.erb +11 -0
  26. data/app/views/users/_edit_user_form.html.erb +22 -0
  27. data/app/views/users/_user_form.html.erb +21 -8
  28. data/app/views/users/edit.html.erb +5 -0
  29. data/app/views/users/show.html.erb +0 -4
  30. data/config/routes.rb +1 -1
  31. data/lib/mumuki/laboratory/locales/datetime.es.yml +14 -14
  32. data/lib/mumuki/laboratory/locales/en.yml +6 -0
  33. data/lib/mumuki/laboratory/locales/es.yml +7 -1
  34. data/lib/mumuki/laboratory/locales/pt.yml +6 -0
  35. data/lib/mumuki/laboratory/version.rb +1 -1
  36. data/spec/dummy/db/schema.rb +11 -0
  37. data/spec/features/exercise_flow_spec.rb +8 -5
  38. data/spec/helpers/avatar_helper_spec.rb +26 -0
  39. data/spec/javascripts/events-spec.js +33 -0
  40. data/spec/javascripts/exercise-spec.js +23 -4
  41. data/spec/mailers/user_mailer_spec.rb +11 -6
  42. data/vendor/assets/javascripts/codemirror-modes/gobstones.js +3 -2
  43. metadata +22 -9
  44. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_follow_us.scss +0 -16
@@ -93,7 +93,7 @@ class ApplicationController < ActionController::Base
93
93
  def validate_user_profile!
94
94
  unless current_user.profile_completed?
95
95
  flash.notice = I18n.t :please_fill_profile_data
96
- redirect_to user_path
96
+ redirect_to edit_user_path
97
97
  end
98
98
  end
99
99
 
@@ -11,7 +11,7 @@ class UsersController < ApplicationController
11
11
 
12
12
  def update
13
13
  current_user.update_and_notify! user_params
14
- redirect_to root_path, notice: I18n.t(:user_data_updated)
14
+ redirect_to user_path, notice: I18n.t(:user_data_updated)
15
15
  end
16
16
 
17
17
  def unsubscribe
@@ -21,6 +21,10 @@ class UsersController < ApplicationController
21
21
  redirect_to root_path, notice: t(:unsubscribed_successfully)
22
22
  end
23
23
 
24
+ def permissible_params
25
+ super << :avatar_id
26
+ end
27
+
24
28
  private
25
29
 
26
30
  def validate_user_profile!
@@ -6,12 +6,14 @@ module ApplicationHelper
6
6
  html_escape html.to_str
7
7
  end
8
8
 
9
- def profile_picture
10
- profile_picture_for current_user
9
+ def profile_picture_for(user, **options)
10
+ options.merge!(height: 40, onError: "this.onerror = null; this.src = '#{image_url(user.placeholder_image_url)}'")
11
+ avatar_image(user.profile_picture, options)
11
12
  end
12
13
 
13
- def profile_picture_for(user, height = 40)
14
- image_tag(user.profile_picture, height: height, class: 'img-circle', onError: "this.onerror = null; this.src = '#{image_url('user_shape.png')}'")
14
+ def avatar_image(avatar_url, **options)
15
+ options.merge!(class: "img-circle #{options[:class]}")
16
+ image_tag(image_url(avatar_url), options)
15
17
  end
16
18
 
17
19
  def paginate(object, options = {})
@@ -0,0 +1,9 @@
1
+ module AvatarHelper
2
+ def avatars_for(user)
3
+ (Avatar.with_current_audience_for(user) + [user.avatar]).compact.uniq
4
+ end
5
+
6
+ def show_avatar_item(item)
7
+ avatar_image(item.image_url, alt: item.description, 'mu-avatar-id': item.id, class: 'mu-avatar-item')
8
+ end
9
+ end
@@ -48,8 +48,8 @@ module DiscussionsHelper
48
48
  {status: :solved, sort: :upvotes_count_desc}
49
49
  end
50
50
 
51
- def user_avatar(user, image_class = '')
52
- image_tag user.profile_picture, height: 40, class: "img-circle #{image_class}"
51
+ def user_avatar(user, image_class='')
52
+ profile_picture_for(user, class: image_class)
53
53
  end
54
54
 
55
55
  def discussions_link_with_teaser(item)
@@ -0,0 +1,5 @@
1
+ module ProfileHelper
2
+ def edit_profile_button
3
+ link_to t(:edit_profile), edit_user_path, class: 'btn btn-success'
4
+ end
5
+ end
@@ -2,7 +2,7 @@ class UserMailer < ApplicationMailer
2
2
  def welcome_email(user, organization)
3
3
  with_locale(user, organization) do
4
4
  organization_name = organization.display_name || t(:your_new_organization)
5
- build_email t(:welcome, name: organization_name), { inline: organization.welcome_email_template }
5
+ build_email t(:welcome, name: organization_name), { inline: organization.welcome_email_template }, from: organization.welcome_email_sender
6
6
  end
7
7
  end
8
8
 
@@ -28,10 +28,10 @@ class UserMailer < ApplicationMailer
28
28
 
29
29
  private
30
30
 
31
- def build_email(subject, template)
32
- mail to: @user.email,
33
- subject: subject,
34
- content_type: 'text/html',
35
- body: render(template)
31
+ def build_email(subject, template, **options)
32
+ mail options.compact.merge(to: @user.email,
33
+ subject: subject,
34
+ content_type: 'text/html',
35
+ body: render(template))
36
36
  end
37
37
  end
@@ -47,6 +47,7 @@
47
47
  <%= hidden_field_tag default_content_tag_id(@exercise), @default_content %>
48
48
  <%= hidden_field_tag "mu-exercise-id", @exercise.id %>
49
49
  <%= hidden_field_tag "mu-exercise-layout", @exercise.layout %>
50
+ <%= hidden_field_tag "mu-exercise-settings", @exercise.settings.to_json %>
50
51
 
51
52
  <div style="display: none" id="processing-template">
52
53
  <div class="bs-callout bs-callout-info">
@@ -12,8 +12,7 @@
12
12
 
13
13
  <% if !loads_more_assets && show_loading_for?(language, assets_kind) %>
14
14
  <script id="<%= assets_kind %>-loading-script">
15
- let assetsKind = '<%= assets_kind %>';
16
- mumuki.load(() => mumuki.assetsLoaderFor(assetsKind));
15
+ mumuki.load(() => mumuki.assetsLoaderFor('<%= assets_kind %>'));
17
16
  </script>
18
17
  <% end %>
19
18
 
@@ -28,7 +28,7 @@
28
28
  </div>
29
29
  <div class="dropdown">
30
30
  <span id="profileDropdown" data-toggle="dropdown" aria-label="<%= t(:user) %>" role="menu" tabindex="0">
31
- <%= profile_picture %>
31
+ <%= profile_picture_for current_user %>
32
32
  </span>
33
33
  <ul class="dropdown-menu dropdown-menu-right" aria-labelledby="profileDropdown">
34
34
  <%= menu_bar_list_items %>
@@ -0,0 +1,16 @@
1
+ <div class="modal fade" id="mu-avatar-picker" tabindex="-1" role="dialog" aria-hidden="true">
2
+ <div class="modal-dialog">
3
+ <div class="modal-content">
4
+ <div class="modal-header">
5
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
6
+ <span aria-hidden="true">&times;</span></button>
7
+ <div class="modal-title">
8
+ <h3 class="text-left" id="gridSystemModalLabel"><%= t :choose_your_avatar %> </h3>
9
+ </div>
10
+ </div>
11
+ <div class="modal-body">
12
+ <%= render partial: 'avatar_list' %>
13
+ </div>
14
+ </div>
15
+ </div>
16
+ </div>
@@ -0,0 +1,11 @@
1
+ <div class="row">
2
+ <div class="mu-avatar-list">
3
+ <% avatars_for(@user).sort_by(&:id).each do |avatar| %>
4
+ <%= show_avatar_item(avatar) %>
5
+ <% end %>
6
+
7
+ <% if @user.image_url %>
8
+ <%= avatar_image @user.image_url, class: 'mu-avatar-item', id: 'mu-user-image' %>
9
+ <% end %>
10
+ </div>
11
+ </div>
@@ -0,0 +1,22 @@
1
+ <%= form_for :user, url: update_user_path, :html => { id: 'mu-user-form' }, method: :put do |f| %>
2
+ <div class="mu-user-header">
3
+ <h1><%= t(:edit_profile) %></h1>
4
+ <div class="mu-profile-actions">
5
+ <%= link_to t(:cancel), :back, class: 'btn btn-default' if @user.profile_completed? %>
6
+ <%= f.submit t(:save), disabled: true, class: 'btn btn-success', id: 'mu-edit-profile-btn' %>
7
+ </div>
8
+ </div>
9
+ <div class="row mu-tab-body">
10
+ <div class="col-md-4 text-center">
11
+ <%= profile_picture_for(@user, id: 'mu-user-avatar', class: 'mu-user-avatar') %>
12
+ <i class="fa fa-pencil fa-2x pointer mu-edit-avatar" id="mu-edit-avatar-icon"></i>
13
+ </div>
14
+ <div class="col-md-8">
15
+ <%= render partial: 'profile_fields', locals: {form: f} %>
16
+ </div>
17
+ </div>
18
+ <% end %>
19
+
20
+ <%= content_for :no_container do %>
21
+ <%= render partial: 'layouts/modals/avatar_picker' %>
22
+ <% end %>
@@ -1,11 +1,24 @@
1
- <%= form_for :user, url: update_user_path, method: :put do |f| %>
2
- <div class="row mu-tab-body">
3
- <div class="col-md-4 text-center">
4
- <%= image_tag @user.profile_picture, size: '250x250', class: 'img-circle' %>
1
+ <div class="mu-user-header">
2
+ <h1><%= @user.name %></h1>
3
+ <div class="mu-profile-actions">
4
+ <%= edit_profile_button %>
5
+ </div>
6
+ </div>
7
+ <div class="row mu-tab-body">
8
+ <div class="col-md-4 text-center">
9
+ <%= profile_picture_for(@user, id: 'mu-user-avatar', class: 'mu-user-avatar') %>
10
+ </div>
11
+ <div class="col-md-8 mu-profile-info">
12
+ <% if @user.age.present? %>
13
+ <div>
14
+ <span> <strong><%= t :age %>:</strong> <%= "#{@user.age} #{t :years}" %> </span>
15
+ </div>
16
+ <% end %>
17
+ <div>
18
+ <span> <strong><%= t :email %>:</strong> <%= @user.email %> </span>
5
19
  </div>
6
- <div class="col-md-8">
7
- <%= render partial: 'profile_fields', locals: {form: f} %>
8
- <div><%= f.submit t(:save), class: 'btn btn-success btn-block' %></div>
20
+ <div>
21
+ <span> <strong><%= t :programming_since %>:</strong> <%= t(:time_since, time: time_ago_in_words(@user.created_at)) %> </span>
9
22
  </div>
10
23
  </div>
11
- <% end %>
24
+ </div>
@@ -0,0 +1,5 @@
1
+ <%= content_for :breadcrumbs do %>
2
+ <%= breadcrumbs @user %>
3
+ <% end %>
4
+
5
+ <%= render partial: 'edit_user_form' %>
@@ -2,10 +2,6 @@
2
2
  <%= breadcrumbs @user %>
3
3
  <% end %>
4
4
 
5
- <div>
6
- <h1><%= @user.name %></h1>
7
- </div>
8
-
9
5
  <ul class="nav nav-tabs" role="tablist">
10
6
  <li role="presentation" class="active">
11
7
  <a data-target="#info" aria-controls="info" role="tab" data-toggle="tab"><%= t :profile %></a>
@@ -52,7 +52,7 @@ Rails.application.routes.draw do
52
52
  resources :exams, only: :show
53
53
 
54
54
  # All users
55
- resource :user, only: :show
55
+ resource :user, only: [:show, :edit]
56
56
 
57
57
  # Current user
58
58
  resources :messages, only: [:index, :create]
@@ -3,23 +3,23 @@ es:
3
3
  datetime:
4
4
  distance_in_words:
5
5
  about_x_hours:
6
- one: Alrededor de 1 hora
7
- other: Alrededor de %{count} horas
6
+ one: aproximadamente una hora
7
+ other: aproximadamente %{count} horas
8
8
  about_x_months:
9
- one: Alrededor de 1 mes
10
- other: Alrededor de %{count} meses
9
+ one: aproximadamente un mes
10
+ other: aproximadamente %{count} meses
11
11
  less_than_x_minutes:
12
- one: Hace menos de 1 minuto
13
- other: Hace menos de %{count} minutos
12
+ one: menos de un minuto
13
+ other: menos de %{count} minutos
14
14
  x_days:
15
- one: Hace 1 dia
16
- other: Hace %{count} dias
15
+ one: un día
16
+ other: '%{count} días'
17
17
  x_minutes:
18
- one: Hace 1 minuto
19
- other: Hace %{count} minutos
18
+ one: un minuto
19
+ other: '%{count} minutos'
20
20
  x_months:
21
- one: Hace 1 mes
22
- other: Hace %{count} meses
21
+ one: un mes
22
+ other: '%{count} meses'
23
23
  x_seconds:
24
- one: Hace 1 segundo
25
- other: Hace %{count} segundos
24
+ one: un segundo
25
+ other: '%{count} segundos'
@@ -7,6 +7,7 @@ en:
7
7
  actions: Actions
8
8
  activity: Activity
9
9
  actual_state: Obtained board
10
+ age: Age
10
11
  all: All
11
12
  appendix: "Appendix"
12
13
  appendix_teaser: Do you want to learn more? <a href="%{link}">Check this chapter's appendix</a>"
@@ -28,11 +29,13 @@ en:
28
29
  bibliotheca_ui: Bibliotheca
29
30
  birthdate: Birthdate
30
31
  blocked_forum_explanation: You are not allowed to see this content. <br> Are you in the middle of an exam right now?
32
+ cancel: Cancel
31
33
  cancel_subscription: Cancel your subscription.
32
34
  chapter: Chapter
33
35
  chapters: Chapters
34
36
  chapter_finished_html: You have finished %{chapter}!
35
37
  chapter_number: Chapter %{number}
38
+ choose_your_avatar: Choose your avatar
36
39
  classroom_ui: Classroom
37
40
  clear_console: Clear console
38
41
  closed: Closed
@@ -65,6 +68,7 @@ en:
65
68
  dont_leave_us: Don't leave us! Learning is fun. You just have to keep at it.
66
69
  download: Download your solution
67
70
  edit: Edit
71
+ edit_profile: Edit profile
68
72
  editor_placeholder: "write your solution here..."
69
73
  email: Email
70
74
  error_description: This is known as a <span class="error-link">%{error}</span>.
@@ -187,6 +191,7 @@ en:
187
191
  processing_your_solution: We are processing you solution
188
192
  profile: Profile
189
193
  profile_of: Profile of %{username}
194
+ programming_since: Started programming
190
195
  progress: Progresss
191
196
  read: Read
192
197
  refresh_or_wait: Please press F5 if results are not displayed after a few seconds
@@ -267,6 +272,7 @@ en:
267
272
  welcome: Welcome to %{name}!
268
273
  working: "Working"
269
274
  wrong_answer: The answer is wrong
275
+ years: years old
270
276
  you_must_sign_in_before_submitting: You must sign in before submitting your solutions
271
277
  you_never_submitted_solutions: It seems that you've never submitted solutions since you created your account.
272
278
  your_new_organization: your new organization
@@ -7,6 +7,7 @@ es:
7
7
  actions: Acciones
8
8
  activity: Actividad
9
9
  actual_state: Tablero obtenido
10
+ age: Edad
10
11
  all: Todos
11
12
  and: y
12
13
  appendix: "Apéndice"
@@ -29,11 +30,13 @@ es:
29
30
  bibliotheca_ui: Biblioteca
30
31
  birthdate: Fecha de nacimiento
31
32
  blocked_forum_explanation: Es decir que no tenés autorización para ver este contenido. <br> ¿Puede que estés en medio de un examen?
33
+ cancel: Cancelar
32
34
  cancel_subscription: Cancelá tu subscripción.
33
35
  chapter: Capítulo
34
36
  chapters: Capítulos
35
37
  chapter_finished_html: ¡Terminaste %{chapter}! ¡Felicitaciones!
36
38
  chapter_number: 'Capítulo %{number}'
39
+ choose_your_avatar: Elegí tu avatar
37
40
  classroom_ui: Aula
38
41
  clear_console: Reiniciar consola
39
42
  closed: Cerrada
@@ -72,6 +75,7 @@ es:
72
75
  dont_leave_us: ¡No nos abandones! Aprender a programar es divertido. Sólo tenés que seguir practicando.
73
76
  download: "Descargá lo que hiciste"
74
77
  edit: Editar
78
+ edit_profile: Editar perfil
75
79
  editor_placeholder: "...escribí tu solución acá..."
76
80
  email: Email
77
81
  error_description: ¡Ups! <br> Esto es lo que se conoce como <span class="error-link">%{error}</span>.
@@ -204,6 +208,7 @@ es:
204
208
  processing_your_solution: Estamos procesando tu solución
205
209
  profile: Perfil
206
210
  profile_of: Perfil de %{username}
211
+ programming_since: Programando desde
207
212
  progress: Progreso
208
213
  read: Leido
209
214
  read_messages: Ver mensajes
@@ -288,13 +293,14 @@ es:
288
293
  view_details: Ver detalles
289
294
  want_permissions: El siguiente usuario requiere permisos
290
295
  we_miss_you: ¡Te extrañamos!
291
- welcome: Te damos la bienvenida a %{name}!
296
+ welcome: ¡Te damos la bienvenida a %{name}!
292
297
  will_paginate:
293
298
  previous_label: "&#8592; Anterior"
294
299
  next_label: "Siguiente &#8594;"
295
300
  page_gap: "&hellip;"
296
301
  working: "Procesando"
297
302
  wrong_answer: La respuesta no es correcta
303
+ years: años
298
304
  you_must_sign_in_before_submitting: Tenés que iniciar sesión antes de empezar a enviar tus soluciones
299
305
  you_never_submitted_solutions: Parece que nunca enviaste soluciones desde que creaste tu cuenta.
300
306
  your_new_organization: tu nueva organización
@@ -7,6 +7,7 @@ pt:
7
7
  actions: Ações
8
8
  activity: Atividade
9
9
  actual_state: Tablero obtido
10
+ age: Idade
10
11
  all: Todos
11
12
  and: e
12
13
  appendix: Apêndice
@@ -29,10 +30,12 @@ pt:
29
30
  bibliotheca_ui: Biblioteca
30
31
  birthdate: Fecha de nacimiento
31
32
  blocked_forum_explanation: Você não tem permissão para ver este conteúdo. <br> Você está no meio de um exame agora?
33
+ cancel: Cancelar
32
34
  chapter: Capítulo
33
35
  chapters: Capítulos
34
36
  chapter_finished_html: Você terminou %{chapter}! Parabéns!
35
37
  chapter_number: Capítulo %{number}
38
+ choose_your_avatar: Escolha seu avatar
36
39
  classroom_ui: Sala de aula
37
40
  clear_console: Reiniciar o console
38
41
  closed: Fechado
@@ -68,6 +71,7 @@ pt:
68
71
  discussions: Consultas
69
72
  download: Faça o download do que você fez
70
73
  edit: Editar
74
+ edit_profile: Editar perfil
71
75
  editor_placeholder: ... Escreva sua solução aqui ...
72
76
  email: E-mail
73
77
  error_description: Opa! <br> Isto é o que é conhecido como <span class = "error-link"> %{error} </ span>.
@@ -195,6 +199,7 @@ pt:
195
199
  processing_your_solution: Estamos processando sua solução
196
200
  profile: Perfil
197
201
  profile_of: Perfil de %{username}
202
+ programming_since: Sou programador
198
203
  progress: Progresso
199
204
  read: Ler
200
205
  read_messages: Ver mensagens
@@ -270,5 +275,6 @@ pt:
270
275
  welcome: Convidamos você a {name}!
271
276
  working: Processamento
272
277
  wrong_answer: A resposta não é correta
278
+ years: anos
273
279
  you_must_sign_in_before_submitting: Você deve fazer o login antes de começar a enviar suas soluções
274
280
  your_new_organization: sua nova organização
@@ -1,5 +1,5 @@
1
1
  module Mumuki
2
2
  module Laboratory
3
- VERSION = '7.8.0'
3
+ VERSION = '7.10.1'
4
4
  end
5
5
  end
@@ -44,6 +44,7 @@ ActiveRecord::Schema.define(version: 20200804191643) do
44
44
  t.bigint "organization_id"
45
45
  t.datetime "submitted_at"
46
46
  t.bigint "parent_id"
47
+ t.integer "top_submission_status"
47
48
  t.index ["exercise_id"], name: "index_assignments_on_exercise_id"
48
49
  t.index ["organization_id"], name: "index_assignments_on_organization_id"
49
50
  t.index ["parent_id"], name: "index_assignments_on_parent_id"
@@ -54,6 +55,7 @@ ActiveRecord::Schema.define(version: 20200804191643) do
54
55
  create_table "avatars", force: :cascade do |t|
55
56
  t.string "image_url"
56
57
  t.string "description"
58
+ t.integer "target_audience", default: 0
57
59
  end
58
60
 
59
61
  create_table "books", id: :serial, force: :cascade do |t|
@@ -309,6 +311,7 @@ ActiveRecord::Schema.define(version: 20200804191643) do
309
311
  t.text "theme", default: "{}", null: false
310
312
  t.text "profile", default: "{}", null: false
311
313
  t.integer "progressive_display_lookahead"
314
+ t.integer "target_audience", default: 0
312
315
  t.boolean "incognito_mode_enabled"
313
316
  t.index ["book_id"], name: "index_organizations_on_book_id"
314
317
  t.index ["name"], name: "index_organizations_on_name", unique: true
@@ -364,6 +367,14 @@ ActiveRecord::Schema.define(version: 20200804191643) do
364
367
  t.index ["parent_item_type", "parent_item_id"], name: "index_usages_on_parent_item_type_and_parent_item_id"
365
368
  end
366
369
 
370
+ create_table "user_stats", force: :cascade do |t|
371
+ t.integer "exp", default: 0
372
+ t.bigint "user_id"
373
+ t.bigint "organization_id"
374
+ t.index ["organization_id"], name: "index_user_stats_on_organization_id"
375
+ t.index ["user_id"], name: "index_user_stats_on_user_id"
376
+ end
377
+
367
378
  create_table "users", id: :serial, force: :cascade do |t|
368
379
  t.string "provider"
369
380
  t.string "social_id"