diff --git a/app/components/inputs/pinyin-input/component.js b/app/components/inputs/pinyin-input/component.js index 659c663..13e60c4 100644 --- a/app/components/inputs/pinyin-input/component.js +++ b/app/components/inputs/pinyin-input/component.js @@ -92,6 +92,8 @@ const PinyinInput = Ember.Component.extend({ } }); -export default PinyinInput.reopenClass({ +PinyinInput.reopenClass({ positionalParams: ['value'] }); + +export default PinyinInput; diff --git a/app/components/media/audio-player/component.js b/app/components/media/audio-player/component.js new file mode 100644 index 0000000..523910a --- /dev/null +++ b/app/components/media/audio-player/component.js @@ -0,0 +1,50 @@ +import Ember from 'ember'; +import firebase from 'firebase'; + +const { + isPresent, + computed: { notEmpty } +} = Ember; + +export default Ember.Component.extend({ + classNames: ['row'], + hifi: Ember.inject.service(), + + hasAudio: notEmpty('audioUrl'), + + init() { + try { + this.set("fbRef", firebase.app().storage().ref()); + } catch(_) { + this.set("fbRef", undefined); + } + this._super(...arguments); + }, + + didReceiveAttrs() { + if(isPresent(this.get('fbRef'))) { + this.set('audioUrl', undefined); + const cloudUrl = this.get('model'); + + if(isPresent(cloudUrl)) { + this.get("fbRef") + .child(cloudUrl) + .getDownloadURL() + .then(url => { + this.set('audioUrl', url) + }) + .catch(() => { + this.set('audioUrl', undefined); + }) + } else { + this.set('audioUrl', undefined); + } + } + }, + + actions: { + play() { + this.get('hifi').play(this.get('audioUrl')); + } + } +}); diff --git a/app/components/media/audio-player/styles.styl b/app/components/media/audio-player/styles.styl new file mode 100644 index 0000000..90a84ba --- /dev/null +++ b/app/components/media/audio-player/styles.styl @@ -0,0 +1,15 @@ +& + border: none + +.btn + margin: 0.25em + display: block + border-radius: 50% + padding: 0.5em + background: rgba(off-white, 0.8) + height: 30px + width: 30px + transition: all 0.2s ease-in-out + +.btn:hover + background: rgba(off-white, 1) diff --git a/app/components/media/audio-player/template.hbs b/app/components/media/audio-player/template.hbs new file mode 100644 index 0000000..d86b24a --- /dev/null +++ b/app/components/media/audio-player/template.hbs @@ -0,0 +1,5 @@ +{{#if hasAudio}} + {{ui/icon-button + click=(action "play") + leftIcon="play"}} +{{/if}} diff --git a/app/components/study/modes/eng-to-pin/component.js b/app/components/study/modes/eng-to-pin/component.js index aef7334..5b7b8d9 100644 --- a/app/components/study/modes/eng-to-pin/component.js +++ b/app/components/study/modes/eng-to-pin/component.js @@ -1,10 +1,12 @@ import Ember from 'ember'; - -const STRIP_PUNCTUATION = /[.,\/#!$%\^&\*;:{}=\-_?`~()]/g; +import { + isCorrectAnswer +} from 'wo-pinyin/utils/study-utils'; export default Ember.Component.extend({ classNames: ['row', 'card-2'], currentAnswer: '', + showingAnswer: false, didInsertElement() { this.$('textarea').focus(); @@ -28,26 +30,24 @@ export default Ember.Component.extend({ this.set('wasAnswered', undefined); this.set('isCorrect', undefined); this.set('currentAnswer', ''); + this.set('showingAnswer', false); }, actions: { handleUpdate(str) { this.set('wasAnswered', false); - this.set("currentAnswer", str); + this.set('currentAnswer', str); }, async submit() { - const pinyin = await this.get('model.pinyin'); - - const answer = this.get('currentAnswer') - .replace(STRIP_PUNCTUATION, "") - .toLowerCase(); + const submission = this.get('currentAnswer'); + const answer = await this.get('model.pinyin'); - const solution = pinyin.get("text") - .replace(STRIP_PUNCTUATION, "") - .toLowerCase(); + this.processAnswer(isCorrectAnswer(submission, answer.get('text'))); + }, - this.processAnswer(answer === solution); + showAnswer() { + this.set('showingAnswer', true); } } }); diff --git a/app/components/study/modes/eng-to-pin/styles.styl b/app/components/study/modes/eng-to-pin/styles.styl index 8af7c36..7be850a 100644 --- a/app/components/study/modes/eng-to-pin/styles.styl +++ b/app/components/study/modes/eng-to-pin/styles.styl @@ -2,12 +2,14 @@ background: dope-blue margin-bottom: 1em +.question-container + margin-bottom: 1em + .content padding: 1em .english font-size: 1.3em - margin-bottom: 1em color: white height: 1.3em @@ -33,3 +35,31 @@ textarea .correct color: dope-blue + +.ui-container + color: white + background: rgba(white, 0.2) + height: 4em + .btn + background: none + .btn-container + width: 50% + border-right: 1px dashed white + .btn-container:last-child + border-right: 0 + +.answer + pointer-events: none + height: 0 + background: dope-pink + opacity: 0 + font-size: 1.4em + color: white + transition: all 0.2s ease-in-out + padding: 0 0.5em + +.answer.showing + height: auto + opacity: 1 + padding: 1em + padding: 1em 0.5em diff --git a/app/components/study/modes/eng-to-pin/template.hbs b/app/components/study/modes/eng-to-pin/template.hbs index 400cb42..c122512 100644 --- a/app/components/study/modes/eng-to-pin/template.hbs +++ b/app/components/study/modes/eng-to-pin/template.hbs @@ -1,8 +1,10 @@
- - {{model.english.text}} - +
+ + {{model.english.text}} + +
{{inputs/pinyin-input currentAnswer class='solution-field' @@ -10,6 +12,19 @@ update=(action "handleUpdate")}}
+
+
+ {{media/audio-player model=model.audioUrl class='audio-btn'}} +
+
+ {{ui/icon-button + class='show-answer-btn' + click=(action (toggle "showingAnswer" this)) + label="Show Answer" + rightIcon='info'}} +
+
+ {{#if wasAnswered}}
{{#if isCorrect}} @@ -19,4 +34,8 @@ {{/if}}
{{/if}} + +
+ {{model.pinyin.text}} +
diff --git a/app/components/study/study-canvas/component.js b/app/components/study/study-canvas/component.js index adbf04a..7e17ea3 100644 --- a/app/components/study/study-canvas/component.js +++ b/app/components/study/study-canvas/component.js @@ -1,24 +1,26 @@ import Ember from 'ember'; - -const { - notEmpty -} = Ember.computed; +import computed from 'ember-computed-decorators'; +import _ from 'lodash'; export default Ember.Component.extend({ - hasFlashCards: notEmpty('flashCards'), + @computed('model.@each.{id}') + shuffledCards(collection) { + return _.shuffle(collection.toArray()); + }, didInsertElement() { this.setupNextStudyCard(); }, setupNextStudyCard() { - const flashCards = this.get('model').toArray(); + const flashCards = this.get('shuffledCards'); const current = this.get('current'); let flashCard; if(Ember.isPresent(current)) { const currentIndex = flashCards.indexOf(current.flashCard); + if(currentIndex + 1 > flashCards.length-1) { flashCard = flashCards[0]; } else { diff --git a/app/components/ui/icon-button/styles.styl b/app/components/ui/icon-button/styles.styl index d6e5b9f..3083e97 100644 --- a/app/components/ui/icon-button/styles.styl +++ b/app/components/ui/icon-button/styles.styl @@ -6,6 +6,9 @@ &:active opacity: 0.5 +.space + margin: 0.25em + .label font-size: 1rem diff --git a/app/components/ui/icon-button/template.hbs b/app/components/ui/icon-button/template.hbs index 438ac10..706aa03 100644 --- a/app/components/ui/icon-button/template.hbs +++ b/app/components/ui/icon-button/template.hbs @@ -1,11 +1,11 @@ -
+
{{#if loading}}
{{fa-icon 'spinner'}}
{{else}} {{#if hasLeftIcon}} -
+
{{fa-icon leftIcon}}
{{/if}} @@ -23,7 +23,7 @@ {{/if}} {{#if hasRightIcon}} -
+
{{fa-icon rightIcon}}
{{/if}} diff --git a/app/routes/lesson.js b/app/routes/lesson.js index df32d4c..bc67bee 100644 --- a/app/routes/lesson.js +++ b/app/routes/lesson.js @@ -104,7 +104,7 @@ export default Ember.Route.extend({ const app = firebase.app(); var storageRef = app.storage().ref(); - const path = `audio/${uuid()}.wav`; + const path = `audio/${uuid()}.webm`; var audioRef = storageRef.child(path); return audioRef.put(blob).then(() => { diff --git a/app/styles/layout.styl b/app/styles/layout.styl index cd07419..e57cd07 100644 --- a/app/styles/layout.styl +++ b/app/styles/layout.styl @@ -16,6 +16,7 @@ .right align-items: flex-end + justify-content: flex-end .bottom align-items: flex-end diff --git a/app/utils/study-utils.js b/app/utils/study-utils.js new file mode 100644 index 0000000..f6a6a4c --- /dev/null +++ b/app/utils/study-utils.js @@ -0,0 +1,13 @@ +const STRIP_PUNCTUATION = /[.,\/#!$%\^&\*;:{}=\-_?`~()]/g; + +const trimStripLower = str => + str + .replace(STRIP_PUNCTUATION, "") + .toLowerCase() + .trim(); + +const isCorrectAnswer = (submission, answer) => trimStripLower(submission) === trimStripLower(answer) + +export { + isCorrectAnswer +} diff --git a/tests/integration/components/media/audio-player/component-test.js b/tests/integration/components/media/audio-player/component-test.js new file mode 100644 index 0000000..d3b29dc --- /dev/null +++ b/tests/integration/components/media/audio-player/component-test.js @@ -0,0 +1,16 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('media/audio-player', 'Integration | Component | media/audio player', { + integration: true +}); + +test('it renders', function(assert) { + + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... }); + + this.render(hbs`{{media/audio-player}}`); + + assert.equal(this.$().text().trim(), ''); +}); diff --git a/tests/integration/components/study/modes/eng-to-pin/component-test.js b/tests/integration/components/study/modes/eng-to-pin/component-test.js index c1b025b..17820c7 100644 --- a/tests/integration/components/study/modes/eng-to-pin/component-test.js +++ b/tests/integration/components/study/modes/eng-to-pin/component-test.js @@ -7,10 +7,7 @@ moduleForComponent('study/modes/eng-to-pin', 'Integration | Component | study/mo test('it renders', function(assert) { - // Set any properties with this.set('myProperty', 'value'); - // Handle any actions with this.on('myAction', function(val) { ... }); - this.render(hbs`{{study/modes/eng-to-pin}}`); - assert.equal(this.$().text().trim(), ''); + assert.equal(this.$().text().trim(), 'Show Answer'); });