mumuki-laboratory 7.10.3 → 7.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +26 -7
  3. data/Rakefile +9 -2
  4. data/app/assets/javascripts/mumuki_laboratory/application/bridge.js +26 -5
  5. data/app/assets/javascripts/mumuki_laboratory/application/gamification.js +85 -0
  6. data/app/assets/javascripts/mumuki_laboratory/application/kindergarten.js +145 -0
  7. data/app/assets/javascripts/mumuki_laboratory/application/profile.js +31 -16
  8. data/app/assets/javascripts/mumuki_laboratory/application/submission.js +1 -0
  9. data/app/assets/javascripts/mumuki_laboratory/application/submissions-store.js +19 -2
  10. data/app/assets/stylesheets/mumuki_laboratory/application/_errors.scss +3 -4
  11. data/app/assets/stylesheets/mumuki_laboratory/application/_modules.scss +3 -1
  12. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_avatar.scss +21 -0
  13. data/app/assets/stylesheets/mumuki_laboratory/application/modules/{_chapter_show.scss → _content_show.scss} +0 -0
  14. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kindergarten.scss +401 -12
  15. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_medal.scss +48 -0
  16. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_terms.scss +44 -0
  17. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_user_profile.scss +9 -0
  18. data/app/controllers/application_controller.rb +15 -8
  19. data/app/controllers/book_discussions_controller.rb +4 -0
  20. data/app/controllers/users_controller.rb +8 -2
  21. data/app/helpers/application_helper.rb +2 -2
  22. data/app/helpers/avatar_helper.rb +11 -3
  23. data/app/helpers/kindergarten_helper.rb +5 -0
  24. data/app/helpers/links_helper.rb +8 -0
  25. data/app/helpers/medal_helper.rb +36 -0
  26. data/app/helpers/open_graph_helper.rb +2 -2
  27. data/app/helpers/organization_list_helper.rb +1 -1
  28. data/app/helpers/overlapped_buttons_helper.rb +1 -1
  29. data/app/helpers/page_title_helper.rb +2 -2
  30. data/app/helpers/profile_helper.rb +9 -1
  31. data/app/views/book/_header.html.erb +17 -0
  32. data/app/views/book/show.html.erb +1 -18
  33. data/app/views/discussions/terms.html.erb +10 -0
  34. data/app/views/exercises/show.html.erb +1 -0
  35. data/app/views/invitations/_invitation_form.html.erb +1 -0
  36. data/app/views/layouts/_discussions.html.erb +5 -1
  37. data/app/views/layouts/_error.html.erb +3 -6
  38. data/app/views/layouts/_guide.html.erb +10 -3
  39. data/app/views/layouts/_kindergarten.html.erb +38 -0
  40. data/app/views/layouts/_main.html.erb +3 -1
  41. data/app/views/layouts/_organization_chooser.html.erb +0 -7
  42. data/app/views/layouts/_terms_acceptance_disclaimer.html.erb +6 -0
  43. data/app/views/layouts/application.html.erb +3 -2
  44. data/app/views/layouts/exercise_inputs/layouts/_input_kindergarten.html.erb +27 -27
  45. data/app/views/layouts/modals/_guide_corollary.html.erb +9 -0
  46. data/app/views/layouts/modals/_kindergarten_context.html.erb +30 -0
  47. data/app/views/layouts/modals/_kindergarten_results.html.erb +23 -0
  48. data/app/views/layouts/modals/_kindergarten_results_aborted.html.erb +24 -0
  49. data/app/views/users/_avatar_list.html.erb +6 -2
  50. data/app/views/users/_edit_user_form.html.erb +9 -4
  51. data/app/views/users/_term.html.erb +10 -0
  52. data/app/views/users/_user_form.html.erb +5 -1
  53. data/app/views/users/terms.html.erb +18 -0
  54. data/config/routes.rb +2 -0
  55. data/lib/mumuki/laboratory/controllers/current_organization.rb +1 -1
  56. data/lib/mumuki/laboratory/controllers/results_rendering.rb +3 -2
  57. data/lib/mumuki/laboratory/locales/en.yml +10 -5
  58. data/lib/mumuki/laboratory/locales/es-CL.yml +5 -4
  59. data/lib/mumuki/laboratory/locales/es.yml +12 -7
  60. data/lib/mumuki/laboratory/locales/pt.yml +12 -7
  61. data/lib/mumuki/laboratory/version.rb +1 -1
  62. data/spec/capybara_helper.rb +99 -0
  63. data/spec/controllers/exercise_solutions_controller_spec.rb +3 -4
  64. data/spec/dummy/db/schema.rb +37 -1
  65. data/spec/dummy/public/medal/outline.svg +1089 -0
  66. data/spec/features/choose_organization_spec.rb +12 -30
  67. data/spec/features/disable_user_flow_spec.rb +3 -5
  68. data/spec/features/disabled_organization_flow_spec.rb +9 -14
  69. data/spec/features/exercise_flow_spec.rb +2 -2
  70. data/spec/features/guide_reset_spec.rb +1 -1
  71. data/spec/features/guides_flow_spec.rb +1 -1
  72. data/spec/features/home_private_flow_spec.rb +1 -3
  73. data/spec/features/home_public_flow_spec.rb +6 -12
  74. data/spec/features/invitations_flow_spec.rb +2 -2
  75. data/spec/features/login_flow_spec.rb +2 -2
  76. data/spec/features/not_found_private_flow_spec.rb +4 -4
  77. data/spec/features/not_found_public_flow_spec.rb +1 -6
  78. data/spec/features/profile_flow_spec.rb +1 -1
  79. data/spec/helpers/page_title_helper_spec.rb +3 -3
  80. data/spec/javascripts/editors-spec.js +23 -0
  81. data/spec/javascripts/gamification-spec.js +58 -0
  82. data/spec/javascripts/submissions-store-spec.js +139 -6
  83. data/spec/spec_helper.rb +2 -0
  84. data/spec/teaspoon_env.rb +24 -6
  85. metadata +40 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbad2f51a31216e426cce2ee48a7588c5eeea4df4873030f8c1df14aba21cc1f
4
- data.tar.gz: 4be9b7426c73cc44f2993fff346054d0022918b0d49c86456047556b62238f1f
3
+ metadata.gz: f6139ebdb5d0f4a7148fb6fd64c07a2b7c28ea534fdafe4593cb185ab2b95371
4
+ data.tar.gz: cea3b7ec16acc73e48a58a9257194a22ad5cf6c46e4dc0530d2b2a92c93dc0cd
5
5
  SHA512:
6
- metadata.gz: 9c7d92aabcdcfb6c188bc2ac57219ef53beeda954df0dc70b4702ed61feaa13a3c8d3558b7910aa5b602b510db42a10e22803e9f0560b3a1f27284120ad95b99
7
- data.tar.gz: 9a3401a323fd4e792d65d6a3c937dd56813bd39db61f48f1fc8f3c7b8813b4346cda3e8977348200de5e525333ad6a0f127219756cfb6c2d20649c889539f98c
6
+ metadata.gz: 95b409773eb897492565a17980ae66cb30e8fbb341edc2034b83d01f117b1f6d6ac1a47c7367179cbf67c430b3c02208c2bda9e5dbc347cd19e3de91ee4d3543
7
+ data.tar.gz: d58e05c853128d3d12b188160066231494945cc571704b588899f4bf61a88b69054d7c50ffca51478328a39110cc346f24008dbccdd907c22a197515a60987b9
data/README.md CHANGED
@@ -1,6 +1,5 @@
1
- [![Build Status](https://travis-ci.org/mumuki/mumuki-laboratory.svg?branch=master)](https://travis-ci.org/mumuki/mumuki-laboratory)
1
+ [![Build Status](https://travis-ci.com/mumuki/mumuki-laboratory.svg?branch=master)](https://travis-ci.com/mumuki/mumuki-laboratory)
2
2
  [![Code Climate](https://codeclimate.com/github/mumuki/mumuki-laboratory/badges/gpa.svg)](https://codeclimate.com/github/mumuki/mumuki-laboratory)
3
- [![Test Coverage](https://codeclimate.com/github/mumuki/mumuki-laboratory/badges/coverage.svg)](https://codeclimate.com/github/mumuki/mumuki-laboratory)
4
3
  [![Issue Count](https://codeclimate.com/github/mumuki/mumuki-laboratory/badges/issue_count.svg)](https://codeclimate.com/github/mumuki/mumuki-laboratory)
5
4
 
6
5
  <img width="60%" src="https://raw.githubusercontent.com/mumuki/mumuki-laboratory/master/laboratory-screenshot.png"></img>
@@ -132,16 +131,35 @@ rails s
132
131
  ## Running tests
133
132
 
134
133
  ```bash
135
- bundle exec rspec
134
+ # Run all tests
135
+ bundle exec rake
136
+
137
+ # Run only web tests (i.e. Capybara and Teaspoon)
138
+ bundle exec rake spec:web
139
+ ```
140
+
141
+ ## Running Capybara tests with Selenium
142
+
143
+ The Capybara config of this project supports running tests on Firefox, Chrome and Safari via Selenium. The [`webdrivers`](https://github.com/titusfortner/webdrivers) gem automatically installs (and updates) all the necessary Selenium webdrivers.
144
+
145
+ By default, Capybara tests will run with the default dummy-driver (Rack test). If you want to run on a real browser, you should set `MUMUKI_SELENIUM_DRIVER` variable to `firefox`, `chrome` or `safari`. Also, a Rake task to run just the Capybara tests is available.
146
+
147
+ Some examples:
148
+
149
+ ```bash
150
+ # Run web tests, using Firefox
151
+ MUMUKI_SELENIUM_DRIVER=firefox bundle exec rake spec:web
152
+
153
+ # Run Capybara tests on Chrome
154
+ MUMUKI_SELENIUM_DRIVER=chrome bundle exec rake spec:web:capybara
136
155
  ```
137
156
 
138
157
  ## Running JS tests
139
158
 
140
- > You need first to download [geckodriver](https://github.com/mozilla/geckodriver/releases/download/v0.27.0/geckodriver-v0.27.0-linux64.tar.gz), uncrompress
141
- > it and add it to your path
159
+ The [`webdrivers`](https://github.com/titusfortner/webdrivers) gem also works with Teaspoon, no need to install anything manually. By default tests run on Firefox, but this behavior can be changed by setting `MUMUKI_SELENIUM_DRIVER` (see section above).
142
160
 
143
161
  ```bash
144
- MOZ_HEADLESS=1 bundle exec rake teaspoon
162
+ bundle exec rake spec:web:teaspoon
145
163
  ```
146
164
 
147
165
  ## Running `eslint`
@@ -278,7 +296,8 @@ which are granted to be safe and stable.
278
296
  "status": "passed|passed_with_warnings|failed",
279
297
  "guide_finished_by_solution": "boolean",
280
298
  "html": "string",
281
- "remaining_attempts_html": "string" ,
299
+ "remaining_attempts_html": "string",
300
+ "current_exp": "integer",
282
301
  "title_html": "string", // kids-only
283
302
  "button_html": "string", // kids-only
284
303
  "expectations": [ // kids-only
data/Rakefile CHANGED
@@ -25,7 +25,7 @@ require 'rspec/core'
25
25
  require 'rspec/core/rake_task'
26
26
 
27
27
  desc "Run all specs in spec directory (excluding plugin specs)"
28
- RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
28
+ RSpec::Core::RakeTask.new
29
29
 
30
30
  desc "Force development environment, required by javascript specs"
31
31
  task :development do
@@ -33,8 +33,15 @@ task :development do
33
33
  ENV['RAILS_ENV'] = 'development'
34
34
  end
35
35
 
36
+ RSpec::Core::RakeTask.new('spec:web:capybara') do |t|
37
+ t.pattern = 'spec/features/*_spec.rb'
38
+ end
39
+
36
40
  desc "Run the javascript specs"
37
- task teaspoon: [:development, "app:teaspoon"]
41
+ task 'spec:web:teaspoon': [:development, "app:teaspoon"]
42
+
43
+ desc "Run all the web-related tests"
44
+ task 'spec:web': ['spec:web:capybara', 'spec:web:teaspoon']
38
45
 
39
46
  task default: :spec
40
47
 
@@ -22,11 +22,32 @@
22
22
  */
23
23
 
24
24
  /**
25
- * @typedef {{
26
- * "solution[content]"?:string,
27
- * solution?: Solution,
28
- * client_result?: SubmissionClientResult
29
- * }} Submission
25
+ * Contents of a submission expressed in the form of params
26
+ * generated and accepted by laboratory HTTP Controllers
27
+ *
28
+ * @typedef {{"solution[content]":string}} ClassicContents
29
+ */
30
+
31
+ /**
32
+ * Contents of a submission expressed as an object
33
+ * that are created programatically
34
+ *
35
+ * @typedef {{solution: Solution}} ProgramaticContents
36
+ */
37
+
38
+ /**
39
+ * Contents of a multifile submission expressed as dictionary of keys in the form
40
+ * `solution[content[the-filename-goes-here]]`
41
+ *
42
+ * @typedef {object} MutifileContents
43
+ */
44
+
45
+ /**
46
+ * @typedef {ClassicContents|ProgramaticContents|MutifileContents} Contents
47
+ */
48
+
49
+ /**
50
+ * @typedef {Contents & {client_result?: SubmissionClientResult}} Submission
30
51
  */
31
52
 
32
53
  /**
@@ -0,0 +1,85 @@
1
+ mumuki.gamification = (() => {
2
+ class Formula {
3
+ static get QUADRATIC_COEFFICIENT() { return 25; }
4
+ static get LINEAR_COEFFICIENT() { return 100; }
5
+ static get CONSTANT_TERM() { return -125; }
6
+ }
7
+
8
+ class LevelProgression {
9
+ constructor(currentExp) {
10
+ this.currentExp = currentExp;
11
+ }
12
+
13
+ expToLevelUp() {
14
+ return this.baseExpNextLevel() - this.currentExp;
15
+ }
16
+
17
+ baseExpNextLevel() {
18
+ return this.expFor(this.currentLevel() + 1);
19
+ }
20
+
21
+ expFor(level) {
22
+ const ax2 = Formula.QUADRATIC_COEFFICIENT * Math.pow(level, 2);
23
+ const bx = Formula.LINEAR_COEFFICIENT * level;
24
+ const c = Formula.CONSTANT_TERM;
25
+
26
+ return ax2 + bx + c;
27
+ }
28
+
29
+ currentLevel() {
30
+ return this.levelFor(this.currentExp);
31
+ }
32
+
33
+ levelFor(exp) {
34
+ const a = Formula.QUADRATIC_COEFFICIENT;
35
+ const b = Formula.LINEAR_COEFFICIENT;
36
+ const c = Formula.CONSTANT_TERM;
37
+
38
+ return Math.floor((-b + Math.sqrt(Math.pow(b, 2) - 4 * a * (c - exp))) / (2 * a));
39
+ }
40
+
41
+ triggersLevelChange(exp) {
42
+ return this.levelFor(exp) !== this.currentLevel();
43
+ }
44
+
45
+ currentLevelProgress() {
46
+ return (this.currentExp - this.baseExpCurrentLevel()) / (this.baseExpNextLevel() - this.baseExpCurrentLevel());
47
+ }
48
+
49
+ baseExpCurrentLevel() {
50
+ return this.expFor(this.currentLevel());
51
+ }
52
+
53
+ setExpMessage(exp) {
54
+ let expGained = exp - this.currentExp;
55
+
56
+ if (expGained > 0) {
57
+ this.currentExp = exp;
58
+ $('#mu-exp-points').html(expGained);
59
+ $('#mu-level-number').html(this.currentLevel());
60
+ }
61
+ }
62
+ }
63
+
64
+ function _setUpCurrentLevelProgression() {
65
+ mumuki.gamification._currentLevelProgression = new LevelProgression(currentExp());
66
+ }
67
+
68
+ function currentExp() {
69
+ return $('#mu-current-exp').val();
70
+ }
71
+
72
+ return {
73
+ Formula,
74
+ LevelProgression,
75
+
76
+ _setUpCurrentLevelProgression,
77
+
78
+ /** @type {LevelProgression} */
79
+ _currentLevelProgression: null
80
+ };
81
+ })();
82
+
83
+ mumuki.load(() => {
84
+ mumuki.gamification._setUpCurrentLevelProgression();
85
+ });
@@ -0,0 +1,145 @@
1
+ mumuki.load(() => {
2
+ mumuki.kindergarten = {
3
+ initialize() {
4
+ this.speech.verifyBrowserSupport();
5
+ this.hint.showOrHideExpandHintButton();
6
+ this.context.showNextOrCloseButton();
7
+ this.result.configureCallbacks();
8
+ },
9
+ speech: {
10
+ _isPlaying: false,
11
+ click(selector, locale) {
12
+ if (this._isPlaying) {
13
+ this.stop();
14
+ } else {
15
+ this.play(selector, locale);
16
+ }
17
+ },
18
+ play(selector, locale) {
19
+ const msg = new SpeechSynthesisUtterance();
20
+ msg.text = $(selector).text();
21
+ msg.lang = locale.split('_')[0];
22
+ msg.pitch = 0;
23
+ msg.onend = () => this.stop();
24
+ this._action('play', 'stop', true, (speech) => speech.speak(msg))
25
+ },
26
+ stop() {
27
+ this._action('stop', 'play', false, (speech) => speech.cancel())
28
+ },
29
+ _action(add, remove, isPlaying, callback = () => {}) {
30
+ callback(window.speechSynthesis);
31
+ const $button = $('.mu-kindergarten-play-description')
32
+ $button.find(`.mu-kindergarten-${add}`).addClass('hidden');
33
+ $button.find(`.mu-kindergarten-${remove}`).removeClass('hidden');
34
+ this._isPlaying = isPlaying;
35
+ },
36
+ verifyBrowserSupport() {
37
+ if (!window.speechSynthesis) {
38
+ const $button = $('.mu-kindergarten-play-description')
39
+ $button.prop('disabled', true);
40
+ $button.css('cursor', 'not-allowed');
41
+ this._action('play', 'stop', false)
42
+ }
43
+ }
44
+ },
45
+ hint: {
46
+ toggle() {
47
+ $('.mu-kindergarten-light-speech-bubble').toggleClass('open');
48
+ },
49
+ toggleMedia() {
50
+ const $hintMedia = $('.mu-kindergarten-hint-media');
51
+ const $i = $('.expand-or-collapse-hint-media').children('i');
52
+ $i.toggleClass('fa-caret-up').toggleClass('fa-caret-down');
53
+ $hintMedia.toggleClass('closed');
54
+ },
55
+ showOrHideExpandHintButton() {
56
+ const $button = $('.expand-or-collapse-hint-media');
57
+ const $hintMedia = $('.mu-kindergarten-hint-media');
58
+ if (!$hintMedia.get(0)) $button.addClass('hidden');
59
+ },
60
+ },
61
+ context: {
62
+ showContext() {
63
+ mumuki.kids.showContext();
64
+ this._showFirstSlideImage();
65
+ },
66
+ nextSlide() {
67
+ this._clickButton('next');
68
+ },
69
+ prevSlide() {
70
+ this._clickButton('prev');
71
+ },
72
+ _imageSlides() {
73
+ return $('.mu-kindergarten-context-image-slides');
74
+ },
75
+ _activeSlideImage() {
76
+ return this._imageSlides().find('.active');
77
+ },
78
+ _clickButton(prevOrNext) {
79
+ this._activeSlideImage().removeClass('active')[prevOrNext]().addClass('active');
80
+ this.showNextOrCloseButton();
81
+ this._hidePreviousButtonIfFirstImage();
82
+ },
83
+ showNextOrCloseButton() {
84
+ const $next = $('.mu-kindergarten-modal-button.mu-next');
85
+ const $close = $('.mu-kindergarten-modal-button.mu-close');
86
+ const isLastChild = this._activeSlideImage().is(':last-child');
87
+ this._addClassIf($next, 'hidden', () => isLastChild);
88
+ this._addClassIf($close, 'hidden', () => !isLastChild);
89
+ },
90
+ _hidePreviousButtonIfFirstImage() {
91
+ const $prev = $('.mu-kindergarten-modal-button.mu-previous');
92
+ this._addClassIf($prev, 'hidden', () => this._activeSlideImage().is(':first-child'))
93
+ },
94
+ _showFirstSlideImage() {
95
+ this._imageSlides().find('img').each((i, el) => this._addClassIf($(el), 'active', () => i === 0))
96
+ this.showNextOrCloseButton();
97
+ this._hidePreviousButtonIfFirstImage();
98
+ },
99
+ _addClassIf(element, clazz, criteria) {
100
+ if (criteria()) {
101
+ element.addClass(clazz)
102
+ } else {
103
+ element.removeClass(clazz);
104
+ }
105
+ }
106
+ },
107
+ result: {
108
+ configureCallbacks() {
109
+ mumuki.kids.resultAction.passed = this._showOnSuccessPopup;
110
+ mumuki.kids.resultAction.passed_with_warnings = this._showOnSuccessPopup;
111
+
112
+ mumuki.kids.resultAction.failed = this._showOnFailurePopup;
113
+ mumuki.kids.resultAction.errored = this._showOnFailurePopup;
114
+ mumuki.kids.resultAction.pending = this._showOnFailurePopup;
115
+ },
116
+ _showOnSuccessPopup(data) {
117
+ $('.submission-results').html(data.title_html);
118
+ mumuki.kids._showOnSuccessPopup(data);
119
+ $('#kids-results .modal-content').addClass(data.status);
120
+ },
121
+ _showOnFailurePopup(data) {
122
+ mumuki.kids._getResultsAbortedModal().modal();
123
+ $('#kids-results-aborted .modal-header').html(data.title_html);
124
+ $('#kids-results-aborted .modal-content').addClass(data.status);
125
+ mumuki.presenterCharacter.playAnimation('failure', mumuki.kids._getCharacterImage());
126
+ }
127
+ }
128
+ };
129
+
130
+ $(document).ready(() => {
131
+
132
+ if ($('.mu-kindergarten').get(0)) {
133
+
134
+ mumuki.resize(() => {
135
+ mumuki.kids.scaleState($('.mu-kids-states'), 100);
136
+ mumuki.kids.scaleBlocksArea($('.mu-kids-blocks'));
137
+ })
138
+
139
+ mumuki.kindergarten.initialize()
140
+
141
+ }
142
+
143
+ })
144
+
145
+ });
@@ -1,12 +1,13 @@
1
1
  mumuki.load(function() {
2
2
  let $userForm = $("#mu-user-form");
3
3
  let $userAvatar = $('#mu-user-avatar');
4
- let $editButton = $('#mu-edit-profile-btn');
4
+ let $editButton = $('.mu-edit-profile-btn');
5
5
  let $avatarPicker = $('#mu-avatar-picker');
6
- let $avatarItem = $('.mu-avatar-item');
6
+ let $avatarItem = $('.mu-avatar-item:not(.mu-locked)');
7
7
 
8
8
  let userImage = "";
9
9
  let avatarId = "";
10
+ let avatarType = "";
10
11
 
11
12
  let originalData = $userForm.serialize();
12
13
  let originalProfilePicture = $userAvatar.attr('src');
@@ -15,14 +16,18 @@ mumuki.load(function() {
15
16
  toggleEditButtonIfThereAreChanges();
16
17
  });
17
18
 
18
- $avatarItem.on('click', function() {
19
- $userAvatar.attr('src', $(this).attr('src'));
20
- $avatarPicker.modal('hide');
19
+ $avatarItem.on('keypress click', function(e) {
20
+ onClickOrSpacebarOrEnter($(this), e, function() {
21
+ $userAvatar.attr('src', $(this).attr('src'));
22
+ $avatarPicker.modal('hide');
21
23
 
22
- const clickedAvatarId = $(this).attr('mu-avatar-id');
23
- avatarId = clickedAvatarId || "";
24
+ const clickedAvatarId = $(this).attr('mu-avatar-id');
25
+ const clickedAvatarType = $(this).attr('type');
26
+ avatarId = clickedAvatarId || "";
27
+ avatarType = clickedAvatarType || "";
24
28
 
25
- toggleEditButtonIfThereAreChanges();
29
+ toggleEditButtonIfThereAreChanges();
30
+ });
26
31
  });
27
32
 
28
33
  function toggleEditButtonIfThereAreChanges() {
@@ -41,18 +46,20 @@ mumuki.load(function() {
41
46
 
42
47
  const avatarChanged = () => $userAvatar.attr('src') !== originalProfilePicture;
43
48
 
44
- $('#mu-user-image').on('click', function(){
45
- userImage = $userAvatar.attr('src');
49
+ $('#mu-user-image').on('keypress click', function(e){
50
+ onClickOrSpacebarOrEnter($(this), e, function() {
51
+ userImage = $userAvatar.attr('src');
52
+ });
46
53
  });
47
54
 
48
55
  $userForm.on('submit', function(){
49
56
  if (userImage) {
50
57
  setImageUrl($(this), userImage);
51
- setAvatarId($(this), "");
58
+ setAvatarIdAndType($(this), "", "");
52
59
  }
53
60
 
54
61
  if (avatarId) {
55
- setAvatarId($(this), avatarId);
62
+ setAvatarIdAndType($(this), avatarId, avatarType);
56
63
  }
57
64
  });
58
65
 
@@ -60,12 +67,20 @@ mumuki.load(function() {
60
67
  form.append(`<input type="hidden" name="user[image_url]" value="${url}"/>`);
61
68
  }
62
69
 
63
- function setAvatarId(form, id) {
64
- form.append(`<input type="hidden" name="user[avatar_id]" value=${id}/>`);
70
+ function setAvatarIdAndType(form, id, type) {
71
+ form.append(`<input type="hidden" name="user[avatar_id]" value="${id}"/>`);
72
+ form.append(`<input type="hidden" name="user[avatar_type]" value="${type}"/>`);
65
73
  }
66
74
 
67
- $("#mu-edit-avatar-icon").on('click', function(){
68
- $avatarPicker.modal();
75
+ $("#mu-edit-avatar-icon").on('keypress click', function(e) {
76
+ onClickOrSpacebarOrEnter($(this), e, function() {
77
+ $avatarPicker.modal();
78
+ });
69
79
  });
70
80
 
81
+ function onClickOrSpacebarOrEnter(element, e, func) {
82
+ if (e.which === 13 || e.which === 32 || e.type === 'click') {
83
+ func.apply(element);
84
+ }
85
+ }
71
86
  });