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;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: white;
|
||||
padding: 4px;
|
||||
@ -163,7 +167,6 @@ div.title {
|
||||
.container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-top: 32px;
|
||||
border: 2pt #000;
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
|
@ -30,11 +30,8 @@
|
||||
</template>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container" class="container">
|
||||
<a style="position: absolute; left: 22px; top: 12px" target="_blank" href="http://www.iwm-tuebingen.de"
|
||||
>IWMLib Doctest</a
|
||||
>
|
||||
</div>
|
||||
<h2><a href="index.html">lib.</a>Doctests</h2>
|
||||
<div id="container" class="container"></div>
|
||||
<script>
|
||||
let index = new Index(itemTemplate, [
|
||||
['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>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container" class="container">
|
||||
<a style="position: absolute; left: 22px; top: 12px" target="_blank" href="http://www.iwm-tuebingen.de"
|
||||
>IWMLib PIXI DeepZoom</a
|
||||
>
|
||||
</div>
|
||||
<h2>
|
||||
<a href="../../index.html">lib.</a><a href="../index.html">pixi.</a
|
||||
><a href="../index.html">deepzoom.</a>Doctests
|
||||
</h2>
|
||||
<div id="container" class="container"></div>
|
||||
<script>
|
||||
const index = new Index(
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container" class="container">
|
||||
<a style="position: absolute; left: 22px; top: 12px" target="_blank" href="http://www.iwm-tuebingen.de"
|
||||
>IWMLib PIXI Doctests</a
|
||||
>
|
||||
</div>
|
||||
<h2><a href="../index.html">lib.</a><a href="index.html">pixi.</a>Doctests</h2>
|
||||
<div id="container" class="container"></div>
|
||||
<script>
|
||||
const index = new Index(
|
||||
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,112 +1,113 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>PIXI Maps Doctests</title>
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta
|
||||
name="viewport"
|
||||
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" />
|
||||
|
||||
<head>
|
||||
<title>PIXI Maps Doctests</title>
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="viewport"
|
||||
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>
|
||||
|
||||
<template id="itemTemplate">
|
||||
<a class="wrapper" href="">
|
||||
<div class="preview">
|
||||
<div class="thumbnail-container">
|
||||
<div class="thumbnail">
|
||||
<img class="icon" src="thumbnails/notfound.png">
|
||||
<!-- <iframe src="" frameborder="0"></iframe> -->
|
||||
<template id="itemTemplate">
|
||||
<a class="wrapper" href="">
|
||||
<div class="preview">
|
||||
<div class="thumbnail-container">
|
||||
<div class="thumbnail">
|
||||
<img class="icon" src="thumbnails/notfound.png" />
|
||||
<!-- <iframe src="" frameborder="0"></iframe> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="title"></div>
|
||||
</div>
|
||||
<div class="title"></div>
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-size: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
<style>
|
||||
body {
|
||||
font-size: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#logo {
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
}
|
||||
#logo {
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
}
|
||||
|
||||
#logo>img {
|
||||
width: 80px;
|
||||
}
|
||||
#logo > img {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
header>h1 {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
word-spacing: 0.25em;
|
||||
margin-top: 0;
|
||||
}
|
||||
header > h1 {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
word-spacing: 0.25em;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
header>p {
|
||||
max-width: 720px;
|
||||
line-height: 1.5em;
|
||||
color: rgb(207, 207, 207);
|
||||
}
|
||||
header > p {
|
||||
max-width: 720px;
|
||||
line-height: 1.5em;
|
||||
color: rgb(207, 207, 207);
|
||||
}
|
||||
|
||||
header {
|
||||
font-family: "Open Sans", sans-serif;
|
||||
background-color: #4c4f4f;
|
||||
color: whitesmoke;
|
||||
header {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
background-color: #4c4f4f;
|
||||
color: whitesmoke;
|
||||
|
||||
padding: 68px 50px 10px 150px;
|
||||
padding: 68px 50px 10px 150px;
|
||||
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.container {
|
||||
.container {
|
||||
justify-content: center;
|
||||
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
height: auto;
|
||||
min-height: auto;
|
||||
width: auto;
|
||||
min-width: auto;
|
||||
|
||||
flex: 1;
|
||||
height: auto;
|
||||
min-height: auto;
|
||||
width: auto;
|
||||
min-width: auto;
|
||||
|
||||
margin: 0 60px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<a id="logo" target="_blank" href="http://www.iwm-tuebingen.de">
|
||||
<img src="../../../assets/logos/iwm_logo_2015_twitter.png">
|
||||
</a>
|
||||
<h1>Maps Module</h1>
|
||||
<p>The maps module provides a handy toolkit to easily integrate maps in an application. Create a full screen map
|
||||
application by using the mapapp. Utilize the GeoLayer-system to integrate maps in an existing 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>
|
||||
</header>
|
||||
<div id="container" class="container">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
const index = new Index(itemTemplate, [
|
||||
["GeoGraphics", "geographics.html"],
|
||||
["GeoJson", "geojson.html"],
|
||||
["GeoMap", "map.html"],
|
||||
["MapApp", "mapapp.html"],
|
||||
["MapProjection", "mapprojection.html"],
|
||||
["MapViewport", "mapviewport.html"],
|
||||
["Overlay", "overlay.html"],
|
||||
["Scatter", "scatter.html"]
|
||||
],
|
||||
null)
|
||||
index.load()
|
||||
</script>
|
||||
</body>
|
||||
margin: 0 60px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1>
|
||||
<a href="../../index.html">lib.</a><a href="../index.html">pixi.</a
|
||||
><a href="../index.html">map.</a>Doctests
|
||||
</h1>
|
||||
<p>
|
||||
The maps module provides a handy toolkit to easily integrate maps in an application. Create a full
|
||||
screen map application by using the mapapp. Utilize the GeoLayer-system to integrate maps in an existing
|
||||
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>
|
||||
</header>
|
||||
<div id="container" class="container"></div>
|
||||
<script>
|
||||
const index = new Index(
|
||||
itemTemplate,
|
||||
[
|
||||
['GeoGraphics', 'geographics.html'],
|
||||
['GeoJson', 'geojson.html'],
|
||||
['GeoMap', 'map.html'],
|
||||
['MapApp', 'mapapp.html'],
|
||||
['MapProjection', 'mapprojection.html'],
|
||||
['MapViewport', 'mapviewport.html'],
|
||||
['Overlay', 'overlay.html'],
|
||||
['Scatter', 'scatter.html']
|
||||
],
|
||||
null
|
||||
)
|
||||
index.load()
|
||||
</script>
|
||||
</body>
|
||||
</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",
|
||||
"version": "2.0.0-beta.1",
|
||||
"description": "An Open Source library for multi-touch, WebGL powered applications.",
|
||||
"main": "index.js",
|
||||
"main": "browser/main.js",
|
||||
"directories": {
|
||||
"example": "examples"
|
||||
},
|
||||
@ -45,7 +45,15 @@
|
||||
"stylelint-config-standard": "^28.0.0"
|
||||
},
|
||||
"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",
|
||||
"html": "^1.0.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
"optimal-select": "^4.0.1",
|
||||
"pixi-compressed-textures": "^2.0.5",
|
||||
|