add websockets and react to updates

This commit is contained in:
MarcZierle 2022-06-22 11:58:10 +02:00
parent 7b2d9a7d6c
commit 3db2070ef3
7 changed files with 207 additions and 85 deletions

View File

@ -22,6 +22,8 @@ import {darkTheme} from 'naive-ui'
import NavBar from '@/components/NavBar.vue' import NavBar from '@/components/NavBar.vue'
import store from '@/store/index.js'
export default defineComponent({ export default defineComponent({
components: { components: {
NavBar, NavBar,
@ -41,7 +43,46 @@ export default defineComponent({
//return !(currentPage == 'Home' || currentPage == 'CameraCapture') //return !(currentPage == 'Home' || currentPage == 'CameraCapture')
return !(currentPage == 'CameraCapture') return !(currentPage == 'CameraCapture')
} }
} },
created() {
console.log("Starting connection to WebSocket Server")
this.connection = new WebSocket("wss://zierle-training-staging.riezel.com/ws/notifications/")
this.connection.onmessage = function(event) {
console.log(event);
let data = JSON.parse(event.data)
if (data.type === 'update_photolog_pdf') {
store.dispatch('setPhotoLogField',
{id: data.content.id, field: 'status', value: null}
)
store.dispatch('setPhotoLogField',
{
id: data.content.id, field: 'pdf',
value: 'https://minio.riezel.com/zierle-training/' + data.content.pdf
}
)
store.dispatch('loadPhotoLogList')
} else if (data.type === 'update_photo') {
store.dispatch('loadPhoto', data.content.id)
store.dispatch('setPhotoStatus', {
id: data.content.id,
status: null
})
}
}
this.connection.onopen = function(event) {
console.log(event)
console.log("Successfully connected to the echo websocket server...")
}
this.connection.onerror = function(event) {
console.error("Error while connecting to the websocket:")
console.error(event)
}
},
}) })
</script> </script>

View File

@ -1,9 +1,9 @@
import axios from 'axios' import axios from 'axios'
const apiClient = axios.create({ const apiClient = axios.create({
baseURL: 'https://zierle-training.riezel.com/api/v1', baseURL: 'https://zierle-training-staging.riezel.com/api/v1',
withCredentials: false, withCredentials: false,
timeout: 120000, timeout: 10000,
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -125,6 +125,10 @@ export function addNewPhoto(original_image, cropped_image=null, group_id=null, o
return apiClient.post('/addphoto/', data, config) return apiClient.post('/addphoto/', data, config)
} }
export function loadPhoto(id) {
return apiClient.get('/photo/' + id+'/')
}
export function updatePhoto(photo) { export function updatePhoto(photo) {
return apiClient.put('/updatephoto/'+photo.id+'/', photo) return apiClient.put('/updatephoto/'+photo.id+'/', photo)
} }

View File

@ -1,64 +1,66 @@
<template> <template>
<div <n-spin :show="loading">
class="card" <div
:style="card_styles" class="card"
@mouseover="setHoverIfLoaded" :style="card_styles"
@mouseleave="hover=false" @mouseover="setHoverIfLoaded"
> @mouseleave="hover=false"
<div >
class="cover" <div
:style="cover_styles" class="cover"
ref="cover" :style="cover_styles"
@click="toggleSelection" ref="cover"
> @click="toggleSelection"
<n-skeleton v-if="!isImgLoaded" :height="height" :width="width" /> >
<div v-if="isImgLoaded" class="selection" :style="selection_styles"> <n-skeleton v-if="!isImgLoaded" :height="height" :width="width" />
<n-icon size="4em" color="rgba(255,255,255,0.75)"><Check /></n-icon> <div v-if="isImgLoaded" class="selection" :style="selection_styles">
</div> <n-icon size="4em" color="rgba(255,255,255,0.75)"><Check /></n-icon>
</div>
<div
class="options"
:style="options_styles"
>
<div class="content">
<div class="options-item" v-if="can_change_group">
<n-button text @click="$emit('update:group')">
<template #icon><n-icon><SortFilled /></n-icon></template>
Move Group
</n-button>
</div>
<div class="options-item" v-if="can_crop">
<n-button text @click="$emit('update:crop')">
<template #icon><n-icon><CropRotateFilled /></n-icon></template>
Crop Photo
</n-button>
</div>
<div class="options-item" v-if="can_change_ocr">
<n-button text @click="$emit('update:ocr')">
<template #icon><n-icon><TextAnnotationToggle /></n-icon></template>
Change OCR
</n-button>
</div>
<div class="options-item" v-if="can_change_tag">
<n-button text @click="$emit('update:tag')">
<template #icon><n-icon><MdPricetag /></n-icon></template>
Change Tag
</n-button>
</div>
<div class="options-item" v-if="can_delete">
<n-button text type="error" @click="$emit('update:delete')">
<template #icon><n-icon><TrashBinSharp /></n-icon></template>
Delete Photo
</n-button>
</div> </div>
</div> </div>
</div>
</div> <div
class="options"
:style="options_styles"
>
<div class="content">
<div class="options-item" v-if="can_change_group">
<n-button text @click="$emit('update:group')">
<template #icon><n-icon><SortFilled /></n-icon></template>
Move Group
</n-button>
</div>
<div class="options-item" v-if="can_crop">
<n-button text @click="$emit('update:crop')">
<template #icon><n-icon><CropRotateFilled /></n-icon></template>
Crop Photo
</n-button>
</div>
<div class="options-item" v-if="can_change_ocr">
<n-button text @click="$emit('update:ocr')">
<template #icon><n-icon><TextAnnotationToggle /></n-icon></template>
Change OCR
</n-button>
</div>
<div class="options-item" v-if="can_change_tag">
<n-button text @click="$emit('update:tag')">
<template #icon><n-icon><MdPricetag /></n-icon></template>
Change Tag
</n-button>
</div>
<div class="options-item" v-if="can_delete">
<n-button text type="error" @click="$emit('update:delete')">
<template #icon><n-icon><TrashBinSharp /></n-icon></template>
Delete Photo
</n-button>
</div>
</div>
</div>
</div>
</n-spin>
</template> </template>
<script> <script>
@ -125,6 +127,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
loading: {
type: Boolean,
required: false,
default: false,
},
}, },
emits: [ emits: [
'update:select', 'update:select',

View File

@ -12,6 +12,7 @@ import {
getPhotoGroups, getPhotoGroups,
getPhotoTags, getPhotoTags,
getPhotosByGroup, getPhotosByGroup,
loadPhoto,
updatePhoto, updatePhoto,
deletePhoto, deletePhoto,
cropPhoto, cropPhoto,
@ -24,9 +25,13 @@ export default createStore({
photoLogs: [], photoLogs: [],
photoGroups: null, photoGroups: null,
photoTags: null, photoTags: null,
photos: [] photos: [],
photosStatus: {},
}, },
mutations: { mutations: {
SET_PHOTO_STATUS(state, {id, status}) {
state.photosStatus[id] = status
},
SET_PHOTO_LOG_LIST(state, newPhotoLogList) { SET_PHOTO_LOG_LIST(state, newPhotoLogList) {
state.photoLogList = newPhotoLogList state.photoLogList = newPhotoLogList
}, },
@ -57,6 +62,12 @@ export default createStore({
let log_index = state.photoLogTemplateList.findIndex(log => log.id === id) let log_index = state.photoLogTemplateList.findIndex(log => log.id === id)
state.photoLogTemplateList.splice(log_index, 1) state.photoLogTemplateList.splice(log_index, 1)
}, },
SET_PHOTO_LOG_FIELD(state, {id, field, value}) {
let log_index = state.photoLogList.findIndex(log => log.id === id)
if (log_index > -1) {
state.photoLogList[log_index][field] = value
}
},
SET_PHOTO_GROUPS(state, groups) { SET_PHOTO_GROUPS(state, groups) {
state.photoGroups = groups state.photoGroups = groups
}, },
@ -79,14 +90,14 @@ export default createStore({
Object.keys(photo).forEach(key => { Object.keys(photo).forEach(key => {
state_photo[key] = photo[key] state_photo[key] = photo[key]
}) })
if (photo['group']) { /*if (photo['group']) {
if (!state.photos[photo.group] || state.photos[photo.group].length === 0) { if (!state.photos[photo.group] || state.photos[photo.group].length === 0) {
state.photos[photo.group] = [] state.photos[photo.group] = []
} }
state.photos[state_photo.group].push(state_photo) state.photos[state_photo.group].push(state_photo)
} else { } else {*/
state.photos[state_photo.group].splice(photo_index, 0, state_photo) state.photos[state_photo.group].splice(photo_index, 0, state_photo)
} //}
break break
} }
} }
@ -102,6 +113,9 @@ export default createStore({
} }
}, },
actions: { actions: {
setPhotoStatus({commit}, {id, status}) {
commit('SET_PHOTO_STATUS', {id, status})
},
loadPhotoLogList({commit}) { loadPhotoLogList({commit}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getPhotoLogList().then((response) => { getPhotoLogList().then((response) => {
@ -183,6 +197,9 @@ export default createStore({
return updateLogTemplate(photologtemplate) return updateLogTemplate(photologtemplate)
} }
}, },
setPhotoLogField({commit}, {id, field, value}) {
commit('SET_PHOTO_LOG_FIELD', {id, field, value})
},
loadPhotoGroups({commit}) { loadPhotoGroups({commit}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getPhotoGroups().then((response) => { getPhotoGroups().then((response) => {
@ -219,6 +236,13 @@ export default createStore({
dispatch('loadPhotosInGroup', group_id) dispatch('loadPhotosInGroup', group_id)
} }
}, },
loadPhoto({commit}, id) {
loadPhoto(id).then((response) => {
commit('UPDATE_PHOTO', response.data)
}).catch((error) => {
console.error(error)
})
},
updatePhoto({commit}, photo) { updatePhoto({commit}, photo) {
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
updatePhoto(photo).then(() => { updatePhoto(photo).then(() => {
@ -241,11 +265,11 @@ export default createStore({
}, },
cropPhoto({commit}, {id, mode}) { cropPhoto({commit}, {id, mode}) {
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
cropPhoto(id, mode).then((response) => { cropPhoto(id, mode).then((/*response*/) => {
commit('UPDATE_PHOTO', { /*commit('UPDATE_PHOTO', {
id: response.data.id, id: response.data.id,
cropped_image: response.data.cropped_image cropped_image: response.data.cropped_image
}) })*/
resolve() resolve()
}).catch((error) => { }).catch((error) => {
reject(error) reject(error)

View File

@ -6,7 +6,7 @@
type="primary" style="margin: 1em;" type="primary" style="margin: 1em;"
@click="getPDF" @click="getPDF"
:disabled="!isValidId" :disabled="!isValidId"
:loading="isGeneratingPDF" :loading="currently_saving"
> >
Save &amp; Generate Save &amp; Generate
</n-button> </n-button>
@ -562,8 +562,9 @@ export default {
this.saveChanges().then(() => { this.saveChanges().then(() => {
this.isGeneratingPDF = true this.isGeneratingPDF = true
getPhotoLogPDF(this.id).then((response) => { getPhotoLogPDF(this.id).then((response) => {
let url = response.data.pdf this.$store.dispatch('setPhotoLogField',
window.open(url, '_blank') {id:this.id, field:'status', value:'generating'})
this.$router.push({name: 'LogsList'})
}).catch((error) => { }).catch((error) => {
this.message.error('Cannot generate PDF file: ' + error) this.message.error('Cannot generate PDF file: ' + error)
}).finally(() => { }).finally(() => {

View File

@ -9,17 +9,28 @@
<th>delete</th> <th>delete</th>
</thead> </thead>
<tbody> <tbody>
<tr v-for="photolog in photoLogList" :key="photolog.id"> <tr
<router-link :to="{name: 'CreateLog', params: {e: photolog.id}}"> v-for="photolog in photoLogList"
<td>{{ photolog.title }}</td> :key="photolog.id"
</router-link> :class="{
<td> 'opacity-25': photolog.status === 'generating'
<a :href="photolog.pdf" target="_blank">PDF</a> }"
</td> >
<td>{{ photolog.date }}</td> <router-link :to="{name: 'CreateLog', params: {e: photolog.id}}">
<td> <td>{{ photolog.title }}</td>
<n-button type="error" @click="askDeleteLog(photolog.id)">Delete</n-button> </router-link>
</td> <td>
<n-spin :show="photolog.status === 'generating'">
<p
@click="openPdfModal(photolog.title, photolog.pdf)"
class="cursor-pointer"
>PDF</p>
</n-spin>
</td>
<td>{{ photolog.date }}</td>
<td>
<n-button type="error" @click="askDeleteLog(photolog.id)">Delete</n-button>
</td>
</tr> </tr>
</tbody> </tbody>
</n-table> </n-table>
@ -35,6 +46,21 @@
negative-text="Delete" negative-text="Delete"
@negative-click="deleteLog" @negative-click="deleteLog"
/> />
<n-modal
v-model:show="showPdfModal"
:mask-closable="true"
:title="pdfModalTitle"
preset="card"
style="max-width: 90vw; max-height: 90vh;"
>
<iframe
:src="pdfModalLink"
width="100%"
style="height: 75vh;"
>
</iframe>
</n-modal>
</div> </div>
</template> </template>
@ -51,10 +77,16 @@ export default {
showDeleteModal: false, showDeleteModal: false,
deleteId: null, deleteId: null,
deleteModalContent: '', deleteModalContent: '',
showPdfModal: false,
pdfModalTitle: '',
pdfModalLink: '',
} }
}, },
mounted() { mounted() {
this.$store.dispatch('loadPhotoLogList') if (this.$store.state.photoLogList.length == 0) {
this.$store.dispatch('loadPhotoLogList')
}
}, },
computed: { computed: {
photoLogList() { photoLogList() {
@ -82,7 +114,12 @@ export default {
this.$store.dispatch('deletePhotoLog', this.deleteId) this.$store.dispatch('deletePhotoLog', this.deleteId)
this.deleteId = null this.deleteId = null
} }
} },
openPdfModal(title, link) {
this.showPdfModal = true
this.pdfModalTitle = title
this.pdfModalLink = link
},
} }
} }
</script> </script>

View File

@ -25,6 +25,7 @@
:can_change_ocr="true" :can_change_ocr="true"
:can_change_tag="true" :can_change_tag="true"
:can_delete="true" :can_delete="true"
:loading="photosStatus[photo.id] === 'cropping'"
@update:group="change_group_modal(photo.id)" @update:group="change_group_modal(photo.id)"
@update:crop="change_crop_modal(photo.id)" @update:crop="change_crop_modal(photo.id)"
@update:ocr="change_ocr_modal(photo.id)" @update:ocr="change_ocr_modal(photo.id)"
@ -216,6 +217,9 @@ export default {
photos() { photos() {
return this.$store.state.photos return this.$store.state.photos
}, },
photosStatus() {
return this.$store.state.photosStatus
},
groups_select_options() { groups_select_options() {
if (this.photoGroups.length > 0) { if (this.photoGroups.length > 0) {
let options = [] let options = []
@ -338,6 +342,10 @@ export default {
rotate: rotate rotate: rotate
}).then(()=>{ }).then(()=>{
setTimeout(()=>{ setTimeout(()=>{
this.$store.dispatch('setPhotoStatus', {
id: this.current_photo,
status: 'cropping'
})
this.$store.dispatch('cropPhoto', { this.$store.dispatch('cropPhoto', {
id: this.current_photo, id: this.current_photo,
mode: 'bbox' mode: 'bbox'