Skip to content

Commit

Permalink
Add webcam upload to part file management
Browse files Browse the repository at this point in the history
  • Loading branch information
rhaamo committed Dec 10, 2022
1 parent e5deb2d commit 96bd9b4
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 7 deletions.
152 changes: 152 additions & 0 deletions front/src/components/parts/CameraSnapshotter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<template>
<div class="camera">
<div class="wrapper">
<PvButton
label="Close"
@click.prevent="closeDialog()"
class="mb-2 p-button-danger"
/>
<PvButton
v-if="isPlaying"
label="Snap!"
@click.prevent="takePhoto()"
class="mb-2 ml-2"
/>
<PvButton
label="save"
@click.prevent="saveAndClose()"
class="mb-2 ml-2 p-button-success"
v-if="isPhotoTaken"
/>

<div class="grid">
<div class="col-6">
<div v-if="!isPlaying">
Please wait while the camera initialize...
</div>
<video id="camera" ref="camera" autoplay playsinline></video>
<canvas
id="photoTaken"
v-show="isPhotoTaken"
class="ml-2"
ref="canvas"
>
</canvas>
</div>

<div class="col-6">
<img class="vertical-align-middle" id="photo" ref="photo" />
</div>
</div>
</div>
</div>
</template>

<script>
export default {
inject: ["dialogRef"],
data: () => ({
isPhotoTaken: false,
isPlaying: false,
width: 320,
height: 0,
}),
setup: () => ({}),
computed: {},
created() {
this.createCameraElement();
},
methods: {
closeDialog() {
this.stopCameraStream();
this.dialogRef.close();
},
saveAndClose() {
this.stopCameraStream();
this.dialogRef.close({
picture: document.getElementById("photoTaken").toDataURL("image/jpeg"),
});
},
createCameraElement() {
let constraints = (window.constraints = {
audio: false,
video: true,
});
navigator.mediaDevices
.getUserMedia(constraints)
.then((stream) => {
this.$refs.camera.srcObject = stream;
this.$refs.camera.play();
this.height =
this.$refs.camera.videoHeight /
(this.$refs.camera.videoWidth / this.width);
if (isNaN(this.height)) {
this.height = this.width / (4 / 3);
}
this.$refs.camera.setAttribute("width", this.width);
this.$refs.camera.setAttribute("height", this.height);
this.$refs.canvas.setAttribute("width", this.width);
this.$refs.canvas.setAttribute("height", this.height);
this.isPlaying = true;
})
.catch((error) => {
this.dialogRef.close({ error: error });
});
},
stopCameraStream() {
const tracks = this.$refs.camera.srcObject.getTracks();
tracks.forEach((track) => {
track.stop();
});
this.isPlaying = false;
},
takePhoto() {
this.isPhotoTaken = !this.isPhotoTaken;
const context = this.$refs.canvas.getContext("2d");
this.$refs.canvas.width = this.$refs.camera.videoWidth;
this.$refs.canvas.height = this.$refs.camera.videoHeight;
context.translate(this.$refs.canvas.width, 0);
context.scale(-1, 1);
context.drawImage(this.$refs.camera, 0, 0);
this.$refs.photo.setAttribute(
"src",
this.$refs.canvas.toDataURL("image/jpeg")
);
},
downloadImage() {
const download = document.getElementById("downloadPhoto");
const canvas = document
.getElementById("photoTaken")
.toDataURL("image/jpeg")
.replace("image/jpeg", "image/octet-stream");
download.setAttribute("href", canvas);
},
},
};
</script>

<style scoped>
canvas#photoTaken {
display: none;
}
video#camera {
width: 340px;
}
img#photo {
width: 340px;
}
</style>
2 changes: 1 addition & 1 deletion front/src/components/parts/view.vue
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export default {
},
{
item: "Category",
value: this.part.category ? this.part.category.name : "",
value: this.part.category ? this.part.category.name : "Uncategorized",
},
{
item: "Internal part number",
Expand Down
14 changes: 14 additions & 0 deletions front/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ function baseName(path, extension = true) {
return base;
}

function 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, lastModified: new Date() });
}

export default {
baseName,
dataUrlToFile,
};
3 changes: 3 additions & 0 deletions front/src/views/parts/AddQuick.vue
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,9 @@ export default {
: null,
footprint: this.form.footprint,
};
if (datas.category === 0 || datas.category === "0") {
datas.category = null;
}
logger.default.info("submitting part", datas);
Expand Down
66 changes: 60 additions & 6 deletions front/src/views/parts/View.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@
type="button"
icon="fa fa-edit"
class="p-button-primary"
v-tooltip="'edit'"
v-tooltip.left="'edit'"
></PvButton>
</router-link>

<PvButton
type="button"
icon="fa fa-trash-o"
class="p-button-danger ml-2"
v-tooltip="'delete'"
v-tooltip.left="'delete'"
@click="deletePart($event, part)"
></PvButton>
</div>
Expand Down Expand Up @@ -211,7 +211,7 @@
@submit.prevent="addAttachment(!v$.$invalid)"
>
<div class="grid">
<div class="col-5">
<div class="col-6">
<InputText
ref="description"
inputId="description"
Expand Down Expand Up @@ -252,6 +252,7 @@
type="file"
v-model="formAddAttachment.file"
@change="attachmentFileChanged($event.target.files)"
v-if="!formAddAttachment.fromWebcam"
:class="{
'p-invalid':
v$.formAddAttachment.file.$invalid &&
Expand All @@ -270,10 +271,23 @@
>
{{ v$.formAddAttachment.file.required.$message }}
</small>
<div v-if="formAddAttachment.fromWebcam">
<PvButton
label="Clear webcam image"
@click.prevent="clearWebcamImage()"
/>
</div>
</div>
</div>

<div class="col-1">
<PvButton label="add" type="submit" />
<div class="grid">
<div class="col-6">
<PvButton
label="Take a picture"
class="p-button-info"
@click.prevent="takeAPicture()"
/>
<PvButton label="add" type="submit" class="ml-3" />
</div>
</div>
</form>
Expand Down Expand Up @@ -380,6 +394,7 @@ import { required, maxLength } from "@vuelidate/validators";
import { mapState } from "pinia";
import { usePreloadsStore } from "@/stores/preloads";
import { useServerStore } from "@/stores/server";
import CameraSnapshotter from "@/components/parts/CameraSnapshotter.vue";
export default {
props: {
Expand All @@ -391,6 +406,7 @@ export default {
formAddAttachment: {
description: null,
file: null,
fromWebcam: false,
},
}),
setup: () => ({
Expand Down Expand Up @@ -449,7 +465,7 @@ export default {
},
{
item: "Category",
value: this.part.category ? this.part.category.name : "",
value: this.part.category ? this.part.category.name : "Uncategorized",
},
{
item: "Internal part number",
Expand Down Expand Up @@ -612,6 +628,11 @@ export default {
logger.default.error("Error with part attachment deletion", err);
});
},
clearWebcamImage() {
this.formAddAttachment.file = null;
this.formAddAttachment.realFile = null;
this.formAddAttachment.fromWebcam = false;
},
setAttachmentAsDefault(partId, fileId) {
apiService
.partAttachmentSetDefault(partId, fileId)
Expand Down Expand Up @@ -668,6 +689,39 @@ export default {
},
});
},
takeAPicture() {
const takeApictureRef = this.$dialog.open(CameraSnapshotter, {
props: {
header: "Take a picture",
modal: true,
closable: false,
},
onClose: async (options) => {
const data = options.data;
if (data) {
if (data.error) {
this.toast.add({
severity: "error",
summary: "Camera",
detail: "An error occured, please try again later",
life: 5000,
});
logger.default.error(
"Error with getting part details",
data.error
);
} else {
this.formAddAttachment.fromWebcam = true;
this.formAddAttachment.file = "C:\\fakepath\\webcam.jpg";
this.formAddAttachment.realFile = utils.dataUrlToFile(
data.picture,
"webcam.jpg"
);
}
}
},
});
},
},
};
</script>

0 comments on commit 96bd9b4

Please sign in to comment.