@hognevevle/vue-avatar-cropper
Version:
A simple and elegant component to crop and upload avatars
341 lines (340 loc) • 9.59 kB
JavaScript
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 };