This commit is contained in:
Tjark Müller 2023-05-10 15:23:51 +02:00
commit 03fda7e79e
168 changed files with 35051 additions and 5929 deletions

4
.gitignore vendored
View File

@ -83,4 +83,6 @@ typings/
# ignore generated contents-
/doc/out/*
**/thumbnails
**/thumbnail.png
**/thumbnail.png
/site/dist
/site/__pycache__

13
bin/browser.sh Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

2814
browser/padAccordionOld.js Normal file

File diff suppressed because it is too large Load Diff

1380
browser/padMinimal.js Normal file

File diff suppressed because it is too large Load Diff

231
browser/preload.js Normal file
View 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
View 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
View 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
View 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
View 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
}

View File

@ -18,7 +18,7 @@ html {
margin: 0 auto;
}
.dark-mode nav{
.dark-mode nav {
border-color: var(--white);
}
@ -264,3 +264,23 @@ canvas {
width: 100%;
height: 100%;
}
#ctxmenu {
position: fixed;
background: white;
color: black;
cursor: pointer;
border: 1px lightgray solid;
}
#ctxmenu > a {
display: block;
padding: 0.25rem 1rem;
font-size: 18px;
margin: 0.125rem;
}
#ctxmenu > a:hover {
background: black;
color: white;
}

View File

@ -27,6 +27,10 @@ body {
-moz-osx-font-smoothing: grayscale;
}
h2 {
padding-left: 20px;
}
h3 {
color: white;
padding: 4px;
@ -34,7 +38,9 @@ h3 {
background-color: rgba(0, 0, 15, 0.5);
}
a { text-decoration: none; }
a {
text-decoration: none;
}
div.wrapper {
overflow: hidden;
@ -46,28 +52,48 @@ div.wrapper {
/* Color animation from https://www.tjvantoll.com/2012/02/20/css3-color-animations/ */
@-webkit-keyframes color_change {
from { background-color: rgba(0, 0, 0, 0); }
to { background-color: red; }
from {
background-color: rgba(0, 0, 0, 0);
}
to {
background-color: red;
}
}
@-moz-keyframes color_change {
from { background-color: rgba(0, 0, 0, 0); }
to { background-color: red; }
from {
background-color: rgba(0, 0, 0, 0);
}
to {
background-color: red;
}
}
@-ms-keyframes color_change {
from { background-color: rgba(0, 0, 0, 0); }
to { background-color: red; }
from {
background-color: rgba(0, 0, 0, 0);
}
to {
background-color: red;
}
}
@-o-keyframes color_change {
from { background-color: rgba(0, 0, 0, 0); }
to { background-color: red; }
from {
background-color: rgba(0, 0, 0, 0);
}
to {
background-color: red;
}
}
@keyframes color_change {
from { background-color: rgba(0, 0, 0, 0); }
to { background-color: red; }
from {
background-color: rgba(0, 0, 0, 0);
}
to {
background-color: red;
}
}
/*** CSS taken from https://medium.com/@jamesfuthey/simulating-the-creation-of-website-thumbnail-screenshots-using-iframes-7145269891db#.7v7fshos5 ***/
@ -83,7 +109,7 @@ div.wrapper {
}
.thumbnail::after {
content: "";
content: '';
display: block;
position: absolute;
top: 0;
@ -145,12 +171,12 @@ div.title {
min-height: 100%;
min-width: 100%;
display: -webkit-flex;
-webkit-align-items: flex-end;
align-items: flex-end;
-webkit-align-items: flex-start;
align-items: flex-start;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-align-content: flex-end;
align-content: flex-end;
-webkit-align-content: flex-start;
align-content: flex-start;
}
/** See https://github.com/electron/electron/issues/4420 */

View File

@ -1,30 +1,28 @@
<!DOCTYPE html>
<meta charset="utf-8">
<meta charset="utf-8" />
<style>
.node circle {
fill: #999;
}
.node circle {
fill: #999;
}
.node text {
font: 10px sans-serif;
}
.node text {
font: 10px sans-serif;
}
.node--internal circle {
fill: #555;
}
.node--internal circle {
fill: #555;
}
.node--internal text {
text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
.node--internal text {
text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
</style>
<svg width="960" height="512"></svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
@ -32,104 +30,106 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.7.1/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/4.0.11/acorn.min.js"></script>
<script>
fetch('../apps/loader/js/main.js')
.then((response) => response.text())
.then((text) => {
const ast = acorn.parse(text, {
sourceType: 'module',
// collect ranges for each node
ranges: true,
// collect comments in Esprima's format
onComment: null,
// collect token ranges
onToken: null
})
fetch("../apps/loader/js/main.js")
.then(response => response.text())
.then(text => {
const ast = acorn.parse(text, {
sourceType: "module",
// collect ranges for each node
ranges: true,
// collect comments in Esprima's format
onComment: null,
// collect token ranges
onToken: null
})
console.info(ast)
console.info(ast)
drawAst(ast)
})
drawAst(ast)
})
/*
*
*/
function drawAst(ast) {
// Create SVG element
//---------------------------
let svg = d3.select("svg")
const width = svg.attr("width")
const height = svg.attr("height")
let g = svg.append("g").attr("transform", "translate(40,0)")
// Convert data
//---------------------------
const data = acornToHierarchy(ast)
// Create D3 Hierarchy
//---------------------------
let root = d3.hierarchy(data).sort((a, b) => {
return (a.height - b.height) || a.data.name.localeCompare(b.data.name);
})
/*
*
*/
function drawAst(ast) {
// Create SVG element
//---------------------------
let svg = d3.select('svg')
const width = svg.attr('width')
const height = svg.attr('height')
let g = svg.append('g').attr('transform', 'translate(40,0)')
// Create D3 Cluster
//---------------------------
let tree = d3.cluster().size([height, width - 200])
tree(root)
// Create SVG elements
//---------------------------
let link = g.selectAll(".link")
.data(root.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.attr("d", d => {
return `M${d.y},${d.x}C${d.parent.y + 100},${d.x} ${d.parent.y + 100},${d.parent.x} ${d.parent.y},${d.parent.x}`
})
let node = g.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", d => "node" + (d.children ? " node--internal" : " node--leaf"))
.attr("transform", d => `translate(${d.y},${d.x})`)
node.append("circle")
.attr("r", 5)
node.append("text")
.attr("dy", 3)
.attr("x", d => d.children ? -8 : 8)
.style("text-anchor", d => d.children ? "end" : "start")
.text(d => d.data.name)
}
// Convert data
//---------------------------
const data = acornToHierarchy(ast)
/*
*
*/
function acornToHierarchy(ast) {
console.info(JSON.stringify(ast))
let data = {}
for (const clazz of ast.body) {
if (clazz.type === "ClassDeclaration") {
data.name = clazz.id.name
data.children = []
for (const method of clazz.body.body) {
if (method.type === "MethodDefinition") {
data.children.push({
name: method.key.name,
children: []
})
}
}
}
}
/*
// Create D3 Hierarchy
//---------------------------
let root = d3.hierarchy(data).sort((a, b) => {
return a.height - b.height || a.data.name.localeCompare(b.data.name)
})
// Create D3 Cluster
//---------------------------
let tree = d3.cluster().size([height, width - 200])
tree(root)
// Create SVG elements
//---------------------------
let link = g
.selectAll('.link')
.data(root.descendants().slice(1))
.enter()
.append('path')
.attr('class', 'link')
.attr('d', (d) => {
return `M${d.y},${d.x}C${d.parent.y + 100},${d.x} ${d.parent.y + 100},${d.parent.x} ${d.parent.y},${
d.parent.x
}`
})
let node = g
.selectAll('.node')
.data(root.descendants())
.enter()
.append('g')
.attr('class', (d) => 'node' + (d.children ? ' node--internal' : ' node--leaf'))
.attr('transform', (d) => `translate(${d.y},${d.x})`)
node.append('circle').attr('r', 5)
node.append('text')
.attr('dy', 3)
.attr('x', (d) => (d.children ? -8 : 8))
.style('text-anchor', (d) => (d.children ? 'end' : 'start'))
.text((d) => d.data.name)
}
/*
*
*/
function acornToHierarchy(ast) {
console.info(JSON.stringify(ast))
let data = {}
for (const clazz of ast.body) {
if (clazz.type === 'ClassDeclaration') {
data.name = clazz.id.name
data.children = []
for (const method of clazz.body.body) {
if (method.type === 'MethodDefinition') {
data.children.push({
name: method.key.name,
children: []
})
}
}
}
}
/*
const data = {
"name": "Eve",
"children": [{
@ -153,22 +153,7 @@ function acornToHierarchy(ast) {
}]
}
*/
return data
}
</script>
return data
}
</script>

27
lib/_menu.js Normal file
View File

@ -0,0 +1,27 @@
const mapping = {
'lib.Application': './app.html',
'lib.Capabilities': './capabilities.html',
'lib.Card': './card/index.html',
'lib.Pixi': './pixi/index.html',
'pixi.App': './pixi/app.html'
}
function menu(event) {
let key = event.target.innerText
let html = ''
for (let k of Object.keys(mapping)) {
if (k.startsWith(key)) {
let rest = k.slice(key.length)
let url = mapping[k]
html += `<a href="${url}">${rest}</a>`
}
}
event.preventDefault()
let contextMenu = document.createElement('div')
contextMenu.id = 'ctxmenu'
contextMenu.style = `top:${event.pageY - 10}px;left:${event.pageX - 40}px`
contextMenu.onmouseleave = () => (contextMenu.outerHTML = '')
contextMenu.innerHTML = html
document.body.appendChild(contextMenu)
}

View File

@ -3,6 +3,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>App</title>
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../css/doctest.css">
<script src="./3rdparty/highlight/highlight.pack.js"></script>
@ -12,7 +13,7 @@
<body onload="Doctest.run()">
<h1>
Application
<a href="index.html">lib.</a>Application
</h1>
<p>
IWM Browser Applications follow a common three phase pattern, shared by many programming environments as diverse as Processing, Arduino, Intern, etc.

View File

@ -1,75 +1,56 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Doctests Capabilities</title>
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../css/doctest.css">
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script type="text/javascript" src="../dist/iwmlib.js"></script>
</head>
<body onload="Doctest.run(); CapabilitiesTests.testAll()">
<main>
<h1>
Capabilities
</h1>
<p>Browsers differ in many aspects, from touch support, support of CSS and HTML5 standards, to
javascript versions. This page collects some of these differences.
<h3>
User Agent
</h3>
<p id="user_agent">
</p>
<h3>
Device Pixel Ratio
</h3>
<p id="device_pixel_ratio">
</p>
<h3>
Multi Touch Table
</h3>
<p id="multi_touch_table">
</p>
<h3>
Supported Events
</h3>
<p id="supported_events">
</p>
<script class="doctest">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Doctests Capabilities</title>
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../css/doctest.css" />
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script type="text/javascript" src="../dist/iwmlib.js"></script>
</head>
<body onload="Doctest.run(); CapabilitiesTests.testAll()">
<main>
<h1><a href="index.html">lib.</a>Capabilities</h1>
<p>
Browsers differ in many aspects, from touch support, support of CSS and HTML5 standards, to javascript
versions. This page collects some of these differences.
</p>
Doctest.expect(Capabilities.supportsMouseEvents(), true)
<h3>User Agent</h3>
<p id="user_agent"></p>
<h3>Device Pixel Ratio</h3>
<p id="device_pixel_ratio"></p>
<h3>Multi Touch Table</h3>
<p id="multi_touch_table"></p>
<h3>Supported Events</h3>
<p id="supported_events"></p>
<script class="doctest">
Doctest.expect(Capabilities.supportsMouseEvents(), true)
if (Capabilities.supportsTouchEvents()) {
Doctest.expect(Capabilities.supportsTouchEvents(), true)
}
if (Capabilities.supportsTouchEvents()) {
Doctest.expect(Capabilities.supportsTouchEvents(), true)
}
if (Capabilities.supportsPointerEvents()) {
Doctest.expect(Capabilities.supportsPointerEvents(), true)
}
</script>
<h3>
Interactive Alerts
</h3>
<p>
Standard alerts are displayed quite differently, on Windows 10, for instance
the browser URL is encluded, and a checkbox that allows to hide the
alert dialogs.
</p>
<button onclick="alert('Ok'); console.log('Alert')">Alert</button>
<button onclick="CapabilitiesTests.testConfirm()">Confirm</button>
<button onclick="CapabilitiesTests.testPrompt()">Prompt</button>
<p id="demo">
Result
</p>
<hr />
if (Capabilities.supportsPointerEvents()) {
Doctest.expect(Capabilities.supportsPointerEvents(), true)
}
</script>
<h3>Interactive Alerts</h3>
<p>
Standard alerts are displayed quite differently, on Windows 10, for instance the browser URL is
encluded, and a checkbox that allows to hide the alert dialogs.
</p>
<button onclick="alert('Ok'); console.log('Alert')">Alert</button>
<button onclick="CapabilitiesTests.testConfirm()">Confirm</button>
<button onclick="CapabilitiesTests.testPrompt()">Prompt</button>
<p id="demo">Result</p>
<hr />
<h2>
References
</h2>
<ul>
<li><a href="http://caniuse.com">Can I use</a></li>
<li><a href="http://webglreport.com">WebGL Report</a></li>
</ul>
</main>
</body>
<h2>References</h2>
<ul>
<li><a href="http://caniuse.com">Can I use</a></li>
<li><a href="http://webglreport.com">WebGL Report</a></li>
</ul>
</main>
</body>
</html>

View File

@ -15,7 +15,7 @@
<body onload="Doctest.run();">
<h1>
Cards
<a href="../index.html">lib.</a><a href="index.html">card.</a>Cards
</h1>
<p>
Cards implement a central UI metaphor for multiuser applications. They allow users to explore information spaces

View File

@ -1,110 +1,115 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Coordinates Doctest</title>
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../css/doctest.css" />
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.3rdparty.js"></script>
<script src="../dist/iwmlib.js"></script>
<script>
function drawPolygons() {
canvas.width = main.getBoundingClientRect().width
let context = canvas.getContext('2d')
context.clearRect(0, 0, canvas.width, canvas.height)
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Coordinates Doctest</title>
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../css/doctest.css">
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.3rdparty.js"></script>
<script src="../dist/iwmlib.js"></script>
<script>
function drawPolygons() {
canvas.width = main.getBoundingClientRect().width
let context = canvas.getContext('2d')
context.clearRect(0, 0, canvas.width, canvas.height)
let stage = scatterContainer.polygon
stage.draw(context, { stroke: '#FF0000' })
for (let scatter of scatterContainer.scatter.values()) {
let polygon = scatter.polygon
polygon.draw(context, { stroke: '#FF0000' })
let stage = scatterContainer.polygon
stage.draw(context, { stroke: '#FF0000' })
for (let scatter of scatterContainer.scatter.values()) {
let polygon = scatter.polygon
polygon.draw(context, { stroke: '#FF0000' })
}
}
}
function animate(callback) {
requestAnimationFrame((dt) => {
drawPolygons()
callback()
animate(callback)
})
}
</script>
</head>
function animate(callback) {
requestAnimationFrame((dt) => {
drawPolygons()
callback()
animate(callback)
})
}
</script>
</head>
<body onload="Doctest.run()">
<h1>
Coordinates
</h1>
<p>
To position objects in defined spatial relationships presupposes a clear understanding of the involved coordinate systems.
Unfortunately, several systems with several conventions are involved:
<a href="https://javascript.info/coordinates">DOM &amp; CSS</a>,
<a href="https://www.sarasoueidan.com/blog/svg-coordinate-systems/SVG">SVG</a>,
<a href="https://www.w3schools.com/graphics/canvas_coordinates.asp">Canvas</a>
</p>
<p>
We need a common reference system to switch between these coordinate systems. As the uttermost context, the browser page
coordinate system is the most natural one. A simple API was long missing but has now been established in most modern
browsers with
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/convertPointFromNoteToPage">window.convertPointFromNoteToPage</a> and the inverse
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/convertPointFromPageToNode">window.convertPointFromPageToNode</a>.
Although MDN Web Docs warns about their Non-standard nature the methods work in browsers targeted
by the IWM Browser project. This doctest assures that this assumption can be tested.
</p>
<p>Let's look at a scatter object with a rotatable local coordinate system. We try to follow a point in this local coordinate
system by showing a marker outside the scatter that follows the point.
</p>
<div id="main" class="grayBorder interactive" style="position: relative; width: 100%; height: 280px;">
<!-- Note that we need to set draggable to false to avoid conflicts. The DOM elements
<body onload="Doctest.run()">
<h1><a href="index.html">lib.</a>Coordinates</h1>
<p>
To position objects in defined spatial relationships presupposes a clear understanding of the involved
coordinate systems. Unfortunately, several systems with several conventions are involved:
<a href="https://javascript.info/coordinates">DOM &amp; CSS</a>,
<a href="https://www.sarasoueidan.com/blog/svg-coordinate-systems/SVG">SVG</a>,
<a href="https://www.w3schools.com/graphics/canvas_coordinates.asp">Canvas</a>
</p>
<p>
We need a common reference system to switch between these coordinate systems. As the uttermost context, the
browser page coordinate system is the most natural one. A simple API was long missing but has now been
established in most modern browsers with
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/convertPointFromNoteToPage"
>window.convertPointFromNoteToPage</a
>
and the inverse
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/convertPointFromPageToNode"
>window.convertPointFromPageToNode</a
>. Although MDN Web Docs warns about their Non-standard nature the methods work in browsers targeted by the
IWM Browser project. This doctest assures that this assumption can be tested.
</p>
<p>
Let's look at a scatter object with a rotatable local coordinate system. We try to follow a point in this
local coordinate system by showing a marker outside the scatter that follows the point.
</p>
<div id="main" class="grayBorder interactive" style="position: relative; width: 100%; height: 280px">
<!-- Note that we need to set draggable to false to avoid conflicts. The DOM elements
must also be positioned absolutely. -->
<img id="women" draggable="false" style="position: absolute;" src="examples/women.jpeg" />
<img id="women" draggable="false" style="position: absolute" src="examples/women.jpeg" />
<canvas id="canvas" height="280" style="z-index: 100000; pointer-events: none; position: absolute; border: 1px solid red;">
Canvas not supported.
</canvas>
</div>
<canvas
id="canvas"
height="280"
style="z-index: 100000; pointer-events: none; position: absolute; border: 1px solid red"
>
Canvas not supported.
</canvas>
</div>
<script class="doctest">
let dx = 44
<script class="doctest">
let dx = 44
let app = new App()
let scatterContainer = new DOMScatterContainer(main)
let angle = 15
let image = document.getElementById('women')
// The DOMScatter needs initial width and height. Therefore we
// define the scatter when the image size is known, i.e. after loading...
image.onload = (e) => {
let scatter = new DOMScatter(image, scatterContainer, {
x: dx,
y: 44,
width: e.target.naturalWidth,
height: e.target.naturalHeight,
rotationDegrees: angle,
throwVisibility: 88,
minScale: 0.5,
maxScale: 1.5
})
dx += 300
angle = -angle
}
let app = new App()
let scatterContainer = new DOMScatterContainer(main)
let angle = 15
let image = document.getElementById('women')
// The DOMScatter needs initial width and height. Therefore we
// define the scatter when the image size is known, i.e. after loading...
image.onload = (e) => {
let scatter = new DOMScatter(image, scatterContainer, {
x: dx,
y: 44,
width: e.target.naturalWidth,
height: e.target.naturalHeight,
rotationDegrees: angle,
throwVisibility: 88,
minScale: 0.5,
maxScale: 1.5
})
dx += 300
angle = -angle
}
app.run()
app.run()
function followPoint() {
let context = canvas.getContext('2d')
let localPoint = { x: 100, y: 100 }
let globalPoint = convertPointFromNodeToPage(image, localPoint.x, localPoint.y)
let canvasPoint = convertPointFromPageToNode(canvas, globalPoint.x, globalPoint.y)
function followPoint() {
let context = canvas.getContext('2d')
let localPoint = { x: 100, y: 100 }
let globalPoint = convertPointFromNodeToPage(image, localPoint.x, localPoint.y)
let canvasPoint = convertPointFromPageToNode(canvas, globalPoint.x, globalPoint.y)
context.strokeStyle = 'red'
context.beginPath()
context.arc(canvasPoint.x, canvasPoint.y, 12, 0, Math.PI * 2)
context.stroke()
}
animate(followPoint)
</script>
</body>
context.strokeStyle = 'red'
context.beginPath()
context.arc(canvasPoint.x, canvasPoint.y, 12, 0, Math.PI * 2)
context.stroke()
}
animate(followPoint)
</script>
</body>
</html>

View File

@ -12,7 +12,7 @@
<body onload="Doctest.run()">
<main>
<h1>
Doctests
<a href="index.html">lib.</a>Doctests
</h1>
<p>
Doctests are explanatory descriptions of programs with executable code examples.

View File

@ -1,47 +1,48 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Electron Node.js Test</title>
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../css/doctest.css">
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="./3rdparty/all.js"></script>
<script src="../dist/iwmlib.js"></script>
</head>
<body onload="Doctest.run()">
<main>
<h1>
Electron Node.js Test
</h1>
<p>
This doctest is expected to work only within the IWMBrowser. IWMBrowser windows
are opened with a preload.js which is evaluated before the HTML is loaded.
According to
<a href="https://electron.atom.io/docs/faq/">"I can not use jQuery/RequireJS/Meteor/AngularJS in Electron"</a> we
have to rename the symbols in the page before other libraries are included.
In order to access node.js modules we can use `nodeRequire` instead.
</p>
<p>As a simple test we try to load a file from the filesystem:</p>
<script class="doctest">
if (typeof(nodeRequire) != 'undefined') {
let fs = nodeRequire('fs')
let content = fs.readFileSync('./index.html')
let lines = content.toString().split(/\n/)
console.log("First line", lines[0] )
Doctest.expect(lines[0], '<html>')
}
</script>
<p>As simple as this test is, it shows that within the IWMBrowser one import all node.js
modules. Don't forget to test for nodeRequire to avoid runtime errors in other browsers.
</p>
<h2>
References
</h2>
<ul>
<li><a href="https://electron.atom.io/docs/faq/">I can not use jQuery/RequireJS/Meteor/AngularJS in Electron</a></li>
</ul>
</main>
</body>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Electron Node.js Test</title>
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../css/doctest.css" />
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="./3rdparty/all.js"></script>
<script src="../dist/iwmlib.js"></script>
</head>
<body onload="Doctest.run()">
<main>
<h1>Electron Node.js Test</h1>
<p>
This doctest is expected to work only within the IWMBrowser. IWMBrowser windows are opened with a
preload.js which is evaluated before the HTML is loaded. According to
<a href="https://electron.atom.io/docs/faq/"
>"I can not use jQuery/RequireJS/Meteor/AngularJS in Electron"</a
>
we have to rename the symbols in the page before other libraries are included. In order to access
node.js modules we can use `nodeRequire` instead.
</p>
<p>As a simple test we try to load a file from the filesystem:</p>
<script class="doctest">
if (typeof nodeRequire != 'undefined') {
let fs = nodeRequire('fs')
let content = fs.readFileSync('./index.html')
let lines = content.toString().split(/\n/)
console.log('First line', lines[0])
Doctest.expect(lines[0], '<html>')
}
</script>
<p>
As simple as this test is, it shows that within the IWMBrowser one import all node.js modules. Don't
forget to test for nodeRequire to avoid runtime errors in other browsers.
</p>
<h2>References</h2>
<ul>
<li>
<a href="https://electron.atom.io/docs/faq/"
>I can not use jQuery/RequireJS/Meteor/AngularJS in Electron</a
>
</li>
</ul>
</main>
</body>
</html>

View File

@ -1,26 +1,25 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../css/doctest.css">
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.3rdparty.js"></script>
<script src="../dist/iwmlib.js"></script>
</head>
<body onload="Doctest.run()" >
<h1>
Events
</h1>
<p>
For functional tests it can be useful to simulate event or record and playback events.
This module provides basic support for extracting data from events and serializing
events into a JSON format that allows to save and load sequences of events.
</p>
<p>
Let's look at an example of a HTML structure with click handlers. The click
handler actions log messages that can be tested.</p>
<pre><code class="html">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../css/doctest.css" />
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.3rdparty.js"></script>
<script src="../dist/iwmlib.js"></script>
</head>
<body onload="Doctest.run()">
<h1><a href="index.html">lib.</a>Events</h1>
<p>
For functional tests it can be useful to simulate event or record and playback events. This module provides
basic support for extracting data from events and serializing events into a JSON format that allows to save
and load sequences of events.
</p>
<p>
Let's look at an example of a HTML structure with click handlers. The click handler actions log messages
that can be tested.
</p>
<pre><code class="html">
&lt;div&gt;
&lt;img id="women" src="examples/women.jpeg"
onclick="record(event); Doctest.log('Lady clicked')"/&gt;
@ -31,46 +30,53 @@ handler actions log messages that can be tested.</p>
&lt;/vide&gt;
&lt;/div&gt;
</code></pre>
<div id="example" class="interactive"
style="position:relative; width: 100%; border: 1px solid lightgray">
<img style="margin:8px" id="women" src="examples/women.jpeg"
onclick="record(event); Doctest.log('Lady clicked')"/>
<video id="movie" style="margin:8px" width="250" data-zoomcap="Kugellaufuhr"
onclick="record(event); Doctest.log('Movie clicked')"
onmousedown="record(event)"
onmouseup="record(event)"
controls>
<source src="examples/movie.mp4" type="video/mp4">
</video>
</div>
<button onclick="eventRecorder.stopRecording(); eventRecorder.startReplay()">Replay</button>
<script class="doctest">
<div id="example" class="interactive" style="position: relative; width: 100%; border: 1px solid lightgray">
<img
style="margin: 8px"
id="women"
src="examples/women.jpeg"
onclick="record(event); Doctest.log('Lady clicked')"
/>
<video
id="movie"
style="margin: 8px"
width="250"
data-zoomcap="Kugellaufuhr"
onclick="record(event); Doctest.log('Movie clicked')"
onmousedown="record(event)"
onmouseup="record(event)"
controls
>
<source src="examples/movie.mp4" type="video/mp4" />
</video>
</div>
<button onclick="eventRecorder.stopRecording(); eventRecorder.startReplay()">Replay</button>
<script class="doctest">
var eventRecorder = new EventRecorder()
var eventRecorder = new EventRecorder()
function record(event) {
let target = event.target
target.style.boxShadow = '0px 5px 10px gray'
setTimeout(() => (target.style.boxShadow = ''), 1000)
eventRecorder.record(event)
}
function record(event) {
let target = event.target
target.style.boxShadow = "0px 5px 10px gray"
setTimeout(() => target.style.boxShadow = "", 1000)
eventRecorder.record(event)
}
let womenSel = Events.selector(women)
let movieSel = Events.selector(movie)
let womenSel = Events.selector(women)
let movieSel = Events.selector(movie)
Events.simulateEvent('click', MouseEvent, { targetSelector: womenSel })
Events.simulateEvent('click', MouseEvent, { targetSelector: movieSel })
Events.simulateEvent('click', MouseEvent, { targetSelector: womenSel})
Events.simulateEvent('click', MouseEvent, { targetSelector: movieSel})
Doctest.expectLog('Lady clicked',
'Movie clicked')
</script>
<h2>
References
</h2>
<ul>
<li><a href="https://gist.github.com/iahu/aafc2492d83d70e42c98">Safari Touch Emulator</a></li>
<li><a href="https://www.reddit.com/r/javascript/comments/2laqaf/how_to_trigger_a_touch_event/">How to Trigger Touch Events</a></li>
</ul>
</body>
Doctest.expectLog('Lady clicked', 'Movie clicked')
</script>
<h2>References</h2>
<ul>
<li><a href="https://gist.github.com/iahu/aafc2492d83d70e42c98">Safari Touch Emulator</a></li>
<li>
<a href="https://www.reddit.com/r/javascript/comments/2laqaf/how_to_trigger_a_touch_event/"
>How to Trigger Touch Events</a
>
</li>
</ul>
</body>
</html>

View File

@ -1,21 +1,21 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Flippable Doctest</title>
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../css/doctest.css">
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.3rdparty.js"></script>
<script src="../dist/iwmlib.js"></script>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Flippable Doctest</title>
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../css/doctest.css" />
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.3rdparty.js"></script>
<script src="../dist/iwmlib.js"></script>
<link rel="stylesheet" href="../css/flipeffect.css">
<template id="flipTemplate">
<div class="flipWrapper">
<div class="flipCard">
<div class="flipFace front"></div>
<div class="flipFace back" style="visibility:hidden;"></div>
</div>
<link rel="stylesheet" href="../css/flipeffect.css" />
<template id="flipTemplate">
<div class="flipWrapper">
<div class="flipCard">
<div class="flipFace front"></div>
<div class="flipFace back" style="visibility: hidden"></div>
</div>
<!-- Very tricky problem to scale svgs: see https://css-tricks.com/scale-svg/ -->
<!-- SVG viewPort interferes with DOMMatrix calculations: see
https://stackoverflow.com/questions/70696387/how-to-get-transform-matrix-of-a-dom-element-->
@ -71,27 +71,24 @@ the viewbox of the SVG will interfere with the coordinate transformation.
&lt;/template&gt;
</code>
</pre>
<h3>
Example
</h3>
<main id="main" style="border: 1px solid gray; position: relative; height: 256px;" >
</main>
<script class="doctest">
let scatterContainer = new DOMScatterContainer(main, {stopEvents: false})
if (Capabilities.supportsTemplate()) {
let flip = new DOMFlip(scatterContainer,
flipTemplate,
new ImageLoader('./examples/king.jpeg'),
new ImageLoader('./examples/women.jpeg'),
{ tapDelegateFactory: CardWrapper, preloadBack: true})
flip.load().then((flip) => {
flip.centerAt({ x: 150, y: 120})
})
}
else {
alert("Templates not supported, use Edge, Chrome, Safari or Firefox.")
}
</script>
</body>
<h3>Example</h3>
<main id="main" style="border: 1px solid gray; position: relative; height: 256px"></main>
<script class="doctest">
let scatterContainer = new DOMScatterContainer(main, { stopEvents: false })
if (Capabilities.supportsTemplate()) {
let flip = new DOMFlip(
scatterContainer,
flipTemplate,
new ImageLoader('./examples/king.jpeg'),
new ImageLoader('./examples/women.jpeg'),
{ tapDelegateFactory: CardWrapper, preloadBack: true }
)
flip.load().then((flip) => {
flip.centerAt({ x: 150, y: 120 })
})
} else {
alert('Templates not supported, use Edge, Chrome, Safari or Firefox.')
}
</script>
</body>
</html>

View File

@ -1,50 +1,51 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no" />
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../css/doctest.css">
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.3rdparty.js"></script>
<script src="../dist/iwmlib.js"></script>
</head>
<body onload="Doctest.run()" >
<h1>
Frames
</h1>
<p>
Frames are a major way to modularize the design of complex applications. Since
pages presented in frames are independent of each other they can fail without
impact on other pages. In addition preparing content in individual HTML files
largely simplfies the workflow of content production.
</p>
<p>This approach, however, has limitations:</p>
<script>
function appleError() {
alert("Refused to display 'http://www.apple.com/de/' in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'.")
}
</script>
<ul><li>Some pages may prevent embedding them
by 'X-Frame-Options', e.g. <a href="javascript:appleError()">www.apple.com</a>
</li>
<li>Sites with responsive design might not be able to detect the available space,
e.g. <a href="https:///de.wikipedia.org">de.wikipedia.org</a>
</li>
<li>Touch events are not dispatched correctly to multiple frames on platforms with
<b>TouchEvents</b>, e.g. if frame one
receives touch1, all related touch points touch2, ... touchN, are send to frame1
although they might occur over frame two.
</li>
</ul>
<p>To solve the last mentioned problem, we prevent frames form touch events by
assigning a <pre>pointer-events: none;</pre> style. A wrapping div is used to capture
the events instead. Captured events are collected by an InteractionMapper and
distributed as synthesized mouse or touch events to the wrapped iframes.
<p>
Let's look at an example of two HTML IFrames embedded in this Doctest.</p>
<pre><code class="html">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../css/doctest.css" />
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.3rdparty.js"></script>
<script src="../dist/iwmlib.js"></script>
</head>
<body onload="Doctest.run()">
<h1><a href="index.html">lib.</a>Frames</h1>
<p>
Frames are a major way to modularize the design of complex applications. Since pages presented in frames are
independent of each other they can fail without impact on other pages. In addition preparing content in
individual HTML files largely simplfies the workflow of content production.
</p>
<p>This approach, however, has limitations:</p>
<script>
function appleError() {
alert(
"Refused to display 'http://www.apple.com/de/' in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'."
)
}
</script>
<ul>
<li>
Some pages may prevent embedding them by 'X-Frame-Options', e.g.
<a href="javascript:appleError()">www.apple.com</a>
</li>
<li>
Sites with responsive design might not be able to detect the available space, e.g.
<a href="https:///de.wikipedia.org">de.wikipedia.org</a>
</li>
<li>
Touch events are not dispatched correctly to multiple frames on platforms with <b>TouchEvents</b>, e.g.
if frame one receives touch1, all related touch points touch2, ... touchN, are send to frame1 although
they might occur over frame two.
</li>
</ul>
<p>To solve the last mentioned problem, we prevent frames form touch events by assigning a</p>
<pre>pointer-events: none;</pre>
style. A wrapping div is used to capture the events instead. Captured events are collected by an
InteractionMapper and distributed as synthesized mouse or touch events to the wrapped iframes.
<p>Let's look at an example of two HTML IFrames embedded in this Doctest.</p>
<pre><code class="html">
&lt;div id="frameWrapper1"&gt;
&lt;iframe style="pointer-events: none;" src="examples/multitouch.html"&gt;&lt;/iframe&gt;
&lt;/div&gt;
@ -53,29 +54,41 @@ Let's look at an example of two HTML IFrames embedded in this Doctest.</p>
&lt;/div&gt;
</code></pre>
<div class="grayBorder" id="container" style="display: flex; justify-content: space-around;">
<div id="frameWrapper1" style="padding: 4px">
<iframe style="width:400px; height:360px; border:0; pointer-events: none;" src="examples/multitouch.html" allowfullscreen></iframe>
</div>
<div id="frameWrapper2" style="padding: 4px">
<iframe style="width:400px; height:360px; border:0; pointer-events: none;" src="examples/multitouch.html" allowfullscreen></iframe>
</div>
</div>
<p>The distribution of events is handled by the enclosing container. The container
registers a InteractionMapper and provides adapter for iframes, that implement
IInteractionTarget by sending programmatically generated events. If you test
these frames on a multitouch device you will notice that the scatters within
the frames can be manipulated independently of each other:
<p/>
<script class="doctest">
let frameContainer = new FrameContainer(container)
<div class="grayBorder" id="container" style="display: flex; justify-content: space-around">
<div id="frameWrapper1" style="padding: 4px">
<iframe
style="width: 400px; height: 360px; border: 0; pointer-events: none"
src="examples/multitouch.html"
allowfullscreen
></iframe>
</div>
<div id="frameWrapper2" style="padding: 4px">
<iframe
style="width: 400px; height: 360px; border: 0; pointer-events: none"
src="examples/multitouch.html"
allowfullscreen
></iframe>
</div>
</div>
<p>
The distribution of events is handled by the enclosing container. The container registers a
InteractionMapper and provides adapter for iframes, that implement IInteractionTarget by sending
programmatically generated events. If you test these frames on a multitouch device you will notice that the
scatters within the frames can be manipulated independently of each other:
</p>
</script>
<h2>
References
</h2>
<ul>
<li><a href="http://stackoverflow.com/questions/8068578/how-do-i-use-multiple-iframes-in-my-html-page">Multiple iFrames</a></li>
<li><a href="https://benmarshall.me/responsive-iframes/">Responsive iFrames</a></li>
</ul>
</body>
<p />
<script class="doctest">
let frameContainer = new FrameContainer(container)
</script>
<h2>References</h2>
<ul>
<li>
<a href="http://stackoverflow.com/questions/8068578/how-do-i-use-multiple-iframes-in-my-html-page"
>Multiple iFrames</a
>
</li>
<li><a href="https://benmarshall.me/responsive-iframes/">Responsive iFrames</a></li>
</ul>
</body>
</html>

View File

@ -1,52 +1,50 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../css/doctest.css">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../css/doctest.css" />
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="./3rdparty/all.js"></script>
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="./3rdparty/all.js"></script>
<script src="../dist/iwmlib.js"></script>
<script src="../dist/iwmlib.js"></script>
</head>
<body onload="Doctest.run()">
<h1><a href="index.html">lib.</a>Image Loader Worker</h1>
<p>
The loading of multiple small images (e.g. loadimng tiles of a zoomable map) is a time consuming task that
leads to small but noticeable delays in touch interaction if standard DOM events are used. With a worker we
can try to do most of the time consuming processing in the background.
</p>
<p>Let's look at an example of a image loader worker:</p>
<img id="img1" width="160" height="120" class="grayBorder interactive" src="" />
<img id="img2" width="160" height="120" class="grayBorder interactive" src="" />
<img id="img3" width="160" height="120" class="grayBorder interactive" src="" />
<img id="img4" width="160" height="120" class="grayBorder interactive" src="" />
<script class="doctest">
let urls = [
'http://i.imgur.com/JmvCQXd.jpg',
'http://i.imgur.com/L4ipvCE.jpg',
'http://i.imgur.com/fKDIYIP.jpg',
'http://i.imgur.com/4ad4bo5.jpg'
]
</head>
<body onload="Doctest.run()" >
<h1>Image Loader Worker</h1>
<p>
The loading of multiple small images (e.g. loadimng tiles of a zoomable
map) is a time consuming task that leads to small but noticeable delays
in touch interaction if standard DOM events are used. With a worker we
can try to do most of the time consuming processing in the background.
</p>
<p>Let's look at an example of a image loader worker:</p>
<img id="img1" width="160" height="120" class="grayBorder interactive" src=""/>
<img id="img2" width="160" height="120" class="grayBorder interactive" src=""/>
<img id="img3" width="160" height="120" class="grayBorder interactive" src=""/>
<img id="img4" width="160" height="120" class="grayBorder interactive" src=""/>
<script class="doctest">
let imgs = [img1, img2, img3, img4]
let count = 0
let worker = new Worker('imageloader.js')
worker.onmessage = (event) => {
console.log('Loaded', event.data)
if (event.data.success) {
console.log('Loaded', event.data.url)
imgs[count].src = event.data.url
count += 1
}
}
let urls = [
'http://i.imgur.com/JmvCQXd.jpg',
'http://i.imgur.com/L4ipvCE.jpg',
'http://i.imgur.com/fKDIYIP.jpg',
'http://i.imgur.com/4ad4bo5.jpg'
]
let imgs = [img1, img2, img3, img4]
let count = 0
let worker = new Worker("imageloader.js")
worker.onmessage = (event) => {
console.log("Loaded", event.data)
if (event.data.success) {
console.log("Loaded", event.data.url)
imgs[count].src = event.data.url
count += 1
}
}
worker.postMessage({command: "load", urls: urls})
worker.postMessage({command: "abort", urls: urls})
worker.postMessage({command: "load", urls: urls})
</script>
</body>
worker.postMessage({ command: 'load', urls: urls })
worker.postMessage({ command: 'abort', urls: urls })
worker.postMessage({ command: 'load', urls: urls })
</script>
</body>
</html>

View File

@ -1,53 +1,55 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Lib Doctests</title>
<meta charset="utf-8"/>
<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>Lib Doctests</title>
<meta charset="utf-8" />
<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" />
<!-- following not necc for index.js included in iwmlib.js -->
<!-- <script src="./index.js"></script> -->
<script src="../dist/iwmlib.js"></script>
<!-- following not necc for index.js included in iwmlib.js -->
<!-- <script src="./index.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> -->
</div>
<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> -->
</div>
</div>
<div class="title"></div>
</div>
<div class="title"></div>
</div>
</a>
</template>
</head>
<body>
<div id="container" class="container">
<a style="position: absolute; left: 22px; top: 12px;" target="_blank" href="http://www.iwm-tuebingen.de">IWM</a>
</div>
<script>
let index = new Index(itemTemplate, [
['Doctest', 'doctest.html'],
['Capabilities', 'capabilities.html'],
['Coordinates', 'coordinates.html'],
['Interface', 'interface.html'],
['Interaction', 'interaction.html'],
['Events', 'events.html'],
['Frames', 'frames.html'],
['Popup', 'popup.html'],
['Popup Menu', 'popupmenu.html'],
['Scatter', 'scatter.html'],
['Flippable', 'flippable.html'],
['Styleguide', 'styleguide.html'],
['UITest', 'uitest.html'],
['PIXI Doctests', 'pixi/index.html']
])
index.load()
</script>
</body>
</a>
</template>
</head>
<body>
<h2><a href="index.html">lib.</a>Doctests</h2>
<div id="container" class="container"></div>
<script>
let index = new Index(itemTemplate, [
['Doctest', 'doctest.html'],
['Capabilities', 'capabilities.html'],
['Coordinates', 'coordinates.html'],
['Interface', 'interface.html'],
['Interaction', 'interaction.html'],
['Events', 'events.html'],
['Frames', 'frames.html'],
['Popup', 'popup.html'],
['Popup Menu', 'popupmenu.html'],
['Scatter', 'scatter.html'],
['Flippable', 'flippable.html'],
['Styleguide', 'styleguide.html'],
['UITest', 'uitest.html'],
['PIXI Doctests', 'pixi/index.html']
])
index.load()
</script>
</body>
</html>

View File

@ -1,36 +1,41 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../css/doctest.css">
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.js"></script>
</head>
<body onload="Doctest.run(); test()" >
<h1>
Code and Class Inspection
</h1>
<p>
To Do: Use SystemJS to load modules and main code. This ensures that
all code can be parsed by acorn into an Abstract Syntax Tree which
in turn allows to extract class statements and related extends phrases.
</p>
<script class="doctest">
function test() {
let sources = Inspect.allScriptSources()
console.log(sources)
}
</script>
<h2>
References
</h2>
<ul>
<li><a href="https://nystudio107.com/blog/using-systemjs-as-javascript-loader">Using SystemJS as JavaScript Loader</a></li>
<li><a href="http://stackoverflow.com/questions/2051678/getting-all-variables-in-scope">Getting all Variables in Scope</a></li>
<li><a href="https://www.keithcirkel.co.uk/metaprogramming-in-es6-symbols/">Metaprogramming in JavaScript</a></li>
</ul>
</body>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../css/doctest.css" />
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.js"></script>
</head>
<body onload="Doctest.run(); test()">
<h1><a href="index.html">lib.</a>Code and Class Inspection</h1>
<p>
To Do: Use SystemJS to load modules and main code. This ensures that all code can be parsed by acorn into an
Abstract Syntax Tree which in turn allows to extract class statements and related extends phrases.
</p>
<script class="doctest">
function test() {
let sources = Inspect.allScriptSources()
console.log(sources)
}
</script>
<h2>References</h2>
<ul>
<li>
<a href="https://nystudio107.com/blog/using-systemjs-as-javascript-loader"
>Using SystemJS as JavaScript Loader</a
>
</li>
<li>
<a href="http://stackoverflow.com/questions/2051678/getting-all-variables-in-scope"
>Getting all Variables in Scope</a
>
</li>
<li>
<a href="https://www.keithcirkel.co.uk/metaprogramming-in-es6-symbols/"
>Metaprogramming in JavaScript</a
>
</li>
</ul>
</body>
</html>

View File

@ -1,409 +1,495 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Interaction Mapper Doctest</title>
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../css/doctest.css">
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.js"></script>
<script src="../dist/iwmlib.3rdparty.js"></script>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Interaction Mapper Doctest</title>
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../css/doctest.css" />
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.js"></script>
<script src="../dist/iwmlib.3rdparty.js"></script>
<script type="module">
import * as Interaction from './interaction.js'
</script>
</head>
<body onload="Doctest.run()" >
<h1>
Interaction Pattern
</h1>
<p>
Since the correct handling of the divergent browser specific multitouch
implementations is a difficult and recurring task we decided to encapsulate
all related handlers for <code>TouchEvent</code> (WebKit, Mozilla) and
<code>PointerEvent</code> (IE, Edge, Chrome) in
a single delegate pattern.
</p>
<p>The main differences are that <code>PointerEvent</code> are fired for each
touch point, whereas the <code>TouchEvent</code> collects multiple
<code>TouchPoints</code> into a single event. The basic PointMap and Interaction
classes unify this behavior by collecting all contact points regardless
of their original mouse, touch, or pointer events.</p>
<h2>
Point Maps
</h2>
<p>The touch and pointer positions are collected in PointMaps which provide
access to the positions via stringified touch and pointer ids. For mouse events the
special id "mouse" is used. PointMaps can be cloned and pretty printed. In addition
they provide auxiliary methods like <code>mean</code> and <code>farthests</code>
which can be used to simplify the computation of gestures. In general
<code>mean</code> can be used to compute the "center of interaction", i.e. the
best guess of the anchor point for rotation and scaling operations.
</p>
<script class="doctest">
let mouse = new PointMap({ mouse: {x:0, y:0}})
let touches = new PointMap({ "touch1": {x:0, y:0}, "touch2": {x: 10, y: 10}})
<script type="module">
import * as Interaction from './interaction.js'
</script>
</head>
<body onload="Doctest.run()">
<h1><a href="index.html">lib.</a>Interaction</h1>
<p>
Since the correct handling of the divergent browser specific multitouch implementations is a difficult and
recurring task we decided to encapsulate all related handlers for <code>TouchEvent</code> (WebKit, Mozilla)
and <code>PointerEvent</code> (IE, Edge, Chrome) in a single delegate pattern.
</p>
<p>
The main differences are that <code>PointerEvent</code> are fired for each touch point, whereas the
<code>TouchEvent</code> collects multiple <code>TouchPoints</code> into a single event. The basic PointMap
and Interaction classes unify this behavior by collecting all contact points regardless of their original
mouse, touch, or pointer events.
</p>
<h2>Point Maps</h2>
<p>
The touch and pointer positions are collected in PointMaps which provide access to the positions via
stringified touch and pointer ids. For mouse events the special id "mouse" is used. PointMaps can be cloned
and pretty printed. In addition they provide auxiliary methods like <code>mean</code> and
<code>farthests</code>
which can be used to simplify the computation of gestures. In general
<code>mean</code> can be used to compute the "center of interaction", i.e. the best guess of the anchor
point for rotation and scaling operations.
</p>
<script class="doctest">
let mouse = new PointMap({ mouse: { x: 0, y: 0 } })
let touches = new PointMap({ touch1: { x: 0, y: 0 }, touch2: { x: 10, y: 10 } })
Doctest.expect(touches,
"[PointMap touch1:{x:0, y:0}, touch2:{x:10, y:10}]")
Doctest.expect(touches.clone(),
"[PointMap touch1:{x:0, y:0}, touch2:{x:10, y:10}]")
Doctest.expect(touches.mean(), {"x":5,"y":5})
</script>
<p>If more than two touch points are involved it may be best to look for the
pair of points which are farthest away from each other. These points will
represent the fingers farthest away from each other, a more simple substitute
for 3, 4 or 5 touch points. Here we add a third point to our example touches
and test whether the maximal distant points are found:
<script class="doctest">
touches.set("touch3", {x:5, y:5})
Doctest.expect(touches.farthests(), [{"x":0,"y":0},{"x":10,"y":10}])
</script>
<h2>
Interaction Points and Interactions
</h2>
Events and points change in time and gestures are computed from this dynamic behavior.
To collect theses changes and to simplify the computation of gestures we
collect PointMaps in a composite class InteractionPoints, which distinguishes
start, current, previous, and ended point coordinates as well as the start timestamps.
<script class="doctest">
Doctest.expect(touches, '[PointMap touch1:{x:0, y:0}, touch2:{x:10, y:10}]')
Doctest.expect(touches.clone(), '[PointMap touch1:{x:0, y:0}, touch2:{x:10, y:10}]')
Doctest.expect(touches.mean(), { x: 5, y: 5 })
</script>
<p>
If more than two touch points are involved it may be best to look for the pair of points which are farthest
away from each other. These points will represent the fingers farthest away from each other, a more simple
substitute for 3, 4 or 5 touch points. Here we add a third point to our example touches and test whether the
maximal distant points are found:
<script class="doctest">
touches.set('touch3', { x: 5, y: 5 })
Doctest.expect(touches.farthests(), [
{ x: 0, y: 0 },
{ x: 10, y: 10 }
])
</script>
</p>
let interactionPoints = new InteractionPoints()
<h2>Interaction Points and Interactions</h2>
Events and points change in time and gestures are computed from this dynamic behavior. To collect theses changes
and to simplify the computation of gestures we collect PointMaps in a composite class InteractionPoints, which
distinguishes start, current, previous, and ended point coordinates as well as the start timestamps.
<script class="doctest">
let interactionPoints = new InteractionPoints()
interactionPoints.update("touch1", {x:0, y:0})
interactionPoints.update("touch2", {x:5, y:5})
interactionPoints.update("touch3", {x:10, y:10})
Doctest.expect(interactionPoints.current.size, 3)
interactionPoints.update('touch1', { x: 0, y: 0 })
interactionPoints.update('touch2', { x: 5, y: 5 })
interactionPoints.update('touch3', { x: 10, y: 10 })
Doctest.expect(interactionPoints.current.size, 3)
// Initially current and previous points are equal
Doctest.expect(interactionPoints.current, interactionPoints.previous)
// Using updatePrevious we copy the current points to the previous ones.
// This is always needed after change events
interactionPoints.updatePrevious()
// Initially current and previous points are equal
Doctest.expect(interactionPoints.current, interactionPoints.previous)
// Using updatePrevious we copy the current points to the previous ones.
// This is always needed after change events
interactionPoints.updatePrevious()
// After this call current and previous can be used to compure the deltas:
interactionPoints.update("touch1", {x: -2, y: -5})
interactionPoints.update("touch2", {x: 5, y: 9})
interactionPoints.update("touch3", {x: 15, y: 20})
Doctest.expect(interactionPoints.current,
"[PointMap touch1:{x:-2, y:-5}, touch2:{x:5, y:9}, touch3:{x:15, y:20}]")
// "[PointMap touch1:{x:-2, y:-5}, touch2:{x:5, y:7}, touch3:{x:15, y:20}]")
// After this call current and previous can be used to compure the deltas:
interactionPoints.update('touch1', { x: -2, y: -5 })
interactionPoints.update('touch2', { x: 5, y: 9 })
interactionPoints.update('touch3', { x: 15, y: 20 })
Doctest.expect(
interactionPoints.current,
'[PointMap touch1:{x:-2, y:-5}, touch2:{x:5, y:9}, touch3:{x:15, y:20}]'
)
// "[PointMap touch1:{x:-2, y:-5}, touch2:{x:5, y:7}, touch3:{x:15, y:20}]")
// The delta object is a convenience object to access translation, scaling,
// and rotation values as well as the center of transformation
let delta = interactionPoints.delta()
// The delta object is a convenience object to access translation, scaling,
// and rotation values as well as the center of transformation
let delta = interactionPoints.delta()
// Doctest.expect(delta.x, 1.5) // Doctest ERROR! Occurs for an unspecified period of time.
// Doctest.expect(delta.y, 2.5) // Doctest ERROR! Occurs for an unspecified period of time.
// Doctest.expect(delta.zoom > 1.5, true) // Doctest ERROR! Occurs for an unspecified period of time.
// Doctest.expect(delta.rotate < 0.2, true) // Doctest ERROR! Occurs for an unspecified period of time.
// Doctest.expect(delta.about, {x: 6.5, y: 7.5}) // Doctest ERROR! Occurs for an unspecified period of time.
// Doctest.expect(delta.x, 1.5) // Doctest ERROR! Occurs for an unspecified period of time.
// Doctest.expect(delta.y, 2.5) // Doctest ERROR! Occurs for an unspecified period of time.
// Doctest.expect(delta.zoom > 1.5, true) // Doctest ERROR! Occurs for an unspecified period of time.
// Doctest.expect(delta.rotate < 0.2, true) // Doctest ERROR! Occurs for an unspecified period of time.
// Doctest.expect(delta.about, {x: 6.5, y: 7.5}) // Doctest ERROR! Occurs for an unspecified period of time.
</script>
<p>
Interaction objects extend the idea of mapping touch ids to points to multiple target objects. Each touch id
is mapped not only to the changing points of this touch but also to the object that has been hit by the
starting touch point. This object is the target of the interaction and remains for the whole duration of the
multitouch gesture.
</p>
</script>
<p>Interaction objects extend the idea of mapping touch ids to
points to multiple target objects. Each touch id is mapped not only to the
changing points of this touch but also to the object that has been
hit by the starting touch point. This object is the target of the interaction
and remains for the whole duration of the multitouch gesture.
<h2>Interaction Delegate</h2>
<p>
The delegator registers all needed <code>TouchEvent</code>, <code>PointerEvent</code>, and
<code>MouseEvent</code>
handlers on a provided DOM elememt for a given target object, ensures that the events are captured by the
target and boils the event handling down to simple
<code>onStart</code>, <code>onMove</code>, <code>onEnd</code> events.
</p>
<p>
Let's look at an example of an InteractionDelegate and a target object that implements the
<code>IInteractionTarget</code> interface. Typically you setup the delegator in the constructor of the class
that uses the interation.
</p>
<script class="doctest">
class InteractionTarget {
// The constructor of the target creates the InteractionDelegate
constructor(domElement) {
this.interaction = new InteractionDelegate(domElement, this)
}
<h2>
Interaction Delegate
</h2>
<p>The delegator registers all needed <code>TouchEvent</code>,
<code>PointerEvent</code>, and <code>MouseEvent</code>
handlers on a provided DOM elememt for a given target object, ensures that the
events are captured by the target and boils the event handling down to simple
<code>onStart</code>, <code>onMove</code>, <code>onEnd</code> events.
// The following methods are needed by the IInteractionTarget interface
// Indicates that we want all events
capture(event) {
return true
}
<p>Let's look at an example of an InteractionDelegate and a target object that
implements the <code>IInteractionTarget</code> interface. Typically you setup
the delegator in the constructor of the class that uses the interation.
</p>
<script class="doctest">
// Handle collected touch points on start
onStart(event, points) {}
class InteractionTarget {
// Handle collected touch points on update
onMove(event, points) {}
// The constructor of the target creates the InteractionDelegate
constructor(domElement) {
this.interaction = new InteractionDelegate(domElement, this)
}
// Handle collected touch points on end
onEnd(event, points, ended) {}
// The following methods are needed by the IInteractionTarget interface
// Indicates that we want all events
capture(event) { return true }
// Handle collected touch points on start
onStart(event, points) {}
// Handle collected touch points on update
onMove(event, points) {}
// Handle collected touch points on end
onEnd(event, points, ended) {}
// Handle mouse wheel event
onMouseWheel(event) {}
}
</script>
<p>We can now check whether the promised interface methods are implemented by the
class:</p>
<script class="doctest">
Doctest.expect(IInteractionTarget.implementedBy(InteractionTarget), true)
</script>
<p>If we define an InteractionTarget that violates the IInteractionTarget interface
we get an error. The following example of an interaction target uses an
InteractionDelegate but does not implement the necessary methods:
</p>
<script class="doctest">
class InvalidInteractionTarget {
constructor(domElement) {
this.interaction = new InteractionDelegate(domElement, this, { debug: true})
}
}
try {
new InvalidInteractionTarget(null)
} catch (error) {
Doctest.expectError(error, "Expected IInteractionTarget")
}
</script>
<h2>
Interaction Mapper
</h2>
<p>Often we need to assign UI elements to touch and pointer events. This is
supported by a special InteractionMapper delegate. A InteractionMapper
maps events to specific parts of a container interaction target. The
InteractionTarget must implement a findTarget method that returns an
object implementing the IInteractionTarget interface.
</p>
<p>
If the InteractionTarget also implements a <code>mapPositionToPoint</code> method this
is used to map the points to the local coordinate space of the the target.
This makes it easier to lookup elements and relate events to local
positions.
</p>
<p>Let's see an example. A graph that uses an <code>InterationMapper</code> for it´s child
objects:
</p>
<script class="doctest">
class Graph {
constructor(domElement) {
this.interaction = new InteractionMapper(domElement, this)
this.nodes = [
new Node('a'),
new Node('b')
]
}
capture(event) { return true }
findTarget() {
for(let node of this.nodes) {
return node
// Handle mouse wheel event
onMouseWheel(event) {}
}
</script>
<p>We can now check whether the promised interface methods are implemented by the class:</p>
<script class="doctest">
Doctest.expect(IInteractionTarget.implementedBy(InteractionTarget), true)
</script>
<p>
If we define an InteractionTarget that violates the IInteractionTarget interface we get an error. The
following example of an interaction target uses an InteractionDelegate but does not implement the necessary
methods:
</p>
<script class="doctest">
class InvalidInteractionTarget {
constructor(domElement) {
this.interaction = new InteractionDelegate(domElement, this, { debug: true })
}
}
return null
}
}
class Node {
try {
new InvalidInteractionTarget(null)
} catch (error) {
Doctest.expectError(error, 'Expected IInteractionTarget')
}
</script>
constructor(name) {
this.name = name
}
<h2>Interaction Mapper</h2>
<p>
Often we need to assign UI elements to touch and pointer events. This is supported by a special
InteractionMapper delegate. A InteractionMapper maps events to specific parts of a container interaction
target. The InteractionTarget must implement a findTarget method that returns an object implementing the
IInteractionTarget interface.
</p>
<p>
If the InteractionTarget also implements a <code>mapPositionToPoint</code> method this is used to map the
points to the local coordinate space of the the target. This makes it easier to lookup elements and relate
events to local positions.
</p>
<p>Let's see an example. A graph that uses an <code>InterationMapper</code> for it´s child objects:</p>
<script class="doctest">
class Graph {
constructor(domElement) {
this.interaction = new InteractionMapper(domElement, this)
this.nodes = [new Node('a'), new Node('b')]
}
capture(event) { return true }
capture(event) {
return true
}
onStart(event, interaction) {
Doctest.log("onStart called")
}
findTarget() {
for (let node of this.nodes) {
return node
}
return null
}
}
onMove(event, interaction) {
Doctest.log("onMove called")
}
class Node {
constructor(name) {
this.name = name
}
onEnd(event, interaction) {
Doctest.log("onEnd called")
}
capture(event) {
return true
}
}
onStart(event, interaction) {
Doctest.log('onStart called')
}
</script>
<p>Now we simulate a sequence of <code>onStart, onMove, onEnd</code> events by calling
the registered event handlers programmatically. Note that the defined
event handlers log their calls.</p>
<script class="doctest">
onMove(event, interaction) {
Doctest.log('onMove called')
}
let graph = new Graph(window)
window.dispatchEvent(Doctest.event('mousedown'))
window.dispatchEvent(Doctest.event('mousemove'))
window.dispatchEvent(Doctest.event('mouseup'))
onEnd(event, interaction) {
Doctest.log('onEnd called')
}
}
</script>
<p>
Now we simulate a sequence of <code>onStart, onMove, onEnd</code> events by calling the registered event
handlers programmatically. Note that the defined event handlers log their calls.
</p>
<script class="doctest">
let graph = new Graph(window)
window.dispatchEvent(Doctest.event('mousedown'))
window.dispatchEvent(Doctest.event('mousemove'))
window.dispatchEvent(Doctest.event('mouseup'))
Doctest.expectLog('onStart called',
'onMove called',
'onEnd called')
</script>
<h2>
Simple Dragging
</h2>
<p>Drag & Drop is a common interaction pattern. This behavior can be accomplished
by a class that implements IInteractionMapperTarget as well as IInteractionTarget.
You can grab the blue circle with touches or mouse and drag it around.</p>
<div class="grayBorder" style="position: relative; width: 100%; height: 200px">
<div id="circle" style="position: absolute; left:50px; top: 50px; border-radius: 50%; width: 32px; height: 32px; background-color: blue;"></div>
</div>
<script class="doctest">
class Dragger {
constructor(element, container) {
/* The events are captured by the container but send to this
Doctest.expectLog('onStart called', 'onMove called', 'onEnd called')
</script>
<h2>Simple Dragging</h2>
<p>
Drag & Drop is a common interaction pattern. This behavior can be accomplished by a class that implements
IInteractionMapperTarget as well as IInteractionTarget. You can grab the blue circle with touches or mouse
and drag it around.
</p>
<div class="grayBorder" style="position: relative; width: 100%; height: 200px">
<div
id="circle"
style="
position: absolute;
left: 50px;
top: 50px;
border-radius: 50%;
width: 32px;
height: 32px;
background-color: blue;
"
></div>
</div>
<script class="doctest">
class Dragger {
constructor(element, container) {
/* The events are captured by the container but send to this
wrapper object, if the event target is identical to the wrapped
object. */
this.target = element
this.interaction = new InteractionMapper(container, this)
}
this.target = element
this.interaction = new InteractionMapper(container, this)
}
capture(event) { return true }
capture(event) {
return true
}
findTarget(event, localPoint, globalPoint) {
return (event.target == this.target) ? this : null
}
findTarget(event, localPoint, globalPoint) {
return event.target == this.target ? this : null
}
onStart(event, interaction) {
// Only needed to fulfill the IInteractionTarget interface
}
onStart(event, interaction) {
// Only needed to fulfill the IInteractionTarget interface
}
onMove(event, interaction) {
let move = interaction.move()
let x = parseInt(this.target.style.left) + move.x
let y = parseInt(this.target.style.top) + move.y
this.target.style.left = x + "px"
this.target.style.top = y + "px"
}
onMove(event, interaction) {
let move = interaction.move()
let x = parseInt(this.target.style.left) + move.x
let y = parseInt(this.target.style.top) + move.y
this.target.style.left = x + 'px'
this.target.style.top = y + 'px'
}
onEnd(event, interaction) {
// Only needed to fulfill the IInteractionTarget interface
}
onEnd(event, interaction) {
// Only needed to fulfill the IInteractionTarget interface
}
onMouseWheel(event) {
// Only needed to fulfill the IInteractionTarget interface
}
}
onMouseWheel(event) {
// Only needed to fulfill the IInteractionTarget interface
}
}
Doctest.expect(IInteractionMapperTarget.implementedBy(Dragger), true)
Doctest.expect(IInteractionTarget.implementedBy(Dragger), true)
Doctest.expect(IInteractionMapperTarget.implementedBy(Dragger), true)
Doctest.expect(IInteractionTarget.implementedBy(Dragger), true)
new Dragger(circle, document.body)
</script>
new Dragger(circle, document.body)
</script>
<h2>
Multitouch
</h2>
<p>
Multitouch-Events (simultaneous events) in browsers cannot be used by default.
Even libraries like jQuery do not fix this problem. The static method "on" of the
InteractionMapper allows simultaneous events and thus multitouch. The following
events (and their specializations) can be used in addition to the default browser
events: tap, doubletap, press, pan, swipe, pinch and rotate. See http://hammerjs.github.io
for more details.
</p>
<svg width="100%" height="300" viewBox="0 0 400 200" class="grayBorder" xmlns="http://www.w3.org/2000/svg" xmlns:bx="https://boxy-svg.com">
<defs>
<pattern id="pattern-1" viewBox="0 0 100 100" patternUnits="userSpaceOnUse" preserveAspectRatio="none" width="100" height="100" />
<linearGradient id="gradient-1" gradientUnits="userSpaceOnUse" x1="235.294" y1="5.386" x2="235.294" y2="63.218" gradientTransform="matrix(0.479375, 0.877612, -1.161752, 0.599143, 216.009222, -193.782169)">
<stop offset="0" style="stop-color: rgba(216, 216, 216, 1)" />
<stop offset="1" style="stop-color: rgb(45, 175, 182);" />
</linearGradient>
<linearGradient id="gradient-4" gradientUnits="userSpaceOnUse" x1="193.252" y1="126.988" x2="193.252" y2="163.836" gradientTransform="matrix(0.978752, 0, 0, 1.126983, 11.124972, -21.238213)">
<stop offset="0" style="stop-color: rgba(161, 110, 0, 1)" />
<stop offset="1" style="stop-color: rgba(59, 40, 0, 1)" />
</linearGradient>
</defs>
<g id="hammer-1">
<rect x="55.329" y="20.25" width="42.523" height="42.523" style="fill: rgb(236, 229, 24); stroke: rgb(0, 179, 207); stroke-width: 2; stroke-linejoin: bevel;" />
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre;" x="68.736" y="45.811">tap</text>
</g>
<g id="hammer-2">
<path d="M 372 149 m -25.496 0 a 25.496 26.092 0 1 0 50.992 0 a 25.496 26.092 0 1 0 -50.992 0 Z M 372 149 m -15.297 0 a 15.297 15.654 0 0 1 30.594 0 a 15.297 15.654 0 0 1 -30.594 0 Z" transform="matrix(-0.535925, 0.844266, -0.844265, -0.535925, 499.054353, -194.103207)" style="fill: rgb(194, 59, 59); stroke: rgb(141, 10, 91); stroke-width: 2;" bx:shape="ring 372 149 15.297 15.654 25.496 26.092 1@9ddd52c9" />
<text transform="matrix(1.226643, 0, 0, 1.226643, 42.737137, 26.559669)" style="fill: rgb(51, 51, 51); font-size: 9.7828px; white-space: pre;">
<tspan x="94.401" y="44.224">press</tspan>
<tspan x="94.401" dy="1em"></tspan>
</text>
</g>
<g>
<polygon id="hammer-3" style="fill: url(#gradient-1); stroke: rgb(182, 40, 92);" points="272.369 20.761 234.949 23.029 227.862 34.652 236.65 46.558 253.943 55.63 293.347 59.315 340.406 62.434 371.306 49.96 374.708 32.951 356.282 29.549 333.319 21.044 311.774 9.705 307.238 23.029 322.263 33.518 347.777 44.007 339.839 48.259 315.459 48.826 292.781 45.991 281.725 32.667 285.977 17.643 281.158 4.602 267.55 6.303 252.525 6.587 252.809 11.973 255.36 17.076 263.014 16.225 267.834 13.674 273.787 13.39 276.622 14.808" />
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre;" x="296.849" y="80.823">pan</text>
</g>
<g>
<ellipse transform="matrix(-0.707107, 0.707107, -0.707107, -0.707107, 362.152622, 115.748229)" cx="221.437" cy="181.098" rx="27.616" ry="27.616" style="fill: rgb(149, 26, 133); stroke: rgb(73, 4, 62); stroke-width: 4; fill-opacity: 0.69;" />
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre;" x="61.759" y="190.447">swipe</text>
</g>
<g>
<rect id="hammer-5" x="146.389" y="121.875" width="107.762" height="41.527" style="fill: url(#gradient-4); stroke-linejoin: round; stroke: rgb(0, 0, 0);" />
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre;" x="187.854" y="190.447">pinch</text>
</g>
<g transform="matrix(1.286049, 0, 0, 1.286049, -103.444145, -48.307945)">
<path class="star" d="M 937 394.847 L 946.206 421.33 L 974.238 421.901 L 951.895 438.84 L 960.014 465.675 L 937 449.661 L 913.986 465.675 L 922.105 438.84 L 899.762 421.901 L 927.794 421.33 Z" transform="matrix(-0.809017, 0.587785, -0.587785, -0.809018, 1346.787902, -60.391979)" style="fill: rgb(83, 230, 226); stroke: rgb(24, 111, 116); stroke-width: 2; stroke-linejoin: round; stroke-dasharray: 2px;" bx:shape="star 937 434 39.154 39.153 0.4 5 1@05a6f642" />
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre;" x="316.854" y="190.447">rotate</text>
</g>
<g transform="matrix(1.286049, 0, 0, 1.286049, -28.431454, -48.307941)">
<path class="star" d="M 937 394.847 L 946.206 421.33 L 974.238 421.901 L 951.895 438.84 L 960.014 465.675 L 937 449.661 L 913.986 465.675 L 922.105 438.84 L 899.762 421.901 L 927.794 421.33 Z" transform="matrix(-0.809017, 0.587785, -0.587785, -0.809018, 1346.787902, -60.391979)" style="fill: rgb(83, 230, 226); stroke: rgb(24, 111, 116); stroke-width: 2; stroke-linejoin: round; stroke-dasharray: 2px;" bx:shape="star 937 434 39.154 39.153 0.4 5 1@05a6f642" />
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre;" x="316.854" y="190.447">rotate</text>
</g>
</svg>
<h2>Multitouch</h2>
<p>
Multitouch-Events (simultaneous events) in browsers cannot be used by default. Even libraries like jQuery do
not fix this problem. The static method "on" of the InteractionMapper allows simultaneous events and thus
multitouch. The following events (and their specializations) can be used in addition to the default browser
events: tap, doubletap, press, pan, swipe, pinch and rotate. See http://hammerjs.github.io for more details.
</p>
<svg
width="100%"
height="300"
viewBox="0 0 400 200"
class="grayBorder"
xmlns="http://www.w3.org/2000/svg"
xmlns:bx="https://boxy-svg.com"
>
<defs>
<pattern
id="pattern-1"
viewBox="0 0 100 100"
patternUnits="userSpaceOnUse"
preserveAspectRatio="none"
width="100"
height="100"
/>
<linearGradient
id="gradient-1"
gradientUnits="userSpaceOnUse"
x1="235.294"
y1="5.386"
x2="235.294"
y2="63.218"
gradientTransform="matrix(0.479375, 0.877612, -1.161752, 0.599143, 216.009222, -193.782169)"
>
<stop offset="0" style="stop-color: rgba(216, 216, 216, 1)" />
<stop offset="1" style="stop-color: rgb(45, 175, 182)" />
</linearGradient>
<linearGradient
id="gradient-4"
gradientUnits="userSpaceOnUse"
x1="193.252"
y1="126.988"
x2="193.252"
y2="163.836"
gradientTransform="matrix(0.978752, 0, 0, 1.126983, 11.124972, -21.238213)"
>
<stop offset="0" style="stop-color: rgba(161, 110, 0, 1)" />
<stop offset="1" style="stop-color: rgba(59, 40, 0, 1)" />
</linearGradient>
</defs>
<g id="hammer-1">
<rect
x="55.329"
y="20.25"
width="42.523"
height="42.523"
style="fill: rgb(236, 229, 24); stroke: rgb(0, 179, 207); stroke-width: 2; stroke-linejoin: bevel"
/>
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre" x="68.736" y="45.811">tap</text>
</g>
<g id="hammer-2">
<path
d="M 372 149 m -25.496 0 a 25.496 26.092 0 1 0 50.992 0 a 25.496 26.092 0 1 0 -50.992 0 Z M 372 149 m -15.297 0 a 15.297 15.654 0 0 1 30.594 0 a 15.297 15.654 0 0 1 -30.594 0 Z"
transform="matrix(-0.535925, 0.844266, -0.844265, -0.535925, 499.054353, -194.103207)"
style="fill: rgb(194, 59, 59); stroke: rgb(141, 10, 91); stroke-width: 2"
bx:shape="ring 372 149 15.297 15.654 25.496 26.092 1@9ddd52c9"
/>
<text
transform="matrix(1.226643, 0, 0, 1.226643, 42.737137, 26.559669)"
style="fill: rgb(51, 51, 51); font-size: 9.7828px; white-space: pre"
>
<tspan x="94.401" y="44.224">press</tspan>
<tspan x="94.401" dy="1em"></tspan>
</text>
</g>
<g>
<polygon
id="hammer-3"
style="fill: url(#gradient-1); stroke: rgb(182, 40, 92)"
points="272.369 20.761 234.949 23.029 227.862 34.652 236.65 46.558 253.943 55.63 293.347 59.315 340.406 62.434 371.306 49.96 374.708 32.951 356.282 29.549 333.319 21.044 311.774 9.705 307.238 23.029 322.263 33.518 347.777 44.007 339.839 48.259 315.459 48.826 292.781 45.991 281.725 32.667 285.977 17.643 281.158 4.602 267.55 6.303 252.525 6.587 252.809 11.973 255.36 17.076 263.014 16.225 267.834 13.674 273.787 13.39 276.622 14.808"
/>
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre" x="296.849" y="80.823">pan</text>
</g>
<g>
<ellipse
transform="matrix(-0.707107, 0.707107, -0.707107, -0.707107, 362.152622, 115.748229)"
cx="221.437"
cy="181.098"
rx="27.616"
ry="27.616"
style="fill: rgb(149, 26, 133); stroke: rgb(73, 4, 62); stroke-width: 4; fill-opacity: 0.69"
/>
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre" x="61.759" y="190.447">
swipe
</text>
</g>
<g>
<rect
id="hammer-5"
x="146.389"
y="121.875"
width="107.762"
height="41.527"
style="fill: url(#gradient-4); stroke-linejoin: round; stroke: rgb(0, 0, 0)"
/>
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre" x="187.854" y="190.447">
pinch
</text>
</g>
<g transform="matrix(1.286049, 0, 0, 1.286049, -103.444145, -48.307945)">
<path
class="star"
d="M 937 394.847 L 946.206 421.33 L 974.238 421.901 L 951.895 438.84 L 960.014 465.675 L 937 449.661 L 913.986 465.675 L 922.105 438.84 L 899.762 421.901 L 927.794 421.33 Z"
transform="matrix(-0.809017, 0.587785, -0.587785, -0.809018, 1346.787902, -60.391979)"
style="
fill: rgb(83, 230, 226);
stroke: rgb(24, 111, 116);
stroke-width: 2;
stroke-linejoin: round;
stroke-dasharray: 2px;
"
bx:shape="star 937 434 39.154 39.153 0.4 5 1@05a6f642"
/>
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre" x="316.854" y="190.447">
rotate
</text>
</g>
<g transform="matrix(1.286049, 0, 0, 1.286049, -28.431454, -48.307941)">
<path
class="star"
d="M 937 394.847 L 946.206 421.33 L 974.238 421.901 L 951.895 438.84 L 960.014 465.675 L 937 449.661 L 913.986 465.675 L 922.105 438.84 L 899.762 421.901 L 927.794 421.33 Z"
transform="matrix(-0.809017, 0.587785, -0.587785, -0.809018, 1346.787902, -60.391979)"
style="
fill: rgb(83, 230, 226);
stroke: rgb(24, 111, 116);
stroke-width: 2;
stroke-linejoin: round;
stroke-dasharray: 2px;
"
bx:shape="star 937 434 39.154 39.153 0.4 5 1@05a6f642"
/>
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre" x="316.854" y="190.447">
rotate
</text>
</g>
</svg>
<script class="doctest">
<script class="doctest">
const from = { scale: 1 }
const to = { scale: 1.3, transformOrigin: 'center', repeat: 1, yoyo: true }
const from = {scale: 1}
const to = {scale: 1.3, transformOrigin: 'center', repeat: 1, yoyo: true}
InteractionMapper.on('tap', document.getElementById('hammer-1'), event => {
TweenLite.fromTo(event.target, .2, from, to)
})
InteractionMapper.on('tap', document.getElementById('hammer-1'), (event) => {
TweenLite.fromTo(event.target, 0.2, from, to)
})
InteractionMapper.on('press', document.getElementById('hammer-2'), event => {
TweenLite.fromTo(event.target, .2, from, to)
}, {time: 1000})
InteractionMapper.on(
'press',
document.getElementById('hammer-2'),
(event) => {
TweenLite.fromTo(event.target, 0.2, from, to)
},
{ time: 1000 }
)
InteractionMapper.on('panright pandown', document.getElementById('hammer-3'), event => {
TweenLite.fromTo(event.target, .2, from, to)
})
InteractionMapper.on('panright pandown', document.getElementById('hammer-3'), (event) => {
TweenLite.fromTo(event.target, 0.2, from, to)
})
InteractionMapper.on(['swipeleft', 'swipedown'], document.getElementsByTagName('ellipse'), event => {
TweenLite.fromTo(event.target, .2, from, to)
})
InteractionMapper.on(['swipeleft', 'swipedown'], document.getElementsByTagName('ellipse'), (event) => {
TweenLite.fromTo(event.target, 0.2, from, to)
})
InteractionMapper
.on('pinch', document.getElementById('hammer-5'), event => {
TweenLite.fromTo(event.target, .2, from, to)
})
InteractionMapper.on('pinch', document.getElementById('hammer-5'), (event) => {
TweenLite.fromTo(event.target, 0.2, from, to)
})
InteractionMapper
.on('rotate', document.querySelectorAll('svg g > path.star'), event => {
TweenLite.fromTo(event.target, .2, from, to)
})
InteractionMapper.on('rotate', document.querySelectorAll('svg g > path.star'), (event) => {
TweenLite.fromTo(event.target, 0.2, from, to)
})
InteractionMapper
.on('click', document.getElementById('hammer-1'), event => {
console.log(event)
})
</script>
InteractionMapper.on('click', document.getElementById('hammer-1'), (event) => {
console.log(event)
})
</script>
<h2>
References
</h2>
<ul>
<li><a href="https://www.amazon.de/Patterns-Elements-Reusable-Object-Oriented-Software/dp/0201633612">Design Patterns [p. 20]</a></li>
<li><a href="http://hammerjs.github.io">Hammer.js</a></li>
</ul>
</body>
<h2>References</h2>
<ul>
<li>
<a href="https://www.amazon.de/Patterns-Elements-Reusable-Object-Oriented-Software/dp/0201633612"
>Design Patterns [p. 20]</a
>
</li>
<li><a href="http://hammerjs.github.io">Hammer.js</a></li>
</ul>
</body>
</html>

BIN
lib/interaction.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1002 KiB

View File

@ -1,50 +1,43 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../css/doctest.css">
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.js"></script>
</head>
<body onload="Doctest.run()" >
<h1>
Interfaces
</h1>
<p>
Interfaces are objects that specify (document) the external behavior of objects
that “provide” them. An interface specifies behavior through method definitions
that specify functions and their signatures.
</p>
<p>Let's look at an example of an interface and a class implementing the interface:</p>
<script class="doctest">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../css/doctest.css" />
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.js"></script>
</head>
<body onload="Doctest.run()">
<h1><a href="index.html">lib.</a>Interfaces</h1>
<p>
Interfaces are objects that specify (document) the external behavior of objects that “provide” them. An
interface specifies behavior through method definitions that specify functions and their signatures.
</p>
<p>Let's look at an example of an interface and a class implementing the interface:</p>
<script class="doctest">
class ITestable extends Interface {
reset() {}
run() {}
}
class ITestable extends Interface {
reset() {}
run() {}
}
class Testable {
reset() {
print('Resetting testable object')
}
class Testable {
reset() {
print("Resetting testable object")
}
run() {
print("Running testable object")
}
}
</script>
<p>We can now check whether the promised interface methods are implemented by the
class:</p>
<script class="doctest">
Doctest.expect(ITestable.implementedBy(Testable), true)
</script>
<p>
<h2>
References
</h2>
<ul>
<li><a href="https://zopeinterface.readthedocs.io">Zope Interfaces</a></li>
</ul>
</body>
run() {
print('Running testable object')
}
}
</script>
<p>We can now check whether the promised interface methods are implemented by the class:</p>
<script class="doctest">
Doctest.expect(ITestable.implementedBy(Testable), true)
</script>
<p></p>
<h2>References</h2>
<ul>
<li><a href="https://zopeinterface.readthedocs.io">Zope Interfaces</a></li>
</ul>
</body>
</html>

View File

@ -1,33 +1,30 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Logging Doctest</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../css/doctest.css" />
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.3rdparty.js"></script>
<script src="../dist/iwmlib.js"></script>
</head>
<head>
<title>Logging Doctest</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../css/doctest.css">
<script src="./3rdparty/highlight/highlight.pack.js"></script>
<script src="../dist/iwmlib.3rdparty.js"></script>
<script src="../dist/iwmlib.js"></script>
</head>
<body id="page" onload="Doctest.run()">
<h1><a href="index.html">lib.</a>Logging</h1>
<p>Store informations of your app permanently or use app specific logging functions.</p>
<script class="doctest">
Logging.log('app started')
Logging.warn("shouldn't happen")
Logging.error('restart')
<body id="page" onload="Doctest.run()">
<h1>
Logging
</h1>
<p>Store informations of your app permanently or use app specific logging functions.</p>
<script class="doctest">
Logging.log('app started')
Logging.warn("shouldn't happen")
Logging.error('restart')
Logging.setup({ log: message => console.log("app specific" + message) })
Logging.log("now app related")
</script>
<p>You can overwrite the log, warn, and error handler by using Logging.setup with
app specific functions.</p>
<script class="doctest">
Logging.setup({ log: message => console.log("app specific" + message) })
Logging.log("now app related")
</script>
</body>
Logging.setup({ log: (message) => console.log('app specific' + message) })
Logging.log('now app related')
</script>
<p>You can overwrite the log, warn, and error handler by using Logging.setup with app specific functions.</p>
<script class="doctest">
Logging.setup({ log: (message) => console.log('app specific' + message) })
Logging.log('now app related')
</script>
</body>
</html>

View File

@ -15,7 +15,7 @@
<script src="../../dist/iwmlib.pixi.js"></script>
</head>
<body onload="Doctest.run()">
<h1>Application</h1>
<h1><a href="../index.html">lib.</a><a href="index.html">pixi.</a>Application</h1>
<p>
The class PIXIApp is the main entry point to create a new PIXI Application.
It inherits from PIXI.Application, set meaningfull defaults, creates a scene and

BIN
lib/pixi/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 KiB

View File

@ -1,36 +1,40 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>PIXI.Application Doctest</title>
<title>PIXI.Application Doctest</title>
<script src="../../dist/iwmlib.3rdparty.js"></script>
<link rel="stylesheet" href=".././3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../../css/doctest.css" />
<script src="../../dist/iwmlib.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script>
</head>
<body>
<h1>Vanilla PIXI.Application</h1>
<script src="../../dist/iwmlib.3rdparty.js"></script>
<script>
const app = new PIXI.Application({
width: 450,
height: 150
})
<script src="../../dist/iwmlib.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script>
</head>
<body>
<h1><a href="../index.html">lib.</a><a href="index.html">pixi.</a>Vanilla PIXI.Application</h1>
// Add the view to the DOM
document.body.appendChild(app.view)
<script>
const app = new PIXI.Application({
width: 450,
height: 150
})
// ex, add display objects
const sprite = PIXI.Sprite.from('./assets/app-circle.png')
sprite.scale.set(.3, .3)
app.stage.addChild(sprite)
// Add the view to the DOM
document.body.appendChild(app.view)
sprite.interactive = true
sprite.buttonMode = true
sprite.on('click', e => {
console.log('sprite clicked')
})
</script>
</body>
// ex, add display objects
const sprite = PIXI.Sprite.from('./assets/app-circle.png')
sprite.scale.set(0.3, 0.3)
app.stage.addChild(sprite)
sprite.interactive = true
sprite.buttonMode = true
sprite.on('click', (e) => {
console.log('sprite clicked')
})
</script>
</body>
</html>

BIN
lib/pixi/application.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

@ -5,10 +5,7 @@
<title>PIXI Badge</title>
<link
rel="stylesheet"
href="../3rdparty/highlight/styles/default.css"
/>
<link rel="stylesheet" href="../3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../../css/doctest.css" />
<script src="../3rdparty/highlight/highlight.pack.js"></script>
@ -18,16 +15,12 @@
<script src="../../dist/iwmlib.pixi.js"></script>
</head>
<body onload="Doctest.run()">
<h1>Badge</h1>
<p>
Small and adaptive tag for adding context to just about any content.
</p>
<h1><a href="../index.html">lib.</a><a href="index.html">pixi.</a>Badge</h1>
<p>Small and adaptive tag for adding context to just about any content.</p>
<p>Let's look at some badge examples:</p>
<br />
<canvas id="canvas" class="interactive"></canvas>
<p>
What you should see: Badges, badges, badges...
</p>
<p>What you should see: Badges, badges, badges...</p>
<script class="doctest">
const app = new PIXIApp({
view: canvas,
@ -120,13 +113,11 @@
fill: 0xfe9727
})
let sprite1 = new PIXI.Sprite(
PIXI.Texture.from('./assets/badge-1.mp4')
)
let sprite1 = new PIXI.Sprite(PIXI.Texture.from('./assets/badge-1.mp4'))
sprite1.scale.set(0.05, 0.05)
let texture1 = PIXI.Texture.from('./assets/badge-1.mp4')
texture1.baseTexture.on('loaded', e => {
texture1.baseTexture.on('loaded', (e) => {
let sprite1 = new PIXI.Sprite(texture1)
sprite1.scale.set(0.05, 0.05)
sprite1.alpha = 0.5
@ -145,14 +136,7 @@
})
app.scene.addChild(circle1, circle2)
app.scene.addChild(
button1,
button2,
button3,
button4,
button5,
button6
)
app.scene.addChild(button1, button2, button3, button4, button5, button6)
app.scene.addChild(badge1, badge2, badge3)
</script>
</body>

BIN
lib/pixi/badge.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 KiB

View File

@ -1,50 +1,51 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>PIXI BlurFilter</title>
<title>PIXI BlurFilter</title>
<link rel="stylesheet" href=".././3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../../css/doctest.css">
<link rel="stylesheet" href=".././3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../../css/doctest.css" />
<script src=".././3rdparty/highlight/highlight.pack.js"></script>
<script src="../../dist/iwmlib.3rdparty.js"></script>
<script src=".././3rdparty/highlight/highlight.pack.js"></script>
<script src="../../dist/iwmlib.3rdparty.js"></script>
<script src="../../dist/iwmlib.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script>
</head>
<body onload="Doctest.run()">
<h1>BlurFilter</h1>
<p>
The BlurFilter class creates a blur filter on the renderer. In contrast to the PIXI BlurFilter, you can specify
a range (defined as a PIXI.Rectangle) on which the filter should be applied.
</p>
<h2>Example with Image</h2>
<p>Let's look at an example of creating a new blur filter near the bottom:</p>
<canvas id="canvas" class="interactive"></canvas>
<p>
What you should see: A sniffing hedgehog and three blurred areas (with different strengths of blur).
</p>
<script class="doctest">
// Create the app
const app = new PIXIApp({
view: canvas,
width: 480,
height: 270,
transparent: false
}).setup().run()
<script src="../../dist/iwmlib.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script>
</head>
<body onload="Doctest.run()">
<h1><a href="../index.html">lib.</a><a href="index.html">pixi.</a>BlurFilter</h1>
<p>
The BlurFilter class creates a blur filter on the renderer. In contrast to the PIXI BlurFilter, you can
specify a range (defined as a PIXI.Rectangle) on which the filter should be applied.
</p>
<h2>Example with Image</h2>
<p>Let's look at an example of creating a new blur filter near the bottom:</p>
<canvas id="canvas" class="interactive"></canvas>
<p>What you should see: A sniffing hedgehog and three blurred areas (with different strengths of blur).</p>
<script class="doctest">
// Create the app
const app = new PIXIApp({
view: canvas,
width: 480,
height: 270,
transparent: false
})
.setup()
.run()
// Load a video and add it to the scene
const videoSprite = new PIXI.Sprite(PIXI.Texture.from("assets/blurfilter.mp4"))
videoSprite.width = app.size.width
videoSprite.height = app.size.height
app.scene.addChild(videoSprite)
// Load a video and add it to the scene
const videoSprite = new PIXI.Sprite(PIXI.Texture.from('assets/blurfilter.mp4'))
videoSprite.width = app.size.width
videoSprite.height = app.size.height
app.scene.addChild(videoSprite)
// Create three filters and assign them to the scene
const blurFilter1 = new BlurFilter(new PIXI.Rectangle(40, 40, 120, 80))
const blurFilter2 = new BlurFilter(new PIXI.Circle(240, 140, 60), 150)
const blurFilter3 = new BlurFilter(new PIXI.Rectangle(380, 40, 100, 100), 20)
app.scene.filters = [blurFilter1, blurFilter2, blurFilter3]
</script>
</body>
// Create three filters and assign them to the scene
const blurFilter1 = new BlurFilter(new PIXI.Rectangle(40, 40, 120, 80))
const blurFilter2 = new BlurFilter(new PIXI.Circle(240, 140, 60), 150)
const blurFilter3 = new BlurFilter(new PIXI.Rectangle(380, 40, 100, 100), 20)
app.scene.filters = [blurFilter1, blurFilter2, blurFilter3]
</script>
</body>
</html>

BIN
lib/pixi/blurfilter.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -1,338 +1,445 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>PIXI Button</title>
<title>PIXI Button</title>
<link rel="stylesheet" href="../3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../../css/doctest.css">
<link rel="stylesheet" href="../3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../../css/doctest.css" />
<script src="../3rdparty/highlight/highlight.pack.js"></script>
<script src="../../dist/iwmlib.3rdparty.js"></script>
<script src="../3rdparty/highlight/highlight.pack.js"></script>
<script src="../../dist/iwmlib.3rdparty.js"></script>
<script src="../../dist/iwmlib.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script>
</head>
<body onload="Doctest.run()">
<h1>Button</h1>
<p>
The Button class defines a clickable/touchable button. Use custom button styles for actions in forms, dialogs,
and more with support for multiple sizes, states, and more. Buttons will appear pressed when active. Make
buttons look inactive by setting the disabled state to true. To allow changing the state between active/inactive, set
the button type to "checkbox".
</p>
<p><a href="../../doc/out/Button.html">JavaScript API</a></p>
<p>Let's look at some button examples:</p><br />
<canvas id="canvas" class="interactive"></canvas>
<p>
What you should see: Many buttons with very different styling and behaviour.
</p>
<script class="doctest">
const app = new PIXIApp({
view: canvas,
width: 900,
height: 600,
transparent: false
}).setup().run()
<script src="../../dist/iwmlib.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script>
</head>
<body onload="Doctest.run()">
<h1><a href="../index.html">lib.</a><a href="index.html">pixi.</a>Button</h1>
<p>
The Button class defines a clickable/touchable button. Use custom button styles for actions in forms,
dialogs, and more with support for multiple sizes, states, and more. Buttons will appear pressed when
active. Make buttons look inactive by setting the disabled state to true. To allow changing the state
between active/inactive, set the button type to "checkbox".
</p>
<p><a href="../../doc/out/Button.html">JavaScript API</a></p>
<p>Let's look at some button examples:</p>
<br />
<canvas id="canvas" class="interactive"></canvas>
<p>What you should see: Many buttons with very different styling and behaviour.</p>
<script class="doctest">
const app = new PIXIApp({
view: canvas,
width: 900,
height: 600,
transparent: false
})
.setup()
.run()
const button1 = new Button({x: 10, y: 10})
const button1 = new Button({ x: 10, y: 10 })
const button2 = new Button({
theme: 'red',
x: 60,
y: 10,
label: 'Button',
type: 'checkbox',
action: e => {
console.info('Button clicked')
}
})
const button2 = new Button({
theme: 'red',
x: 60,
y: 10,
label: 'Button',
type: 'checkbox',
action: (e) => {
console.info('Button clicked')
}
})
const button3 = new Button({
x: 150,
y: 10,
label: 'Checkbox button',
type: 'checkbox',
action: e => {
console.info('Button clicked', e)
}
})
const button3 = new Button({
x: 150,
y: 10,
label: 'Checkbox button',
type: 'checkbox',
action: (e) => {
console.info('Button clicked', e)
}
})
const button4 = new Button({
x: 330,
y: 10,
label: 'Disabled button',
disabled: true,
action: e => {
console.info('Disabled button clicked')
}
})
const button4 = new Button({
x: 330,
y: 10,
label: 'Disabled button',
disabled: true,
action: (e) => {
console.info('Disabled button clicked')
}
})
const button5 = new Button({
x: 500,
y: 10,
label: 'Active button',
active: true
})
const button5 = new Button({
x: 500,
y: 10,
label: 'Active button',
active: true
})
const button6 = new Button({
x: 650,
y: 10,
label: 'Active disabled button',
type: 'checkbox',
active: true,
disabled: true
})
const button6 = new Button({
x: 650,
y: 10,
label: 'Active disabled button',
type: 'checkbox',
active: true,
disabled: true
})
const button7 = new Button({
x: 10,
y: 70,
label: 'Icon button',
type: 'checkbox',
active: true,
icon: 'arrow_back'
})
const button7 = new Button({
x: 10,
y: 70,
label: 'Icon button',
type: 'checkbox',
active: true,
icon: 'arrow_back'
})
const button8 = new Button({
x: 180,
y: 70,
theme: 'light',
label: 'Icon button',
icon: 'arrow_forward',
type: 'checkbox',
iconPosition: 'right'
})
const button8 = new Button({
x: 180,
y: 70,
theme: 'light',
label: 'Icon button',
icon: 'arrow_forward',
type: 'checkbox',
iconPosition: 'right'
})
const button9 = new Button({
x: 10,
y: 130,
type: 'checkbox',
icon: 'play_arrow',
iconActive: 'pause'
})
const button9 = new Button({
x: 10,
y: 130,
type: 'checkbox',
icon: 'play_arrow',
iconActive: 'pause'
})
const button10 = new Button({
x: 60,
y: 130,
icon: 'stop',
action: function() {
this.iconColor = Math.round(Math.random() * 16777215)
}
})
const button10 = new Button({
x: 60,
y: 130,
icon: 'stop',
action: function () {
this.iconColor = Math.round(Math.random() * 16777215)
}
})
const button11 = new Button({
x: 110,
y: 130,
icon: 'star_border',
tooltip: 'Bookmark'
})
const button11 = new Button({
x: 110,
y: 130,
icon: 'star_border',
tooltip: 'Bookmark'
})
const button12 = new Button({
x: 10,
y: 190,
icon: 'airplay',
fillAlpha: 0,
strokeAlpha: 0,
iconColor: 0xdd0000,
iconColorActive: 0x00dd00,
fillActiveAlpha: 0,
strokeActiveAlpha: 0,
type: 'checkbox'
})
const button12 = new Button({
x: 10,
y: 190,
icon: 'airplay',
fillAlpha: 0,
strokeAlpha: 0,
iconColor: 0xdd0000,
iconColorActive: 0x00dd00,
fillActiveAlpha: 0,
strokeActiveAlpha: 0,
type: 'checkbox'
})
const button13 = new Button({
x: 50,
y: 190,
label: 'Button',
fillAlpha: 0,
strokeAlpha: 0,
fillActiveAlpha: 0,
strokeActiveAlpha: 0,
textStyle: {
fontSize: 20,
stroke: 'brown',
fill: 'orange',
strokeThickness: 4,
miterLimit: 1,
letterSpacing: 6
},
textStyleActive: {
fontSize: 20,
stroke: 'orange',
fill: 'brown',
strokeThickness: 4,
fontWeight: 'bold',
miterLimit: 1,
letterSpacing: 5
},
type: 'checkbox'
})
const button13 = new Button({
x: 50,
y: 190,
label: 'Button',
fillAlpha: 0,
strokeAlpha: 0,
fillActiveAlpha: 0,
strokeActiveAlpha: 0,
textStyle: {
fontSize: 20,
stroke: 'brown',
fill: 'orange',
strokeThickness: 4,
miterLimit: 1,
letterSpacing: 6
},
textStyleActive: {
fontSize: 20,
stroke: 'orange',
fill: 'brown',
strokeThickness: 4,
fontWeight: 'bold',
miterLimit: 1,
letterSpacing: 5
},
type: 'checkbox'
})
const button14 = new Button({
x: 10,
y: 250,
label: 'Button',
type: 'checkbox',
icon: null,
iconActive: 'add_circle'
})
const button14 = new Button({
x: 10,
y: 250,
label: 'Button',
type: 'checkbox',
icon: null,
iconActive: 'add_circle'
})
const button15 = new Button({
x: 200,
y: 250,
label: 'Button',
type: 'checkbox',
icon: 'add_circle',
iconActive: null
})
const button15 = new Button({
x: 200,
y: 250,
label: 'Button',
type: 'checkbox',
icon: 'add_circle',
iconActive: null
})
const button16 = new Button({
x: 400,
y: 250,
label: 'Button',
type: 'checkbox',
icon: null,
iconActive: 'add_circle',
active: true
})
const button16 = new Button({
x: 400,
y: 250,
label: 'Button',
type: 'checkbox',
icon: null,
iconActive: 'add_circle',
active: true
})
const button17 = new Button({
x: 600,
y: 250,
label: 'Button',
type: 'checkbox',
icon: 'add_circle',
iconActive: null,
active: true
})
const button17 = new Button({
x: 600,
y: 250,
label: 'Button',
type: 'checkbox',
icon: 'add_circle',
iconActive: null,
active: true
})
let graphic1 = new PIXI.Graphics()
graphic1.beginFill(0xd7a3f9)
graphic1.drawCircle(10, 10, 10)
let graphic1 = new PIXI.Graphics()
graphic1.beginFill(0xd7a3f9)
graphic1.drawCircle(10, 10, 10)
let graphic2 = new PIXI.Graphics()
graphic2.beginFill(0x40c3f2)
graphic2.drawCircle(30, 30, 30)
let graphic2 = new PIXI.Graphics()
graphic2.beginFill(0x40c3f2)
graphic2.drawCircle(30, 30, 30)
const button18 = new Button({
x: 10,
y: 310,
label: 'Button',
type: 'checkbox',
icon: graphic1,
iconActive: graphic2
})
const button18 = new Button({
x: 10,
y: 310,
label: 'Button',
type: 'checkbox',
icon: graphic1,
iconActive: graphic2
})
let graphic3 = new PIXI.Graphics()
graphic3.beginFill(0xfd6b6a)
graphic3.drawCircle(2, 2, 2)
let graphic3 = new PIXI.Graphics()
graphic3.beginFill(0xfd6b6a)
graphic3.drawCircle(2, 2, 2)
let graphic4 = new PIXI.Graphics()
graphic4.beginFill(0xf8ce2d)
graphic4.drawCircle(40, 40, 40)
let graphic4 = new PIXI.Graphics()
graphic4.beginFill(0xf8ce2d)
graphic4.drawCircle(40, 40, 40)
const button19 = new Button({
x: 200,
y: 310,
label: 'Button',
type: 'checkbox',
icon: graphic3,
iconActive: graphic4,
active: true,
iconPosition: 'right'
})
const button19 = new Button({
x: 200,
y: 310,
label: 'Button',
type: 'checkbox',
icon: graphic3,
iconActive: graphic4,
active: true,
iconPosition: 'right'
})
const button20 = new Button({
x: 400,
y: 310,
label: 'Link Button',
type: 'checkbox',
style: 'link',
action: event => {
console.log('Link button clicked')
}
})
const button20 = new Button({
x: 400,
y: 310,
label: 'Link Button',
type: 'checkbox',
style: 'link',
action: (event) => {
console.log('Link button clicked')
}
})
const button21 = new Button({
x: 600,
y: 310,
minWidth: 70,
minHeight: 70,
icon: 'loop',
type: 'checkbox',
style: 'link'
})
const button21 = new Button({
x: 600,
y: 310,
minWidth: 70,
minHeight: 70,
icon: 'loop',
type: 'checkbox',
style: 'link'
})
const button22 = new Button({
x: 10,
y: 440,
icon: 'play_arrow',
badge: '19'
})
const button22 = new Button({
x: 10,
y: 440,
icon: 'play_arrow',
badge: '19'
})
const button23 = new Button({
x: 100,
y: 440,
icon: 'stop',
badge: 'Stop'
})
const button23 = new Button({
x: 100,
y: 440,
icon: 'stop',
badge: 'Stop'
})
const button24 = new Button({
x: 200,
y: 440,
icon: 'star_border',
badge: {
content: 'Bookmark',
align: 'center',
verticalAlign: 'bottom',
offsetTop: 8,
radius: 14,
fill: 0xfe832d
}
})
const button24 = new Button({
x: 200,
y: 440,
icon: 'star_border',
badge: {
content: 'Bookmark',
align: 'center',
verticalAlign: 'bottom',
offsetTop: 8,
radius: 14,
fill: 0xfe832d
}
})
const button25 = new Button({
x: 300,
y: 460,
icon: 'add',
badge: {
content: 'Sweden',
align: 'center',
verticalAlign: 'top',
offsetTop: -20,
radius: 12,
fill: 0x5856d6
}
})
const button25 = new Button({
x: 300,
y: 460,
icon: 'add',
badge: {
content: 'Sweden',
align: 'center',
verticalAlign: 'top',
offsetTop: -20,
radius: 12,
fill: 0x5856d6
}
})
const countries = ['Tajikistan', 'Zambia', 'Dominica', 'Australia', 'Botswana', 'Mozambique', 'Lesotho', 'Thailand', 'Gabon', 'Cuba', 'Mexico', 'Central African Republic', 'Réunion', 'Montenegro', 'Romania', 'Jamaica', 'Thailand', 'Cameroon', 'French Guiana', 'Nigeria', 'Tokelau', 'Slovenia', 'Kuwait', 'Palestinian Territories', 'Estonia', 'Germany', 'Cameroon', 'Somalia', 'El Salvador', 'San Marino', 'Sierra Leone', 'Sierra Leone', 'Gibraltar', 'Benin', 'Russia', 'Iraq', 'Tunisia', 'Greenland', 'Côte d\'Ivoire', 'Tanzania', 'Zambia', 'Bermuda', 'Somalia', 'Malaysia', 'Croatia', 'Togo', 'Belgium', 'Uruguay', 'Equatorial Guinea', 'Nigeria', 'St. Martin', 'Tuvalu', 'South Africa', 'Hong Kong SAR China', 'Palau', 'Canary Islands', 'Algeria', 'Hong Kong SAR China', 'Brunei', 'Dominican Republic', 'Sierra Leone', 'Moldova', 'Indonesia', 'Central African Republic', 'Anguilla', 'Malaysia', 'Bahrain', 'Indonesia', 'Peru', 'Namibia', 'Congo - Brazzaville', 'Micronesia', 'Cambodia', 'Réunion', 'Honduras', 'Hungary', 'Brazil', 'Trinidad & Tobago', 'Hungary', 'Madagascar', 'Sierra Leone', 'Seychelles', 'St. Martin', 'New Caledonia', 'Tokelau', 'Macedonia', 'Netherlands', 'Panama', 'Venezuela', 'Nepal', 'Guernsey', 'Papua New Guinea', 'Finland', 'Malaysia', 'Hong Kong SAR China', 'Trinidad & Tobago', 'Montserrat', 'Comoros', 'Benin', 'South Korea', 'Peru', 'Botswana', 'Cambodia', 'Isle of Man', 'Mozambique']
setInterval(() => {
button25.badge.content = countries[Math.floor(Math.random() * countries.length)]
button25.layout()
}, 1000)
const countries = [
'Tajikistan',
'Zambia',
'Dominica',
'Australia',
'Botswana',
'Mozambique',
'Lesotho',
'Thailand',
'Gabon',
'Cuba',
'Mexico',
'Central African Republic',
'Réunion',
'Montenegro',
'Romania',
'Jamaica',
'Thailand',
'Cameroon',
'French Guiana',
'Nigeria',
'Tokelau',
'Slovenia',
'Kuwait',
'Palestinian Territories',
'Estonia',
'Germany',
'Cameroon',
'Somalia',
'El Salvador',
'San Marino',
'Sierra Leone',
'Sierra Leone',
'Gibraltar',
'Benin',
'Russia',
'Iraq',
'Tunisia',
'Greenland',
"Côte d'Ivoire",
'Tanzania',
'Zambia',
'Bermuda',
'Somalia',
'Malaysia',
'Croatia',
'Togo',
'Belgium',
'Uruguay',
'Equatorial Guinea',
'Nigeria',
'St. Martin',
'Tuvalu',
'South Africa',
'Hong Kong SAR China',
'Palau',
'Canary Islands',
'Algeria',
'Hong Kong SAR China',
'Brunei',
'Dominican Republic',
'Sierra Leone',
'Moldova',
'Indonesia',
'Central African Republic',
'Anguilla',
'Malaysia',
'Bahrain',
'Indonesia',
'Peru',
'Namibia',
'Congo - Brazzaville',
'Micronesia',
'Cambodia',
'Réunion',
'Honduras',
'Hungary',
'Brazil',
'Trinidad & Tobago',
'Hungary',
'Madagascar',
'Sierra Leone',
'Seychelles',
'St. Martin',
'New Caledonia',
'Tokelau',
'Macedonia',
'Netherlands',
'Panama',
'Venezuela',
'Nepal',
'Guernsey',
'Papua New Guinea',
'Finland',
'Malaysia',
'Hong Kong SAR China',
'Trinidad & Tobago',
'Montserrat',
'Comoros',
'Benin',
'South Korea',
'Peru',
'Botswana',
'Cambodia',
'Isle of Man',
'Mozambique'
]
setInterval(() => {
button25.badge.content = countries[Math.floor(Math.random() * countries.length)]
button25.layout()
}, 1000)
const button26 = new Button({
x: 10,
y: 520,
label: 'add',
type: 'checkbox',
strokeActive: 0x28a745,
textStyleActive: {
fill: 0x28a745
},
textAlpha: .2,
textActiveAlpha: .6
})
const button26 = new Button({
x: 10,
y: 520,
label: 'add',
type: 'checkbox',
strokeActive: 0x28a745,
textStyleActive: {
fill: 0x28a745
},
textAlpha: 0.2,
textActiveAlpha: 0.6
})
app.scene.addChild(button1, button2, button3, button4, button5, button6)
app.scene.addChild(button7, button8)
app.scene.addChild(button9, button10, button11)
app.scene.addChild(button12, button13)
app.scene.addChild(button14, button15, button16, button17)
app.scene.addChild(button18, button19, button20, button21)
app.scene.addChild(button22, button23, button24, button25)
app.scene.addChild(button26)
</script>
</body>
</html>
app.scene.addChild(button1, button2, button3, button4, button5, button6)
app.scene.addChild(button7, button8)
app.scene.addChild(button9, button10, button11)
app.scene.addChild(button12, button13)
app.scene.addChild(button14, button15, button16, button17)
app.scene.addChild(button18, button19, button20, button21)
app.scene.addChild(button22, button23, button24, button25)
app.scene.addChild(button26)
</script>
</body>
</html>

BIN
lib/pixi/button.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

View File

@ -1,375 +1,390 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-eqv="Content-Type" content="text/html; charset=utf-8">
<head>
<meta http-eqv="Content-Type" content="text/html; charset=utf-8" />
<title>PIXI ButtonGroup</title>
<title>PIXI ButtonGroup</title>
<link rel="stylesheet" href="../3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../../css/doctest.css">
<link rel="stylesheet" href="../3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../../css/doctest.css" />
<script src="../3rdparty/highlight/highlight.pack.js"></script>
<script src="../../dist/iwmlib.3rdparty.js"></script>
<script src="../3rdparty/gsap/src/uncompressed/plugins/ThrowPropsPlugin.js"></script>
<script src="../3rdparty/highlight/highlight.pack.js"></script>
<script src="../../dist/iwmlib.3rdparty.js"></script>
<script src="../3rdparty/gsap/src/uncompressed/plugins/ThrowPropsPlugin.js"></script>
<script src="../../dist/iwmlib.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script>
</head>
<body onload="Doctest.run()">
<h1>ButtonGroup</h1>
<p>
Group a series of buttons together on a single line with the button group.
</p>
<p><a href="../../doc/out/ButtonGroup.html">JavaScript API</a></p>
<p>Let's look at some button groups:</p><br />
<canvas id="canvas" class="interactive"></canvas>
<p>
What you should see: Many button groups with very different styling and behaviour.
</p>
<script class="doctest">
const app = new PIXIApp({
view: canvas,
width: 1000,
height: 1700
}).setup().run()
<script src="../../dist/iwmlib.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script>
</head>
<body onload="Doctest.run()">
<h1><a href="../index.html">lib.</a><a href="index.html">pixi.</a>ButtonGroup</h1>
<p>Group a series of buttons together on a single line with the button group.</p>
<p><a href="../../doc/out/ButtonGroup.html">JavaScript API</a></p>
<p>Let's look at some button groups:</p>
<br />
<canvas id="canvas" class="interactive"></canvas>
<p>What you should see: Many button groups with very different styling and behaviour.</p>
<script class="doctest">
const app = new PIXIApp({
view: canvas,
width: 1000,
height: 1700
})
.setup()
.run()
const buttonGroup1 = new ButtonGroup({
x: 10,
y: 10,
buttons: [
{icon: 'keyboard_arrow_left'},
{icon: 'keyboard_arrow_up'},
{icon: 'keyboard_arrow_down'},
{icon: 'keyboard_arrow_right'}
]
})
const buttonGroup1 = new ButtonGroup({
x: 10,
y: 10,
buttons: [
{ icon: 'keyboard_arrow_left' },
{ icon: 'keyboard_arrow_up' },
{ icon: 'keyboard_arrow_down' },
{ icon: 'keyboard_arrow_right' }
]
})
const buttonGroup2 = new ButtonGroup({
x: 260,
y: 10,
buttons: [
{icon: 'directions_walk', tooltip: 'Gehen'},
{icon: 'directions_run', tooltip: 'Laufen'},
{icon: 'directions_bike', tooltip: 'Fahrrad'},
{icon: 'directions_bus', tooltip: 'Bus'},
{icon: 'directions_car', tooltip: 'Auto'},
{icon: 'directions_boat', tooltip: 'Schiff'},
{icon: 'directions_railway', tooltip: 'Bahn'}
],
margin: 0,
stroke: 0x0088ff,
strokeWidth: 3
})
const buttonGroup2 = new ButtonGroup({
x: 260,
y: 10,
buttons: [
{ icon: 'directions_walk', tooltip: 'Gehen' },
{ icon: 'directions_run', tooltip: 'Laufen' },
{ icon: 'directions_bike', tooltip: 'Fahrrad' },
{ icon: 'directions_bus', tooltip: 'Bus' },
{ icon: 'directions_car', tooltip: 'Auto' },
{ icon: 'directions_boat', tooltip: 'Schiff' },
{ icon: 'directions_railway', tooltip: 'Bahn' }
],
margin: 0,
stroke: 0x0088ff,
strokeWidth: 3
})
const buttonGroup3 = new ButtonGroup({
x: 610,
y: 10,
buttons: [
{icon: 'laptop'},
{label: 'Linux'},
{icon: 'laptop_windows', label: 'Windows', align: 'center'},
{icon: 'laptop_mac', iconPosition: 'right', label: 'macOS'}
],
margin: 0,
stroke: 0xffffff,
strokeWidth: 1
})
const buttonGroup3 = new ButtonGroup({
x: 610,
y: 10,
buttons: [
{ icon: 'laptop' },
{ label: 'Linux' },
{ icon: 'laptop_windows', label: 'Windows', align: 'center' },
{ icon: 'laptop_mac', iconPosition: 'right', label: 'macOS' }
],
margin: 0,
stroke: 0xffffff,
strokeWidth: 1
})
const buttonGroup4 = new ButtonGroup({
x: 10,
y: 90,
buttons: [
{label: 'Button 1', action: (event, button) => console.log(button.id)},
{label: 'Button 2', action: (event, button) => console.log(button.id), radius: 0},
{label: 'Button 3', textStyle: {fill: '#fd6b6a'}, stroke: 0xd7a3f9, strokeWidth: 8, strokeAlpha: .8},
{label: 'Button 4', textStyle: {fill: '#40c3f2'}, radius: 20, icon: 'looks', iconPosition: 'right', iconColor: 0xd7ff30}
],
margin: 40,
minWidth: 180,
minHeight: 60,
textStyle: {
fill: '#f8ce2d'
},
stroke: 0xffffff,
strokeWidth: 1
})
const buttonGroup4 = new ButtonGroup({
x: 10,
y: 90,
buttons: [
{ label: 'Button 1', action: (event, button) => console.log(button.id) },
{ label: 'Button 2', action: (event, button) => console.log(button.id), radius: 0 },
{
label: 'Button 3',
textStyle: { fill: '#fd6b6a' },
stroke: 0xd7a3f9,
strokeWidth: 8,
strokeAlpha: 0.8
},
{
label: 'Button 4',
textStyle: { fill: '#40c3f2' },
radius: 20,
icon: 'looks',
iconPosition: 'right',
iconColor: 0xd7ff30
}
],
margin: 40,
minWidth: 180,
minHeight: 60,
textStyle: {
fill: '#f8ce2d'
},
stroke: 0xffffff,
strokeWidth: 1
})
const buttonGroup5 = new ButtonGroup({
x: 10,
y: 180,
buttons: [
{label: 'ButtonGroup'},
{label: 'of', active: true},
{label: 'type'},
{minWidth: 30, style: 'link'},
{label: 'checkbox', active: true}
],
margin: 6,
type: 'checkbox'
})
const buttonGroup5 = new ButtonGroup({
x: 10,
y: 180,
buttons: [
{ label: 'ButtonGroup' },
{ label: 'of', active: true },
{ label: 'type' },
{ minWidth: 30, style: 'link' },
{ label: 'checkbox', active: true }
],
margin: 6,
type: 'checkbox'
})
const buttonGroup6 = new ButtonGroup({
x: 450,
y: 180,
buttons: [
{label: 'ButtonGroup'},
{label: 'of'},
{label: 'type', active: true},
{label: 'radio'}
],
margin: 0,
type: 'radio'
})
const buttonGroup6 = new ButtonGroup({
x: 450,
y: 180,
buttons: [
{ label: 'ButtonGroup' },
{ label: 'of' },
{ label: 'type', active: true },
{ label: 'radio' }
],
margin: 0,
type: 'radio'
})
const buttonGroup7 = new ButtonGroup({
x: 10,
y: 250,
theme: 'light',
buttons: [
{label: 'ButtonGroup'},
{label: 'of'},
{label: 'style'},
{label: 'link'},
{label: 'with'},
{label: 'one exception', style: 'default'}
],
style: 'link'
})
const buttonGroup7 = new ButtonGroup({
x: 10,
y: 250,
theme: 'light',
buttons: [
{ label: 'ButtonGroup' },
{ label: 'of' },
{ label: 'style' },
{ label: 'link' },
{ label: 'with' },
{ label: 'one exception', style: 'default' }
],
style: 'link'
})
const buttonGroup8 = new ButtonGroup({
x: 610,
y: 250,
buttons: [
{icon: 'airline_seat_legroom_extra'},
{icon: 'airline_seat_legroom_normal'},
{icon: 'airline_seat_legroom_reduced'},
{icon: 'wifi_tethering', type: 'checkbox'}
],
type: 'radio',
margin: 0
})
const buttonGroup8 = new ButtonGroup({
x: 610,
y: 250,
buttons: [
{ icon: 'airline_seat_legroom_extra' },
{ icon: 'airline_seat_legroom_normal' },
{ icon: 'airline_seat_legroom_reduced' },
{ icon: 'wifi_tethering', type: 'checkbox' }
],
type: 'radio',
margin: 0
})
const buttonGroup9 = new ButtonGroup({
x: 10,
y: 320,
buttons: [
{icon: 'attachment'},
{icon: 'autorenew'},
{icon: 'backup'},
{icon: 'apps'}
],
orientation: 'vertical',
minWidth: 70
})
const buttonGroup9 = new ButtonGroup({
x: 10,
y: 320,
buttons: [{ icon: 'attachment' }, { icon: 'autorenew' }, { icon: 'backup' }, { icon: 'apps' }],
orientation: 'vertical',
minWidth: 70
})
const buttonGroup10 = new ButtonGroup({
x: 100,
y: 320,
buttons: [
{label: 'Vertical'},
{label: 'ButtonGroup'},
{label: 'align: left', active: true},
{label: 'margin: 0'}
],
orientation: 'vertical',
stroke: 0xff0000,
strokeWidth: 3,
align: 'left',
margin: 0
})
const buttonGroup10 = new ButtonGroup({
x: 100,
y: 320,
buttons: [
{ label: 'Vertical' },
{ label: 'ButtonGroup' },
{ label: 'align: left', active: true },
{ label: 'margin: 0' }
],
orientation: 'vertical',
stroke: 0xff0000,
strokeWidth: 3,
align: 'left',
margin: 0
})
const buttonGroup11 = new ButtonGroup({
x: 250,
y: 320,
buttons: [
{label: 'Vertical', active: true, verticalAlign: 'top'},
{label: 'ButtonGroup'},
{label: 'centered', active: true, disabled: true, verticalAlign: 'middle'},
{label: 'of', disabled: true, align: 'left'},
{label: 'type'},
{label: 'checkbox', align: 'right', verticalAlign: 'bottom'}
],
orientation: 'vertical',
margin: 0,
align: 'center',
verticalAlign: 'bottom',
minHeight: 100,
minWidth: 100,
stroke: 0x22ee22,
type: 'checkbox'
})
const buttonGroup11 = new ButtonGroup({
x: 250,
y: 320,
buttons: [
{ label: 'Vertical', active: true, verticalAlign: 'top' },
{ label: 'ButtonGroup' },
{ label: 'centered', active: true, disabled: true, verticalAlign: 'middle' },
{ label: 'of', disabled: true, align: 'left' },
{ label: 'type' },
{ label: 'checkbox', align: 'right', verticalAlign: 'bottom' }
],
orientation: 'vertical',
margin: 0,
align: 'center',
verticalAlign: 'bottom',
minHeight: 100,
minWidth: 100,
stroke: 0x22ee22,
type: 'checkbox'
})
const buttonGroup12 = new ButtonGroup({
x: 400,
y: 320,
buttons: [
{label: 'Controls', disabled: true},
{icon: 'play_arrow'},
{icon: 'pause', active: true, align: 'left'},
{icon: 'stop', verticalAlign: 'bottom'}
],
orientation: 'vertical',
margin: 0,
align: 'right',
type: 'radio'
})
const buttonGroup12 = new ButtonGroup({
x: 400,
y: 320,
buttons: [
{ label: 'Controls', disabled: true },
{ icon: 'play_arrow' },
{ icon: 'pause', active: true, align: 'left' },
{ icon: 'stop', verticalAlign: 'bottom' }
],
orientation: 'vertical',
margin: 0,
align: 'right',
type: 'radio'
})
const buttonGroup13 = new ButtonGroup({
x: 520,
y: 320,
buttons: [
{label: 'Volume', align: 'center', disabled: true},
{icon: 'volume_off', label: 'Aus', align: 'left', iconColor: 0x99ffff, verticalAlign: 'top'},
{icon: 'volume_mute', label: 'Lautlos', active: true, iconColorActive: 0xd7a3f9},
{icon: 'volume_down', label: 'Leiser', align: 'right', iconPosition: 'right'},
{icon: 'volume_up', label: 'Lauter', align: 'right', iconPosition: 'right', verticalAlign: 'bottom'}
],
orientation: 'vertical',
margin: 0,
type: 'radio',
stroke: 0x7b4073,
strokeWidth: 4,
minWidth: 200,
minHeight: 100
})
const buttonGroup13 = new ButtonGroup({
x: 520,
y: 320,
buttons: [
{ label: 'Volume', align: 'center', disabled: true },
{ icon: 'volume_off', label: 'Aus', align: 'left', iconColor: 0x99ffff, verticalAlign: 'top' },
{ icon: 'volume_mute', label: 'Lautlos', active: true, iconColorActive: 0xd7a3f9 },
{ icon: 'volume_down', label: 'Leiser', align: 'right', iconPosition: 'right' },
{
icon: 'volume_up',
label: 'Lauter',
align: 'right',
iconPosition: 'right',
verticalAlign: 'bottom'
}
],
orientation: 'vertical',
margin: 0,
type: 'radio',
stroke: 0x7b4073,
strokeWidth: 4,
minWidth: 200,
minHeight: 100
})
const buttonGroup14 = new ButtonGroup({
x: 10,
y: 960,
buttons: [
{label: 'Stacked button 1', action: event => console.log('clicked 1')},
{label: 'Stacked button 2', action: event => console.log('clicked 2')},
{label: 'Stacked button 3', action: event => console.log('clicked 3')},
{label: 'Stacked button 4', action: event => console.log('clicked 4')},
{label: 'Stacked button 5', action: event => console.log('clicked 5')},
{label: 'Stacked button 6', action: event => console.log('clicked 6')},
{label: 'Stacked button 7', action: event => console.log('clicked 7')},
{label: 'Stacked button 8', action: event => console.log('clicked 8')}
],
stackPadding: 6,
maxWidth: 620,
app
})
const buttonGroup14 = new ButtonGroup({
x: 10,
y: 960,
buttons: [
{ label: 'Stacked button 1', action: (event) => console.log('clicked 1') },
{ label: 'Stacked button 2', action: (event) => console.log('clicked 2') },
{ label: 'Stacked button 3', action: (event) => console.log('clicked 3') },
{ label: 'Stacked button 4', action: (event) => console.log('clicked 4') },
{ label: 'Stacked button 5', action: (event) => console.log('clicked 5') },
{ label: 'Stacked button 6', action: (event) => console.log('clicked 6') },
{ label: 'Stacked button 7', action: (event) => console.log('clicked 7') },
{ label: 'Stacked button 8', action: (event) => console.log('clicked 8') }
],
stackPadding: 6,
maxWidth: 620,
app
})
const buttonGroup15 = new ButtonGroup({
x: 10,
y: 1040,
buttons: [
{icon: 'battery_charging_20', type: 'checkbox', iconColorActive: 0xd43e36},
{icon: 'battery_charging_30', type: 'checkbox', iconColorActive: 0xf99927},
{icon: 'battery_charging_50', type: 'checkbox', iconColorActive: 0xefc201},
{icon: 'battery_charging_60', type: 'checkbox', iconColorActive: 0x839b00},
{icon: 'battery_charging_80', type: 'checkbox', iconColorActive: 0x4ba8af},
{icon: 'battery_charging_90', type: 'checkbox', iconColorActive: 0x5386bc},
{icon: 'battery_charging_full', type: 'checkbox', iconColorActive: 0x9c71b7}
],
orientation: 'vertical',
margin: 1,
maxHeight: 200,
app
})
const buttonGroup15 = new ButtonGroup({
x: 10,
y: 1040,
buttons: [
{ icon: 'battery_charging_20', type: 'checkbox', iconColorActive: 0xd43e36 },
{ icon: 'battery_charging_30', type: 'checkbox', iconColorActive: 0xf99927 },
{ icon: 'battery_charging_50', type: 'checkbox', iconColorActive: 0xefc201 },
{ icon: 'battery_charging_60', type: 'checkbox', iconColorActive: 0x839b00 },
{ icon: 'battery_charging_80', type: 'checkbox', iconColorActive: 0x4ba8af },
{ icon: 'battery_charging_90', type: 'checkbox', iconColorActive: 0x5386bc },
{ icon: 'battery_charging_full', type: 'checkbox', iconColorActive: 0x9c71b7 }
],
orientation: 'vertical',
margin: 1,
maxHeight: 200,
app
})
const buttons16 = []
for (let i = 1; i < 101; i++) {
buttons16.push({label: `Button ${i}`, stroke: Math.floor(Math.random() * 16777215), strokeWidth: 3, radius: 16})
}
const buttons16 = []
for (let i = 1; i < 101; i++) {
buttons16.push({
label: `Button ${i}`,
stroke: Math.floor(Math.random() * 16777215),
strokeWidth: 3,
radius: 16
})
}
buttons16.splice(6, 0, {minWidth: 50, style: 'link'})
const buttonGroup16 = new ButtonGroup({
x: 90,
y: 1040,
buttons: buttons16,
stackPadding: 3,
maxWidth: 900,
app
})
buttons16.splice(6, 0, { minWidth: 50, style: 'link' })
const buttonGroup16 = new ButtonGroup({
x: 90,
y: 1040,
buttons: buttons16,
stackPadding: 3,
maxWidth: 900,
app
})
const buttonGroup17 = new ButtonGroup({
x: 10,
y: 1270,
buttons: [
{icon: 'local_airport', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Airport'},
{icon: 'local_bar', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Bar'},
{icon: 'local_cafe', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Cafe'},
{icon: 'local_car_wash', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Car wash'},
{icon: 'local_dining', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Dining'},
{icon: 'local_florist', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Florist'},
{icon: 'local_gas_station', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Gas station'},
{icon: 'local_grocery_store', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Grocery store'},
{icon: 'local_mall', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Mall'},
{icon: 'local_pizza', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Pizza'},
{icon: 'local_printshop', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Printshop'},
{icon: 'local_pharmacy', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Pharmacy'}
],
margin: 50,
maxWidth: 400,
app
})
const buttonGroup17 = new ButtonGroup({
x: 10,
y: 1270,
buttons: [
{ icon: 'local_airport', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Airport' },
{ icon: 'local_bar', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Bar' },
{ icon: 'local_cafe', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Cafe' },
{ icon: 'local_car_wash', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Car wash' },
{ icon: 'local_dining', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Dining' },
{ icon: 'local_florist', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Florist' },
{ icon: 'local_gas_station', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Gas station' },
{
icon: 'local_grocery_store',
type: 'checkbox',
iconColorActive: 0xefc201,
badge: 'Grocery store'
},
{ icon: 'local_mall', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Mall' },
{ icon: 'local_pizza', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Pizza' },
{ icon: 'local_printshop', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Printshop' },
{ icon: 'local_pharmacy', type: 'checkbox', iconColorActive: 0xefc201, badge: 'Pharmacy' }
],
margin: 50,
maxWidth: 400,
app
})
const buttonGroup18 = new ButtonGroup({
x: 10,
y: 1340,
buttons: [
{label: 'move'},
{label: 'explanation dried'},
{label: 'out catch'},
{label: 'late either'},
{label: 'tell pour'},
{label: 'willing apart airplane'},
{label: 'high war'},
{label: 'future struck'},
{label: 'sense image'},
{label: 'never'},
{label: 'mark cloth'},
{label: 'everywhere due large'}
],
maxWidth: 500,
app
})
const buttonGroup18 = new ButtonGroup({
x: 10,
y: 1340,
buttons: [
{ label: 'move' },
{ label: 'explanation dried' },
{ label: 'out catch' },
{ label: 'late either' },
{ label: 'tell pour' },
{ label: 'willing apart airplane' },
{ label: 'high war' },
{ label: 'future struck' },
{ label: 'sense image' },
{ label: 'never' },
{ label: 'mark cloth' },
{ label: 'everywhere due large' }
],
maxWidth: 500,
app
})
const buttonGroup19 = new ButtonGroup({
x: 10,
y: 1420,
buttons: [
{label: 'move'},
{label: 'explanation dried'},
{label: 'out catch'}
],
maxWidth: 500,
app
})
const buttonGroup19 = new ButtonGroup({
x: 10,
y: 1420,
buttons: [{ label: 'move' }, { label: 'explanation dried' }, { label: 'out catch' }],
maxWidth: 500,
app
})
const buttonGroup20 = new ButtonGroup({
x: 10,
y: 1520,
type: 'checkbox',
buttons: [
{label: 'one'},
{label: 'two'},
{label: 'three'}
],
textAlpha: .7,
textActiveAlpha: .2
})
const buttonGroup20 = new ButtonGroup({
x: 10,
y: 1520,
type: 'checkbox',
buttons: [{ label: 'one' }, { label: 'two' }, { label: 'three' }],
textAlpha: 0.7,
textActiveAlpha: 0.2
})
const buttonGroup21 = new ButtonGroup({
x: 300,
y: 1520,
type: 'checkbox',
buttons: [
{label: 'eins', textAlpha: 1, textActiveAlpha: .2},
{label: 'zwei', textAlpha: .2, textActiveAlpha: 1},
{label: 'drei'}
]
})
const buttonGroup21 = new ButtonGroup({
x: 300,
y: 1520,
type: 'checkbox',
buttons: [
{ label: 'eins', textAlpha: 1, textActiveAlpha: 0.2 },
{ label: 'zwei', textAlpha: 0.2, textActiveAlpha: 1 },
{ label: 'drei' }
]
})
app.scene.addChild(buttonGroup1, buttonGroup2, buttonGroup3)
app.scene.addChild(buttonGroup4)
app.scene.addChild(buttonGroup5, buttonGroup6)
app.scene.addChild(buttonGroup7, buttonGroup8)
app.scene.addChild(buttonGroup9, buttonGroup10, buttonGroup11, buttonGroup12, buttonGroup13)
app.scene.addChild(buttonGroup14, buttonGroup15, buttonGroup16, buttonGroup17, buttonGroup18, buttonGroup19)
app.scene.addChild(buttonGroup20, buttonGroup21)
</script>
</body>
app.scene.addChild(buttonGroup1, buttonGroup2, buttonGroup3)
app.scene.addChild(buttonGroup4)
app.scene.addChild(buttonGroup5, buttonGroup6)
app.scene.addChild(buttonGroup7, buttonGroup8)
app.scene.addChild(buttonGroup9, buttonGroup10, buttonGroup11, buttonGroup12, buttonGroup13)
app.scene.addChild(buttonGroup14, buttonGroup15, buttonGroup16, buttonGroup17, buttonGroup18, buttonGroup19)
app.scene.addChild(buttonGroup20, buttonGroup21)
</script>
</body>
</html>

BIN
lib/pixi/buttongroup.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 KiB

View File

@ -15,7 +15,7 @@
<body onload="Doctest.run()">
<h1>
Coordinates
<a href="../index.html">lib.</a><a href="index.html">pixi.</a>Coordinates
</h1>
<p>
To position objects in defined spatial relationships presupposes a clear understanding of the involved coordinate systems.

BIN
lib/pixi/coordinates.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 KiB

View File

@ -1,219 +1,226 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>DeepZoomImage Doctests</title>
<title>DeepZoomImage Doctests</title>
<link rel="stylesheet" href="../../3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../../../css/doctest.css" />
<link rel="stylesheet" href="../../3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../../../css/doctest.css">
<script src="../../3rdparty/highlight/highlight.pack.js"></script>
<script src="../../3rdparty/highlight/highlight.pack.js"></script>
<script src="../../../dist/iwmlib.3rdparty.js"></script>
<script src="../../../dist/iwmlib.3rdparty.js"></script>
<script src="../../../dist/iwmlib.js"></script>
<script src="../../../dist/iwmlib.pixi.js"></script>
<script src="../../../dist/iwmlib.js"></script>
<script src="../../../dist/iwmlib.pixi.js"></script>
<style>
#app {
display: table;
margin: 0 auto;
}
<style>
#app {
display: table;
margin: 0 auto;
}
#app > * {
margin-bottom: 5px;
}
</style>
</head>
#app > * {
margin-bottom: 5px;
}
</style>
</head>
<body onload="Doctest.run()">
<h1>
<a href="../../index.html">lib.</a><a href="../index.html">pixi.</a><a href="index.html">deepzoom.</a>
DeepZoom
</h1>
<p>
The main class of a deeply zoomable image that is represented by a hierarchy of tile layers for each zoom
level. This gives the user the impression that even huge pictures (up to gigapixel-images) can be zoomed
instantaneously, since the tiles at smaller levels are scaled immediately and overloaded by more detailed
tiles at the larger level as fast as possible.
</p>
<br />
<div id="div1" style="float: left"></div>
<div id="div2" style="float: right"></div>
<div style="clear: left; margin-top: 540px" />
<script class="doctest">
// deepZoom
//--------------------
const deepZoomInfo = new DeepZoomInfo({
tileSize: 128,
format: 'jpg',
overlap: 0,
type: 'map',
height: 4096,
width: 4096,
path: '../assets/maps/test',
urlTileTemplate: '{path}/{level}/{column}/{row}.{format}'
})
// const deepZoomInfo = new DeepZoomInfo({
// compression: [
// "dds"
// ],
// clip: {
// minLevel: 12,
// maxLevel: 20,
// startCol: 275215,
// startRow: 181050,
// bounds: {
// min: [48.458353, 8.96484374976547],
// max: [48.5747899110263, 9.14062499976523]
// }
// },
// tileSize: 512,
// format: "png",
// overlap: 0,
// type: "map",
// height: 131072,
// width: 131072,
// path: "../../../var/tuesch/luftbild_2016_full",
// urlTileTemplate: "{path}/{level}/{row}/{column}.{format}"
// })
<body onload="Doctest.run()">
<h1>Double DeepZoomImage</h1>
<p>
The main class of a deeply zoomable image that is represented by a hierarchy of tile layers for each zoom level. This gives
the user the impression that even huge pictures (up to gigapixel-images) can be zoomed instantaneously, since the
tiles at smaller levels are scaled immediately and overloaded by more detailed tiles at the larger level as fast
as possible.
</p>
<br />
<div id="div1" style="float: left;"></div>
<div id="div2" style="float: right;"></div>
<div style="clear: left; margin-top: 540px;" />
<script class="doctest">
// app
//--------------------
window.app = new PIXIApp({
width: 400,
height: 500,
backgroundColor: 0xffcccccc
})
.setup()
.run()
// deepZoom
//--------------------
const deepZoomInfo = new DeepZoomInfo({
"tileSize": 128,
"format": "jpg",
"overlap": 0,
"type": "map",
"height": 4096,
"width": 4096,
"path": "../assets/maps/test",
"urlTileTemplate": "{path}/{level}/{column}/{row}.{format}"
})
// const deepZoomInfo = new DeepZoomInfo({
// compression: [
// "dds"
// ],
// clip: {
// minLevel: 12,
// maxLevel: 20,
// startCol: 275215,
// startRow: 181050,
// bounds: {
// min: [48.458353, 8.96484374976547],
// max: [48.5747899110263, 9.14062499976523]
// }
// },
// tileSize: 512,
// format: "png",
// overlap: 0,
// type: "map",
// height: 131072,
// width: 131072,
// path: "../../../var/tuesch/luftbild_2016_full",
// urlTileTemplate: "{path}/{level}/{row}/{column}.{format}"
// })
div1.appendChild(app.view)
// app
//--------------------
window.app = new PIXIApp({
width: 400,
height: 500,
backgroundColor: 0xFFCCCCCC
}).setup().run()
div1.appendChild(app.view)
// create the ScatterContainer
//--------------------
const scatterContainer1 = new ScatterContainer(app.renderer, { showBounds: true, app: app })
app.scene.addChild(scatterContainer1)
// create the ScatterContainer
//--------------------
const scatterContainer1 = new ScatterContainer(app.renderer, {showBounds: true, app: app})
app.scene.addChild(scatterContainer1)
// Create the DeepZoomImage
//--------------------
setTimeout(() => {
const deepZoomImage1 = new DeepZoomImage(deepZoomInfo, { app, world: scatterContainer1 })
deepZoomImage1.scatter = new DisplayObjectScatter(deepZoomImage1, app.renderer, {
minScale: 0,
maxScale: 50,
onTransform: (event) => {
//console.log('currentLevel', deepZoomImage1.currentLevel)
deepZoomImage1.transformed(event)
}
})
// Create the DeepZoomImage
//--------------------
setTimeout(() => {
const deepZoomImage1 = new DeepZoomImage(deepZoomInfo, {app, world: scatterContainer1})
deepZoomImage1.scatter = new DisplayObjectScatter(deepZoomImage1, app.renderer, {
scatterContainer1.addChild(deepZoomImage1)
}, 1000)
// app2
//--------------------
const app2 = new PIXIApp({
width: 400,
height: 500,
backgroundColor: 0xffcccccc
})
.setup()
.run()
div2.appendChild(app2.view)
// create the ScatterContainer
//--------------------
const scatterContainer2 = new ScatterContainer(app2.renderer, { showBounds: true, app: app2 })
app2.scene.addChild(scatterContainer2)
// Create the DeepZoomImage
//--------------------
const deepZoomImage2 = new DeepZoomImage(deepZoomInfo, { app: app2 })
deepZoomImage2.scatter = new DisplayObjectScatter(deepZoomImage2, app2.renderer, {
minScale: 0,
maxScale: 50,
onTransform: event => {
//console.log('currentLevel', deepZoomImage1.currentLevel)
deepZoomImage1.transformed(event)
maxScale: 100,
onTransform: (event) => {
deepZoomImage2.transformed(event)
}
})
scatterContainer1.addChild(deepZoomImage1)
}, 1000)
// app2
//--------------------
const app2 = new PIXIApp({
width: 400,
height: 500,
backgroundColor: 0xFFCCCCCC
}).setup().run()
div2.appendChild(app2.view)
scatterContainer2.addChild(deepZoomImage2)
</script>
// create the ScatterContainer
//--------------------
const scatterContainer2 = new ScatterContainer(app2.renderer, {showBounds: true, app: app2})
app2.scene.addChild(scatterContainer2)
<h1>DeepZoomImage in DeepZoomImage</h1>
<p>
The main class of a deeply zoomable image that is represented by a hierarchy of tile layers for each zoom
level. This gives the user the impression that even huge pictures (up to gigapixel-images) can be zoomed
instantaneously, since the tiles at smaller levels are scaled immediately and overloaded by more detailed
tiles at the larger level as fast as possible.
</p>
<br />
<div id="div3"></div>
<script class="doctest">
// app3
//--------------------
const app3 = new PIXIApp({
width: 900,
height: 500,
backgroundColor: 0xffcccccc
})
.setup()
.run()
// Create the DeepZoomImage
//--------------------
const deepZoomImage2 = new DeepZoomImage(deepZoomInfo, {app: app2})
deepZoomImage2.scatter = new DisplayObjectScatter(deepZoomImage2, app2.renderer, {
minScale: 0,
maxScale: 100,
onTransform: (event) => {
deepZoomImage2.transformed(event)
}
})
window.app3 = app3
scatterContainer2.addChild(deepZoomImage2)
div3.appendChild(app3.view)
</script>
// create the ScatterContainer
//--------------------
const scatterContainer3 = new ScatterContainer(app3.renderer, {
app: app3,
showBounds: true,
claimEvent: false,
stopEvents: false
})
app3.scene.addChild(scatterContainer3)
<h1>DeepZoomImage in DeepZoomImage</h1>
<p>
The main class of a deeply zoomable image that is represented by a hierarchy of tile layers for each zoom level. This gives
the user the impression that even huge pictures (up to gigapixel-images) can be zoomed instantaneously, since the
tiles at smaller levels are scaled immediately and overloaded by more detailed tiles at the larger level as fast
as possible.
</p>
<br />
<div id="div3"></div>
<script class="doctest">
// Create the DeepZoomImage
//--------------------
const deepZoomImage3 = new DeepZoomImage(deepZoomInfo, { app: app3 })
deepZoomImage3.scatter = new DisplayObjectScatter(deepZoomImage3, app3.renderer, {
minScale: 0,
maxScale: 100,
startScale: 2,
autoBringToFront: false,
onTransform: (event) => {
deepZoomImage3.transformed(event)
}
})
// app3
//--------------------
const app3 = new PIXIApp({
width: 900,
height: 500,
backgroundColor: 0xFFCCCCCC
}).setup().run()
app3._deepZoomImage3 = deepZoomImage3
window.app3 = app3
div3.appendChild(app3.view)
scatterContainer3.addChild(deepZoomImage3)
// create the ScatterContainer
//--------------------
const scatterContainer3 = new ScatterContainer(app3.renderer, {app: app3, showBounds: true, claimEvent: false, stopEvents: false})
app3.scene.addChild(scatterContainer3)
// Create the second DeepZoomImage
//--------------------
const border = new PIXI.Graphics()
border.beginFill(0x282828, 1)
border.drawRect(0, 0, 264, 244)
scatterContainer3.addChild(border)
// Create the DeepZoomImage
//--------------------
const deepZoomImage3 = new DeepZoomImage(deepZoomInfo, {app: app3})
deepZoomImage3.scatter = new DisplayObjectScatter(deepZoomImage3, app3.renderer, {
minScale: 0,
maxScale: 100,
startScale: 2,
autoBringToFront: false,
onTransform: (event) => {
deepZoomImage3.transformed(event)
}
})
const mask = new PIXI.Graphics()
mask.beginFill(0x282828, 1)
mask.drawRect(0, 0, 260, 240)
scatterContainer3.addChild(mask)
app3._deepZoomImage3 = deepZoomImage3
const deepZoomImage4 = new DeepZoomImage(deepZoomInfo, { app: app3 })
deepZoomImage4.x = 4
deepZoomImage4.y = 4
deepZoomImage4.scatter = new DisplayObjectScatter(deepZoomImage4, app3.renderer, {
minScale: 0,
maxScale: 100,
onTransform: (event) => {
deepZoomImage4.transformed(event)
}
})
deepZoomImage4.mask = mask
scatterContainer3.addChild(deepZoomImage3)
// Create the second DeepZoomImage
//--------------------
const border = new PIXI.Graphics()
border.beginFill(0x282828, 1)
border.drawRect(0, 0, 264, 244)
scatterContainer3.addChild(border)
const mask = new PIXI.Graphics()
mask.beginFill(0x282828, 1)
mask.drawRect(0, 0, 260, 240)
scatterContainer3.addChild(mask)
const deepZoomImage4 = new DeepZoomImage(deepZoomInfo, {app: app3})
deepZoomImage4.x = 4
deepZoomImage4.y = 4
deepZoomImage4.scatter = new DisplayObjectScatter(deepZoomImage4, app3.renderer, {
minScale: 0,
maxScale: 100,
onTransform: (event) => {
deepZoomImage4.transformed(event)
}
})
deepZoomImage4.mask = mask
app3._deepZoomImage4 = deepZoomImage4
scatterContainer3.addChild(deepZoomImage4)
</script>
</body>
app3._deepZoomImage4 = deepZoomImage4
scatterContainer3.addChild(deepZoomImage4)
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

View File

@ -1,143 +1,135 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>DeepZoomImage Doctests</title>
<title>DeepZoomImage Doctests</title>
<link rel="stylesheet" href="../../3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../../../css/doctest.css" />
<link rel="stylesheet" href="../../3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../../../css/doctest.css">
<script src="../../3rdparty/highlight/highlight.pack.js"></script>
<script src="../../../dist/iwmlib.3rdparty.js"></script>
<script src="../../3rdparty/highlight/highlight.pack.js"></script>
<script src="../../../dist/iwmlib.3rdparty.js"></script>
<script src="../../../dist/iwmlib.js"></script>
<script src="../../../dist/iwmlib.pixi.js"></script>
<script src="../../../dist/iwmlib.js"></script>
<script src="../../../dist/iwmlib.pixi.js"></script>
<style>
#app {
display: table;
margin: 0 auto;
}
#app > * {
margin-bottom: 5px;
}
</style>
</head>
<body onload="Doctest.run()">
<h1>DeepZoomImage</h1>
<p>
The main class of a deeply zoomable image that is represented by a hierarchy of tile layers for each zoom level. This gives
the user the impression that even huge pictures (up to gigapixel-images) can be zoomed instantaneously, since the
tiles at smaller levels are scaled immediately and overloaded by more detailed tiles at the larger level as fast
as possible.
</p>
<br />
<div id="app">
<button id="change_dpr">Change Pixel Ratio</button>
<div id="canvas_container"></div>
<div id="info"></div>
</div>
<script class="doctest">
// When an element is added, the ScatterApp wrapps it in it's own Scatter Container.
// Just as in the doctest: scatter.html
class ScatterApp extends PIXIApp {
sceneFactory() {
return new ScatterContainer(this.renderer, { showBounds: true, app: this })
}
}
let app
let state = 0
//Destroys the PIXIApp element and the corresponding canvas,
//to reinstantiate the entire application.
changePIXI()
function changePIXI() {
if (typeof app != 'undefined') {
//The parameter destroys the canvas, when destroying the app.
// Not deleting a new canvas resulted in some
// weird PIXI error.
app.destroy(true)
<style>