const {fileURL} = require('./utils.js') const path = require('path') /* A specialization that ignores webview events and thus allows * webviews to get touch, mouse and wheel events. */ class DOMPadContainer extends DOMScatterContainer { capture(event) { if (event.target.tagName === 'WEBVIEW' || event.target.classList.contains('interactiveElement')) return false return super.capture(event) } } /* A wrapper for a webview that behaves like a virtual tablet browser. * Uses a DOMScatter to zoom and rotate the virtual browser window. * The position of buttons and the border size remain constant. */ class Pad { constructor(scatterContainer, { startScale=1.0, minScale=0.25, maxScale=10.5, autoBringToFront=true, url="https://www.iwm-tuebingen.de/www/index.html", hideOnStart=false, translatable=true, scalable=true, rotatable=true, movableX=true, movableY=true, rotationDegrees=null, rotation=null, onTransform=null, transformOrigin = 'center center', // extras which are in part needed x=0, y=0, width=null, height=null, resizable=false, } ={}) { this.x = x this.y = y this.url = url this.hideOnStart = hideOnStart this.width = width this.height = height this.minScale = minScale this.maxScale = maxScale this.scatterContainer = scatterContainer this.startScale = startScale this.scale = startScale this.scalable = scalable this.rotatable = rotatable this.rotationDegrees = this.startRotationDegrees this.transformOrigin = transformOrigin this.frame = document.createElement('div') this.frame.classList.add("pad") this.border = 50 / startScale Elements.setStyle(this.frame, { backgroundColor: "#333", position: "absolute", display: 'flex', width: this.width+"px", height: this.height+"px", top: 0, left: 0, // boxShadow: `10px 10px 10px rgba(0, 0, 0, 0.5)`, // borderRadius: '10px', overflow: "visible"}) document.body.appendChild( this.frame) this.web=document.createElement("webview") this.webBackground=document.createElement("div") this.webViewSnapshot=document.createElement("img") this.overlay = document.createElement('div') this.loadAnim = document.createElement("div") this.loadAnim.style.webkitAnimation= "spin 2s linear infinite" this.loadAnim.style.animation= "spin 2s linear infinite" this.loadAnim.style.position = "absolute" document.styleSheets[0].insertRule('\ @keyframes spin {\ from { transform: rotateZ(0deg); }\ to { transform: rotateZ(360deg); }\ }' ) this.overlay.appendChild(this.loadAnim) Elements.setStyle(this.web, { position: "absolute", overflow: "auto", border: "1px solid #fff" }) // this.web.classList.add("interactiveElement") // this.web.style.pointerEvents="none" Elements.setStyle(this.webBackground, { position: "absolute", overflow: "auto", background: "white" }) Elements.setStyle(this.overlay, { position: "absolute", overflow: "auto", background: "white", opacity: "0.8" }) Elements.setStyle(this.webViewSnapshot, { position: "absolute", overflow: "auto" }) let timeTouchStart = 0 this.overlayCaptureEvents = document.createElement('div') // overlay.style.background="white" this.overlayCaptureEvents.classList.add("interactiveElement") this.overlayCaptureEvents.addEventListener('touchmove',(e)=>{ e.preventDefault() e.stopPropagation() }) this.overlayCaptureEvents.addEventListener('pointerup',(e)=>{ e.preventDefault() e.stopPropagation() let p = {x:e.clientX, y:e.clientY} let webviewPosition = Points.fromPageToNode(this.web,p) let d = new Date() if(d.getTime()-timeTouchStart<150)this.web.sendInputEvent({type:'mouseUp', x: webviewPosition.x, y: webviewPosition.y, button:'left', clickCount: 1}) }) this.overlayCaptureEvents.addEventListener('pointerdown',(e)=>{ e.preventDefault() e.stopPropagation() this.scatter.bringToFront() let p = {x:e.clientX, y:e.clientY} let webviewPosition = Points.fromPageToNode(this.web,p) let d = new Date() timeTouchStart = d.getTime() this.web.sendInputEvent({type:'mouseDown', x: webviewPosition.x, y: webviewPosition.y, button:'left', clickCount: 1}) }) this.overlayCaptureEvents.addEventListener('pointermove',(e)=>{ if(e.pointerType!='mouse'){ let rotation = Angle.radian2degree(this.scatter.rotation); rotation = (rotation + 360) % 360; let r = Math.sqrt(Math.pow(e.movementX, 2) + Math.pow(e.movementY, 2)); let phi = Angle.radian2degree(Math.atan2(e.movementX, e.movementY)); phi = ((phi) + 630) % 360; let rot = ((rotation + 90) + 630) % 360; let diffAngle = ((0 + rot) + 360) % 360; let phiCorrected = (phi + diffAngle + 360) % 360; let deltaX = r * Math.cos(Angle.degree2radian(phiCorrected)); let deltaY = -r * Math.sin(Angle.degree2radian(phiCorrected)); this.web.executeJavaScript("window.scrollTo(scrollX+"+(-1*deltaX)+", scrollY+"+ (-1*deltaY)+")") } }) this.overlayCaptureEvents.addEventListener('mousewheel',(e)=>{ console.log("mousewheel",e.deltaY) // webview.sendInputEvent({type:'mouseWheel', x: 0, y: 0, deltaX: e.deltaX, deltaY: -e.deltaY, canScroll: true }) this.web.executeJavaScript("window.scrollTo(scrollX+"+e.deltaX+", scrollY+"+ e.deltaY+")") }) this.frame.appendChild(this.webBackground) this.frame.appendChild(this.web) this.frame.appendChild(this.webViewSnapshot) this.frame.appendChild(this.overlay) if(remote.getGlobal('multiUserMode'))this.frame.appendChild(this.overlayCaptureEvents) this.webViewSnapshot.style.visibility="hidden" this.web.src=url this.web.preload= path.join(__dirname, './preloadPad.js') this.closeButton = this.addButton("../assets/icons/svg/cross.svg", "close") this.backButton = this.addButton("../assets/icons/svg/left.svg", "go back") this.forwardButton = this.addButton("../assets/icons/svg/right.svg", "go forward") this.backButton.style.opacity = 0.5 this.forwardButton.style.opacity = 0.5 /*for (let callback of window.padLoadedHandler) { callback(this, url) }*/ this.web.addEventListener('new-window', (e) => { if(e.url.indexOf("youtube")>-1)return if (urlPadMap.has(e.url)) { let childPad = urlPadMap.get(e.url) childPad.scatter.moveTo(x, y) return childPad } let childPad = new Pad(this.scatterContainer, { x: this.scatter.position.x+100, y: this.scatter.position.y+100, url: e.url, width: this.scatter.width, height: this.scatter.height, scalable: true, rotatable: true}) urlPadMap.set(e.url, childPad) for(let callback of window.padLoadedHandler) { callback(childPad, url) } }) this.web.addEventListener('did-navigate', (e) => { this.enableButtons() }) this.web.addEventListener('dom-ready',()=>{ //if(this.url.indexOf('local')>-1)this.web.openDevTools() }) this.web.addEventListener('ipc-message', (e) => { if(e.channel='webviewPointerDown')this.scatter.bringToFront() }) this.web.addEventListener('did-start-loading', ()=>{ this.overlay.style.visibility="visible" let w = this.overlay.offsetWidth let h = this.overlay.offsetHeight console.log("did start loading",h,w) let animationSize = w{ this.overlay.style.visibility="hidden" }) /*this.backButton.addEventListener('click', ()=>{ if(this.web.canGoBack()) this.web.goBack() }) this.forwardButton.addEventListener('click', ()=>{ if(this.web.canGoForward()) this.web.goForward() }) this.closeButton.addEventListener('click', ()=>{ this.close() })*/ InteractionMapper.on('tap',this.backButton, e => { if(this.web.canGoBack()) this.web.goBack() }) InteractionMapper.on('tap',this.forwardButton, e => { if(this.web.canGoForward()) this.web.goForward() }) InteractionMapper.on('tap',this.closeButton, e => { this.close() }) this.scatter = new DOMScatter(this.frame, scatterContainer, { x: this.x, y: this.y, startScale: this.startScale, width: this.width, height: this.height, minScale: this.minScale, maxScale: this.maxScale, scalable: this.scalable, resizable: true, rotatable: this.rotatable}) let img=document.createElement("img") img.style.width = "70%" img.style.position = "absolute" img.style.bottom = "20%" img.style.right = "20%" img.style.pointerEvents="none" img.src = "../../assets/icons/png/flat/resize.png" this.scatter.resizeButton.appendChild(img) this.scatter.moveTo({x, y}) this.scatter.bringToFront() this.scatter.addTransformEventCallback((e) => { let newBorder = 50 / e.scale if (newBorder !== this.border) { this.border = newBorder this.layout() } }) this.layout() } rad2degree(alpha){ return alpha * 180 / Math.PI; } degree2rad(alpha){ return alpha * Math.PI / 180; } close() { // this.frame.style.display="none" this.frame.parentNode.removeChild(this.frame) urlPadMap.delete(this.url) } enableButtons() { this.backButton.style.opacity = (this.web.canGoBack()) ? 1 : 0.5 this.forwardButton.style.opacity = (this.web.canGoForward()) ? 1 : 0.5 } addButton(src, value) { let button = document.createElement("img") button.type = "image" button.className = "frameButton" button.style.position = "absolute" button.src = fileURL(src) button.value="close" button.draggable = false button.classList.add("interactiveElement") this.frame.appendChild(button) return button } layout() { let b = this.border let b2 = b * 2 let b8 = b / 8 let b25 = b / 25 let b15 = b / 15 this.scatter.resizeButton.style.width=b+"px" this.scatter.resizeButton.style.height=b+"px" Elements.setStyle(this.frame, { // borderRadius: b8 + "px", // boxShadow: `${b25}px ${b15}px ${b8}px rgba(0, 0, 0, 0.5)` }) let size = "calc(100% - " + (2*b+2) +"px)" Elements.setStyle(this.web, { width: size, height: size, margin: b+"px"}) Elements.setStyle(this.webViewSnapshot, { width: size, height: size, margin: b+"px"}) Elements.setStyle(this.webBackground, { width: size, height: size, margin: b+"px"}) Elements.setStyle(this.overlay, { width: size, height: size, margin: b+"px"}) Elements.setStyle(this.overlayCaptureEvents, { width: size, height: size, opacity: 0.0001, background: "white", margin: b+"px"}) Elements.setStyle(this.closeButton, { right: (b * 1.75) + "px", bottom: "0px", width: b+"px", height: b+"px"}) Elements.setStyle(this.backButton, { left: (b * 0.8) +"px", bottom: "0px", width: b+"px", height: b+"px"}) Elements.setStyle(this.forwardButton, { left: (this.border + (b * 0.8)) +"px", bottom: "0px", width: b+"px", height: b+"px"}) } } class PadFromElement { constructor(element, scatterContainer, { startScale=1.0, minScale=0.1, maxScale=1.0, autoBringToFront=true, translatable=true, scalable=true, rotatable=true, movableX=true, movableY=true, rotationDegrees=null, rotation=null, onTransform=null, transformOrigin = 'center center', // extras which are in part needed x=0, y=0, width=null, height=null, resizable=false, } ={}) { this.element = element this.x = x this.y = y this.width = width this.height = height this.minScale = minScale this.maxScale = maxScale this.scatterContainer = scatterContainer this.scale = startScale this.scalable = scalable this.rotatable = rotatable this.rotationDegrees = this.startRotationDegrees this.transformOrigin = transformOrigin this.frame = document.createElement('div') Elements.setStyle(this.frame, { width: this.width+"px", height: this.height+"px", backgroundColor: "#333", position: "fixed", top: 0, left: 0, overflow: "auto"}) this.closeButton = this.addButton("../assets/icons/svg/cross.svg", "close") document.body.appendChild( this.frame) this.border = 50 this.frame.appendChild(this.element) this.title = document.createElement("div") this.title.innerHTML = "Titel" this.title.style.color = "white" this.frame.appendChild(this.title) Elements.setStyle(this.title, { position: "absolute" }) // this.element.style.overflow = "auto" // this.element.style.position = "absolute" this.layout() this.closeButton.addEventListener('click', ()=>{ this.frame.style.display="none" }) this.scatter = new DOMScatter(this.frame, scatterContainer, { x: this.x, y: this.y, startScale: this.startScale, width: this.width, height: this.height, minScale: this.minScale, maxScale: this.maxScale, scalable: this.scalable, resizable: true, rotatable: this.rotatable}) this.scatter.bringToFront() this.scatter.addTransformEventCallback((e) => { let newBorder = 50 / e.scale if (newBorder !== this.border) { this.border = newBorder this.layout() } }) this.element.addEventListener('pointerdown', (e) => { this.scatter.bringToFront() }) } close() { // this.frame.style.display="none" this.frame.parentNode.removeChild(this.frame) urlPadMap.delete(this.url) } addButton(src, value) { let button = document.createElement("img") button.type = "image" button.className = "frameButton" button.style.position = "absolute" button.src = fileURL(src) button.value="close" button.draggable = false this.frame.appendChild(button) return button } layout() { let b = this.border let b2 = b * 2 let b8 = b / 8 let b25 = b / 25 let b15 = b / 15 this.scatter.resizeButton.style.width=b+"px" this.scatter.resizeButton.style.height=b+"px" Elements.setStyle(this.frame, { // borderRadius: b8 + "px", // boxShadow: `${b25}px ${b15}px ${b8}px rgba(0, 0, 0, 0.5)` }) let size = "calc(100% - " + (2*b+2) +"px)" Elements.setStyle(this.element, { width: size, height: size, top: b+"px", left: b+"px"}) Elements.setStyle(this.closeButton, { right: (b * 0.75) + "px", bottom: "0px", width: b+"px", height: b+"px"}) Elements.setStyle(this.title, { left: (b * 1.5) + "px", fontSize: (b * 0.8) + "px", top: (0.1)+"0px"}) } } module.exports = { Pad, DOMPadContainer, PadFromElement }