571 lines
18 KiB
JavaScript
571 lines
18 KiB
JavaScript
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<h ? w*0.5 : h*0.5
|
|
let animationRingWidth = animationSize*0.1;
|
|
|
|
this.loadAnim.style.border=animationRingWidth+"px solid #f3f3f3"
|
|
this.loadAnim.style.borderTop=animationRingWidth+"px solid #ffb18c"
|
|
this.loadAnim.style.borderRadius="50%"
|
|
this.loadAnim.style.height=animationSize-animationRingWidth*2+"px"
|
|
this.loadAnim.style.width=animationSize-animationRingWidth*2+"px"
|
|
this.loadAnim.style.top = h*0.25+"px"
|
|
this.loadAnim.style.left = w*0.25+"px"
|
|
w<h ? this.loadAnim.style.top = 0.5*(h-animationSize)+"px" : this.loadAnim.style.left = 0.5*(w-animationSize)+"px"
|
|
})
|
|
|
|
this.web.addEventListener('did-stop-loading', ()=>{
|
|
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 }
|