UNPKG

globular-mvc

Version:

Generic template to create web-application that made use of globular as backend and materialize as css (wrap in web-component's)

901 lines (811 loc) 28.8 kB
import '@polymer/paper-button/paper-button.js'; import '@polymer/paper-slider/paper-slider.js'; import { ApplicationView } from '../ApplicationView'; import { generatePeerToken, getUrl, Model } from "../Model"; import { File as File__ } from "../File"; // File object already exist in js and I need to use it... import { createThumbmail } from './BlogPost'; import * as Masonry from 'masonry-layout' import domtoimage from 'dom-to-image'; import { fireResize } from './utility'; import { GetItemDefinitionRequest } from 'globular-web-client/catalog/catalog_pb'; export class ImageCropper extends HTMLElement { constructor() { super(); this.oldSrc = ''; this.croppedImage = null; this.attachShadow({ mode: 'open' }); } get width() { return this.hasAttribute('width'); } get height() { return this.hasAttribute('height'); } get rounded() { return this.hasAttribute('rounded'); } setCropImage(dataUrl) { this.croppedImage = dataUrl; } // Set the image from data url. setImage(data) { this.loadPic({ target: { files: [data] } }) } loadPic(e) { this.resetAll(); var reader = new FileReader(); reader.readAsDataURL(e.target.files[0]); reader.cmp = this; reader.onload = function (event) { var shdRoot = event.target.cmp.shadowRoot; shdRoot.querySelector(".resize-image").setAttribute('src', event.target.result); event.target.cmp.oldSrc = event.target.result; shdRoot.querySelector(".resize-image").cmp = shdRoot; shdRoot.querySelector(".resize-image").onload = function (e) { var shdRoot = e.target.cmp; shdRoot.querySelector('.slidecontainer').style.display = 'block'; shdRoot.querySelector('.crop').style.display = 'initial'; var widthTotal = shdRoot.querySelector(".resize-image").offsetWidth; shdRoot.querySelector(".resize-container").style.width = widthTotal + 'px'; shdRoot.querySelector(".resize-image").style.width = widthTotal + 'px'; shdRoot.querySelector("#myRange").max = widthTotal + widthTotal; shdRoot.querySelector("#myRange").value = widthTotal; shdRoot.querySelector("#myRange").min = widthTotal - widthTotal; } } } dragElement(elmnt) { var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; elmnt.onmousedown = dragMouseDown; elmnt.ontouchstart = dragMouseDown; function dragMouseDown(e) { e.preventDefault(); // get the mouse cursor position at startup: pos3 = e.clientX || e.targetTouches[0].pageX; pos4 = e.clientY || e.targetTouches[0].pageY; document.onmouseup = closeDragElement; document.ontouchend = closeDragElement; // call a function whenever the cursor moves: document.onmousemove = elementDrag; document.ontouchmove = elementDrag; } function elementDrag(e) { e = e || window.event; // calculate the new cursor position: pos1 = pos3 - (e.clientX || e.targetTouches[0].pageX); pos2 = pos4 - (e.clientY || e.targetTouches[0].pageY); pos3 = (e.clientX || e.targetTouches[0].pageX); pos4 = (e.clientY || e.targetTouches[0].pageY); // set the element's new position: elmnt.style.top = (elmnt.offsetTop - pos2) + "px"; elmnt.style.left = (elmnt.offsetLeft - pos1) + "px"; } function closeDragElement() { // stop moving when mouse button is released: document.onmouseup = ''; document.ontouchend = ''; document.onmousemove = ''; document.ontouchmove = ''; } } crop() { this.shadowRoot.querySelector('.crop').style.display = 'none'; this.shadowRoot.querySelector('.reset').style.display = 'initial'; this.shadowRoot.querySelector('.slidecontainer').style.display = 'none'; var image = this.shadowRoot.querySelector('.resize-image'); var resize_canvas = document.createElement('canvas'); resize_canvas.width = image.offsetWidth; resize_canvas.height = image.offsetHeight; resize_canvas.getContext('2d').drawImage(image, 0, 0, image.offsetWidth, image.offsetHeight); image.setAttribute('src', resize_canvas.toDataURL("image/jepg")); var imageContainer = this.shadowRoot.querySelector('.resize-container'); var centerContainer = this.shadowRoot.querySelector('.center'); var left = centerContainer.offsetLeft - imageContainer.offsetLeft; var top = centerContainer.offsetTop - imageContainer.offsetTop; var width = centerContainer.offsetWidth; var height = centerContainer.offsetHeight; var newTop = centerContainer.offsetTop; var newLeft = centerContainer.offsetLeft; var crop_canvas = document.createElement('canvas'); crop_canvas.width = width; crop_canvas.height = height; crop_canvas.getContext('2d').drawImage(resize_canvas, left, top, width, height, 0, 0, width, height); var imageC = this.shadowRoot.querySelector('.imageCropped'); imageC.src = crop_canvas.toDataURL("image/jepg"); this.shadowRoot.querySelector('.resize-image').setAttribute('src', ''); } slide(w) { this.shadowRoot.querySelector(".resize-container").style.width = (w) + 'px'; this.shadowRoot.querySelector(".resize-image").style.width = (w) + 'px'; } getCropped() { return this.shadowRoot.querySelector(".imageCropped").getAttribute('src'); } resetAll() { this.shadowRoot.querySelector(".reset").style.display = 'none'; this.shadowRoot.querySelector(".crop").style.display = 'none'; this.shadowRoot.querySelector(".slidecontainer").style.display = 'none'; this.shadowRoot.querySelector(".resize-container").removeAttribute('style'); this.shadowRoot.querySelector(".resize-image").setAttribute('src', ''); this.shadowRoot.querySelector(".imageCropped").setAttribute('src', ''); this.shadowRoot.querySelector(".resize-image").style.width = '100%'; this.shadowRoot.querySelector("#myRange").max = 10; this.shadowRoot.querySelector("#myRange").value = 5; this.shadowRoot.querySelector("#myRange").min = 0; } reset() { this.resetAll(); this.shadowRoot.querySelector(".resize-image").setAttribute('src', this.oldSrc); } connectedCallback() { this.shadowRoot.innerHTML = ` <style> #container{ background-color: var(--palette-background-paper); color: var(--palette-text-primary); } .slidecontainer { width: 100%; display:none; z-index: 1; position: relative; margin-top:8px; } .slider { -webkit-appearance: none; width: 100%; height: 15px; border-radius: 5px; background: #d3d3d3; outline: none; opacity: 0.9; -webkit-transition: .2s; transition: opacity .2s; } .slider:hover { opacity: 1; } .slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 25px; height: 25px; border-radius: 50%; background: #2196F3; cursor: pointer; } .slider::-moz-range-thumb { width: 25px; height: 25px; border-radius: 50%; background: #2196F3; cursor: pointer; border:none; } .resize-container { position: relative; display: inline-block; cursor: move; margin: 0 auto; } .resize-container img { display: block; } .resize-container:hover img, .resize-container:active img { outline: 2px dashed gray; } .parent{ width:99%; height:99%; overflow: hidden; position:absolute; top:0px; left:0px; } .center{ position: absolute; width: 150px; height: 150px; top: calc(50% - 150px/2); left: calc(50% - 150px/2); z-index: 2; background: rgba(255, 255, 255, .3); border: 2px solid #cecece; } .imageCropped{ position: relative; left: -2px; top: -2px; } .uploader{ z-index: 1; position: relative; display:none; } .lb_uploader{ z-index: 1; position: relative; cursor:pointer; } .crop, .reset { display:none; } .btn{ z-index:1; position: relative; font-size: .85rem; border: none; color: var(--palette-text-accent); background: var(--palette-primary-accent); max-height: 32px; border: none; z-index:1; } </style> <div id="container"> <label class='lb_uploader' for='uploader'> <slot name='select'> <paper-button class='btn' toggles raised ><slot name='selectText'>Select</slot></paper-button> </slot> </label> <label class='reset'> <slot name='reset'> <paper-button class='btn' toggles raised ><slot name='resetText'>Reset</slot></paper-button> </slot> </label> <label class='crop'> <slot name='crop'> <paper-button class='btn' toggles raised ><slot name='cropText'>Crop</slot></paper-button> </slot> </label> <input type="file" class="uploader" id='uploader'/> <div class="slidecontainer"> <paper-slider id="myRange" class="slider"> </paper-slider> </div> <div class='parent'> <div class="resize-container"> <img class="resize-image" src="" style='width:100%'> </div> <div class='center'><img class="imageCropped"></div> </div> </div> `; this.shadowRoot.querySelector('.uploader').addEventListener('change', e => { this.loadPic(e); }); this.shadowRoot.querySelector('#myRange').addEventListener('immediate-value-change', e => { this.slide(e.target.immediateValue); }); this.shadowRoot.querySelector('.crop').addEventListener('click', e => { this.crop(); }); this.shadowRoot.querySelector('.reset').addEventListener('click', e => { this.reset(); }); if (this.width) { this.shadowRoot.querySelector('.center').style.width = this.getAttribute('width'); this.shadowRoot.querySelector('.center').style.left = 'calc(50% - ' + this.getAttribute('width') + '/2)'; } if (this.height) { this.shadowRoot.querySelector('.center').style.height = this.getAttribute('height'); this.shadowRoot.querySelector('.center').style.top = 'calc(50% - ' + this.getAttribute('height') + '/2)'; } if (this.rounded) { this.shadowRoot.querySelector('.center').style.borderRadius = '200px'; this.shadowRoot.querySelector('.imageCropped').style.borderRadius = '200px'; } if (this.croppedImage != null) { var imageC = this.shadowRoot.querySelector('.imageCropped'); imageC.src = this.croppedImage; } this.dragElement(this.shadowRoot.querySelector(".resize-container")); } } window.customElements.define('globular-image-cropper', ImageCropper); /** * Classic image viewer */ export class ImageViewer extends HTMLElement { constructor() { super(); this.onclose = null; let shadowRoot = this.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = ` <style> ::-webkit-scrollbar { width: 5px; height: 5px; } ::-webkit-scrollbar-track { background: var(--palette-background-default); } ::-webkit-scrollbar-thumb { background: var(--palette-divider); } .modal { z-index: 3000; display: none; position: absolute; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0, 0, 0, 0.94); font-family: Verdana,sans-serif; display:flex; justify-content:center; align-items: center; } #info { background-color:#2196F3; left:88px; font-size:18px; text-align:center; color:white; margin-top:8px; padding: 5px 16px; } #leftA { position:absolute; top:53%; left:0%; transform:translate(0%,-53%); font-size:30px; background-color: #3e3c3c99; color:white; } #rightA { position:absolute; top:53%; right:0%; transform:translate(0%,-53%); font-size:30px; background-color: #3e3c3c99; color:white; } .btn, .button { border: none; display: inline-block; padding: 8px 16px; vertical-align: middle; overflow: hidden; text-decoration: none; color: inherit; background-color: inherit; text-align: center; cursor: pointer; white-space: nowrap; } .btn, .button { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .display-topright { position: absolute; right: 0; top: 0; } .display-topleft { position: absolute; left: 20px; top: 0; font-size:25px; text-align:center; color:white; margin-top:5px; } .container, .w3-panel { padding: 0.01em 16px; } .image { max-width: 100%; height: auto; } img { vertical-align: middle; border-style: none; } @media (max-width:768px){ .modal{ padding-top:50px; } } </style> <div id="imageViewer" class="modal" > <span id='closeBtn' class="button display-topright" style='color:white;font-size:30px;'> × </span> <div id='counter' class='display-topleft' ></div> <div id='info' class='display-topleft btn' style="display: none;"> Description </div> <div class="container"> <slot name='images'><span style='color:white;'>No images to show</span></slot> <div id='leftA' class="button" >❮</div> <div id='rightA' class="button" >❯</div> </div> </div>`; shadowRoot.querySelector('#closeBtn').addEventListener('click', e => { this.style.display = 'none'; if (this.onclose != undefined) { this.onclose() } }); if (this.noinfo) { shadowRoot.querySelector('#info').style.display = 'none'; } //right arrow event shadowRoot.querySelector('#rightA').addEventListener('click', e => { this.nextImage(); }); //left arrow event shadowRoot.querySelector('#leftA').addEventListener('click', e => { this.prevImage(); }); } connectedCallback() { if (this.children.length != 0) { var ch = this.children; var cant = ch.length; for (var i = 0; i < cant; i++) { ch[i].style.maxHeight = '75vh' if (this.parentNode.tagName == "BODY") ch[i].style.maxHeight = 'calc(100vh - 20px)'; } } } get noinfo() { return this.hasAttribute('noinfo'); } populateChildren() { if (this.children.length != 0) { var ch = this.children; var cant = ch.length; for (var i = 0; i < cant; i++) { if (i == 0) ch[i].style.display = 'block'; else ch[i].style.display = 'none'; ch[i].style.margin = 'auto'; ch[i].style.maxWidth = '100%'; ch[i].style.maxHeight = '75vh' } //counter this.shadowRoot.querySelector('#counter').innerHTML = '1/' + cant; } else { //hide the arrows this.shadowRoot.querySelector('#leftA').style.display = 'none'; this.shadowRoot.querySelector('#rightA').style.display = 'none'; } } activeImage(index) { var ch = this.children; var cant = ch.length; for (var i = 0; i < cant; i++) { ch[i].style.display = 'none'; } ch[index].style.display = 'block'; this.shadowRoot.querySelector('#counter').innerHTML = (index + 1) + '/' + (cant); } addImage(e) { e.slot = "images" this.appendChild(e); this.populateChildren(); //show the arrows this.shadowRoot.querySelector('#leftA').style.display = 'block'; this.shadowRoot.querySelector('#rightA').style.display = 'block'; } loadImgFrom(ele) { var el = ele.querySelectorAll('img'); this.style.display = 'block'; this.innerHTML = ''; for (var i = 0; i < el.length; i++) { var src = el[i].getAttribute('src') src += "?application=" + Model.application; if (localStorage.getItem("user_token") != undefined) { src += "&token=" + localStorage.getItem("user_token"); } var newPic = document.createElement('img'); newPic.setAttribute('slot', 'images'); newPic.setAttribute('src', src); //if have data-info if (el[i].getAttribute('data-info')) newPic.setAttribute('data-info', el[i].getAttribute('data-info')); //adding to the component this.addImage(newPic); } } infoClick(title, fn) { this.shadowRoot.querySelector('#info').innerHTML = title; this.shadowRoot.querySelector('#info').addEventListener('click', function func(event) { fn(event); }); } nextImage() { var ch = this.children; var cant = ch.length; for (var i = 0; i < cant; i++) { if (ch[i].style.display == 'block') { var actived = ch[0]; var index = 0; if (i < (cant - 1)) { actived = ch[i + 1]; index = i + 1; } } ch[i].style.display = 'none'; } actived.style.display = 'block'; this.shadowRoot.querySelector('#counter').innerHTML = (index + 1) + '/' + (cant); } prevImage() { var ch = this.children; var cant = ch.length; for (var i = 0; i < cant; i++) { if (ch[i].style.display == 'block') { var actived = ch[cant - 1]; var index = cant - 1; if (i > 0) { actived = ch[i - 1]; index = i - 1; } } ch[i].style.display = 'none'; } actived.style.display = 'block'; this.shadowRoot.querySelector('#counter').innerHTML = (index + 1) + '/' + (cant); } } window.customElements.define('globular-image-viewer', ImageViewer); /** * That component will be use to select image with drag and drop */ export class ImageSelector extends HTMLElement { // attributes. // Create the applicaiton view. constructor(label, url) { super() // Set the shadow dom. this.attachShadow({ mode: 'open' }); /** The title of the image selector */ if (this.hasAttribute("label")) { label = this.getAttribute("label") } if (!label) { label = "" } /** The url of the selected image (can be undefied or empty string) */ if (this.hasAttribute("url")) { url = this.getAttribute("url") } if (!url) { url = "" } this.imageUrl = url // Innitialisation of the layout. this.shadowRoot.innerHTML = ` <style> #container{ color: var(--palette-text-primary); } .image-selector{ max-width: 200px; position: relative; } #delete-cover-image-btn { ${url.length == 0 ? "display:none;" : "display: block;"} z-index: 100; position: absolute; top: -10px; left: -16px; background-color: black; --paper-icon-button-ink-color: white; --iron-icon-fill-color: white; border-bottom: 1px solid var(--palette-divider); border-right: 1px solid var(--palette-divider); padding: 4px; width: 30px; height: 30px; --iron-icon-width: 24px; --iron-icon-height: 24px; } #drop-zone{ min-width: 180px; transition: background 0.2s ease,padding 0.8s linear; background-color: var(--palette-background-default); position: relative; border: 2px dashed var(--palette-divider); border-radius: 5px; min-height: 120px; display: flex; align-items: center; justify-content: center; padding: 5px; } </style> <div id="container"> <span id="label">${label}</span> <div id="drop-zone"> <div style="position: relative; display: flex;"> <paper-icon-button id="delete-cover-image-btn" icon="icons:close"></paper-icon-button> <img class="image-selector" src="${this.imageUrl}"> </img> </div> </div> </div> ` // Set image selector this.image = this.shadowRoot.querySelector(".image-selector") this.deleteBtn = this.shadowRoot.querySelector("#delete-cover-image-btn") // Delete the postser/cover image. this.shadowRoot.querySelector("#delete-cover-image-btn").onclick = () => { // Here I will ask the user for confirmation before actually delete the contact informations. let toast = ApplicationView.displayMessage( ` <style> #yes-no-picture-delete-box{ display: flex; flex-direction: column; } #yes-no-picture-delete-box globular-picture-card{ padding-bottom: 10px; } #yes-no-picture-delete-box div{ display: flex; padding-bottom: 10px; } </style> <div id="yes-no-picture-delete-box"> <div>Your about to remove ${label} image</div> <img style="max-height: 256px; object-fit: contain; width: 100%;" src="${this.imageUrl}"></img> <div>Is it what you want to do? </div> <div style="justify-content: flex-end;"> <paper-button raised id="yes-delete-picture">Yes</paper-button> <paper-button raised id="no-delete-picture">No</paper-button> </div> </div> `, 60 * 1000 // 60 sec... ); let yesBtn = document.querySelector("#yes-delete-picture") let noBtn = document.querySelector("#no-delete-picture") // On yes yesBtn.onclick = () => { // Call the function if defined... if (this.ondelete) { this.ondelete() } this.image.removeAttribute("src") this.deleteBtn.style.display = "none" toast.dismiss(); } noBtn.onclick = () => { toast.dismiss(); } } // The drag and drop event... let imageCoverDropZone = this.shadowRoot.querySelector("#drop-zone") imageCoverDropZone.ondragenter = (evt) => { evt.stopPropagation(); evt.preventDefault(); imageCoverDropZone.style.filter = "invert(10%)" } imageCoverDropZone.ondragleave = (evt) => { evt.preventDefault() imageCoverDropZone.style.filter = "" } imageCoverDropZone.ondragover = (evt) => { evt.stopPropagation(); evt.preventDefault(); } imageCoverDropZone.ondrop = (evt) => { evt.stopPropagation(); evt.preventDefault(); imageCoverDropZone.style.filter = "" if (evt.dataTransfer.files.length > 0) { var file = evt.dataTransfer.files[0], reader = new FileReader(); reader.onload = (event) => { let dataUrl = event.target.result this.deleteBtn.style.display = "block" this.imageUrl = dataUrl this.image.src = dataUrl if (this.onselectimage) { this.onselectimage(dataUrl) } }; reader.readAsDataURL(file); } else if (evt.dataTransfer.getData('files')) { // So here I will try to get the image from drop files from the file-explorer. let paths = JSON.parse(evt.dataTransfer.getData('files')) let domain = evt.dataTransfer.getData('domain') // keep track paths.forEach(path => { // so here I will read the file let globule = Model.getGlobule(domain) File__.getFile(globule, path, -1, -1, f => { generatePeerToken(globule, token => { let url = getUrl(globule) f.path.split("/").forEach(item => { let component = encodeURIComponent(item.trim()) if (component.length > 0) { url += "/" + component } }) url += "?application=" + Model.application; url += "&token=" + token createThumbmail(url, 500, dataUrl => { this.deleteBtn.style.display = "block" this.image.src = dataUrl this.imageUrl = dataUrl if (this.onselectimage) { this.onselectimage(dataUrl) } }) }) }, err => ApplicationView.displayMessage(err, 3000)) }) } } } setImageUrl(url) { this.image.src = url if (url.length > 0) { this.deleteBtn.style.display = "block" } else { this.deleteBtn.style.display = "none" } } getImageUrl() { return this.image.src } // That functions will create images from multiple images and set the result as // results. createMosaic(images, callback) { let grid = document.createElement("div") grid.classList.add("grid") grid.setAttribute("data-masonry", '{ "itemSelector": ".grid-item", "columnWidth": 50 }') if (images.length > 3) { grid.style.width = "300px"; } // must be in the layout... grid.style.backgroundColor = "black" var masonery = new Masonry(grid, {}) // Maximum of 9 image... images.forEach((img, index) => { if (index < 9) { img.classList.add("grid-item") img.style.maxWidth = "100px" img.style.maxHeight = "100px" grid.appendChild(img) } }) // Display message to the user and take screenshot of the grid... let toast = ApplicationView.displayMessage( ` <div style="display: flex; flex-direction: column;"> <div>Generate cover from content...</div> <div id="grid-div" style="background-color: black; min-height: 300px; margin-top: 20px;"></div> </div> `, 3000) // apprend the grid to the toast.el.querySelector("#grid-div").appendChild(grid) fireResize() // wait for the grid to be organized... setTimeout(() => { domtoimage.toJpeg(grid, { quality: 0.95 }) .then((dataUrl) => { ///grid.parentNode.style.height = grid.offsetHeight + "px" this.image.src = dataUrl; callback(dataUrl) }); }, 1000) } } customElements.define('globular-image-selector', ImageSelector)