Skip to content

Instantly share code, notes, and snippets.

@b4n92uid
Last active August 17, 2021 22:19
Show Gist options
  • Save b4n92uid/ff2eae85828388ce0da74d47261cda5b to your computer and use it in GitHub Desktop.
Save b4n92uid/ff2eae85828388ce0da74d47261cda5b to your computer and use it in GitHub Desktop.
[Vuetify] PictureField
<template>
<v-card
class="v-picture-field rounded-lg"
:loading="loading > 0"
outlined
:class="{
'--has-content': previewSrc !== null
}"
@click.stop="sourceDialog = true"
>
<input
ref="field"
type="file"
class="__input"
accept="image/*"
@click.stop
@change="onFileSelected"
/>
<div v-if="previewSrc">
<div class="py-4 rounded-lg">
<v-img
:src="previewSrc"
:aspect-ratio="16 / 9"
contain
@error="previewSrc = null"
>
</v-img>
</div>
<div class="d-flex justify-space-between">
<v-btn color="primary" text small @click.stop="choose">
<v-icon left small>mdi-camera</v-icon>
Choisir
</v-btn>
<v-btn color="red" text small @click.stop="clear">
<v-icon left small>mdi-delete</v-icon>
Supprimer
</v-btn>
</div>
</div>
<div v-else>
<v-responsive
:aspect-ratio="16 / 9"
class="d-flex justify-center align-center"
>
<div class="text-center pa-4 grey--text">
<v-icon color="grey" size="128">mdi-camera</v-icon>
<div>Sélectionner ou prenez une photo</div>
</div>
</v-responsive>
</div>
<v-bottom-sheet v-model="sourceDialog" inset>
<v-sheet tile>
<v-list>
<v-list-item @click.stop="fromCamera">
<v-list-item-icon>
<v-icon>mdi-camera</v-icon>
</v-list-item-icon>
<v-list-item-title>Prendre une photo</v-list-item-title>
</v-list-item>
<v-list-item @click.stop="fromDevice">
<v-list-item-icon>
<v-icon>mdi-folder-open</v-icon>
</v-list-item-icon>
<v-list-item-title>Choisir depuis l'appareil</v-list-item-title>
</v-list-item>
</v-list>
</v-sheet>
</v-bottom-sheet>
</v-card>
</template>
<script>
import imageCompression from "browser-image-compression";
import { Plugins, CameraResultType, CameraSource } from "@capacitor/core";
export default {
props: {
src: {
type: String,
default: null
}
},
data() {
return {
loading: 0,
sourceDialog: false,
previewSrc: this.src
};
},
watch: {
src(v) {
this.previewSrc = v;
}
},
methods: {
isImageSizeValid(src, { width, height }) {
return new Promise(resolve => {
const loader = new Image();
loader.onload = () =>
resolve(loader.width <= width && loader.height <= height);
loader.src = src;
});
},
async readAsDataURL(f) {
return new Promise(resolve => {
const reader = new FileReader();
reader.addEventListener("load", () => resolve(reader.result), false);
reader.readAsDataURL(f);
});
},
async onFileSelected() {
if (!this.$refs.field.files.length) return;
let [file] = this.$refs.field.files;
this.loading++;
file = await imageCompression(file, {
maxSizeMB: 1
});
this.previewSrc = await this.readAsDataURL(file);
this.$emit("change", file);
this.$refs.field.files.value = "";
this.loading--;
},
async fromCamera() {
this.sourceDialog = false;
const { Camera } = Plugins;
try {
const image = await Camera.getPhoto({
quality: 80,
width: 1024,
resultType: CameraResultType.Uri,
source: CameraSource.Camera
});
this.loading++;
this.previewSrc = image.webPath;
const blob = await fetch(image.webPath).then(r => r.blob());
const file = new File([blob], "camera.jpeg");
this.$emit("change", file);
} catch (error) {
// TODO: Handle error
}
this.loading--;
},
fromDevice() {
this.sourceDialog = false;
this.$nextTick(() => this.$refs.field.click());
},
async choose() {
if (process.env.VUE_APP_IS_CAPACITOR) this.sourceDialog = true;
else this.fromDevice();
},
clear() {
this.previewSrc = null;
this.$refs.field.files.value = "";
this.$emit("change", null);
}
}
};
</script>
<style lang="scss">
.v-picture-field.v-card {
> .__input {
display: none;
}
border: thin dashed rgba(0, 0, 0, 0.12);
&.--has-content {
border: thin solid rgba(0, 0, 0, 0.12);
}
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment