mirror of
https://github.com/Dannecron/ich-lerne-deutsch.git
synced 2025-12-25 12:52:35 +03:00
Add user words logic. Some refactiring.
Add async/await things. Fix back-up json.
This commit is contained in:
@@ -111,16 +111,19 @@
|
||||
],
|
||||
"articleId": "hp1",
|
||||
"partTitle": "kapitel 1",
|
||||
"words": [
|
||||
{
|
||||
"origWord": "Hallo",
|
||||
"transWord": "Привет"
|
||||
"words": {
|
||||
"diemutter": {
|
||||
"origText": "Mutter",
|
||||
"origPrefix": "die",
|
||||
"transText": "Мама",
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"transWord": "Привет",
|
||||
"origWord": "Hallo1"
|
||||
"etwasmachen": {
|
||||
"origText": "etwasmachen",
|
||||
"transText": "Что-то делать",
|
||||
"type": 2
|
||||
}
|
||||
],
|
||||
},
|
||||
"articlePartId": "k1",
|
||||
"youtubeId": "hHW1oY26kxQ",
|
||||
"articleTitle": "Harry Potter und Stein der Weisen - 1",
|
||||
@@ -139,12 +142,13 @@
|
||||
],
|
||||
"articleId": "hp1",
|
||||
"partTitle": "kapitel 2",
|
||||
"words": [
|
||||
{
|
||||
"transWord": "Привет",
|
||||
"origWord": "Hallo2"
|
||||
"words": {
|
||||
"hallo": {
|
||||
"origText": "Hallo",
|
||||
"transText": "Привет",
|
||||
"type": 1
|
||||
}
|
||||
],
|
||||
},
|
||||
"articlePartId": "k2",
|
||||
"youtubeId": "hHW1oY26kxQ",
|
||||
"articleTitle": "Harry Potter und Stein der Weisen - 2",
|
||||
@@ -163,12 +167,7 @@
|
||||
],
|
||||
"articleId": "hp1",
|
||||
"partTitle": "kapitel 3",
|
||||
"words": [
|
||||
{
|
||||
"transWord": "Привет",
|
||||
"origWord": "Hallo3"
|
||||
}
|
||||
],
|
||||
"words": {},
|
||||
"articlePartId": "k3",
|
||||
"youtubeId": "hHW1oY26kxQ",
|
||||
"articleTitle": "Harry Potter und Stein der Weisen - 3",
|
||||
|
||||
90
src/components/Article/Word/Card.vue
Normal file
90
src/components/Article/Word/Card.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<div class="headline">
|
||||
<v-tooltip bottom>
|
||||
<v-avatar v-if="isWord" color="teal" size="45" slot="activator">
|
||||
<span class="white--text">W</span>
|
||||
</v-avatar>
|
||||
<span>Слово / das word</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom>
|
||||
<v-avatar v-if="isRedewndung" color="indigo" size="45" slot="activator">
|
||||
<span class="white--text">RW</span>
|
||||
</v-avatar>
|
||||
<span>Выражение / die Redewendung</span>
|
||||
</v-tooltip>
|
||||
{{ getFullOriginalWord(wordEntity) }}
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-card-text>
|
||||
{{ wordEntity.transText }}
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn fab dark small color="primary" @click.stop="addWord(wordEntity)" :disabled="isProcessing">
|
||||
<v-icon class="d-flex" dark>add</v-icon>
|
||||
</v-btn>
|
||||
<v-snackbar v-model="snackbar.isEnabled" color="error" bottom light>
|
||||
<v-icon>warning</v-icon>
|
||||
{{ snackbar.text }}
|
||||
</v-snackbar>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { getFullOriginalWord, WORD_TYPES } from '@/helpers';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
wordEntity: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
snackbar: {
|
||||
isEnabled: false,
|
||||
text: null,
|
||||
},
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters(['userData', 'getProcessing']),
|
||||
isWord() {
|
||||
return this.wordEntity.type === WORD_TYPES.WORD;
|
||||
},
|
||||
isRedewndung() {
|
||||
return this.wordEntity.type === WORD_TYPES.REDEWNNDUNG;
|
||||
},
|
||||
isProcessing() {
|
||||
return this.getProcessing;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getFullOriginalWord,
|
||||
addWord(entity) {
|
||||
const userWords = this.userData.words;
|
||||
const wordAdded = userWords[entity.key];
|
||||
if (wordAdded) {
|
||||
this.snackbar.isEnabled = true;
|
||||
this.snackbar.text = 'Слово уже было добавлено';
|
||||
return;
|
||||
}
|
||||
|
||||
if (Object.keys(userWords).length > 100) {
|
||||
this.snackbar.isEnabled = true;
|
||||
this.snackbar.text = 'Слишком много добавленных слов';
|
||||
return;
|
||||
}
|
||||
|
||||
this.$store.dispatch('addUserWord', entity);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -13,40 +13,43 @@
|
||||
slot-scope="props"
|
||||
xs12
|
||||
sm6
|
||||
md4
|
||||
lg3
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<h4>{{ props.item.origWord }}</h4>
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>
|
||||
{{ props.item.transWord }}
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn fab dark small color="primary">
|
||||
<v-icon class="d-flex" dark>add</v-icon>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
<word-card :wordEntity="props.item"></word-card>
|
||||
</v-flex>
|
||||
</v-data-iterator>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WordCard from '@/components/Article/Word/Card';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
words: {
|
||||
type: Array,
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
userData() {
|
||||
return this.$store.getters.userData;
|
||||
},
|
||||
words() {
|
||||
const words = [];
|
||||
|
||||
for (let property in this.data) {
|
||||
if (this.data.hasOwnProperty(property)) {
|
||||
const word = Object.assign({}, this.data[property], { key: property });
|
||||
words.push(word);
|
||||
}
|
||||
}
|
||||
|
||||
return words;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
WordCard,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from '@/helpers/article';
|
||||
export * from '@/helpers/utils';
|
||||
export * from '@/helpers/formRules';
|
||||
export * from '@/helpers/utils';
|
||||
export * from '@/helpers/word';
|
||||
|
||||
14
src/helpers/word.js
Normal file
14
src/helpers/word.js
Normal file
@@ -0,0 +1,14 @@
|
||||
export const getFullOriginalWord = (entity) => {
|
||||
const { origText } = entity;
|
||||
|
||||
if (entity.origPrefix) {
|
||||
return `${entity.origPrefix} ${origText}`;
|
||||
}
|
||||
|
||||
return origText;
|
||||
};
|
||||
|
||||
export const WORD_TYPES = {
|
||||
WORD: 1,
|
||||
REDEWNNDUNG: 2,
|
||||
};
|
||||
@@ -29,12 +29,12 @@ export default {
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
signUp({ commit }, payload) {
|
||||
commit('setProcessing', true);
|
||||
async signUp({ commit }, payload) {
|
||||
await commit('setProcessing', true);
|
||||
commit('clearError');
|
||||
|
||||
const { email, password, name } = payload;
|
||||
firebase.auth()
|
||||
await firebase.auth()
|
||||
.createUserWithEmailAndPassword(email, password)
|
||||
.then(() => {
|
||||
firebase.auth().currentUser
|
||||
@@ -42,29 +42,29 @@ export default {
|
||||
displayName: name,
|
||||
})
|
||||
.then(() => commit('setUserName', name));
|
||||
commit('setProcessing', false);
|
||||
})
|
||||
.catch(function(error) {
|
||||
const { message } = error;
|
||||
commit('setProcessing', false);
|
||||
commit('setError', message);
|
||||
});
|
||||
|
||||
await commit('setProcessing', false);
|
||||
},
|
||||
signIn({ commit }, payload) {
|
||||
commit('setProcessing', true);
|
||||
async signIn({ commit }, payload) {
|
||||
await commit('setProcessing', true);
|
||||
commit('clearError');
|
||||
|
||||
const { email, password } = payload;
|
||||
firebase.auth()
|
||||
await firebase.auth()
|
||||
.signInWithEmailAndPassword(email, password)
|
||||
.then(() => {
|
||||
commit('setProcessing', false);
|
||||
})
|
||||
.catch(function(error) {
|
||||
const { message } = error;
|
||||
commit('setProcessing', false);
|
||||
commit('setError', message);
|
||||
});
|
||||
|
||||
await commit('setProcessing', false);
|
||||
},
|
||||
signOut() {
|
||||
firebase.auth().signOut();
|
||||
@@ -78,14 +78,14 @@ export default {
|
||||
commit('unSetUser');
|
||||
}
|
||||
},
|
||||
changeUserProfileData({ commit }, payload) {
|
||||
async changeUserProfileData({ commit }, payload) {
|
||||
const { email, password } = payload;
|
||||
const credential = firebase.auth.EmailAuthProvider.credential(email, password);
|
||||
|
||||
commit('setProcessing', true);
|
||||
await commit('setProcessing', true);
|
||||
commit('clearError');
|
||||
|
||||
firebase.auth().currentUser.reauthenticateAndRetrieveDataWithCredential(credential)
|
||||
await firebase.auth().currentUser.reauthenticateAndRetrieveDataWithCredential(credential)
|
||||
.then(() => {
|
||||
const { changeType } = payload;
|
||||
const reauthenticatedUser = firebase.auth().currentUser;
|
||||
@@ -111,14 +111,13 @@ export default {
|
||||
|
||||
throw Error('invalid change type');
|
||||
})
|
||||
.then(() => commit('setProcessing', false))
|
||||
.catch((e) => {
|
||||
window.console.error(e);
|
||||
|
||||
const { message } = e;
|
||||
commit('setProcessing', false);
|
||||
commit('setError', message);
|
||||
});
|
||||
|
||||
await commit('setProcessing', false);
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
|
||||
@@ -10,11 +10,11 @@ export default {
|
||||
userData: defaultUserData,
|
||||
},
|
||||
actions: {
|
||||
loadUserData({ commit }, userId) {
|
||||
commit('setProcessing', true);
|
||||
async loadUserData({ commit }, userId) {
|
||||
await commit('setProcessing', true);
|
||||
|
||||
let userDataRef = Vue.$db.collection('userData').doc(userId);
|
||||
userDataRef.get()
|
||||
await userDataRef.get()
|
||||
.then((data) => {
|
||||
let userData = data.exists ? data.data() : defaultUserData;
|
||||
|
||||
@@ -22,14 +22,18 @@ export default {
|
||||
userData.articles = {};
|
||||
}
|
||||
|
||||
if (!userData.words) {
|
||||
userData.words = {};
|
||||
}
|
||||
|
||||
commit('setUserData', userData);
|
||||
})
|
||||
.catch(e => window.console.error(e));
|
||||
|
||||
commit('setProcessing', false);
|
||||
await commit('setProcessing', false);
|
||||
},
|
||||
addUserArticle({ commit, getters }, articleId) {
|
||||
commit('setProcessing', true);
|
||||
async addUserArticle({ commit, getters }, articleId) {
|
||||
await commit('setProcessing', true);
|
||||
|
||||
const userDataRef = Vue.$db.collection('userData').doc(getters.userId);
|
||||
const article = {
|
||||
@@ -37,7 +41,7 @@ export default {
|
||||
parts: {},
|
||||
};
|
||||
|
||||
userDataRef.set({
|
||||
await userDataRef.set({
|
||||
articles: {
|
||||
[articleId]: article,
|
||||
}
|
||||
@@ -45,7 +49,45 @@ export default {
|
||||
.then(() => commit('addUserArticle', { articleId, article }))
|
||||
.catch(e => window.console.error(e));
|
||||
|
||||
commit('setProcessing', false);
|
||||
await commit('setProcessing', false);
|
||||
},
|
||||
async addUserWord({ commit, getters }, wordEntity) {
|
||||
await commit('setProcessing', true);
|
||||
|
||||
const { key, ...wordEntityData } = wordEntity;
|
||||
|
||||
const userDataRef = Vue.$db.collection('userData').doc(getters.userId);
|
||||
const word = Object.assign({}, wordEntityData, {
|
||||
addedAt: new Date(),
|
||||
// about this {@see https://en.wikipedia.org/wiki/Leitner_system}
|
||||
bucket: 1,
|
||||
nextShowDate: new Date(),
|
||||
});
|
||||
|
||||
await userDataRef.set({
|
||||
words: {
|
||||
[key]: word,
|
||||
}
|
||||
}, { merge: true })
|
||||
.then(() => commit('addUserWord', { wordKey: key, word }))
|
||||
.catch(e => window.console.error(e));
|
||||
|
||||
await commit('setProcessing', false);
|
||||
},
|
||||
async finishUserArticlePart({ commit, getters }, { articleId, partId, rating }) {
|
||||
await commit('setProcessing', true);
|
||||
|
||||
const userDataRef = Vue.$db.collection('userData').doc(getters.userId);
|
||||
const timestamp = new Date();
|
||||
|
||||
await userDataRef.update({
|
||||
[`articles.${articleId}.parts.${partId}.finishedAt`]: timestamp,
|
||||
[`articles.${articleId}.parts.${partId}.rating`]: rating,
|
||||
})
|
||||
.then(() => commit('finishUserArticlePart', { articleId, partId, timestamp, rating }))
|
||||
.catch(e => window.console.error(e));
|
||||
|
||||
await commit('setProcessing', false);
|
||||
},
|
||||
updateUserArticlePartStats({ commit, getters }, { articleId, partId }) {
|
||||
const userDataRef = Vue.$db.collection('userData').doc(getters.userId);
|
||||
@@ -66,21 +108,6 @@ export default {
|
||||
.then(() => commit('openUserArticlePart', { articleId, partId, timestamp }))
|
||||
.catch(e => window.console.error(e));
|
||||
},
|
||||
finishUserArticlePart({ commit, getters }, { articleId, partId, rating }) {
|
||||
commit('setProcessing', true);
|
||||
|
||||
const userDataRef = Vue.$db.collection('userData').doc(getters.userId);
|
||||
const timestamp = new Date();
|
||||
|
||||
userDataRef.update({
|
||||
[`articles.${articleId}.parts.${partId}.finishedAt`]: timestamp,
|
||||
[`articles.${articleId}.parts.${partId}.rating`]: rating,
|
||||
})
|
||||
.then(() => commit('finishUserArticlePart', { articleId, partId, timestamp, rating }))
|
||||
.catch(e => window.console.error(e));
|
||||
|
||||
commit('setProcessing', false);
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
setUserData(state, payload) {
|
||||
@@ -97,6 +124,9 @@ export default {
|
||||
|
||||
Vue.set(state.userData.articles[articleId].parts, partId, { addedAt: timestamp });
|
||||
},
|
||||
addUserWord(state, { wordKey, word }) {
|
||||
Vue.set(state.userData.words, wordKey, word);
|
||||
},
|
||||
openUserArticlePart(state, { articleId, partId, timestamp }) {
|
||||
Vue.set(state.userData.articles[articleId].parts[partId], 'lastOpenedAt', timestamp);
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm10 offset-sm1>
|
||||
<book-part-words :words="part.words"></book-part-words>
|
||||
<book-part-words :data="part.words"></book-part-words>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm10 offset-sm1 class="text-xs-center">
|
||||
@@ -105,7 +105,7 @@
|
||||
storedRating() {
|
||||
const articlePart = this.currentUserArticlePart;
|
||||
return articlePart ? articlePart.rating : 0;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
finishWork() {
|
||||
|
||||
Reference in New Issue
Block a user