mirror of
https://github.com/MarcZierle/photo-log-frontend.git
synced 2025-04-12 07:04:36 +00:00
add photo tags and template management
This commit is contained in:
parent
bfb3b2afbe
commit
a60e0666df
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"es6": true
|
"es6": true,
|
||||||
|
"node": true
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
|
15641
package-lock.json
generated
15641
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -8,12 +8,12 @@
|
|||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/postcss7-compat": "^2.2.17",
|
"autoprefixer": "^9.8.8",
|
||||||
"autoprefixer": "^9",
|
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"postcss": "^7",
|
"postcss": "^7.0.39",
|
||||||
|
"tailwind-children": "^0.5.0",
|
||||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17",
|
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
"vue-advanced-cropper": "^2.8.0",
|
"vue-advanced-cropper": "^2.8.0",
|
||||||
@ -49,6 +49,6 @@
|
|||||||
"sass": "^1.26.5",
|
"sass": "^1.26.5",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"vfonts": "^0.1.0",
|
"vfonts": "^0.1.0",
|
||||||
"vue-cli-plugin-tailwind": "~2.2.18"
|
"vue-cli-plugin-tailwind": "~3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,8 @@ export default defineComponent({
|
|||||||
computed: {
|
computed: {
|
||||||
doesntNeedNav() {
|
doesntNeedNav() {
|
||||||
let currentPage = this.$route.name
|
let currentPage = this.$route.name
|
||||||
return !(currentPage == 'Home' || this.$route.name == 'CameraCapture')
|
//return !(currentPage == 'Home' || currentPage == 'CameraCapture')
|
||||||
|
return !(currentPage == 'CameraCapture')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -14,6 +14,10 @@ export function getPhotoLogList() {
|
|||||||
return apiClient.get('/photologs/')
|
return apiClient.get('/photologs/')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPhotoLogTemplateList() {
|
||||||
|
return apiClient.get('/photolog/templates/')
|
||||||
|
}
|
||||||
|
|
||||||
export function getPhotoLog(id) {
|
export function getPhotoLog(id) {
|
||||||
return apiClient.get('/photolog/'+id+'/')
|
return apiClient.get('/photolog/'+id+'/')
|
||||||
}
|
}
|
||||||
@ -22,7 +26,17 @@ export function addNewPhotoLog(title, date) {
|
|||||||
return apiClient.post('/addphotolog/', {
|
return apiClient.post('/addphotolog/', {
|
||||||
title,
|
title,
|
||||||
date: date,
|
date: date,
|
||||||
render_date: true,
|
render_date: false,
|
||||||
|
start_slide_image: null,
|
||||||
|
slides: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addNewPhotoLogTemplate(title, date) {
|
||||||
|
return apiClient.post('/photolog/template/', {
|
||||||
|
title,
|
||||||
|
date: date,
|
||||||
|
render_date: false,
|
||||||
start_slide_image: null,
|
start_slide_image: null,
|
||||||
slides: []
|
slides: []
|
||||||
})
|
})
|
||||||
@ -38,14 +52,32 @@ export function updatePhotoLog({id, title, date, render_date, start_slide_image,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateLogTemplate({id, title, date, render_date, start_slide_image, slides}) {
|
||||||
|
return apiClient.put('/photolog/template/' + id + '/', {
|
||||||
|
title,
|
||||||
|
date,
|
||||||
|
render_date,
|
||||||
|
start_slide_image,
|
||||||
|
slides
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function deletePhotoLog(id) {
|
export function deletePhotoLog(id) {
|
||||||
return apiClient.delete('/deletephotolog/'+id+'/')
|
return apiClient.delete('/deletephotolog/'+id+'/')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deletePhotoLogTemplate(id) {
|
||||||
|
return apiClient.delete('/photolog/template/'+id+'/')
|
||||||
|
}
|
||||||
|
|
||||||
export function getPhotoGroups() {
|
export function getPhotoGroups() {
|
||||||
return apiClient.get('/photogroups/')
|
return apiClient.get('/photogroups/')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPhotoTags() {
|
||||||
|
return apiClient.get('/phototags/')
|
||||||
|
}
|
||||||
|
|
||||||
export function getPhotosByGroup(group_id) {
|
export function getPhotosByGroup(group_id) {
|
||||||
return apiClient.get('/photos/?photogroup='+group_id)
|
return apiClient.get('/photos/?photogroup='+group_id)
|
||||||
}
|
}
|
||||||
@ -61,7 +93,14 @@ export function addNewPhotoGroup(name, date) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addNewPhoto(original_image, cropped_image=null, group_id=null, ocr_text=null, legacy_id=null) {
|
export function addNewPhotoTag(name, color) {
|
||||||
|
return apiClient.post('/phototag/', {
|
||||||
|
name,
|
||||||
|
color
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addNewPhoto(original_image, cropped_image=null, group_id=null, ocr_text=null, tag_id=null, legacy_id=null) {
|
||||||
const data = new FormData()
|
const data = new FormData()
|
||||||
|
|
||||||
data.append('original_image', original_image, original_image.name)
|
data.append('original_image', original_image, original_image.name)
|
||||||
@ -72,6 +111,10 @@ export function addNewPhoto(original_image, cropped_image=null, group_id=null, o
|
|||||||
data.append('group', group_id)
|
data.append('group', group_id)
|
||||||
if (ocr_text !== null)
|
if (ocr_text !== null)
|
||||||
data.append('ocr_text', ocr_text)
|
data.append('ocr_text', ocr_text)
|
||||||
|
if (tag_id === null) {
|
||||||
|
tag_id = 1
|
||||||
|
}
|
||||||
|
data.append('tag', tag_id)
|
||||||
if (legacy_id !== null)
|
if (legacy_id !== null)
|
||||||
data.append('legacy_id', legacy_id)
|
data.append('legacy_id', legacy_id)
|
||||||
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav>
|
<nav class="flex flex-row static">
|
||||||
<router-link :to="{name: 'Home'}">Home</router-link>
|
<router-link :to="{name: 'Home'}">Home</router-link>
|
||||||
|
<router-link :to="{name: 'ManagePhotos'}">Photos</router-link>
|
||||||
<router-link :to="{name: 'LogsList'}">All Logs</router-link>
|
<router-link :to="{name: 'LogsList'}">All Logs</router-link>
|
||||||
<router-link :to="{name: 'CreateLog'}">+ New</router-link>
|
<router-link :to="{name: 'CreateLog'}">+ New Log</router-link>
|
||||||
|
<router-link :to="{name: 'LogTemplatesList'}">All Templates</router-link>
|
||||||
|
<router-link :to="{name: 'CreateLogTemplate'}">+ New Template</router-link>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -16,8 +19,27 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
nav {
|
nav {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-bottom: 1px solid gray;
|
box-shadow: 0px 0px 10px rgba(0,0,0,0.2);
|
||||||
|
margin-bottom: 1em;
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
padding: 0.75em;
|
||||||
|
transition: all 0.15s ease-in-out;
|
||||||
|
border-right: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.router-link-exact-active,
|
||||||
|
a:hover {
|
||||||
|
background-color: seagreen;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.router-link-exact-active {
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -155,9 +155,9 @@ export default {
|
|||||||
|
|
||||||
return [top_left, top_right, bottom_right, bottom_left]
|
return [top_left, top_right, bottom_right, bottom_left]
|
||||||
},
|
},
|
||||||
change({ coordinates, canvas }) {
|
change({ coordinates, /*canvas*/ }) {
|
||||||
try {
|
try {
|
||||||
this.preview_data_url = canvas.toDataURL()
|
//this.preview_data_url = canvas.toDataURL()
|
||||||
this.bbox = this.coordinates_to_bbox(coordinates)
|
this.bbox = this.coordinates_to_bbox(coordinates)
|
||||||
} catch (e) {return}
|
} catch (e) {return}
|
||||||
},
|
},
|
||||||
|
@ -43,6 +43,13 @@
|
|||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</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">
|
<div class="options-item" v-if="can_delete">
|
||||||
<n-button text type="error" @click="$emit('update:delete')">
|
<n-button text type="error" @click="$emit('update:delete')">
|
||||||
<template #icon><n-icon><TrashBinSharp /></n-icon></template>
|
<template #icon><n-icon><TrashBinSharp /></n-icon></template>
|
||||||
@ -60,6 +67,7 @@ import TrashBinSharp from '@vicons/ionicons5/TrashBinSharp'
|
|||||||
import SortFilled from '@vicons/material/SortFilled'
|
import SortFilled from '@vicons/material/SortFilled'
|
||||||
import CropRotateFilled from '@vicons/material/CropRotateFilled'
|
import CropRotateFilled from '@vicons/material/CropRotateFilled'
|
||||||
import Check from '@vicons/fa/Check'
|
import Check from '@vicons/fa/Check'
|
||||||
|
import MdPricetag from '@vicons/ionicons4/MdPricetag'
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -69,6 +77,7 @@ export default {
|
|||||||
SortFilled,
|
SortFilled,
|
||||||
CropRotateFilled,
|
CropRotateFilled,
|
||||||
Check,
|
Check,
|
||||||
|
MdPricetag,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
src: {
|
src: {
|
||||||
@ -91,6 +100,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
can_change_tag: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
can_crop: {
|
can_crop: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@ -117,6 +131,7 @@ export default {
|
|||||||
'update:delete',
|
'update:delete',
|
||||||
'update:crop',
|
'update:crop',
|
||||||
'update:ocr',
|
'update:ocr',
|
||||||
|
'update:tag',
|
||||||
'update:group',
|
'update:group',
|
||||||
],
|
],
|
||||||
data() {
|
data() {
|
||||||
@ -124,7 +139,7 @@ export default {
|
|||||||
hover: false,
|
hover: false,
|
||||||
selected: false,
|
selected: false,
|
||||||
|
|
||||||
isImgLoaded: false,
|
isImgLoaded: true,
|
||||||
imgSrcObj: null,
|
imgSrcObj: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -153,6 +168,7 @@ export default {
|
|||||||
this.can_change_group,
|
this.can_change_group,
|
||||||
this.can_crop,
|
this.can_crop,
|
||||||
this.can_change_ocr,
|
this.can_change_ocr,
|
||||||
|
this.can_change_tag,
|
||||||
this.can_delete
|
this.can_delete
|
||||||
].filter(Boolean).length * 35
|
].filter(Boolean).length * 35
|
||||||
},
|
},
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
<PhotoItem
|
<PhotoItem
|
||||||
v-for="photo in photos[group.id]"
|
v-for="photo in photos[group.id]"
|
||||||
:key="photo.id"
|
:key="photo.id"
|
||||||
width="150"
|
:width="150"
|
||||||
:src="photo.cropped_image !== null ? photo.cropped_image : photo.original_image"
|
:src="photo.cropped_image !== null ? photo.cropped_image : photo.original_image"
|
||||||
:can_select="true"
|
:can_select="true"
|
||||||
:init_selection="is_photo_selected(photo.id)"
|
:init_selection="is_photo_selected(photo.id)"
|
||||||
|
@ -17,7 +17,7 @@ var app = createApp(App).use(store).use(router)
|
|||||||
app.use(createMetaManager())
|
app.use(createMetaManager())
|
||||||
app.use(metaPlugin)
|
app.use(metaPlugin)
|
||||||
|
|
||||||
app.use(i18n)
|
//app.use(i18n)
|
||||||
app.use(naive)
|
app.use(naive)
|
||||||
|
|
||||||
loadAndSetLocale(i18n, 'en')
|
loadAndSetLocale(i18n, 'en')
|
||||||
|
@ -7,7 +7,9 @@ import {
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import HomeView from '../views/Home.vue'
|
import HomeView from '../views/Home.vue'
|
||||||
import LogsList from '../views/LogsList.vue'
|
import LogsList from '../views/LogsList.vue'
|
||||||
|
import LogTemplatesList from '../views/LogTemplatesList.vue'
|
||||||
import CreateLog from '../views/CreateLog.vue'
|
import CreateLog from '../views/CreateLog.vue'
|
||||||
|
import CreateLogTemplate from '../views/CreateLogTemplate.vue'
|
||||||
import CameraCapture from '../views/CameraCapture.vue'
|
import CameraCapture from '../views/CameraCapture.vue'
|
||||||
import ManagePhotos from '../views/ManagePhotos.vue'
|
import ManagePhotos from '../views/ManagePhotos.vue'
|
||||||
import DevView from '../views/DevView.vue'
|
import DevView from '../views/DevView.vue'
|
||||||
@ -35,6 +37,23 @@ const routes = [
|
|||||||
return {e}
|
return {e}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/templates',
|
||||||
|
name: 'LogTemplatesList',
|
||||||
|
component: LogTemplatesList
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/templates/create/:e?',
|
||||||
|
name: 'CreateLogTemplate',
|
||||||
|
component: CreateLogTemplate,
|
||||||
|
props: (route) => {
|
||||||
|
const e = Number.parseInt(route.params.e)
|
||||||
|
if (Number.isNaN(e)) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return {e}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/capture',
|
path: '/capture',
|
||||||
name: 'CameraCapture',
|
name: 'CameraCapture',
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
import { createStore } from 'vuex'
|
import { createStore } from 'vuex'
|
||||||
import {
|
import {
|
||||||
getPhotoLogList,
|
getPhotoLogList,
|
||||||
|
getPhotoLogTemplateList,
|
||||||
deletePhotoLog,
|
deletePhotoLog,
|
||||||
|
deletePhotoLogTemplate,
|
||||||
addNewPhotoLog,
|
addNewPhotoLog,
|
||||||
|
addNewPhotoLogTemplate,
|
||||||
getPhotoLog,
|
getPhotoLog,
|
||||||
updatePhotoLog,
|
updatePhotoLog,
|
||||||
|
updateLogTemplate,
|
||||||
getPhotoGroups,
|
getPhotoGroups,
|
||||||
|
getPhotoTags,
|
||||||
getPhotosByGroup,
|
getPhotosByGroup,
|
||||||
updatePhoto,
|
updatePhoto,
|
||||||
deletePhoto,
|
deletePhoto,
|
||||||
@ -15,14 +20,19 @@ import {
|
|||||||
export default createStore({
|
export default createStore({
|
||||||
state: {
|
state: {
|
||||||
photoLogList: [],
|
photoLogList: [],
|
||||||
|
photoLogTemplateList: [],
|
||||||
photoLogs: [],
|
photoLogs: [],
|
||||||
photoGroups: null,
|
photoGroups: null,
|
||||||
|
photoTags: null,
|
||||||
photos: []
|
photos: []
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
SET_PHOTO_LOG_LIST(state, newPhotoLogList) {
|
SET_PHOTO_LOG_LIST(state, newPhotoLogList) {
|
||||||
state.photoLogList = newPhotoLogList
|
state.photoLogList = newPhotoLogList
|
||||||
},
|
},
|
||||||
|
SET_PHOTO_LOG_TEMPLATE_LIST(state, newPhotoLogTemplateList) {
|
||||||
|
state.photoLogTemplateList = newPhotoLogTemplateList
|
||||||
|
},
|
||||||
SET_PHOTO_LOG(state, photoLog) {
|
SET_PHOTO_LOG(state, photoLog) {
|
||||||
let log_index = state.photoLogs.findIndex(log => log.id === photoLog.id)
|
let log_index = state.photoLogs.findIndex(log => log.id === photoLog.id)
|
||||||
if (log_index > -1) {
|
if (log_index > -1) {
|
||||||
@ -31,13 +41,28 @@ export default createStore({
|
|||||||
state.photoLogs.push(photoLog)
|
state.photoLogs.push(photoLog)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
SET_PHOTO_LOG_TEMPLATE(state, photoLogtemplate) {
|
||||||
|
let log_index = state.photoLogTemplateList.findIndex(log => log.id === photoLogtemplate.id)
|
||||||
|
if (log_index > -1) {
|
||||||
|
state.photoLogTemplateList[log_index] = photoLogtemplate
|
||||||
|
} else {
|
||||||
|
state.photoLogTemplateList.push(photoLogtemplate)
|
||||||
|
}
|
||||||
|
},
|
||||||
REMOVE_PHOTO_LOG(state, id) {
|
REMOVE_PHOTO_LOG(state, id) {
|
||||||
let log_index = state.photoLogList.findIndex(log => log.id === id)
|
let log_index = state.photoLogList.findIndex(log => log.id === id)
|
||||||
state.photoLogList.splice(log_index, 1)
|
state.photoLogList.splice(log_index, 1)
|
||||||
},
|
},
|
||||||
|
REMOVE_PHOTO_LOG_TEMPLATE(state, id) {
|
||||||
|
let log_index = state.photoLogTemplateList.findIndex(log => log.id === id)
|
||||||
|
state.photoLogTemplateList.splice(log_index, 1)
|
||||||
|
},
|
||||||
SET_PHOTO_GROUPS(state, groups) {
|
SET_PHOTO_GROUPS(state, groups) {
|
||||||
state.photoGroups = groups
|
state.photoGroups = groups
|
||||||
},
|
},
|
||||||
|
SET_PHOTO_TAGS(state, tags) {
|
||||||
|
state.photoTags = tags
|
||||||
|
},
|
||||||
SET_PHOTOS_IN_GROUP(state, {group_id, photos}) {
|
SET_PHOTOS_IN_GROUP(state, {group_id, photos}) {
|
||||||
if (group_id in state.photos) {
|
if (group_id in state.photos) {
|
||||||
state.photos.group_id = photos
|
state.photos.group_id = photos
|
||||||
@ -88,6 +113,17 @@ export default createStore({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
loadPhotoLogTemplateList({commit}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getPhotoLogTemplateList().then((response) => {
|
||||||
|
commit('SET_PHOTO_LOG_TEMPLATE_LIST', response.data)
|
||||||
|
resolve()
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log(error)
|
||||||
|
reject()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
loadPhotoLog({commit}, id) {
|
loadPhotoLog({commit}, id) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getPhotoLog(id).then((response) => {
|
getPhotoLog(id).then((response) => {
|
||||||
@ -106,6 +142,13 @@ export default createStore({
|
|||||||
console.log(error)
|
console.log(error)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
deletePhotoLogTemplate({commit}, id) {
|
||||||
|
deletePhotoLogTemplate(id).then(() => {
|
||||||
|
commit('REMOVE_PHOTO_LOG_TEMPLATE', id)
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
addNewPhotoLog({dispatch}, {title, date}) {
|
addNewPhotoLog({dispatch}, {title, date}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
addNewPhotoLog(title, date).then((response) => {
|
addNewPhotoLog(title, date).then((response) => {
|
||||||
@ -117,12 +160,29 @@ export default createStore({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
addNewPhotoLogTemplate({dispatch}, {title, date}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
addNewPhotoLogTemplate(title, date).then((response) => {
|
||||||
|
dispatch('loadPhotoLogTemplateList')
|
||||||
|
resolve(response.data.id)
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log(error)
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
updatePhotoLogDetails({commit}, photolog) {
|
updatePhotoLogDetails({commit}, photolog) {
|
||||||
if (photolog !== null) {
|
if (photolog !== null) {
|
||||||
commit('SET_PHOTO_LOG', photolog)
|
commit('SET_PHOTO_LOG', photolog)
|
||||||
return updatePhotoLog(photolog)
|
return updatePhotoLog(photolog)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updatePhotoLogTemplate({commit}, photologtemplate) {
|
||||||
|
if (photologtemplate !== null) {
|
||||||
|
commit('SET_PHOTO_LOG_TEMPLATE', photologtemplate)
|
||||||
|
return updateLogTemplate(photologtemplate)
|
||||||
|
}
|
||||||
|
},
|
||||||
loadPhotoGroups({commit}) {
|
loadPhotoGroups({commit}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getPhotoGroups().then((response) => {
|
getPhotoGroups().then((response) => {
|
||||||
@ -133,6 +193,16 @@ export default createStore({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
loadPhotoTags({commit}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getPhotoTags().then((response) => {
|
||||||
|
commit('SET_PHOTO_TAGS', response.data)
|
||||||
|
resolve(response.data)
|
||||||
|
}).catch((error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
loadPhotosInGroup({commit}, group_id) {
|
loadPhotosInGroup({commit}, group_id) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getPhotosByGroup(group_id).then((response) => {
|
getPhotosByGroup(group_id).then((response) => {
|
||||||
@ -191,6 +261,13 @@ export default createStore({
|
|||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
|
photoLogTemplateById: (state) => (id) => {
|
||||||
|
let log = state.photoLogTemplateList.filter(log => log.id == id)
|
||||||
|
if (log.length > 0) {
|
||||||
|
return log[0]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
photoLogDetailsById: (state) => (id) => {
|
photoLogDetailsById: (state) => (id) => {
|
||||||
let log = state.photoLogs.filter(log => log.id == id)
|
let log = state.photoLogs.filter(log => log.id == id)
|
||||||
if (log.length > 0) {
|
if (log.length > 0) {
|
||||||
@ -204,6 +281,9 @@ export default createStore({
|
|||||||
photos (state) {
|
photos (state) {
|
||||||
return state.photos
|
return state.photos
|
||||||
},
|
},
|
||||||
|
photoTags(state) {
|
||||||
|
return state.photoTags
|
||||||
|
},
|
||||||
photoById: (state) => (id) => {
|
photoById: (state) => (id) => {
|
||||||
for (const group_idx in state.photos) {
|
for (const group_idx in state.photos) {
|
||||||
let found_photo = state.photos[group_idx].filter(p => p.id == id)
|
let found_photo = state.photos[group_idx].filter(p => p.id == id)
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="take_photo">
|
<div id="take_photo">
|
||||||
<n-space justify-content="center">
|
<div class="grid grid-cols-2">
|
||||||
<label for="camera_file_input">
|
<label for="camera_file_input">
|
||||||
<span :class="{disabled: !selected_group, camera_btn: true}">Take new photo</span>
|
<span :class="{disabled: !selected_group, camera_btn: true}">Take new photo</span>
|
||||||
<input
|
<input
|
||||||
@ -33,11 +33,22 @@
|
|||||||
:disabled="!selected_group"
|
:disabled="!selected_group"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<n-button v-if="selected_group && photo_src"
|
|
||||||
|
<n-button
|
||||||
|
v-if="selected_group && photo_src"
|
||||||
type="info" size="large" round
|
type="info" size="large" round
|
||||||
@click="uploadPhoto"
|
@click="uploadPhoto"
|
||||||
>Upload</n-button>
|
>Upload</n-button>
|
||||||
</n-space>
|
|
||||||
|
<div id="tag_select" v-if="selected_group && photo_src" class="col-span-2 grid grid-cols-10">
|
||||||
|
<n-select v-model:value="selected_tag" :options="tags" size="large" class="col-span-8" />
|
||||||
|
<div class="col-span-2 content-start">
|
||||||
|
<n-button secondary type="primary" @click="showAddTagModal = true" size="large" style="margin-top: 0 !important;">
|
||||||
|
<n-icon><TagEdit /></n-icon>
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -59,6 +70,25 @@
|
|||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showAddTagModal"
|
||||||
|
:mask-closable="true"
|
||||||
|
transform-origin="center"
|
||||||
|
preset="card"
|
||||||
|
title="Add new Tag"
|
||||||
|
:bordered="false"
|
||||||
|
size="huge">
|
||||||
|
|
||||||
|
<n-input placeholder="Enter tag name" size="large" v-model:value="new_tag_name" />
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify-content="end">
|
||||||
|
<n-button size="large" @click="showAddTagModal = false">Cancel</n-button>
|
||||||
|
<n-button type="primary" size="large" @click="addNewTag">Add Tag</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
</n-config-provider>
|
</n-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -69,13 +99,15 @@ import {darkTheme} from 'naive-ui'
|
|||||||
|
|
||||||
import CreateNewFolderRound from '@vicons/material/CreateNewFolderRound'
|
import CreateNewFolderRound from '@vicons/material/CreateNewFolderRound'
|
||||||
import HandPointUp from '@vicons/fa/HandPointUp'
|
import HandPointUp from '@vicons/fa/HandPointUp'
|
||||||
|
import TagEdit from '@vicons/carbon/TagEdit'
|
||||||
|
|
||||||
import { getPhotoGroups, addNewPhotoGroup, addNewPhoto, cropPhoto } from '@/api'
|
import { getPhotoGroups, getPhotoTags, addNewPhotoGroup, addNewPhotoTag, addNewPhoto, cropPhoto } from '@/api'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CreateNewFolderRound,
|
CreateNewFolderRound,
|
||||||
HandPointUp,
|
HandPointUp,
|
||||||
|
TagEdit,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
useMeta({
|
useMeta({
|
||||||
@ -92,7 +124,12 @@ export default {
|
|||||||
selected_group: null,
|
selected_group: null,
|
||||||
showAddGroupModal: false,
|
showAddGroupModal: false,
|
||||||
|
|
||||||
|
tags: [],
|
||||||
|
selected_tag: null,
|
||||||
|
showAddTagModal: false,
|
||||||
|
|
||||||
new_group_name: '',
|
new_group_name: '',
|
||||||
|
new_tag_name: '',
|
||||||
|
|
||||||
isPhotoTaken: false,
|
isPhotoTaken: false,
|
||||||
photo_src: '',
|
photo_src: '',
|
||||||
@ -102,7 +139,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
this.fetchPhotoGroups()
|
this.fetchPhotoGroupsAndTags()
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
window.scrollTo(0, 1)
|
window.scrollTo(0, 1)
|
||||||
@ -116,7 +153,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchPhotoGroups() {
|
fetchPhotoGroupsAndTags() {
|
||||||
this.groups = []
|
this.groups = []
|
||||||
getPhotoGroups().then((response) => {
|
getPhotoGroups().then((response) => {
|
||||||
let data = response.data
|
let data = response.data
|
||||||
@ -130,6 +167,19 @@ export default {
|
|||||||
}).catch((error)=>{
|
}).catch((error)=>{
|
||||||
this.message.error('There was an error while fetching photo groups: '+error)
|
this.message.error('There was an error while fetching photo groups: '+error)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.tags = []
|
||||||
|
getPhotoTags().then((response) => {
|
||||||
|
let data = response.data
|
||||||
|
for(const data_idx in data) {
|
||||||
|
this.tags.push({
|
||||||
|
label: data[data_idx].name,
|
||||||
|
value: data[data_idx].id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).catch((error)=>{
|
||||||
|
this.message.error('There was an error while fetching photo tags: '+error)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
sortPhotoGroupsByDate(groups) {
|
sortPhotoGroupsByDate(groups) {
|
||||||
if (groups !== null && groups.length > 0) {
|
if (groups !== null && groups.length > 0) {
|
||||||
@ -168,6 +218,29 @@ export default {
|
|||||||
this.new_group_name = ''
|
this.new_group_name = ''
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
addNewTag() {
|
||||||
|
if (this.new_tag_name.length == 0) {
|
||||||
|
this.message.error('Please enter a new tag name')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = Math.floor(Math.random() * 256).toString(16)
|
||||||
|
let g = Math.floor(Math.random() * 256).toString(16)
|
||||||
|
let b = Math.floor(Math.random() * 256).toString(16)
|
||||||
|
|
||||||
|
let color = '#' + r + g + b
|
||||||
|
|
||||||
|
addNewPhotoTag(this.new_tag_name, color).then((response) => {
|
||||||
|
this.message.success('Added tag '+this.new_tag_name)
|
||||||
|
this.fetchPhotoGroupsAndTags()
|
||||||
|
this.showAddTagModal = false
|
||||||
|
this.selected_tag = response.data.id
|
||||||
|
}).catch((error)=>{
|
||||||
|
this.message.error('There was an error while adding the tag: '+error)
|
||||||
|
}).finally(() => {
|
||||||
|
this.new_tag_name = ''
|
||||||
|
})
|
||||||
|
},
|
||||||
previewImage(event) {
|
previewImage(event) {
|
||||||
let files = event.target.files
|
let files = event.target.files
|
||||||
this.photo_file = files[0]
|
this.photo_file = files[0]
|
||||||
@ -176,12 +249,13 @@ export default {
|
|||||||
uploadPhoto() {
|
uploadPhoto() {
|
||||||
this.isUploading = true
|
this.isUploading = true
|
||||||
|
|
||||||
addNewPhoto(this.photo_file, null, this.selected_group).then((response) => {
|
addNewPhoto(this.photo_file, null, this.selected_group, null, this.selected_tag).then((response) => {
|
||||||
this.message.success('Done!')
|
this.message.success('Done!')
|
||||||
|
|
||||||
this.isUploading = false
|
this.isUploading = false
|
||||||
this.photo_src = ''
|
this.photo_src = ''
|
||||||
this.photo_file = null
|
this.photo_file = null
|
||||||
|
this.selected_tag = null
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
cropPhoto(response.data.id, 'auto').then(()=>{
|
cropPhoto(response.data.id, 'auto').then(()=>{
|
||||||
@ -229,6 +303,10 @@ html, body {
|
|||||||
height: 7.5vh;
|
height: 7.5vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#tag_select {
|
||||||
|
height: 7.5vh;
|
||||||
|
}
|
||||||
|
|
||||||
#prompt_select {
|
#prompt_select {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<n-space justify="space-around">
|
<n-space justify="space-around">
|
||||||
<h1>Create Log</h1>
|
<h1 class="font-bold text-2xl m-4 my-6">Create Log</h1>
|
||||||
<n-button
|
<n-button
|
||||||
type="primary" style="margin: 1em;"
|
type="primary" style="margin: 1em;"
|
||||||
@click="getPDF"
|
@click="getPDF"
|
||||||
|
644
src/views/CreateLogTemplate.vue
Normal file
644
src/views/CreateLogTemplate.vue
Normal file
@ -0,0 +1,644 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<n-space justify="space-around">
|
||||||
|
<h1 class="font-bold text-2xl m-4 my-6">Create Log Template</h1>
|
||||||
|
<n-button type="primary"
|
||||||
|
:disabled="!unsaved_changes"
|
||||||
|
:loading="currently_saving"
|
||||||
|
@click="saveChanges"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon><SaveFilled /></n-icon>
|
||||||
|
</template>
|
||||||
|
{{saving_info}}
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
|
||||||
|
<n-space justify="space-around">
|
||||||
|
<n-input size="large" round placeholder="Photo Log title" v-model:value="title" @change="updateServerData" />
|
||||||
|
<n-space vertical>
|
||||||
|
<n-button size="small" @click="selectPhotoForStartSlide">Choose Cover Photo</n-button>
|
||||||
|
<n-image
|
||||||
|
width="100"
|
||||||
|
:src="getPhotoSrcById(start_slide_image)"
|
||||||
|
/>
|
||||||
|
</n-space>
|
||||||
|
<n-space vertical>
|
||||||
|
<n-date-picker v-model:value="date" type="date" @update:value="updateServerData" />
|
||||||
|
<n-checkbox v-model:checked="render_date">show date on first slide?</n-checkbox>
|
||||||
|
</n-space>
|
||||||
|
</n-space>
|
||||||
|
|
||||||
|
<n-divider title-placement="center">Slides</n-divider>
|
||||||
|
|
||||||
|
<p class="text-sm m-6 italic" style="color: #ccc;">HINT: Double click on a photo slot to remove it.</p>
|
||||||
|
|
||||||
|
<n-space justify="center">
|
||||||
|
<n-space vertical style="max-width: 80vw; width: 600px;">
|
||||||
|
<n-card
|
||||||
|
class="add-slide-between-button:hover"
|
||||||
|
@click="addSlideAfter(-1)">
|
||||||
|
<n-space justify="center">
|
||||||
|
<n-button circle secondary type="info">
|
||||||
|
<n-icon><LibraryAddRound /></n-icon>
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<draggable
|
||||||
|
:list="slides"
|
||||||
|
item-key="id"
|
||||||
|
style="width: 100%;"
|
||||||
|
>
|
||||||
|
<template #item="{ element: slide, index }">
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<n-card
|
||||||
|
:title="'#'+(index+1)" class=".move-slide-handle">
|
||||||
|
|
||||||
|
<template #header-extra>
|
||||||
|
<n-space>
|
||||||
|
<n-button round secondary type="default" @click="selectPhotoTagForSlide(index)">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon><MdPricetag /></n-icon>
|
||||||
|
</template>
|
||||||
|
Add Photo Slot
|
||||||
|
</n-button>
|
||||||
|
|
||||||
|
<n-button round secondary type="success" @click="selectPhotosForSlide(index)">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon><AddPhotoAlternateOutlined /></n-icon>
|
||||||
|
</template>
|
||||||
|
Select Photos
|
||||||
|
</n-button>
|
||||||
|
|
||||||
|
<n-popconfirm
|
||||||
|
type="error"
|
||||||
|
>
|
||||||
|
<template #trigger>
|
||||||
|
<n-button
|
||||||
|
circle secondary
|
||||||
|
type="error"
|
||||||
|
>
|
||||||
|
<n-icon><TrashBinSharp /></n-icon>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
Delete slide?
|
||||||
|
<template #action>
|
||||||
|
<n-button size="small" type="error" @click="removeSlide(index)"> Delete </n-button>
|
||||||
|
</template>
|
||||||
|
</n-popconfirm>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<n-image-group>
|
||||||
|
<draggable
|
||||||
|
:list="slide"
|
||||||
|
item-key="id"
|
||||||
|
@start="drag = true"
|
||||||
|
@end="drag = false"
|
||||||
|
>
|
||||||
|
<template #item="{element: slide_item, index: item_index}">
|
||||||
|
<n-image
|
||||||
|
style="cursor: move; vertical-align: middle;"
|
||||||
|
v-if="slide_item.type == 'photo'"
|
||||||
|
width="100"
|
||||||
|
:src="getPhotoSrcById(slide_item.id)"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-else-if="slide_item.type == 'tag'"
|
||||||
|
class="tag-slot"
|
||||||
|
:style="tag_style(slide_item.id)"
|
||||||
|
@dblclick="removeTagFromSlide(index, item_index)"
|
||||||
|
>
|
||||||
|
<p>{{getPhotoTagById(slide_item.id).name}}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
</n-image-group>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<n-card
|
||||||
|
class="add-slide-between-button"
|
||||||
|
@click="addSlideAfter(index)">
|
||||||
|
<n-space justify="center">
|
||||||
|
<n-button circle secondary type="info">
|
||||||
|
<n-icon><LibraryAddRound /></n-icon>
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</n-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
|
||||||
|
</n-space>
|
||||||
|
</n-space>
|
||||||
|
|
||||||
|
<PhotoSelectModal
|
||||||
|
v-model:showSelection="selectPhotosModal"
|
||||||
|
@closed="selectPhotosModal=false"
|
||||||
|
@selected="addPhotosToSlide"
|
||||||
|
:max_select=max_photos_per_slide :or_less=true
|
||||||
|
:already-selected=getPhotosInSlide(selectedSlide) />
|
||||||
|
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showPreventLeaveModal"
|
||||||
|
:mask-closable="true"
|
||||||
|
type="warning"
|
||||||
|
preset="dialog"
|
||||||
|
title="Unsaved Changes"
|
||||||
|
content="Are you sure that you want to leave this page. All unsaved data might be lost!"
|
||||||
|
transform-origin="center"
|
||||||
|
positive-text="Stay on page"
|
||||||
|
@negative-click="continueLeavePage"
|
||||||
|
@positive-click="showPreventLeaveModal = false; leaving_to_page = null;"
|
||||||
|
negative-text="Leave"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showSelectTagModal"
|
||||||
|
:mask-closable="true" >
|
||||||
|
<n-card
|
||||||
|
style="width: 400px;"
|
||||||
|
title="Add Photo Slot"
|
||||||
|
:bordered="false"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
>
|
||||||
|
<n-select v-model:value="selected_tag_id" :options="tags_select_options" />
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify="end">
|
||||||
|
<n-button @click="showSelectTagModal=false">Cancel</n-button>
|
||||||
|
<n-button type="success" @click="addSlotToSlide">Select Tag</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-card>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
|
<n-modal v-model:show="showCreateModal" :mask-closable=false>
|
||||||
|
<n-spin :show="isCreateLoading">
|
||||||
|
<n-card
|
||||||
|
style="width: 600px;"
|
||||||
|
title="Create a new Photo Log Template"
|
||||||
|
:bordered="false"
|
||||||
|
size="huge"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
>
|
||||||
|
|
||||||
|
<n-form-item label="What's the name of the photo log template?" path="title">
|
||||||
|
<n-input v-model:value="title" type="text" placeholder="Photo Log Template title" />
|
||||||
|
</n-form-item>
|
||||||
|
<!--
|
||||||
|
<n-form-item label="When did the training take place?" path="date">
|
||||||
|
<n-date-picker v-model:value="date" type="date" clearable />
|
||||||
|
</n-form-item>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify="end">
|
||||||
|
<n-button @click="navigateBack">Cancel</n-button>
|
||||||
|
<n-button @click="createNewPhotoLogTemplate" type="success">Create</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</n-card>
|
||||||
|
</n-spin>
|
||||||
|
</n-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {ref} from 'vue'
|
||||||
|
|
||||||
|
import { useMeta } from 'vue-meta'
|
||||||
|
import { useMessage } from 'naive-ui'
|
||||||
|
|
||||||
|
import draggable from 'vuedraggable'
|
||||||
|
|
||||||
|
import TrashBinSharp from '@vicons/ionicons5/TrashBinSharp'
|
||||||
|
import LibraryAddRound from '@vicons/material/LibraryAddRound'
|
||||||
|
import AddPhotoAlternateOutlined from '@vicons/material/AddPhotoAlternateOutlined'
|
||||||
|
import SaveFilled from '@vicons/material/SaveFilled'
|
||||||
|
import MdPricetag from '@vicons/ionicons4/MdPricetag'
|
||||||
|
|
||||||
|
import PhotoSelectModal from '@/components/PhotoSelectModal'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CreateLog',
|
||||||
|
components: {
|
||||||
|
draggable,
|
||||||
|
PhotoSelectModal,
|
||||||
|
TrashBinSharp,
|
||||||
|
LibraryAddRound,
|
||||||
|
AddPhotoAlternateOutlined,
|
||||||
|
SaveFilled,
|
||||||
|
MdPricetag,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
e: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: -1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: Number.parseInt(this.e),
|
||||||
|
title: '',
|
||||||
|
date: null,
|
||||||
|
start_slide_image: null,
|
||||||
|
slides: [],
|
||||||
|
|
||||||
|
slide_for_adding_slot: null,
|
||||||
|
selected_tag_id: null,
|
||||||
|
showSelectTagModal: false,
|
||||||
|
|
||||||
|
selectPhotosModal: false,
|
||||||
|
selectedSlide: null,
|
||||||
|
max_photos_per_slide: 3,
|
||||||
|
|
||||||
|
unsaved_changes: false,
|
||||||
|
currently_saving: false,
|
||||||
|
|
||||||
|
isLoadingData: false,
|
||||||
|
isCreateLoading: false,
|
||||||
|
isSavingServer: false,
|
||||||
|
isGeneratingPDF: false,
|
||||||
|
|
||||||
|
showPreventLeaveModal: false,
|
||||||
|
leaving_to_page: null,
|
||||||
|
|
||||||
|
drag: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
useMeta({ title: 'Create a new Photo Log Template' })
|
||||||
|
const message = useMessage()
|
||||||
|
return { message, render_date: ref(false) }
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.isValidId()) {
|
||||||
|
this.isLoadingData = true
|
||||||
|
|
||||||
|
this.$store.dispatch('loadPhotoGroups').then(() => {
|
||||||
|
this.photoGroups = this.$store.getters.photoGroups
|
||||||
|
this.$store.dispatch('loadPhotosInAllGroups')
|
||||||
|
})
|
||||||
|
|
||||||
|
this.findPhotoLogTemplate().then(() => {
|
||||||
|
this.updateLocalData()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.dispatch('loadPhotoTags')
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
title: function () { this.unsaved_changes = true },
|
||||||
|
date: function () { this.unsaved_changes = true},
|
||||||
|
render_date: function () { this.unsaved_changes = true},
|
||||||
|
start_slide_image: function () { this.unsaved_changes = true},
|
||||||
|
slides: {handler() { this.unsaved_changes = true}, deep: true},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
saving_info() {
|
||||||
|
if (!this.unsaved_changes)
|
||||||
|
return 'No changes made'
|
||||||
|
if (this.currently_saving)
|
||||||
|
return 'Saving...'
|
||||||
|
return 'Save Changes'
|
||||||
|
},
|
||||||
|
photos() {
|
||||||
|
return this.$store.getters.photos
|
||||||
|
},
|
||||||
|
tags() {
|
||||||
|
return this.$store.getters.photoTags
|
||||||
|
},
|
||||||
|
showCreateModal () {
|
||||||
|
return !this.isValidId()
|
||||||
|
},
|
||||||
|
dateStr () {
|
||||||
|
if (this.date !== null) {
|
||||||
|
let date_obj = new Date(this.date)
|
||||||
|
let month = Number.parseInt(date_obj.getMonth())+1
|
||||||
|
month = month < 10 ? '0' + month : month
|
||||||
|
let day = Number.parseInt(date_obj.getDate())
|
||||||
|
day = day < 10 ? '0' + day : day
|
||||||
|
let date_str = date_obj.getFullYear() + '-' + month + '-' + day
|
||||||
|
return date_str
|
||||||
|
}
|
||||||
|
return '1970-01-01'
|
||||||
|
},
|
||||||
|
tags_select_options() {
|
||||||
|
if (this.tags != null && this.tags.length > 0) {
|
||||||
|
let options = []
|
||||||
|
for (const idx in this.tags) {
|
||||||
|
options.push({
|
||||||
|
value: this.tags[idx].id,
|
||||||
|
label: this.tags[idx].name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
saveChanges() {
|
||||||
|
return new Promise((reject, resolve) => {
|
||||||
|
this.currently_saving = true
|
||||||
|
this.updateServerData().catch(() => {
|
||||||
|
reject()
|
||||||
|
}).finally(() => {
|
||||||
|
this.currently_saving = false
|
||||||
|
this.unsaved_changes = false
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
selectPhotosForSlide(slide_index) {
|
||||||
|
this.selectedSlide = slide_index
|
||||||
|
this.max_photos_per_slide = 3
|
||||||
|
this.selectPhotosModal = true
|
||||||
|
},
|
||||||
|
selectPhotoForStartSlide() {
|
||||||
|
this.selectedSlide = 'start_slide'
|
||||||
|
this.max_photos_per_slide = 1
|
||||||
|
this.selectPhotosModal = true
|
||||||
|
},
|
||||||
|
getPhotoSrcById(photo_id){
|
||||||
|
for (const index in this.photos) {
|
||||||
|
let group = this.photos[index]
|
||||||
|
let photo = group.filter((photo) => photo.id == photo_id)
|
||||||
|
if (photo.length > 0) {
|
||||||
|
photo = photo[0]
|
||||||
|
return photo.cropped_image !== null ? photo.cropped_image : photo.original_image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
getPhotoTagById(tag_id) {
|
||||||
|
if (this.tags != null && this.tags.length != 0) {
|
||||||
|
let tag = this.tags.filter(tag => tag.id == tag_id)
|
||||||
|
if (tag.length > 0) {
|
||||||
|
return tag[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: tag_id,
|
||||||
|
name: '',
|
||||||
|
color: '#FFFFFF'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tag_style(tag_id) {
|
||||||
|
let tag = this.getPhotoTagById(tag_id)
|
||||||
|
let color = tag.color
|
||||||
|
return `
|
||||||
|
color: ${color};
|
||||||
|
border: 2px dashed ${color};
|
||||||
|
background: ${color}55;
|
||||||
|
`
|
||||||
|
},
|
||||||
|
getPhotosInSlide(slide_index) {
|
||||||
|
if (this.slides.length == 0 || this.slides[slide_index] === undefined) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let photos = this.slides[slide_index].filter(elm => elm.type == 'photo')
|
||||||
|
let photo_ids = []
|
||||||
|
for (const index in photos) {
|
||||||
|
photo_ids.push(photos[index].id)
|
||||||
|
}
|
||||||
|
return photo_ids
|
||||||
|
},
|
||||||
|
addPhotosToSlide(selected_photos) {
|
||||||
|
if (this.selectedSlide === 'start_slide') {
|
||||||
|
if (selected_photos.length > 0)
|
||||||
|
this.start_slide_image = selected_photos[0]
|
||||||
|
else
|
||||||
|
this.start_slide_image = null
|
||||||
|
this.max_photos_per_slide = 3
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let items_in_slide = this.slides[this.selectedSlide].filter(elm => elm.type == 'tag' || (elm.type == 'photo' && selected_photos.includes(elm.id)))
|
||||||
|
let old_image_ids = this.getPhotosInSlide(this.selectedSlide)
|
||||||
|
let new_image_ids = selected_photos.filter(id => !old_image_ids.includes(id))
|
||||||
|
for (const id of new_image_ids) {
|
||||||
|
items_in_slide.push({
|
||||||
|
type: 'photo',
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.slides[this.selectedSlide] = items_in_slide
|
||||||
|
},
|
||||||
|
selectPhotoTagForSlide(index) {
|
||||||
|
this.slide_for_adding_slot = index
|
||||||
|
this.showSelectTagModal = true
|
||||||
|
},
|
||||||
|
addSlotToSlide() {
|
||||||
|
this.slides[this.slide_for_adding_slot].push({
|
||||||
|
type: 'tag',
|
||||||
|
id: this.selected_tag_id
|
||||||
|
})
|
||||||
|
this.selected_tag_id = null
|
||||||
|
this.showSelectTagModal = false
|
||||||
|
},
|
||||||
|
removeTagFromSlide(slide_index, element_index) {
|
||||||
|
this.slides[slide_index].splice(element_index, 1)
|
||||||
|
},
|
||||||
|
addSlideAfter(index) {
|
||||||
|
this.slides.splice(index+1, 0, [])
|
||||||
|
},
|
||||||
|
removeSlide(index) {
|
||||||
|
this.slides.splice(index, 1)
|
||||||
|
},
|
||||||
|
navigateBack() {
|
||||||
|
this.$router.go(-1)
|
||||||
|
},
|
||||||
|
createNewPhotoLogTemplate() {
|
||||||
|
if (this.title.length > 0) {
|
||||||
|
this.date = new Date()
|
||||||
|
this.isCreateLoading = true
|
||||||
|
this.$store.dispatch('addNewPhotoLogTemplate', {title:this.title, date:this.dateStr}).then(id => {
|
||||||
|
if (id > -1 && id !== null) {
|
||||||
|
this.id = id
|
||||||
|
this.$router.push({name: 'CreateLogTemplate', params: {e: id}})
|
||||||
|
} else {
|
||||||
|
this.message.error('Something went wrong. Please try again later.')
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.isCreateLoading = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.message.error('Please enter a title')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isValidId() {
|
||||||
|
return !(
|
||||||
|
this.id == null
|
||||||
|
|| Number.isNaN(this.id)
|
||||||
|
|| this.id <= 0
|
||||||
|
|| this.id === undefined
|
||||||
|
)
|
||||||
|
},
|
||||||
|
findPhotoLogTemplate() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let found_template = this.$store.getters.photoLogTemplateById(this.id)
|
||||||
|
if (found_template === null) {
|
||||||
|
this.$store.dispatch('loadPhotoLogTemplateList').then(() => {
|
||||||
|
found_template = this.$store.getters.photoLogTemplateById(this.id)
|
||||||
|
if (found_template === null) {
|
||||||
|
this.message.error('Photo Log Template could not be found')
|
||||||
|
reject()
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
reject()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
updateLocalData() {
|
||||||
|
let found_template = this.$store.getters.photoLogTemplateById(this.id)
|
||||||
|
if (found_template !== null) {
|
||||||
|
this.title = found_template.title
|
||||||
|
this.date = new Date(found_template.date).getTime()
|
||||||
|
this.render_date = found_template.render_date
|
||||||
|
this.start_slide_image = found_template.start_slide_image
|
||||||
|
this.slides = found_template.slides
|
||||||
|
|
||||||
|
for(const idx in this.slides) {
|
||||||
|
this.slides[idx] = this.slides[idx].filter((s)=>s!==null)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoadingData = false
|
||||||
|
this.unsaved_changes = false
|
||||||
|
} else {
|
||||||
|
this.message.error('Photo Log Template could not be found')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateServerData() {
|
||||||
|
return new Promise((reject, resolve) => {
|
||||||
|
this.isSavingServer = true
|
||||||
|
|
||||||
|
let server_slides = [...this.slides]
|
||||||
|
/*
|
||||||
|
for (const idx in server_slides) {
|
||||||
|
server_slides[idx] = [
|
||||||
|
...server_slides[idx],
|
||||||
|
...new Array(
|
||||||
|
this.max_photos_per_slide-server_slides[idx].length)
|
||||||
|
.map(()=>null)]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
this.$store.dispatch('updatePhotoLogTemplate', {
|
||||||
|
id: this.id,
|
||||||
|
title: this.title,
|
||||||
|
date: this.dateStr,
|
||||||
|
render_date: this.render_date,
|
||||||
|
start_slide_image: this.start_slide_image,
|
||||||
|
slides: server_slides
|
||||||
|
}).then(() => {
|
||||||
|
this.message.success('Changes saved')
|
||||||
|
}).catch((error) => {
|
||||||
|
this.message.error('There was a problem saving the changes: '+error.message)
|
||||||
|
reject()
|
||||||
|
}).finally(() => {
|
||||||
|
this.isSavingServer = false
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
continueLeavePage() {
|
||||||
|
console.log(this.leaving_to_page)
|
||||||
|
this.$router.push(this.leaving_to_page)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeRouteUpdate (to) {
|
||||||
|
this.id = Number.parseInt(to.params.e)
|
||||||
|
if (this.isValidId()) {
|
||||||
|
this.isLoadingData = true
|
||||||
|
this.findPhotoLogTemplate().then(()=> {
|
||||||
|
this.updateLocalData()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
beforeRouteLeave (to, from) {
|
||||||
|
if (this.unsaved_changes && this.leaving_to_page === null) {
|
||||||
|
this.showPreventLeaveModal = true
|
||||||
|
this.leaving_to_page = to
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.n-input {
|
||||||
|
min-width: 25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-card .n-image {
|
||||||
|
margin-right: 2em;
|
||||||
|
border: 2px solid gray;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-slot {
|
||||||
|
cursor: move;
|
||||||
|
margin-right: 2em;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 100px;
|
||||||
|
height: 141px;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-slot p {
|
||||||
|
text-align: center;
|
||||||
|
transform: translate(0px, 55px) rotate(54.74deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-slide-between-button, .add-slide-between-button * {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-slide-between-button {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 1.5em;
|
||||||
|
background-color: #addeff;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 30%;
|
||||||
|
transition: all 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-slide-between-button:hover {
|
||||||
|
height: 5em;
|
||||||
|
opacity: 90%;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #addeff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-cards-enter-active,
|
||||||
|
.slide-cards-leave-active {
|
||||||
|
transition: all 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-cards-enter-from,
|
||||||
|
.slide-cards-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-cards-move {
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
}
|
||||||
|
</style>
|
@ -13,18 +13,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="home-menu">
|
<div class="home-menu">
|
||||||
<h1 class="text-3xl font-bold underline">
|
|
||||||
{{ $t('messages.hello') }}
|
|
||||||
</h1>
|
|
||||||
<n-space justify="space-around" >
|
<n-space justify="space-around" >
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
<h2>Documents</h2>
|
<h2 class="font-bold text-xl mb-4">Documents</h2>
|
||||||
<a target="_blank" href='https://dev.marczierle.com/zierle-training/generate_document/modify_document.php?selection=0'>
|
<a target="_blank" href='https://dev.marczierle.com/zierle-training/generate_document/modify_document.php?selection=0'>
|
||||||
<n-button type="primary">All Documents</n-button>
|
<n-button type="primary">All Documents</n-button>
|
||||||
</a>
|
</a>
|
||||||
</n-space>
|
</n-space>
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
<h2>Photo Logs</h2>
|
<h2 class="font-bold text-xl mb-4">Photo Logs</h2>
|
||||||
<n-space>
|
<n-space>
|
||||||
<router-link :to="{name: 'LogsList'}">
|
<router-link :to="{name: 'LogsList'}">
|
||||||
<n-button type="primary">All Logs</n-button>
|
<n-button type="primary">All Logs</n-button>
|
||||||
@ -33,7 +30,7 @@
|
|||||||
<n-button type="info">+ New</n-button>
|
<n-button type="info">+ New</n-button>
|
||||||
</router-link>
|
</router-link>
|
||||||
</n-space>
|
</n-space>
|
||||||
<n-space>
|
<n-space class="mt-4">
|
||||||
<router-link :to="{name: 'CameraCapture'}">
|
<router-link :to="{name: 'CameraCapture'}">
|
||||||
<n-button secondary circle type="info">
|
<n-button secondary circle type="info">
|
||||||
<n-icon><CameraAdd20Filled /></n-icon>
|
<n-icon><CameraAdd20Filled /></n-icon>
|
||||||
@ -45,8 +42,10 @@
|
|||||||
</n-space>
|
</n-space>
|
||||||
</n-space>
|
</n-space>
|
||||||
</n-space>
|
</n-space>
|
||||||
|
<!--
|
||||||
<n-button @click='set_lang("en")'>EN</n-button>
|
<n-button @click='set_lang("en")'>EN</n-button>
|
||||||
<n-button @click='set_lang("de")'>DE</n-button>
|
<n-button @click='set_lang("de")'>DE</n-button>
|
||||||
|
-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -96,6 +95,8 @@ export default {
|
|||||||
border-bottom-left-radius: 100% 30%;
|
border-bottom-left-radius: 100% 30%;
|
||||||
border-bottom-right-radius: 100% 30%;
|
border-bottom-right-radius: 100% 30%;
|
||||||
box-shadow: 0 0 50px rgba(0,0,0,0.25);
|
box-shadow: 0 0 50px rgba(0,0,0,0.25);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.banner-bg div {
|
.banner-bg div {
|
||||||
|
84
src/views/LogTemplatesList.vue
Normal file
84
src/views/LogTemplatesList.vue
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1 class="font-bold text-2xl m-4 my-6">Log Templates List</h1>
|
||||||
|
<n-table :bordered="false" :single-line="true">
|
||||||
|
<thead>
|
||||||
|
<th>Photo Log Template Title</th>
|
||||||
|
<th>Date created</th>
|
||||||
|
<th>delete</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="photologtemplate in photoLogTemplateList" :key="photologtemplate.id">
|
||||||
|
<router-link :to="{name: 'CreateLogTemplate', params: {e: photologtemplate.id}}">
|
||||||
|
<td>{{ photologtemplate.title }}</td>
|
||||||
|
</router-link>
|
||||||
|
<td>{{ photologtemplate.date }}</td>
|
||||||
|
<td>
|
||||||
|
<n-button type="error" @click="askDeleteLogTemplate(photologtemplate.id)">Delete</n-button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</n-table>
|
||||||
|
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showDeleteModal"
|
||||||
|
:mask-closable="true"
|
||||||
|
preset="dialog"
|
||||||
|
type="warning"
|
||||||
|
title="Delete Log Template"
|
||||||
|
:content=deleteModalContent
|
||||||
|
positive-text="Cancel"
|
||||||
|
negative-text="Delete"
|
||||||
|
@negative-click="deleteLogTemplate"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { useMeta } from 'vue-meta'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'LogTemplatesList',
|
||||||
|
setup() {
|
||||||
|
useMeta({ title: 'All Photo Log Templates' })
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showDeleteModal: false,
|
||||||
|
deleteId: null,
|
||||||
|
deleteModalContent: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$store.dispatch('loadPhotoLogTemplateList')
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
photoLogTemplateList() {
|
||||||
|
let list = this.$store.state.photoLogTemplateList
|
||||||
|
if (list !== null) {
|
||||||
|
list.sort((a,b) => {
|
||||||
|
return a.date < b.date ? 1 : -1
|
||||||
|
})
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
askDeleteLogTemplate(logTemplateId) {
|
||||||
|
this.showDeleteModal = true
|
||||||
|
this.deleteId = logTemplateId
|
||||||
|
let logtitle = this.photoLogTemplateList.filter((o) => {
|
||||||
|
return o.id == logTemplateId
|
||||||
|
})[0].title
|
||||||
|
this.deleteModalContent = 'Do you want to permanently delete "' + logtitle + '"?'
|
||||||
|
},
|
||||||
|
deleteLogTemplate() {
|
||||||
|
if (this.deleteId !== null) {
|
||||||
|
this.$store.dispatch('deletePhotoLogTemplate', this.deleteId)
|
||||||
|
this.deleteId = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h1>Logs List</h1>
|
<h1 class="font-bold text-2xl m-4 my-6">Logs List</h1>
|
||||||
<n-table :bordered="false" :single-line="true">
|
<n-table :bordered="false" :single-line="true">
|
||||||
<thead>
|
<thead>
|
||||||
<th>Photo Log Title</th>
|
<th>Photo Log Title</th>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h1>Manage Photos</h1>
|
<h1 class="font-bold text-2xl m-4 my-6">Manage Photos</h1>
|
||||||
<n-collapse
|
<n-collapse
|
||||||
v-if="photoGroups && photoGroups.length > 0"
|
v-if="photoGroups && photoGroups.length > 0"
|
||||||
@item-header-click="loadPhotosInGroup"
|
@item-header-click="loadPhotosInGroup"
|
||||||
@ -23,10 +23,12 @@
|
|||||||
:can_change_group="true"
|
:can_change_group="true"
|
||||||
:can_crop="true"
|
:can_crop="true"
|
||||||
:can_change_ocr="true"
|
:can_change_ocr="true"
|
||||||
|
:can_change_tag="true"
|
||||||
:can_delete="true"
|
:can_delete="true"
|
||||||
@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)"
|
||||||
|
@update:tag="change_tag_modal(photo.id)"
|
||||||
@update:delete="change_delete_modal(photo.id)"
|
@update:delete="change_delete_modal(photo.id)"
|
||||||
/>
|
/>
|
||||||
</n-space>
|
</n-space>
|
||||||
@ -129,6 +131,26 @@
|
|||||||
</template>
|
</template>
|
||||||
</n-card>
|
</n-card>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showChangeTagModal"
|
||||||
|
:mask-closable="true" >
|
||||||
|
<n-card
|
||||||
|
style="width: 400px;"
|
||||||
|
title="Change Photo Tag"
|
||||||
|
:bordered="false"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
>
|
||||||
|
<n-select v-model:value="current_tag_id" :options="tags_select_options" />
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify="end">
|
||||||
|
<n-button @click="showChangeTagModal=false">Cancel</n-button>
|
||||||
|
<n-button type="success" @click="changePhotoTag">Change Tag</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-card>
|
||||||
|
</n-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -157,10 +179,12 @@ export default {
|
|||||||
showChangeGroupModal: false,
|
showChangeGroupModal: false,
|
||||||
showCropModal: false,
|
showCropModal: false,
|
||||||
showChangeOCRModal: false,
|
showChangeOCRModal: false,
|
||||||
|
showChangeTagModal: false,
|
||||||
showDeleteModal: false,
|
showDeleteModal: false,
|
||||||
|
|
||||||
new_group: null,
|
new_group: null,
|
||||||
current_ocr_text: '',
|
current_ocr_text: '',
|
||||||
|
current_tag_id: 0,
|
||||||
current_photo_src: null,
|
current_photo_src: null,
|
||||||
|
|
||||||
is_group_loading: new Map(),
|
is_group_loading: new Map(),
|
||||||
@ -182,6 +206,13 @@ export default {
|
|||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
},
|
},
|
||||||
|
photoTags() {
|
||||||
|
let tags = this.$store.state.photoTags
|
||||||
|
if (tags !== null && tags.length > 0) {
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
},
|
||||||
photos() {
|
photos() {
|
||||||
return this.$store.state.photos
|
return this.$store.state.photos
|
||||||
},
|
},
|
||||||
@ -198,11 +229,27 @@ export default {
|
|||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
},
|
},
|
||||||
|
tags_select_options() {
|
||||||
|
if (this.photoTags.length > 0) {
|
||||||
|
let options = []
|
||||||
|
for (const idx in this.photoTags) {
|
||||||
|
options.push({
|
||||||
|
value: this.photoTags[idx].id,
|
||||||
|
label: this.photoTags[idx].name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$store.dispatch('loadPhotoGroups').catch((error) => {
|
this.$store.dispatch('loadPhotoGroups').catch((error) => {
|
||||||
this.message.error('Cannot load photo groups: ' + error)
|
this.message.error('Cannot load photo groups: ' + error)
|
||||||
})
|
})
|
||||||
|
this.$store.dispatch('loadPhotoTags').catch((error) => {
|
||||||
|
this.message.error('Cannot load photo tags: ' + error)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loadPhotosInGroup(group_data) {
|
loadPhotosInGroup(group_data) {
|
||||||
@ -242,6 +289,16 @@ export default {
|
|||||||
this.current_ocr_text = ''
|
this.current_ocr_text = ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
change_tag_modal(photo_id) {
|
||||||
|
this.current_photo = photo_id
|
||||||
|
this.showChangeTagModal = true
|
||||||
|
let current_tag = this.$store.getters.photoById(photo_id).tag
|
||||||
|
if (current_tag == null) {
|
||||||
|
this.current_tag_id = null
|
||||||
|
} else {
|
||||||
|
this.current_tag_id = current_tag.id
|
||||||
|
}
|
||||||
|
},
|
||||||
change_delete_modal(photo_id) {
|
change_delete_modal(photo_id) {
|
||||||
this.current_photo = photo_id
|
this.current_photo = photo_id
|
||||||
this.showDeleteModal = true
|
this.showDeleteModal = true
|
||||||
@ -258,6 +315,18 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
changePhotoTag(){
|
||||||
|
if (this.current_tag_id && this.current_photo) {
|
||||||
|
this.$store.dispatch('updatePhoto', {
|
||||||
|
id: this.current_photo,
|
||||||
|
tag: this.current_tag_id
|
||||||
|
}).then(()=>{
|
||||||
|
this.current_photo = null
|
||||||
|
this.current_tag_id = null
|
||||||
|
this.showChangeTagModal = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
changeCrop() {
|
changeCrop() {
|
||||||
let results = this.$refs.cropper.getResult()
|
let results = this.$refs.cropper.getResult()
|
||||||
let bbox = results.bbox
|
let bbox = results.bbox
|
||||||
|
@ -967,5 +967,7 @@ module.exports = {
|
|||||||
wordBreak: ['responsive'],
|
wordBreak: ['responsive'],
|
||||||
zIndex: ['responsive', 'focus-within', 'focus'],
|
zIndex: ['responsive', 'focus-within', 'focus'],
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [
|
||||||
|
require('tailwind-children'),
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user