mirror of
				https://github.com/MarcZierle/photo-log-frontend.git
				synced 2025-11-04 02:54:58 +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