import { CocoComponent } from "@js/coco"; import { getData } from "@helpers/alpine"; import { captureElementScreenshot } from "@helpers/screenshot"; import { isDark } from "@helpers/color"; import { withUndo } from "@js/base/mixins"; export default CocoComponent("appSlideEditor", (data) => { const initialData = { layout: data.layout, title: data.title, text1: data.text1, bgColor: data.bgColor, textColor: data.textColor, bgImage: { name: data.bgImage, data: data.bgImage, }, }; return { use: [withUndo()], ...initialData, saved: { ...initialData }, saving: false, ready: false, dragging: false, errors: [], thumbnailFile: null, get bgImagePicker() { return getData(this.$root.querySelector("[data-role='bg-image-picker']")); }, init() { this.$watch("errors", (errors) => { errors.forEach((error) => console.error(error.message)); // TODO display errors properly! }); this.$nextTick(() => { // Add property changes to the undo/redo history this._fields.forEach((name) => { this.$watch(name, (value, oldValue) => this.history.add(name, value, oldValue) ); }); this.ready = true; }); }, undo(name, value) { this[name] = value; }, redo(name, value) { this[name] = value; }, handleImageDrop(event) { this.dragging = false; if (this.bgImagePicker) { this.bgImagePicker.handleExternalDrop(event); } else { event.preventDefault(); } }, async save() { if (this.saving) return; this.clearErrors(); this.saving = true; if (this.$refs.thumbnail) { try { await this._generateThumbnail(); } catch (error) { this.thumbnailFile = null; message = error.message || "Error generating slide thumbnail"; this._handleSaveError(message, { error }); return; } } const form = this.$refs.form && this.$refs.form.querySelector("form"); if (form && form.dataset.submit !== "false") { form.requestSubmit(); } else { this.submitSuccess(); } }, submitSuccess() { this.history.clear(); this._fields.forEach((name) => (this.saved[name] = this[name])); this.saving = false; console.info("Slide changes saved"); this.$dispatch("slide:save-end", { success: true }); }, submitError($event) { message = "Error saving slide"; this._handleSaveError(message, { event: $event }); }, directUploadError($event) { $event.preventDefault(); this._handleSaveError($event.detail.error, { event: $event }); }, clearErrors() { this.errors.length = 0; }, syncFileInput(input, file) { const dataTransfer = new DataTransfer(); if (file && file instanceof File) { dataTransfer.items.add(file); } input.files = dataTransfer.files; }, get hasBgImage() { return !!(this.bgImage && this.bgImage.data); }, get bgColorHex() { return this.bgColor.replace("#", ""); }, get textColorHex() { return this.textColor.replace("#", ""); }, get isDarkBg() { return this.hasBgImage || this.bgColor ? isDark(this.bgColor) : false; }, get _fields() { return Object.keys(initialData); }, async _generateThumbnail() { this.thumbnailFile = await captureElementScreenshot( this.$refs.thumbnail.firstElementChild, "slide-thumbnail" ); console.info( "Slide thumbnail generated", `${this.thumbnailFile.size / 1000}KiB` ); }, _handleSaveError(message = "Error saving slide", context = {}) { this.errors.push({ message, context }); this.saving = false; this.$dispatch("slide:save-end", { success: false }); }, /* bindings */ slide: { [":style"]() { return { backgroundColor: this.bgColor, color: this.textColor, backgroundImage: this.hasBgImage ? `url('${this.bgImage.data}')` : "none", }; }, [":class"]() { return { "slide-bg-dark": this.isDarkBg, "slide-bg-light": !this.isDarkBg, }; }, }, input: { layout: { "x-model.fill": "layout" }, title: { "x-model.fill": "title" }, text1: { "x-model.fill": "text1" }, bgColor: { "x-model.fill": "bgColorHex" }, textColor: { "x-model.fill": "textColorHex" }, bgImage: { "x-effect": "syncFileInput($el, bgImage.file)" }, bgImagePurge: { ":checked": "!hasBgImage" }, thumbnailImage: { "x-effect": "syncFileInput($el, thumbnailFile)" }, }, }; });