UNPKG

@hognevevle/vue-avatar-cropper

Version:

A simple and elegant component to crop and upload avatars

341 lines (340 loc) 9.59 kB
import "cropperjs/dist/cropper.css"; import Cropper from "cropperjs"; import mime from "mime/lite"; import { openBlock, createElementBlock, normalizeClass, createElementVNode, createCommentVNode, withModifiers, toDisplayString } from "vue"; var vueAvatarCropper_vue_vue_type_style_index_0_lang = ""; var _export_sfc = (sfc, props) => { const target = sfc.__vccOpts || sfc; for (const [key, val] of props) { target[key] = val; } return target; }; const _sfc_main = { name: "AvatarCropper", model: { prop: "trigger", event: "triggered" }, props: { trigger: { type: Boolean, default: false }, file: { type: File }, uploadHandler: { type: Function }, uploadUrl: { type: String }, requestOptions: { type: Object, default() { return { method: "POST" }; } }, uploadFileField: { type: String, default: "file" }, uploadFileName: { type: [String, Function] }, uploadFormData: { type: FormData, default() { return new FormData(); } }, cropperOptions: { type: Object, default() { return { aspectRatio: 1, autoCropArea: 1, viewMode: 1, movable: false, zoomable: false }; } }, outputOptions: { type: Object }, outputMime: { type: String, default: null }, outputQuality: { type: Number, default: 0.9 }, mimes: { type: String, default: "image/png, image/gif, image/jpeg, image/bmp, image/x-icon" }, capture: { type: String }, labels: { type: Object, default() { return { submit: "Ok", cancel: "Cancel" }; } }, inline: { type: Boolean, default: false } }, data() { return { cropper: void 0, dataUrl: void 0, fileName: void 0, mimeType: void 0 }; }, computed: { cleanedMimes() { if (!this.mimes) throw new Error("vue-avatar-cropper: mimes prop cannot be empty"); return this.mimes.trim().toLowerCase(); } }, watch: { trigger(value) { if (!value) return; if (this.file) { this.onFileChange(this.file); } else { this.pickImage(); } this.$emit("triggered", false); } }, mounted() { this.$emit("triggered", false); }, methods: { destroy() { if (this.cropper) this.cropper.destroy(); if (this.$refs.input) this.$refs.input.value = ""; this.dataUrl = void 0; }, submit() { this.$emit("submit"); if (this.uploadUrl) { this.uploadImage(); } else if (this.uploadHandler) { this.uploadHandler(this.cropper); } else { this.$emit("error", { type: "user", message: "No upload handler found" }); } this.destroy(); }, cancel() { this.$emit("cancel"); this.destroy(); }, onImgElementError() { this.$emit("error", { type: "load", message: "File loading failed" }); this.destroy(); }, pickImage() { if (this.$refs.input) this.$refs.input.click(); }, onFileChange(file) { if (this.cleanedMimes === "image/*") { if (file.type.split("/")[0] !== "image") { this.$emit("error", { type: "user", message: "File type not correct" }); return; } } else if (this.cleanedMimes) { const correctType = this.cleanedMimes.split(", ").find((mime2) => mime2 === file.type); if (!correctType) { this.$emit("error", { type: "user", message: "File type not correct" }); return; } } const reader = new FileReader(); reader.onload = (e) => { this.dataUrl = e.target.result; }; reader.readAsDataURL(file); this.fileName = file.name || "unknown"; this.mimeType = file.type; this.$emit("changed", { file, reader }); }, onFileInputChange(e) { if (!e.target.files || !e.target.files[0]) return; this.onFileChange(e.target.files[0]); }, createCropper() { this.cropper = new Cropper(this.$refs.img, this.cropperOptions); }, getFilename(blob) { const extension = mime.getExtension(blob.type); if (!this.uploadFileName) { let actualFilename = this.fileName; const filenameParts = this.fileName.split("."); if (filenameParts.length > 1) actualFilename = filenameParts.slice(0, -1).join("."); return `${actualFilename}.${extension}`; } if (typeof this.uploadFileName === "string") return this.uploadFileName; if (typeof this.uploadFileName === "function") return this.uploadFileName({ filename: this.fileName, mime: blob.type, extension }); return `unknown.${extension}`; }, uploadImage() { this.cropper.getCroppedCanvas(this.outputOptions).toBlob(async (blob) => { const form = new FormData(); for (const [key, value] in this.uploadFormData.entries()) { form.append(key, value); } form.append(this.uploadFileField, blob, this.getFilename(blob)); const requestOptions = Object.assign({ body: form }, this.requestOptions); const request = new Request(this.uploadUrl, requestOptions); const reqPromise = fetch(request); this.$emit("uploading", { form, request, response: reqPromise }); const response = await reqPromise; this.$emit("completed", { form, request, response }); if (response.ok) { this.$emit("uploaded", { form, request, response }); } else { this.$emit("error", { type: "upload", message: "Image upload fail", context: { request, response } }); } }, this.outputMime || this.mimeType, this.outputQuality); } } }; const _hoisted_1 = { class: "avatar-cropper" }; const _hoisted_2 = { key: 0, class: "avatar-cropper-mark" }; const _hoisted_3 = ["title"]; const _hoisted_4 = { class: "avatar-cropper-container" }; const _hoisted_5 = { class: "avatar-cropper-image-container" }; const _hoisted_6 = ["src"]; const _hoisted_7 = { class: "avatar-cropper-footer" }; const _hoisted_8 = ["accept", "capture"]; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { return openBlock(), createElementBlock("div", _hoisted_1, [ $data.dataUrl ? (openBlock(), createElementBlock("div", { key: 0, class: normalizeClass(["avatar-cropper-overlay", { "avatar-cropper-overlay-inline": $props.inline }]) }, [ !$props.inline ? (openBlock(), createElementBlock("div", _hoisted_2, [ createElementVNode("a", { onClick: _cache[0] || (_cache[0] = (...args) => $options.cancel && $options.cancel(...args)), class: "avatar-cropper-close", title: $props.labels.cancel, href: "javascript:;" }, "\xD7", 8, _hoisted_3) ])) : createCommentVNode("", true), createElementVNode("div", _hoisted_4, [ createElementVNode("div", _hoisted_5, [ createElementVNode("img", { ref: "img", src: $data.dataUrl, alt: "", onLoad: _cache[1] || (_cache[1] = withModifiers((...args) => $options.createCropper && $options.createCropper(...args), ["stop"])), onError: _cache[2] || (_cache[2] = (...args) => $options.onImgElementError && $options.onImgElementError(...args)) }, null, 40, _hoisted_6) ]), createElementVNode("div", _hoisted_7, [ createElementVNode("button", { onClick: _cache[3] || (_cache[3] = withModifiers((...args) => $options.cancel && $options.cancel(...args), ["stop", "prevent"])), class: "avatar-cropper-btn" }, toDisplayString($props.labels.cancel), 1), createElementVNode("button", { onClick: _cache[4] || (_cache[4] = withModifiers((...args) => $options.submit && $options.submit(...args), ["stop", "prevent"])), class: "avatar-cropper-btn" }, toDisplayString($props.labels.submit), 1) ]) ]) ], 2)) : createCommentVNode("", true), !$props.file ? (openBlock(), createElementBlock("input", { key: 1, accept: $options.cleanedMimes, capture: $props.capture, class: "avatar-cropper-img-input", ref: "input", type: "file", onChange: _cache[5] || (_cache[5] = (...args) => $options.onFileInputChange && $options.onFileInputChange(...args)) }, null, 40, _hoisted_8)) : createCommentVNode("", true) ]); } var component = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]); const install = (Vue) => { if (install.installed) return; install.installed = true; Vue.component(component.name, component); }; let GlobalVue = null; if (typeof window !== "undefined") { GlobalVue = window.Vue; } else if (typeof global !== "undefined") { GlobalVue = global.Vue; } if (GlobalVue) { GlobalVue.use({ install }); } export { component as default };