mirror of
https://github.com/MarcZierle/photo-log-frontend.git
synced 2025-04-09 22:04:38 +00:00
358 lines
8.8 KiB
Vue
358 lines
8.8 KiB
Vue
<template>
|
|
<n-config-provider :theme="darkTheme">
|
|
<div id="all">
|
|
<div id="group_select">
|
|
<n-select v-model:value="selected_group" :options="groups" size="large" />
|
|
<n-button secondary type="primary" @click="showAddGroupModal = true" size="large">
|
|
<n-icon><CreateNewFolderRound /></n-icon>
|
|
</n-button>
|
|
</div>
|
|
|
|
<div id="camera">
|
|
<div id="prompt_select" v-show="!selected_group">
|
|
<n-icon size="4em"><HandPointUp/></n-icon><br/>
|
|
<h1 class="text-3xl font-bold">Please select a photo group</h1>
|
|
<p class="text-xl font-bold">(or create a new one)</p>
|
|
</div>
|
|
<n-spin :show="isCameraLoading || isPhotoTaken" v-show="selected_group">
|
|
<div id="camera_preview">
|
|
<video ref="camera" autoplay></video>
|
|
<canvas v-show="false" id="photoTaken" ref="canvas" width="100" height="100"></canvas>
|
|
</div>
|
|
</n-spin>
|
|
</div>
|
|
|
|
<div id="take_photo">
|
|
<n-space justify-content="center">
|
|
<n-button
|
|
type="primary" round size="large"
|
|
:disabled="!selected_group || isPhotoTaken || !isCameraCreated"
|
|
@click="takePhoto"
|
|
>
|
|
Take Photo
|
|
</n-button>
|
|
</n-space>
|
|
</div>
|
|
</div>
|
|
|
|
<n-modal
|
|
v-model:show="showAddGroupModal"
|
|
:mask-closable="true"
|
|
transform-origin="center"
|
|
preset="card"
|
|
title="Add new Group"
|
|
:bordered="false"
|
|
size="huge">
|
|
|
|
<n-input placeholder="Enter group name" size="large" v-model:value="new_group_name" />
|
|
|
|
<template #footer>
|
|
<n-space justify-content="end">
|
|
<n-button size="large" @click="showAddGroupModal = false">Cancel</n-button>
|
|
<n-button type="primary" size="large" @click="addNewGroup">Add Group</n-button>
|
|
</n-space>
|
|
</template>
|
|
</n-modal>
|
|
|
|
<n-modal
|
|
v-model:show="showPhotoTakenModal"
|
|
:mask-closable="false"
|
|
transform-origin="center"
|
|
preset="card"
|
|
:title="'Upload Photo to '+selected_group_label"
|
|
:bordered="false"
|
|
size="huge">
|
|
|
|
<n-space justify-content="center">
|
|
<n-image
|
|
:src="photo.src"
|
|
:width="photo.width*0.75"
|
|
/>
|
|
</n-space>
|
|
|
|
<template #footer>
|
|
<n-space justify-content="end">
|
|
<n-button size="large" @click="showPhotoTakenModal = false; isPhotoTaken = false">Back</n-button>
|
|
<n-button type="primary" size="large" @click="uploadPhoto" :loading="isUploading">Upload</n-button>
|
|
</n-space>
|
|
</template>
|
|
</n-modal>
|
|
</n-config-provider>
|
|
</template>
|
|
|
|
<script>
|
|
import { useMeta } from 'vue-meta'
|
|
import { useMessage } from 'naive-ui'
|
|
import {darkTheme} from 'naive-ui'
|
|
|
|
import CreateNewFolderRound from '@vicons/material/CreateNewFolderRound'
|
|
import HandPointUp from '@vicons/fa/HandPointUp'
|
|
|
|
import { getPhotoGroups, addNewPhotoGroup, addNewPhoto, cropPhoto } from '@/api'
|
|
|
|
export default {
|
|
components: {
|
|
CreateNewFolderRound,
|
|
HandPointUp,
|
|
},
|
|
setup() {
|
|
useMeta({
|
|
title: 'Capture new Photo',
|
|
})
|
|
const message = useMessage()
|
|
return { message }
|
|
},
|
|
data() {
|
|
return {
|
|
darkTheme,
|
|
|
|
groups: [],
|
|
selected_group: null,
|
|
|
|
showAddGroupModal: false,
|
|
new_group_name: '',
|
|
|
|
isCameraCreated: false,
|
|
isCameraLoading: true,
|
|
isPhotoTaken: false,
|
|
photo: {
|
|
width: 0,
|
|
height: 0,
|
|
src: null
|
|
},
|
|
stream: {
|
|
width: 0,
|
|
height: 0,
|
|
},
|
|
|
|
showPhotoTakenModal: false,
|
|
isUploading: false,
|
|
}
|
|
},
|
|
beforeMount() {
|
|
this.fetchPhotoGroups()
|
|
},
|
|
mounted() {
|
|
window.scrollTo(0, 1)
|
|
},
|
|
watch: {
|
|
selected_group: function () {
|
|
if (this.isCameraCreated)
|
|
return
|
|
this.startCamera()
|
|
},
|
|
},
|
|
computed: {
|
|
selected_group_label() {
|
|
if (this.selected_group !== null && this.groups.length > 0) {
|
|
return this.groups.filter(group => group.value === this.selected_group)[0].label
|
|
}
|
|
return ''
|
|
},
|
|
},
|
|
methods: {
|
|
fetchPhotoGroups() {
|
|
this.groups = []
|
|
getPhotoGroups().then((response) => {
|
|
let data = response.data
|
|
data = this.sortPhotoGroupsByDate(data)
|
|
for(const data_idx in data) {
|
|
this.groups.push({
|
|
label: data[data_idx].name,
|
|
value: data[data_idx].id,
|
|
})
|
|
}
|
|
}).catch((error)=>{
|
|
this.message.error('There was an error while fetching photo groups: '+error)
|
|
})
|
|
},
|
|
sortPhotoGroupsByDate(groups) {
|
|
if (groups !== null && groups.length > 0) {
|
|
groups = [...groups].sort((a,b) => {
|
|
if (a.date === null) return 1
|
|
if (b.date === null) return -1
|
|
return a.date > b.date ? -1 : 1
|
|
})
|
|
// move group with id 1 ("unsorted"-group) to front
|
|
let index = groups.indexOf(groups.filter(g=>g.id==1)[0])
|
|
groups.unshift(groups.splice(index, 1)[0])
|
|
return groups
|
|
}
|
|
return []
|
|
},
|
|
addNewGroup() {
|
|
if (this.new_group_name.length == 0) {
|
|
this.message.error('Please enter a new group name')
|
|
return
|
|
}
|
|
|
|
let today = new Date()
|
|
let dd = String(today.getDate()).padStart(2, '0')
|
|
let mm = String(today.getMonth() + 1).padStart(2, '0')
|
|
let yyyy = today.getFullYear()
|
|
today = yyyy + '-' + mm + '-' + dd
|
|
|
|
addNewPhotoGroup(this.new_group_name, today).then((response) => {
|
|
this.message.success('Added group '+this.new_group_name)
|
|
this.fetchPhotoGroups()
|
|
this.showAddGroupModal = false
|
|
this.selected_group = response.data.id
|
|
}).catch((error)=>{
|
|
this.message.error('There was an error while adding the group: '+error)
|
|
}).finally(() => {
|
|
this.new_group_name = ''
|
|
})
|
|
},
|
|
startCamera() {
|
|
this.isCameraLoading = true
|
|
|
|
const constraints = (window.constraints = {
|
|
audio: false,
|
|
video: {
|
|
facingMode: 'environment',
|
|
width: { ideal: 4096 },
|
|
height: { ideal: 2160 },
|
|
}
|
|
})
|
|
|
|
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
|
|
this.isCameraLoading = false
|
|
this.$refs.camera.srcObject = stream
|
|
this.isCameraCreated = true
|
|
|
|
let stream_settings = stream.getVideoTracks()[0].getSettings()
|
|
this.stream.width = stream_settings.width
|
|
this.stream.height = stream_settings.height
|
|
|
|
this.photo.width = document.getElementById('camera_preview').offsetWidth
|
|
this.photo.height = document.getElementById('camera_preview').offsetHeight
|
|
|
|
if (this.stream.width > this.stream.height) {
|
|
this.$refs.camera.width = this.photo.width
|
|
} else {
|
|
let ratio = this.photo.height / this.stream.height
|
|
this.$refs.camera.width = this.stream.width * ratio
|
|
}
|
|
}).catch(error => {
|
|
this.message.error('Cannot load device camera! '+error)
|
|
})
|
|
},
|
|
takePhoto() {
|
|
this.isPhotoTaken = true
|
|
|
|
setTimeout(() => {
|
|
this.photo.height = document.getElementById('camera_preview').offsetHeight
|
|
|
|
this.$refs.canvas.width = this.stream.width
|
|
this.$refs.canvas.height = this.stream.height
|
|
|
|
const context = this.$refs.canvas.getContext('2d')
|
|
context.drawImage(
|
|
this.$refs.camera,
|
|
0, 0,
|
|
this.stream.width,
|
|
this.stream.height)
|
|
|
|
this.photo.src = this.$refs.canvas.toDataURL('image/png')//.replace('image/png', 'image/octet-stream')
|
|
|
|
this.showPhotoTakenModal = true
|
|
}, 50)
|
|
},
|
|
uploadPhoto() {
|
|
this.isUploading = true
|
|
const dataURLtoFile = (dataurl, filename) => {
|
|
const arr = dataurl.split(',')
|
|
const mime = arr[0].match(/:(.*?);/)[1]
|
|
const bstr = atob(arr[1])
|
|
let n = bstr.length
|
|
const u8arr = new Uint8Array(n)
|
|
while (n) {
|
|
u8arr[n - 1] = bstr.charCodeAt(n - 1)
|
|
n -= 1 // to make eslint happy
|
|
}
|
|
return new File([u8arr], filename, { type: mime })
|
|
}
|
|
|
|
const file = dataURLtoFile(this.photo.src, 'upload.png')
|
|
|
|
addNewPhoto(file, null, this.selected_group).then((response) => {
|
|
this.message.success('Done!')
|
|
this.isPhotoTaken = false
|
|
this.showPhotoTakenModal = false
|
|
this.isUploading = false
|
|
|
|
setTimeout(() => {
|
|
cropPhoto(response.data.id, 'auto').then(()=>{
|
|
console.log('photo has been cropped')
|
|
}).catch((error)=>{
|
|
console.error(error)
|
|
})
|
|
}, 100)
|
|
}).catch((error) => {
|
|
this.message.error('There was an error uploading the photo: ' + error)
|
|
})
|
|
},
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
html, body {
|
|
margin: 0;
|
|
padding: 0;
|
|
overflow: hidden;
|
|
height: 100vh;
|
|
}
|
|
|
|
* {
|
|
overflow: hidden;
|
|
}
|
|
|
|
#all {
|
|
background-color: #111;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-wrap: nowrap;
|
|
overflow: hidden;
|
|
color: white;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
#camera_preview {
|
|
height: 75vh;
|
|
width: 100%;
|
|
}
|
|
|
|
#group_select {
|
|
display: flex;
|
|
height: 7.5vh;
|
|
}
|
|
|
|
#prompt_select {
|
|
display: flex;
|
|
flex-direction: column;
|
|
text-align: center;
|
|
margin-top: 25vh;
|
|
}
|
|
|
|
#prompt_select .n-icon {
|
|
margin: auto;
|
|
}
|
|
|
|
#take_photo {
|
|
height: 15vh;
|
|
background-color: #222;
|
|
display: flex;
|
|
justify-content: center;
|
|
bottom: 0;
|
|
position: fixed;
|
|
width: 100%;
|
|
}
|
|
|
|
#take_photo .n-button {
|
|
margin-top: 5vh;
|
|
}
|
|
</style> |