Added electron browser to allow snapshots of doctests that are stored in lib thumbnail subfolders.
13
bin/browser.sh
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ "$1" = "dist" ]
|
||||||
|
then
|
||||||
|
if [ -z "$2" ]
|
||||||
|
then
|
||||||
|
npm run package-all
|
||||||
|
else
|
||||||
|
npm run package-app-$2
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
electron . ./lib/index.html
|
||||||
|
fi
|
289
browser/browser.html
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar { display: none; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 22pt;
|
||||||
|
-webkit-tap-highlight-color: #ccc;
|
||||||
|
background-color: #DDD;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-hyphens: auto;
|
||||||
|
hyphens: auto;
|
||||||
|
/* https://davidwalsh.name/font-smoothing */
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progressBar {
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgb(165, 165, 196);
|
||||||
|
width: 0%;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info {
|
||||||
|
width: 100%;
|
||||||
|
margin: 3px;
|
||||||
|
font-size: 16px;
|
||||||
|
position: absolute;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title>
|
||||||
|
Browser
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="width: 100%; height: 100%; -webkit-app-region: no-drag">
|
||||||
|
<header id="header" style="-webkit-app-region: drag">
|
||||||
|
<div id="progressBar"></div>
|
||||||
|
<span id="info">Minimal Header</span>
|
||||||
|
</header>
|
||||||
|
<main></main>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
const { ipcRenderer } = require("electron")
|
||||||
|
|
||||||
|
let urls = new Set()
|
||||||
|
let favicons = new Set()
|
||||||
|
let progress = 0
|
||||||
|
|
||||||
|
info.innerHTML = window.location.href
|
||||||
|
|
||||||
|
function notify(url) {
|
||||||
|
if (urls.has(url)) return
|
||||||
|
console.log(url)
|
||||||
|
//header.innerHTML += `<p>${url}</p>`
|
||||||
|
urls.add(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
let colorExtractorCanvas = document.createElement('canvas')
|
||||||
|
let colorExtractorContext = colorExtractorCanvas.getContext('2d')
|
||||||
|
let colorExtractorImage = document.createElement('img')
|
||||||
|
|
||||||
|
function getColor(url, callback) {
|
||||||
|
colorExtractorImage.onload = function (e) {
|
||||||
|
let w = colorExtractorImage.width
|
||||||
|
let h = colorExtractorImage.height
|
||||||
|
colorExtractorCanvas.width = w
|
||||||
|
colorExtractorCanvas.height = h
|
||||||
|
let offset = Math.max(1, Math.round(0.00032 * w * h))
|
||||||
|
colorExtractorContext.drawImage(colorExtractorImage, 0, 0, w, h)
|
||||||
|
let data = colorExtractorContext.getImageData(0, 0, w, h).data
|
||||||
|
let pixels = {}
|
||||||
|
let d, add, sum
|
||||||
|
for (let i = 0; i < data.length; i += 4 * offset) {
|
||||||
|
d = Math.round(data[i] / 5) * 5 + ',' + Math.round(data[i + 1] / 5) * 5 + ',' + Math.round(data[i + 2] / 5) * 5
|
||||||
|
add = 1
|
||||||
|
sum = data[i] + data[i + 1] + data[i + 2]
|
||||||
|
// very dark or light pixels shouldn't be counted as heavily
|
||||||
|
if (sum < 310) {
|
||||||
|
add = 0.35
|
||||||
|
}
|
||||||
|
if (sum < 50) {
|
||||||
|
add = 0.01
|
||||||
|
}
|
||||||
|
if (data[i] > 210 || data[i + 1] > 210 || data[i + 2] > 210) {
|
||||||
|
add = 0.5 - (0.0001 * sum)
|
||||||
|
}
|
||||||
|
if (pixels[d]) {
|
||||||
|
pixels[d] = pixels[d] + add
|
||||||
|
} else {
|
||||||
|
pixels[d] = add
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// find the largest pixel set
|
||||||
|
let largestPixelSet = null
|
||||||
|
let ct = 0
|
||||||
|
for (let k in pixels) {
|
||||||
|
if (k === '255,255,255' || k === '0,0,0') {
|
||||||
|
pixels[k] *= 0.05
|
||||||
|
}
|
||||||
|
if (pixels[k] > ct) {
|
||||||
|
largestPixelSet = k
|
||||||
|
ct = pixels[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let res = largestPixelSet.split(',')
|
||||||
|
|
||||||
|
for (let i = 0; i < res.length; i++) {
|
||||||
|
res[i] = parseInt(res[i])
|
||||||
|
}
|
||||||
|
callback(res)
|
||||||
|
}
|
||||||
|
colorExtractorImage.src = url
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTextColor(bgColor) {
|
||||||
|
let output = runNetwork(bgColor)
|
||||||
|
if (output.black > 0.5) {
|
||||||
|
return 'black'
|
||||||
|
}
|
||||||
|
return 'white'
|
||||||
|
}
|
||||||
|
|
||||||
|
var runNetwork = function anonymous(input) {
|
||||||
|
var net = {
|
||||||
|
'layers': [{
|
||||||
|
'r': {},
|
||||||
|
'g': {},
|
||||||
|
'b': {}
|
||||||
|
}, {
|
||||||
|
'0': {
|
||||||
|
'bias': 14.176907520571566,
|
||||||
|
'weights': {
|
||||||
|
'r': -3.2764240497480652,
|
||||||
|
'g': -16.90247884718719,
|
||||||
|
'b': -2.9976364179397814
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'1': {
|
||||||
|
'bias': 9.086071102351246,
|
||||||
|
'weights': {
|
||||||
|
'r': -4.327474143397604,
|
||||||
|
'g': -15.780660155750773,
|
||||||
|
'b': 2.879230202567851
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'2': {
|
||||||
|
'bias': 22.274487339773476,
|
||||||
|
'weights': {
|
||||||
|
'r': -3.5830205067960965,
|
||||||
|
'g': -25.498384261673618,
|
||||||
|
'b': -6.998329189107962
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'black': {
|
||||||
|
'bias': 17.873962570788997,
|
||||||
|
'weights': {
|
||||||
|
'0': -15.542217788633987,
|
||||||
|
'1': -13.377152708685674,
|
||||||
|
'2': -24.52215186113144
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
'outputLookup': true,
|
||||||
|
'inputLookup': true
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 1; i < net.layers.length; i++) {
|
||||||
|
var layer = net.layers[i]
|
||||||
|
var output = {}
|
||||||
|
|
||||||
|
for (var id in layer) {
|
||||||
|
var node = layer[id]
|
||||||
|
var sum = node.bias
|
||||||
|
|
||||||
|
for (var iid in node.weights) {
|
||||||
|
sum += node.weights[iid] * input[iid]
|
||||||
|
}
|
||||||
|
output[id] = (1 / (1 + Math.exp(-sum)))
|
||||||
|
}
|
||||||
|
input = output
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyColors(backgroundColor, foregroundColor) {
|
||||||
|
console.log("applyColors", backgroundColor, foregroundColor)
|
||||||
|
progressBar.style.backgroundColor = backgroundColor
|
||||||
|
info.style.color = foregroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.on('title', (sender, title) => {
|
||||||
|
info.innerHTML = title
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on('favicons', (sender, urls) => {
|
||||||
|
console.log("favicons event", urls)
|
||||||
|
for (let url of urls) {
|
||||||
|
if (!favicons.has(url)) {
|
||||||
|
getColor(url, c => {
|
||||||
|
let cr = 'rgb(' + c[0] + ',' + c[1] + ',' + c[2] + ')'
|
||||||
|
let obj = {
|
||||||
|
r: c[0] / 255,
|
||||||
|
g: c[1] / 255,
|
||||||
|
b: c[2] / 255
|
||||||
|
}
|
||||||
|
let textclr = getTextColor(obj)
|
||||||
|
applyColors(cr, textclr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
favicons.add(url)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on('progress', (sender, amount) => {
|
||||||
|
console.log("progress event", amount)
|
||||||
|
if (amount > progress) {
|
||||||
|
progress = Math.min(amount, 1)
|
||||||
|
}
|
||||||
|
progressBar.style.width = Math.round(progress * 100) + '%'
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on('did-start-loading', (sender, url) => {
|
||||||
|
console.log('did-start-loading', url)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on('did-get-response-details', (sender, info) => {
|
||||||
|
let {
|
||||||
|
status, newURL, originalURL,
|
||||||
|
httpResponseCode,
|
||||||
|
requestMethod,
|
||||||
|
referrer,
|
||||||
|
headers,
|
||||||
|
resourceType
|
||||||
|
} = info
|
||||||
|
notify(newURL)
|
||||||
|
notify(originalURL)
|
||||||
|
//console.log('did-get-response-details', info)
|
||||||
|
})
|
||||||
|
ipcRenderer.on('did-get-redirect-request', (sender, info) => {
|
||||||
|
let { oldURL,
|
||||||
|
newURL,
|
||||||
|
isMainFrame,
|
||||||
|
httpResponseCode,
|
||||||
|
requestMethod,
|
||||||
|
referrer,
|
||||||
|
headers
|
||||||
|
} = info
|
||||||
|
notify(newURL)
|
||||||
|
notify(oldURL)
|
||||||
|
//console.log('did-get-response-details', info)
|
||||||
|
})
|
||||||
|
ipcRenderer.on('did-stop-loading', (sender, info) => {
|
||||||
|
//console.log('did-stop-loading', info)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
49
browser/carlo.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
const carlo = require('carlo');
|
||||||
|
const fse = require('fs-extra');
|
||||||
|
const urlExists = require('url-exists');
|
||||||
|
|
||||||
|
// command line arguments
|
||||||
|
let path = 'index.html'
|
||||||
|
process.argv.forEach((value, index, array) => {
|
||||||
|
if (index === 2) {
|
||||||
|
path = value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
// Launch the browser.
|
||||||
|
const opts = {}
|
||||||
|
|
||||||
|
// Set path to custom chrome
|
||||||
|
const chrome = `${__dirname}/../chrome/chrome.exe`
|
||||||
|
if (fse.pathExistsSync(chrome)) {
|
||||||
|
opts.executablePath = chrome
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch app
|
||||||
|
const app = await carlo.launch(opts)
|
||||||
|
|
||||||
|
// Terminate Node.js process on app window closing.
|
||||||
|
app.on('exit', () => process.exit())
|
||||||
|
|
||||||
|
// Tell carlo where your web files are located.
|
||||||
|
app.serveFolder(`${__dirname}/../`)
|
||||||
|
|
||||||
|
// Check if URL exists
|
||||||
|
urlExists('https://localhost:8443', async (error, exists) => {
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
console.info('Serve files via server')
|
||||||
|
app.serveOrigin('https://localhost:8443') // Optional
|
||||||
|
} else {
|
||||||
|
console.info('Serve files from file system')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose 'env' function in the web environment.
|
||||||
|
await app.exposeFunction('env', _ => process.env)
|
||||||
|
|
||||||
|
// Navigate to the main page of your app.
|
||||||
|
console.info('Starting carlo with', path)
|
||||||
|
await app.load(path)
|
||||||
|
})
|
||||||
|
})()
|
24
browser/i18n.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const electron = require('electron')
|
||||||
|
const fs = require('fs')
|
||||||
|
let loadedLanguage
|
||||||
|
let app = electron.app ? electron.app : electron.remote.app
|
||||||
|
|
||||||
|
module.exports = i18n
|
||||||
|
|
||||||
|
function i18n() {
|
||||||
|
if (fs.existsSync(path.join(__dirname, 'i18n', app.getLocale() + '.js'))) {
|
||||||
|
loadedLanguage = JSON.parse(fs.readFileSync(path.join(__dirname, 'i18n', app.getLocale() + '.js'), 'utf8'))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
loadedLanguage = JSON.parse(fs.readFileSync(path.join(__dirname, 'i18n', 'en.js'), 'utf8'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i18n.prototype.__ = function(phrase) {
|
||||||
|
let translation = loadedLanguage[phrase]
|
||||||
|
if (translation === undefined) {
|
||||||
|
translation = phrase
|
||||||
|
}
|
||||||
|
return translation
|
||||||
|
}
|
61
browser/i18n/de.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"edit": "Bearbeiten",
|
||||||
|
"undo": "Widerrufen",
|
||||||
|
"redo": "Wiederholen",
|
||||||
|
"cut": "Ausschneiden",
|
||||||
|
"copy": "Kopieren",
|
||||||
|
"paste": "Einsetzen",
|
||||||
|
"pasteandmatchstyle": "Einsetzen und Stil anpassen",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"selectall": "Alles auswählen",
|
||||||
|
"view": "Darstellung",
|
||||||
|
"reload": "Seite neu laden",
|
||||||
|
"forcereload": "Cache löschen und Seite neu laden",
|
||||||
|
"resetzoom": "Originalgröße",
|
||||||
|
"zoomin": "Vergrößern",
|
||||||
|
"zoomout": "Verkleinern",
|
||||||
|
"togglefullscreen": "Vollbildmodus umschalten",
|
||||||
|
"minimalpad": "Minimal-Pad",
|
||||||
|
"multiuserbrowser": "Mehrbenutzer-Browser",
|
||||||
|
"history": "Verlauf",
|
||||||
|
"back": "Zurück",
|
||||||
|
"forward": "Vorwärts",
|
||||||
|
"home": "Startseite",
|
||||||
|
"recentlyvisited": "Kürzlich besucht",
|
||||||
|
"bookmarks": "Lesezeichen",
|
||||||
|
"localfilesystem": "Lokales Dateisystem",
|
||||||
|
"testframes": "Testseiten",
|
||||||
|
"develop": "Entwickler",
|
||||||
|
"toggledevelopertools": "Webinformationen umschalten",
|
||||||
|
"openprocessmonitor": "Prozessmonitor öffnen",
|
||||||
|
"selectfolder": "Datenverzeichnis auswählen...",
|
||||||
|
"selectfolder.noadmin.ok": "OK",
|
||||||
|
"selectfolder.noadmin.message": "Keine ausreichenden Berechtigungen",
|
||||||
|
"selectfolder.noadmin.detail": "Um das Datenverzeichnis zu ändern, muss der IWM Browser mit Administrator-Berechtigungen gestartet werden.",
|
||||||
|
"selectfolder.warning.next": "Weiter",
|
||||||
|
"selectfolder.warning.cancel": "Abbrechen",
|
||||||
|
"selectfolder.warning.message": "Datenverzeichnis vorhanden",
|
||||||
|
"selectfolder.warning.detail": "Ihr IWM Browser besitzt bereits ein (verlinktes) Datenverzeichnis. Wenn Sie fortfahren, wird das alte Verzeichnis gesichert und ein neues wird erstellt.",
|
||||||
|
"selectfolder.select.title": "Datenverzeichnis wählen",
|
||||||
|
"selectfolder.select.buttonLabel": "Auswählen",
|
||||||
|
"selectfolder.samefolder.ok": "OK",
|
||||||
|
"selectfolder.samefolder.message": "Ungültiges Datenverzeichnis",
|
||||||
|
"selectfolder.samefolder.detail.same": "Das alte Datenverzeichnis darf nicht als neues Verzeichnis ausgewählt werden.",
|
||||||
|
"selectfolder.samefolder.detail.within": "Das neue Datenverzeichnis darf sich nicht innerhalb des alten Verzeichnisses befinden.",
|
||||||
|
"selectfolder.info.ok": "OK",
|
||||||
|
"selectfolder.info.message": "Link auf Datenverzeichnis erstellt",
|
||||||
|
"selectfolder.info.detail": "Der IWM Browser verwendet nun den Ordner \"${0}\" als neues Datenverzeichnis.",
|
||||||
|
"startserver": "Starte Server",
|
||||||
|
"stopserver": "Stoppe Server",
|
||||||
|
"runloadtests": "Starte Ladetests",
|
||||||
|
"window": "Fenster",
|
||||||
|
"close": "Fenster schließen",
|
||||||
|
"minimize": "Im Dock ablegen",
|
||||||
|
"zoom": "Zoomen",
|
||||||
|
"front": "Alle nach vorne bringen",
|
||||||
|
"screenshot": "Bildschirmfoto erstellen",
|
||||||
|
"help": "Hilfe",
|
||||||
|
"iwm": "Leibniz-Institut für Wissensmedien",
|
||||||
|
"about": "Über IWM Browser",
|
||||||
|
"quit": "IWM Browser beenden"
|
||||||
|
}
|
61
browser/i18n/en.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"edit": "Edit",
|
||||||
|
"undo": "Undo",
|
||||||
|
"redo": "Redo",
|
||||||
|
"cut": "Cut",
|
||||||
|
"copy": "Copy",
|
||||||
|
"paste": "Paste",
|
||||||
|
"pasteandmatchstyle": "Paste and Match Style",
|
||||||
|
"delete": "Delete",
|
||||||
|
"selectall": "Select all",
|
||||||
|
"view": "View",
|
||||||
|
"reload": "Reload",
|
||||||
|
"forcereload": "Force Reload",
|
||||||
|
"resetzoom": "Actual size",
|
||||||
|
"zoomin": "Zoom in",
|
||||||
|
"zoomout": "Zoom out",
|
||||||
|
"togglefullscreen": "Toggle Full Screen",
|
||||||
|
"minimalpad": "Minimal Pad",
|
||||||
|
"multiuserbrowser": "Multi-User Browser",
|
||||||
|
"history": "History",
|
||||||
|
"back": "Back",
|
||||||
|
"forward": "Forward",
|
||||||
|
"home": "Home",
|
||||||
|
"recentlyvisited": "Recently Visited",
|
||||||
|
"bookmarks": "Bookmarks",
|
||||||
|
"localfilesystem": "Local Filesystem",
|
||||||
|
"testframes": "Test Frames",
|
||||||
|
"develop": "Develop",
|
||||||
|
"toggledevelopertools": "Toggle Developer Tools",
|
||||||
|
"openprocessmonitor": "Open Process Monitor",
|
||||||
|
"selectfolder": "Select data folder...",
|
||||||
|
"selectfolder.noadmin.ok": "OK",
|
||||||
|
"selectfolder.noadmin.message": "Insufficient permissions",
|
||||||
|
"selectfolder.noadmin.detail": "To change the data directory, the IWM Browser must be started with administrator privileges.",
|
||||||
|
"selectfolder.warning.next": "Next",
|
||||||
|
"selectfolder.warning.cancel": "Cancel",
|
||||||
|
"selectfolder.warning.message": "Data folder exists",
|
||||||
|
"selectfolder.warning.detail": "Your IWM Browser already has a (linked) data directory. If you continue, the old directory is backed up and a new one is created.",
|
||||||
|
"selectfolder.select.title": "Select data folder",
|
||||||
|
"selectfolder.select.buttonLabel": "Select",
|
||||||
|
"selectfolder.samefolder.ok": "OK",
|
||||||
|
"selectfolder.samefolder.message": "Invalid data folder",
|
||||||
|
"selectfolder.samefolder.detail.same": "The old data directory cannot be selected as the new directory.",
|
||||||
|
"selectfolder.samefolder.detail.within": "The new data directory cannot be inside the old directory.",
|
||||||
|
"selectfolder.info.ok": "OK",
|
||||||
|
"selectfolder.info.message": "Created link to data folder",
|
||||||
|
"selectfolder.info.detail": "The IWM Browser now uses the folder \"${0}\" as the new data folder.",
|
||||||
|
"startserver": "Start Server",
|
||||||
|
"stopserver": "Stop Server",
|
||||||
|
"runloadtests": "Run Load Tests",
|
||||||
|
"window": "Window",
|
||||||
|
"close": "Close",
|
||||||
|
"minimize": "Minimize",
|
||||||
|
"zoom": "Zoom",
|
||||||
|
"front": "Bring All to Front",
|
||||||
|
"screenshot": "Make Screenshot",
|
||||||
|
"help": "Help",
|
||||||
|
"iwm": "Leibniz-Institut für Wissensmedien",
|
||||||
|
"about": "About IWM Browser",
|
||||||
|
"quit": "Quit IWM Browser"
|
||||||
|
}
|
578
browser/main.js
Normal file
@ -0,0 +1,578 @@
|
|||||||
|
/* globals require, __dirname, process */
|
||||||
|
/*eslint no-console: ["error", { allow: ["log"] }]*/
|
||||||
|
|
||||||
|
const { app, BrowserWindow, BrowserView, ipcMain, dialog, shell } = require('electron')
|
||||||
|
const electronLocalshortcut = require('electron-localshortcut')
|
||||||
|
//const electron = require('electron')
|
||||||
|
const os = require('os')
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const { URL } = require('url')
|
||||||
|
const Store = require('./store.js')
|
||||||
|
const { prettyPrint } = require('html')
|
||||||
|
|
||||||
|
// Use this constant to start the application in kiosk-mode or in development mode
|
||||||
|
const DEVELOPMENT = true
|
||||||
|
// true: Dev-Tools are open
|
||||||
|
// false (KIOSK-Mode): No application switcher, no menu, no taskbar (or dock on a mac), shortcuts are working
|
||||||
|
|
||||||
|
global.multiUserMode = true
|
||||||
|
global.errorCount = 0
|
||||||
|
global.stopTestsOnError = false
|
||||||
|
global.jsonData = { value: null }
|
||||||
|
// UO: Experimental feature using Native Windows
|
||||||
|
global.useBrowserView = false
|
||||||
|
global.useMinimalPad = true
|
||||||
|
global.menu = null
|
||||||
|
global.observeTraffic = false
|
||||||
|
|
||||||
|
// Keep a global reference of the window object, if you don't, the window will
|
||||||
|
// be closed automatically when the JavaScript object is garbage collected.
|
||||||
|
let win
|
||||||
|
let browsers = new Map() // url: BrowserWindow
|
||||||
|
|
||||||
|
const store = new Store({
|
||||||
|
// We'll call our data file 'user-preferences'
|
||||||
|
configName: 'user-preferences',
|
||||||
|
defaults: {
|
||||||
|
url: `file://${__dirname}/index.html`,
|
||||||
|
devTools: DEVELOPMENT,
|
||||||
|
multiUserBrowser: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function createWindow() {
|
||||||
|
|
||||||
|
if (global.observeTraffic) {
|
||||||
|
const {session} = require('electron')
|
||||||
|
session.defaultSession.webRequest.onCompleted((details) => {
|
||||||
|
console.log("onCompleted", details.url)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let { screen } = require('electron')
|
||||||
|
|
||||||
|
let bounds = store.get('storedBounds')
|
||||||
|
? store.get('storedBounds')
|
||||||
|
: screen.getPrimaryDisplay().bounds
|
||||||
|
|
||||||
|
// let displays = screen.getAllDisplays()
|
||||||
|
// let externalDisplay = null
|
||||||
|
|
||||||
|
// externalDisplay = displays[displays.length-1]
|
||||||
|
// const {width, height} =displays[displays.length-1].workAreaSize
|
||||||
|
|
||||||
|
// externalDisplay = displays[0]
|
||||||
|
// const {width, height} =displays[0].workAreaSize
|
||||||
|
|
||||||
|
win = new BrowserWindow({
|
||||||
|
x: bounds.x,
|
||||||
|
y: bounds.y,
|
||||||
|
width: bounds.width,
|
||||||
|
height: bounds.height,
|
||||||
|
fullscreenable: true,
|
||||||
|
fullscreen: !DEVELOPMENT,
|
||||||
|
title: 'IWM Browser',
|
||||||
|
show: false,
|
||||||
|
kiosk: !DEVELOPMENT,
|
||||||
|
acceptFirstMouse: true,
|
||||||
|
webPreferences: {
|
||||||
|
webSecurity: false,
|
||||||
|
allowRunningInsecureContent: true,
|
||||||
|
nodeIntegration: true,
|
||||||
|
webviewTag: true,
|
||||||
|
nativeWindowOpen: true,
|
||||||
|
devTools: true,
|
||||||
|
preload: path.join(__dirname, './preload.js')
|
||||||
|
},
|
||||||
|
icon: path.join(__dirname, 'assets/icons/png/64x64.png')
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports.win = win
|
||||||
|
|
||||||
|
let url = store.get('url')
|
||||||
|
if (process.argv.length > 2) {
|
||||||
|
let path = process.argv[2]
|
||||||
|
url = `file://${__dirname}/../${path}`
|
||||||
|
console.log('Using process.argv[2]', url)
|
||||||
|
}
|
||||||
|
console.log('Using', url)
|
||||||
|
win.maximize()
|
||||||
|
|
||||||
|
// BAD: All other methods don't work (like ensureFileSync, fileExists...)
|
||||||
|
try {
|
||||||
|
let settings = require('./settings.json')
|
||||||
|
console.log('Using settings', `file://${__dirname}/${settings.url}`)
|
||||||
|
win.loadURL(`file://${__dirname}/${settings.url}`)
|
||||||
|
} catch (ex) {
|
||||||
|
win.loadURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { webContents } = win
|
||||||
|
//
|
||||||
|
// if (process.platform === 'win32' && win.isKiosk()) {
|
||||||
|
// webContents.on('did-finish-load', function() {
|
||||||
|
// webContents.executeJavaScript('document.body.style.cursor = "none";')
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Add the app menu
|
||||||
|
let menu = require('./menu.js')
|
||||||
|
global.menu = menu
|
||||||
|
|
||||||
|
// Add global shortcuts
|
||||||
|
// Esc quits the app
|
||||||
|
electronLocalshortcut.register('Esc', () => {
|
||||||
|
app.quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Command (Mac) or Control (Win) + K toggles the Kiosk mode
|
||||||
|
electronLocalshortcut.register('CommandOrControl+K', () => {
|
||||||
|
if (win) {
|
||||||
|
win.setKiosk(!win.isKiosk())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Show if its ready.
|
||||||
|
win.once('ready-to-show', () => {
|
||||||
|
webContents.send('preparePads')
|
||||||
|
win.show()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Clear cache
|
||||||
|
webContents.session.clearCache(() => console.log('Cache cleared'))
|
||||||
|
|
||||||
|
// Open dev tools when in development mode
|
||||||
|
if (store.get('devTools')) {
|
||||||
|
webContents.openDevTools({ mode: 'right' })
|
||||||
|
} else {
|
||||||
|
webContents.closeDevTools()
|
||||||
|
}
|
||||||
|
|
||||||
|
webContents.on('devtools-opened', () => {
|
||||||
|
store.set('devTools', true)
|
||||||
|
})
|
||||||
|
|
||||||
|
webContents.on('devtools-closed', () => {
|
||||||
|
store.set('devTools', false)
|
||||||
|
})
|
||||||
|
|
||||||
|
webContents.on('did-navigate', (event, url) => {
|
||||||
|
menu.setHistoryStatus()
|
||||||
|
})
|
||||||
|
|
||||||
|
/* UO: At this point we have no access to the event or link position*/
|
||||||
|
|
||||||
|
webContents.on('new-window', (event, url, frameName, disposition, options, additionalFeatures) => {
|
||||||
|
console.log('new-window', global.multiUserMode)
|
||||||
|
if (global.multiUserMode) {
|
||||||
|
event.preventDefault()
|
||||||
|
webContents.send('newPad', url, options.x, options.y)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// WORKAROUND: On windows, if the app was set to fullscreen, the menubar is not hidden
|
||||||
|
if (win.isKiosk()) {
|
||||||
|
win.setMenuBarVisibility(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
win.on('focus', event => {
|
||||||
|
menu.focus()
|
||||||
|
})
|
||||||
|
|
||||||
|
win.on('blur', event => {
|
||||||
|
menu.blur()
|
||||||
|
})
|
||||||
|
|
||||||
|
win.on('enter-full-screen', () => {
|
||||||
|
win.setMenuBarVisibility(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
win.on('leave-full-screen', () => {
|
||||||
|
win.setMenuBarVisibility(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
win.on('enter-html-full-screen', () => {
|
||||||
|
win.setMenuBarVisibility(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
win.on('leave-html-full-screen', () => {
|
||||||
|
win.setMenuBarVisibility(true)
|
||||||
|
})
|
||||||
|
win.on('close', () => {
|
||||||
|
store.set('storedBounds', win.getBounds())
|
||||||
|
})
|
||||||
|
|
||||||
|
// Emitted when the window is closed.
|
||||||
|
win.on('closed', () => {
|
||||||
|
app.quit()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// When work makes progress, show the progress bar
|
||||||
|
function onProgress(progress) {
|
||||||
|
// Use values 0 to 1, or -1 to hide the progress bar
|
||||||
|
try {
|
||||||
|
win.setProgressBar(progress || -1) // Progress bar works on all platforms
|
||||||
|
} catch (e) {
|
||||||
|
if (DEVELOPMENT) console.log(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function trySend(target, ...args) {
|
||||||
|
try {
|
||||||
|
target.send(...args)
|
||||||
|
} catch (e) {
|
||||||
|
if (DEVELOPMENT) console.log(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openBrowserView(url, x, y) {
|
||||||
|
const useMinBrowser = false // Change this to switch between Min and a custom browser
|
||||||
|
|
||||||
|
const minURL = 'file:///Users/uo/devel/min/index.html'
|
||||||
|
const browserURL = `file://${__dirname}/browser.html`
|
||||||
|
let width = 640
|
||||||
|
let height = 1200
|
||||||
|
let [winWidth, winHeight] = win.getSize()
|
||||||
|
if (x + width > winWidth) {
|
||||||
|
x = winWidth - width
|
||||||
|
}
|
||||||
|
if (y + height > winHeight) {
|
||||||
|
y = winHeight - height
|
||||||
|
}
|
||||||
|
console.log('open browser view')
|
||||||
|
let browser = new BrowserWindow({
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
minWidth: 320,
|
||||||
|
minHeight: 350,
|
||||||
|
titleBarStyle: useMinBrowser ? 'hidden-inset' : 'hidden',
|
||||||
|
frame: process.platform !== 'win32'
|
||||||
|
})
|
||||||
|
let browserContents = browser.webContents
|
||||||
|
browser.setAlwaysOnTop(true)
|
||||||
|
if (useMinBrowser) {
|
||||||
|
browserContents.on('did-finish-load', event => {
|
||||||
|
console.log('did-finish-load', browserContents.getURL())
|
||||||
|
|
||||||
|
browserContents.executeJavaScript(
|
||||||
|
'Object.values(window.webviews.elementMap).map(obj => obj.src)',
|
||||||
|
result => {
|
||||||
|
console.log(
|
||||||
|
'window.webviews',
|
||||||
|
result,
|
||||||
|
url,
|
||||||
|
result.indexOf(url)
|
||||||
|
)
|
||||||
|
if (result.indexOf(url) == -1) {
|
||||||
|
console.log('Adding tab')
|
||||||
|
browserContents.send('addTab', { url })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
browser.loadURL(minURL)
|
||||||
|
} else {
|
||||||
|
console.log('Loading', browserURL)
|
||||||
|
browser.loadURL(browserURL)
|
||||||
|
let view = new BrowserView({
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: false,
|
||||||
|
devTools: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
//browserContents.openDevTools({mode: 'right'})
|
||||||
|
|
||||||
|
browser.setBrowserView(view)
|
||||||
|
view.setBounds({ x: 0, y: 24, width: width, height: height - 24 })
|
||||||
|
view.setAutoResize({ width: true, height: true })
|
||||||
|
|
||||||
|
let viewContents = view.webContents
|
||||||
|
let progress = 0
|
||||||
|
|
||||||
|
viewContents.on('page-title-set', event => {
|
||||||
|
console.log('page-title-set', event)
|
||||||
|
})
|
||||||
|
viewContents.on('page-favicon-updated', (event, favicons) => {
|
||||||
|
//console.log("page-favicon-updated", event, favicons)
|
||||||
|
trySend(browserContents, 'favicons', favicons)
|
||||||
|
})
|
||||||
|
|
||||||
|
viewContents.on('did-start-loading', event => {
|
||||||
|
onProgress(0)
|
||||||
|
trySend(browserContents, 'progress', 0)
|
||||||
|
trySend(browserContents, 'did-start-loading')
|
||||||
|
//let senderURL = event.sender.getURL() || url
|
||||||
|
//console.log('did-start-loading', senderURL)
|
||||||
|
})
|
||||||
|
viewContents.on(
|
||||||
|
'did-get-response-details',
|
||||||
|
(
|
||||||
|
event,
|
||||||
|
status,
|
||||||
|
newURL,
|
||||||
|
originalURL,
|
||||||
|
httpResponseCode,
|
||||||
|
requestMethod,
|
||||||
|
referrer,
|
||||||
|
headers,
|
||||||
|
resourceType
|
||||||
|
) => {
|
||||||
|
trySend(browserContents, 'did-get-response-details', {
|
||||||
|
status,
|
||||||
|
newURL,
|
||||||
|
originalURL,
|
||||||
|
httpResponseCode,
|
||||||
|
requestMethod,
|
||||||
|
referrer,
|
||||||
|
headers,
|
||||||
|
resourceType
|
||||||
|
})
|
||||||
|
progress += 0.01
|
||||||
|
onProgress(progress)
|
||||||
|
trySend(browserContents, 'progress', progress)
|
||||||
|
//console.log('did-get-response-details', newURL)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
viewContents.on(
|
||||||
|
'did-get-redirect-request',
|
||||||
|
(
|
||||||
|
event,
|
||||||
|
oldURL,
|
||||||
|
newURL,
|
||||||
|
isMainFrame,
|
||||||
|
httpResponseCode,
|
||||||
|
requestMethod,
|
||||||
|
referrer,
|
||||||
|
headers
|
||||||
|
) => {
|
||||||
|
trySend(browserContents, 'did-get-redirect-request', {
|
||||||
|
oldURL,
|
||||||
|
newURL,
|
||||||
|
isMainFrame,
|
||||||
|
httpResponseCode,
|
||||||
|
requestMethod,
|
||||||
|
referrer,
|
||||||
|
headers
|
||||||
|
})
|
||||||
|
//console.log('did-get-redirect-request', newURL)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
viewContents.on('did-stop-loading', event => {
|
||||||
|
//console.log('did-stop-loading', event.sender.getURL())
|
||||||
|
trySend(browserContents, 'did-stop-loading')
|
||||||
|
})
|
||||||
|
viewContents.on('did-finish-load', event => {
|
||||||
|
//console.log('did-finish-load', event.sender.getURL())
|
||||||
|
progress = 1
|
||||||
|
onProgress(progress)
|
||||||
|
trySend(browserContents, 'progress', progress)
|
||||||
|
})
|
||||||
|
viewContents.on('dom-ready', event => {
|
||||||
|
if (progress < 0.5) {
|
||||||
|
progress = 0.5
|
||||||
|
onProgress(progress)
|
||||||
|
trySend(browserContents, 'progress', progress)
|
||||||
|
}
|
||||||
|
viewContents.executeJavaScript('document.title', result => {
|
||||||
|
trySend(browserContents, 'title', result)
|
||||||
|
})
|
||||||
|
|
||||||
|
//console.log('dom-ready', event.sender.getURL())
|
||||||
|
})
|
||||||
|
|
||||||
|
viewContents.on('new-window', function (event, url) {
|
||||||
|
event.preventDefault()
|
||||||
|
console.log('new-window')
|
||||||
|
openBrowserView(url, x, y)
|
||||||
|
})
|
||||||
|
|
||||||
|
viewContents.loadURL(url)
|
||||||
|
}
|
||||||
|
browsers.set(url, browser)
|
||||||
|
browser.on('closed', e => {
|
||||||
|
for (let [url, browser] of browsers.entries()) {
|
||||||
|
if (browser == e.sender) {
|
||||||
|
browsers.delete(url)
|
||||||
|
console.log('removed browser view', url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UO: Experimental call. Opens a Min Browser window or a limited window with a browser view
|
||||||
|
ipcMain.on('loadBrowserView', (e, opts = {}) => {
|
||||||
|
let { url, x, y } = opts
|
||||||
|
openBrowserView(url, x, y)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('multiUserMode', (e, opts = {}) => {
|
||||||
|
global.multiUserMode = opts
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('padContainerLoaded', e => {
|
||||||
|
win.webContents.send('padContainerAvailable')
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('createScreenshot', (e, opts = {}) => {
|
||||||
|
opts = Object.assign(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
name: `iwmbrowser-${new Date()
|
||||||
|
.toISOString()
|
||||||
|
.replace(/:/g, '-')}.png`,
|
||||||
|
path: os.tmpdir()
|
||||||
|
},
|
||||||
|
opts
|
||||||
|
)
|
||||||
|
|
||||||
|
win.webContents.capturePage(image => {
|
||||||
|
if (image) {
|
||||||
|
let file = path.join(opts.path, opts.name)
|
||||||
|
fs.writeFile(file, image.toPNG(), err => {
|
||||||
|
if (err) {
|
||||||
|
//throw err
|
||||||
|
} else {
|
||||||
|
console.log(`Screenshot saved: ${file}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('directoryListing', (e, opts = {}) => {
|
||||||
|
let { directory, files, folders } = opts
|
||||||
|
console.log("directoryListing", opts)
|
||||||
|
try {
|
||||||
|
let listing = fs.readdirSync(directory)
|
||||||
|
let result = { directory, files: [], folders: [] }
|
||||||
|
for (let name of listing) {
|
||||||
|
if (name.startsWith('.'))
|
||||||
|
continue
|
||||||
|
let fullPath = path.join(directory, name)
|
||||||
|
let stat = fs.lstatSync(fullPath)
|
||||||
|
if (files && stat.isFile()) {
|
||||||
|
if (typeof files == 'string' && !files.endsWith(files))
|
||||||
|
continue
|
||||||
|
result.files.push(name)
|
||||||
|
}
|
||||||
|
if (folders && stat.isDirectory())
|
||||||
|
result.folders.push(name)
|
||||||
|
}
|
||||||
|
e.sender.send('directoryListing', result)
|
||||||
|
} catch (err) {
|
||||||
|
let args = { directory, errorMessage: err.message}
|
||||||
|
e.sender.send('directoryListingError', args)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('createTextfile', (e, opts = {}) => {
|
||||||
|
opts = Object.assign(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
name: `iwmbrowser-${new Date()
|
||||||
|
.toISOString()
|
||||||
|
.replace(/:/g, '-')}.txt`,
|
||||||
|
path: os.tmpdir(),
|
||||||
|
text: ''
|
||||||
|
},
|
||||||
|
opts
|
||||||
|
)
|
||||||
|
let file = path.join(opts.path, opts.name)
|
||||||
|
fs.writeFile(file, opts.text, err => {
|
||||||
|
if (err) {
|
||||||
|
//throw err
|
||||||
|
} else {
|
||||||
|
console.log(`Textfile saved: ${file}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('error', e => {
|
||||||
|
console.log('Received error notification')
|
||||||
|
global.errorCount += 1
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('openExternal', (e, url=null) => {
|
||||||
|
console.log('Received openExternal', url)
|
||||||
|
if (url) {
|
||||||
|
shell.openExternal(url)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('save', (e, opts = {}) => {
|
||||||
|
let { url, html, saveAs, action } = opts
|
||||||
|
// url must absolute URL
|
||||||
|
let urlObj = new URL(url)
|
||||||
|
let pathname = urlObj.pathname
|
||||||
|
if (saveAs) {
|
||||||
|
pathname = dialog.showSaveDialog(win, { title: 'Save as:', defaultPath: pathname })
|
||||||
|
if (typeof pathname == 'undefined')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
console.log("Saving", pathname, action)
|
||||||
|
html = prettyPrint(html, { indent_size: 4 });
|
||||||
|
fs.writeFileSync(pathname, html, 'utf-8')
|
||||||
|
if (saveAs) {
|
||||||
|
let normalized = pathname.replace(/\\/g, '/')
|
||||||
|
e.sender.send('savedAs', {url: `file://${normalized}`, action})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.warn('Failed to save the file', pathname)
|
||||||
|
e.sender.send('saveFailed', pathname)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// This method will be called when Electron has finished
|
||||||
|
// initialization and is ready to create browser windows.
|
||||||
|
// Some APIs can only be used after this event occurs.
|
||||||
|
app.on('ready', createWindow)
|
||||||
|
|
||||||
|
// Quit when all windows are closed.
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
// On macOS it is common for applications and their menu bar
|
||||||
|
// to stay active until the user quits explicitly with Cmd + Q
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on(
|
||||||
|
'select-client-certificate',
|
||||||
|
(event, webContents, url, list, callback) => {
|
||||||
|
console.log('select-client-certificate', url, list)
|
||||||
|
event.preventDefault()
|
||||||
|
ipc.once('client-certificate-selected', (event, item) => {
|
||||||
|
console.log('selected:', item)
|
||||||
|
callback(item)
|
||||||
|
})
|
||||||
|
mainWindow.webContents.send('select-client-certificate', list)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
app.on(
|
||||||
|
'certificate-error',
|
||||||
|
(event, webContents, url, error, certificate, callback) => {
|
||||||
|
console.log('certificate-error', url)
|
||||||
|
event.preventDefault()
|
||||||
|
const result = true // TODO: do real validation here
|
||||||
|
callback(result)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
// On macOS it's common to re-create a window in the app when the
|
||||||
|
// dock icon is clicked and there are no other windows open.
|
||||||
|
if (win === null) {
|
||||||
|
createWindow()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
store: store
|
||||||
|
}
|
629
browser/menu.js
Normal file
@ -0,0 +1,629 @@
|
|||||||
|
/* globals require, process */
|
||||||
|
/*eslint no-console: ["error", { allow: ["log", "error"] }] */
|
||||||
|
|
||||||
|
const { Menu, app, shell, dialog } = require('electron')
|
||||||
|
const fs = require('fs')
|
||||||
|
const fse = require('fs-extra')
|
||||||
|
const os = require('os')
|
||||||
|
const path = require('path')
|
||||||
|
const { openProcessManager } = require('electron-process-manager')
|
||||||
|
const main = require('./main.js')
|
||||||
|
|
||||||
|
let { thumbnail } = require('./utils.js')
|
||||||
|
const loadTests = require('./test.js')
|
||||||
|
const i18n = new (require('./i18n.js'))()
|
||||||
|
|
||||||
|
function selectURL(url) {
|
||||||
|
url = url.replace(/\\/g, '/')
|
||||||
|
console.log('selectURL', url)
|
||||||
|
main.win.loadURL(url)
|
||||||
|
main.store.set('url', url)
|
||||||
|
}
|
||||||
|
|
||||||
|
function findItems(key, value) {
|
||||||
|
let items = []
|
||||||
|
|
||||||
|
for (let i = 0; i < menu.items.length; i++) {
|
||||||
|
for (let j = 0; j < menu.items[i].submenu.items.length; j++) {
|
||||||
|
let item = menu.items[i].submenu.items[j]
|
||||||
|
if (item[key] === value) {
|
||||||
|
items.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
function findItem(key, value) {
|
||||||
|
return findItems(key, value)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleBookmarks(bookmark) {
|
||||||
|
let items = findItems('class', 'bookmark')
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
items[i].checked = false
|
||||||
|
}
|
||||||
|
|
||||||
|
bookmark.checked = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkBookmark(url) {
|
||||||
|
let items = findItems('url', url)
|
||||||
|
if (items.length === 1) {
|
||||||
|
toggleBookmarks(items[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setHistoryStatus() {
|
||||||
|
const historyBack = findItem('id', 'history-back')
|
||||||
|
historyBack.enabled = main.win.webContents.canGoBack()
|
||||||
|
|
||||||
|
const historyForward = findItem('id', 'history-forward')
|
||||||
|
historyForward.enabled = main.win.webContents.canGoForward()
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSelectDataFolderDialog(focusedWindow) {
|
||||||
|
dialog.showOpenDialog(
|
||||||
|
{
|
||||||
|
title: i18n.__('selectfolder.select.title'),
|
||||||
|
buttonLabel: i18n.__('selectfolder.select.buttonLabel'),
|
||||||
|
properties: ['openDirectory', 'createDirectory', 'noResolveAliases', 'treatPackageAsDirectory']
|
||||||
|
},
|
||||||
|
(filePaths) => {
|
||||||
|
if (filePaths && filePaths.length === 1) {
|
||||||
|
const varPath = path.join(__dirname, '../var')
|
||||||
|
|
||||||
|
// Check if the same folder was used
|
||||||
|
if (filePaths[0].startsWith(varPath)) {
|
||||||
|
const same = filePaths[0] === varPath
|
||||||
|
|
||||||
|
dialog.showMessageBox(
|
||||||
|
{
|
||||||
|
type: 'error',
|
||||||
|
icon: path.join(__dirname, '../assets/icons/png/512x512-empty.png'),
|
||||||
|
buttons: [i18n.__('selectfolder.samefolder.ok')],
|
||||||
|
defaultId: 0,
|
||||||
|
message: i18n.__('selectfolder.samefolder.message'),
|
||||||
|
detail: same
|
||||||
|
? i18n.__('selectfolder.samefolder.detail.same')
|
||||||
|
: i18n.__('selectfolder.samefolder.detail.within'),
|
||||||
|
cancelId: 0
|
||||||
|
},
|
||||||
|
(response) => {
|
||||||
|
showSelectDataFolderDialog(focusedWindow)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Backup
|
||||||
|
if (fse.pathExistsSync(varPath)) {
|
||||||
|
const varPathBackup = findNextVarFolder()
|
||||||
|
// Rename old var folder or link
|
||||||
|
fse.renameSync(varPath, varPathBackup)
|
||||||
|
} else {
|
||||||
|
// BUG: Workaround because pathExistsSync return false on existing symbolic links with a missing target
|
||||||
|
fse.removeSync(varPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new symlink
|
||||||
|
main.store.set('dataFolder', filePaths[0])
|
||||||
|
fs.symlinkSync(filePaths[0], varPath, 'dir')
|
||||||
|
|
||||||
|
dialog.showMessageBox(
|
||||||
|
{
|
||||||
|
type: 'info',
|
||||||
|
icon: path.join(__dirname, '../assets/icons/png/link.png'),
|
||||||
|
buttons: [i18n.__('selectfolder.info.ok')],
|
||||||
|
defaultId: 0,
|
||||||
|
message: i18n.__('selectfolder.info.message'),
|
||||||
|
detail: i18n.__('selectfolder.info.detail').replace(/\$\{0\}/, filePaths[0]),
|
||||||
|
cancelId: 0
|
||||||
|
},
|
||||||
|
(response) => {
|
||||||
|
if (focusedWindow) focusedWindow.reload()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNextVarFolder() {
|
||||||
|
let exists = true
|
||||||
|
let counter = 0
|
||||||
|
|
||||||
|
while (exists) {
|
||||||
|
counter++
|
||||||
|
exists = fse.pathExistsSync(path.join(__dirname, `../var${counter}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.join(__dirname, `../var${counter}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function showFolderBrowser(focusedWindow) {
|
||||||
|
const varPath = path.join(__dirname, '../var')
|
||||||
|
const varPathExists = fse.pathExistsSync(varPath)
|
||||||
|
if (varPathExists) {
|
||||||
|
dialog.showMessageBox(
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
icon: path.join(__dirname, '../assets/icons/png/512x512-empty.png'),
|
||||||
|
buttons: [i18n.__('selectfolder.warning.next'), i18n.__('selectfolder.warning.cancel')],
|
||||||
|
defaultId: 1,
|
||||||
|
message: i18n.__('selectfolder.warning.message'),
|
||||||
|
detail: i18n.__('selectfolder.warning.detail'),
|
||||||
|
cancelId: 1
|
||||||
|
},
|
||||||
|
(response) => {
|
||||||
|
if (response === 0) {
|
||||||
|
showSelectDataFolderDialog(focusedWindow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
showSelectDataFolderDialog(focusedWindow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = [
|
||||||
|
{
|
||||||
|
label: i18n.__('edit'),
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
role: 'undo',
|
||||||
|
label: i18n.__('undo')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'redo',
|
||||||
|
label: i18n.__('redo')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'cut',
|
||||||
|
label: i18n.__('cut')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'copy',
|
||||||
|
label: i18n.__('copy')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'paste',
|
||||||
|
label: i18n.__('paste')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'pasteandmatchstyle',
|
||||||
|
label: i18n.__('pasteandmatchstyle')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'delete',
|
||||||
|
label: i18n.__('delete')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'selectall',
|
||||||
|
label: i18n.__('selectall')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('view'),
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: i18n.__('reload'),
|
||||||
|
accelerator: 'CmdOrCtrl+R',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
focusedWindow.webContents.setVisualZoomLevelLimits(1, 1)
|
||||||
|
focusedWindow.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'forcereload',
|
||||||
|
label: i18n.__('forcereload'),
|
||||||
|
accelerator: 'CmdOrCtrl+Shift+R',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
focusedWindow.webContents.session.clearCache(() => console.log('Cache cleared'))
|
||||||
|
|
||||||
|
focusedWindow.webContents.setVisualZoomLevelLimits(1, 1)
|
||||||
|
focusedWindow.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'resetzoom',
|
||||||
|
label: i18n.__('resetzoom')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'zoomin',
|
||||||
|
label: i18n.__('zoomin')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'zoomout',
|
||||||
|
label: i18n.__('zoomout')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'togglefullscreen',
|
||||||
|
label: i18n.__('togglefullscreen'),
|
||||||
|
accelerator: process.platform === 'darwin' ? 'Cmd+Ctrl+F' : 'F11',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
focusedWindow.setFullScreen(!focusedWindow.isFullScreen())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('multiuserbrowser'),
|
||||||
|
accelerator: 'CmdOrCtrl+M',
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: true,
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
main.store.set('multiUserBrowser', item.checked)
|
||||||
|
global.multiUserMode = item.checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('minimalpad'),
|
||||||
|
accelerator: 'CmdOrCtrl+p',
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: true,
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
main.store.set('minimalPad', item.checked)
|
||||||
|
global.useMinimalPad = item.checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('history'),
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
id: 'history-back',
|
||||||
|
label: i18n.__('back'),
|
||||||
|
accelerator: 'CmdOrCtrl+Left',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
main.win.webContents.goBack()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'history-forward',
|
||||||
|
label: i18n.__('forward'),
|
||||||
|
accelerator: 'CmdOrCtrl+Right',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
main.win.webContents.goForward()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('home'),
|
||||||
|
accelerator: 'CmdOrCtrl+Up',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
main.win.webContents.goToIndex(0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('recentlyvisited'),
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('bookmarks'),
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: i18n.__('localfilesystem'),
|
||||||
|
class: 'bookmark',
|
||||||
|
type: 'checkbox',
|
||||||
|
url: `file://${__dirname}/../index.html`,
|
||||||
|
accelerator: 'CmdOrCtrl+L',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
selectURL(item.url)
|
||||||
|
toggleBookmarks(item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('testframes'),
|
||||||
|
class: 'bookmark',
|
||||||
|
type: 'checkbox',
|
||||||
|
url: `file://${__dirname}/../index.html?test`,
|
||||||
|
accelerator: 'CmdOrCtrl+T',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
selectURL(item.url)
|
||||||
|
toggleBookmarks(item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'localhost',
|
||||||
|
label: 'https://localhost:8443',
|
||||||
|
class: 'bookmark',
|
||||||
|
type: 'checkbox',
|
||||||
|
enabled: false,
|
||||||
|
url: 'https://localhost:8443/index.html',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
selectURL(item.url)
|
||||||
|
toggleBookmarks(item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'localhost',
|
||||||
|
label: 'https://localhost:3000',
|
||||||
|
class: 'bookmark',
|
||||||
|
type: 'checkbox',
|
||||||
|
enabled: true,
|
||||||
|
url: 'https://localhost:3000/index.html',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
selectURL(item.url)
|
||||||
|
toggleBookmarks(item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// label: 'http://tornado.iwm-kmrc.de:8000',
|
||||||
|
// class: 'bookmark',
|
||||||
|
// type: 'checkbox',
|
||||||
|
// url: 'http://tornado.iwm-kmrc.de:8000/index.html',
|
||||||
|
// click(item, focusedWindow) {
|
||||||
|
// selectURL(item.url)
|
||||||
|
// toggleBookmarks(item)
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
label: 'http://rousseau.iwm-kmrc.de/index.html',
|
||||||
|
class: 'bookmark',
|
||||||
|
type: 'checkbox',
|
||||||
|
url: 'http://rousseau.iwm-kmrc.de/index.html',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
selectURL(item.url)
|
||||||
|
toggleBookmarks(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('develop'),
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
id: 'toggledevelopertools',
|
||||||
|
label: i18n.__('toggledevelopertools'),
|
||||||
|
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) focusedWindow.webContents.toggleDevTools()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('openprocessmonitor'),
|
||||||
|
accelerator: process.platform === 'darwin' ? 'Alt+Command+P' : 'Ctrl+Shift+P',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
openProcessManager()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('selectfolder'),
|
||||||
|
accelerator: process.platform === 'darwin' ? 'Alt+Command+D' : 'Ctrl+Shift+D',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
var exec = require('child_process').exec
|
||||||
|
exec('NET SESSION', function (err, so, se) {
|
||||||
|
const admin = se.length === 0 ? true : false
|
||||||
|
|
||||||
|
if (admin) {
|
||||||
|
showFolderBrowser(focusedWindow)
|
||||||
|
} else {
|
||||||
|
dialog.showMessageBox({
|
||||||
|
type: 'error',
|
||||||
|
icon: path.join(__dirname, '../assets/icons/png/512x512-empty.png'),
|
||||||
|
buttons: [i18n.__('selectfolder.noadmin.ok')],
|
||||||
|
message: i18n.__('selectfolder.noadmin.message'),
|
||||||
|
detail: i18n.__('selectfolder.noadmin.detail')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
showFolderBrowser(focusedWindow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'startserver',
|
||||||
|
label: i18n.__('startserver'),
|
||||||
|
accelerator: process.platform === 'darwin' ? 'Alt+Command+S' : 'Ctrl+Shift+S',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
const { server } = require('../server/main.js')
|
||||||
|
server.start()
|
||||||
|
item.visible = false
|
||||||
|
findItem('id', 'stopserver').visible = true
|
||||||
|
findItem('id', 'localhost').enabled = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'stopserver',
|
||||||
|
label: i18n.__('stopserver'),
|
||||||
|
accelerator: process.platform === 'darwin' ? 'Alt+Command+S' : 'Ctrl+Shift+S',
|
||||||
|
visible: false,
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
const { server } = require('../server/main.js')
|
||||||
|
server.stop()
|
||||||
|
item.visible = false
|
||||||
|
findItem('id', 'startserver').visible = true
|
||||||
|
findItem('id', 'localhost').enabled = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('runloadtests'),
|
||||||
|
accelerator: process.platform === 'darwin' ? 'Alt+Command+L' : 'Ctrl+Shift+L',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
loadTests(focusedWindow)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Aktualisiere Tüsch POIs',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
const UpdatePOI = require('../dev/tuesch/bin/menu/update-pois.js')
|
||||||
|
UpdatePOI.update('./dev/tuesch')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'window',
|
||||||
|
label: i18n.__('window'),
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
role: 'close',
|
||||||
|
label: i18n.__('close')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'minimize',
|
||||||
|
label: i18n.__('minimize')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'zoom',
|
||||||
|
label: i18n.__('zoom')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'front',
|
||||||
|
label: i18n.__('front')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.__('screenshot'),
|
||||||
|
accelerator: 'CmdOrCtrl+S',
|
||||||
|
async click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
await focusedWindow.webContents.capturePage().then((image) => {
|
||||||
|
let screenshotFile = path.join(os.tmpdir(), 'screenshot.png')
|
||||||
|
|
||||||
|
console.log('image captured', screenshotFile)
|
||||||
|
|
||||||
|
let url = focusedWindow.webContents.getURL()
|
||||||
|
if (url.startsWith('file://')) {
|
||||||
|
let normalized = path.normalize(url).replace('.html', '.png')
|
||||||
|
screenshotFile = normalized.replace('file:', '')
|
||||||
|
let thumbnailFile = screenshotFile.replace('index.png', 'thumbnail.png')
|
||||||
|
if (url.endsWith('index.html')) {
|
||||||
|
thumbnailFile = screenshotFile.replace('index.png', 'thumbnail.png')
|
||||||
|
} else {
|
||||||
|
let folderName = path.dirname(screenshotFile)
|
||||||
|
let baseName = path.basename(screenshotFile)
|
||||||
|
thumbnailFile = path.join(folderName, 'thumbnails', baseName)
|
||||||
|
}
|
||||||
|
fs.writeFile(thumbnailFile, thumbnail(image), (err) => {
|
||||||
|
if (err) {
|
||||||
|
throw err
|
||||||
|
} else {
|
||||||
|
console.log(`Thumbnail written to ${thumbnailFile}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fs.writeFile(screenshotFile, image.toPNG(), (err) => {
|
||||||
|
if (err) {
|
||||||
|
throw err
|
||||||
|
} else {
|
||||||
|
console.log(`Screenshot written to ${screenshotFile}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'help',
|
||||||
|
label: i18n.__('help'),
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: i18n.__('iwm'),
|
||||||
|
click() {
|
||||||
|
shell.openExternal('https://www.iwm-tuebingen.de')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
const name = app.getName()
|
||||||
|
template.unshift({
|
||||||
|
label: name,
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
role: 'about',
|
||||||
|
label: i18n.__('about')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'quit',
|
||||||
|
label: i18n.__('quit')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const menu = Menu.buildFromTemplate(template)
|
||||||
|
Menu.setApplicationMenu(menu)
|
||||||
|
|
||||||
|
checkBookmark(main.store.get('url'))
|
||||||
|
setHistoryStatus()
|
||||||
|
|
||||||
|
function focus() {
|
||||||
|
findItem('id', 'forcereload').enabled = true
|
||||||
|
findItem('id', 'togglefullscreen').enabled = true
|
||||||
|
findItem('id', 'toggledevelopertools').enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function blur() {
|
||||||
|
findItem('id', 'forcereload').enabled = false
|
||||||
|
findItem('id', 'togglefullscreen').enabled = false
|
||||||
|
findItem('id', 'toggledevelopertools').enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
menu,
|
||||||
|
setHistoryStatus,
|
||||||
|
focus,
|
||||||
|
blur
|
||||||
|
}
|
171
browser/package.js
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/* globals require, __dirname, process */
|
||||||
|
/*eslint no-console: ["error", { allow: ["log", "info", "warn", "error"] }]*/
|
||||||
|
|
||||||
|
const fse = require('fs-extra')
|
||||||
|
const path = require('path')
|
||||||
|
const packager = require('electron-packager')
|
||||||
|
const rebuild = require('electron-rebuild')
|
||||||
|
|
||||||
|
// Arguments
|
||||||
|
//----------------------
|
||||||
|
let folder = null
|
||||||
|
|
||||||
|
if (process.argv.length < 3) {
|
||||||
|
console.error('Missing command line parameter "folder"!')
|
||||||
|
process.exit(1)
|
||||||
|
} else {
|
||||||
|
folder = process.argv[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
//----------------------
|
||||||
|
let settings = null
|
||||||
|
const root = path.join(__dirname, '../')
|
||||||
|
|
||||||
|
try {
|
||||||
|
settings = require(`../${folder}/settings.json`)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Cannot read settings.json in folder, does it exist?')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read settings
|
||||||
|
//----------------------
|
||||||
|
const title = `--- Build "${settings.name || settings.id}" ---`
|
||||||
|
const line = Array(title.length + 1).join('-')
|
||||||
|
console.info(line)
|
||||||
|
console.info(title)
|
||||||
|
console.info(line)
|
||||||
|
|
||||||
|
// Using folder
|
||||||
|
//----------------------
|
||||||
|
const tempFolder = path.join(root, 'temp', settings.id)
|
||||||
|
console.log(`Using folder ${tempFolder}`)
|
||||||
|
|
||||||
|
// Delete temp folder (when last run aborted)
|
||||||
|
fse.removeSync(tempFolder)
|
||||||
|
console.log(`Folder ${tempFolder} deleted`)
|
||||||
|
|
||||||
|
// Create folder
|
||||||
|
fse.ensureDirSync(tempFolder)
|
||||||
|
console.log(`Folder ${tempFolder} created`)
|
||||||
|
|
||||||
|
// Create subfolders
|
||||||
|
const defaultFolders = ['assets', 'browser', 'css', 'lib', 'node_modules', 'server']
|
||||||
|
console.log(`The folders ${defaultFolders.join(', ')} are included by default`)
|
||||||
|
const folders = new Set(settings.browser.folders.concat(defaultFolders))
|
||||||
|
for (let folder of folders) {
|
||||||
|
console.log(`Copy folder ${folder}`)
|
||||||
|
const folderOld = path.join(root, folder)
|
||||||
|
const folderNew = path.join(root, 'temp', settings.id, folder)
|
||||||
|
|
||||||
|
fse.copySync(folderOld, folderNew)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write package.json
|
||||||
|
//----------------------
|
||||||
|
let json = {
|
||||||
|
name: settings.id,
|
||||||
|
productName: settings.name || settings.id,
|
||||||
|
version: settings.version,
|
||||||
|
main: 'browser/main.js',
|
||||||
|
dependencies: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and write dependencies
|
||||||
|
const packageJson = fse.readJsonSync(path.join(root, 'package.json'))
|
||||||
|
Object.assign(json.dependencies, packageJson.dependencies)
|
||||||
|
|
||||||
|
// Add browser dependencies
|
||||||
|
if (settings.browser.dependencies) {
|
||||||
|
let dependencies = {}
|
||||||
|
for (let dependency of settings.browser.dependencies) {
|
||||||
|
dependencies[dependency] = '*'
|
||||||
|
}
|
||||||
|
Object.assign(json.dependencies, dependencies)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Create package.json')
|
||||||
|
fse.writeJsonSync(path.join(tempFolder, 'package.json'), json, {spaces: 4})
|
||||||
|
|
||||||
|
// Write URL to settings.json
|
||||||
|
//----------------------
|
||||||
|
console.log('Write URL to browser/settings.json')
|
||||||
|
fse.writeJsonSync(path.join(tempFolder, 'browser/settings.json'), {url: `../${folder}/index.html`}, {spaces: 4})
|
||||||
|
|
||||||
|
// Build with electron-packager
|
||||||
|
//----------------------
|
||||||
|
console.log('Start electron-packager')
|
||||||
|
packager({
|
||||||
|
dir: `./temp/${settings.id}`,
|
||||||
|
arch: 'x64',
|
||||||
|
asar: false,
|
||||||
|
overwrite: true,
|
||||||
|
out: './dist/electron',
|
||||||
|
icon: './assets/icons/icon',
|
||||||
|
platform: settings.browser.platform || ['darwin', 'win32'],
|
||||||
|
prune: false,
|
||||||
|
afterCopy: [(buildPath, electronVersion, platform, arch, callback) => {
|
||||||
|
console.log(`Rebuild Node.js modules for ${platform}...`)
|
||||||
|
rebuild.rebuild({buildPath, electronVersion, arch})
|
||||||
|
.then(() => {
|
||||||
|
console.log(`...Node.js modules for ${platform} rebuilded`)
|
||||||
|
callback()
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`Error: ${error}`)
|
||||||
|
callback(error)
|
||||||
|
});
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
.then(appPaths => {
|
||||||
|
console.log('electron-packager finished')
|
||||||
|
|
||||||
|
// Delete temp folder
|
||||||
|
//----------------------
|
||||||
|
fse.removeSync(tempFolder)
|
||||||
|
console.log(`Folder ${tempFolder} deleted`)
|
||||||
|
|
||||||
|
// Write data folders
|
||||||
|
//----------------------
|
||||||
|
if (settings.browser.data) {
|
||||||
|
console.log('Copy data folders')
|
||||||
|
for (let folder of settings.browser.data) {
|
||||||
|
for (let appPath of appPaths) {
|
||||||
|
console.log(`Copy folder ${folder} to ${appPath}`)
|
||||||
|
const source = path.join(root, folder)
|
||||||
|
const target = path.join(getResourcesPath(root, appPath), folder)
|
||||||
|
fse.copySync(source, target, {
|
||||||
|
dereference: true,
|
||||||
|
filter: item => {
|
||||||
|
if (settings.browser.dataExtensions && fse.lstatSync(item).isFile() && !settings.browser.dataExtensions.includes(path.extname(item).substring(1).toLowerCase())) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finished
|
||||||
|
//----------------------
|
||||||
|
console.info('Finished')
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error)
|
||||||
|
})
|
||||||
|
|
||||||
|
function getResourcesPath(root, appPath) {
|
||||||
|
|
||||||
|
let resourcesPath = ""
|
||||||
|
|
||||||
|
if (/darwin/.test(appPath) || /mas/.test(appPath)) {
|
||||||
|
resourcesPath = path.join(root, appPath, `${json.productName}.app/Contents/Resources/app`)
|
||||||
|
} else if (/win32/.test(appPath) || /linux/.test(appPath)) {
|
||||||
|
resourcesPath = path.join(root, appPath, 'resources/app')
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourcesPath
|
||||||
|
}
|
570
browser/pad.js
Normal file
@ -0,0 +1,570 @@
|
|||||||
|
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 }
|
3540
browser/padAccordion.js
Normal file
2814
browser/padAccordionOld.js
Normal file
1380
browser/padMinimal.js
Normal file
231
browser/preload.js
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
const { fileURL, loadScript, hideCursor, showCursor } = require('./utils.js')
|
||||||
|
let { remote } = require('electron')
|
||||||
|
|
||||||
|
const webFrame = require('electron').webFrame
|
||||||
|
|
||||||
|
// UO: Disable unintended zoom of fullscreen page if user wants to zoom
|
||||||
|
// only parts like Eyevisit info cards.
|
||||||
|
console.log('Disable pinch zoom', webFrame)
|
||||||
|
webFrame.setVisualZoomLevelLimits(1, 1)
|
||||||
|
|
||||||
|
let padContainer = null
|
||||||
|
let hideCursorTimeout = null
|
||||||
|
let debug = false
|
||||||
|
let urlPadMap = new Map()
|
||||||
|
|
||||||
|
window.urlPadMap = urlPadMap
|
||||||
|
window.padLoadedHandler = []
|
||||||
|
window.nodeDirname = __dirname
|
||||||
|
|
||||||
|
function pageSize() {
|
||||||
|
var w = window,
|
||||||
|
d = document,
|
||||||
|
e = d.documentElement,
|
||||||
|
g = d.getElementsByTagName('body')[0],
|
||||||
|
width = w.innerWidth || e.clientWidth || g.clientWidth,
|
||||||
|
height = w.innerHeight || e.clientHeight || g.clientHeight
|
||||||
|
|
||||||
|
return [width, height]
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = pageSize()
|
||||||
|
let pageWidth = size[0]
|
||||||
|
let pageHeight = size[1]
|
||||||
|
|
||||||
|
/* Open a new at x, y position. */
|
||||||
|
function openPad(url, x, y) {
|
||||||
|
console.log('openPad')
|
||||||
|
if (remote.getGlobal('useBrowserView')) {
|
||||||
|
return ipcRenderer.send('loadBrowserView', { url, x, y })
|
||||||
|
}
|
||||||
|
const { Pad } = require('./pad.js')
|
||||||
|
const { minimalPad } = require('./padMinimal.js')
|
||||||
|
pad = null
|
||||||
|
if (urlPadMap.has(url)) {
|
||||||
|
let pad = urlPadMap.get(url)
|
||||||
|
pad.scatter.bringToFront()
|
||||||
|
/*TweenMax.to(pad.frame, 0.5, {
|
||||||
|
boxShadow: "0 0 25px 5px white", onComplete: () => {
|
||||||
|
TweenMax.to(pad.frame, 0.5, { boxShadow: "none" })
|
||||||
|
}
|
||||||
|
})*/
|
||||||
|
TweenMax.to(pad.frame, 0.2, {
|
||||||
|
scale: '1.01',
|
||||||
|
onComplete: () => {
|
||||||
|
TweenMax.to(pad.frame, 0.2, { scale: '1' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// pad.scatter.moveTo(x, y)
|
||||||
|
return pad
|
||||||
|
}
|
||||||
|
y + 1600 > pageHeight ? (y = pageHeight - 1600) : (y = y)
|
||||||
|
if (remote.getGlobal('useMinimalPad')) {
|
||||||
|
pad = new minimalPad(padContainer, {
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
url: url,
|
||||||
|
tabbedView: true,
|
||||||
|
hasTtitleBar: false,
|
||||||
|
hideOnStart: false,
|
||||||
|
startScale: 1,
|
||||||
|
width: 1000,
|
||||||
|
height: 1500,
|
||||||
|
scalable: true,
|
||||||
|
rotatable: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (!remote.getGlobal('useMinimalPad')) {
|
||||||
|
pad = new Pad(padContainer, {
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
url: url,
|
||||||
|
hideOnStart: false,
|
||||||
|
startScale: 1,
|
||||||
|
width: 1000,
|
||||||
|
height: 1500,
|
||||||
|
scalable: true,
|
||||||
|
rotatable: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
urlPadMap.set(url, pad)
|
||||||
|
for (let callback of window.padLoadedHandler) {
|
||||||
|
callback(pad, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.padLoadedHandler.push((pad, url) => {
|
||||||
|
console.log('Add specific behavior')
|
||||||
|
})
|
||||||
|
|
||||||
|
/* According to https://electron.atom.io/docs/faq/
|
||||||
|
"I can not use jQuery/RequireJS/Meteor/AngularJS in Electron" we
|
||||||
|
have to rename the symbols in the page before including other libraries.
|
||||||
|
Remember to use nodeRequire after this point.
|
||||||
|
*/
|
||||||
|
window.nodeRequire = require
|
||||||
|
delete window.require
|
||||||
|
delete window.exports
|
||||||
|
delete window.module
|
||||||
|
|
||||||
|
/* Create a DOMPadContainer, i.e. a special DOMScatterContainer, as a wrapper
|
||||||
|
of the document body.
|
||||||
|
*/
|
||||||
|
window.addEventListener('load', e => {
|
||||||
|
console.log('preloading')
|
||||||
|
// ../iwmlib/dist/iwmlib.3rdparty.js
|
||||||
|
// loadScript('../iwmlib/lib/3rdparty/preload.js', () => {
|
||||||
|
loadScript('../../iwmlib/dist/iwmlib.3rdparty.preload.js', () => {
|
||||||
|
//console.log("../iwmlib/dist/iwmlib.3rdparty.js loaded")
|
||||||
|
console.log('greensock loaded')
|
||||||
|
// loadScript('../iwmlib/dist/iwmlib.js', () => {
|
||||||
|
loadScript('../../iwmlib/dist/iwmlib.js', () => {
|
||||||
|
console.log('../iwmlib/dist/iwmlib.js loaded')
|
||||||
|
|
||||||
|
/* const { Pad, DOMPadContainer } = nodeRequire('./pad.js')
|
||||||
|
padContainer = new DOMPadContainer(document.body)
|
||||||
|
|
||||||
|
window.nodePadContainer = padContainer
|
||||||
|
ipcRenderer.send('padContainerLoaded') */
|
||||||
|
|
||||||
|
/*Register a handler for mousemove events. Hide the cursor a few
|
||||||
|
*seconds after the last move event.
|
||||||
|
*/
|
||||||
|
document.body.addEventListener('mousemove', e => {
|
||||||
|
showCursor()
|
||||||
|
clearTimeout(hideCursorTimeout)
|
||||||
|
hideCursorTimeout = setTimeout(hideCursor, 3000)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
loadScript('../iwmlib/dist/iwmlib.pixi.js', () => {
|
||||||
|
console.log('../iwmlib/dist/iwmlib.pixi.js loaded')
|
||||||
|
const DebugApp = require('./debug.js')
|
||||||
|
let debugApp = new DebugApp(document.body)
|
||||||
|
debugApp.setup()
|
||||||
|
debugApp.run()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Register a handler for all click events and check whether the link target
|
||||||
|
opens a new window. If the link target is blank, prevent the default behavior
|
||||||
|
and create a Pad scatter object instead.
|
||||||
|
*/
|
||||||
|
window.addEventListener('click', e => {
|
||||||
|
let node = e.target
|
||||||
|
let url = ''
|
||||||
|
let target = ''
|
||||||
|
|
||||||
|
let multiUserMode = false // remote.getGlobal('multiUserMode') // DOMScatter && remote.getGlobal('multiUserMode')
|
||||||
|
|
||||||
|
// console.log("click", multiUserMode, remote.getGlobal('multiUserMode'))
|
||||||
|
if (multiUserMode) {
|
||||||
|
while (node !== null) {
|
||||||
|
if (node.tagName === 'A' || node.tagName === 'a') {
|
||||||
|
if (node.target instanceof SVGAnimatedString) {
|
||||||
|
url = node.href.baseVal
|
||||||
|
target = node.target.baseVal
|
||||||
|
} else {
|
||||||
|
url = node.href
|
||||||
|
target = node.target
|
||||||
|
}
|
||||||
|
if (target === '_blank') {
|
||||||
|
e.preventDefault()
|
||||||
|
openPad(url, e.clientX, e.clientY)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node = node.parentNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Register a handler for contextmenu events and check whether the link target
|
||||||
|
// opens a new window. If the link target is blank, prevent the default behavior
|
||||||
|
and show a popup menu with the option to open a pad instead.
|
||||||
|
*/
|
||||||
|
window.addEventListener('contextmenu', e => {
|
||||||
|
let node = e.target
|
||||||
|
let url = null
|
||||||
|
if (remote.getGlobal('multiUserMode')) {
|
||||||
|
while (node !== null) {
|
||||||
|
if (node.tagName === 'A' || node.tagName === 'a') {
|
||||||
|
if (node.target instanceof SVGAnimatedString) {
|
||||||
|
url = node.href.baseVal
|
||||||
|
} else {
|
||||||
|
url = node.href
|
||||||
|
}
|
||||||
|
e.preventDefault()
|
||||||
|
let point = { x: e.clientX, y: e.clientY }
|
||||||
|
PopupMenu.open(
|
||||||
|
{
|
||||||
|
'Open new Pad': () => openPad(url, e.clientX, e.clientY)
|
||||||
|
},
|
||||||
|
point,
|
||||||
|
{ fontSize: '0.8em' }
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node = node.parentNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Special error handling if the rendere process sends a error notification
|
||||||
|
log this error on the main console.
|
||||||
|
*/
|
||||||
|
let { Console } = require('console')
|
||||||
|
let debugConsole = new Console(process.stdout, process.stderr)
|
||||||
|
|
||||||
|
let { ipcRenderer } = require('electron')
|
||||||
|
window.addEventListener('error', event => {
|
||||||
|
debugConsole.error('PAGE ERROR', event.error, event.filename)
|
||||||
|
debugConsole.error('open', window.location.href)
|
||||||
|
ipcRenderer.send('error', 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on('newPad', (e, data) => {
|
||||||
|
openPad(data, 0, 0)
|
||||||
|
})
|
83
browser/preloadPad.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
let { remote } = require('electron')
|
||||||
|
let { ipcRenderer } = require('electron')
|
||||||
|
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const webFrame = require('electron').webFrame
|
||||||
|
console.log('Disable pinch zoom', webFrame)
|
||||||
|
webFrame.setVisualZoomLevelLimits(1, 1)
|
||||||
|
|
||||||
|
window.nodePath = path
|
||||||
|
|
||||||
|
window.nodeDirname = __dirname
|
||||||
|
|
||||||
|
window.nodeRequire = require
|
||||||
|
delete window.require
|
||||||
|
delete window.exports
|
||||||
|
delete window.module
|
||||||
|
|
||||||
|
window.padLoadedHandler = []
|
||||||
|
|
||||||
|
let pointerCounter = 0
|
||||||
|
|
||||||
|
window.addEventListener('pointerdown', (e) => {
|
||||||
|
//e.preventDefault()
|
||||||
|
// console.log("ipcRenderer.sendToHost('webviewPointerDown')")
|
||||||
|
ipcRenderer.sendToHost('webviewPointerDown')
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener('pointerup', (e) => {
|
||||||
|
// console.log("ipcRenderer.sendToHost('webviewPointerUp')")
|
||||||
|
ipcRenderer.sendToHost('webviewPointerUp')
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener('pointerenter', (e) => {
|
||||||
|
// console.log("ipcRenderer.sendToHost('webviewPointerEnter')")
|
||||||
|
ipcRenderer.sendToHost('webviewPointerEnter')
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener('pointercancel', (e) => {
|
||||||
|
// console.log("ipcRenderer.sendToHost('webviewPointerCancel')")
|
||||||
|
ipcRenderer.sendToHost('webviewPointerCancel')
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener('pointerleave', (e) => {
|
||||||
|
// console.log("ipcRenderer.sendToHost('webviewPointerLeave')")
|
||||||
|
ipcRenderer.sendToHost('webviewPointerLeave')
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener('pointerout', (e) => {
|
||||||
|
// console.log("ipcRenderer.sendToHost('webviewPointerOut')")
|
||||||
|
ipcRenderer.sendToHost('webviewPointerOut')
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener('pointerover', (e) => {
|
||||||
|
// console.log("ipcRenderer.sendToHost('webviewPointerOver')")
|
||||||
|
ipcRenderer.sendToHost('webviewPointerOver')
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener('pointermove', (e) => {
|
||||||
|
// console.log("ipcRenderer.sendToHost('webviewPointerMove')")
|
||||||
|
ipcRenderer.sendToHost('webviewPointerMove')
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener('touchmove', (e) => {
|
||||||
|
// console.log("ipcRenderer.sendToHost('touchmove')")
|
||||||
|
ipcRenderer.sendToHost('touchMove')
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener('touchstart', (e) => {
|
||||||
|
pointerCounter++
|
||||||
|
// console.log("ipcRenderer.sendToHost('touchstart')")
|
||||||
|
ipcRenderer.sendToHost('touchStart')
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener('touchend', (e) => {
|
||||||
|
pointerCounter--
|
||||||
|
// console.log("ipcRenderer.sendToHost('touchend')")
|
||||||
|
ipcRenderer.sendToHost('touchEnd')
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on('overlayEvent', function () {
|
||||||
|
console.log('hello world From Preload')
|
||||||
|
})
|
47
browser/store.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
const electron = require('electron')
|
||||||
|
const path = require('path')
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
class Store {
|
||||||
|
|
||||||
|
constructor(opts) {
|
||||||
|
|
||||||
|
// Renderer process has to get `app` module via `remote`, whereas the main process can get it directly
|
||||||
|
// app.getPath('userData') will return a string of the user's app data directory path.
|
||||||
|
const userDataPath = (electron.app || electron.remote.app).getPath('userData')
|
||||||
|
// We'll use the `configName` property to set the file name and path.join to bring it all together as a string
|
||||||
|
this.path = path.join(userDataPath, opts.configName + '.json')
|
||||||
|
|
||||||
|
this.data = parseDataFile(this.path, opts.defaults)
|
||||||
|
this.data = Object.assign(opts.defaults, this.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will just return the property on the `data` object
|
||||||
|
get(key) {
|
||||||
|
return this.data[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...and this will set it
|
||||||
|
set(key, val) {
|
||||||
|
this.data[key] = val
|
||||||
|
// Wait, I thought using the node.js' synchronous APIs was bad form?
|
||||||
|
// We're not writing a server so there's not nearly the same IO demand on the process
|
||||||
|
// Also if we used an async API and our app was quit before the asynchronous write had a chance to complete,
|
||||||
|
// we might lose that data. Note that in a real app, we would try/catch this.
|
||||||
|
fs.writeFileSync(this.path, JSON.stringify(this.data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDataFile(filePath, defaults) {
|
||||||
|
// We'll try/catch it in case the file doesn't exist yet, which will be the case on the first application run.
|
||||||
|
// `fs.readFileSync` will return a JSON string which we then parse into a Javascript object
|
||||||
|
try {
|
||||||
|
return JSON.parse(fs.readFileSync(filePath))
|
||||||
|
} catch(error) {
|
||||||
|
// if there was some kind of error, return the passed in defaults instead.
|
||||||
|
return defaults
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// expose the class
|
||||||
|
module.exports = Store
|
113
browser/test.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
let fs = require('fs')
|
||||||
|
let path = require('path')
|
||||||
|
let {thumbnail} = require('./utils.js')
|
||||||
|
|
||||||
|
let pairs = []
|
||||||
|
let urlMap = new Map()
|
||||||
|
function isFile(path) {
|
||||||
|
try {
|
||||||
|
return !fs.lstatSync(path).isDirectory()
|
||||||
|
} catch(e) {
|
||||||
|
if (e.code == 'ENOENT'){
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listPairs(src, dst, skipFiles=true) {
|
||||||
|
let dir = fs.readdirSync(src)
|
||||||
|
for(let name of dir) {
|
||||||
|
let srcPath = src + name
|
||||||
|
if (isFile(srcPath) && !skipFiles) {
|
||||||
|
if (srcPath.endsWith('.html')) {
|
||||||
|
let dstPath = dst + name.replace(/.html/, '.png')
|
||||||
|
pairs.push([srcPath, dstPath])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (srcPath.endsWith('.'))
|
||||||
|
continue
|
||||||
|
let indexPath = srcPath + path.sep + 'index.html'
|
||||||
|
if (isFile(indexPath)) {
|
||||||
|
let thumbnailPath = indexPath.replace(/index.html/, 'thumbnail.png')
|
||||||
|
pairs.push([indexPath, thumbnailPath])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function capturePage(focusedWindow, dstPath) {
|
||||||
|
focusedWindow.capturePage( (image) => {
|
||||||
|
fs.writeFile(dstPath, thumbnail(image), (err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
nextLoadTest(focusedWindow)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function testLoading(focusedWindow, filePath, dstPath) {
|
||||||
|
let urlPath = filePath.replace(path.sep, '/')
|
||||||
|
let basePath = `${__dirname}`.replace('/browser', '')
|
||||||
|
let shortURL = `file://${basePath}/${urlPath}`
|
||||||
|
let fullURL = `file://${__dirname}/../../${urlPath}`
|
||||||
|
// console.log({basePath, shortURL, fullURL})
|
||||||
|
if (focusedWindow) {
|
||||||
|
urlMap.set(shortURL, dstPath)
|
||||||
|
focusedWindow.webContents.session.clearCache(() => console.log('Cache cleared'))
|
||||||
|
focusedWindow.webContents.loadURL(shortURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listLibPairs() {
|
||||||
|
let src = "lib" + path.sep
|
||||||
|
let dst = "lib" + path.sep + 'thumbnails' + path.sep
|
||||||
|
listPairs(src, dst, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function listAppPairs() {
|
||||||
|
let src = "apps" + path.sep
|
||||||
|
let dst = "apps" + path.sep
|
||||||
|
listPairs(src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
function listSrcPairs() {
|
||||||
|
let src = "src" + path.sep
|
||||||
|
let dst = "src" + path.sep
|
||||||
|
listPairs(src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextLoadTest(focusedWindow) {
|
||||||
|
if (global.errorCount > 0 && global.stopTestsOnError) {
|
||||||
|
console.log("Test aborted")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (pairs.length > 0) {
|
||||||
|
let [file, image] = pairs.pop()
|
||||||
|
testLoading(focusedWindow, file, image)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("All thumbnails created")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadTests(focusedWindow) {
|
||||||
|
global.errorCount = 0
|
||||||
|
focusedWindow.webContents.on('did-finish-load', (e) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
let url = e.sender.history[e.sender.history.length-1]
|
||||||
|
dstPath = urlMap.get(url)
|
||||||
|
capturePage(focusedWindow, dstPath)
|
||||||
|
}, 5000)
|
||||||
|
})
|
||||||
|
|
||||||
|
listLibPairs()
|
||||||
|
listSrcPairs()
|
||||||
|
listAppPairs()
|
||||||
|
nextLoadTest(focusedWindow)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = loadTests
|
||||||
|
|
||||||
|
|
38
browser/utils.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
function fileURL(src) {
|
||||||
|
let dir = __dirname.replace(/\\/g, '/')
|
||||||
|
return `file://${dir}/${src}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadScript(src, callback) {
|
||||||
|
let url = fileURL(src)
|
||||||
|
let script = document.createElement('script')
|
||||||
|
script.onload = () => {
|
||||||
|
if (callback) {
|
||||||
|
callback.call(this, script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
script.src = url
|
||||||
|
document.head.appendChild(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
function thumbnail(screenshot) {
|
||||||
|
return screenshot.resize({ width: 1024 }).toPNG()
|
||||||
|
}
|
||||||
|
|
||||||
|
let hiddenCursor = fileURL('../assets/cursor/cur0000.cur')
|
||||||
|
let defaultCursor = fileURL('../assets/cursor/cur1054.cur')
|
||||||
|
|
||||||
|
function hideCursor() {
|
||||||
|
// console.log("hideCursor")
|
||||||
|
document.body.style.cursor = `url('${hiddenCursor}'), default`
|
||||||
|
}
|
||||||
|
|
||||||
|
function showCursor() {
|
||||||
|
document.body.style.cursor = `url('${defaultCursor}'), default`
|
||||||
|
// console.log("showCursor")
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
fileURL, loadScript, thumbnail, hideCursor, showCursor
|
||||||
|
}
|
@ -27,6 +27,10 @@ body {
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
color: white;
|
color: white;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
@ -163,7 +167,6 @@ div.title {
|
|||||||
.container {
|
.container {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-top: 32px;
|
|
||||||
border: 2pt #000;
|
border: 2pt #000;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
|
@ -30,11 +30,8 @@
|
|||||||
</template>
|
</template>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="container" class="container">
|
<h2><a href="index.html">lib.</a>Doctests</h2>
|
||||||
<a style="position: absolute; left: 22px; top: 12px" target="_blank" href="http://www.iwm-tuebingen.de"
|
<div id="container" class="container"></div>
|
||||||
>IWMLib Doctest</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<script>
|
<script>
|
||||||
let index = new Index(itemTemplate, [
|
let index = new Index(itemTemplate, [
|
||||||
['Doctest', 'doctest.html'],
|
['Doctest', 'doctest.html'],
|
||||||
|
BIN
lib/interaction.png
Normal file
After Width: | Height: | Size: 1002 KiB |
BIN
lib/pixi/app.png
Normal file
After Width: | Height: | Size: 491 KiB |
BIN
lib/pixi/application.png
Normal file
After Width: | Height: | Size: 94 KiB |
BIN
lib/pixi/badge.png
Normal file
After Width: | Height: | Size: 506 KiB |
BIN
lib/pixi/blurfilter.png
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
lib/pixi/button.png
Normal file
After Width: | Height: | Size: 509 KiB |
BIN
lib/pixi/buttongroup.png
Normal file
After Width: | Height: | Size: 508 KiB |
BIN
lib/pixi/coordinates.png
Normal file
After Width: | Height: | Size: 688 KiB |
BIN
lib/pixi/deepzoom/deepzoom.png
Normal file
After Width: | Height: | Size: 429 KiB |
BIN
lib/pixi/deepzoom/image.png
Normal file
After Width: | Height: | Size: 650 KiB |
@ -25,11 +25,11 @@
|
|||||||
</template>
|
</template>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="container" class="container">
|
<h2>
|
||||||
<a style="position: absolute; left: 22px; top: 12px" target="_blank" href="http://www.iwm-tuebingen.de"
|
<a href="../../index.html">lib.</a><a href="../index.html">pixi.</a
|
||||||
>IWMLib PIXI DeepZoom</a
|
><a href="../index.html">deepzoom.</a>Doctests
|
||||||
>
|
</h2>
|
||||||
</div>
|
<div id="container" class="container"></div>
|
||||||
<script>
|
<script>
|
||||||
const index = new Index(
|
const index = new Index(
|
||||||
itemTemplate,
|
itemTemplate,
|
||||||
|
BIN
lib/pixi/deepzoom/index.png
Normal file
After Width: | Height: | Size: 163 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 148 KiB |
BIN
lib/pixi/flipeffect.png
Normal file
After Width: | Height: | Size: 739 KiB |
BIN
lib/pixi/flippable.png
Normal file
After Width: | Height: | Size: 1022 KiB |
@ -25,11 +25,8 @@
|
|||||||
</template>
|
</template>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="container" class="container">
|
<h2><a href="../index.html">lib.</a><a href="index.html">pixi.</a>Doctests</h2>
|
||||||
<a style="position: absolute; left: 22px; top: 12px" target="_blank" href="http://www.iwm-tuebingen.de"
|
<div id="container" class="container"></div>
|
||||||
>IWMLib PIXI Doctests</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<script>
|
<script>
|
||||||
const index = new Index(
|
const index = new Index(
|
||||||
itemTemplate,
|
itemTemplate,
|
||||||
|
BIN
lib/pixi/index.png
Normal file
After Width: | Height: | Size: 1.0 MiB |
BIN
lib/pixi/labeledgraphics.png
Normal file
After Width: | Height: | Size: 609 KiB |
BIN
lib/pixi/list.png
Normal file
After Width: | Height: | Size: 1000 KiB |
BIN
lib/pixi/maps/geographics.png
Normal file
After Width: | Height: | Size: 537 KiB |
BIN
lib/pixi/maps/geojson.png
Normal file
After Width: | Height: | Size: 360 KiB |
@ -1,11 +1,12 @@
|
|||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>PIXI Maps Doctests</title>
|
<title>PIXI Maps Doctests</title>
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<meta name="viewport"
|
<meta
|
||||||
content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0" />
|
name="viewport"
|
||||||
<link rel="stylesheet" href="../../../css/index.css">
|
content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0"
|
||||||
|
/>
|
||||||
|
<link rel="stylesheet" href="../../../css/index.css" />
|
||||||
|
|
||||||
<script src="../../../dist/iwmlib.js"></script>
|
<script src="../../../dist/iwmlib.js"></script>
|
||||||
|
|
||||||
@ -14,7 +15,7 @@
|
|||||||
<div class="preview">
|
<div class="preview">
|
||||||
<div class="thumbnail-container">
|
<div class="thumbnail-container">
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
<img class="icon" src="thumbnails/notfound.png">
|
<img class="icon" src="thumbnails/notfound.png" />
|
||||||
<!-- <iframe src="" frameborder="0"></iframe> -->
|
<!-- <iframe src="" frameborder="0"></iframe> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -54,7 +55,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
font-family: "Open Sans", sans-serif;
|
font-family: 'Open Sans', sans-serif;
|
||||||
background-color: #4c4f4f;
|
background-color: #4c4f4f;
|
||||||
color: whitesmoke;
|
color: whitesmoke;
|
||||||
|
|
||||||
@ -64,7 +65,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -80,33 +80,34 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<a id="logo" target="_blank" href="http://www.iwm-tuebingen.de">
|
<h1>
|
||||||
<img src="../../../assets/logos/iwm_logo_2015_twitter.png">
|
<a href="../../index.html">lib.</a><a href="../index.html">pixi.</a
|
||||||
</a>
|
><a href="../index.html">map.</a>Doctests
|
||||||
<h1>Maps Module</h1>
|
</h1>
|
||||||
<p>The maps module provides a handy toolkit to easily integrate maps in an application. Create a full screen map
|
<p>
|
||||||
application by using the mapapp. Utilize the GeoLayer-system to integrate maps in an existing application.
|
The maps module provides a handy toolkit to easily integrate maps in an application. Create a full
|
||||||
Draw graphics onto the map using geographical positions instead of pixel positions with the GeoGraphics.
|
screen map application by using the mapapp. Utilize the GeoLayer-system to integrate maps in an existing
|
||||||
Or just use an Overlay to quickly draw icons for each point on a map.
|
application. Draw graphics onto the map using geographical positions instead of pixel positions with the
|
||||||
|
GeoGraphics. Or just use an Overlay to quickly draw icons for each point on a map.
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
<div id="container" class="container">
|
<div id="container" class="container"></div>
|
||||||
|
|
||||||
</div>
|
|
||||||
<script>
|
<script>
|
||||||
const index = new Index(itemTemplate, [
|
const index = new Index(
|
||||||
["GeoGraphics", "geographics.html"],
|
itemTemplate,
|
||||||
["GeoJson", "geojson.html"],
|
[
|
||||||
["GeoMap", "map.html"],
|
['GeoGraphics', 'geographics.html'],
|
||||||
["MapApp", "mapapp.html"],
|
['GeoJson', 'geojson.html'],
|
||||||
["MapProjection", "mapprojection.html"],
|
['GeoMap', 'map.html'],
|
||||||
["MapViewport", "mapviewport.html"],
|
['MapApp', 'mapapp.html'],
|
||||||
["Overlay", "overlay.html"],
|
['MapProjection', 'mapprojection.html'],
|
||||||
["Scatter", "scatter.html"]
|
['MapViewport', 'mapviewport.html'],
|
||||||
|
['Overlay', 'overlay.html'],
|
||||||
|
['Scatter', 'scatter.html']
|
||||||
],
|
],
|
||||||
null)
|
null
|
||||||
|
)
|
||||||
index.load()
|
index.load()
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
BIN
lib/pixi/maps/index.png
Normal file
After Width: | Height: | Size: 242 KiB |
BIN
lib/pixi/maps/map.png
Normal file
After Width: | Height: | Size: 554 KiB |
BIN
lib/pixi/maps/mapapp.png
Normal file
After Width: | Height: | Size: 1.8 MiB |
BIN
lib/pixi/maps/mapprojection.png
Normal file
After Width: | Height: | Size: 1.6 MiB |
BIN
lib/pixi/maps/mapviewport.png
Normal file
After Width: | Height: | Size: 1.8 MiB |
BIN
lib/pixi/maps/overlay.png
Normal file
After Width: | Height: | Size: 718 KiB |
BIN
lib/pixi/maps/scatter.png
Normal file
After Width: | Height: | Size: 268 KiB |
BIN
lib/pixi/message.png
Normal file
After Width: | Height: | Size: 486 KiB |
BIN
lib/pixi/modal.png
Normal file
After Width: | Height: | Size: 581 KiB |
BIN
lib/pixi/popover.png
Normal file
After Width: | Height: | Size: 981 KiB |
BIN
lib/pixi/popup.png
Normal file
After Width: | Height: | Size: 561 KiB |
BIN
lib/pixi/popupmenu.png
Normal file
After Width: | Height: | Size: 539 KiB |
BIN
lib/pixi/progress.png
Normal file
After Width: | Height: | Size: 366 KiB |
BIN
lib/pixi/scatter.png
Normal file
After Width: | Height: | Size: 799 KiB |
BIN
lib/pixi/scrollview.png
Normal file
After Width: | Height: | Size: 559 KiB |
BIN
lib/pixi/slider.png
Normal file
After Width: | Height: | Size: 394 KiB |
BIN
lib/pixi/stylus.png
Normal file
After Width: | Height: | Size: 410 KiB |
BIN
lib/pixi/switch.png
Normal file
After Width: | Height: | Size: 647 KiB |
BIN
lib/pixi/text-transform.png
Normal file
After Width: | Height: | Size: 568 KiB |
BIN
lib/pixi/text.png
Normal file
After Width: | Height: | Size: 242 KiB |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 109 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 113 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 164 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 158 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 160 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 135 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 164 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 141 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 194 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 126 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 149 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 154 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 160 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 85 KiB |
BIN
lib/pixi/tooltip.png
Normal file
After Width: | Height: | Size: 687 KiB |
BIN
lib/pixi/volatile.png
Normal file
After Width: | Height: | Size: 383 KiB |
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 270 KiB |
18689
package-lock.json
generated
10
package.json
@ -2,7 +2,7 @@
|
|||||||
"name": "iwmlib",
|
"name": "iwmlib",
|
||||||
"version": "2.0.0-beta.1",
|
"version": "2.0.0-beta.1",
|
||||||
"description": "An Open Source library for multi-touch, WebGL powered applications.",
|
"description": "An Open Source library for multi-touch, WebGL powered applications.",
|
||||||
"main": "index.js",
|
"main": "browser/main.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
"example": "examples"
|
"example": "examples"
|
||||||
},
|
},
|
||||||
@ -45,7 +45,15 @@
|
|||||||
"stylelint-config-standard": "^28.0.0"
|
"stylelint-config-standard": "^28.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"electron": "^16.2.8",
|
||||||
|
"electron-localshortcut": "^3.1.0",
|
||||||
|
"electron-packager": "^13.1.1",
|
||||||
|
"electron-prebuilt": "^1.4.13",
|
||||||
|
"electron-prebuilt-compile": "^4.0.0",
|
||||||
|
"electron-process-manager": "^0.7.0",
|
||||||
|
"electron-rebuild": "^1.8.5",
|
||||||
"gsap": "^2.1.3",
|
"gsap": "^2.1.3",
|
||||||
|
"html": "^1.0.0",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"optimal-select": "^4.0.1",
|
"optimal-select": "^4.0.1",
|
||||||
"pixi-compressed-textures": "^2.0.5",
|
"pixi-compressed-textures": "^2.0.5",
|
||||||
|