Browse Source

Update frontend to add comment feature

pull/1155/head
Hironsan 3 years ago
parent
commit
1fb04cccab
8 changed files with 248 additions and 5 deletions
  1. 5
      Dockerfile
  2. 14
      app/api/migrations/0004_merge_20210114_1117.py
  3. 2
      app/api/serializers.py
  4. 158
      frontend/components/containers/annotation/CommentButton.vue
  5. 2
      frontend/i18n/en/projects/annotation.js
  6. 5
      frontend/layouts/annotation.vue
  7. 26
      frontend/services/comment.service.js
  8. 41
      frontend/store/documents.js

5
Dockerfile

@ -46,9 +46,9 @@ RUN pip install --no-cache-dir -U pip \
&& rm -rf /deps
COPY --chown=doccano:doccano . /doccano
WORKDIR /doccano
WORKDIR /doccano/app
COPY --from=frontend-builder /frontend/dist /doccano/app/client/dist
RUN python app/manage.py collectstatic --noinput
RUN python manage.py collectstatic --noinput
VOLUME /data
ENV DATABASE_URL="sqlite:////data/doccano.db"
@ -61,7 +61,6 @@ ENV GOOGLE_TRACKING_ID=""
ENV AZURE_APPINSIGHTS_IKEY=""
USER doccano
WORKDIR /doccano
EXPOSE ${PORT}
CMD ["/doccano/tools/run.sh"]

14
app/api/migrations/0004_merge_20210114_1117.py

@ -0,0 +1,14 @@
# Generated by Django 3.1.5 on 2021-01-14 11:17
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('api', '0002_comment'),
('api', '0003_merge_20200612_0205'),
]
operations = [
]

2
app/api/serializers.py

@ -7,7 +7,7 @@ from rest_framework.exceptions import ValidationError
from .models import Label, Project, Document, RoleMapping, Role, Comment
from .models import TextClassificationProject, SequenceLabelingProject, Seq2seqProject
from .models import TextClassificationProject, SequenceLabelingProject, Seq2seqProject, Speech2textProject
from .models import DocumentAnnotation, SequenceAnnotation, Seq2seqAnnotation, Speech2textAnnotation

158
frontend/components/containers/annotation/CommentButton.vue

@ -0,0 +1,158 @@
<template>
<div style="display:inline;">
<v-tooltip bottom>
<template v-slot:activator="{ on }">
<v-btn
class="text-capitalize ps-1 pe-1"
min-width="36"
icon
v-on="on"
@click="dialog=true"
>
<v-icon>
mdi-chat
</v-icon>
</v-btn>
</template>
<span>{{ $t('annotation.commentTooltip') }}</span>
</v-tooltip>
<v-dialog
v-model="dialog"
width="800"
>
<base-card
:title="$t('annotation.commentPopupTitle')"
:cancel-text="$t('generic.close')"
@cancel="dialog=false"
>
<template #content>
<v-text-field
v-model="comment"
outlined
>
<template
v-slot:append
>
<v-btn
tile
large
icon
height="auto"
width="auto"
color="primary"
class="ma-0"
@click="addItem"
>
<v-icon>mdi-plus</v-icon>
</v-btn>
</template>
</v-text-field>
<v-data-table
:headers="headers"
:items="currentDoc.comments"
:items-per-page="10"
class="elevation-1"
>
<template v-slot:item.action="{ item }">
<v-icon
small
@click="deleteItem(item.id)"
>
mdi-delete
</v-icon>
</template>
<template v-slot:item.text="props">
<v-edit-dialog
:return-value.sync="props.item.text"
large
persistent
@save="editItem({ id: props.item.id })"
>
<div>{{ props.item.text }}</div>
<template v-slot:input>
<div class="mt-4 title">
Update Comment
</div>
<v-text-field
:value="props.item.text"
label="Edit"
single-line
counter
autofocus
@input="setNewComment"
/>
</template>
</v-edit-dialog>
</template>
</v-data-table>
</template>
</base-card>
</v-dialog>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import BaseCard from '@/components/molecules/BaseCard'
export default {
components: {
BaseCard
},
data() {
return {
dialog: false,
comment: '',
newComment: ''
}
},
computed: {
...mapGetters('documents', ['currentDoc']),
headers() {
return [
{
text: 'Comments',
align: 'start',
sortable: false,
value: 'text'
},
{ text: 'Action', value: 'action', sortable: false }
]
}
},
methods: {
...mapActions('documents', ['addComment', 'deleteComment', 'updateComment']),
addItem() {
if (this.comment === '') {
return
}
const payload = {
text: this.comment,
projectId: this.$route.params.id
}
this.addComment(payload)
this.comment = ''
},
deleteItem(id) {
const payload = {
commentId: id,
projectId: this.$route.params.id
}
this.deleteComment(payload)
},
editItem(payload) {
this.updateComment({
projectId: this.$route.params.id,
commentId: payload.id,
text: this.newComment
})
},
setNewComment(value) {
this.newComment = value
}
}
}
</script>

2
frontend/i18n/en/projects/annotation.js

@ -7,6 +7,8 @@ export default {
filterOption3: 'Undone',
guidelineTooltip: 'Show Guideline',
guidelinePopupTitle: 'Annotation Guideline',
commentTooltip: 'Click to comment on document',
commentPopupTitle: 'Comment',
metadataDefaultMessage: 'No data available',
key: 'Key',
value: 'Value',

5
frontend/layouts/annotation.vue

@ -39,6 +39,7 @@
v-model="filterOption"
/>
<guideline-button />
<comment-button />
<clear-annotations-button />
</v-col>
<v-spacer />
@ -79,6 +80,7 @@ import GuidelineButton from '@/components/containers/annotation/GuidelineButton'
import MetadataBox from '@/components/organisms/annotation/MetadataBox'
import FilterButton from '@/components/containers/annotation/FilterButton'
import ApproveButton from '@/components/containers/annotation/ApproveButton'
import CommentButton from '@/components/containers/annotation/CommentButton'
import Pagination from '~/components/containers/annotation/Pagination'
import TheHeader from '~/components/organisms/layout/TheHeader'
import TheSideBar from '~/components/organisms/layout/TheSideBar'
@ -95,7 +97,8 @@ export default {
FilterButton,
ApproveButton,
MetadataBox,
ClearAnnotationsButton
ClearAnnotationsButton,
CommentButton
},
fetch() {

26
frontend/services/comment.service.js

@ -0,0 +1,26 @@
import ApiService from '@/services/api.service'
class CommentService {
constructor() {
this.request = ApiService
}
getCommentList({ projectId, docId }) {
return this.request.get(`/projects/${projectId}/docs/${docId}/comments`)
}
addComment(projectId, docId, payload) {
console.log(payload)
return this.request.post(`/projects/${projectId}/docs/${docId}/comments`, payload)
}
deleteComment(projectId, docId, commentId) {
return this.request.delete(`/projects/${projectId}/docs/${docId}/comments/${commentId}`)
}
updateComment(projectId, docId, commentId, payload) {
return this.request.patch(`/projects/${projectId}/docs/${docId}/comments/${commentId}`, payload)
}
}
export default new CommentService()

41
frontend/store/documents.js

@ -1,3 +1,4 @@
import CommentService from '@/services/comment.service'
import DocumentService from '@/services/document.service'
import AnnotationService from '@/services/annotation.service'
@ -73,6 +74,16 @@ export const mutations = {
const item = state.items[state.current].annotations.find(item => item.id === payload.id)
Object.assign(item, payload)
},
addComment(state, payload) {
state.items[state.current].comments.push(payload)
},
updateComment(state, payload) {
const item = state.items[state.current].comments.find(item => item.id === payload.id)
Object.assign(item, payload)
},
deleteComment(state, commentId) {
state.items[state.current].comments = state.items[state.current].comments.filter(item => item.id !== commentId)
},
updateSearchOptions(state, payload) {
state.searchOptions = Object.assign(state.searchOptions, payload)
},
@ -222,5 +233,35 @@ export const actions = {
.catch((error) => {
alert(error)
})
},
addComment({ commit, state }, payload) {
const documentId = state.items[state.current].id
CommentService.addComment(payload.projectId, documentId, payload)
.then((response) => {
commit('addComment', response.data)
})
.catch((error) => {
alert(error)
})
},
updateComment({ commit, state }, payload) {
const documentId = state.items[state.current].id
CommentService.updateComment(payload.projectId, documentId, payload.commentId, payload)
.then((response) => {
commit('updateComment', response.data)
})
.catch((error) => {
alert(error)
})
},
deleteComment({ commit, state }, payload) {
const documentId = state.items[state.current].id
CommentService.deleteComment(payload.projectId, documentId, payload.commentId)
.then((response) => {
commit('deleteComment', payload.commentId)
})
.catch((error) => {
alert(error)
})
}
}
Loading…
Cancel
Save