add delete, edit ocr and simple cropper to manage photos

This commit is contained in:
MarcZierle 2022-01-22 14:20:45 +01:00
parent 7b6ad0c475
commit a9c8e2cbda
8 changed files with 281 additions and 12 deletions

58
package-lock.json generated
View File

@ -11,6 +11,7 @@
"axios": "^0.24.0",
"core-js": "^3.6.5",
"vue": "^3.0.0",
"vue-advanced-cropper": "^2.8.0",
"vue-meta": "^3.0.0-alpha.8",
"vue-router": "^4.0.0-0",
"vuedraggable": "^4.1.0",
@ -4354,6 +4355,11 @@
"node": ">=0.10.0"
}
},
"node_modules/classnames": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
},
"node_modules/clean-css": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz",
@ -5581,6 +5587,11 @@
"date-fns": ">=2.0.0"
}
},
"node_modules/debounce": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
"integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="
},
"node_modules/debug": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
@ -6171,6 +6182,11 @@
"stream-shift": "^1.0.0"
}
},
"node_modules/easy-bem": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/easy-bem/-/easy-bem-1.1.1.tgz",
"integrity": "sha512-GJRqdiy2h+EXy6a8E6R+ubmqUM08BK0FWNq41k24fup6045biQ8NXxoXimiwegMQvFFV3t1emADdGNL1TlS61A=="
},
"node_modules/easy-stack": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz",
@ -14729,6 +14745,23 @@
"@vue/shared": "3.2.26"
}
},
"node_modules/vue-advanced-cropper": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/vue-advanced-cropper/-/vue-advanced-cropper-2.8.0.tgz",
"integrity": "sha512-whia8uYhAgxBnW8HtP5FVrNJqRkpzkje5OmyDKKipQRWfsHd9At0Sk6+0RBkKBI5x59Xin7b9AsuXuweCTQNXg==",
"dependencies": {
"classnames": "^2.2.6",
"debounce": "^1.2.0",
"easy-bem": "^1.0.2"
},
"engines": {
"node": ">=8",
"npm": ">=5"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-eslint-parser": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.0.1.tgz",
@ -19544,6 +19577,11 @@
}
}
},
"classnames": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
},
"clean-css": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz",
@ -20530,6 +20568,11 @@
"dev": true,
"requires": {}
},
"debounce": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
"integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="
},
"debug": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
@ -20996,6 +21039,11 @@
"stream-shift": "^1.0.0"
}
},
"easy-bem": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/easy-bem/-/easy-bem-1.1.1.tgz",
"integrity": "sha512-GJRqdiy2h+EXy6a8E6R+ubmqUM08BK0FWNq41k24fup6045biQ8NXxoXimiwegMQvFFV3t1emADdGNL1TlS61A=="
},
"easy-stack": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz",
@ -27874,6 +27922,16 @@
"@vue/shared": "3.2.26"
}
},
"vue-advanced-cropper": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/vue-advanced-cropper/-/vue-advanced-cropper-2.8.0.tgz",
"integrity": "sha512-whia8uYhAgxBnW8HtP5FVrNJqRkpzkje5OmyDKKipQRWfsHd9At0Sk6+0RBkKBI5x59Xin7b9AsuXuweCTQNXg==",
"requires": {
"classnames": "^2.2.6",
"debounce": "^1.2.0",
"easy-bem": "^1.0.2"
}
},
"vue-eslint-parser": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.0.1.tgz",

View File

@ -11,6 +11,7 @@
"axios": "^0.24.0",
"core-js": "^3.6.5",
"vue": "^3.0.0",
"vue-advanced-cropper": "^2.8.0",
"vue-meta": "^3.0.0-alpha.8",
"vue-router": "^4.0.0-0",
"vuedraggable": "^4.1.0",

View File

@ -84,4 +84,8 @@ export function addNewPhoto(original_image, cropped_image=null, group_id=null, o
export function updatePhoto(photo) {
return apiClient.put('/updatephoto/'+photo.id+'/', photo)
}
export function deletePhoto(id) {
return apiClient.delete('/deletephoto/'+id+'/')
}

View File

@ -0,0 +1,51 @@
<template>
<div>
<cropper
class="cropper"
:src="src"
:stencil-props="{
aspectRatio: 1/1.41421356
}"
@change="change"
/>
</div>
</template>
<script>
import {Cropper} from 'vue-advanced-cropper'
import 'vue-advanced-cropper/dist/style.css'
export default {
components: {
Cropper,
},
props: {
src: {
type: String,
required: true,
}
},
data() {
return {
}
},
mounted() {
console.log(this.src)
},
methods: {
change({ coordinates, canvas }) {
console.log(coordinates, canvas)
}
}
}
</script>
<style scoped>
.cropper {
height: 600px;
width: 600px;
background: #DDD;
}
</style>

View File

@ -25,7 +25,7 @@
<div class="options-item" v-if="can_change_group">
<n-button text @click="$emit('update:group')">
<template #icon><n-icon><SortFilled /></n-icon></template>
Change Group
Move Group
</n-button>
</div>

View File

@ -8,6 +8,7 @@ import {
getPhotoGroups,
getPhotosByGroup,
updatePhoto,
deletePhoto,
} from '@/api'
export default createStore({
@ -49,13 +50,27 @@ export default createStore({
let photo_index = group.findIndex(p => p.id == photo.id)
if (photo_index > -1) {
let state_photo = state.photos[idx].splice(photo_index, 1)[0]
state_photo.group = photo.group
state_photo.bbox_coords = photo.bbox_coords
state_photo.ocr_text = photo.ocr_text
if (!state.photos[photo.group] || state.photos[photo.group].length === 0) {
state.photos[photo.group] = []
Object.keys(photo).forEach(key => {
state_photo[key] = photo[key]
})
if (photo['group']) {
if (!state.photos[photo.group] || state.photos[photo.group].length === 0) {
state.photos[photo.group] = []
}
state.photos[state_photo.group].push(state_photo)
} else {
state.photos[state_photo.group].splice(photo_index, 0, state_photo)
}
state.photos[photo.group].push(state_photo)
break
}
}
},
DELETE_PHOTO(state, id) {
for (const group_id in state.photos) {
let group = state.photos[group_id]
let photo_index = group.findIndex(p => p.id == id)
if (photo_index > -1) {
state.photos[group_id].splice(photo_index, 1)
}
}
}
@ -142,6 +157,16 @@ export default createStore({
reject(error)
})
})
},
deletePhoto({commit}, id) {
new Promise((resolve, reject) => {
deletePhoto(id).then(() => {
commit('DELETE_PHOTO', id)
resolve()
}).catch((error) => {
reject(error)
})
})
}
},
getters: {
@ -164,7 +189,16 @@ export default createStore({
},
photos (state) {
return state.photos
}
},
photoById: (state) => (id) => {
for (const group_idx in state.photos) {
let found_photo = state.photos[group_idx].filter(p => p.id == id)
if (found_photo.length > 0) {
return found_photo[0]
}
}
return null
},
},
modules: {},
})

View File

@ -212,7 +212,7 @@ export default {
},
takePhoto() {
this.isPhotoTaken = true
setTimeout(() => {
this.photo.height = document.getElementById('camera_preview').offsetHeight

View File

@ -22,18 +22,44 @@
:can_change_ocr="true"
:can_delete="true"
@update:group="change_group_modal(photo.id)"
@update:crop="change_crop_modal(photo.id)"
@update:ocr="change_ocr_modal(photo.id)"
@update:delete="change_delete_modal(photo.id)"
/>
</n-space>
</n-collapse-item>
</n-collapse>
<p v-else>No Photo Groups</p>
<n-modal
v-model:show="showCropModal"
:mask-closable="false"
>
<n-card
style="width: 95vw; height: 90vh;"
title="Adjust Photo Cropping"
:bordered="true"
>
<template #header-extra> Header </template>
<PhotoCropper :src="current_photo_src" />
<template #footer> Footer </template>
<template #action>
<n-space justify="end">
<n-button @click="showCropModal = false">Cancel</n-button>
<n-button type="primary">Save</n-button>
</n-space>
</template>
</n-card>
</n-modal>
<n-modal
v-model:show="showChangeGroupModal"
:mask-closable="true" >
<n-card
style="width: 400px;"
title="Change Photo Group"
title="Move to Photo Group"
:bordered="false"
role="dialog"
aria-modal="true"
@ -42,7 +68,57 @@
<template #footer>
<n-space justify="end">
<n-button @click="showChangeGroupModal=false">Cancel</n-button>
<n-button type="success" @click="changePhotoGroup">Change Group</n-button>
<n-button type="success" @click="changePhotoGroup">Move to Group</n-button>
</n-space>
</template>
</n-card>
</n-modal>
<n-modal
v-model:show="showDeleteModal"
:mask-closable="true" >
<n-card
style="width: 400px;"
title="Delete Photo"
:bordered="false"
role="dialog"
aria-modal="true"
>
Do you want to permanently delete the photo?
<template #footer>
<n-space justify="end">
<n-button @click="showDeleteModal=false">Cancel</n-button>
<n-button type="warning" @click="deletePhoto">Delete</n-button>
</n-space>
</template>
</n-card>
</n-modal>
<n-modal
v-model:show="showChangeOCRModal"
:mask-closable="true" >
<n-card
style="width: 400px;"
title="Change OCR Text"
:bordered="false"
role="dialog"
aria-modal="true"
>
<n-input
placeholder="Enter the text on the photo"
type="textarea"
v-model:value="current_ocr_text"
:autosize="{
minRows: 3,
maxRows: 5
}"
/>
<template #footer>
<n-space justify="end">
<n-button @click="showChangeOCRModal=false">Cancel</n-button>
<n-button type="success" @click="changeOCRText">Change Text</n-button>
</n-space>
</template>
</n-card>
@ -55,10 +131,12 @@ import { useMeta } from 'vue-meta'
import { useMessage } from 'naive-ui'
import PhotoItem from '@/components/PhotoItem'
import PhotoCropper from '@/components/PhotoCropper'
export default {
components: {
PhotoItem,
PhotoCropper,
},
setup() {
useMeta({ title: 'Manage Photos' })
@ -70,7 +148,13 @@ export default {
current_photo: null,
showChangeGroupModal: false,
showCropModal: false,
showChangeOCRModal: false,
showDeleteModal: false,
new_group: null,
current_ocr_text: '',
current_photo_src: null,
}
},
computed: {
@ -127,7 +211,24 @@ export default {
change_group_modal(photo_id) {
this.current_photo = photo_id
this.showChangeGroupModal = true
this.new_group = null
this.new_group = this.$store.getters.photoById(photo_id).group
},
change_crop_modal(photo_id) {
this.current_photo = photo_id
this.showCropModal = true
this.current_photo_src = this.$store.getters.photoById(photo_id).original_image
},
change_ocr_modal(photo_id) {
this.current_photo = photo_id
this.showChangeOCRModal = true
this.current_ocr_text = this.$store.getters.photoById(photo_id).ocr_text
if (this.current_ocr_text == null) {
this.current_ocr_text = ''
}
},
change_delete_modal(photo_id) {
this.current_photo = photo_id
this.showDeleteModal = true
},
changePhotoGroup(){
if (this.new_group && this.current_photo) {
@ -140,6 +241,26 @@ export default {
this.showChangeGroupModal = false
})
}
},
changeOCRText(){
if (this.current_ocr_text.length !== null && this.current_photo) {
this.$store.dispatch('updatePhoto', {
id: this.current_photo,
ocr_text: this.current_ocr_text
}).then(()=>{
this.current_photo = null
this.current_ocr_text = null
this.showChangeOCRModal = false
})
}
},
deletePhoto(){
if (this.current_photo) {
this.$store.dispatch('deletePhoto', this.current_photo).then(()=>{
this.current_photo = null
this.showDeleteModal = false
})
}
}
},
}