diff --git a/frontend/src/client/quasar.conf.js b/frontend/src/client/quasar.conf.js index 3f4b482..9f78536 100644 --- a/frontend/src/client/quasar.conf.js +++ b/frontend/src/client/quasar.conf.js @@ -102,9 +102,8 @@ module.exports = function (ctx) { short_name: 'Blue Eel', description: 'Learn to read', display: 'standalone', - orientation: 'portrait', - background_color: '#81d4fa', - theme_color: '#b3e5fc', + background_color: '#178CA4', + theme_color: '#178CA4', icons: [ { 'src': 'statics/icons/icon-128x128.png', diff --git a/frontend/src/client/src/components/EelCanvas.vue b/frontend/src/client/src/components/EelCanvas.vue index 6f3a7c9..274da41 100644 --- a/frontend/src/client/src/components/EelCanvas.vue +++ b/frontend/src/client/src/components/EelCanvas.vue @@ -1,8 +1,21 @@ diff --git a/frontend/src/client/src/pages/Index.vue b/frontend/src/client/src/pages/Index.vue index 79a4663..ef762a1 100644 --- a/frontend/src/client/src/pages/Index.vue +++ b/frontend/src/client/src/pages/Index.vue @@ -10,7 +10,7 @@

Writing made simple!

- +
diff --git a/frontend/src/client/src/pages/Letter.vue b/frontend/src/client/src/pages/Letter.vue new file mode 100644 index 0000000..92de826 --- /dev/null +++ b/frontend/src/client/src/pages/Letter.vue @@ -0,0 +1,19 @@ + + + diff --git a/frontend/src/client/src/pages/Pattern.vue b/frontend/src/client/src/pages/Pattern.vue deleted file mode 100644 index eb8bb67..0000000 --- a/frontend/src/client/src/pages/Pattern.vue +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/frontend/src/client/src/pages/Tracing.vue b/frontend/src/client/src/pages/Tracing.vue deleted file mode 100644 index 0881fa7..0000000 --- a/frontend/src/client/src/pages/Tracing.vue +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/frontend/src/client/src/pages/Word.vue b/frontend/src/client/src/pages/Word.vue index 2e688c1..dd31705 100644 --- a/frontend/src/client/src/pages/Word.vue +++ b/frontend/src/client/src/pages/Word.vue @@ -1,6 +1,6 @@ @@ -11,6 +11,9 @@ export default { name: 'PageWord', components: { EelPractice + }, + created: function () { + this.$store.dispatch('common/changeLevel', 'word') } } diff --git a/frontend/src/client/src/router/routes.js b/frontend/src/client/src/router/routes.js index 2aa3ab6..0daaabb 100644 --- a/frontend/src/client/src/router/routes.js +++ b/frontend/src/client/src/router/routes.js @@ -4,9 +4,7 @@ const routes = [ component: () => import('layouts/MyLayout.vue'), children: [ { name: 'home', path: '', component: () => import('pages/Index.vue') }, - { name: 'tracing', path: 'tracing', component: () => import('pages/Tracing.vue') }, - { name: 'pattern', path: 'pattern', component: () => import('pages/Pattern.vue') }, - { name: 'freeform', path: 'freeform', component: () => import('pages/Freeform.vue') }, + { name: 'letter', path: 'letter', component: () => import('pages/Letter.vue') }, { name: 'word', path: 'word', component: () => import('pages/Word.vue') }, { name: 'congratulations', path: 'congratulations', component: () => import('pages/Congratulations.vue') } ] diff --git a/frontend/src/client/src/store/common/actions.js b/frontend/src/client/src/store/common/actions.js index e5182ec..4bcd730 100644 --- a/frontend/src/client/src/store/common/actions.js +++ b/frontend/src/client/src/store/common/actions.js @@ -1,5 +1,7 @@ import { axios } from 'boot/axios' +const STALE_REINTRODUCE_COUNT = 2 + // ////////////////// // FETCH FROM CLOUD // ////////////////// @@ -7,14 +9,27 @@ export function fetchLetter (ctx, letter) { if (ctx.state.patterns[letter] == null) { axios.get(`https://eel3-data.s3.us-east-2.amazonaws.com/patterns/${letter}/master.json`) .then(res => { - ctx.commit('setPattern', res.data) + let pattern = { + boundary: res.data.boundary || { + top: 0, + ascenderLine: res.data.dimensions.upperGuidePixels, + capLine: res.data.dimensions.upperGuidePixels, + meanLine: res.data.dimensions.middleGuidePixels, + baseLine: res.data.dimensions.lowerGuidePixels, + beardLine: res.data.dimensions.lowestGuidePixels, + bottom: res.data.dimensions.heightPixels + }, + letter: res.data.letter, + path: res.data.path + } + ctx.commit('setPattern', pattern) }).catch(err => err) // TODO: Properly catch error } } export function fetchSequence (ctx) { let sequence = { - letters: [ + expressions: [ ['b', 'c', 'd', 'f'], ['g', 'h', 'l', 'r', 's', 't'], ['a', 'e', 'i', 'o', 'u'], @@ -23,12 +38,12 @@ export function fetchSequence (ctx) { ['q', 'w', 'x', 'y', 'z'] ], words: [ - ['feature', 'coming', 'soon'] + ['hi', 'have', 'fun'] ] } - if (ctx.state.user != null && ctx.state.user.sequenceId != null && ctx.state.user.sequenceId === 'Tm9haCBH') { + if (ctx.state.user != null && ctx.state.user.sequence != null && ctx.state.user.sequence === 'Tm9haCBH') { sequence = { - letters: [ + expressions: [ ['n', 't', 'm', 'f'], ['i', 'a', 'c'] ], @@ -38,11 +53,22 @@ export function fetchSequence (ctx) { ] } } - if (ctx.state.user != null && ctx.state.user.sequenceId != null && ctx.state.user.sequenceId === 'jim') { + if (ctx.state.user != null && ctx.state.user.sequence != null && ctx.state.user.sequence === 'QnJlbmRh') { + sequence = { + expressions: [ + ['n', 'b', 's', 'r', 'k', 'e', 'p'] + ], + words: [ + ['trains', 'are', 'cool'], + ['planes', 'fly', 'high'] + ] + } + } + if (ctx.state.user != null && ctx.state.user.sequence != null && ctx.state.user.sequence === 'jim') { sequence = { - letters: [ - ['a', 'b'], - ['c', 'd'] + expressions: [ + ['j', 'i', 'm'], + ['f'] ], words: [ ['jim', 'rocks'] @@ -52,16 +78,32 @@ export function fetchSequence (ctx) { ctx.commit('setStabilizeCount', 1) ctx.commit('setReintroduceCount', 1) } + for (let i = 0; i < sequence.expressions.length; i++) { + let bunch = sequence.expressions[i] + for (let j = 0; j < bunch.length; j++) { + ctx.dispatch('fetchLetter', bunch[j]) + } + } + for (let i = 0; i < sequence.words.length; i++) { + let bunch = sequence.words[i] + for (let j = 0; j < bunch.length; j++) { + let word = bunch[j] + for (let z = 0; z < word.length; z++) { + ctx.dispatch('fetchLetter', word[j]) + } + } + } ctx.commit('setSequence', sequence) + ctx.commit('resetState', ctx.state.level) } // ////////////////// // UPLOAD TO CLOUD // ////////////////// export function uploadPractice (ctx, update) { - let sequenceId = ctx.state.user.sequenceId - if (sequenceId == null || sequenceId === '') { - sequenceId = 'Standard' + let sequence = ctx.state.user.sequence + if (sequence == null || sequence === '') { + return } let data = { @@ -78,7 +120,7 @@ export function uploadPractice (ctx, update) { } } let filename = `${timestamp}.json` - axios.put(`https://eel3-data.s3.us-east-2.amazonaws.com/practice/${sequenceId}/${filename}`, data, config) + axios.put(`https://eel3-data.s3.us-east-2.amazonaws.com/practice/${sequence}/${filename}`, data, config) } // ////////////////// @@ -89,66 +131,89 @@ export function loginUser (ctx, user) { ctx.dispatch('fetchSequence') } -export function startPractice (ctx, level) { - ctx.commit('resetState', level) - ctx.dispatch('nextLetter') +export function changeLevel (ctx, level) { + ctx.commit('setLevel', level) +} + +export function startPractice (ctx) { + ctx.commit('resetState') + ctx.dispatch('nextExpression') } export function practiceAttempted (ctx, update) { - ctx.commit('incrementAttempts', update.letter) + ctx.commit('incrementAttempts', update.expression) if (ctx.state.user.uploading) { + update.technique = ctx.state.user.technique + update.level = ctx.state.level ctx.dispatch('uploadPractice', update) } if (update.success) { - ctx.commit('recordSuccess', update.letter) - ctx.dispatch('nextLetter') - ctx.dispatch('resetLetter', update.letter) - } else if (ctx.state.history[update.letter].attempts >= ctx.state.retryLimit) { - ctx.dispatch('nextLetter') - ctx.dispatch('resetLetter', update.letter) - } - if (ctx.state.history[update.letter].singleAttemptSuccesses >= ctx.state.stabilizeCount) { - ctx.dispatch('stabilizeLetter', update.letter) + ctx.commit('recordSuccess', update.expression) + ctx.commit('resetFail') + if (ctx.state.history[update.expression].singleAttemptSuccesses >= ctx.state.stabilizeCount) { + ctx.commit('stabilizeExpression', update.expression) + } + ctx.dispatch('nextExpression') + ctx.commit('resetExpression', update.expression) + } else if (ctx.state.history[update.expression].attempts >= ctx.state.retryLimit) { + ctx.commit('incrementFail') + if (ctx.state.consecutiveFails >= ctx.state.activeQueue.length) { + ctx.dispatch('staleFail') + } + ctx.dispatch('nextExpression') + ctx.commit('resetExpression', update.expression) } } -export function nextLetter (ctx) { - if (ctx.state.activeQueue.length < 2 && ctx.state.pendingQueue.length > 0) { - ctx.dispatch('activateLetters') - ctx.dispatch('nextLetter') - } else { - ctx.commit('nextLetter') +export function nextExpression (ctx) { + if (ctx.state.activeQueue.length < 1) { + ctx.dispatch('activateExpressions') } + ctx.commit('nextExpression') } -export function resetLetter (ctx, letter) { - ctx.commit('resetLetter', letter) -} +export function activateExpressions (ctx) { + let countToReintroduce = 0 -export function stabilizeLetter (ctx, letter) { - ctx.commit('stabilizeLetter', letter) -} + if (ctx.state.pendingQueue.length > 0) { + ctx.state.isFinalReview = false + ctx.commit('activateNextBunch') + countToReintroduce = Math.min(ctx.state.reintroduceCount, ctx.state.stableQueue.length) + } else if (!ctx.state.isFinalReview) { + ctx.state.isFinalReview = true + countToReintroduce = ctx.state.stableQueue.length + } -export function activateLetters (ctx) { - let countToAdd = Math.min(ctx.state.reintroduceCount, ctx.state.stableQueue.length) - for (let i = 0; i < countToAdd; i++) { - const newLetter = ctx.state.stableQueue[0] - ctx.dispatch('fetchLetter', newLetter) - ctx.commit('reintroduceLetter', newLetter) - } - for (let i = 0; i < ctx.state.pendingQueue[0].length; i++) { - const newLetter = ctx.state.pendingQueue[0][i] - for (let j = 0; j < newLetter.length; j++) { - ctx.dispatch('fetchLetter', newLetter[j]) - } + for (let i = 0; i < countToReintroduce; i++) { + const newExpression = ctx.state.stableQueue[0] + ctx.commit('reintroduceExpression', newExpression) } - ctx.commit('activateNextBunch') } -export function lowActive (ctx, level) { - +export function staleFail (ctx) { + if (ctx.state.staleFails > (ctx.state.stableQueue.length / STALE_REINTRODUCE_COUNT)) { + ctx.dispatch('activateExpressions') + ctx.commit('resetStaleFail') + } else if (ctx.state.stableQueue.length >= STALE_REINTRODUCE_COUNT) { + for (let i = 0; i < STALE_REINTRODUCE_COUNT; i++) { + const newExpression = ctx.state.stableQueue[0] + ctx.commit('reintroduceExpression', newExpression) + } + } else { + ctx.dispatch('activateExpressions') + } + ctx.commit('recordStaleFail') + ctx.commit('resetFail') } -export function staleFail (ctx, level) { - +export function completedTechnique (ctx) { + if (ctx.state.user.technique === 'Tracing') { + ctx.commit('setTechnique', 'Pattern') + } else if (this.level === 'pattern') { + ctx.commit('setTechnique', 'Freeform') + } else { + ctx.commit('setTechnique', 'Tracing') + this.$router.push({ name: 'congratulations' }) + } + ctx.dispatch('startPractice') } diff --git a/frontend/src/client/src/store/common/mutations.js b/frontend/src/client/src/store/common/mutations.js index d61a1fe..332052c 100644 --- a/frontend/src/client/src/store/common/mutations.js +++ b/frontend/src/client/src/store/common/mutations.js @@ -8,6 +8,14 @@ export function setSequence (state, sequence) { state.sequence = sequence } +export function setLevel (state, level) { + state.level = level +} + +export function setTechnique (state, technique) { + Vue.set(state.user, 'technique', technique) +} + export function setRetryLimit (state, limit) { state.retryLimit = limit } @@ -24,73 +32,91 @@ export function setPattern (state, pattern) { Vue.set(state.patterns, pattern.letter, pattern) } -export function resetLetter (state, letter) { - let letterHistory = state.history[letter] || {} - letterHistory.previousAttempts = letterHistory.attempts || 0 - if (letterHistory.previousAttempts > 1) { - letterHistory.totalSingleAttemptSuccesses = 0 +export function resetExpression (state, expression) { + let expressionHistory = state.history[expression] || {} + expressionHistory.previousAttempts = expressionHistory.attempts || 0 + if (expressionHistory.previousAttempts > 1) { + expressionHistory.totalSingleAttemptSuccesses = 0 } - letterHistory.attempts = 0 - Vue.set(state.history, letter, letterHistory) + expressionHistory.attempts = 0 + Vue.set(state.history, expression, expressionHistory) } -export function incrementAttempts (state, letter) { - let letterHistory = state.history[letter] || {} - letterHistory.attempts = letterHistory.attempts + 1 || 1 - letterHistory.totalAttempts = letterHistory.totalAttempts + 1 || 1 - Vue.set(state.history, letter, letterHistory) +export function incrementAttempts (state, expression) { + let expressionHistory = state.history[expression] || {} + expressionHistory.attempts = expressionHistory.attempts + 1 || 1 + expressionHistory.totalAttempts = expressionHistory.totalAttempts + 1 || 1 + Vue.set(state.history, expression, expressionHistory) } -export function recordSuccess (state, letter) { - let letterHistory = state.history[letter] || {} - letterHistory.success = true - if (letterHistory.attempts === 1) { - letterHistory.singleAttemptSuccesses = letterHistory.singleAttemptSuccesses + 1 || 1 - letterHistory.totalSingleAttemptSuccesses = letterHistory.totalSingleAttemptSuccesses + 1 || 1 +export function incrementFail (state) { + state.consecutiveFails = state.consecutiveFails + 1 || 1 +} + +export function resetFail (state) { + state.consecutiveFails = 0 +} + +export function recordStaleFail (state) { + state.staleFails = state.staleFails + 1 || 1 +} + +export function resetStaleFail (state) { + state.staleFails = 0 +} + +export function recordSuccess (state, expression) { + let expressionHistory = state.history[expression] || {} + expressionHistory.success = true + if (expressionHistory.attempts === 1) { + expressionHistory.singleAttemptSuccesses = expressionHistory.singleAttemptSuccesses + 1 || 1 + expressionHistory.totalSingleAttemptSuccesses = expressionHistory.totalSingleAttemptSuccesses + 1 || 1 + if (expressionHistory.totalAttempts === 1) { // Bonus for getting it right on your very first try + expressionHistory.singleAttemptSuccesses = expressionHistory.singleAttemptSuccesses + 1 + expressionHistory.totalSingleAttemptSuccesses = expressionHistory.totalSingleAttemptSuccesses + 1 + } } - Vue.set(state.history, letter, letterHistory) + Vue.set(state.history, expression, expressionHistory) } -export function nextLetter (state) { +export function nextExpression (state) { let next = state.activeQueue.shift() - if (state.letter != null && state.letter !== '') { - state.activeQueue.push(state.letter) - } - state.letter = next + state.expression = next + state.activeQueue.push(next) } -export function stabilizeLetter (state, letter) { - var index = state.activeQueue.indexOf(letter) +export function stabilizeExpression (state, expression) { + var index = state.activeQueue.indexOf(expression) if (index !== -1) { state.activeQueue.splice(index, 1) } - state.stableQueue.push(letter) + state.stableQueue.push(expression) } export function activateNextBunch (state) { let nextBunch = state.pendingQueue.shift() while (nextBunch.length > 0) { - let letter = nextBunch.shift() - state.activeQueue.push(letter) + let expression = nextBunch.shift() + state.activeQueue.push(expression) } } -export function reintroduceLetter (state, letter) { - var index = state.stableQueue.indexOf(letter) +export function reintroduceExpression (state, expression) { + var index = state.stableQueue.indexOf(expression) if (index !== -1) { state.stableQueue.splice(index, 1) } - state.activeQueue.push(letter) + state.activeQueue.push(expression) } -export function resetState (state, level) { +export function resetState (state) { state.history = {} - state.letter = '' + state.expression = '' state.activeQueue = [] state.stableQueue = [] - if (level === 'word') { + if (state.level === 'word') { state.pendingQueue = Array.from(state.sequence.words, block => Array.from(block, word => word)) } else { - state.pendingQueue = Array.from(state.sequence.letters, block => Array.from(block, letter => letter)) + state.pendingQueue = Array.from(state.sequence.expressions, block => Array.from(block, expression => expression)) } } diff --git a/frontend/src/client/src/store/common/state.js b/frontend/src/client/src/store/common/state.js index 4c03c8a..56a8ca5 100644 --- a/frontend/src/client/src/store/common/state.js +++ b/frontend/src/client/src/store/common/state.js @@ -1,17 +1,21 @@ export default { - version: '1.0.2.0', + version: '1.1.0.9', patternsLoading: true, patterns: {}, history: {}, - letter: '', + expression: '', + consecutiveFails: 0, + staleFails: 0, activeQueue: [], stableQueue: [], pendingQueue: [], + level: '', sequence: {}, user: { - sequenceId: 'Standard', - uploading: false, - name: '' + sequence: '', + uploading: true, + name: '', + technique: 'Tracing' }, retryLimit: 3, stabilizeCount: 3,