Add user data logic. Some refactoring.

User can add articles and parts, it stores in firestore.
This commit is contained in:
2019-03-08 15:23:05 +07:00
parent 62414d39e0
commit 4f15b462a1
6 changed files with 270 additions and 7 deletions

View File

@@ -45,7 +45,17 @@
<v-btn v-if="!expandDetails" class="primary" flat :to="{ name: 'article', params: { articleId: article.id } }">
Открыть
</v-btn>
<v-btn v-if="expandDetails" class="primary" flat>Добавить</v-btn>
<v-btn v-if="expandDetails && canAddArticle(article.id)"
class="primary"
flat
@click="addArticle(article.id)"
>
Добавить
</v-btn>
<div v-if="getUserDataArticle(article.id)" class="ml-2">
<v-icon color="white">work_outline</v-icon>
Добавлено {{ getArticleAddedAt(article.id) | formattedDate }}
</div>
</v-card-actions>
</v-flex>
</v-layout>
@@ -54,6 +64,7 @@
</template>
<script>
import { mapGetters } from 'vuex';
import YoutubeButton from '@/components/Article/YoutubeButton';
import { getArticleLevel, declOfNum } from '@/helpers';
@@ -74,9 +85,24 @@
const partsCountWord = declOfNum(partsCount, ['часть', 'части', 'частей']);
return `${partsCount} ${partsCountWord}`;
},
...mapGetters(['isUserAuthentificated', 'getProcessing', 'userData']),
},
methods: {
getArticleLevel: getArticleLevel,
getArticleLevel,
getUserDataArticle(articleId) {
return this.userData.articles[articleId];
},
canAddArticle(articleId) {
const article = this.getUserDataArticle(articleId);
return this.isUserAuthentificated && !this.getProcessing && !article;
},
addArticle(articleId) {
this.$store.dispatch('addUserArticle', articleId);
},
getArticleAddedAt(articleId) {
const article = this.getUserDataArticle(articleId);
return article.addedAt;
}
},
components: {
YoutubeButton,

View File

@@ -6,7 +6,13 @@
</v-card-title>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn flat class="primary" :to="{
<div v-if="finishedAt" class="mr-2">
<v-icon dark>check</v-icon> Завершено {{ finishedAt | formattedDate }}
</div>
<v-btn v-if="isUserArticleAdded"
flat
class="primary"
:to="{
name: 'articlePart',
params: { articleId: this.articleId, partId: part.id },
}"
@@ -19,6 +25,8 @@
</template>
<script>
import { mapGetters } from 'vuex';
export default {
props: {
part: {
@@ -30,6 +38,25 @@
required: true,
},
},
computed: {
...mapGetters(['isUserAuthentificated', 'getProcessing', 'userData']),
currentUserArticle() {
return this.userData.articles[this.articleId];
},
currentUserArticlePart() {
const article = this.currentUserArticle;
return article ? article.parts[this.part.id] : null;
},
isUserArticleAdded() {
return this.isUserAuthentificated && !this.getProcessing && !!this.currentUserArticle;
},
finishedAt() {
const { currentUserArticlePart } = this;
return currentUserArticlePart ? currentUserArticlePart.finishedAt : null;
},
},
methods: {
},
};
</script>

View File

@@ -4,6 +4,7 @@ import Vuex from 'vuex';
import articleModule from '@/store/articles';
import generalModule from '@/store/general';
import userModule from '@/store/user';
import userDataModule from '@/store/userData';
Vue.use(Vuex);
@@ -12,5 +13,6 @@ export default new Vuex.Store({
articleModule,
generalModule,
userModule,
userDataModule,
},
});

View File

@@ -54,9 +54,10 @@ export default {
signOut() {
firebase.auth().signOut();
},
stateChanged({ commit }, payload) {
stateChanged({ commit, dispatch }, payload) {
if (payload) {
commit('setUser', payload.uid);
dispatch('loadUserData', payload.uid);
} else {
commit('unSetUser');
}
@@ -64,5 +65,6 @@ export default {
},
getters: {
isUserAuthentificated: state => state.user.isAuthentificated,
userId: state => state.user.uid,
},
};

111
src/store/userData.js Normal file
View File

@@ -0,0 +1,111 @@
import Vue from 'vue';
const defaultUserData = {
articles: {},
words: {},
};
export default {
state: {
userData: defaultUserData,
},
actions: {
loadUserData({ commit }, userId) {
commit('setProcessing', true);
let userDataRef = Vue.$db.collection('userData').doc(userId);
userDataRef.get()
.then((data) => {
let userData = data.exists ? data.data() : defaultUserData;
if (!userData.articles) {
userData.articles = {};
}
commit('setUserData', userData);
})
.catch(e => window.console.error(e));
commit('setProcessing', false);
},
addUserArticle({ commit, getters }, articleId) {
commit('setProcessing', true);
const userDataRef = Vue.$db.collection('userData').doc(getters.userId);
const article = {
addedAt: new Date(),
parts: {},
};
userDataRef.set({
articles: {
[articleId]: article,
}
}, { merge: true })
.then(() => commit('addUserArticle', { articleId, article }))
.catch(e => window.console.error(e));
commit('setProcessing', false);
},
updateUserArticlePartStats({ commit, getters }, { articleId, partId }) {
const userDataRef = Vue.$db.collection('userData').doc(getters.userId);
const timestamp = new Date();
const articles = getters.userData.articles;
if (!articles.hasOwnProperty(articleId) || !articles[articleId].parts.hasOwnProperty(partId)) {
userDataRef.update({
[`articles.${articleId}.parts.${partId}.addedAt`]: timestamp,
})
.then(() => commit('addUserArticlePart', { articleId, partId, timestamp }))
.catch(e => window.console.error(e));
}
userDataRef.update({
[`articles.${articleId}.parts.${partId}.lastOpenedAt`]: timestamp,
})
.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) {
Vue.set(state, 'userData', payload);
},
addUserArticle(state, { articleId, article }) {
Vue.set(state.userData.articles, articleId, article);
},
addUserArticlePart(state, { articleId, partId, timestamp }) {
if (state.userData.articles[articleId].parts[partId]) {
Vue.set(state.userData.articles[articleId].parts[partId], 'addedAt', timestamp);
return;
}
Vue.set(state.userData.articles[articleId].parts, partId, { addedAt: timestamp });
},
openUserArticlePart(state, { articleId, partId, timestamp }) {
Vue.set(state.userData.articles[articleId].parts[partId], 'lastOpenedAt', timestamp);
},
finishUserArticlePart(state, { articleId, partId, timestamp, rating }) {
Vue.set(state.userData.articles[articleId].parts[partId], 'finishedAt', timestamp);
Vue.set(state.userData.articles[articleId].parts[partId], 'rating', rating);
},
},
getters: {
userData: state => state.userData,
},
};

View File

@@ -1,12 +1,58 @@
<template>
<v-container grid-list-md v-if="part">
<v-layout row wrap>
<v-flex v-if="finishedAt" xs12 sm10 offset-sm1>
<v-card>
<v-img src="https://img.icons8.com/nolan/64/000000/inspection.png"
max-width="100px"
>
</v-img>
<v-card-title primary-title>
<div class="headline">Чтение этой части завершено {{ finishedAt | formattedDate }}</div>
</v-card-title>
<v-card-actions>
<span>Оценка</span>
<v-rating v-model="storedRating" color="success" half-increments readonly large></v-rating>
</v-card-actions>
</v-card>
</v-flex>
<v-flex xs12 sm10 offset-sm1>
<book-part-content :part="part"></book-part-content>
</v-flex>
<v-flex xs12 sm10 offset-sm1>
<book-part-words :words="part.words"></book-part-words>
</v-flex>
<v-flex xs12 sm10 offset-sm1 class="text-xs-center">
<v-dialog v-model="finishDialog" persistent max-width="600px">
<v-btn v-if="!finishedAt" slot="activator" color="success" dark @click.stop="finishDialog = true">
<v-icon>check</v-icon> Завершить
</v-btn>
<v-card>
<v-img src="https://img.icons8.com/nolan/64/000000/inspection.png"
max-width="100px"
>
</v-img>
<v-card-title primary-title>
<div class="headline">Чтение этой части завершено.</div>
</v-card-title>
<v-card-text>
<span>Оценка</span>
<v-rating v-model="rating" color="success" half-increments large></v-rating>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" dark flat @click.native="finishDialog = false">
<v-icon>close</v-icon> Закрыть
</v-btn>
<v-btn color="success" dark flat @click.native="finishWork">
<v-icon>check</v-icon> Подтвердить
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-flex>
</v-layout>
</v-container>
</template>
@@ -31,13 +77,56 @@
BookPartContent,
BookPartWords,
},
computed: {
currentUserArticle() {
const articles = this.$store.getters.userData.articles;
if (!articles) {
return null;
}
return articles[this.articleId];
},
currentUserArticlePart() {
const article = this.currentUserArticle;
if (!article) {
return null;
}
const articleParts = article.parts;
if (!articleParts) {
return null;
}
return articleParts[this.partId];
},
finishedAt() {
const articlePart = this.currentUserArticlePart;
return articlePart ? articlePart.finishedAt : null
},
storedRating() {
const articlePart = this.currentUserArticlePart;
return articlePart ? articlePart.rating : 0;
}
},
methods: {
finishWork() {
this.$store.dispatch('finishUserArticlePart', {
articleId: this.articleId,
partId: this.partId,
rating: this.rating,
});
this.finishDialog = false;
}
},
data: () => ({
part: null,
finishDialog: false,
rating: 0,
}),
created() {
const { articleId, partId } = this;
Vue.$db.collection('articleParts')
.where('articleId', '==', this.articleId)
.where('articlePartId', '==', this.partId)
.where('articleId', '==', articleId)
.where('articlePartId', '==', partId)
.get()
.then((querySnapshot) => {
const snapDocs = querySnapshot.docs;
@@ -45,7 +134,13 @@
this.part = Object.assign({}, snapDocs[0].data());
}
})
.catch(e => console.error(e));
.then(() => {
this.$store.dispatch('updateUserArticlePartStats', {
articleId,
partId,
});
})
.catch(e => window.console.error(e));
}
};
</script>