Created
August 11, 2020 13:06
-
-
Save sambarrowclough/27f9321ae4fb6b521268d00cf53db29e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import tippy, { createSingleton } from "tippy.js"; | |
| import "tippy.js/themes/light-border.css"; | |
| import "tippy.js/dist/tippy.css"; | |
| import "@babel/polyfill"; | |
| import Swal from "sweetalert2/dist/sweetalert2.js"; | |
| import "sweetalert2/src/sweetalert2.scss"; | |
| import "loaders.css" | |
| // Local | |
| import * as _ from "./utils"; | |
| import "./Editor.scss"; | |
| import "./libs/pickr/src/scss/themes/nano.scss"; | |
| import Pickr from "./libs/pickr"; | |
| import Croppie from "./libs/Croppie/croppie"; | |
| import "./libs/Croppie/croppie.css"; | |
| import Spinner from "./Spinner"; | |
| import Logger from "./Logger"; | |
| // import data from "./data.json"; | |
| let isDev = __ENV__ === "dev"; | |
| window.log = { | |
| debug: () => {}, | |
| error: () => {}, | |
| warn: () => {}, | |
| info: () => {} | |
| } | |
| class Editor { | |
| static _croppie = Croppie; | |
| static _utils = _; | |
| static _pickr = Pickr; | |
| static _tippy = tippy; | |
| static EDITOR_ACTIONS = { | |
| DONE: "DONE", | |
| CANCEL: "CANCEL", | |
| ZOOM: "ZOOM", | |
| SELECT: "SELECT", | |
| REPLACE: "REPLACE", | |
| SETTINGS_DURATION:"SETTINGS_DURATION", | |
| SETTINGS_START: "SETTINGS_START", | |
| SETTINGS_RANDOM: "SETTINGS_RANDOM", | |
| SETTINGS_ARROWS: "SETTINGS_ARROWS", | |
| SETTINGS_HOVER_PAUSE: "SETTINGS_HOVER_PAUSE", | |
| SETTINGS_NUMBERED: "SETTINGS_NUMBERED", | |
| SETTINGS_EFFECT: "SETTINGS_EFFECT", | |
| SETTINGS_VISIBLE: "SETTINGS_VISIBLE", | |
| SETTINGS: "SETTINGS", | |
| URL: "URL", | |
| CAPTION: "CAPTION", | |
| PREVIEW: "PREVIEW", | |
| DELETE: "DELETE", | |
| ADD: "ADD", | |
| } | |
| // Evenlistener name: [callbacks] | |
| _eventListener = { | |
| // Actions | |
| init: [], | |
| move: [], | |
| edit: [], | |
| add: [], | |
| delete: [], | |
| select: [], | |
| publish: [], | |
| settings:[], | |
| replace:[], | |
| url:[], | |
| caption:[], | |
| preview:[], | |
| back: [], | |
| done:[], | |
| cancel:[], | |
| // Logs | |
| error: [], | |
| debug: [], | |
| warn: [] | |
| }; | |
| static DEFAULT_OPTIONS = { | |
| boundaryHeight: 500, | |
| boundaryWidth: 1200, | |
| animSpeed: 0, | |
| effect: 8, | |
| nav: true, | |
| nav2: false, | |
| pauseOnHover: true, | |
| pauseTime: 3000, | |
| next: "Next", | |
| previous: "Prev", | |
| randomStart: true, | |
| startSlide: 0, | |
| visible: true, | |
| forceManual: false, | |
| slides: [ | |
| { | |
| text: null, | |
| imageUrl: "https://d13z1xw8270sfc.cloudfront.net/origin/477866/my-img-1595420917353.png", | |
| url: null, | |
| linkMode: null | |
| } | |
| ] | |
| } | |
| _slides = []; | |
| _slide = null; | |
| _slideLimit = 10; | |
| _logo = null; | |
| _sortables = []; | |
| _style = {}; | |
| pickr = null; | |
| _slideInitIndex = 0; | |
| constructor(opt) { | |
| log.debug("Begin initialisation"); | |
| log.debug("Resolving options"); | |
| this._resolveOptions(opt) | |
| .then(() => { | |
| log.debug("Resolved options"); | |
| log.debug("Binding components"); | |
| return this._bindComponents(); | |
| }) | |
| .then(() => { | |
| log.debug("Bound component"); | |
| this._emit("init", this); | |
| }) | |
| .catch((e) => { | |
| this._emit("error", e); | |
| }); | |
| } | |
| _resolveOptions(opt) { | |
| return new Promise((resolve, reject) => { | |
| this.namespace = "designeditor"; | |
| // Assign default values | |
| this.options = this.opt = Object.assign( | |
| {}, | |
| Editor.DEFAULT_OPTIONS, | |
| opt | |
| ); | |
| log.debug("Options:", this.options); | |
| this._root = { | |
| editorActions: { | |
| select: [], | |
| }, | |
| }; | |
| // Slideshow | |
| if (opt.slides?.length) { | |
| log.debug("Setting up slideshow controls"); | |
| this._slides = opt.slides | |
| } else { | |
| log.debug("No slides given in options, setting up with default image") | |
| this._slides = [{ imageUrl: "https://d13z1xw8270sfc.cloudfront.net/origin/477866/plain-white-background_1592919904955.jpg" }] | |
| } | |
| if (!opt?.boundaryHeight) { | |
| log.warn(`Using default boundary width ${Editor.DEFAULT_OPTIONS.boundaryHeight}`) | |
| } | |
| if (!opt?.boundaryWidth) { | |
| log.warn(`Using default boundary width ${Editor.DEFAULT_OPTIONS.boundaryWidth}`) | |
| } | |
| resolve(); | |
| }); | |
| } | |
| _flexLoaded(){ | |
| const url = "javascripts/flexslider/jquery.flexslider-min_v1.js" | |
| return document.body.innerHTML.search(url) > -1 | |
| } | |
| async _bindComponents() { | |
| const { flex, slideshowTarget } = this.options | |
| // TODO: resolveOptions should set the this.startImageUrl so we are not doing checks like this | |
| // Set default image as white-background | |
| if (this._slides && this._slides.length && this._activeSlide() && this._activeSlide().imageUrl) { | |
| const defaultImage = "https://d13z1xw8270sfc.cloudfront.net/origin/477866/plain-white-background_1592919904955.jpg"; | |
| const url = this._activeSlide().imageUrl | |
| var isImg = await _.isImg(url); | |
| if (!isImg) { | |
| this._activeSlide().imageUrl = defaultImage | |
| log.warn(`Image [${url}] is not an image. Binding default image to the editor.`) | |
| } | |
| } else { | |
| this._emit("warn", "Found no image in options, binding default image.") | |
| } | |
| if (flex) { | |
| flex.remove(); | |
| log.debug("Removed flex slider"); | |
| await this._buildImageEditorShell().catch(err => this._emit("error", err)); | |
| log.debug("Built image editor shell"); | |
| } else if (flex && !isImg) { | |
| log.error("Failed to build shideshow. No flex object and startImage is not an image."); | |
| this._emit("error", err) | |
| } else { | |
| await this._buildImageEditorShell().catch(err => this._emit("error", err)); | |
| log.debug("Built image editor shell"); | |
| } | |
| // Remove default spacing on buttons for editor | |
| var btns = document.querySelectorAll(".ql-toolbar button"); | |
| for (let index = 0; index < btns.length; index++) { | |
| const btn = btns[index]; | |
| if (btn instanceof HTMLElement) { | |
| btn.style.marginBottom = "0px"; | |
| } | |
| } | |
| // Styles | |
| if (this._style.enabled) { | |
| // Disable main style sheet | |
| document.querySelector("#design_css_master").disabled = true; | |
| this._buildStyles(); | |
| } | |
| // Spotlight | |
| slideshowTarget.style.zIndex = "1002"; | |
| const overlay = _.createFromTemplate(`<div :ref=overlay id="overlay"></div>`); | |
| _.inBefore(overlay.overlay, document.body); | |
| } | |
| // blob -> https://d13z1xw8270sfc.cloudfront.net/origin/{storeId}/{file} | |
| async url(blob, storeId) { | |
| var time = new Date().getTime(); | |
| if (/Edge/.test(navigator.userAgent) || /Trident/.test(navigator.userAgent)) { | |
| var file = new Blob([blob], { type: "image/png" }); | |
| file.name = "my-img-" + time + ".png"; | |
| } else { | |
| var file = new File([blob], "my-img-" + time + ".png", { | |
| type: "image/png", | |
| }); | |
| } | |
| const signed = await this.sign(storeId, file.name, file.type, "prodimg") | |
| return this.upload(file, signed.url, storeId); | |
| } | |
| async sign(shopkeeper, fileName, contentType, type = "prodimg") { | |
| var url = "https://2iiejpzs1a.execute-api.eu-west-1.amazonaws.com/v1/geturl"; | |
| var opts = { | |
| headers: { | |
| "content-type": "application/json", | |
| }, | |
| body: JSON.stringify({ | |
| shopkeeper: shopkeeper, | |
| fileName: fileName, | |
| contentType: contentType, | |
| type: type, | |
| }), | |
| method: "POST", | |
| }; | |
| return fetch(url, opts).then(function (res) { | |
| if (res.status === 200) { | |
| return res.json(); | |
| } else { | |
| throw new Error(res.statusText); | |
| } | |
| }); | |
| } | |
| async upload(file, url, storeId) { | |
| var opts = { | |
| headers: { | |
| "content-type": file.type, | |
| "x-amz-acl": "public-read", | |
| }, | |
| body: file, | |
| method: "PUT", | |
| }; | |
| return fetch(url, opts).then(function (res) { | |
| if (res.status === 200) { | |
| return ( | |
| "https://d13z1xw8270sfc.cloudfront.net/origin/" + | |
| storeId + | |
| "/" + | |
| file.name | |
| ); | |
| } else { | |
| throw new Error(res.statusText); | |
| } | |
| }); | |
| } | |
| async _skip(e) { | |
| log.debug("SKIP:START", this._activeSlide()); | |
| // Get the original image | |
| this._activeSlide().imageUrl = this._slide.data.url | |
| // Hide done, cancel actions | |
| this._hideCropActions() | |
| // Wait between 0.6s-1.0s | |
| await new Promise(r => setTimeout(r, Math.floor(Math.random()*300+200))) | |
| // Reset points | |
| if (this._slide.backup) { | |
| const { url } = this._slide.data | |
| const { points, zoom, orientation } = this._slide.backup | |
| await this._slide.bind({ url, points, orientation, zoom }) | |
| } | |
| // Show toolbar, publish settings tools | |
| this._showEditorActions() | |
| log.debug("SKIP:END", this._activeSlide()); | |
| } | |
| _preview(e){ | |
| log.debug("PREVIEW:START", $("#slider1")) | |
| const { captionInput, urlInput, preview } = this._root.editorActions | |
| if (preview.classList.contains("disabled")) return | |
| const slides = this._slides.map((x) => x.imageUrl).filter(Boolean) | |
| const editor = this._slide.elements.boundary; | |
| if (captionInput) { | |
| this._saveCaptionText() | |
| _.rm(captionInput) | |
| this._root.editorActions.captionInput = null | |
| } | |
| if (urlInput) { | |
| this._saveUrlText() | |
| _.rm(urlInput) | |
| this._root.editorActions.urlInput = null | |
| } | |
| if (slides.length === 0) { | |
| this._emit("error", new Error("PREVIEW_UNAVAILABLE: Slide list is empty. Please Add a slide to use the Preview")) | |
| return | |
| } | |
| if (!this._flexLoaded()) { | |
| this._emit("error", new Error("PREVIEW_UNAVAILABLE: flex.jquery dependency not loaded. Note: We only load this dependency if you have slides on initialisation")) | |
| return | |
| } | |
| const html = _.createFromTemplate(` | |
| <div :ref=flexslider class="slider-wrapper theme-default"> | |
| <div id="slider1" class="flexslider"> | |
| <div :ref=back class="btn-group settings slider-prev"> | |
| <button class="btn"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-corner-down-left"><polyline points="9 10 4 15 9 20"></polyline><path d="M20 4v7a4 4 0 0 1-4 4H4"></path></svg> | |
| </button> | |
| </div> | |
| <ul class="slides"> | |
| ${ this._slides && this._slides.length | |
| ? this._slides.map( | |
| (x) => ` | |
| <li> | |
| ${ x.url | |
| ? `<a target="_blank" href="${x.url}"> <img data-src="${x.imageUrl}" class="lazyload" /> ${ x.text ? `<p class="flex-caption">${x.text}</p>` : "" } </a>` | |
| : `<img data-src="${x.imageUrl}" class="lazyload" /> ${ x.text ? `<p class="flex-caption">${x.text}</p>` : "" }` | |
| } | |
| </li> | |
| ` | |
| ) | |
| : "" | |
| } | |
| </ul> | |
| </div> | |
| </div> | |
| `); | |
| // Bind flexslider for preview | |
| editor.insertAdjacentElement("beforebegin", html.flexslider); | |
| let animation = document.querySelector(".effect").value; | |
| if (animation === "8") { | |
| animation = "fade"; | |
| } else if (animation === "1") { | |
| animation = "slide"; | |
| } | |
| const opts = { | |
| useCSS: true, | |
| touch: true, | |
| animation: animation, | |
| animationSpeed: document.querySelector(".speed").value, | |
| slideshowSpeed: document.querySelector(".duration").value, | |
| // startAt: parseInt(document.querySelector(".start").value), | |
| directionNav: document.querySelector("#arrows").checked, | |
| prevText: "<", | |
| nextText: ">", | |
| pauseOnHover: document.querySelector("#pause").checked, | |
| randomize: document.querySelector("#random").checked, | |
| start: (e) => { | |
| window.flexslider = e; | |
| log.debug("START") | |
| }, | |
| }; | |
| $("#slider1").flexslider(opts); | |
| editor.style.display = "none"; | |
| // Preview events | |
| this._on(html.back, "click", (e) => { | |
| log.debug("BACK:START", this._slide) | |
| editor.style.display = "block"; | |
| $("#slider1").remove(); | |
| _.rm(html.flexslider) | |
| log.debug("BACK:END", this._slide) | |
| }); | |
| tippy(html.back, { | |
| content: "Back", | |
| delay: 350 | |
| }); | |
| // Send event | |
| log.debug("PREVIEW:END", $("#slider1")) | |
| } | |
| spinner = { | |
| start: () => { | |
| const { editorSpinner } = this._root.editorActions | |
| editorSpinner.style.opacity="1"; | |
| editorSpinner.style.zIndex="1000"; | |
| }, | |
| stop: () => { | |
| const { editorSpinner } = this._root.editorActions | |
| editorSpinner.style.opacity="0"; | |
| editorSpinner.style.zIndex="0"; | |
| } | |
| } | |
| async _done(e) { | |
| log.debug("DONE:START", this._activeSlide()) | |
| const { zoomerWrap, overlay, boundary } = this._slide.elements; | |
| const { _slideInitIndex, _slides } = this; | |
| const { editorActions } = this._root; | |
| const { storeId, publishPosition } = this.options; | |
| this._slide.elements.img.classList.add('sleep') | |
| this.spinner.start() | |
| zoomerWrap.style.zIndex="0"; | |
| // Save edited photo | |
| const blob = await this._slide.result({ type: "blob" }); | |
| const url = window.URL.createObjectURL(blob); | |
| _slides[_slideInitIndex].imageUrl = url; | |
| // Hide loader | |
| await new Promise(r => setTimeout(r, Math.floor(Math.random()*700+400))) | |
| zoomerWrap.style.zIndex="100"; | |
| this.spinner.stop() | |
| await new Promise(r => setTimeout(r, 50)) | |
| this._slide.elements.img.classList.remove('sleep') | |
| this._hideCropActions() | |
| this._showEditorActions() | |
| log.debug("DONE:END", this._activeSlide()) | |
| } | |
| async _replace(e){ | |
| log.debug("REPLACE:START", e) | |
| if (e.target.files?.length === 0) return; | |
| const { left, right, selectContainer, edit, rm, preview, URL, caption } = this._root.editorActions | |
| // Loader | |
| this._slide.elements.img.classList.add('sleep') | |
| this.spinner.start() | |
| // Bind replacing image to the Editor | |
| const file = e.target.files[0]; | |
| const url = window.URL.createObjectURL(file); | |
| // less than 1mb? wait a bit | |
| if (file.size < 1000000) { | |
| await new Promise(r => setTimeout(r, Math.floor(Math.random()*500+300))) | |
| } | |
| await this._slide.bind(url) | |
| // Enable editor actions | |
| if (this._isDefaultImage()) { | |
| [edit, preview, rm, URL, caption, left, right, selectContainer].forEach(x => x.classList.remove("disabled")) | |
| } | |
| // Crop image and save it | |
| const blob = await this._slide.result({ type: "blob" }); | |
| const img = window.URL.createObjectURL(blob); | |
| this._slides[this._slideInitIndex].imageUrl = img; | |
| // Hide loader | |
| this._slide.elements.img.classList.remove('sleep') | |
| this.spinner.stop() | |
| log.debug("REPLACE:END", this._activeSlide()) | |
| } | |
| async _publish(e) { | |
| log.debug("PUBLISH:START", this); | |
| const { storeId } = this.options | |
| const { publish, publishSpinner, publishText } = this._root.editorActions | |
| publish.classList.remove("active") | |
| publish.classList.add("active"); | |
| publishSpinner.style.display="block" | |
| publishText.style.opacity="0" | |
| try { | |
| // Get urls from blobs | |
| const { err, slides } = await (async () => { | |
| let slides = [] | |
| let err | |
| for(const slide of this._slides) { | |
| const { imageUrl } = slide | |
| // Ignore the default image | |
| if (imageUrl.indexOf('plain-white-background') > -1) { | |
| continue; | |
| } | |
| if (imageUrl && imageUrl.startsWith("blob")) { | |
| const blob = await fetch(imageUrl).then(r => r.blob()) | |
| const url = await this.url(blob, storeId).catch(err => { | |
| acc.err += err | |
| }) | |
| if (url) { | |
| slide.imageUrl = url | |
| } | |
| } | |
| slides.push(slide) | |
| } | |
| return { err, slides } | |
| })() | |
| if (err) { | |
| throw new Error(err) | |
| } | |
| const set = this._saveSettings() | |
| const cp = _.shallow(set) | |
| const parsed = _.parse(cp) | |
| const slide = _.assign(parsed, {slides}) | |
| // Save slides | |
| let request = { | |
| uri: "Editor/svc/Services.svc/UpdateSlideShow", | |
| method: "POST", | |
| body: _.str({ | |
| storeId: storeId, | |
| debugId: log._uuid, | |
| slide: _.str(slide) | |
| }), | |
| headers: { | |
| "content-type": "application/json", | |
| }, | |
| }; | |
| await fetch(request.uri, request).then((response) => | |
| handleResponse({ | |
| request, | |
| response | |
| })).then(() => { | |
| // Success! | |
| Swal.fire({ | |
| icon: "success", | |
| animation: false, | |
| title: "Your work has been saved", | |
| showConfirmButton: false, | |
| timer: 1500, | |
| }); | |
| }).catch((err) => { | |
| Swal.fire({ | |
| icon: "error", | |
| title: "Oops...", | |
| html: err, | |
| animation: false, | |
| confirmButtonColor: "#f054a7", | |
| }); | |
| log.error("Failed to save SlideShow. Request: " + _.str(request) + ". Error: " + err); | |
| }).then(async () => { | |
| // Stop spinner | |
| publish.classList.remove("active"); | |
| publishSpinner.style.display="none" | |
| publishText.style.opacity="1" | |
| log.debug("PUBLISH:END", this); | |
| }) | |
| } catch (err) { | |
| Swal.fire({ | |
| icon: "error", | |
| title: "Oops...", | |
| html: err, | |
| animation: false, | |
| confirmButtonColor: "#f054a7", | |
| }); | |
| log.error("Something went wrong prepparing the slideshow for save. Error:", err); | |
| publish.classList.remove("active"); | |
| publishSpinner.style.display="none" | |
| publishText.style.opacity="1" | |
| } | |
| } | |
| activeSlideIndex() { | |
| let { select } = this._root.editorActions; | |
| select = Array.from(select).filter(Boolean); | |
| const active = select.findIndex((x) => x.classList.contains("active")); | |
| return active; | |
| } | |
| async _buildImageEditorShell() { | |
| const { slideshowTarget } = this.options; | |
| const { _slideInitIndex, namespace } = this; | |
| const show = this.options?.visible | |
| const slideSettings = this.options; | |
| // Destory any previous croppie as we can only init one at a time | |
| if (this._slide) { | |
| this._slide.destroy(); | |
| this._slide = null; | |
| log.debug("Destoryed croppie instance"); | |
| } | |
| // Build slide editor | |
| // Default image | |
| // TODO: resolveOptions should set the this.startImageUrl so we are not doing checks like this | |
| let url = this._activeSlide().imageUrl | |
| if (!url) { | |
| url = "https://d13z1xw8270sfc.cloudfront.net/origin/477866/plain-white-background_1592919904955.jpg"; | |
| } | |
| var { offsetWidth } = slideshowTarget.parentElement; | |
| const { width, height } = await _.dimen(url); | |
| const aspect = _.aspect(width, height, offsetWidth, offsetWidth); | |
| const opt = { | |
| width: aspect.width, | |
| height: aspect.height, | |
| target: slideshowTarget, | |
| url, | |
| mouseWheelZoom: false, | |
| disabled: !show | |
| }; | |
| log.debug("Building croppie instance with options: ", opt); | |
| this._slide = await this._buildCroppieInstance(opt); | |
| log.debug("Built croppie instance with options: ", opt); | |
| const { zoomerWrap, overlay, boundary } = this._slide.elements; | |
| // Disable interaction | |
| zoomerWrap.style.opacity = "0"; | |
| overlay.style.pointerEvents = "none"; | |
| log.debug(`Building editor html`) | |
| // Paint shell actions over editor | |
| const html = _.createFromTemplate(` | |
| <div :ref=slideEditorShellContainer style="width:${ | |
| boundary.offsetWidthh | |
| }px; height:${ | |
| boundary.offsetHeight | |
| }px" class="${namespace}-slide-editor-shell-container"> | |
| <div class="${namespace}-editor-toolbar" :ref=container> | |
| <!-- SPINER --> | |
| <div :ref=editorSpinner style="top:50%;left:50%;z-index:0;" class="spinner"> | |
| <span style="border-right-color:black;"></span> | |
| </div> | |
| <!-- SPINER --> | |
| <!-- CROP ACTIONS --> | |
| <div style="top:2.25em; visibility:hidden; opacity:0;" :ref=cropActions class="btn-group shell-actions"> | |
| <button :ref=cancel class="btn"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg> | |
| </button> | |
| <button :ref=skip class="btn"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-skip-forward"><polygon points="5 4 15 12 5 20 5 4"></polygon><line x1="19" y1="5" x2="19" y2="19"></line></svg> | |
| </button> | |
| <button :ref=done class="btn"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg> | |
| </button> | |
| </div> | |
| <!-- CROP ACTIONS --> | |
| <div :ref=toolbarContainer class="btn-group shell-actions"> | |
| <!-- Add --> | |
| <button :ref=add class="btn"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg> | |
| </button> | |
| <input :ref=addInput type="file" class="shell-input" style="display:none;"> | |
| <!-- Add --> | |
| <div class="de-toolbar-divide"></div> | |
| <!-- Crop --> | |
| <button :ref=edit class="btn ${this._isDefaultImage() ? "disabled": ""}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-crop"><path d="M6.13 1L6 16a2 2 0 0 0 2 2h15"></path><path d="M1 6.13L16 6a2 2 0 0 1 2 2v15"></path></svg> | |
| </button> | |
| <!-- Crop --> | |
| <!-- Order slides --> | |
| <div :ref=moveContainer class="btn-group de-order-slides"> | |
| <!-- Left --> | |
| <div :ref=left class="btn ${this._isDefaultImage() || this._slides.length === 1 ? "disabled" : ""}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg> | |
| </div> | |
| <!-- Left --> | |
| <!-- Right --> | |
| <div :ref=right class="btn ${this._isDefaultImage() || this._slides.length === 1 ? "disabled" : ""}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg> | |
| </div> | |
| <!-- Right --> | |
| </div> | |
| <!-- Order slides --> | |
| <!-- URL --> | |
| <button :ref=URL class="btn ${this._isDefaultImage() ? "disabled": ""}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link-2"><path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path><line x1="8" y1="12" x2="16" y2="12"></line></svg> | |
| </button> | |
| <!-- URL --> | |
| <!-- Caption --> | |
| <button :ref=caption class="btn ${this._isDefaultImage() ? "disabled": ""}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-type"><polyline points="4 7 4 4 20 4 20 7"></polyline><line x1="9" y1="20" x2="15" y2="20"></line><line x1="12" y1="4" x2="12" y2="20"></line></svg> | |
| </button> | |
| <!-- Delete --> | |
| <button :ref=rm class="btn ${this._isDefaultImage() ? "disabled": ""}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-trash"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg> | |
| </button> | |
| <!-- Delete --> | |
| </div> | |
| <!-- shell actions --> | |
| <!-- Select Actions --> | |
| <div :ref=selectMoveContainer class="select-actions"> | |
| <!-- Select --> | |
| <div :ref=selectContainer class="btn-group select-container ${this._isDefaultImage() || this._slides.length === 1 ? "disabled" : ""}"> | |
| ${ | |
| this._slides && this._slides.length | |
| ? this._slides | |
| .map((slide) => | |
| `<div :arr=select class="select-item"></div>` | |
| ) | |
| .join("") | |
| : "" | |
| } | |
| </div> | |
| <!-- Select --> | |
| </div> | |
| <!-- Select Actions --> | |
| <!-- settings --> | |
| <div :ref=settingsPanel class='slideshow-settings-panel'> | |
| <div class='setting'> | |
| <div class='setting-header'> | |
| <svg :ref=visibleHelp cmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#aaa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg> | |
| <div class='setting-title'>Visible</div> | |
| </div> | |
| <div class='setting-action'> | |
| <input type="checkbox" :ref=visible id="visible" name="set-name" class="switch-input"> | |
| <label for="visible" class="switch-label"></label> | |
| </div> | |
| </div> | |
| <div class='divide'></div> | |
| <div class='setting'> | |
| <div class='setting-header'> | |
| <svg :ref=effectHelp cmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#aaa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg> | |
| <div class='setting-title'>Effect</div> | |
| </div> | |
| <div class='setting-action'> | |
| <select :ref=effect class="setting-select effect"> | |
| <option value="1">Slide</option> | |
| <option value="8">Fade</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class='setting'> | |
| <div class='setting-header'> | |
| <svg :ref=speedHelp cmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#aaa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg> | |
| <div class='setting-title'>Speed</div> | |
| </div> | |
| <div class='setting-action'> | |
| <select :ref=speed class="setting-select speed"> | |
| <option value="0">Manual</option> | |
| <option value="1800">Very Slow</option> | |
| <option value="1200">Slow</option> | |
| <option value="800">Fast</option> | |
| <option value="300">Very Fast</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class='setting'> | |
| <div class='setting-header'> | |
| <svg :ref=durationHelp cmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#aaa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg> | |
| <div class='setting-title'>Duration</div> | |
| </div> | |
| <div class='setting-action'> | |
| <select :ref=duration class="setting-select duration"> | |
| <option value="3000">Very Short</option> | |
| <option value="5000">Short</option> | |
| <option value="7000">Long</option> | |
| <option value="10000">Very Long</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class='divide'></div> | |
| <div class='setting'> | |
| <div class='setting-header'> | |
| <svg :ref=randomHelp cmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#aaa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg> | |
| <div class='setting-title'>Random</div> | |
| </div> | |
| <div class='setting-action'> | |
| <input type="checkbox" :ref=random id="random" name="set-name" class="switch-input"> | |
| <label for="random" class="switch-label"></label> | |
| </div> | |
| </div> | |
| <div class='setting'> | |
| <div class='setting-header'> | |
| <svg :ref=hoverPauseHelp cmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#aaa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg> | |
| <div class='setting-title'>Hover pause</div> | |
| </div> | |
| <div class='setting-action'> | |
| <input type="checkbox" :ref=pause id="pause" name="set-name" class="switch-input"> | |
| <label for="pause" class="switch-label"></label> | |
| </div> | |
| </div> | |
| <div class='setting'> | |
| <div class='setting-header'> | |
| <svg :ref=arrowsHelp cmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#aaa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg> | |
| <div class='setting-title'>Arrows</div> | |
| </div> | |
| <div class='setting-action'> | |
| <input type="checkbox" :ref=arrows id="arrows" name="set-name" class="switch-input"> | |
| <label for="arrows" class="switch-label"></label> | |
| </div> | |
| </div> | |
| </div> | |
| <div :ref=settingsContainer class="btn-group settings"> | |
| <button :ref=settings class="btn"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-settings"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg> | |
| </button> | |
| </div> | |
| <!-- settings --> | |
| <input :ref=replace type="file" class="shell-input"> | |
| <!-- PUBLISH/PREVIEW ACTIONS --> | |
| <div style="position: absolute; bottom:1em; right:1em; display:flex;"> | |
| <!-- PREVIEW --> | |
| <div :ref=preview style="color:#717171; background:#dcdcdc;" class="${this._isDefaultImage() ? "disabled" : ""} de-widget-item de-publish"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg> | |
| <p :ref=previewText style="padding-left:8px; font-size:14px;" class="de-widget-item-text">Preview</p> | |
| </div> | |
| <!-- PREVIEW --> | |
| <!-- PUBLISH --> | |
| <div style="margin-left: 1em;" :ref=publish class="de-widget-item de-publish"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-save"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg> | |
| <p :ref=publishText style="padding-left:8px; font-size:14px;" class="de-widget-item-text">Save</p> | |
| <!-- SPINER --> | |
| <div :ref=publishSpinner style="top:8px; width:100px; position:absolute; transform: scale(0.4, 0.4); width: 100px; display:none" class="loader-inner ball-pulse"> | |
| <div></div> | |
| <div></div> | |
| <div></div> | |
| </div> | |
| <!-- SPINER --> | |
| </div> | |
| <!-- PUBLISH --> | |
| <div> | |
| <!-- PUBLISH/PREVIEW ACTIONS --> | |
| </div> | |
| `); | |
| const { | |
| slideEditorShellContainer, | |
| addInput, | |
| add, | |
| edit, | |
| replace, | |
| rm, | |
| settingsContainer, | |
| preview, | |
| duration, | |
| start, | |
| random, | |
| pause, | |
| speed, | |
| arrows, | |
| effect, | |
| visible, | |
| URL, | |
| caption, | |
| left, | |
| right, | |
| done, | |
| cancel, | |
| select, | |
| skip | |
| } = html; | |
| boundary.appendChild(slideEditorShellContainer); | |
| log.debug(`Built editor html`) | |
| // Set slideshowsetiings | |
| log.debug(`Setting slideshow setting`) | |
| duration.value = slideSettings.pauseTime; | |
| // start.value = slideSettings.startSlide; | |
| random.checked = slideSettings.randomStart; | |
| pause.checked = slideSettings.pauseOnHover; | |
| speed.value = slideSettings.animSpeed; | |
| arrows.checked = slideSettings.nav; | |
| effect.value = slideSettings.effect; | |
| visible.checked = slideSettings.visible; | |
| log.debug(`Set slideshow setting`) | |
| // Save in memory | |
| this._root.editorActions = Object.assign(this._root.editorActions, html); | |
| this._root.editorActions.select[_slideInitIndex].classList.add("active"); | |
| // Tooltips | |
| const toolbarTips = [ | |
| tippy(URL, { | |
| content: "URL", | |
| }), | |
| tippy(caption, { | |
| content: "Caption", | |
| }), | |
| tippy(rm, { | |
| content: "Delete", | |
| }), | |
| tippy(edit, { | |
| content: "Crop", | |
| }), | |
| tippy(add, { | |
| content: "Add", | |
| }), | |
| tippy(right, { | |
| content: "Move Right", | |
| }), | |
| tippy(left, { | |
| content: "Move Left", | |
| }), | |
| ] | |
| const settingsTips = [ | |
| tippy(html.durationHelp, { | |
| content: | |
| "This changes the length of time the slide shows for, before automatically moving to the next slide.", | |
| theme: "light-border", | |
| }), | |
| tippy(html.speedHelp, { | |
| content: | |
| "This setting changes the length of time the effect takes when moving between slides.", | |
| theme: "light-border", | |
| }), | |
| tippy(html.effectHelp, { | |
| content: | |
| "This setting changes the effect used when moving between slides.", | |
| theme: "light-border", | |
| }), | |
| tippy(html.visibleHelp, { | |
| content: "This controls whether to show the Slideshow on your store.", | |
| theme: "light-border", | |
| }), | |
| tippy(html.arrowsHelp, { | |
| content: | |
| "Checking this option will allow the customer to manually move back and forth through the slideshow if they wish. Unchecking it will remove that navigation. In either case, the slideshow will still automatically progress according to your other settings.", | |
| theme: "light-border", | |
| }), | |
| tippy(html.hoverPauseHelp, { | |
| content: | |
| "This will pause the slideshow when the customer hovers over the image, and continue once they move their mouse off. A pointer hovering over a slide usually indicates interest in that slide, and the customer wants or needs more time to look at it.", | |
| theme: "light-border", | |
| }), | |
| tippy(html.randomHelp, { | |
| content: | |
| "Checking this will always start the slideshow on a random slide when first loaded. The slideshow will continue from that point, and loop around to the beginning once it reaches the end.", | |
| theme: "light-border", | |
| }), | |
| ] | |
| tippy(settingsContainer, { | |
| content: "Settings", | |
| delay: 350, | |
| }) | |
| const cropActionTips = [ | |
| tippy(done, { | |
| content: "Crop Image", | |
| delay: 350, | |
| }), | |
| tippy(skip, { | |
| content: "Skip Crop", | |
| delay: 350, | |
| }), | |
| tippy(cancel, { | |
| content: "Cancel", | |
| delay: 350, | |
| }) | |
| ] | |
| createSingleton(cropActionTips, { | |
| delay: 350, | |
| moveTransition: 'transform 0.2s ease-out', | |
| updateDuration: 200, | |
| }) | |
| createSingleton(toolbarTips, { | |
| delay: 350, | |
| moveTransition: 'transform 0.2s ease-out', | |
| updateDuration: 200, | |
| }) | |
| createSingleton(settingsTips, { | |
| delay: 350, | |
| moveTransition: 'transform 0.2s ease-out', | |
| updateDuration: 200, | |
| }) | |
| // Events | |
| this._on(html.publish, "click", (e) => this._publish(e)); | |
| this._on(html.skip, "click", (e) => this._skip(e)) | |
| this._on(cancel, "click", async (e) => this._cancel(e)); | |
| this._on(done, "click", async (e) => this._done(e)); | |
| this._on(preview, "click", async (e) => this._preview(e)); | |
| this._on(replace, "change", async (e) => this._replace(e)); | |
| this._on(rm, "click", (e) => this._delete(e)); | |
| this._on( | |
| [ | |
| duration, | |
| random, | |
| pause, | |
| speed, | |
| arrows, | |
| effect, | |
| visible, | |
| ], "click", (e) => { | |
| if (e.target === visible) { | |
| if (visible.checked) { | |
| this._slide.elements.img.classList.remove('disabled') | |
| } else { | |
| this._slide.elements.img.classList.add('disabled') | |
| } | |
| } | |
| this._saveSettings() | |
| }) | |
| this._on(URL, "click", (e) => { | |
| const { captionInput, urlInput } = this._root.editorActions | |
| if (URL.classList.contains("disabled")) return | |
| if (captionInput) { | |
| this._saveCaptionText() | |
| _.rm(captionInput) | |
| this._root.editorActions.captionInput = null | |
| } | |
| if (urlInput) { | |
| this._saveUrlText() | |
| _.rm(urlInput) | |
| this._root.editorActions.urlInput = null | |
| return | |
| } | |
| // Get position | |
| let { top, left, height, width} = URL.getBoundingClientRect() | |
| const urlWidth = 170 | |
| top = top + height + 5 + window.scrollY | |
| left = left - (urlWidth / 2) + 20 | |
| // Add HTML Component | |
| const html = _.createFromTemplate(` | |
| <div style="width:${urlWidth}px; left:${left}px; top:${top}px" :ref=urlInput class="de-input de-url"> | |
| <input :ref=urlInputField style="width:${urlWidth}px;" ${this._activeSlide().url ? `value=${this._activeSlide().url}` : 'placeholder="https://google.com"'}> | |
| `) | |
| Object.assign(this._root.editorActions, html) | |
| _.inAfter(this._root.editorActions.urlInput, document.body) | |
| // Focus cursor | |
| html.urlInputField.focus() | |
| // close URL input if its active | |
| if (this._root.editorActions.urlInput.classList.contains("active")) { | |
| this._root.editorActions.urlInput.classList.remove("active"); | |
| } | |
| }); | |
| this._on(caption, "click", (e) => { | |
| const { urlInput, captionInput } = this._root.editorActions | |
| if (caption.classList.contains("disabled")) return | |
| if (urlInput) { | |
| this._saveUrlText() | |
| _.rm(urlInput) | |
| this._root.editorActions.urlInput = null | |
| } | |
| if (captionInput) { | |
| this._saveCaptionText() | |
| _.rm(captionInput) | |
| this._root.editorActions.captionInput = null | |
| return | |
| } | |
| // Get position | |
| let { top, left, height, width} = caption.getBoundingClientRect() | |
| const captionWidth = 170 | |
| top = top + height + 5 + window.scrollY | |
| left = left - (captionWidth / 2) + 20 | |
| // Add HTML Component | |
| const html = _.createFromTemplate(` | |
| <div style="width:${captionWidth}px; left:${left}px; top:${top}px" :ref=captionInput class="active de-input de-caption"> | |
| <input :ref=captionInputField style="width:${captionWidth}px;" ${this._activeSlide().text ? `value=${this._activeSlide().text}` : 'placeholder="Your Caption Here"'}> | |
| `) | |
| Object.assign(this._root.editorActions, html) | |
| _.inAfter(this._root.editorActions.captionInput, document.body) | |
| // Focus cursor | |
| html.captionInputField.focus() | |
| // close URL input if its active | |
| if (this._root.editorActions.captionInput.classList.contains("active")) { | |
| this._root.editorActions.captionInput.classList.remove("active"); | |
| } | |
| }); | |
| this._on(left, "click", (e) => { | |
| log.debug("MOVE:START", this._slides) | |
| const { select } = this._root.editorActions; | |
| // Get index of selected image | |
| const activeIndex = this.activeSlideIndex(); | |
| const newIndex = activeIndex - 1; | |
| // Prevent clicking too far left | |
| if (newIndex === -1) return; | |
| // Update init index | |
| this._slideInitIndex = newIndex; | |
| // Give active class to selected img | |
| select.forEach((s) => s.classList.remove("active")); | |
| select[newIndex].classList.add("active"); | |
| // Set the order on the image | |
| select.forEach((s, i) => this._slides[i].sequence); | |
| // Reorder slides | |
| _.arrayMove(this._slides, activeIndex, newIndex); | |
| // Reindex slide sequence | |
| this._slides.forEach((s, i) => { | |
| // Index starts @ 1 :( | |
| s.sequence = i + 1; | |
| }); | |
| log.debug("MOVE:END", this._slides) | |
| }); | |
| this._on(right, "click", (e) => { | |
| log.debug("MOVE:START", this._slides) | |
| const { select } = this._root.editorActions; | |
| // Get index of selected image | |
| const activeIndex = this.activeSlideIndex(); | |
| const newIndex = activeIndex + 1; | |
| // Prevent clicking too far right | |
| if (newIndex === select.length) return; | |
| // Update init index | |
| this._slideInitIndex = newIndex; | |
| // Give active class to selected img | |
| select.forEach((s) => s.classList.remove("active")); | |
| select[newIndex].classList.add("active"); | |
| // Set the order on the image | |
| select.forEach((s, i) => this._slides[i].sequence); | |
| // Reorder slides | |
| _.arrayMove(this._slides, activeIndex, newIndex); | |
| // Reindex slide sequence | |
| this._slides.forEach((s, i) => { | |
| // Index starts @ 1 :( | |
| s.sequence = i + 1; | |
| }); | |
| log.debug("MOVE:END", this._slides) | |
| }); | |
| this._on(settingsContainer, "click", (e) => { | |
| const el = document.querySelector(".slideshow-settings-panel"); | |
| if (el.style.display === "none" || el.style.display === "") { | |
| el.style.display = "block"; | |
| } else { | |
| el.style.display = "none"; | |
| } | |
| }); | |
| this._on(add, "click", (e) => addInput.click()); | |
| // https://stackoverflow.com/a/12102992/2263032 | |
| this._on(addInput, "click", function(){ | |
| this.value = null; | |
| }) | |
| this._on(addInput, "change", async (e) => this._addSlide(e)); | |
| this._on(edit, "click", async (e) => this._edit(e)); | |
| this._on(select, "click", async (e) => this._select(e)); | |
| } | |
| _saveCaptionText(){ | |
| const { captionInput } = this._root.editorActions | |
| if (!captionInput) return | |
| const activeIndex = this.activeSlideIndex(); | |
| const caption = captionInput.querySelector( | |
| "input" | |
| ).value; | |
| this._slides[activeIndex].text = _.slug(caption) | |
| } | |
| // Save url text | |
| _saveUrlText(){ | |
| const { urlInput } = this._root.editorActions | |
| if (!urlInput) return | |
| const activeIndex = this.activeSlideIndex(); | |
| const url = urlInput.querySelector( | |
| "input" | |
| ).value; | |
| this._slides[activeIndex].url = _.slug(url) | |
| } | |
| _delete(e) { | |
| log.debug("DELETE:START", this._slides); | |
| if (this._root.editorActions.rm.classList.contains("disabled")) return | |
| return Swal.fire({ | |
| animation: false, | |
| title: "Are you sure?", | |
| text: "You won't be able to revert this!", | |
| icon: "warning", | |
| showCancelButton: true, | |
| confirmButtonColor: "#20b7e6", | |
| cancelButtonColor: "#ea1636", | |
| confirmButtonText: "Yes, delete it!", | |
| }).then((result) => { | |
| if (!result.value) return | |
| const { edit, preview, rm, URL, left, right, selectContainer, caption } = this._root.editorActions | |
| const oldIndex = this._root.editorActions.select.findIndex((x) => | |
| x.classList.contains("active") | |
| ); | |
| let newIndex = 0; | |
| // Use default image if _slides have been deleted | |
| if (this._slides.length === 1) { | |
| this._slides[oldIndex].imageUrl = "https://d13z1xw8270sfc.cloudfront.net/origin/477866/plain-white-background_1592919904955.jpg"; | |
| // Disable editor actions | |
| [left, right, selectContainer, edit, preview, rm, URL, caption].forEach(x => x.classList.add("disabled")) | |
| } | |
| // Remove from | |
| // - slides array | |
| // - select array | |
| // - select html | |
| else { | |
| _.rm(this._root.editorActions.select[oldIndex]); | |
| this._slides.splice(oldIndex, 1); | |
| this._root.editorActions.select.splice(oldIndex, 1); | |
| // Move SELECT gallery left one place | |
| // | |
| // FROM | |
| // x - x - o - x | |
| // ^ | |
| // ' | |
| // | |
| // TO | |
| // x - o - x - x | |
| // ^ | |
| // ' | |
| if (oldIndex > 0) { | |
| newIndex = oldIndex - 1; | |
| } | |
| } | |
| if (this._slides.length < 2) { | |
| [left, right, selectContainer].forEach(x => x.classList.add("disabled")) | |
| } | |
| this._root.editorActions.select[newIndex].classList.add("active"); | |
| const url = this._slides[newIndex].imageUrl; | |
| this._slide.bind({ url }); | |
| this._slideInitIndex = newIndex; | |
| log.debug("DELETE:END", this._slides); | |
| }); | |
| } | |
| async _edit(e) { | |
| log.debug("EDIT:START", this._slide) | |
| const { preview, edit, selectMoveContainer, captionInput, editorSpinner, urlInput, publish, settingsPanel, cropActions, toolbarContainer, moveContainer, selectContainer, settingsContainer, replace } = this._root.editorActions | |
| const { zoomerWrap, overlay, boundary } = this._slide.elements; | |
| if (edit.classList.contains("disabled")) return | |
| // Loader | |
| this._slide.elements.img.classList.add('sleep') | |
| // Save points incase the user decides to cancel | |
| this._slide.backup = this._slide.get() | |
| if (captionInput) { | |
| // Save text | |
| const activeIndex = this.activeSlideIndex(); | |
| this._slides[activeIndex].text = this._root.editorActions.captionInput.querySelector( | |
| "input" | |
| ).value; | |
| _.rm(captionInput) | |
| this._root.editorActions.captionInput = null | |
| } | |
| if (urlInput) { | |
| // Save text | |
| const activeIndex = this.activeSlideIndex(); | |
| this._slides[activeIndex].url = this._root.editorActions.urlInput.querySelector( | |
| "input" | |
| ).value; | |
| _.rm(urlInput) | |
| this._root.editorActions.urlInput = null | |
| } | |
| this._hideEditorActions() | |
| this.spinner.start() | |
| // Wait between 0.6s-1.0s | |
| await new Promise(r => setTimeout(r, Math.floor(Math.random()*1000+600))) | |
| this._slide.elements.img.classList.remove('sleep') | |
| // Enable interaction | |
| zoomerWrap.style.opacity = "1"; | |
| zoomerWrap.style.display = "block"; | |
| overlay.style.pointerEvents = "auto"; | |
| this.spinner.stop() | |
| // Show DONE and CANCEL | |
| cropActions.style.opacity="1" | |
| cropActions.style.zIndex="100" | |
| cropActions.style.visibility="visible" | |
| log.debug("EDIT:END", this._slide) | |
| } | |
| async _select(e){ | |
| log.debug("SELECT:START", this._slides[newIndex]); | |
| const { urlInput, captionInput, } = this._root.editorActions | |
| if (captionInput) { | |
| // Save text | |
| const activeIndex = this.activeSlideIndex(); | |
| this._slides[activeIndex].text = this._root.editorActions.captionInput.querySelector( | |
| "input" | |
| ).value; | |
| _.rm(captionInput) | |
| this._root.editorActions.captionInput = null | |
| } | |
| if (urlInput) { | |
| // Save text | |
| const activeIndex = this.activeSlideIndex(); | |
| this._slides[activeIndex].url = this._root.editorActions.urlInput.querySelector( | |
| "input" | |
| ).value; | |
| _.rm(urlInput) | |
| this._root.editorActions.urlInput = null | |
| } | |
| // Get index of selected image | |
| var newIndex = Array.from(this._root.editorActions.select).indexOf( | |
| e.target | |
| ); | |
| this._slideInitIndex = newIndex; | |
| // Give active class to selected img | |
| this._root.editorActions.select.forEach((s) => | |
| s.classList.remove("active") | |
| ); | |
| this._root.editorActions.select[newIndex].classList.add("active"); | |
| // Bind image to editor | |
| const url = this._slides[newIndex].imageUrl; | |
| await this._slide.bind({ url }); | |
| log.debug("SELECT:END", this._slides[newIndex]); | |
| } | |
| _saveSettings() { | |
| const { duration, start, random, pause, speed, arrows, effect, visible } = this._root.editorActions | |
| log.debug("SETTINGS:START", this.options) | |
| Object.assign(this.options, { | |
| animSpeed: speed.value, | |
| effect: effect.value, | |
| nav: arrows.checked, | |
| pauseOnHover: pause.checked, | |
| pauseTime: duration.value, | |
| randomStart: random.checked, | |
| // startSlide: start.value ? start.value : 0, | |
| visible: visible.checked ? 1 : 0, | |
| }); | |
| log.debug("SETTINGS:END", this.options) | |
| return this.options | |
| } | |
| _onError(err) { | |
| this._emit("error", err) | |
| } | |
| /** | |
| * Add event(s) to element(s). | |
| * @param elements DOM-Elements | |
| * @param events Event names | |
| * @param fn Callback | |
| * @param options Optional options | |
| * @return Array passed arguments | |
| */ | |
| _on = this._listen.bind(this, "addEventListener"); | |
| _listen(method, elements, events, fn, options = {}) { | |
| // Normalize array | |
| if (elements instanceof HTMLCollection || elements instanceof NodeList) { | |
| elements = Array.from(elements); | |
| } else if (!Array.isArray(elements)) { | |
| elements = [elements]; | |
| } | |
| if (!Array.isArray(events)) { | |
| events = [events]; | |
| } | |
| for (const el of elements) { | |
| for (const ev of events) { | |
| el[method](ev, this._handleError(fn), { capture: false, ...options }); | |
| } | |
| } | |
| return Array.prototype.slice.call(arguments, 1); | |
| } | |
| _handleError(func) { | |
| const that = this | |
| return function() { | |
| try { | |
| Promise.resolve( | |
| func.apply(this, arguments) | |
| ).catch(err =>{ | |
| that._emit("error", err) | |
| }) | |
| } catch(err) { | |
| that._emit("error", err) | |
| } | |
| } | |
| } | |
| _isDefaultImage() { | |
| return this._slides && this._slides.length && this._slides[0].imageUrl.indexOf('plain-white-background') > -1 | |
| } | |
| async _addSlide(e) { | |
| log.debug("ADD:START", e) | |
| if (e.target.files?.length === 0) return; | |
| const file = e.target.files[0] | |
| e = null | |
| const { left, right, urlInput, captionInput, start, select, selectContainer, edit, rm, preview, URL, caption } = this._root.editorActions | |
| // Loader | |
| this._slide.elements.img.classList.add('sleep') | |
| this.spinner.start() | |
| // Save other image meta data | |
| if (captionInput) { | |
| this._saveCaptionText() | |
| _.rm(captionInput) | |
| this._root.editorActions.captionInput = null | |
| } | |
| if (urlInput) { | |
| // Save text | |
| const activeIndex = this.activeSlideIndex(); | |
| this._slides[activeIndex].url = this._root.editorActions.urlInput.querySelector( | |
| "input" | |
| ).value; | |
| _.rm(urlInput) | |
| this._root.editorActions.urlInput = null | |
| } | |
| // Slide limit | |
| if (this._slides.length >= this._slideLimit) { | |
| this._emit("error", `Reached slide limit of ${this._slideLimit}!`); | |
| return; | |
| } | |
| const url = window.URL.createObjectURL(file); | |
| // ONLY add slide if its NOT the DEFAULT image | |
| if (!this._isDefaultImage()) { | |
| // Add gallery item to SELECT | |
| const html = _.createFromTemplate(`<div :ref=select class="select-item"></div>`) | |
| _.inAfter(html.select, selectContainer) | |
| this._on(html.select, "click", async (e) => this._select(e)); | |
| let i = select.push(html.select) | |
| this._slides.push({ | |
| imageUrl: url, | |
| }); | |
| // Set next active slide on SELECT html | |
| const active = this.activeSlideIndex() | |
| const next = active + 1 | |
| select[active].classList.remove('active') | |
| select[next].classList.add('active') | |
| // Move slide next to the active one in _slides | |
| const last = this._slides.length - 1 | |
| const ls = this._slides | |
| _.arrayMove(ls, last, next) | |
| this._slideInitIndex = next; | |
| } else { | |
| // Enable editor actions | |
| [edit, preview, rm, URL, caption, left, right, selectContainer].forEach(x => x.classList.remove("disabled")) | |
| } | |
| if (this._slides.length > 1) { | |
| [selectContainer, left, right].forEach(x => x.classList.remove("disabled")) | |
| } | |
| // less than 1mb? wait a bit | |
| if (file.size < 1000000) { | |
| await new Promise(r => setTimeout(r, Math.floor(Math.random()*500+300))) | |
| } | |
| // Bind image to editor | |
| await this._slide.bind(url); | |
| // Save edited photo | |
| const blob = await this._slide.result({ type: "blob" }); | |
| const img = window.URL.createObjectURL(blob); | |
| this._slides[this._slideInitIndex].imageUrl = img; | |
| // Hide loader | |
| this._slide.elements.img.classList.remove('sleep') | |
| this.spinner.stop() | |
| log.debug("ADD:END", this._activeSlide()) | |
| } | |
| _destroyImageEditor() { | |
| Object.keys(this._root.editorActions).forEach((key) => { | |
| if (Array.isArray(this._root.editorActions[key])) { | |
| this._root.editorActions[key].forEach((el, i) => { | |
| _.rm(this._root.editorActions[key][i]); | |
| delete this._root.editorActions[key][i]; | |
| }); | |
| // reindex array | |
| this._root.editorActions[key] = Array.from( | |
| this._root.editorActions[key] | |
| ).filter(Boolean); | |
| delete this._root.editorActions[key]; | |
| } else { | |
| _.rm(this._root.editorActions[key]); | |
| delete this._root.editorActions[key]; | |
| } | |
| }); | |
| this._root.editorActions = {}; | |
| } | |
| async _buildImageEditor() { | |
| const { namespace, _slideInitIndex, } = this; | |
| const { slideshowTarget } = this.options | |
| // Destory any previous croppie as we can only init one at a time | |
| if (this._slide) { | |
| this._slide.destroy(); | |
| this._slide = null; | |
| } | |
| // Build Croppie image dimentions | |
| const url = this._slides[_slideInitIndex].imageUrl; | |
| var { offsetWidth } = slideshowTarget.parentElement; | |
| const { width, height } = await _.dimen(url); | |
| const aspect = _.aspect(width, height, offsetWidth, offsetWidth); | |
| const opt = { | |
| width: aspect.width, | |
| height: aspect.height, | |
| target: slideshowTarget, | |
| url, | |
| }; | |
| this._slide = await this._buildCroppieInstance(opt); | |
| var el = _.createFromTemplate(` | |
| <div :obj="editorActions"> | |
| <div class="${namespace}-editor-toolbar" :ref=container> | |
| <!-- DONE --> | |
| <div class="done" :ref=done> | |
| <button :ref=done class="${namespace}-toolbar-actions-item"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg> | |
| </button> | |
| </div> | |
| <!-- DONE --> | |
| <!-- MODIFY --> | |
| <div :ref=modify class="modify"> | |
| <!-- REPLACE --> | |
| <button :ref=cancel class="${namespace}-toolbar-actions-item"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg> | |
| </button> | |
| <input :ref="input" type="file" style="display:none;"/> | |
| <!-- REPLACE --> | |
| </div> | |
| <!-- MODIFY --> | |
| </div> | |
| </div> | |
| `); | |
| // Ref | |
| this._root.editorActions = Object.assign( | |
| this._root.editorActions, | |
| el.editorActions | |
| ); | |
| const { done, cancel } = this._root.editorActions; | |
| // Tooltips | |
| tippy(done, { | |
| content: "Done", | |
| delay: 350, | |
| }); | |
| tippy(cancel, { | |
| content: "Cancel", | |
| delay: 350 | |
| }); | |
| // Events | |
| this._on(cancel, "click", async (e) => this._cancel(e)); | |
| this._on(done, "click", async (e) => this._done(e)); | |
| slideshowTarget.appendChild(el.editorActions.container); | |
| } | |
| _buildStyles() { | |
| var { selectedColours, css, el } = this._style; | |
| if (!selectedColours) return; | |
| // Compile new style sheet | |
| var compiled_style; | |
| selectedColours.forEach(function (clr) { | |
| var regex = new RegExp("\\[" + clr.name + "\\]", "g"); | |
| var hex = clr.hex.trim(); | |
| if (hex.startsWith("#")) { | |
| hex = hex.slice(1); | |
| } | |
| if (compiled_style) { | |
| compiled_style = compiled_style.replace(regex, hex); | |
| } else { | |
| compiled_style = css.replace(regex, hex); | |
| } | |
| }); | |
| // Append the compiled style sheet | |
| var css = document.querySelector(el); | |
| if (!css) { | |
| _.addStyle(el, compiled_style); | |
| } else { | |
| _.rm(css); | |
| _.addStyle(el, compiled_style); | |
| } | |
| } | |
| _buildColourPicker(e) { | |
| if (this._pickr) { | |
| this._pickr.destroyAndRemove(); | |
| } | |
| // Create the colour pickr | |
| var pickr = new Pickr({ | |
| el: e.target, | |
| theme: "nano", | |
| padding: 10, | |
| default: this._style.initColour, | |
| useAsButton: true, | |
| // showAlways: true, | |
| autoReposition: false, | |
| components: { | |
| preview: true, | |
| opacity: true, | |
| hue: true, | |
| }, | |
| }); | |
| pickr.show(); | |
| // pickr._root.app.style.display = 'none' | |
| // // Reposition the pickr to where the click occured. | |
| // var { top, left } = this._style.rectangle | |
| // top = top + 30 | |
| // left = left - 93 | |
| // setTimeout(function(){ | |
| // var arrow = document.createElement('div') | |
| // arrow.className = 'pcr-tooltip-arrow' | |
| // pickr._root.app.appendChild(arrow) | |
| // pickr._root.app.style.top = top.toFixed(3) + 'px' | |
| // pickr._root.app.style.left = left.toFixed(3) + 'px' | |
| // pickr._root.app.style.display = 'block' | |
| // }, 50) | |
| this._pickr = pickr; | |
| // Append the colour picker in our container | |
| // this._root.widget.reset.insertAdjacentElement( 'beforebegin', this._pickr._root.app) | |
| // Reposition the pickr | |
| // this._pickr._root.app.style.paddingLeft = "20px"; | |
| // this._pickr._root.app.style.position = "static"; | |
| // Build the custom style sheet when a colour is picked | |
| this._pickr.on("changestop", (instance) => { | |
| // Get index of active colour | |
| var index = this._style.activeColourIndex; | |
| if (typeof index !== "undefined") { | |
| // Get selected colour hex | |
| var hex = instance._color.toHEXA().toString(3); | |
| // Update the colour palette on the page | |
| this._root.colour[index].style.backgroundColor = hex; | |
| // Update the colour swatch | |
| this._style.selectedColours[index].hex = hex; | |
| // TODO: Push only colours to the publish array that have changed. | |
| // Build styles | |
| this._buildStyles(); | |
| } else { | |
| throw "No Active Colour Selected!"; | |
| } | |
| }); | |
| } | |
| // Returns | |
| // - 0 if has not slides | |
| // - the active _slide object if _slides has length | |
| _activeSlide(){ | |
| return this._slides && this._slides.length && this._slides[this._slideInitIndex] | |
| } | |
| async _cancel(){ | |
| log.debug('START:CANCEL', this._slides) | |
| const { editorSpinner } = this._root.editorActions | |
| // Hide done, cancel actions | |
| this._hideCropActions() | |
| // Wait between 0.6s-1.0s | |
| await new Promise(r => setTimeout(r, Math.floor(Math.random()*300+200))) | |
| // Reset points | |
| if (this._slide.backup) { | |
| const { url } = this._slide.data | |
| const { points, zoom, orientation } = this._slide.backup | |
| await this._slide.bind({ url, points, orientation, zoom }) | |
| } | |
| // Show toolbar, publish settings tools | |
| this._showEditorActions() | |
| log.debug('END:CANCEL', this._slides) | |
| } | |
| _showEditorActions(){ | |
| const { preview, moveContainer, publish, toolbarContainer, selectMoveContainer, selectContainer, settingsContainer, replace } = this._root.editorActions | |
| // Show toolbar elements on page | |
| toolbarContainer.style.opacity="1" | |
| toolbarContainer.style.zIndex="100" | |
| selectContainer.style.opacity="1" | |
| settingsContainer.style.opacity="1" | |
| settingsContainer.style.zIndex="100" | |
| publish.style.opacity="1" | |
| replace.style.display="block" | |
| selectMoveContainer.style.visibility="visible" | |
| selectMoveContainer.style.opacity="1" | |
| selectMoveContainer.style.zIndex="100" | |
| moveContainer.style.opacity="1" | |
| preview.style.opacity="1" | |
| } | |
| _hideEditorActions(){ | |
| const { preview, edit, selectMoveContainer, captionInput, editorSpinner, urlInput, publish, settingsPanel, cropActions, toolbarContainer, moveContainer, selectContainer, settingsContainer, replace } = this._root.editorActions | |
| // Hide elements on page | |
| toolbarContainer.style.opacity="0" | |
| toolbarContainer.style.zIndex="0" | |
| selectContainer.style.opacity="0" | |
| selectContainer.style.zIndex="0" | |
| settingsContainer.style.opacity="0" | |
| settingsContainer.style.zIndex="0" | |
| replace.style.display="none" | |
| settingsPanel.style.display="none" | |
| publish.style.opacity="0" | |
| selectMoveContainer.style.opacity="0" | |
| selectMoveContainer.style.zIndex="0" | |
| moveContainer.style.opacity="0" | |
| preview.style.opacity="0" | |
| } | |
| _hideCropActions(){ | |
| const { zoomerWrap, overlay } = this._slide.elements; | |
| const { cropActions } = this._root.editorActions | |
| // Hide DONE and CANCEL | |
| cropActions.style.opacity="0" | |
| cropActions.style.zIndex="0" | |
| // Disable interaction | |
| zoomerWrap.style.opacity = "0"; | |
| overlay.style.pointerEvents = "none"; | |
| } | |
| async _buildCroppieInstance({ | |
| width, | |
| height, | |
| target, | |
| url, | |
| mouseWheelZoom = true, | |
| disabled = false | |
| }) { | |
| const { boundaryHeight, boundaryWidth } = this.options | |
| var c = new Croppie(target, { | |
| viewport: { width: boundaryWidth, height: boundaryHeight }, | |
| boundary: { width: boundaryWidth, height: boundaryHeight }, | |
| showZoomer: true, | |
| mouseWheelZoom: mouseWheelZoom, | |
| enableResize: true, | |
| disabled: disabled | |
| }); | |
| var options = { | |
| url: url, | |
| zoom: 0, | |
| }; | |
| await c.bind(options); | |
| return c; | |
| } | |
| _buildCroppieGalleryToolbar() { | |
| var namespace = this.namespace; | |
| var images = this._slides; | |
| var ctnr = this._root.resize.gallery; | |
| if (ctnr) { | |
| ctnr.container.parentNode.removeChild(ctnr.container); | |
| } | |
| // Append the toolbox gallery html | |
| var html = createFromTemplate(` | |
| <div :obj=gallery> | |
| <div :ref=container> | |
| <div class="${namespace}-gallery-toolbar"> | |
| ${images.map((img, i) => `<div :arr=item data-seq=${i}>${i}</div>`).join("")} | |
| </div> | |
| </div> | |
| </div> | |
| `); | |
| this._root.resize.gallery = html.gallery; | |
| document | |
| .querySelector(".cr-boundary") | |
| .append(this._root.resize.gallery.container); | |
| // Rebind selected gallery image to main slide | |
| this._on(this._root.resize.gallery.item, "click", (e) => { | |
| var i = e.target.dataset.seq; | |
| var image = this._slides[i]; | |
| this._slide.bind(image); | |
| e.target.classList.add("active"); | |
| }); | |
| } | |
| _emit(event, ...args) { | |
| this._eventListener[event].forEach((cb) => cb(...args, this)); | |
| } | |
| on(event, cb) { | |
| // Validate | |
| if ( | |
| typeof cb === "function" && | |
| typeof event === "string" && | |
| event in this._eventListener | |
| ) { | |
| this._eventListener[event].push(cb); | |
| } | |
| return this; | |
| } | |
| } | |
| export default Editor; | |
| // async function getThemeColours(storeId) { | |
| // return fetch("Editor/svc/Services.svc/GetThemeColours?storeId=" + storeId) | |
| // .then((res) => res.json()) | |
| // .then((json) => JSON.parse(json).d); | |
| // } | |
| async function getSlideshowImages(storeId) { | |
| if (isDev){ | |
| return Promise.resolve() | |
| } else { | |
| return fetch("Editor/svc/Services.svc/GetSlideShowImages?storeId=" + storeId) | |
| .then((res) => res.json()) | |
| .then((json) => JSON.parse(json)) | |
| .catch(err => log.error("Something went wrong in getSlideshowImages. Reason:", err)) | |
| } | |
| // return fetch("Editor/svc/Services.svc/GetSlideShowImages?storeId=" + storeId) | |
| // .then((res) => res.json()) | |
| // .then((json) => JSON.parse(json)); | |
| } | |
| var s; | |
| const storeId = g_fws_sk; | |
| var str = JSON.stringify; | |
| var prs = JSON.parse; | |
| function handleError(err) { | |
| log.error("Failed to handleResponse. ", err); | |
| } | |
| function handleResponse({ request, response }) { | |
| return new Promise((resolve, reject) => { | |
| const responseHeaders = {}; | |
| for (var pair of response.headers.entries()) { | |
| responseHeaders[pair[0]] = pair[1]; | |
| } | |
| if (response.status !== 200) { | |
| return Promise.all([ | |
| // Try get text | |
| // response.text().then((r) => { | |
| // return Promise.resolve(r); | |
| // }) | |
| // .catch(e => { | |
| // return Promise.resolve('Failed to parse TEXT response.' + e) | |
| // }), | |
| // Try get JSON | |
| response.json() | |
| .then(r => { | |
| return Promise.resolve('JSON response: ' + r) | |
| }) | |
| .catch(e => { | |
| return Promise.resolve('Failed to parse JSON response.' + e) | |
| }) | |
| ]).then((res) => { | |
| const msg = | |
| "Failed to make request. Request headers: " + | |
| str(request) + | |
| ". Response headers: " + | |
| str(responseHeaders) + | |
| ". Response message: " + | |
| res; | |
| reject(msg); | |
| }); | |
| } else { | |
| log.debug( | |
| "Success! Request headers: " + | |
| str(request) + | |
| ". Response headers: " + | |
| str(responseHeaders) | |
| ); | |
| resolve(responseHeaders) | |
| } | |
| }); | |
| } | |
| function init(event) { | |
| let mammothBoundaryWidth = 900 | |
| if (document.querySelector(".slider-wrapper.theme-default")) { | |
| mammothBoundaryWidth = document.querySelector(".slider-wrapper.theme-default").offsetWidth | |
| } | |
| const configurations = { | |
| "storebuilder/89137/electron": { | |
| storeId: storeId, | |
| slideshowTarget: document.querySelector("#main-content .row"), | |
| flex: document.querySelector("#slider"), | |
| }, | |
| "storebuilder/284203/ritz": { | |
| storeId: storeId, | |
| slideshowTarget:document.querySelector(".nivo_container"), | |
| flex: document.querySelector("#slider"), | |
| boundaryHeight: 372, | |
| boundaryWidth: 960 | |
| }, | |
| "storebuilder/309223/mammoth": { | |
| storeId: storeId, | |
| slideshowTarget: document.querySelector(".slider-wrapper.theme-default"), | |
| flex: document.querySelector("#slider"), | |
| boundaryHeight: 387, | |
| // TODO: use the slider width for mammoth until the slider is responsiv | |
| boundaryWidth: mammothBoundaryWidth | |
| }, | |
| }; | |
| var allowed = [ | |
| "storebuilder/309223/mammoth", | |
| "storebuilder/284203/ritz", | |
| "storebuilder/89137/electron", | |
| ]; | |
| let template; | |
| if (localStorage && localStorage.getItem("template")) { | |
| template = localStorage.getItem("template"); | |
| } | |
| // Only run fow allowed templates | |
| if (allowed.indexOf(template) === -1) { | |
| log.debug("Template [%s] is not in allowed list", template); | |
| return; | |
| } | |
| // Index of designs dont need logging, so they are set as isDev | |
| if (localStorage && localStorage.getItem("isDev")) { | |
| isDev = localStorage.getItem("isDev") === "true" | |
| } | |
| window.log = log = new Logger({ | |
| debugEnabled: true, | |
| storeId: g_fws_sk, | |
| isDev: isDev, | |
| uuid: _.uuid(), | |
| }); | |
| log.debug(`isDev: [${isDev}], Template: [${template}]`); | |
| if (navigator && navigator.userAgent) { | |
| log.debug("User agent:", navigator.userAgent); | |
| } | |
| Promise.all([getSlideshowImages(storeId)]).then( | |
| function ([images]) { | |
| log.debug("Loaded slides:", images && images.slides && images.slides.length); | |
| const conf = Object.assign(configurations[template], images) | |
| log.debug("Initializing Editor with options:", conf) | |
| window.editor = new Editor(conf); | |
| editor | |
| .on("init", (e) => log.debug("INIT", e)) | |
| .on("warn", (e) => log.warn("WARN", e)) | |
| .on("error", function (e) { | |
| log.error("EDITOR_ERROR", e); | |
| Swal.fire({ | |
| icon: "error", | |
| title: "Oops...", | |
| html: e + "<br><br>" + "[DEBUG_ID] " + log._uuid, | |
| animation: false, | |
| confirmButtonColor: "#f054a7", | |
| }); | |
| }) | |
| } | |
| ).catch(err => { | |
| Swal.fire({ | |
| icon: "error", | |
| title: "Oops...", | |
| html: err, | |
| // footer: "<a href>Why do I have this issue?</a>", | |
| animation: false, | |
| confirmButtonColor: "#f054a7", | |
| }); | |
| log.error("Failed to run getSlideshowImages. Reason:", err); | |
| }); | |
| } | |
| // Initial Editor, attach advanced logging | |
| document.addEventListener("DOMContentLoaded", function () { | |
| init(); | |
| }); | |
| window.addEventListener('unhandledrejection', function(event) { | |
| const err = "[GLOBAL_ERROR]: " + event.reason | |
| log.error(err); | |
| Swal.fire({ | |
| icon: "error", | |
| title: "DEBUG_ID: ["+log._uuid+"]", | |
| html: err, | |
| animation: false, | |
| confirmButtonColor: "#f054a7", | |
| }); | |
| event.preventDefault(); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment