105 Commits
Author SHA1 Message Date
Tjark Müller 1c74b23700 Merge branch 'main' into new-logging 2025-07-21 11:29:08 +02:00
Tjark Müller 458a4c41eb ad event types 2025-07-21 11:28:29 +02:00
Uwe Oestermeier ee76c41a2b Fixed resolution problems. 2024-05-27 13:52:15 +02:00
Uwe Oestermeier 3092eb53cb Removed another console log. 2024-05-27 09:36:32 +02:00
Uwe Oestermeier 3c7b220dd7 Removed console log. 2024-05-27 09:19:20 +02:00
Uwe Oestermeier 81f18ea2e9 Fixed memory problems of deepzoom images. 2024-05-24 10:29:24 +02:00
Uwe Oestermeier 13ea23186f Fixed flippable scaling problem. 2024-02-12 15:05:12 +01:00
Uwe Oestermeier 57d6e8b461 Merge branch 'main' of gitea.iwm-tuebingen.de:IWMBrowser/iwmlib into main
* 'main' of gitea.iwm-tuebingen.de:IWMBrowser/iwmlib:
  Removed console.logs
  Fixed timestamp name mismatch.
  Updated iwmlib and removed log statement.
  Added pluggable throw behavior.
  Replaced requestAnimationFrame with setInterval
  Moved throws to a single loop.
  Added cancelAnimationFrame
  update code block to reflect current state
  Fixed html double code
  Fixed problem with scrolling text in card drawers.
  added fix for viewBox rotation bug
  fixed rollup
  documented/ solved svg rotation bug

# Conflicts:
#	package.json
2024-02-12 14:15:58 +01:00
Uwe Oestermeier 8d8090cdde Added doctest for flippable images with different sizes. 2024-02-12 14:08:24 +01:00
Uwe Oestermeier 80355ea636 Removed console.logs 2023-07-28 14:03:39 +02:00
Uwe Oestermeier 45d081c225 Fixed timestamp name mismatch. 2023-07-21 12:44:03 +02:00
Uwe Oestermeier 07f50974f9 Updated iwmlib and removed log statement. 2023-07-21 09:48:00 +02:00
Uwe Oestermeier 8b33bc0536 Added pluggable throw behavior. 2023-07-21 08:36:14 +02:00
Uwe Oestermeier 47992755e2 Replaced requestAnimationFrame with setInterval 2023-07-20 14:50:48 +02:00
Uwe Oestermeier 97fb30d741 Moved throws to a single loop. 2023-07-20 14:38:23 +02:00
Uwe Oestermeier 52ccb49fa8 Added cancelAnimationFrame 2023-07-20 11:44:13 +02:00
Tjark Müller 1a0b9d55c1 update code block to reflect current state 2023-06-06 17:31:58 +02:00
Sebastian Kupke 4b80702b10 Merge branch 'main' of https://gitea.iwm-tuebingen.de/IWMBrowser/iwmlib 2023-06-06 14:26:17 +02:00
Sebastian Kupke 1e6bee007e Fixed html double code 2023-06-06 14:24:58 +02:00
Tjark Müller 98085006bb Merge branch 'main' of https://gitea.iwm-tuebingen.de/IWMBrowser/iwmlib 2023-06-02 14:24:51 +02:00
Uwe Oestermeier a748d05c38 Fixed problem with scrolling text in card drawers. 2023-06-01 18:19:30 +02:00
Tjark Müller 43e69365e0 Merge branch 'main' of gitea.iwm-tuebingen.de:IWMBrowser/iwmlib 2023-05-12 12:33:56 +02:00
Tjark Müller 12eb712fce added fix for viewBox rotation bug 2023-05-12 12:30:17 +02:00
Tjark Müller 03fda7e79e Merge branch 'main' of https://gitea.iwm-tuebingen.de/IWMBrowser/iwmlib 2023-05-10 15:25:13 +02:00
Uwe Oestermeier c8f7e39235 Added electron browser to allow snapshots of doctests that are stored in lib thumbnail subfolders. 2023-05-09 15:40:18 +02:00
Uwe Oestermeier 9501264f08 Added minimal navigation breadcrumbs to doctests. 2023-05-09 13:25:39 +02:00
Uwe Oestermeier 13e0473328 Added support for static doctest files generated by the iwmsite static site generator. 2023-05-09 09:52:39 +02:00
Tjark Müller d818ef9737 Merge branch 'main' of https://gitea.iwm-tuebingen.de/IWMBrowser/iwmlib 2023-05-04 13:33:02 +02:00
Tjark Müller d45b3e68dd fixed rollup 2023-05-04 13:32:47 +02:00
Tjark Müller 7a5bc222fd documented/ solved svg rotation bug 2023-05-04 13:28:58 +02:00
Sebastian Kupke 0cff31e65b Added pixi-compressed-textures plugin again. In Pixi v6 the plugin ist included, but cannot be activated if the browser don't use JavaScript Modules 2023-05-02 15:09:38 +02:00
Uwe Oestermeier dc40b4cffa Fixed bug in nearestActive. 2023-04-28 09:17:03 +02:00
Uwe Oestermeier 4da99ef04b Added new configuration flag and default behavior that prevents pointer click events which might lead to conflicts in touch applications. 2023-02-15 15:11:12 +01:00
Sebastian Kupke 7f1a3aae8e Removed compressed textures Pixi.js plugin from 3rdpartylib 2023-02-15 15:05:11 +01:00
Uwe Oestermeier 20d209cbe1 Changed order of tap handler and simulated clicks. 2023-02-09 16:02:09 +01:00
Sebastian Kupke 5af350182d Fixed touch event bug 2022-11-09 16:09:22 +01:00
Sebastian Kupke 2987b820a9 Moved polyfill from utils to own file 2022-11-09 14:11:34 +01:00
Sebastian Kupke 34792f20d6 Fixed popup bug 2022-11-08 15:44:20 +01:00
Sebastian Kupke 8275dad36c Removed alerts 2022-11-08 13:58:10 +01:00
Sebastian Kupke 7965c8459b Added more logging 2022-11-08 13:42:50 +01:00
Sebastian Kupke 89de46b0e6 Added more logging 2022-11-08 12:31:21 +01:00
Sebastian Kupke d8dc158be0 Added more logging 2022-11-08 12:07:37 +01:00
Sebastian Kupke 68e46e81c1 Fixed hyphenate bug 2022-11-08 11:11:39 +01:00
Sebastian Kupke 3856e59093 Removed hyphenation 2022-11-03 16:06:14 +01:00
Sebastian Kupke bbae811b50 Removed hyphenate 2022-11-03 15:28:01 +01:00
Sebastian Kupke 6767064ea7 Moved convertPointFromPageToNode polyfill to utils 2022-11-03 15:27:47 +01:00
Sebastian Kupke 3b1ab1f392 Removed pixelDeviceRatio 2022-10-04 15:26:17 +02:00
Sebastian Kupke c5c2759ebd Initial commit 2.0 beta 0 2022-10-04 10:51:35 +02:00
Sebastian Kupke f87b19140b Removed header from test images. 2022-05-05 12:31:58 +02:00
Sebastian Kupke 63ebbba42b Added second resolution test. 2022-05-05 12:29:27 +02:00
Sebastian Kupke 72c6abf031 Resolution tests. 2022-05-04 15:18:11 +02:00
Sebastian Kupke 9db52a004a Added scatter resolution test. 2022-05-04 13:27:31 +02:00
Sebastian Kupke 527fcb993c Removed videos from resolution test. 2022-05-04 09:53:55 +02:00
Sebastian Kupke c41209d0fc Added description to resolution. 2022-05-04 09:10:01 +02:00
Sebastian Kupke 7e9f0159e8 Added resolution test. 2022-05-03 16:50:19 +02:00
Uwe Oestermeier 074fb67906 Fixed problem with outdated loader and error handler.s 2022-05-02 16:41:22 +02:00
Sebastian Kupke 51b0ef4b1b Added VSCode workspace file. 2022-05-02 11:13:03 +02:00
Sebastian Kupke 1efe94ef97 Bump version number. 2022-04-29 10:30:05 +02:00
Sebastian Kupke 7f068c5d94 Fixed resize fullscreen bug. 2022-04-29 10:28:59 +02:00
Uwe Oestermeier eff934e8b5 Moved the addVelocity method after the transform to allow a modification of the delta within the transform method. To make this destructive side effect more explicit transform could return a modified value. But that could effect several projects. 2021-04-28 17:02:54 +02:00
Sebastian Kupke 266a770c8e Merge branch 'master' into obersalzberg
# Conflicts:
#	dist/iwmlib.js
#	dist/iwmlib.pixi.js
2021-03-15 17:02:08 +01:00
Uwe Oestermeier a5f94e0a5f Reverted changes from commit fa0256d782 and moved addVelocity from _move back to old locations. 2021-03-15 15:39:07 +01:00
Uwe Oestermeier fff7a4f685 Fixed import order. 2021-03-01 08:58:44 +01:00
Sebastian Kupke d0ae8893cc Version 1.1.1. 2021-02-24 16:15:20 +01:00
Sebastian Kupke 1b4eee6498 Merge branch 'master' of gitea.iwm-tuebingen.de:IWMBrowser/iwmlib 2021-02-24 16:14:26 +01:00
Sebastian Kupke ac779af339 Popup close buttons can take a URL. 2021-02-24 16:13:50 +01:00
Severin Opel 1316a727f3 Fixed offset error. 2021-02-01 13:54:40 +01:00
Severin Opel be950ec8c1 Tried fix scatter. 2021-02-01 13:40:57 +01:00
Severin Opel ca5a39d661 Changed wrapper and events to allow other EventListeners. 2021-01-26 08:01:51 +01:00
Severin Opel fa0256d782 Moved addVelocity to _move. 2021-01-05 16:25:55 +01:00
Severin Opel 114c217ffa Improved logging and adjusted card's click handler
+ Logging now also works when ipcRenderer is appended to the window element.
+ Card's callback replacement method was changed from click to pointerdown.
2020-09-15 11:36:59 +02:00
Severin Opel 28cfdc180c Fixt highlight order on creation. 2020-09-01 13:38:22 +02:00
Uwe Oestermeier 116058ba77 Fixed a problem with clicking transformed rects in card wrappers. 2020-08-27 07:38:35 +02:00
Uwe Oestermeier 4211548b8b Removed console logs. 2020-08-04 18:13:24 +02:00
Uwe Oestermeier 12b17fd7af Fixed call of popup menu commands with parameters.s 2020-08-04 18:07:48 +02:00
Uwe Oestermeier 47b178fbc1 Added event parameter to PopupMenu perform. 2020-08-04 17:35:15 +02:00
Severin Opel 6ab63eb32c Now calling 'this' to allow overloading of methods. 2020-04-21 12:43:55 +02:00
Severin Opel 6445ab3f57 CardLoader.load now checks for ready state. 2020-04-19 13:30:35 +02:00
Severin Opel 129672eb0f Added options object to toggle function. 2020-04-14 12:14:40 +02:00
Uwe Oestermeier 70031d3bfa Removed comment since border case appeared in Edge. 2020-02-25 15:19:01 +01:00
Uwe Oestermeier 9db5723746 Updated dist 2020-02-17 16:20:16 +01:00
Uwe Oestermeier d4ab5c7e9c Merge branch 'master' of https://gitea.iwm-tuebingen.de/IWMBrowser/iwmlib
* 'master' of https://gitea.iwm-tuebingen.de/IWMBrowser/iwmlib:
  Changed scale
  Updated to PixiJS 5.2.1.
2020-02-17 16:19:33 +01:00
Uwe Oestermeier b068fd176f Added lineWidth support. 2020-02-17 16:18:55 +01:00
Uwe Oestermeier f70968fe01 Changed scale 2020-02-07 20:08:44 +01:00
Sebastian Kupke 1c70433b86 Updated to PixiJS 5.2.1. 2020-02-04 16:48:36 +01:00
Uwe Oestermeier 54b685ab42 Changed calling order 2020-02-03 10:06:27 +01:00
Uwe Oestermeier e6f6f6b185 Changed calling order 2020-02-03 10:06:01 +01:00
Uwe Oestermeier 7bb3b696f7 Created new build. 2020-01-31 10:26:41 +01:00
Sebastian Kupke 6cdf5310ba Added graphics alpha workaround. 2020-01-30 15:41:31 +01:00
Sebastian Kupke 1967b791e0 Updated dependencies. 2020-01-22 13:50:40 +01:00
Sebastian Kupke ebb4b9154e Updated pixi-compressed-textures. 2020-01-20 11:33:53 +01:00
Sebastian Kupke b32132c71f Removed stopEvents from RigidContainer. 2020-01-10 14:18:28 +01:00
Sebastian Kupke f398fe4824 Fixed reference bug. 2020-01-09 15:14:27 +01:00
Severin Opel d5b87f23cf Updated generated content (Docs & dist). 2019-12-12 19:04:11 +01:00
Severin Opel 7f2fc93fa3 Reworked overlay itself and doctest. 2019-12-12 19:02:58 +01:00
Severin Opel 32e913c629 Fixed wrongly assigned parameters 2019-12-12 17:23:39 +01:00
Severin Opel 87c966062c Map's cover state now updates every time cover is requested. 2019-12-12 17:01:51 +01:00
Severin Opel d7f867f1a6 Added maps graphics. 2019-12-11 17:30:11 +01:00
Severin Opel e188f3474f Reverted index.css to previous version. 2019-12-11 16:52:23 +01:00
Severin Opel 1e80845aa6 Fixed cover bug. Renamed mapdata to mapprojection. 2019-12-11 16:45:26 +01:00
Severin Opel a85569e54d commited for presentation. 2019-12-11 15:29:59 +01:00
Severin Opel 65fac2f406 Renamed 'MapView' to 'MapViewport'. Added documentation to the maps module. 2019-12-09 18:15:28 +01:00
Severin Opel 60e28f8fe5 Improved doctest css. 2019-12-09 14:29:26 +01:00
Severin Opel 792892cb82 Removed PIXIUtils from geographics. 2019-12-09 14:27:32 +01:00
Severin Opel 9159073483 Fixed GeoGraphics doctest. 2019-12-05 17:06:50 +01:00
346 changed files with 257752 additions and 103618 deletions
+4 -1
View File
@@ -28,5 +28,8 @@
"plugins": [ "plugins": [
"prettier" "prettier"
], ],
"extends": ["eslint:recommended", "plugin:prettier/recommended"] "extends": ["eslint:recommended", "plugin:prettier/recommended"],
"rules": {
"comma-dangle": ["error", "never"]
}
} }
+7 -1
View File
@@ -77,6 +77,12 @@ typings/
.fusebox/ .fusebox/
# own # own
*.code-workspace
.history/ .history/
.vscode/ .vscode/
# ignore generated contents-
/doc/out/*
**/thumbnails
**/thumbnail.png
/site/dist
/site/__pycache__
+2 -1
View File
@@ -3,5 +3,6 @@
"jsxSingleQuote": true, "jsxSingleQuote": true,
"tabWidth": 4, "tabWidth": 4,
"semi": false, "semi": false,
"printWidth": 120 "printWidth": 120,
"trailingComma": "none"
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

+13
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
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
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
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
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
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
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
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
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
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 }
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+231
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
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
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
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
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
}
+148 -10
View File
@@ -1,15 +1,47 @@
:root {
--white: rgb(250, 250, 250);
--light-gray: rgb(219, 219, 219);
--gray: rgb(66, 66, 66);
--black: rgb(20, 20, 20);
--dark-background: rgb(50, 50, 50);
--border-radius: 5px;
}
html { html {
padding: 0; padding: 0;
font-size: 16px; font-size: 16px;
background: white; background: var(--white);
color: #000; color: var(--black);
font-family: 'Open Sans', Arial, sans-serif; font-family: 'Open Sans', Arial, sans-serif;
max-width: 1200px; max-width: 1200px;
margin: 0 auto; margin: 0 auto;
} }
.dark-mode nav {
border-color: var(--white);
}
nav {
margin-top: 50px;
min-width: 30%;
border-radius: var(--border-radius);
display: inline-block;
padding: 20px 30px;
/* background-color: var(--light-gray); */
border: 1px solid var(--black);
}
nav > h3 {
margin: 0;
}
nav > ol a {
font-weight: bold;
}
html.dark-mode { html.dark-mode {
background: rgb(51, 51, 51); background: var(--dark-background);
color: #ccc; color: #ccc;
} }
@@ -22,16 +54,102 @@ html.dark-mode {
color: white; color: white;
} }
.dark-mode a{ a {
color: #569CD6; color: #569cd6;
font-weight: bold; font-weight: bold;
} }
.dark-mode a:hover{ .dark-mode a:hover {
color: white; color: white;
} }
a{ .dark-mode button {
border: 1px solid rgb(211, 211, 211);
color: rgb(211, 211, 211);
background: linear-gradient(to top, rgb(53, 53, 53), rgb(73, 73, 73));
}
button {
background-color: var(--white);
border: 1px solid var(--gray);
color: var(--gray);
padding: 10px 20px;
border-radius: 5px;
transition: transform 0.2s;
}
label {
display: flex;
align-items: center;
user-select: none;
cursor: pointer;
}
input[type='checkbox'] {
display: none;
}
input[type='checkbox'] + .checkbox {
position: relative;
margin-right: 10px;
width: 32px;
height: 32px;
}
.controls {
display: flex;
}
.controls > * {
margin-right: 1em;
}
.dark-mode .checkbox {
background-color: var(--gray);
border: 1px solid var(--white);
}
.checkbox {
display: inline-block;
border-radius: 3px;
background-color: var(--white);
border: 1px solid var(--black);
}
.checkbox:after {
content: '';
position: absolute;
}
input[type='checkbox']:checked + .checkbox {
background-color: rgb(174, 238, 78);
}
input[type='checkbox']:checked + .checkbox:after {
left: 11px;
top: 6px;
width: 5px;
height: 12px;
border: solid var(--white);
border-width: 0 4px 4px 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
button:active {
transform: scale(0.98);
}
button:hover {
filter: brightness(0.95);
}
button:focus {
box-shadow: 0 0 3px 1px #569cd6;
outline: none;
}
a {
text-decoration: none; text-decoration: none;
} }
@@ -53,8 +171,8 @@ a{
.doctest { .doctest {
margin: 0; margin: 0;
padding-top: 50px; padding-top: 20px;
padding-bottom: 50px; padding-bottom: 20px;
box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.5); box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.5);
} }
.collapsed .doctest { .collapsed .doctest {
@@ -71,7 +189,7 @@ a{
margin-bottom: 1em; margin-bottom: 1em;
} }
.doctest-collapsible-toggle { .doctest-titlebar {
display: flex; display: flex;
align-items: center; align-items: center;
color: whitesmoke; color: whitesmoke;
@@ -146,3 +264,23 @@ canvas {
width: 100%; width: 100%;
height: 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;
}
-1
View File
@@ -1,4 +1,3 @@
.flipWrapper { .flipWrapper {
position: absolute; position: absolute;
top: 0; top: 0;
+42 -16
View File
@@ -27,6 +27,10 @@ body {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
h2 {
padding-left: 20px;
}
h3 { h3 {
color: white; color: white;
padding: 4px; padding: 4px;
@@ -34,7 +38,9 @@ h3 {
background-color: rgba(0, 0, 15, 0.5); background-color: rgba(0, 0, 15, 0.5);
} }
a { text-decoration: none; } a {
text-decoration: none;
}
div.wrapper { div.wrapper {
overflow: hidden; overflow: hidden;
@@ -46,28 +52,48 @@ div.wrapper {
/* Color animation from https://www.tjvantoll.com/2012/02/20/css3-color-animations/ */ /* Color animation from https://www.tjvantoll.com/2012/02/20/css3-color-animations/ */
@-webkit-keyframes color_change { @-webkit-keyframes color_change {
from { background-color: rgba(0, 0, 0, 0); } from {
to { background-color: red; } background-color: rgba(0, 0, 0, 0);
}
to {
background-color: red;
}
} }
@-moz-keyframes color_change { @-moz-keyframes color_change {
from { background-color: rgba(0, 0, 0, 0); } from {
to { background-color: red; } background-color: rgba(0, 0, 0, 0);
}
to {
background-color: red;
}
} }
@-ms-keyframes color_change { @-ms-keyframes color_change {
from { background-color: rgba(0, 0, 0, 0); } from {
to { background-color: red; } background-color: rgba(0, 0, 0, 0);
}
to {
background-color: red;
}
} }
@-o-keyframes color_change { @-o-keyframes color_change {
from { background-color: rgba(0, 0, 0, 0); } from {
to { background-color: red; } background-color: rgba(0, 0, 0, 0);
}
to {
background-color: red;
}
} }
@keyframes color_change { @keyframes color_change {
from { background-color: rgba(0, 0, 0, 0); } from {
to { background-color: red; } 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 ***/ /*** 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 { .thumbnail::after {
content: ""; content: '';
display: block; display: block;
position: absolute; position: absolute;
top: 0; top: 0;
@@ -145,12 +171,12 @@ div.title {
min-height: 100%; min-height: 100%;
min-width: 100%; min-width: 100%;
display: -webkit-flex; display: -webkit-flex;
-webkit-align-items: flex-end; -webkit-align-items: flex-start;
align-items: flex-end; align-items: flex-start;
-webkit-flex-wrap: wrap; -webkit-flex-wrap: wrap;
flex-wrap: wrap; flex-wrap: wrap;
-webkit-align-content: flex-end; -webkit-align-content: flex-start;
align-content: flex-end; align-content: flex-start;
} }
/** See https://github.com/electron/electron/issues/4420 */ /** See https://github.com/electron/electron/issues/4420 */
+69320 -68876
View File
File diff suppressed because one or more lines are too long
+253 -1
View File
File diff suppressed because one or more lines are too long
+15 -15
View File
@@ -8119,10 +8119,10 @@ if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } //necessary in case Tween
css transforms without perspective projection) css transforms without perspective projection)
*/ */
(function () { ;(function () {
'use strict' 'use strict'
var I = (typeof(WebKitCSSMatrix) == 'undefined') ? new DOMMatrix() : new WebKitCSSMatrix() var I = typeof WebKitCSSMatrix == 'undefined' ? new DOMMatrix() : new WebKitCSSMatrix()
function Point(x, y, z) { function Point(x, y, z) {
this.x = x this.x = x
@@ -8137,8 +8137,8 @@ if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } //necessary in case Tween
function createMatrix(transform) { function createMatrix(transform) {
try { try {
return (typeof(WebKitCSSMatrix) == 'undefined') ? new DOMMatrix(transform) : new WebKitCSSMatrix(transform) return typeof WebKitCSSMatrix == 'undefined' ? new DOMMatrix(transform) : new WebKitCSSMatrix(transform)
} catch(e) { } catch (e) {
console.warn(transform) console.warn(transform)
console.warn(e.toString()) console.warn(e.toString())
return I return I
@@ -8163,8 +8163,9 @@ if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } //necessary in case Tween
var left = +Infinity var left = +Infinity
var top = +Infinity var top = +Infinity
while (--i >= 0) { while (--i >= 0) {
var p = new Point(i === 0 || i === 1 ? 0 : w, i === 0 || i === 3 ? 0 : h, var p = new Point(i === 0 || i === 1 ? 0 : w, i === 0 || i === 3 ? 0 : h, 0).transformBy(
0).transformBy(transformationMatrix) transformationMatrix
)
if (p.x < left) { if (p.x < left) {
left = p.x left = p.x
} }
@@ -8173,20 +8174,19 @@ if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } //necessary in case Tween
} }
} }
var rect = element.getBoundingClientRect() var rect = element.getBoundingClientRect()
transformationMatrix = I.translate(window.pageXOffset + rect.left - left, transformationMatrix = I.translate(
window.pageYOffset + rect.top - top, 0) window.pageXOffset + rect.left - left,
.multiply(transformationMatrix) window.pageYOffset + rect.top - top,
0
).multiply(transformationMatrix)
return transformationMatrix return transformationMatrix
} }
window.convertPointFromPageToNode = function (element, pageX, pageY) { window.convertPointFromPageToNode = function (element, pageX, pageY) {
return new Point(pageX, pageY, 0).transformBy( return new Point(pageX, pageY, 0).transformBy(getTransformationMatrix(element).inverse())
getTransformationMatrix(element).inverse())
} }
window.convertPointFromNodeToPage = function (element, offsetX, offsetY) { window.convertPointFromNodeToPage = function (element, offsetX, offsetY) {
return new Point(offsetX, offsetY, 0).transformBy( return new Point(offsetX, offsetY, 0).transformBy(getTransformationMatrix(element))
getTransformationMatrix(element))
} }
})();
}());
File diff suppressed because one or more lines are too long
+563 -458
View File
File diff suppressed because it is too large Load Diff
+1779 -1007
View File
File diff suppressed because it is too large Load Diff
+117 -132
View File
@@ -1,30 +1,28 @@
<!DOCTYPE html> <!DOCTYPE html>
<meta charset="utf-8"> <meta charset="utf-8" />
<style> <style>
.node circle {
fill: #999;
}
.node circle { .node text {
fill: #999; font: 10px sans-serif;
} }
.node text { .node--internal circle {
font: 10px sans-serif; fill: #555;
} }
.node--internal circle { .node--internal text {
fill: #555; text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff;
} }
.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;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
</style> </style>
<svg width="960" height="512"></svg> <svg width="960" height="512"></svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <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/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 src="https://cdnjs.cloudflare.com/ajax/libs/acorn/4.0.11/acorn.min.js"></script>
<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") console.info(ast)
.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)
drawAst(ast) drawAst(ast)
}) })
/* /*
* *
*/ */
function drawAst(ast) { function drawAst(ast) {
// Create SVG element
// Create SVG element //---------------------------
//--------------------------- let svg = d3.select('svg')
let svg = d3.select("svg") const width = svg.attr('width')
const width = svg.attr("width") const height = svg.attr('height')
const height = svg.attr("height") let g = svg.append('g').attr('transform', 'translate(40,0)')
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);
})
// Create D3 Cluster // Convert data
//--------------------------- //---------------------------
let tree = d3.cluster().size([height, width - 200]) const data = acornToHierarchy(ast)
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)
}
/* // Create D3 Hierarchy
* //---------------------------
*/ let root = d3.hierarchy(data).sort((a, b) => {
function acornToHierarchy(ast) { return a.height - b.height || a.data.name.localeCompare(b.data.name)
})
console.info(JSON.stringify(ast))
// Create D3 Cluster
let data = {} //---------------------------
let tree = d3.cluster().size([height, width - 200])
for (const clazz of ast.body) { tree(root)
if (clazz.type === "ClassDeclaration") {
data.name = clazz.id.name // Create SVG elements
data.children = [] //---------------------------
let link = g
for (const method of clazz.body.body) { .selectAll('.link')
if (method.type === "MethodDefinition") { .data(root.descendants().slice(1))
data.children.push({ .enter()
name: method.key.name, .append('path')
children: [] .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 = { const data = {
"name": "Eve", "name": "Eve",
"children": [{ "children": [{
@@ -153,22 +153,7 @@ function acornToHierarchy(ast) {
}] }]
} }
*/ */
return data
}
return data
}
</script>
</script>
+20 -3
View File
@@ -36,7 +36,21 @@
"./lib/pixi/switch.js", "./lib/pixi/switch.js",
"./lib/pixi/theme.js", "./lib/pixi/theme.js",
"./lib/pixi/tooltip.js", "./lib/pixi/tooltip.js",
"./lib/pixi/volatile.js" "./lib/pixi/volatile.js",
"./lib/pixi/maps/mapapp.js",
"./lib/pixi/maps/mapviewport.js",
"./lib/pixi/maps/geographics.js",
"./lib/pixi/maps/geojson.js",
"./lib/pixi/maps/geolayer.js",
"./lib/pixi/maps/mapprojection.js",
"./lib/pixi/maps/maplist.js",
"./lib/pixi/maps/overlay.js",
"./lib/pixi/maps/scatter.js",
"./lib/pixi/maps/utils.js",
"./lib/pixi/maps/map.js",
"./lib/pixi/maps/projections/projection.js",
"./lib/pixi/maps/projections/mercator.js",
"./lib/pixi/maps/projections/robinson.js"
], ],
"exclude": [], "exclude": [],
"includePattern": ".+\\.js(doc|x)?$", "includePattern": ".+\\.js(doc|x)?$",
@@ -45,7 +59,10 @@
"sourceType": "module", "sourceType": "module",
"tags": { "tags": {
"allowUnknownTags": true, "allowUnknownTags": true,
"dictionaries": ["jsdoc", "closure"] "dictionaries": [
"jsdoc",
"closure"
]
}, },
"templates": { "templates": {
"applicationName": "iwmlib", "applicationName": "iwmlib",
@@ -70,4 +87,4 @@
"useLongnameInNav": false "useLongnameInNav": false
} }
} }
} }
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+1830 -239
View File
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+1825 -234
View File
File diff suppressed because it is too large Load Diff
+1825 -234
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1825 -234
View File
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+1825 -244
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1825 -244
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1828 -237
View File
File diff suppressed because it is too large Load Diff
+1830 -246
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1826 -251
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+1829 -236
View File
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+1825 -234
View File
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+1829 -238
View File
File diff suppressed because it is too large Load Diff
+1826 -235
View File
File diff suppressed because it is too large Load Diff
+2583 -236
View File
File diff suppressed because it is too large Load Diff
+1817 -230
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1817 -230
View File
File diff suppressed because it is too large Load Diff
+1817 -230
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1829 -240
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1817 -230
View File
File diff suppressed because it is too large Load Diff
+1817 -230
View File
File diff suppressed because it is too large Load Diff
+1817 -230
View File
File diff suppressed because it is too large Load Diff
+1841 -233
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1817 -230
View File
File diff suppressed because it is too large Load Diff
+1817 -230
View File
File diff suppressed because it is too large Load Diff
+1817 -230
View File
File diff suppressed because it is too large Load Diff
+1817 -230
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
(function(){var e=0;var a;var t=document.getElementById("source-code");if(t){var n=config.linenums;if(n){t=t.getElementsByTagName("ol")[0];a=Array.prototype.slice.apply(t.children);a=a.map(function(a){e++;a.id="line"+e})}else{t=t.getElementsByTagName("code")[0];a=t.innerHTML.split("\n");a=a.map(function(a){e++;return'<span id="line'+e+'"></span>'+a});t.innerHTML=a.join("\n")}}})();$(function(){var e=$(".navigation");var a=e.find(".list");var t=$(".search");$("#search").on("keyup",function(t){var n=this.value.trim();if(n){var s=new RegExp(n,"i");e.addClass("searching").removeClass("not-searching").find("li, .itemMembers").removeClass("match");e.find("li").each(function(e,a){var t=$(a);if(t.data("name")&&s.test(t.data("name"))){t.addClass("match");t.closest(".itemMembers").addClass("match");t.closest(".item").addClass("match")}})}else{e.removeClass("searching").addClass("not-searching").find(".item, .itemMembers").removeClass("match")}a.scrollTop(0)});$("#menuToggle").click(function(){a.toggleClass("show");t.toggleClass("show")});e.addClass("not-searching");var n=$(".page-title").data("filename").replace(/\.[a-z]+$/,"");var s=e.find('.item[data-name*="'+n+'"]:eq(0)');if(s.length){s.remove().prependTo(a).addClass("current")}if(config.disqus){$(window).on("load",function(){var e=config.disqus;var a=document.createElement("script");a.type="text/javascript";a.async=true;a.src="http://"+e+".disqus.com/embed.js";(document.getElementsByTagName("head")[0]||document.getElementsByTagName("body")[0]).appendChild(a);var t=document.createElement("script");t.async=true;t.type="text/javascript";t.src="http://"+e+".disqus.com/count.js";document.getElementsByTagName("BODY")[0].appendChild(t)})}}); (function(){var e=0;var a;var t=document.getElementById("source-code");if(t){var n=config.linenums;if(n){t=t.getElementsByTagName("ol")[0];a=Array.prototype.slice.apply(t.children);a=a.map(function(a){e++;a.id="line"+e})}else{t=t.getElementsByTagName("code")[0];a=t.innerHTML.split("\n");a=a.map(function(a){e++;return'<span id="line'+e+'"></span>'+a});t.innerHTML=a.join("\n")}}})();$(function(){var e=$(".navigation");var a=e.find(".list");var t=$(".search");$("#search").on("keyup",function(t){var n=this.value.trim();if(n){var s=new RegExp(n,"i");e.addClass("searching").removeClass("not-searching").find("li, .itemMembers").removeClass("match");e.find("li").each(function(e,a){var t=$(a);if(t.data("name")&&s.test(t.data("name"))){t.addClass("match");t.closest(".itemMembers").addClass("match");t.closest(".item").addClass("match")}})}else{e.removeClass("searching").addClass("not-searching").find(".item, .itemMembers").removeClass("match")}a.scrollTop(0)});$("#menuToggle").click(function(){a.toggleClass("show");t.toggleClass("show")});e.addClass("not-searching");var n=$(".page-title").data("filename").replace(/\.[a-z]+$/,"");var s=e.find('a[href*="'+n+'"]:eq(0)').closest(".item");if(s.length){if(s.parents(".children").length){s.addClass("current");s.find("li.item").addClass("notCurrent");s=s.parents("ul.list>li.item")}s.remove().prependTo(a).addClass("current")}if(config.disqus){$(window).on("load",function(){var e=config.disqus;var a=document.createElement("script");a.type="text/javascript";a.async=true;a.src="http://"+e+".disqus.com/embed.js";(document.getElementsByTagName("head")[0]||document.getElementsByTagName("body")[0]).appendChild(a);var t=document.createElement("script");t.async=true;t.type="text/javascript";t.src="http://"+e+".disqus.com/count.js";document.getElementsByTagName("BODY")[0].appendChild(t)})}});
File diff suppressed because one or more lines are too long
+1817 -230
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -11,12 +11,12 @@ function vendors() {
'./node_modules/optimal-select/dist/optimal-select.js', './node_modules/optimal-select/dist/optimal-select.js',
'./node_modules/hammerjs/hammer.js', './node_modules/hammerjs/hammer.js',
'./node_modules/propagating-hammerjs/propagating.js', './node_modules/propagating-hammerjs/propagating.js',
'./node_modules/pixi.js/dist/pixi.js', './node_modules/pixi.js/dist/browser/pixi.js',
'./node_modules/pixi-compressed-textures/lib/crn_decomp.js', './node_modules/pixi-compressed-textures/lib/crn_decomp.js',
'./node_modules/pixi-compressed-textures/dist/pixi-compressed-textures.js', './node_modules/pixi-compressed-textures/dist/pixi-compressed-textures.js',
'./node_modules/pixi-filters/dist/pixi-filters.js', './node_modules/pixi-filters/dist/pixi-filters.js',
'./node_modules/pixi-particles/dist/pixi-particles.js', './node_modules/pixi-particles/dist/pixi-particles.js',
'./node_modules/pixi-projection/dist/pixi-projection.js', './node_modules/pixi-projection/dist/pixi-projection.umd.js',
'./node_modules/gsap/src/uncompressed/TweenMax.js', './node_modules/gsap/src/uncompressed/TweenMax.js',
'./node_modules/gsap/src/uncompressed/TimelineMax.js', './node_modules/gsap/src/uncompressed/TimelineMax.js',
'./lib/3rdparty/jquery.js', './lib/3rdparty/jquery.js',
+8
View File
@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}
+15 -15
View File
@@ -10,10 +10,10 @@
css transforms without perspective projection) css transforms without perspective projection)
*/ */
(function () { ;(function () {
'use strict' 'use strict'
var I = (typeof(WebKitCSSMatrix) == 'undefined') ? new DOMMatrix() : new WebKitCSSMatrix() var I = typeof WebKitCSSMatrix == 'undefined' ? new DOMMatrix() : new WebKitCSSMatrix()
function Point(x, y, z) { function Point(x, y, z) {
this.x = x this.x = x
@@ -28,8 +28,8 @@
function createMatrix(transform) { function createMatrix(transform) {
try { try {
return (typeof(WebKitCSSMatrix) == 'undefined') ? new DOMMatrix(transform) : new WebKitCSSMatrix(transform) return typeof WebKitCSSMatrix == 'undefined' ? new DOMMatrix(transform) : new WebKitCSSMatrix(transform)
} catch(e) { } catch (e) {
console.warn(transform) console.warn(transform)
console.warn(e.toString()) console.warn(e.toString())
return I return I
@@ -54,8 +54,9 @@
var left = +Infinity var left = +Infinity
var top = +Infinity var top = +Infinity
while (--i >= 0) { while (--i >= 0) {
var p = new Point(i === 0 || i === 1 ? 0 : w, i === 0 || i === 3 ? 0 : h, var p = new Point(i === 0 || i === 1 ? 0 : w, i === 0 || i === 3 ? 0 : h, 0).transformBy(
0).transformBy(transformationMatrix) transformationMatrix
)
if (p.x < left) { if (p.x < left) {
left = p.x left = p.x
} }
@@ -64,20 +65,19 @@
} }
} }
var rect = element.getBoundingClientRect() var rect = element.getBoundingClientRect()
transformationMatrix = I.translate(window.pageXOffset + rect.left - left, transformationMatrix = I.translate(
window.pageYOffset + rect.top - top, 0) window.pageXOffset + rect.left - left,
.multiply(transformationMatrix) window.pageYOffset + rect.top - top,
0
).multiply(transformationMatrix)
return transformationMatrix return transformationMatrix
} }
window.convertPointFromPageToNode = function (element, pageX, pageY) { window.convertPointFromPageToNode = function (element, pageX, pageY) {
return new Point(pageX, pageY, 0).transformBy( return new Point(pageX, pageY, 0).transformBy(getTransformationMatrix(element).inverse())
getTransformationMatrix(element).inverse())
} }
window.convertPointFromNodeToPage = function (element, offsetX, offsetY) { window.convertPointFromNodeToPage = function (element, offsetX, offsetY) {
return new Point(offsetX, offsetY, 0).transformBy( return new Point(offsetX, offsetY, 0).transformBy(getTransformationMatrix(element))
getTransformationMatrix(element))
} }
})();
}());
+191 -183
View File
@@ -1,216 +1,224 @@
(function () { ;(function () {
var module = {
var module = { exports: null,
exports: null
};
/**
* @constructor
* @param {!{patterns: !Object, leftmin: !number, rightmin: !number}} language The language pattern file. Compatible with Hyphenator.js.
*/
function Hypher(language) {
var exceptions = [],
i = 0;
/**
* @type {!Hypher.TrieNode}
*/
this.trie = this.createTrie(language['patterns']);
/**
* @type {!number}
* @const
*/
this.leftMin = language['leftmin'];
/**
* @type {!number}
* @const
*/
this.rightMin = language['rightmin'];
/**
* @type {!Object.<string, !Array.<string>>}
*/
this.exceptions = {};
if (language['exceptions']) {
exceptions = language['exceptions'].split(/,\s?/g);
for (; i < exceptions.length; i += 1) {
this.exceptions[exceptions[i].replace(/\u2027/g, '').toLowerCase()] = new RegExp('(' + exceptions[i].split('\u2027').join(')(') + ')', 'i');
}
} }
} /**
* @constructor
* @param {!{patterns: !Object, leftmin: !number, rightmin: !number}} language The language pattern file. Compatible with Hyphenator.js.
*/
function Hypher(language) {
var exceptions = [],
i = 0
/**
* @type {!Hypher.TrieNode}
*/
this.trie = this.createTrie(language['patterns'])
/** /**
* @typedef {{_points: !Array.<number>}} * @type {!number}
*/ * @const
Hypher.TrieNode; */
this.leftMin = language['leftmin']
/** /**
* Creates a trie from a language pattern. * @type {!number}
* @private * @const
* @param {!Object} patternObject An object with language patterns. */
* @return {!Hypher.TrieNode} An object trie. this.rightMin = language['rightmin']
*/
Hypher.prototype.createTrie = function (patternObject) {
var size = 0,
i = 0,
c = 0,
p = 0,
chars = null,
points = null,
codePoint = null,
t = null,
tree = {
_points: []
},
patterns;
for (size in patternObject) { /**
if (patternObject.hasOwnProperty(size)) { * @type {!Object.<string, !Array.<string>>}
patterns = patternObject[size].match(new RegExp('.{1,' + (+size) + '}', 'g')); */
this.exceptions = {}
for (i = 0; i < patterns.length; i += 1) { if (language['exceptions']) {
chars = patterns[i].replace(/[0-9]/g, '').split(''); exceptions = language['exceptions'].split(/,\s?/g)
points = patterns[i].split(/\D/);
t = tree;
for (c = 0; c < chars.length; c += 1) { for (; i < exceptions.length; i += 1) {
codePoint = chars[c].charCodeAt(0); this.exceptions[exceptions[i].replace(/\u2027/g, '').toLowerCase()] = new RegExp(
'(' + exceptions[i].split('\u2027').join(')(') + ')',
if (!t[codePoint]) { 'i'
t[codePoint] = {}; )
}
t = t[codePoint];
}
t._points = [];
for (p = 0; p < points.length; p += 1) {
t._points[p] = points[p] || 0;
}
} }
} }
} }
return tree;
};
/** /**
* Hyphenates a text. * @typedef {{_points: !Array.<number>}}
* */
* @param {!string} str The text to hyphenate. Hypher.TrieNode
* @return {!string} The same text with soft hyphens inserted in the right positions.
*/
Hypher.prototype.hyphenateText = function (str, minLength) {
minLength = minLength || 4;
// Regexp("\b", "g") splits on word boundaries, /**
// compound separators and ZWNJ so we don't need * Creates a trie from a language pattern.
// any special cases for those characters. Unfortunately * @private
// it does not support unicode word boundaries, so * @param {!Object} patternObject An object with language patterns.
// we implement it manually. * @return {!Hypher.TrieNode} An object trie.
var words = str.split(/([a-zA-Z0-9_\u0027\u00DF-\u00EA\u00EC-\u00EF\u00F1-\u00F6\u00F8-\u00FD\u0101\u0103\u0105\u0107\u0109\u010D\u010F\u0111\u0113\u0117\u0119\u011B\u011D\u011F\u0123\u0125\u012B\u012F\u0131\u0135\u0137\u013C\u013E\u0142\u0144\u0146\u0148\u0151\u0153\u0155\u0159\u015B\u015D\u015F\u0161\u0165\u016B\u016D\u016F\u0171\u0173\u017A\u017C\u017E\u017F\u0219\u021B\u02BC\u0390\u03AC-\u03CE\u03F2\u0401\u0410-\u044F\u0451\u0454\u0456\u0457\u045E\u0491\u0531-\u0556\u0561-\u0587\u0902\u0903\u0905-\u090B\u090E-\u0910\u0912\u0914-\u0928\u092A-\u0939\u093E-\u0943\u0946-\u0948\u094A-\u094D\u0982\u0983\u0985-\u098B\u098F\u0990\u0994-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BE-\u09C3\u09C7\u09C8\u09CB-\u09CD\u09D7\u0A02\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A14-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A82\u0A83\u0A85-\u0A8B\u0A8F\u0A90\u0A94-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABE-\u0AC3\u0AC7\u0AC8\u0ACB-\u0ACD\u0B02\u0B03\u0B05-\u0B0B\u0B0F\u0B10\u0B14-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3E-\u0B43\u0B47\u0B48\u0B4B-\u0B4D\u0B57\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB5\u0BB7-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C02\u0C03\u0C05-\u0C0B\u0C0E-\u0C10\u0C12\u0C14-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3E-\u0C43\u0C46-\u0C48\u0C4A-\u0C4D\u0C82\u0C83\u0C85-\u0C8B\u0C8E-\u0C90\u0C92\u0C94-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBE-\u0CC3\u0CC6-\u0CC8\u0CCA-\u0CCD\u0D02\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3E-\u0D43\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D60\u0D61\u0D7A-\u0D7F\u1F00-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB2-\u1FB4\u1FB6\u1FB7\u1FBD\u1FBF\u1FC2-\u1FC4\u1FC6\u1FC7\u1FD2\u1FD3\u1FD6\u1FD7\u1FE2-\u1FE7\u1FF2-\u1FF4\u1FF6\u1FF7\u200D\u2019]+)/g); */
Hypher.prototype.createTrie = function (patternObject) {
var size = 0,
i = 0,
c = 0,
p = 0,
chars = null,
points = null,
codePoint = null,
t = null,
tree = {
_points: [],
},
patterns
for (var i = 0; i < words.length; i += 1) { for (size in patternObject) {
if (words[i].indexOf('/') !== -1) { if (patternObject.hasOwnProperty(size)) {
// Don't insert a zero width space if the slash is at the beginning or end patterns = patternObject[size].match(new RegExp('.{1,' + +size + '}', 'g'))
// of the text, or right after or before a space.
if (i !== 0 && i !== words.length - 1 && !(/\s+\/|\/\s+/.test(words[i]))) {
words[i] += '\u200B';
}
} else if (words[i].length > minLength) {
words[i] = this.hyphenate(words[i]).join('\u00AD');
}
}
return words.join('');
};
/** for (i = 0; i < patterns.length; i += 1) {
* Hyphenates a word. chars = patterns[i].replace(/[0-9]/g, '').split('')
* points = patterns[i].split(/\D/)
* @param {!string} word The word to hyphenate t = tree
* @return {!Array.<!string>} An array of word fragments indicating valid hyphenation points.
*/
Hypher.prototype.hyphenate = function (word) {
var characters,
characterPoints = [],
originalCharacters,
i,
j,
k,
node,
points = [],
wordLength,
lowerCaseWord = word.toLowerCase(),
nodePoints,
nodePointsLength,
m = Math.max,
trie = this.trie,
result = [''];
if (this.exceptions.hasOwnProperty(lowerCaseWord)) { for (c = 0; c < chars.length; c += 1) {
return word.match(this.exceptions[lowerCaseWord]).slice(1); codePoint = chars[c].charCodeAt(0)
}
if (word.indexOf('\u00AD') !== -1) { if (!t[codePoint]) {
return [word]; t[codePoint] = {}
} }
t = t[codePoint]
}
word = '_' + word + '_'; t._points = []
characters = word.toLowerCase().split(''); for (p = 0; p < points.length; p += 1) {
originalCharacters = word.split(''); t._points[p] = points[p] || 0
wordLength = characters.length;
for (i = 0; i < wordLength; i += 1) {
points[i] = 0;
characterPoints[i] = characters[i].charCodeAt(0);
}
for (i = 0; i < wordLength; i += 1) {
node = trie;
for (j = i; j < wordLength; j += 1) {
node = node[characterPoints[j]];
if (node) {
nodePoints = node._points;
if (nodePoints) {
for (k = 0, nodePointsLength = nodePoints.length; k < nodePointsLength; k += 1) {
points[i + k] = m(points[i + k], nodePoints[k]);
} }
} }
}
}
return tree
}
/**
* Hyphenates a text.
*
* @param {!string} str The text to hyphenate.
* @return {!string} The same text with soft hyphens inserted in the right positions.
*/
Hypher.prototype.hyphenateText = function (str, minLength) {
minLength = minLength || 4
// Regexp("\b", "g") splits on word boundaries,
// compound separators and ZWNJ so we don't need
// any special cases for those characters. Unfortunately
// it does not support unicode word boundaries, so
// we implement it manually.
var words = str.split(
/([a-zA-Z0-9_\u0027\u00DF-\u00EA\u00EC-\u00EF\u00F1-\u00F6\u00F8-\u00FD\u0101\u0103\u0105\u0107\u0109\u010D\u010F\u0111\u0113\u0117\u0119\u011B\u011D\u011F\u0123\u0125\u012B\u012F\u0131\u0135\u0137\u013C\u013E\u0142\u0144\u0146\u0148\u0151\u0153\u0155\u0159\u015B\u015D\u015F\u0161\u0165\u016B\u016D\u016F\u0171\u0173\u017A\u017C\u017E\u017F\u0219\u021B\u02BC\u0390\u03AC-\u03CE\u03F2\u0401\u0410-\u044F\u0451\u0454\u0456\u0457\u045E\u0491\u0531-\u0556\u0561-\u0587\u0902\u0903\u0905-\u090B\u090E-\u0910\u0912\u0914-\u0928\u092A-\u0939\u093E-\u0943\u0946-\u0948\u094A-\u094D\u0982\u0983\u0985-\u098B\u098F\u0990\u0994-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BE-\u09C3\u09C7\u09C8\u09CB-\u09CD\u09D7\u0A02\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A14-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A82\u0A83\u0A85-\u0A8B\u0A8F\u0A90\u0A94-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABE-\u0AC3\u0AC7\u0AC8\u0ACB-\u0ACD\u0B02\u0B03\u0B05-\u0B0B\u0B0F\u0B10\u0B14-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3E-\u0B43\u0B47\u0B48\u0B4B-\u0B4D\u0B57\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB5\u0BB7-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C02\u0C03\u0C05-\u0C0B\u0C0E-\u0C10\u0C12\u0C14-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3E-\u0C43\u0C46-\u0C48\u0C4A-\u0C4D\u0C82\u0C83\u0C85-\u0C8B\u0C8E-\u0C90\u0C92\u0C94-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBE-\u0CC3\u0CC6-\u0CC8\u0CCA-\u0CCD\u0D02\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3E-\u0D43\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D60\u0D61\u0D7A-\u0D7F\u1F00-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB2-\u1FB4\u1FB6\u1FB7\u1FBD\u1FBF\u1FC2-\u1FC4\u1FC6\u1FC7\u1FD2\u1FD3\u1FD6\u1FD7\u1FE2-\u1FE7\u1FF2-\u1FF4\u1FF6\u1FF7\u200D\u2019]+)/g
)
for (var i = 0; i < words.length; i += 1) {
if (words[i].indexOf('/') !== -1) {
// Don't insert a zero width space if the slash is at the beginning or end
// of the text, or right after or before a space.
if (i !== 0 && i !== words.length - 1 && !/\s+\/|\/\s+/.test(words[i])) {
words[i] += '\u200B'
}
} else if (words[i].length > minLength) {
words[i] = this.hyphenate(words[i]).join('\u00AD')
}
}
return words.join('')
}
/**
* Hyphenates a word.
*
* @param {!string} word The word to hyphenate
* @return {!Array.<!string>} An array of word fragments indicating valid hyphenation points.
*/
Hypher.prototype.hyphenate = function (word) {
var characters,
characterPoints = [],
originalCharacters,
i,
j,
k,
node,
points = [],
wordLength,
lowerCaseWord = word.toLowerCase(),
nodePoints,
nodePointsLength,
m = Math.max,
trie = this.trie,
result = ['']
if (this.exceptions.hasOwnProperty(lowerCaseWord)) {
return word.match(this.exceptions[lowerCaseWord]).slice(1)
}
if (word.indexOf('\u00AD') !== -1) {
return [word]
}
word = '_' + word + '_'
characters = word.toLowerCase().split('')
originalCharacters = word.split('')
wordLength = characters.length
for (i = 0; i < wordLength; i += 1) {
points[i] = 0
characterPoints[i] = characters[i].charCodeAt(0)
}
for (i = 0; i < wordLength; i += 1) {
node = trie
for (j = i; j < wordLength; j += 1) {
node = node[characterPoints[j]]
if (node) {
nodePoints = node._points
if (nodePoints) {
for (k = 0, nodePointsLength = nodePoints.length; k < nodePointsLength; k += 1) {
points[i + k] = m(points[i + k], nodePoints[k])
}
}
} else {
break
}
}
}
for (i = 1; i < wordLength - 1; i += 1) {
if (i > this.leftMin && i < wordLength - this.rightMin && points[i] % 2) {
result.push(originalCharacters[i])
} else { } else {
break; result[result.length - 1] += originalCharacters[i]
} }
} }
return result
} }
for (i = 1; i < wordLength - 1; i += 1) { module.exports = Hypher
if (i > this.leftMin && i < (wordLength - this.rightMin) && points[i] % 2) { window['Hypher'] = module.exports
result.push(originalCharacters[i]);
} else {
result[result.length - 1] += originalCharacters[i];
}
}
return result; window['Hypher']['languages'] = {}
}; })()
;(function ($) {
module.exports = Hypher;
window['Hypher'] = module.exports;
window['Hypher']['languages'] = {};
}());(function ($) {
$.fn.hyphenate = function (language) { $.fn.hyphenate = function (language) {
if (window['Hypher']['languages'][language]) { if (window['Hypher']['languages'][language]) {
return this.each(function () { return this.each(function () {
var i = 0, len = this.childNodes.length; var i = 0,
len = this.childNodes.length
for (; i < len; i += 1) { for (; i < len; i += 1) {
if (this.childNodes[i].nodeType === 3) { if (this.childNodes[i].nodeType === 3) {
this.childNodes[i].nodeValue = window['Hypher']['languages'][language].hyphenateText(this.childNodes[i].nodeValue); this.childNodes[i].nodeValue = window['Hypher']['languages'][language].hyphenateText(
this.childNodes[i].nodeValue
)
} }
} }
}); })
} }
}; }
}(jQuery)); })(jQuery);
+592 -4073
View File
File diff suppressed because it is too large Load Diff
+5433 -3042
View File
File diff suppressed because it is too large Load Diff
+27
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)
}
+2 -1
View File
@@ -3,6 +3,7 @@
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <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="./3rdparty/highlight/styles/default.css">
<link rel="stylesheet" href="../css/doctest.css"> <link rel="stylesheet" href="../css/doctest.css">
<script src="./3rdparty/highlight/highlight.pack.js"></script> <script src="./3rdparty/highlight/highlight.pack.js"></script>
@@ -12,7 +13,7 @@
<body onload="Doctest.run()"> <body onload="Doctest.run()">
<h1> <h1>
Application <a href="index.html">lib.</a>Application
</h1> </h1>
<p> <p>
IWM Browser Applications follow a common three phase pattern, shared by many programming environments as diverse as Processing, Arduino, Intern, etc. IWM Browser Applications follow a common three phase pattern, shared by many programming environments as diverse as Processing, Arduino, Intern, etc.
+5 -5
View File
@@ -122,17 +122,17 @@ class Bootstrap extends Object {
baseURL: baseUrl, baseURL: baseUrl,
map: { map: {
'plugin-babel': baseUrl + '/3rdparty/systemjs/plugin-babel.js', 'plugin-babel': baseUrl + '/3rdparty/systemjs/plugin-babel.js',
'systemjs-babel-build': baseUrl + '/3rdparty/systemjs/systemjs-babel-browser.js' 'systemjs-babel-build': baseUrl + '/3rdparty/systemjs/systemjs-babel-browser.js',
}, },
transpiler: 'plugin-babel', transpiler: 'plugin-babel',
meta: { meta: {
'*.js': { '*.js': {
authorization: true, authorization: true,
babelOptions: { babelOptions: {
es2015: false es2015: false,
} },
} },
} },
} }
} }
+3 -3
View File
@@ -22,7 +22,7 @@ import {
InteractionDelta, InteractionDelta,
InteractionMapper, InteractionMapper,
InteractionDelegate, InteractionDelegate,
IInteractionMapperTarget IInteractionMapperTarget,
} from './interaction.js' } from './interaction.js'
import { import {
ITapDelegate, ITapDelegate,
@@ -31,7 +31,7 @@ import {
AbstractScatter, AbstractScatter,
DOMScatter, DOMScatter,
ScatterEvent, ScatterEvent,
BaseEvent BaseEvent,
} from './scatter.js' } from './scatter.js'
import { import {
Cycle, Cycle,
@@ -50,7 +50,7 @@ import {
debounce, debounce,
randomInt, randomInt,
randomFloat, randomFloat,
LowPassFilter LowPassFilter,
} from './utils.js' } from './utils.js'
import UITest from './uitest.js' import UITest from './uitest.js'
+51 -70
View File
@@ -1,75 +1,56 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Doctests Capabilities</title> <title>Doctests Capabilities</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"> <link rel="stylesheet" href="../css/doctest.css" />
<script src="./3rdparty/highlight/highlight.pack.js"></script> <script src="./3rdparty/highlight/highlight.pack.js"></script>
<script type="text/javascript" src="../dist/iwmlib.js"></script> <script type="text/javascript" src="../dist/iwmlib.js"></script>
</head> </head>
<body onload="Doctest.run(); CapabilitiesTests.testAll()"> <body onload="Doctest.run(); CapabilitiesTests.testAll()">
<main> <main>
<h1> <h1><a href="index.html">lib.</a>Capabilities</h1>
Capabilities <p>
</h1> Browsers differ in many aspects, from touch support, support of CSS and HTML5 standards, to javascript
<p>Browsers differ in many aspects, from touch support, support of CSS and HTML5 standards, to versions. This page collects some of these differences.
javascript versions. This page collects some of these differences. </p>
<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) <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()) { if (Capabilities.supportsTouchEvents()) {
Doctest.expect(Capabilities.supportsTouchEvents(), true) Doctest.expect(Capabilities.supportsTouchEvents(), true)
} }
if (Capabilities.supportsPointerEvents()) { if (Capabilities.supportsPointerEvents()) {
Doctest.expect(Capabilities.supportsPointerEvents(), true) Doctest.expect(Capabilities.supportsPointerEvents(), true)
} }
</script> </script>
<h3> <h3>Interactive Alerts</h3>
Interactive Alerts <p>
</h3> Standard alerts are displayed quite differently, on Windows 10, for instance the browser URL is
<p> encluded, and a checkbox that allows to hide the alert dialogs.
Standard alerts are displayed quite differently, on Windows 10, for instance </p>
the browser URL is encluded, and a checkbox that allows to hide the <button onclick="alert('Ok'); console.log('Alert')">Alert</button>
alert dialogs. <button onclick="CapabilitiesTests.testConfirm()">Confirm</button>
</p> <button onclick="CapabilitiesTests.testPrompt()">Prompt</button>
<button onclick="alert('Ok'); console.log('Alert')">Alert</button> <p id="demo">Result</p>
<button onclick="CapabilitiesTests.testConfirm()">Confirm</button> <hr />
<button onclick="CapabilitiesTests.testPrompt()">Prompt</button>
<p id="demo">
Result
</p>
<hr />
<h2>References</h2>
<h2> <ul>
References <li><a href="http://caniuse.com">Can I use</a></li>
</h2> <li><a href="http://webglreport.com">WebGL Report</a></li>
<ul> </ul>
<li><a href="http://caniuse.com">Can I use</a></li> </main>
<li><a href="http://webglreport.com">WebGL Report</a></li> </body>
</ul> </html>
</main>
</body>
+96 -90
View File
@@ -55,7 +55,7 @@ export default class Card {
context.classList.add('info-card') context.classList.add('info-card')
context.setAttribute('data-id', Card.id++) context.setAttribute('data-id', Card.id++)
modules.forEach(module => { modules.forEach((module) => {
if (module.apply(context)) { if (module.apply(context)) {
const moduleName = module.constructor.name const moduleName = module.constructor.name
context.modules.push(moduleName) context.modules.push(moduleName)
@@ -157,7 +157,7 @@ export default class Card {
*/ */
static _replaceAttributes(context, html, attribute, replaceFunc) { static _replaceAttributes(context, html, attribute, replaceFunc) {
let attributeCarrier = html.querySelectorAll(`[${attribute}]`) let attributeCarrier = html.querySelectorAll(`[${attribute}]`)
attributeCarrier.forEach(element => { attributeCarrier.forEach((element) => {
let attributeVal = element.getAttribute(attribute) let attributeVal = element.getAttribute(attribute)
element.removeAttribute(attribute) element.removeAttribute(attribute)
replaceFunc.call(this, context, element, attributeVal) replaceFunc.call(this, context, element, attributeVal)
@@ -176,7 +176,7 @@ export default class Card {
*/ */
static _replaceCallback(context, element, attributeVal) { static _replaceCallback(context, element, attributeVal) {
if (element.tagName == 'A') { if (element.tagName == 'A') {
element.addEventListener('click', event => { element.addEventListener('click', (event) => {
event.preventDefault() event.preventDefault()
}) })
} }
@@ -189,7 +189,7 @@ export default class Card {
trimmedArgs = trimmedArgs.substring(0, trimmedArgs.length - 1) trimmedArgs = trimmedArgs.substring(0, trimmedArgs.length - 1)
let callParts = funcPart.split('.') let callParts = funcPart.split('.')
let argsStrings = trimmedArgs.split(',').filter(entry => { let argsStrings = trimmedArgs.split(',').filter((entry) => {
return entry.trim() != '' return entry.trim() != ''
}) })
/** /**
@@ -218,12 +218,12 @@ export default class Card {
// These are 'hardcoded' inside the convert.js. // These are 'hardcoded' inside the convert.js.
if (element.tagName == 'circle') return false if (element.tagName == 'circle') return false
this.registerEvent(context, interactionType, element, event => { this.registerEvent(context, interactionType, element, (event) => {
/** /**
* Replaces the strings from the listener with the corresponding variables. * Replaces the strings from the listener with the corresponding variables.
*/ */
let args = [] let args = []
argsStrings.forEach(arg => { argsStrings.forEach((arg) => {
arg = arg.trim() arg = arg.trim()
if (arg == 'this') args.push(event.target) if (arg == 'this') args.push(event.target)
else if (arg == 'event') args.push(event) else if (arg == 'event') args.push(event)
@@ -269,7 +269,7 @@ export default class Card {
*/ */
return html.replace( return html.replace(
/<\s*(a|video|img|image|circle)\s(.*?)(xlink:href|href|src)\s*=\s*["'](\..*?)["']\s*(.*?)>/g, /<\s*(a|video|img|image|circle)\s(.*?)(xlink:href|href|src)\s*=\s*["'](\..*?)["']\s*(.*?)>/g,
function() { function () {
let path = that._getRelativePath(arguments[4]) let path = that._getRelativePath(arguments[4])
const tag = `<${arguments[1]} ${arguments[2]} ${arguments[3]}="${path}" ${arguments[5]}>` const tag = `<${arguments[1]} ${arguments[2]} ${arguments[3]}="${path}" ${arguments[5]}>`
/* if (that.debug) */ console.log('Adjusted: ', tag) /* if (that.debug) */ console.log('Adjusted: ', tag)
@@ -298,7 +298,7 @@ export default class Card {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let request = new XMLHttpRequest() let request = new XMLHttpRequest()
request.onreadystatechange = function() { request.onreadystatechange = function () {
if (this.readyState == 4) { if (this.readyState == 4) {
if (this.status == 200 || Card._isLocal()) { if (this.status == 200 || Card._isLocal()) {
try { try {
@@ -405,6 +405,12 @@ export default class Card {
static _openPopup(context, src, position, content, options = {}) { static _openPopup(context, src, position, content, options = {}) {
if (this.debug) console.log('Card._openPopup', position) if (this.debug) console.log('Card._openPopup', position)
console.log('context', context)
console.log('src', src)
console.log('position', position)
console.log('content', content)
console.log('options', options)
//logging //logging
if (src) { if (src) {
let strparts = src.split('/') let strparts = src.split('/')
@@ -423,7 +429,7 @@ export default class Card {
} }
this._createPopup(context, position, content, options) this._createPopup(context, position, content, options)
.then(popup => { .then((popup) => {
if ( if (
//Test if meanwhile another popup was registered... //Test if meanwhile another popup was registered...
this._getPopup(context) || this._getPopup(context) ||
@@ -441,9 +447,9 @@ export default class Card {
// let unnecessaryPopupElement = popup.element.querySelector(".popupContent > .popup") // let unnecessaryPopupElement = popup.element.querySelector(".popupContent > .popup")
// unnecessaryPopupElement.classList.remove("popup") // unnecessaryPopupElement.classList.remove("popup")
popupParagraphs.forEach(popupParagraph => { popupParagraphs.forEach((popupParagraph) => {
popupParagraph.setAttribute('draggable', false) popupParagraph.setAttribute('draggable', false)
popupParagraph.addEventListener('mousedown', event => { popupParagraph.addEventListener('mousedown', (event) => {
event.preventDefault() event.preventDefault()
}) })
}) })
@@ -451,7 +457,7 @@ export default class Card {
this._setPopup(context, popup, src) this._setPopup(context, popup, src)
} }
}) })
.catch(e => console.error(e)) .catch((e) => console.error(e))
} }
/** /**
@@ -491,14 +497,14 @@ export default class Card {
Object.assign( Object.assign(
{ {
parent: context, parent: context,
content content,
}, },
Object.assign( Object.assign(
{ {
noStyle: true, noStyle: true,
// TODO: Remove offset when positioning according to element position // TODO: Remove offset when positioning according to element position
// is working. // is working.
posOffset: 10 posOffset: 10,
}, },
options options
) )
@@ -510,7 +516,7 @@ export default class Card {
// We manually prevent this here. // We manually prevent this here.
popup.element.style.display = 'none' popup.element.style.display = 'none'
let promise = new Promise(resolve => { let promise = new Promise((resolve) => {
if (popup.loaded) resolve(popup) if (popup.loaded) resolve(popup)
else { else {
popup.onload = () => { popup.onload = () => {
@@ -519,7 +525,7 @@ export default class Card {
} }
}) })
promise.then(popup => { promise.then((popup) => {
popup.element.style.display = 'block' popup.element.style.display = 'block'
popup.element.style.visibility = 'hidden' popup.element.style.visibility = 'hidden'
popup.element.style.opacity = 0 popup.element.style.opacity = 0
@@ -531,12 +537,12 @@ export default class Card {
}) })
Object.assign(popup.element.style, { Object.assign(popup.element.style, {
zIndex: this.zIndices.popup zIndex: this.zIndices.popup,
}) })
TweenLite.to(popup.element, this.animation.popup, { TweenLite.to(popup.element, this.animation.popup, {
autoAlpha: 1, autoAlpha: 1,
ease: Power2.easeIn ease: Power2.easeIn,
}) })
}) })
@@ -560,7 +566,7 @@ export default class Card {
popup.remove() popup.remove()
//this._cleanup(context) //this._cleanup(context)
//overlay.parentNode.removeChild(overlay) //overlay.parentNode.removeChild(overlay)
} },
}) })
} }
} }
@@ -589,7 +595,9 @@ export default class Card {
// Prevents loading the link in the current tab. // Prevents loading the link in the current tab.
// Prevents loading the link in the current tab. // Prevents loading the link in the current tab.
if (event.type != 'Follow') event.preventDefault() if (event.type != 'Follow') {
event.preventDefault()
}
if (editable && event.type == 'click') { if (editable && event.type == 'click') {
return false return false
@@ -618,7 +626,7 @@ export default class Card {
let popupPage = parser.parseFromString(xhr.responseText, 'text/html') let popupPage = parser.parseFromString(xhr.responseText, 'text/html')
//Fix the relative path of loaded images in the popup. //Fix the relative path of loaded images in the popup.
popupPage.querySelectorAll('img').forEach(node => { popupPage.querySelectorAll('img').forEach((node) => {
node.setAttribute('src', that._getRelativePath(node.getAttribute('src'))) node.setAttribute('src', that._getRelativePath(node.getAttribute('src')))
}) })
let html = popupPage.body.innerHTML let html = popupPage.body.innerHTML
@@ -637,6 +645,8 @@ export default class Card {
let selector = Card.popupHtmlSelector let selector = Card.popupHtmlSelector
let content = { html, selector } let content = { html, selector }
console.log('loadPopup', selector, html)
let isSame = Card._checkForActiveSource(context, src) let isSame = Card._checkForActiveSource(context, src)
Card._cleanup(context) Card._cleanup(context)
@@ -648,7 +658,7 @@ export default class Card {
if (editable) { if (editable) {
let isDirty = mainController.askSaveNode() let isDirty = mainController.askSaveNode()
if (isDirty) if (isDirty)
mainController.saveNode(html.innerHTML, url => { mainController.saveNode(html.innerHTML, (url) => {
if (callback) { if (callback) {
callback() callback()
} }
@@ -670,12 +680,12 @@ export default class Card {
} }
Card._openPopup(context, src, local, content, { Card._openPopup(context, src, local, content, {
closeCommand: callback closeCommand: callback,
}) })
} }
} }
overlay.onclick = e => { overlay.onclick = (e) => {
if (editable) e.preventDefault() if (editable) e.preventDefault()
} }
//console.log("onreadystatechange", editable) //console.log("onreadystatechange", editable)
@@ -712,7 +722,7 @@ export default class Card {
for (let highlight of correspondingHighlights) { for (let highlight of correspondingHighlights) {
if (highlight.parentNode && highlight.parentNode.nodeName.toLowerCase() == 'g') { if (highlight.parentNode && highlight.parentNode.nodeName.toLowerCase() == 'g') {
Highlight.openHighlight(highlight, { Highlight.openHighlight(highlight, {
animation: Card.highlightAnimation animation: Card.highlightAnimation,
}) })
this._addHighlight(context, highlight) this._addHighlight(context, highlight)
} }
@@ -756,7 +766,6 @@ export default class Card {
} }
static _calculateCenterRelativeTo(target, image) { static _calculateCenterRelativeTo(target, image) {
// console.log('_calculateCenterRelativeTo', target, image)
let bbox = image.getBBox() let bbox = image.getBBox()
let width = bbox.width let width = bbox.width
let height = bbox.height let height = bbox.height
@@ -841,19 +850,19 @@ export default class Card {
// TODO: Adjust to load while animating (Problem: Unload when cancelled). // TODO: Adjust to load while animating (Problem: Unload when cancelled).
// console.log('loadHighlightPopup', src, position, local) // console.log('loadHighlightPopup', src, position, local)
this._loadPopupContent(context, src) this._loadPopupContent(context, src)
.then(content => { .then((content) => {
this._openPopup(context, src, local, content, { this._openPopup(context, src, local, content, {
highlight: node, highlight: node,
closeCommand: popup => { closeCommand: (popup) => {
this._overlayCleanup(context, overlay) this._overlayCleanup(context, overlay)
popup.remove() popup.remove()
} },
}) })
}) })
.catch(err => { .catch((err) => {
console.error(err) console.error(err)
}) })
} },
}) })
} }
} }
@@ -937,7 +946,7 @@ export default class Card {
let globalBottomRight = { let globalBottomRight = {
x: imageWidth, x: imageWidth,
y: imageHeight y: imageHeight,
} }
globalBottomRight = Points.fromNodeToPage(zoomable, globalBottomRight) globalBottomRight = Points.fromNodeToPage(zoomable, globalBottomRight)
@@ -1003,7 +1012,7 @@ export default class Card {
position: 'absolute', position: 'absolute',
top: 0, top: 0,
left: 0, left: 0,
zIndex: 200 zIndex: 200,
}) })
wrapper.appendChild(zoomContainer) wrapper.appendChild(zoomContainer)
@@ -1071,13 +1080,13 @@ export default class Card {
y: current.y, y: current.y,
width: current.width + borderX, width: current.width + borderX,
height: current.height + borderY, height: current.height + borderY,
transformOrigin transformOrigin,
}) })
TweenLite.set(zoomable, { opacity: 0 }) TweenLite.set(zoomable, { opacity: 0 })
let icon = zoomedFig.querySelector('.icon') let icon = zoomedFig.querySelector('.icon')
TweenLite.set(icon, { TweenLite.set(icon, {
transformOrigin transformOrigin,
}) })
zoomedFig.style.transformOrigin = zoomedFig.style.transformOrigin =
'calc(100% - ' + 'calc(100% - ' +
@@ -1097,8 +1106,8 @@ export default class Card {
ease: Power2.easeIn, ease: Power2.easeIn,
css: { css: {
scaleX: scaleFactor, scaleX: scaleFactor,
scaleY: scaleFactor scaleY: scaleFactor,
} },
}, },
0 0
) )
@@ -1107,11 +1116,11 @@ export default class Card {
display: 'block', display: 'block',
opacity: 0, opacity: 0,
x: -parseFloat(zoomedFigStyle.borderLeftWidth), x: -parseFloat(zoomedFigStyle.borderLeftWidth),
width: current.width + borderX width: current.width + borderX,
} },
}) })
.to(zoomCaption, this.animation.fade, { .to(zoomCaption, this.animation.fade, {
autoAlpha: 1 autoAlpha: 1,
}) })
} else this._openZoomableEditorBehaviour(wrapper, img, zoomable, zoomedFig, current) } else this._openZoomableEditorBehaviour(wrapper, img, zoomable, zoomedFig, current)
} }
@@ -1160,7 +1169,7 @@ export default class Card {
x: current.x, x: current.x,
y: current.y, y: current.y,
width: current.width, width: current.width,
height: current.height height: current.height,
}) })
let editor = mainController.topController().ensureEditor(img) let editor = mainController.topController().ensureEditor(img)
let savedDisplay = zoomIcon.style.display let savedDisplay = zoomIcon.style.display
@@ -1187,13 +1196,13 @@ export default class Card {
wrapper.appendChild(iconClone) wrapper.appendChild(iconClone)
TweenLite.set(iconClone, { x: current.iconPos.x, y: current.iconPos.y }) TweenLite.set(iconClone, { x: current.iconPos.x, y: current.iconPos.y })
zoomable.onmousedown = event => { zoomable.onmousedown = (event) => {
if (this.debug) console.log('mousedown', event.target) if (this.debug) console.log('mousedown', event.target)
event.preventDefault() event.preventDefault()
zoomable.dragging = true zoomable.dragging = true
zoomable.dragStartPos = { x: event.pageX, y: event.pageY } zoomable.dragStartPos = { x: event.pageX, y: event.pageY }
} }
zoomable.onmousemove = event => { zoomable.onmousemove = (event) => {
if (this.debug) console.log('onmousemove', event.target) if (this.debug) console.log('onmousemove', event.target)
if (zoomable.dragging) { if (zoomable.dragging) {
event.preventDefault() event.preventDefault()
@@ -1201,7 +1210,7 @@ export default class Card {
let dy = event.pageY - zoomable.dragStartPos.y let dy = event.pageY - zoomable.dragStartPos.y
TweenLite.set([zoomable, iconClone], { TweenLite.set([zoomable, iconClone], {
x: '+=' + dx, x: '+=' + dx,
y: '+=' + dy y: '+=' + dy,
}) })
zoomable.dragStartPos = { x: event.pageX, y: event.pageY } zoomable.dragStartPos = { x: event.pageX, y: event.pageY }
if (editor) { if (editor) {
@@ -1214,7 +1223,7 @@ export default class Card {
} }
let startZoom = 1 let startZoom = 1
zoomable.onmousewheel = event => { zoomable.onmousewheel = (event) => {
event.preventDefault() event.preventDefault()
let direction = event.detail < 0 || event.wheelDelta > 0 let direction = event.detail < 0 || event.wheelDelta > 0
const zoomFactor = 1.1 const zoomFactor = 1.1
@@ -1250,24 +1259,24 @@ export default class Card {
let zoomedCaption = zoomedFig.querySelector('figcaption.zoomcap') let zoomedCaption = zoomedFig.querySelector('figcaption.zoomcap')
TweenLite.to(zoomedCaption, this.animation.fade, { TweenLite.to(zoomedCaption, this.animation.fade, {
autoAlpha: 0 autoAlpha: 0,
}) })
TweenLite.to(zoomedFig, this.animation.zoomable, { TweenLite.to(zoomedFig, this.animation.zoomable, {
css: { css: {
scaleX: 1, scaleX: 1,
scaleY: 1 scaleY: 1,
}, },
onComplete: () => { onComplete: () => {
TweenLite.set(zoomable, { TweenLite.set(zoomable, {
opacity: 1 opacity: 1,
}) })
let div = zoomedFig.parentNode let div = zoomedFig.parentNode
let videoElement = div.querySelector('video') let videoElement = div.querySelector('video')
if (videoElement) videoElement.pause() if (videoElement) videoElement.pause()
div.parentNode.removeChild(div) div.parentNode.removeChild(div)
} },
}) })
InteractionMapper.off(zoomedFig) InteractionMapper.off(zoomedFig)
@@ -1341,12 +1350,12 @@ export default class Card {
height: globalIndexCardRect.height, height: globalIndexCardRect.height,
maxWidth: '100%', maxWidth: '100%',
margin: 0, margin: 0,
zIndex: this.zIndices.article zIndex: this.zIndices.article,
} },
}) })
TweenLite.set(articleClone, { TweenLite.set(articleClone, {
autoAlpha: 0 autoAlpha: 0,
}) })
TweenLite.set(card, { css: { maxWidth: '100%' } }) TweenLite.set(card, { css: { maxWidth: '100%' } })
@@ -1356,7 +1365,7 @@ export default class Card {
scaleX, scaleX,
scaleY, scaleY,
transformOrigin: '0% 0%', transformOrigin: '0% 0%',
rotation: angle rotation: angle,
}) })
indexbox.prepend(clone) indexbox.prepend(clone)
clone.setAttribute('data-source', src) clone.setAttribute('data-source', src)
@@ -1365,7 +1374,7 @@ export default class Card {
let title = titlebar.querySelector('h2') let title = titlebar.querySelector('h2')
let titlebarStyle = window.getComputedStyle(titlebar) let titlebarStyle = window.getComputedStyle(titlebar)
let start = { let start = {
height: parseInt(titlebarStyle.height) height: parseInt(titlebarStyle.height),
} }
if (this.dynamicHeight) { if (this.dynamicHeight) {
@@ -1385,10 +1394,7 @@ export default class Card {
//jquery hyphenate below //jquery hyphenate below
if (this._isJQueryPresent()) { if (this._isJQueryPresent()) {
$('.column') $('.column').not('.overview').children('p').hyphenate('de')
.not('.overview')
.children('p')
.hyphenate('de')
} }
//logging //logging
@@ -1414,23 +1420,23 @@ export default class Card {
card.classList.add('visited') card.classList.add('visited')
}, },
onUpdateParams: ['{self}'], onUpdateParams: ['{self}'],
onUpdate: self => { onUpdate: (self) => {
let transform = self.target._gsTransform let transform = self.target._gsTransform
TweenLite.set(title, { TweenLite.set(title, {
scale: 1 / transform.scaleX scale: 1 / transform.scaleX,
}) })
TweenLite.set(titlebar, { TweenLite.set(titlebar, {
height: (start.height * 1) / transform.scaleY height: (start.height * 1) / transform.scaleY,
}) })
// Retain the border at same visual thickness. // Retain the border at same visual thickness.
titlebar.style.borderBottomWidth = desiredBorderBottomWidth / transform.scaleY + 'px' titlebar.style.borderBottomWidth = desiredBorderBottomWidth / transform.scaleY + 'px'
} },
}) })
TweenLite.to([articleClone], this.animation.articleTransition / 2, { TweenLite.to([articleClone], this.animation.articleTransition / 2, {
delay: this.animation.articleTransition / 2, delay: this.animation.articleTransition / 2,
autoAlpha: 1 autoAlpha: 1,
}) })
if (editable) { if (editable) {
@@ -1479,11 +1485,11 @@ export default class Card {
if (editable) { if (editable) {
let isDirty = mainController.askSaveNode() let isDirty = mainController.askSaveNode()
if (isDirty) { if (isDirty) {
mainController.saveNode(html.innerHTML, url => { mainController.saveNode(html.innerHTML, (url) => {
callback(url) callback(url)
this._closeIndexCard(context, card, clone, articleClone, { this._closeIndexCard(context, card, clone, articleClone, {
eventElements, eventElements,
src src,
}) })
}) })
} else { } else {
@@ -1522,7 +1528,7 @@ export default class Card {
let scale = { let scale = {
x: globalPreviewRect.width / globalIndexCardRect.width, x: globalPreviewRect.width / globalIndexCardRect.width,
y: globalPreviewRect.height / globalIndexCardRect.height y: globalPreviewRect.height / globalIndexCardRect.height,
} }
let titlebar = clonedSubcard.querySelector('.titlebar') let titlebar = clonedSubcard.querySelector('.titlebar')
@@ -1551,16 +1557,16 @@ export default class Card {
let titlebarStyle = window.getComputedStyle(previewTitlebar) let titlebarStyle = window.getComputedStyle(previewTitlebar)
TweenLite.to(titlebar, this.animation.articleTransition, { TweenLite.to(titlebar, this.animation.articleTransition, {
height: parseInt(titlebarStyle.height) height: parseInt(titlebarStyle.height),
}) })
TweenLite.to(clonedArticle, this.animation.articleTransition / 2, { TweenLite.to(clonedArticle, this.animation.articleTransition / 2, {
autoAlpha: 0 autoAlpha: 0,
}) })
let title = titlebar.querySelector('h2') let title = titlebar.querySelector('h2')
let original = { let original = {
height: parseInt(titlebarStyle.height) height: parseInt(titlebarStyle.height),
} }
if (this.dynamicHeight) { if (this.dynamicHeight) {
@@ -1589,24 +1595,24 @@ export default class Card {
mainController.popController() mainController.popController()
} }
clonedSubcard.remove() clonedSubcard.remove()
} },
}) })
}, },
onUpdateParams: ['{self}'], onUpdateParams: ['{self}'],
onUpdate: function(self) { onUpdate: function (self) {
let transform = self.target._gsTransform let transform = self.target._gsTransform
TweenLite.set(title, { TweenLite.set(title, {
scale: 1 / transform.scaleX scale: 1 / transform.scaleX,
}) })
TweenLite.set(titlebar, { TweenLite.set(titlebar, {
height: (original.height * 1) / transform.scaleY height: (original.height * 1) / transform.scaleY,
}) })
// Retain the border at same visual thickness. // Retain the border at same visual thickness.
titlebar.style.borderBottomWidth = desiredBorderBottomWidth / transform.scaleY + 'px' titlebar.style.borderBottomWidth = desiredBorderBottomWidth / transform.scaleY + 'px'
} },
}) })
} }
@@ -1653,7 +1659,7 @@ export default class Card {
* Called by the expandIndexCard(...) * Called by the expandIndexCard(...)
*/ */
let target = event.target let target = event.target
const saveCallback = url => { const saveCallback = (url) => {
let handler = `Card.openIndexCard(event, '${url}')` let handler = `Card.openIndexCard(event, '${url}')`
if (this.debug) console.log('File has changed', target, handler) if (this.debug) console.log('File has changed', target, handler)
@@ -1744,9 +1750,9 @@ export default class Card {
} }
let highlights = this._getHighlights(context) let highlights = this._getHighlights(context)
highlights.forEach(highlight => { highlights.forEach((highlight) => {
Highlight.closeHighlight(highlight, { Highlight.closeHighlight(highlight, {
animation: Card.highlightAnimation animation: Card.highlightAnimation,
}) })
}) })
@@ -1768,13 +1774,13 @@ export default class Card {
let global = this._getGlobalRect(element) let global = this._getGlobalRect(element)
let localPosition = Points.fromPageToNode(context, { let localPosition = Points.fromPageToNode(context, {
x: global.x, x: global.x,
y: global.y y: global.y,
}) })
return DOMRectReadOnly.fromRect({ return DOMRectReadOnly.fromRect({
x: localPosition.x, x: localPosition.x,
y: localPosition.y, y: localPosition.y,
width: global.width, width: global.width,
height: global.height height: global.height,
}) })
} }
@@ -1797,7 +1803,7 @@ export default class Card {
x: globalPosition.x, x: globalPosition.x,
y: globalPosition.y, y: globalPosition.y,
width: cardWidth, width: cardWidth,
height: cardHeight height: cardHeight,
}) })
} }
@@ -1819,19 +1825,19 @@ export default class Card {
this._replaceAttributes(context, html, 'onclick', this._replaceCallback) this._replaceAttributes(context, html, 'onclick', this._replaceCallback)
} }
let zoomableWrappers = html.querySelectorAll('.svg-wrapper') let zoomableWrappers = html.querySelectorAll('.svg-wrapper')
zoomableWrappers.forEach(wrapper => { zoomableWrappers.forEach((wrapper) => {
let svg = wrapper.querySelector('svg') let svg = wrapper.querySelector('svg')
Object.assign(wrapper.style, { Object.assign(wrapper.style, {
width: svg.getAttribute('width') + 'px', width: svg.getAttribute('width') + 'px',
height: svg.getAttribute('height') + 'px' height: svg.getAttribute('height') + 'px',
}) })
}) })
let zoomableVideoWrappers = html.querySelectorAll('.video-wrapper') let zoomableVideoWrappers = html.querySelectorAll('.video-wrapper')
zoomableVideoWrappers.forEach(wrapper => { zoomableVideoWrappers.forEach((wrapper) => {
let video = wrapper.querySelector('video') let video = wrapper.querySelector('video')
Object.assign(wrapper.style, { Object.assign(wrapper.style, {
width: video.getAttribute('width') + 'px', width: video.getAttribute('width') + 'px',
height: video.getAttribute('height') + 'px' height: video.getAttribute('height') + 'px',
}) })
}) })
@@ -1870,12 +1876,12 @@ export default class Card {
let globalClick = event.center ? event.center : { x: event.x, y: event.y } let globalClick = event.center ? event.center : { x: event.x, y: event.y }
let localClick = Points.fromPageToNode(indexbox, globalClick) let localClick = Points.fromPageToNode(indexbox, globalClick)
let linkRects = links.map(link => { let linkRects = links.map((link) => {
let rect = link.getBoundingClientRect() let rect = link.getBoundingClientRect()
let topLeft = Points.fromPageToNode(indexbox, rect) let topLeft = Points.fromPageToNode(indexbox, rect)
let center = Points.fromPageToNode(indexbox, { let center = Points.fromPageToNode(indexbox, {
x: rect.x + rect.width / 2, x: rect.x + rect.width / 2,
y: rect.y + rect.height / 2 y: rect.y + rect.height / 2,
}) })
return { return {
x: topLeft.x, x: topLeft.x,
@@ -1883,12 +1889,12 @@ export default class Card {
width: rect.width, width: rect.width,
height: rect.height, height: rect.height,
center, center,
link link,
} }
}) })
let distances = [] let distances = []
linkRects.forEach(rect => { linkRects.forEach((rect) => {
let distance = Card.pointRectDist(localClick, rect) let distance = Card.pointRectDist(localClick, rect)
if (distance == 0.0) { if (distance == 0.0) {
// Distance == 0.0 indicates an inside relation. Since these // Distance == 0.0 indicates an inside relation. Since these
@@ -2095,7 +2101,7 @@ Card.zIndices = {
article: 10, article: 10,
popup: 100, popup: 100,
zoomable: 101, zoomable: 101,
zoomedFigure: 102 zoomedFigure: 102,
} }
Card.animation = { Card.animation = {
@@ -2103,5 +2109,5 @@ Card.animation = {
fade: 0.2, fade: 0.2,
popup: 0.1, popup: 0.1,
highlight: 0.4, highlight: 0.4,
zoomable: 0.5 zoomable: 0.5,
} }
+40 -39
View File
@@ -27,10 +27,13 @@ function round(value) {
* @class Highlight * @class Highlight
* @extends {Object} * @extends {Object}
*/ */
const SCALE = 1.5
export default class Highlight extends Object { export default class Highlight extends Object {
static disableAnimations() { static disableAnimations() {
_HighlightEnabled = false _HighlightEnabled = false
let expanded = document.querySelectorAll('.' + Highlight.expandedClass) let expanded = document.querySelectorAll('.' + this.expandedClass)
for (let obj of expanded) { for (let obj of expanded) {
this.shrink(obj) this.shrink(obj)
} }
@@ -41,11 +44,11 @@ export default class Highlight extends Object {
} }
static removeAnimations(svgRoot) { static removeAnimations(svgRoot) {
let expanded = svgRoot.querySelectorAll('.' + Highlight.expandedClass) let expanded = svgRoot.querySelectorAll('.' + this.expandedClass)
for (let obj of expanded) { for (let obj of expanded) {
TweenLite.set(obj, { scale: 1 }) TweenLite.set(obj, { scale: 1 })
obj.classList.remove('zooming') obj.classList.remove('zooming')
obj.classList.remove(Highlight.expandedClass) obj.classList.remove(this.expandedClass)
} }
let defs = svgRoot.querySelector('defs') let defs = svgRoot.querySelector('defs')
while (defs.firstChild) { while (defs.firstChild) {
@@ -75,12 +78,12 @@ export default class Highlight extends Object {
} }
} }
static expand(obj, { scale = 2, duration = 3, stroke = 2, onComplete = null } = {}) { static expand(obj, { scale = SCALE, duration = 3, stroke = 2, onComplete = null } = {}) {
if (obj == null) return if (obj == null) return
//console.log("expand") //console.log("expand")
obj.classList.add('zooming') obj.classList.add('zooming')
TweenLite.to(obj, duration, { TweenLite.to(obj, duration, {
scale: scale, scale,
onUpdate: () => { onUpdate: () => {
let scale = obj._gsTransform.scaleX let scale = obj._gsTransform.scaleX
obj.setAttribute('stroke-width', stroke / scale) obj.setAttribute('stroke-width', stroke / scale)
@@ -88,10 +91,10 @@ export default class Highlight extends Object {
onComplete: () => { onComplete: () => {
console.log('expand complete') console.log('expand complete')
obj.classList.remove('zooming') obj.classList.remove('zooming')
obj.classList.add(Highlight.expandedClass) obj.classList.add(this.expandedClass)
obj.setAttribute('stroke-width', stroke / scale) obj.setAttribute('stroke-width', stroke / scale)
if (onComplete) onComplete() if (onComplete) onComplete()
} },
}) })
} }
@@ -108,9 +111,9 @@ export default class Highlight extends Object {
onComplete: () => { onComplete: () => {
//console.log("shrink complete") //console.log("shrink complete")
obj.classList.remove('zooming') obj.classList.remove('zooming')
obj.classList.remove(Highlight.expandedClass) obj.classList.remove(this.expandedClass)
obj.setAttribute('stroke-width', stroke) obj.setAttribute('stroke-width', stroke)
} },
}) })
} }
@@ -127,9 +130,9 @@ export default class Highlight extends Object {
let id = circle.getAttribute('id') let id = circle.getAttribute('id')
TweenLite.set(circle, { transformOrigin: '50% 50%' }) TweenLite.set(circle, { transformOrigin: '50% 50%' })
/*if (circle.classList.contains('zooming')) { /*if (circle.classList.contains('zooming')) {
console.log("already zooming") console.log("already zooming")
return return
}*/ }*/
let svgRoot = circle.closest('svg') let svgRoot = circle.closest('svg')
let circleGroup = circle.parentNode let circleGroup = circle.parentNode
@@ -147,7 +150,7 @@ export default class Highlight extends Object {
let maskImageId = 'maskImage' + id let maskImageId = 'maskImage' + id
let maskImage = svgRoot.getElementById(maskImageId) let maskImage = svgRoot.getElementById(maskImageId)
if (circle.classList.contains(Highlight.expandedClass)) { if (circle.classList.contains(this.expandedClass)) {
if (!circle.classList.contains('zooming')) { if (!circle.classList.contains('zooming')) {
this.shrink(circle, { stroke }) this.shrink(circle, { stroke })
this.shrink(maskImage, { stroke }) this.shrink(maskImage, { stroke })
@@ -160,7 +163,7 @@ export default class Highlight extends Object {
this.shrink(maskImage, { stroke }) this.shrink(maskImage, { stroke })
return return
} }
let circles = Array.from(circleGroup.children).filter(e => e.tagName == 'circle') let circles = Array.from(circleGroup.children).filter((e) => e.tagName == 'circle')
for (let c of circles) { for (let c of circles) {
//console.log("shrinking all circles") //console.log("shrinking all circles")
this.shrink(c, { stroke }) this.shrink(c, { stroke })
@@ -170,7 +173,7 @@ export default class Highlight extends Object {
this.shrink(m, { stroke }) this.shrink(m, { stroke })
} }
Highlight._createSVGMask(svgRoot, image, id) this._createSVGMask(svgRoot, image, id)
// TweenLite.set(maskImage, { transformOrigin: `${tx}% ${ty}%` }) // TweenLite.set(maskImage, { transformOrigin: `${tx}% ${ty}%` })
@@ -180,8 +183,8 @@ export default class Highlight extends Object {
return false return false
} }
static openHighlight(target, { animation = 0.5, scale = 2, onExpanded = null } = {}) { static openHighlight(target, { animation = 0.5, scale = SCALE, onExpanded = null } = {}) {
if (Highlight._isExpanded(target)) { if (this._isExpanded(target)) {
console.log('Target is already expanded!') console.log('Target is already expanded!')
return return
} else { } else {
@@ -205,32 +208,31 @@ export default class Highlight extends Object {
let image = svgRoot.querySelector('image') let image = svgRoot.querySelector('image')
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
let [mask, maskImage] = Highlight._getSVGMask(target, { let [mask, maskImage] = this._getSVGMask(target, {
svgRoot, svgRoot,
image image,
}) })
let center = Highlight._calculateCenterRelativeTo(target, image) let center = this._calculateCenterRelativeTo(target, image)
console.log('_calculateCenterRelativeTo', center)
TweenLite.set(maskImage, { TweenLite.set(maskImage, {
transformOrigin: `${center.x} ${center.y}` transformOrigin: `${center.x} ${center.y}`,
}) })
TweenLite.set(target, { transformOrigin: '50% 50%' }) TweenLite.set(target, { transformOrigin: '50% 50%' })
TweenLite.to([target, maskImage], animation, { TweenLite.to([target, maskImage], animation, {
scale, scale,
onComplete: onExpanded onComplete: onExpanded,
}) })
target.classList.add(Highlight.expandedClass) target.classList.add(this.expandedClass)
} }
} }
static toggleHighlight(node) { static toggleHighlight(node, options = {}) {
console.log('toggleHighlight', Highlight._isExpanded(node)) console.log('toggleHighlight', this._isExpanded(node))
if (Highlight._isExpanded(node)) { if (this._isExpanded(node)) {
Highlight.closeHighlight(node) this.closeHighlight(node, options)
} else { } else {
Highlight.openHighlight(node) this.openHighlight(node, options)
} }
} }
@@ -253,10 +255,10 @@ export default class Highlight extends Object {
let maskImage = svgRoot.getElementById(maskImageId) let maskImage = svgRoot.getElementById(maskImageId)
if (!mask || !maskImage) if (!mask || !maskImage)
[mask, maskImage] = Highlight._createSVGMask(circle, { [mask, maskImage] = this._createSVGMask(circle, {
svgRoot, svgRoot,
image, image,
id id,
}) })
return [mask, maskImage] return [mask, maskImage]
@@ -319,12 +321,11 @@ export default class Highlight extends Object {
maskImage.setAttribute('width', width) maskImage.setAttribute('width', width)
maskImage.setAttribute('height', height) maskImage.setAttribute('height', height)
maskImage.setAttribute('class', 'addedImage') maskImage.setAttribute('class', 'addedImage')
svgGroup.insertBefore(maskImage, element) // image.nextSibling)
TweenLite.set(maskImage, { scale: 1 }) TweenLite.set(maskImage, { scale: 1 })
maskImage.style.mask = 'url(#' + maskId + ')' maskImage.style.mask = 'url(#' + maskId + ')'
} }
svgGroup.appendChild(maskImage) element.insertAdjacentElement('beforebegin', maskImage) // image.nextSibling)
// svgGroup.appendChild(element) // svgGroup.appendChild(element)
return [mask, maskImage] return [mask, maskImage]
@@ -342,16 +343,16 @@ export default class Highlight extends Object {
} }
static _isExpanded(target) { static _isExpanded(target) {
return target.classList.contains(Highlight.expandedClass) return target.classList.contains(this.expandedClass)
} }
static closeHighlight(target, { animation = 0.5 } = {}) { static closeHighlight(target, { animation = 0.5 } = {}) {
target.classList.remove(Highlight.expandedClass) target.classList.remove(this.expandedClass)
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
let [mask, maskImage] = Highlight._getSVGMask(target) let [mask, maskImage] = this._getSVGMask(target)
console.log('Close Highlight', maskImage) // console.log('Close Highlight', maskImage)
TweenLite.to([target, maskImage], animation, { TweenLite.to([target, maskImage], animation, {
scale: 1 scale: 1,
}) })
} }
@@ -359,7 +360,7 @@ export default class Highlight extends Object {
if (!_HighlightEnabled) return if (!_HighlightEnabled) return
event.stopPropagation() event.stopPropagation()
Highlight.animateCircle(event.target) this.animateCircle(event.target)
return false return false
} }
+2 -2
View File
@@ -9,13 +9,13 @@
<link rel="stylesheet" href="../../css/highlight.css"> <link rel="stylesheet" href="../../css/highlight.css">
<link rel="stylesheet" href="../../css/popup.css"> <link rel="stylesheet" href="../../css/popup.css">
<script src="../3rdparty/highlight/highlight.pack.js"></script> <script src="../3rdparty/highlight/highlight.pack.js"></script>
<script src="../../dist/iwmlib.js"></script>
<script src="../../dist/iwmlib.3rdparty.js"></script> <script src="../../dist/iwmlib.3rdparty.js"></script>
<script src="../../dist/iwmlib.js"></script>
</head> </head>
<body onload="Doctest.run();"> <body onload="Doctest.run();">
<h1> <h1>
Cards <a href="../index.html">lib.</a><a href="index.html">card.</a>Cards
</h1> </h1>
<p> <p>
Cards implement a central UI metaphor for multiuser applications. They allow users to explore information spaces Cards implement a central UI metaphor for multiuser applications. They allow users to explore information spaces
+19 -19
View File
@@ -63,7 +63,7 @@ export class CardPluginBase {
let requirements = this._collectAllRequirements() let requirements = this._collectAllRequirements()
let missing = [] let missing = []
requirements.forEach(module => { requirements.forEach((module) => {
if (context.modules.indexOf(module.name) == -1) { if (context.modules.indexOf(module.name) == -1) {
missing.push(module.name) missing.push(module.name)
} }
@@ -121,12 +121,12 @@ CardPlugin.LightBox = class LightBox extends CardPluginBase {
top: 0, top: 0,
left: 0, left: 0,
width: '100%', width: '100%',
height: '100%' height: '100%',
}, },
this.style, this.style,
{ {
display: 'none', display: 'none',
position: 'absolute' position: 'absolute',
} }
) )
@@ -199,7 +199,7 @@ CardPlugin.EnlargeableThumbnail = class EnlargeableThumbnail extends CardPluginB
wrapper.appendChild(icon) wrapper.appendChild(icon)
Object.assign(wrapper.style, { Object.assign(wrapper.style, {
cursor: 'pointer' cursor: 'pointer',
}) })
InteractionMapper.on(this.interactionType, wrapper, () => { InteractionMapper.on(this.interactionType, wrapper, () => {
@@ -221,13 +221,13 @@ CardPlugin.EnlargeableThumbnail = class EnlargeableThumbnail extends CardPluginB
Object.assign(imageWrapper.style, { Object.assign(imageWrapper.style, {
maxWidth: 'none', maxWidth: 'none',
maxHeight: 'none' maxHeight: 'none',
}) })
Object.assign(image.style, { Object.assign(image.style, {
width: '100%', width: '100%',
height: '100%', height: '100%',
objectFit: 'cover' objectFit: 'cover',
}) })
this._replaceIcon(imageWrapper) this._replaceIcon(imageWrapper)
@@ -250,7 +250,7 @@ CardPlugin.EnlargeableThumbnail = class EnlargeableThumbnail extends CardPluginB
const max = { const max = {
width: context.offsetWidth * maxFillRatio, width: context.offsetWidth * maxFillRatio,
height: context.offsetHeight * maxFillRatio height: context.offsetHeight * maxFillRatio,
} }
let majorSide let majorSide
@@ -284,14 +284,14 @@ CardPlugin.EnlargeableThumbnail = class EnlargeableThumbnail extends CardPluginB
let targetDimensions = { let targetDimensions = {
width: 0, width: 0,
height: 0 height: 0,
} }
let position = Points.fromPageToNode(context, Points.fromNodeToPage(source, { x: 0, y: 0 })) let position = Points.fromPageToNode(context, Points.fromNodeToPage(source, { x: 0, y: 0 }))
let targetOffset = { let targetOffset = {
x: 0, x: 0,
y: 0 y: 0,
} }
targetDimensions[majorSide.name] = size targetDimensions[majorSide.name] = size
@@ -311,22 +311,22 @@ CardPlugin.EnlargeableThumbnail = class EnlargeableThumbnail extends CardPluginB
y: position.y, y: position.y,
position: 'absolute', position: 'absolute',
width: parseInt(sourceStyle.width), width: parseInt(sourceStyle.width),
height: parseInt(sourceStyle.height) height: parseInt(sourceStyle.height),
}) })
TweenMax.set(overlay, { TweenMax.set(overlay, {
display: 'flex', display: 'flex',
autoAlpha: 0 autoAlpha: 0,
}) })
TweenMax.to(imageWrapper, this.zoomAnimationDuration, { TweenMax.to(imageWrapper, this.zoomAnimationDuration, {
x: targetOffset.x, x: targetOffset.x,
y: targetOffset.y, y: targetOffset.y,
width: targetDimensions.width, width: targetDimensions.width,
height: targetDimensions.height height: targetDimensions.height,
}) })
TweenMax.to(overlay, this.fadeAnimationTime, { TweenMax.to(overlay, this.fadeAnimationTime, {
autoAlpha: 1 autoAlpha: 1,
}) })
} }
@@ -358,10 +358,10 @@ CardPlugin.EnlargeableThumbnail = class EnlargeableThumbnail extends CardPluginB
timeline timeline
.to(overlay, this.fadeAnimationDuration, { .to(overlay, this.fadeAnimationDuration, {
autoAlpha: 0 autoAlpha: 0,
}) })
.set(overlay, { .set(overlay, {
display: 'none' display: 'none',
}) })
} }
} }
@@ -418,7 +418,7 @@ CardPlugin.Speech = class SpeechPlugin extends CardPluginBase {
this._end = this._end.bind(this) this._end = this._end.bind(this)
this._setupUtterance() this._setupUtterance()
this.utterance.addEventListener('end', event => { this.utterance.addEventListener('end', (event) => {
this._end() this._end()
}) })
} }
@@ -451,7 +451,7 @@ CardPlugin.Speech = class SpeechPlugin extends CardPluginBase {
} else { } else {
if (subcard) { if (subcard) {
let clone = subcard.cloneNode(true) let clone = subcard.cloneNode(true)
clone.querySelectorAll('figure').forEach(figure => { clone.querySelectorAll('figure').forEach((figure) => {
figure.parentNode.removeChild(figure) figure.parentNode.removeChild(figure)
}) })
@@ -554,10 +554,10 @@ CardPlugin.Speech = class SpeechPlugin extends CardPluginBase {
} }
async _stop() { async _stop() {
return new Promise(resolve => { return new Promise((resolve) => {
if (this.activeUtterance) { if (this.activeUtterance) {
this.activeUtterance.addEventListener('end', resolve, { this.activeUtterance.addEventListener('end', resolve, {
once: true once: true,
}) })
} }
+6 -6
View File
@@ -66,12 +66,12 @@ export default class ScatterCard extends Card {
scatterContainer.element.appendChild(element) scatterContainer.element.appendChild(element)
new DOMScatter(element, scatterContainer, { new DOMScatter(element, scatterContainer, {
width: 1400, width: 1400,
height: 1200 height: 1200,
}) })
this.setup(element, html, { this.setup(element, html, {
basePath, basePath,
modules modules,
}) })
return element return element
} }
@@ -132,15 +132,15 @@ export default class ScatterCard extends Card {
let url = basePath + '/' + item + '/index.html' let url = basePath + '/' + item + '/index.html'
console.log('Loading', url) console.log('Loading', url)
this.loadHTML(url) this.loadHTML(url)
.then(html => { .then((html) => {
console.log('Received', html) console.log('Received', html)
let element = this.createCardScatter(html, scatterContainer, { let element = this.createCardScatter(html, scatterContainer, {
basePath, basePath,
modules modules,
}) })
resolve(element) resolve(element)
}) })
.catch(e => reject(e)) .catch((e) => reject(e))
}) })
} }
@@ -157,6 +157,6 @@ ScatterCard.selectedLanguage = 0
ScatterCard.languages = ['Deutsch', 'English'] ScatterCard.languages = ['Deutsch', 'English']
ScatterCard.languageTags = { ScatterCard.languageTags = {
Deutsch: 'de', Deutsch: 'de',
English: 'en' English: 'en',
} }
ScatterCard.scatterContainer = null ScatterCard.scatterContainer = null
+1 -1
View File
@@ -20,7 +20,7 @@ export default class Theme {
path = path ? path : './config.json' path = path ? path : './config.json'
let xhttp = new XMLHttpRequest() let xhttp = new XMLHttpRequest()
xhttp.onreadystatechange = function() { xhttp.onreadystatechange = function () {
if (this.readyState == 4) { if (this.readyState == 4) {
if (this.status == 200 || Theme._isLocal()) { if (this.status == 200 || Theme._isLocal()) {
try { try {
+22 -39
View File
@@ -17,7 +17,7 @@ export default class CardWrapper extends Object {
handleClicks() { handleClicks() {
this.domNode.addEventListener( this.domNode.addEventListener(
'click', 'click',
event => { (event) => {
if (event.isTrusted) { if (event.isTrusted) {
Events.stop(event) Events.stop(event)
if (this.triggerSVGClicks && this.isSVGNode(event.target)) { if (this.triggerSVGClicks && this.isSVGNode(event.target)) {
@@ -32,7 +32,7 @@ export default class CardWrapper extends Object {
handleClicksAsTaps() { handleClicksAsTaps() {
this.domNode.addEventListener( this.domNode.addEventListener(
'click', 'click',
event => { (event) => {
if (event.isTrusted) { if (event.isTrusted) {
Events.stop(event) Events.stop(event)
} }
@@ -94,34 +94,14 @@ export default class CardWrapper extends Object {
} }
nearestActive(event) { nearestActive(event) {
let element = this.domNode
let activeNodes = this.activeNodes() let activeNodes = this.activeNodes()
let globalClick = event.center ? event.center : { x: event.x, y: event.y } let globalClick = event.center ? event.center : { x: event.x, y: event.y }
let localClick = Points.fromPageToNode(element, globalClick)
let clickRects = activeNodes.map(link => {
let rect = link.getBoundingClientRect()
let topLeft = Points.fromPageToNode(element, rect)
let center = Points.fromPageToNode(element, {
x: rect.x + rect.width / 2,
y: rect.y + rect.height / 2
})
return {
x: topLeft.x,
y: topLeft.y,
width: rect.width,
height: rect.height,
center,
link
}
})
let distances = [] let distances = []
clickRects.forEach(rect => { activeNodes.forEach((link) => {
let distance = Points.distanceToRect(localClick, rect) let rect = link.getBoundingClientRect()
let distance = Points.distanceToRect(globalClick, rect)
distances.push(parseInt(distance)) distances.push(parseInt(distance))
}) })
let closestClickIndex = distances.indexOf(Math.min(...distances)) let closestClickIndex = distances.indexOf(Math.min(...distances))
let closestClickable = activeNodes[closestClickIndex] let closestClickable = activeNodes[closestClickIndex]
if (distances[closestClickIndex] < this.allowClickDistance) { if (distances[closestClickIndex] < this.allowClickDistance) {
@@ -138,22 +118,22 @@ export default class CardWrapper extends Object {
/* https://stackoverflow.com/questions/49564905/is-it-possible-to-use-click-function-on-svg-tags-i-tried-element-click-on-a /* https://stackoverflow.com/questions/49564905/is-it-possible-to-use-click-function-on-svg-tags-i-tried-element-click-on-a
proposes the dispatchEvent solution. But this leads to problems in flippable.html hiding the back page. proposes the dispatchEvent solution. But this leads to problems in flippable.html hiding the back page.
Therefore we use the original click event (see constructor). */ Therefore we use the original click event (see constructor). */
if (this.isSVGNode(node)) { // if (this.isSVGNode(node)) {
if (this.triggerSVGClicks) { // if (this.triggerSVGClicks) {
let click = new Event('click') // let click = new Event('click')
node.dispatchEvent(click) // node.dispatchEvent(click)
} // }
return // return
} // }
node.click() // node.click()
let click = new Event('click')
click.clientX = event.clientX
click.clientY = event.clientY
node.dispatchEvent(click)
} }
nodeTapped(node, event) { nodeTapped(node, event) {
console.log('nodeTapped', node, this.isClickable(node))
if (this.isClickable(node)) {
this.simulateClick(node, event)
return true
}
if (this.tapNodes.has(node)) { if (this.tapNodes.has(node)) {
let handler = this.tapNodes.get(node) let handler = this.tapNodes.get(node)
handler(event, node) handler(event, node)
@@ -168,13 +148,16 @@ export default class CardWrapper extends Object {
} }
} }
} }
if (this.isClickable(node)) {
this.simulateClick(node, event)
return true
}
return false return false
} }
tap(event, calledBy = 'unknown') { tap(event, calledBy = 'unknown') {
if (event.isTrusted) { if (event.isTrusted) {
let node = this.nearestActive(event) let node = this.nearestActive(event)
console.log('tap', node)
this.nodeTapped(node, event) this.nodeTapped(node, event)
/* let node = document.elementFromPoint(event.clientX, event.clientY) /* let node = document.elementFromPoint(event.clientX, event.clientY)
+103 -98
View File
@@ -1,110 +1,115 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <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> let stage = scatterContainer.polygon
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> stage.draw(context, { stroke: '#FF0000' })
<title>Coordinates Doctest</title> for (let scatter of scatterContainer.scatter.values()) {
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css"> let polygon = scatter.polygon
<link rel="stylesheet" href="../css/doctest.css"> polygon.draw(context, { stroke: '#FF0000' })
<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' })
} }
}
function animate(callback) { function animate(callback) {
requestAnimationFrame((dt) => { requestAnimationFrame((dt) => {
drawPolygons() drawPolygons()
callback() callback()
animate(callback) animate(callback)
}) })
} }
</script> </script>
</head> </head>
<body onload="Doctest.run()"> <body onload="Doctest.run()">
<h1> <h1><a href="index.html">lib.</a>Coordinates</h1>
Coordinates <p>
</h1> To position objects in defined spatial relationships presupposes a clear understanding of the involved
<p> coordinate systems. Unfortunately, several systems with several conventions are involved:
To position objects in defined spatial relationships presupposes a clear understanding of the involved coordinate systems. <a href="https://javascript.info/coordinates">DOM &amp; CSS</a>,
Unfortunately, several systems with several conventions are involved: <a href="https://www.sarasoueidan.com/blog/svg-coordinate-systems/SVG">SVG</a>,
<a href="https://javascript.info/coordinates">DOM &amp; CSS</a>, <a href="https://www.w3schools.com/graphics/canvas_coordinates.asp">Canvas</a>
<a href="https://www.sarasoueidan.com/blog/svg-coordinate-systems/SVG">SVG</a>, </p>
<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
<p> browser page coordinate system is the most natural one. A simple API was long missing but has now been
We need a common reference system to switch between these coordinate systems. As the uttermost context, the browser page established in most modern browsers with
coordinate system is the most natural one. A simple API was long missing but has now been established in most modern <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/convertPointFromNoteToPage"
browsers with >window.convertPointFromNoteToPage</a
<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>. and the inverse
Although MDN Web Docs warns about their Non-standard nature the methods work in browsers targeted <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/convertPointFromPageToNode"
by the IWM Browser project. This doctest assures that this assumption can be tested. >window.convertPointFromPageToNode</a
</p> >. Although MDN Web Docs warns about their Non-standard nature the methods work in browsers targeted by the
<p>Let's look at a scatter object with a rotatable local coordinate system. We try to follow a point in this local coordinate IWM Browser project. This doctest assures that this assumption can be tested.
system by showing a marker outside the scatter that follows the point. </p>
</p> <p>
<div id="main" class="grayBorder interactive" style="position: relative; width: 100%; height: 280px;"> Let's look at a scatter object with a rotatable local coordinate system. We try to follow a point in this
<!-- Note that we need to set draggable to false to avoid conflicts. The DOM elements 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. --> 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
Canvas not supported. id="canvas"
</canvas> height="280"
</div> style="z-index: 100000; pointer-events: none; position: absolute; border: 1px solid red"
>
Canvas not supported.
</canvas>
</div>
<script class="doctest"> <script class="doctest">
let dx = 44 let dx = 44
let app = new App() let app = new App()
let scatterContainer = new DOMScatterContainer(main) let scatterContainer = new DOMScatterContainer(main)
let angle = 15 let angle = 15
let image = document.getElementById('women') let image = document.getElementById('women')
// The DOMScatter needs initial width and height. Therefore we // The DOMScatter needs initial width and height. Therefore we
// define the scatter when the image size is known, i.e. after loading... // define the scatter when the image size is known, i.e. after loading...
image.onload = (e) => { image.onload = (e) => {
let scatter = new DOMScatter(image, scatterContainer, { let scatter = new DOMScatter(image, scatterContainer, {
x: dx, x: dx,
y: 44, y: 44,
width: e.target.naturalWidth, width: e.target.naturalWidth,
height: e.target.naturalHeight, height: e.target.naturalHeight,
rotationDegrees: angle, rotationDegrees: angle,
throwVisibility: 88, throwVisibility: 88,
minScale: 0.5, minScale: 0.5,
maxScale: 1.5 maxScale: 1.5
}) })
dx += 300 dx += 300
angle = -angle angle = -angle
} }
app.run() app.run()
function followPoint() { function followPoint() {
let context = canvas.getContext('2d') let context = canvas.getContext('2d')
let localPoint = { x: 100, y: 100 } let localPoint = { x: 100, y: 100 }
let globalPoint = convertPointFromNodeToPage(image, localPoint.x, localPoint.y) let globalPoint = convertPointFromNodeToPage(image, localPoint.x, localPoint.y)
let canvasPoint = convertPointFromPageToNode(canvas, globalPoint.x, globalPoint.y) let canvasPoint = convertPointFromPageToNode(canvas, globalPoint.x, globalPoint.y)
context.strokeStyle = 'red' context.strokeStyle = 'red'
context.beginPath() context.beginPath()
context.arc(canvasPoint.x, canvasPoint.y, 12, 0, Math.PI * 2) context.arc(canvasPoint.x, canvasPoint.y, 12, 0, Math.PI * 2)
context.stroke() context.stroke()
}
} animate(followPoint)
animate(followPoint) </script>
</script> </body>
</html>
</body>
+1 -1
View File
@@ -12,7 +12,7 @@
<body onload="Doctest.run()"> <body onload="Doctest.run()">
<main> <main>
<h1> <h1>
Doctests <a href="index.html">lib.</a>Doctests
</h1> </h1>
<p> <p>
Doctests are explanatory descriptions of programs with executable code examples. Doctests are explanatory descriptions of programs with executable code examples.
+38 -32
View File
@@ -4,10 +4,10 @@
var docTestLogMessages = [] var docTestLogMessages = []
Array.prototype.equals = function(array) { Array.prototype.equals = function (array) {
return ( return (
this.length == array.length && this.length == array.length &&
this.every(function(this_i, i) { this.every(function (this_i, i) {
return this_i == array[i] return this_i == array[i]
}) })
) )
@@ -145,43 +145,49 @@ export default class Doctest {
let container = document.createElement('div') let container = document.createElement('div')
container.className = 'doctest-wrapper' container.className = 'doctest-wrapper'
let titleParent = container if (doctest.hasAttribute('data-title') || doctest.hasAttribute('data-collapsible')) {
if (doctest.hasAttribute('data-collapsible')) { let titlebar = document.createElement('div')
let collapsibleToggle = document.createElement('div') titlebar.className = 'doctest-titlebar'
titlebar.style = 'min-height: 10px;'
container.appendChild(titlebar)
let icon = document.createElement('i') if (doctest.hasAttribute('data-title')) {
icon.className = 'material-icons' let title = document.createElement('h6')
collapsibleToggle.appendChild(icon) title.innerText = doctest.getAttribute('data-title')
title.className = 'doctest-section-title'
titlebar.appendChild(title)
}
collapsibleToggle.className = 'doctest-collapsible-toggle' if (doctest.hasAttribute('data-collapsible')) {
collapsibleToggle.style = 'min-height: 10px;' let icon = document.createElement('i')
titleParent = collapsibleToggle icon.className = 'material-icons'
container.appendChild(collapsibleToggle) titlebar.classList.add('doctest-collapsible-toggle')
const collapsedClass = 'collapsed' if (titlebar.childNodes.length > 0) {
titlebar.insertBefore(icon, titlebar.childNodes[0])
function setToggleMode(collapse) {
if (collapse) {
container.classList.add(collapsedClass)
icon.innerText = 'arrow_drop_down'
} else { } else {
container.classList.remove(collapsedClass) titlebar.appendChild(icon)
icon.innerText = 'arrow_drop_up'
} }
}
function determineToggleMode() { const collapsedClass = 'collapsed'
setToggleMode(!container.classList.contains(collapsedClass))
}
setToggleMode(doctest.hasAttribute('data-collapsed')) function setToggleMode(collapse) {
collapsibleToggle.addEventListener('click', determineToggleMode) if (collapse) {
} container.classList.add(collapsedClass)
if (doctest.hasAttribute('data-title')) { icon.innerText = 'arrow_drop_down'
let title = document.createElement('h6') } else {
title.innerText = doctest.getAttribute('data-title') container.classList.remove(collapsedClass)
title.className = 'doctest-section-title' icon.innerText = 'arrow_drop_up'
titleParent.appendChild(title) }
}
function determineToggleMode() {
setToggleMode(!container.classList.contains(collapsedClass))
}
setToggleMode(doctest.hasAttribute('data-collapsed'))
titlebar.addEventListener('click', determineToggleMode)
}
} }
let pre = document.createElement('pre') let pre = document.createElement('pre')
+47 -46
View File
@@ -1,47 +1,48 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Electron Node.js Test</title> <title>Electron Node.js Test</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"> <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="./3rdparty/all.js"></script> <script src="./3rdparty/all.js"></script>
<script src="../dist/iwmlib.js"></script> <script src="../dist/iwmlib.js"></script>
</head> </head>
<body onload="Doctest.run()"> <body onload="Doctest.run()">
<main> <main>
<h1> <h1>Electron Node.js Test</h1>
Electron Node.js Test <p>
</h1> This doctest is expected to work only within the IWMBrowser. IWMBrowser windows are opened with a
<p> preload.js which is evaluated before the HTML is loaded. According to
This doctest is expected to work only within the IWMBrowser. IWMBrowser windows <a href="https://electron.atom.io/docs/faq/"
are opened with a preload.js which is evaluated before the HTML is loaded. >"I can not use jQuery/RequireJS/Meteor/AngularJS in Electron"</a
According to >
<a href="https://electron.atom.io/docs/faq/">"I can not use jQuery/RequireJS/Meteor/AngularJS in Electron"</a> we we have to rename the symbols in the page before other libraries are included. In order to access
have to rename the symbols in the page before other libraries are included. node.js modules we can use `nodeRequire` instead.
In order to access node.js modules we can use `nodeRequire` instead. </p>
</p> <p>As a simple test we try to load a file from the filesystem:</p>
<p>As a simple test we try to load a file from the filesystem:</p> <script class="doctest">
<script class="doctest"> if (typeof nodeRequire != 'undefined') {
let fs = nodeRequire('fs')
if (typeof(nodeRequire) != 'undefined') { let content = fs.readFileSync('./index.html')
let fs = nodeRequire('fs') let lines = content.toString().split(/\n/)
let content = fs.readFileSync('./index.html') console.log('First line', lines[0])
let lines = content.toString().split(/\n/) Doctest.expect(lines[0], '<html>')
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
</script> forget to test for nodeRequire to avoid runtime errors in other browsers.
<p>As simple as this test is, it shows that within the IWMBrowser one import all node.js </p>
modules. Don't forget to test for nodeRequire to avoid runtime errors in other browsers. <h2>References</h2>
</p> <ul>
<h2> <li>
References <a href="https://electron.atom.io/docs/faq/"
</h2> >I can not use jQuery/RequireJS/Meteor/AngularJS in Electron</a
<ul> >
<li><a href="https://electron.atom.io/docs/faq/">I can not use jQuery/RequireJS/Meteor/AngularJS in Electron</a></li> </li>
</ul> </ul>
</main> </main>
</body> </body>
</html>
+13 -10
View File
@@ -33,7 +33,7 @@ export default class Errors {
errors = document.createElement('div') errors = document.createElement('div')
errors.setAttribute('id', 'runtime-errors') errors.setAttribute('id', 'runtime-errors')
this.setStyle(document.body, { this.setStyle(document.body, {
border: '2px solid red' border: '2px solid red',
}) })
this.setStyle(errors, { this.setStyle(errors, {
position: 'absolute', position: 'absolute',
@@ -41,7 +41,7 @@ export default class Errors {
padding: '8px', padding: '8px',
width: '100%', width: '100%',
background: 'red', background: 'red',
color: 'white' color: 'white',
}) })
document.body.appendChild(errors) document.body.appendChild(errors)
let counter = document.createElement('div') let counter = document.createElement('div')
@@ -55,7 +55,7 @@ export default class Errors {
fontSize: '18px', fontSize: '18px',
textAlign: 'center', textAlign: 'center',
lineHeight: '32px', lineHeight: '32px',
verticalAlign: 'middle' verticalAlign: 'middle',
}) })
counter.innerHTML = '1' counter.innerHTML = '1'
errors.appendChild(counter) errors.appendChild(counter)
@@ -66,7 +66,7 @@ export default class Errors {
top: '6px', top: '6px',
left: '48px', left: '48px',
height: '44px', height: '44px',
fontSize: '32px' fontSize: '32px',
}) })
header.innerHTML = 'Runtime Errors' header.innerHTML = 'Runtime Errors'
errors.appendChild(header) errors.appendChild(header)
@@ -97,7 +97,7 @@ export default class Errors {
let errors = document.getElementById('runtime-errors') let errors = document.getElementById('runtime-errors')
let infos = errors.querySelectorAll('.info') let infos = errors.querySelectorAll('.info')
if (infos.length > 0) { if (infos.length > 0) {
infos.forEach(info => errors.removeChild(info)) infos.forEach((info) => errors.removeChild(info))
} else { } else {
this.expandErrors() this.expandErrors()
} }
@@ -116,16 +116,19 @@ export default class Errors {
// Register more informative error handler // Register more informative error handler
window.addEventListener( window.addEventListener(
'error', 'error',
event => { (event) => {
// if (typeof(event.error) == 'undefined') { if (typeof event.error == 'undefined') {
// console.info("Catched undefined error", event) // This sometimes happens in Edge. Since we have no error
// } // position, we cannot do much beside an info log.
console.info('Catched undefined error', event)
return
}
this.appendError(event.error, event.filename) this.appendError(event.error, event.filename)
}, },
true true
) )
document.addEventListener('DOMContentLoaded', event => { document.addEventListener('DOMContentLoaded', (event) => {
this.showErrors() this.showErrors()
}) })
} }
+67 -61
View File
@@ -1,26 +1,25 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<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"> <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.js"></script>
</head> </head>
<body onload="Doctest.run()" > <body onload="Doctest.run()">
<h1> <h1><a href="index.html">lib.</a>Events</h1>
Events <p>
</h1> For functional tests it can be useful to simulate event or record and playback events. This module provides
<p> basic support for extracting data from events and serializing events into a JSON format that allows to save
For functional tests it can be useful to simulate event or record and playback events. and load sequences of events.
This module provides basic support for extracting data from events and serializing </p>
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
<p> that can be tested.
Let's look at an example of a HTML structure with click handlers. The click </p>
handler actions log messages that can be tested.</p> <pre><code class="html">
<pre><code class="html">
&lt;div&gt; &lt;div&gt;
&lt;img id="women" src="examples/women.jpeg" &lt;img id="women" src="examples/women.jpeg"
onclick="record(event); Doctest.log('Lady clicked')"/&gt; 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;/vide&gt;
&lt;/div&gt; &lt;/div&gt;
</code></pre> </code></pre>
<div id="example" class="interactive" <div id="example" class="interactive" style="position: relative; width: 100%; border: 1px solid lightgray">
style="position:relative; width: 100%; border: 1px solid lightgray"> <img
<img style="margin:8px" id="women" src="examples/women.jpeg" style="margin: 8px"
onclick="record(event); Doctest.log('Lady clicked')"/> id="women"
<video id="movie" style="margin:8px" width="250" data-zoomcap="Kugellaufuhr" src="examples/women.jpeg"
onclick="record(event); Doctest.log('Movie clicked')" onclick="record(event); Doctest.log('Lady clicked')"
onmousedown="record(event)" />
onmouseup="record(event)" <video
controls> id="movie"
<source src="examples/movie.mp4" type="video/mp4"> style="margin: 8px"
</video> width="250"
</div> data-zoomcap="Kugellaufuhr"
<button onclick="eventRecorder.stopRecording(); eventRecorder.startReplay()">Replay</button> onclick="record(event); Doctest.log('Movie clicked')"
<script class="doctest"> 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 womenSel = Events.selector(women)
let target = event.target let movieSel = Events.selector(movie)
target.style.boxShadow = "0px 5px 10px gray"
setTimeout(() => target.style.boxShadow = "", 1000)
eventRecorder.record(event)
}
let womenSel = Events.selector(women) Events.simulateEvent('click', MouseEvent, { targetSelector: womenSel })
let movieSel = Events.selector(movie) Events.simulateEvent('click', MouseEvent, { targetSelector: movieSel })
Events.simulateEvent('click', MouseEvent, { targetSelector: womenSel}) Doctest.expectLog('Lady clicked', 'Movie clicked')
Events.simulateEvent('click', MouseEvent, { targetSelector: movieSel}) </script>
<h2>References</h2>
Doctest.expectLog('Lady clicked', <ul>
'Movie clicked') <li><a href="https://gist.github.com/iahu/aafc2492d83d70e42c98">Safari Touch Emulator</a></li>
<li>
</script> <a href="https://www.reddit.com/r/javascript/comments/2laqaf/how_to_trigger_a_touch_event/"
<h2> >How to Trigger Touch Events</a
References >
</h2> </li>
<ul> </ul>
<li><a href="https://gist.github.com/iahu/aafc2492d83d70e42c98">Safari Touch Emulator</a></li> </body>
<li><a href="https://www.reddit.com/r/javascript/comments/2laqaf/how_to_trigger_a_touch_event/">How to Trigger Touch Events</a></li> </html>
</ul>
</body>
+9 -6
View File
@@ -1,7 +1,10 @@
export default class Events { export default class Events {
static stop(event) { static stop(event) {
event.preventDefault() event.preventDefault()
event.stopPropagation() // I removed this, as it destroys all the Hammer.js events.
// And also the click event.
// It seems to have no (apparent) negative impact. -SO
// event.stopPropagation()
} }
static extractPoint(event) { static extractPoint(event) {
@@ -62,7 +65,7 @@ export default class Events {
clientX: t.clientX, clientX: t.clientX,
clientY: t.clientY, clientY: t.clientY,
pageX: t.pageX, pageX: t.pageX,
pageY: t.pageY pageY: t.pageY,
}) })
} }
return touches return touches
@@ -102,8 +105,8 @@ export default class Events {
ctrlKey: event.ctrlKey, ctrlKey: event.ctrlKey,
altKey: event.altKey, altKey: event.altKey,
shiftKey: event.shiftKey, shiftKey: event.shiftKey,
metaKey: event.metaKey metaKey: event.metaKey,
} },
} }
if (event.type.startsWith('touch')) { if (event.type.startsWith('touch')) {
// On Safari-WebKit the TouchEvent has layerX, layerY coordinates // On Safari-WebKit the TouchEvent has layerX, layerY coordinates
@@ -216,7 +219,7 @@ export default class Events {
width: '480px', width: '480px',
height: '640px', height: '640px',
overflow: 'auto', overflow: 'auto',
backgroundColor: 'lightgray' backgroundColor: 'lightgray',
}) })
document.body.appendChild(element) document.body.appendChild(element)
this.popup = element this.popup = element
@@ -237,7 +240,7 @@ export default class Events {
} }
Elements.setStyle(this.popup, { Elements.setStyle(this.popup, {
left: event.clientX + 'px', left: event.clientX + 'px',
top: event.clientY + 'px' top: event.clientY + 'px',
}) })
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 568 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 755 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.
+77 -53
View File
@@ -1,35 +1,43 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Flippable Doctest</title> <title>Flippable Doctest</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"> <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.js"></script>
<link rel="stylesheet" href="../css/flipeffect.css"> <link rel="stylesheet" href="../css/flipeffect.css" />
<template id="flipTemplate"> <template id="flipTemplate">
<div class="flipWrapper"> <div class="flipWrapper">
<div class="flipCard"> <div class="flipCard">
<div class="flipFace front"></div> <div class="flipFace front"></div>
<div class="flipFace back" style="visibility:hidden;"></div> <div class="flipFace back" style="visibility: hidden"></div>
</div> </div>
<!-- Very tricky problem to scale svgs: see https://css-tricks.com/scale-svg/ --> <!-- Very tricky problem to scale svgs: see https://css-tricks.com/scale-svg/ -->
<svg class="flipButton backBtn" style="visibility:hidden;" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"> <!-- SVG viewPort interferes with DOMMatrix calculations: see
<g stroke-width="8" stroke="white"> https://stackoverflow.com/questions/70696387/how-to-get-transform-matrix-of-a-dom-element-->
<circle cx="50" cy="50" r="44" fill="gray" /> <div class="flipButton backBtn" style="visibility:hidden;">
<line x1="30" y1="30" x2="70" y2="70" /> <svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet" style="width:inherit; height:inherit;">
<line x1="30" y1="70" x2="70" y2="30" /> <g stroke-width="8" stroke="white">
</g> <circle cx="50" cy="50" r="44" fill="gray" />
</svg> <line x1="30" y1="30" x2="70" y2="70" />
<line x1="30" y1="70" x2="70" y2="30" />
</g>
</svg>
</div>
<svg class="flipButton infoBtn" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"> <div class="flipButton infoBtn">
<circle cx="50" cy="50" r="44" stroke="white" stroke-width="8" fill="gray" /> <svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet" style="width:inherit; height:inherit;">
<circle cx="50" cy="32" r="7" fill="white" /> <circle cx="50" cy="50" r="44" stroke="white" stroke-width="8" fill="gray" />
<line x1="50" y1="46" x2="50" y2="78" stroke="white" stroke-width="12" /> <circle cx="50" cy="32" r="7" fill="white" />
</svg> <line x1="50" y1="46" x2="50" y2="78" stroke="white" stroke-width="12" />
</svg>
</div>
</div> </div>
</template> </template>
@@ -46,6 +54,10 @@ via a HTML template that defines the placeholders for front and back views. The
style file "css/flipeffect.css" holds reasonable default styles for this kind of style file "css/flipeffect.css" holds reasonable default styles for this kind of
templates. templates.
</p> </p>
<p>
The SVG buttons have to be wrapped in an HTML DOM element which handles events. Otherwise,
the viewbox of the SVG will interfere with the coordinate transformation.
</p>
<pre><code> <pre><code>
&lt;template id="flipTemplate"&gt; &lt;template id="flipTemplate"&gt;
&lt;div class="flipWrapper"&gt; &lt;div class="flipWrapper"&gt;
@@ -53,33 +65,45 @@ templates.
&lt;div class="flipFace front"&gt;&lt;/div&gt; &lt;div class="flipFace front"&gt;&lt;/div&gt;
&lt;div class="flipFace back" style="visibility:hidden;"&gt;&lt;/div&gt; &lt;div class="flipFace back" style="visibility:hidden;"&gt;&lt;/div&gt;
&lt;/div> &lt;/div>
&lt;svg class="flipButton backBtn" .../&gt; &lt;div class="flipButton backBtn" .../&gt;
&lt;svg class="flipButton infoBtn" .../&gt; &lt;svg .../&gt;
&lt;/div>
&lt;div class="flipButton infoBtn" .../&gt;
&lt;svg .../&gt;
&lt;/div>
&lt;/div&gt; &lt;/div&gt;
&lt;/template&gt; &lt;/template&gt;
</code> </code>
</pre> </pre>
<h3> <h3>Example</h3>
Example <main id="main" style="border: 1px solid gray; position: relative; height: 256px"></main>
</h3> <script class="doctest">
<main id="main" style="border: 1px solid gray; position: relative; height: 256px;" > let scatterContainer = new DOMScatterContainer(main, { stopEvents: false })
if (Capabilities.supportsTemplate()) {
let flip1 = new DOMFlip(
scatterContainer,
flipTemplate,
new ImageLoader('./examples/king.jpeg'),
new ImageLoader('./examples/women.jpeg'),
{ /*tapDelegateFactory: CardWrapper,*/ preloadBack: true }
)
flip1.load().then((flip) => {
flip.centerAt({ x: 150, y: 120 })
})
</main> let flip2 = new DOMFlip(
<script class="doctest"> scatterContainer,
let scatterContainer = new DOMScatterContainer(main, {stopEvents: false}) flipTemplate,
if (Capabilities.supportsTemplate()) { new ImageLoader('./examples/king.jpeg'),
new ImageLoader('./examples/queen.jpeg'),
let flip = new DOMFlip(scatterContainer, { /*tapDelegateFactory: CardWrapper,*/ preloadBack: true }
flipTemplate, )
new ImageLoader('./examples/king.jpeg'), flip2.load().then((flip) => {
new ImageLoader('./examples/women.jpeg'), flip.centerAt({ x: 650, y: 120 })
{ tapDelegateFactory: CardWrapper, preloadBack: true}) })
flip.load().then((flip) => { } else {
flip.centerAt({ x: 150, y: 120}) alert('Templates not supported, use Edge, Chrome, Safari or Firefox.')
}) }
} </script>
else { </body>
alert("Templates not supported, use Edge, Chrome, Safari or Firefox.") </html>
}
</script>
</body>
+54 -25
View File
@@ -52,8 +52,8 @@ export class PDFLoader extends CardLoader {
load(domNode) { load(domNode) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
PDFJS.getDocument(this.src).then(pdf => { PDFJS.getDocument(this.src).then((pdf) => {
pdf.getPage(1).then(page => { pdf.getPage(1).then((page) => {
let scale = this.scale * app.renderer.resolution let scale = this.scale * app.renderer.resolution
let invScale = 1 / scale let invScale = 1 / scale
let viewport = page.getViewport(scale) let viewport = page.getViewport(scale)
@@ -87,7 +87,7 @@ export class ImageLoader extends CardLoader {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let isImage = domNode instanceof HTMLImageElement let isImage = domNode instanceof HTMLImageElement
let image = isImage ? domNode : document.createElement('img') let image = isImage ? domNode : document.createElement('img')
image.onload = e => { image.onload = (e) => {
if (!isImage) { if (!isImage) {
domNode.appendChild(image) domNode.appendChild(image)
this.addedNode = image this.addedNode = image
@@ -103,7 +103,7 @@ export class ImageLoader extends CardLoader {
image.height = image.naturalHeight image.height = image.naturalHeight
resolve(this) resolve(this)
} }
image.onerror = e => { image.onerror = (e) => {
reject(this) reject(this)
} }
image.src = this.src image.src = this.src
@@ -125,10 +125,10 @@ export class FrameLoader extends CardLoader {
domNode.appendChild(iframe) domNode.appendChild(iframe)
this.addedNode = iframe this.addedNode = iframe
} }
iframe.onload = e => { iframe.onload = (e) => {
resolve(this) resolve(this)
} }
iframe.onerror = e => { iframe.onerror = (e) => {
reject(this) reject(this)
} }
iframe.src = this.src iframe.src = this.src
@@ -141,15 +141,17 @@ export class HTMLLoader extends CardLoader {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest() let xhr = new XMLHttpRequest()
xhr.open('GET', this.src, false) xhr.open('GET', this.src, false)
xhr.onload = e => { xhr.onreadystatechange = (e) => {
domNode.innerHTML = this.prepare(xhr.response) if (xhr.readyState == 4) {
this.addedNode = domNode.firstElementChild domNode.innerHTML = this.prepare(xhr.response)
let { width, height } = this.size(this.addedNode) this.addedNode = domNode.firstElementChild
if (width) this.wantedWidth = width || this.wantedWidth let { width, height } = this.size(this.addedNode)
if (height) this.wantedHeight = height || this.wantedHeight if (width) this.wantedWidth = width || this.wantedWidth
resolve(this) if (height) this.wantedHeight = height || this.wantedHeight
resolve(this)
}
} }
xhr.onerror = e => { xhr.onerror = (e) => {
reject(this) reject(this)
} }
xhr.send() xhr.send()
@@ -249,8 +251,8 @@ export class DOMFlip {
// call we can access the new dom element by id // call we can access the new dom element by id
this.cardWrapper = dom.querySelector('#' + this.id) this.cardWrapper = dom.querySelector('#' + this.id)
let front = this.cardWrapper.querySelector('.front') let front = this.cardWrapper.querySelector('.front')
this.frontLoader.load(front).then(loader => { this.frontLoader.load(front).then((loader) => {
this.frontLoaded(loader).then(obj => { this.frontLoaded(loader).then((obj) => {
if (this.onLoaded) this.onLoaded() if (this.onLoaded) this.onLoaded()
resolve(this) resolve(this)
}) })
@@ -282,7 +284,7 @@ export class DOMFlip {
} }
if (this.closeOnMinScale) { if (this.closeOnMinScale) {
const removeOnMinScale = function() { const removeOnMinScale = function () {
if (scatter.scale <= scatter.minScale) { if (scatter.scale <= scatter.minScale) {
this.flippable.close() this.flippable.close()
@@ -304,7 +306,7 @@ export class DOMFlip {
let back = this.cardWrapper.querySelector('.back') let back = this.cardWrapper.querySelector('.back')
if (this.preloadBack) { if (this.preloadBack) {
this.backLoader.load(back).then(loader => { this.backLoader.load(back).then((loader) => {
this.setupFlippable(flippable, loader) this.setupFlippable(flippable, loader)
}) })
} }
@@ -338,7 +340,7 @@ export class DOMFlip {
} else { } else {
let back = this.cardWrapper.querySelector('.back') let back = this.cardWrapper.querySelector('.back')
let flippable = this.flippable let flippable = this.flippable
this.backLoader.load(back).then(loader => { this.backLoader.load(back).then((loader) => {
this.setupFlippable(flippable, loader) this.setupFlippable(flippable, loader)
flippable.start({ duration: this.flipDuration, targetCenter }) flippable.start({ duration: this.flipDuration, targetCenter })
}) })
@@ -410,18 +412,18 @@ export class DOMFlippable {
scatter.tapDelegate = tapDelegate scatter.tapDelegate = tapDelegate
} }
if (this.infoBtn) { if (this.infoBtn) {
scatter.tapDelegate.onTap(this.infoBtn, event => { scatter.tapDelegate.onTap(this.infoBtn, (event) => {
this.flip.start() this.flip.start()
}) })
this.enable(this.infoBtn) this.enable(this.infoBtn)
} }
if (this.backBtn) { if (this.backBtn) {
scatter.tapDelegate.onTap(this.backBtn, event => { scatter.tapDelegate.onTap(this.backBtn, (event) => {
this.start() this.start()
}) })
} }
if (this.closeBtn) { if (this.closeBtn) {
scatter.tapDelegate.onTap(this.closeBtn, event => { scatter.tapDelegate.onTap(this.closeBtn, (event) => {
this.close() this.close()
}) })
this.enable(this.closeBtn) this.enable(this.closeBtn)
@@ -587,7 +589,7 @@ export class DOMFlippable {
ease: Power1.easeOut, ease: Power1.easeOut,
transformOrigin: '50% 50%', transformOrigin: '50% 50%',
onUpdate, onUpdate,
onComplete: e => { onComplete: (e) => {
if (this.flipped) { if (this.flipped) {
//this.hide(this.front) //this.hide(this.front)
this.enable(this.backBtn) this.enable(this.backBtn)
@@ -619,7 +621,8 @@ export class DOMFlippable {
force3D: true force3D: true
}) })
// See https://greensock.com/forums/topic/7997-rotate-the-shortest-way/ // See https://greensock.com/forums/topic/7997-rotate-the-shortest-way/
TweenLite.to(this.element, this.flipDuration / 2, { const duration = this.flipDuration / 2
TweenLite.to(this.element, duration, {
scale: targetScale, scale: targetScale,
ease: Power1.easeOut, ease: Power1.easeOut,
rotationZ: targetZ + '_short', rotationZ: targetZ + '_short',
@@ -628,7 +631,8 @@ export class DOMFlippable {
height: h, height: h,
x: x, x: x,
y: y, y: y,
onComplete: e => { onComplete: (e) => {
console.log('onComplete', e)
if (this.flipped) { if (this.flipped) {
this.hide(this.front) this.hide(this.front)
// this.hide(this.infoBtn) // this.hide(this.infoBtn)
@@ -638,5 +642,30 @@ export class DOMFlippable {
} }
} }
}) })
//uo: check for special case that the front image must be adapted to the back size
let frontImage = this.front.querySelector('img')
if (frontImage) {
// Assumes that startWidth and startHeight are encoded in px
let sh = parseInt(this.startHeight)
let sw = parseInt(this.startWidth)
if (this.flipped) {
let scaleY = h / sh
let scaleX = w / sw
TweenLite.to(frontImage, duration, {
ease: Power1.easeOut,
transformOrigin: '0% 0%',
scaleY,
scaleX
})
} else {
TweenLite.to(frontImage, duration, {
ease: Power1.easeOut,
transformOrigin: '0% 0%',
scaleY: 1.0,
scaleX: 1.0
})
}
}
} }
} }
+84 -71
View File
@@ -1,50 +1,51 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" /> <meta name="viewport" content="width=device-width, user-scalable=no" />
<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"> <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.js"></script>
</head> </head>
<body onload="Doctest.run()" > <body onload="Doctest.run()">
<h1> <h1><a href="index.html">lib.</a>Frames</h1>
Frames <p>
</h1> Frames are a major way to modularize the design of complex applications. Since pages presented in frames are
<p> independent of each other they can fail without impact on other pages. In addition preparing content in
Frames are a major way to modularize the design of complex applications. Since individual HTML files largely simplfies the workflow of content production.
pages presented in frames are independent of each other they can fail without </p>
impact on other pages. In addition preparing content in individual HTML files <p>This approach, however, has limitations:</p>
largely simplfies the workflow of content production. <script>
</p> function appleError() {
<p>This approach, however, has limitations:</p> alert(
<script> "Refused to display 'http://www.apple.com/de/' in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'."
function appleError() { )
alert("Refused to display 'http://www.apple.com/de/' in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'.") }
} </script>
</script> <ul>
<ul><li>Some pages may prevent embedding them <li>
by 'X-Frame-Options', e.g. <a href="javascript:appleError()">www.apple.com</a> Some pages may prevent embedding them by 'X-Frame-Options', e.g.
</li> <a href="javascript:appleError()">www.apple.com</a>
<li>Sites with responsive design might not be able to detect the available space, </li>
e.g. <a href="https:///de.wikipedia.org">de.wikipedia.org</a> <li>
</li> Sites with responsive design might not be able to detect the available space, e.g.
<li>Touch events are not dispatched correctly to multiple frames on platforms with <a href="https:///de.wikipedia.org">de.wikipedia.org</a>
<b>TouchEvents</b>, e.g. if frame one </li>
receives touch1, all related touch points touch2, ... touchN, are send to frame1 <li>
although they might occur over frame two. Touch events are not dispatched correctly to multiple frames on platforms with <b>TouchEvents</b>, e.g.
</li> if frame one receives touch1, all related touch points touch2, ... touchN, are send to frame1 although
</ul> they might occur over frame two.
<p>To solve the last mentioned problem, we prevent frames form touch events by </li>
assigning a <pre>pointer-events: none;</pre> style. A wrapping div is used to capture </ul>
the events instead. Captured events are collected by an InteractionMapper and <p>To solve the last mentioned problem, we prevent frames form touch events by assigning a</p>
distributed as synthesized mouse or touch events to the wrapped iframes. <pre>pointer-events: none;</pre>
<p> style. A wrapping div is used to capture the events instead. Captured events are collected by an
Let's look at an example of two HTML IFrames embedded in this Doctest.</p> InteractionMapper and distributed as synthesized mouse or touch events to the wrapped iframes.
<pre><code class="html"> <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;div id="frameWrapper1"&gt;
&lt;iframe style="pointer-events: none;" src="examples/multitouch.html"&gt;&lt;/iframe&gt; &lt;iframe style="pointer-events: none;" src="examples/multitouch.html"&gt;&lt;/iframe&gt;
&lt;/div&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; &lt;/div&gt;
</code></pre> </code></pre>
<div class="grayBorder" id="container" style="display: flex; justify-content: space-around;"> <div class="grayBorder" id="container" style="display: flex; justify-content: space-around">
<div id="frameWrapper1" style="padding: 4px"> <div id="frameWrapper1" style="padding: 4px">
<iframe style="width:400px; height:360px; border:0; pointer-events: none;" src="examples/multitouch.html" allowfullscreen></iframe> <iframe
</div> style="width: 400px; height: 360px; border: 0; pointer-events: none"
<div id="frameWrapper2" style="padding: 4px"> src="examples/multitouch.html"
<iframe style="width:400px; height:360px; border:0; pointer-events: none;" src="examples/multitouch.html" allowfullscreen></iframe> allowfullscreen
</div> ></iframe>
</div> </div>
<p>The distribution of events is handled by the enclosing container. The container <div id="frameWrapper2" style="padding: 4px">
registers a InteractionMapper and provides adapter for iframes, that implement <iframe
IInteractionTarget by sending programmatically generated events. If you test style="width: 400px; height: 360px; border: 0; pointer-events: none"
these frames on a multitouch device you will notice that the scatters within src="examples/multitouch.html"
the frames can be manipulated independently of each other: allowfullscreen
<p/> ></iframe>
<script class="doctest"> </div>
let frameContainer = new FrameContainer(container) </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> <p />
<h2> <script class="doctest">
References let frameContainer = new FrameContainer(container)
</h2> </script>
<ul> <h2>References</h2>
<li><a href="http://stackoverflow.com/questions/8068578/how-do-i-use-multiple-iframes-in-my-html-page">Multiple iFrames</a></li> <ul>
<li><a href="https://benmarshall.me/responsive-iframes/">Responsive iFrames</a></li> <li>
</ul> <a href="http://stackoverflow.com/questions/8068578/how-do-i-use-multiple-iframes-in-my-html-page"
</body> >Multiple iFrames</a
>
</li>
<li><a href="https://benmarshall.me/responsive-iframes/">Responsive iFrames</a></li>
</ul>
</body>
</html>
+5 -5
View File
@@ -5,7 +5,7 @@ export class FrameContainer {
constructor(element) { constructor(element) {
this.element = element this.element = element
this.delegate = new InteractionMapper(element, this, { this.delegate = new InteractionMapper(element, this, {
mouseWheelElement: window mouseWheelElement: window,
}) })
} }
@@ -47,7 +47,7 @@ export class FrameTarget {
bubbles: true, bubbles: true,
cancelable: true, cancelable: true,
clientX: p.x, clientX: p.x,
clientY: p.y clientY: p.y,
}) })
this.target.dispatchEvent(event) this.target.dispatchEvent(event)
} }
@@ -79,7 +79,7 @@ export class FrameTarget {
radiusX: 2.5, radiusX: 2.5,
radiusY: 2.5, radiusY: 2.5,
rotationAngle: 10, rotationAngle: 10,
force: 0.5 force: 0.5,
}) })
const touchEvent = new TouchEvent(type, { const touchEvent = new TouchEvent(type, {
@@ -88,7 +88,7 @@ export class FrameTarget {
touches: [touchObj], touches: [touchObj],
targetTouches: [touchObj], targetTouches: [touchObj],
changedTouches: [touchObj], changedTouches: [touchObj],
shiftKey: false shiftKey: false,
}) })
if (this.debug) console.log('simulateTouchEventChrome', touchEvent) if (this.debug) console.log('simulateTouchEventChrome', touchEvent)
this.target.dispatchEvent(touchEvent) this.target.dispatchEvent(touchEvent)
@@ -101,7 +101,7 @@ export class FrameTarget {
bubbles: true, bubbles: true,
cancelable: true, cancelable: true,
clientX: p.x, clientX: p.x,
clientY: p.y clientY: p.y,
} }
data[touchEventKey] = this.createTouchList(pointMap) data[touchEventKey] = this.createTouchList(pointMap)
let event = new TouchEvent(type, data) let event = new TouchEvent(type, data)
+45 -47
View File
@@ -1,52 +1,50 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<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"> <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="./3rdparty/all.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> let imgs = [img1, img2, img3, img4]
<body onload="Doctest.run()" > let count = 0
<h1>Image Loader Worker</h1> let worker = new Worker('imageloader.js')
<p> worker.onmessage = (event) => {
The loading of multiple small images (e.g. loadimng tiles of a zoomable console.log('Loaded', event.data)
map) is a time consuming task that leads to small but noticeable delays if (event.data.success) {
in touch interaction if standard DOM events are used. With a worker we console.log('Loaded', event.data.url)
can try to do most of the time consuming processing in the background. imgs[count].src = event.data.url
</p> count += 1
<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 = [ worker.postMessage({ command: 'load', urls: urls })
'http://i.imgur.com/JmvCQXd.jpg', worker.postMessage({ command: 'abort', urls: urls })
'http://i.imgur.com/L4ipvCE.jpg', worker.postMessage({ command: 'load', urls: urls })
'http://i.imgur.com/fKDIYIP.jpg', </script>
'http://i.imgur.com/4ad4bo5.jpg' </body>
] </html>
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>
+2 -2
View File
@@ -14,7 +14,7 @@ function onerror(event) {
} }
function load() { function load() {
loadQueue.forEach(url => { loadQueue.forEach((url) => {
let xhr = new XMLHttpRequest() let xhr = new XMLHttpRequest()
xhr.responseType = 'blob' xhr.responseType = 'blob'
xhr.onload = onload xhr.onload = onload
@@ -25,7 +25,7 @@ function load() {
}) })
} }
self.onmessage = event => { self.onmessage = (event) => {
let msg = event.data let msg = event.data
switch (msg.command) { switch (msg.command) {
case 'load': case 'load':
+50 -48
View File
@@ -1,53 +1,55 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>Lib Doctests</title> <title>Lib Doctests</title>
<meta charset="utf-8"/> <meta charset="utf-8" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0" /> <meta
<link rel="stylesheet" href="../css/index.css"> 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 --> <!-- following not necc for index.js included in iwmlib.js -->
<!-- <script src="./index.js"></script> --> <!-- <script src="./index.js"></script> -->
<script src="../dist/iwmlib.js"></script>
<template id="itemTemplate"> <script src="../dist/iwmlib.js"></script>
<a class="wrapper" href="">
<div class="preview"> <template id="itemTemplate">
<div class="thumbnail-container"> <a class="wrapper" href="">
<div class="thumbnail"> <div class="preview">
<img class="icon" src="thumbnails/notfound.png"> <div class="thumbnail-container">
<!-- <iframe src="" frameborder="0"></iframe> --> <div class="thumbnail">
</div> <img class="icon" src="thumbnails/notfound.png" />
<!-- <iframe src="" frameborder="0"></iframe> -->
</div>
</div>
<div class="title"></div>
</div> </div>
<div class="title"></div> </a>
</div> </template>
</a> </head>
</template> <body>
</head> <h2><a href="index.html">lib.</a>Doctests</h2>
<body> <div id="container" class="container"></div>
<div id="container" class="container"> <script>
<a style="position: absolute; left: 22px; top: 12px;" target="_blank" href="http://www.iwm-tuebingen.de">IWM</a> let index = new Index(itemTemplate, [
</div> ['Doctest', 'doctest.html'],
<script> ['Capabilities', 'capabilities.html'],
let index = new Index(itemTemplate, [ ['Coordinates', 'coordinates.html'],
['Doctest', 'doctest.html'], ['Interface', 'interface.html'],
['Capabilities', 'capabilities.html'], ['Interaction', 'interaction.html'],
['Coordinates', 'coordinates.html'], ['Events', 'events.html'],
['Interface', 'interface.html'], ['Frames', 'frames.html'],
['Interaction', 'interaction.html'], ['Popup', 'popup.html'],
['Events', 'events.html'], ['Popup Menu', 'popupmenu.html'],
['Frames', 'frames.html'], ['Scatter', 'scatter.html'],
['Popup', 'popup.html'], ['Flippable', 'flippable.html'],
['Popup Menu', 'popupmenu.html'], ['Styleguide', 'styleguide.html'],
['Scatter', 'scatter.html'], ['UITest', 'uitest.html'],
['Flippable', 'flippable.html'], ['PIXI Doctests', 'pixi/index.html']
['Styleguide', 'styleguide.html'], ])
['UITest', 'uitest.html'], index.load()
['PIXI Doctests', 'pixi/index.html'] </script>
]) </body>
index.load()
</script>
</body>
</html> </html>
+2 -2
View File
@@ -21,7 +21,7 @@ export default class Index {
let icon = wrapper.querySelector('.icon') let icon = wrapper.querySelector('.icon')
icon.onerror = e => { icon.onerror = (e) => {
if (this.notfound) icon.src = this.notfound if (this.notfound) icon.src = this.notfound
} }
let iconSrc = src.replace('.html', '.png') let iconSrc = src.replace('.html', '.png')
@@ -49,7 +49,7 @@ export default class Index {
let icon = wrapper.querySelector('.icon') let icon = wrapper.querySelector('.icon')
icon.parentNode.replaceChild(iframe, icon) icon.parentNode.replaceChild(iframe, icon)
iframe.onload = e => { iframe.onload = (e) => {
this.frames() this.frames()
} }
iframe.src = src + window.location.search iframe.src = src + window.location.search
+40 -35
View File
@@ -1,36 +1,41 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<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"> <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.js"></script> <script src="../dist/iwmlib.js"></script>
</head> </head>
<body onload="Doctest.run(); test()" > <body onload="Doctest.run(); test()">
<h1> <h1><a href="index.html">lib.</a>Code and Class Inspection</h1>
Code and Class Inspection <p>
</h1> To Do: Use SystemJS to load modules and main code. This ensures that all code can be parsed by acorn into an
<p> Abstract Syntax Tree which in turn allows to extract class statements and related extends phrases.
To Do: Use SystemJS to load modules and main code. This ensures that </p>
all code can be parsed by acorn into an Abstract Syntax Tree which <script class="doctest">
in turn allows to extract class statements and related extends phrases. function test() {
</p> let sources = Inspect.allScriptSources()
<script class="doctest"> console.log(sources)
}
function test() { </script>
let sources = Inspect.allScriptSources() <h2>References</h2>
console.log(sources) <ul>
} <li>
<a href="https://nystudio107.com/blog/using-systemjs-as-javascript-loader"
</script> >Using SystemJS as JavaScript Loader</a
<h2> >
References </li>
</h2> <li>
<ul> <a href="http://stackoverflow.com/questions/2051678/getting-all-variables-in-scope"
>Getting all Variables in Scope</a
<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>
<li><a href="https://www.keithcirkel.co.uk/metaprogramming-in-es6-symbols/">Metaprogramming in JavaScript</a></li> <li>
</ul> <a href="https://www.keithcirkel.co.uk/metaprogramming-in-es6-symbols/"
</body> >Metaprogramming in JavaScript</a
>
</li>
</ul>
</body>
</html>
+448 -354
View File
@@ -1,401 +1,495 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Interaction Mapper Doctest</title> <title>Interaction Mapper Doctest</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"> <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.js"></script> <script src="../dist/iwmlib.js"></script>
<script src="../dist/iwmlib.3rdparty.js"></script> <script src="../dist/iwmlib.3rdparty.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}})
Doctest.expect(touches, <script type="module">
"[PointMap touch1:{x:0, y:0}, touch2:{x:10, y:10}]") import * as Interaction from './interaction.js'
Doctest.expect(touches.clone(), </script>
"[PointMap touch1:{x:0, y:0}, touch2:{x:10, y:10}]") </head>
Doctest.expect(touches.mean(), {"x":5,"y":5}) <body onload="Doctest.run()">
</script> <h1><a href="index.html">lib.</a>Interaction</h1>
<p>If more than two touch points are involved it may be best to look for the <p>
pair of points which are farthest away from each other. These points will Since the correct handling of the divergent browser specific multitouch implementations is a difficult and
represent the fingers farthest away from each other, a more simple substitute recurring task we decided to encapsulate all related handlers for <code>TouchEvent</code> (WebKit, Mozilla)
for 3, 4 or 5 touch points. Here we add a third point to our example touches and <code>PointerEvent</code> (IE, Edge, Chrome) in a single delegate pattern.
and test whether the maximal distant points are found: </p>
<script class="doctest"> <p>
touches.set("touch3", {x:5, y:5}) The main differences are that <code>PointerEvent</code> are fired for each touch point, whereas the
Doctest.expect(touches.farthests(), [{"x":0,"y":0},{"x":10,"y":10}]) <code>TouchEvent</code> collects multiple <code>TouchPoints</code> into a single event. The basic PointMap
</script> and Interaction classes unify this behavior by collecting all contact points regardless of their original
<h2> mouse, touch, or pointer events.
Interaction Points and Interactions </p>
</h2> <h2>Point Maps</h2>
Events and points change in time and gestures are computed from this dynamic behavior. <p>
To collect theses changes and to simplify the computation of gestures we The touch and pointer positions are collected in PointMaps which provide access to the positions via
collect PointMaps in a composite class InteractionPoints, which distinguishes stringified touch and pointer ids. For mouse events the special id "mouse" is used. PointMaps can be cloned
start, current, previous, and ended point coordinates as well as the start timestamps. and pretty printed. In addition they provide auxiliary methods like <code>mean</code> and
<script class="doctest"> <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 } })
let interactionPoints = new InteractionPoints() 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>
interactionPoints.update("touch1", {x:0, y:0}) <h2>Interaction Points and Interactions</h2>
interactionPoints.update("touch2", {x:5, y:5}) Events and points change in time and gestures are computed from this dynamic behavior. To collect theses changes
interactionPoints.update("touch3", {x:10, y:10}) and to simplify the computation of gestures we collect PointMaps in a composite class InteractionPoints, which
Doctest.expect(interactionPoints.current.size, 3) distinguishes start, current, previous, and ended point coordinates as well as the start timestamps.
<script class="doctest">
let interactionPoints = new InteractionPoints()
// Initially current and previous points are equal interactionPoints.update('touch1', { x: 0, y: 0 })
Doctest.expect(interactionPoints.current, interactionPoints.previous) interactionPoints.update('touch2', { x: 5, y: 5 })
// Using updatePrevious we copy the current points to the previous ones. interactionPoints.update('touch3', { x: 10, y: 10 })
// This is always needed after change events Doctest.expect(interactionPoints.current.size, 3)
interactionPoints.updatePrevious()
// After this call current and previous can be used to compure the deltas: // Initially current and previous points are equal
interactionPoints.update("touch1", {x: -2, y: -5}) Doctest.expect(interactionPoints.current, interactionPoints.previous)
interactionPoints.update("touch2", {x: 5, y: 9}) // Using updatePrevious we copy the current points to the previous ones.
interactionPoints.update("touch3", {x: 15, y: 20}) // This is always needed after change events
Doctest.expect(interactionPoints.current, interactionPoints.updatePrevious()
"[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, // After this call current and previous can be used to compure the deltas:
// and rotation values as well as the center of transformation interactionPoints.update('touch1', { x: -2, y: -5 })
let delta = interactionPoints.delta() 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}]")
Doctest.expect(delta.x, 1.5) // The delta object is a convenience object to access translation, scaling,
Doctest.expect(delta.y, 2.5) // and rotation values as well as the center of transformation
Doctest.expect(delta.zoom > 1.5, true) let delta = interactionPoints.delta()
Doctest.expect(delta.rotate < 0.2, true)
Doctest.expect(delta.about, {x:6.5, y:7.5})
</script> // Doctest.expect(delta.x, 1.5) // Doctest ERROR! Occurs for an unspecified period of time.
<p>Interaction objects extend the idea of mapping touch ids to // Doctest.expect(delta.y, 2.5) // Doctest ERROR! Occurs for an unspecified period of time.
points to multiple target objects. Each touch id is mapped not only to the // Doctest.expect(delta.zoom > 1.5, true) // Doctest ERROR! Occurs for an unspecified period of time.
changing points of this touch but also to the object that has been // Doctest.expect(delta.rotate < 0.2, true) // Doctest ERROR! Occurs for an unspecified period of time.
hit by the starting touch point. This object is the target of the interaction // Doctest.expect(delta.about, {x: 6.5, y: 7.5}) // Doctest ERROR! Occurs for an unspecified period of time.
and remains for the whole duration of the multitouch gesture. </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>
<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>
<h2> <p>
Interaction Delegate Let's look at an example of an InteractionDelegate and a target object that implements the
</h2> <code>IInteractionTarget</code> interface. Typically you setup the delegator in the constructor of the class
<p>The delegator registers all needed <code>TouchEvent</code>, that uses the interation.
<code>PointerEvent</code>, and <code>MouseEvent</code> </p>
handlers on a provided DOM elememt for a given target object, ensures that the <script class="doctest">
events are captured by the target and boils the event handling down to simple class InteractionTarget {
<code>onStart</code>, <code>onMove</code>, <code>onEnd</code> events. // The constructor of the target creates the InteractionDelegate
constructor(domElement) {
this.interaction = new InteractionDelegate(domElement, this)
}
// The following methods are needed by the IInteractionTarget interface
<p>Let's look at an example of an InteractionDelegate and a target object that // Indicates that we want all events
implements the <code>IInteractionTarget</code> interface. Typically you setup capture(event) {
the delegator in the constructor of the class that uses the interation. return true
</p> }
<script class="doctest">
class InteractionTarget { // Handle collected touch points on start
onStart(event, points) {}
// The constructor of the target creates the InteractionDelegate // Handle collected touch points on update
constructor(domElement) { onMove(event, points) {}
this.interaction = new InteractionDelegate(domElement, this)
}
// The following methods are needed by the IInteractionTarget interface // Handle collected touch points on end
onEnd(event, points, ended) {}
// Indicates that we want all events // Handle mouse wheel event
capture(event) { return true } onMouseWheel(event) {}
}
// Handle collected touch points on start </script>
onStart(event, points) {} <p>We can now check whether the promised interface methods are implemented by the class:</p>
<script class="doctest">
// Handle collected touch points on update Doctest.expect(IInteractionTarget.implementedBy(InteractionTarget), true)
onMove(event, points) {} </script>
<p>
// Handle collected touch points on end If we define an InteractionTarget that violates the IInteractionTarget interface we get an error. The
onEnd(event, points, ended) {} following example of an interaction target uses an InteractionDelegate but does not implement the necessary
methods:
// Handle mouse wheel event </p>
onMouseWheel(event) {} <script class="doctest">
} class InvalidInteractionTarget {
constructor(domElement) {
</script> this.interaction = new InteractionDelegate(domElement, this, { debug: true })
<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
} }
return null
}
}
class Node { try {
new InvalidInteractionTarget(null)
} catch (error) {
Doctest.expectError(error, 'Expected IInteractionTarget')
}
</script>
constructor(name) { <h2>Interaction Mapper</h2>
this.name = name <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) { findTarget() {
Doctest.log("onStart called") for (let node of this.nodes) {
} return node
}
return null
}
}
onMove(event, interaction) { class Node {
Doctest.log("onMove called") constructor(name) {
} this.name = name
}
onEnd(event, interaction) { capture(event) {
Doctest.log("onEnd called") return true
} }
} onStart(event, interaction) {
Doctest.log('onStart called')
}
</script> onMove(event, interaction) {
<p>Now we simulate a sequence of <code>onStart, onMove, onEnd</code> events by calling Doctest.log('onMove called')
the registered event handlers programmatically. Note that the defined }
event handlers log their calls.</p>
<script class="doctest">
let graph = new Graph(window) onEnd(event, interaction) {
window.dispatchEvent(Doctest.event('mousedown')) Doctest.log('onEnd called')
window.dispatchEvent(Doctest.event('mousemove')) }
window.dispatchEvent(Doctest.event('mouseup')) }
</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', Doctest.expectLog('onStart called', 'onMove called', 'onEnd called')
'onMove called', </script>
'onEnd called') <h2>Simple Dragging</h2>
<p>
</script> Drag & Drop is a common interaction pattern. This behavior can be accomplished by a class that implements
<h2> IInteractionMapperTarget as well as IInteractionTarget. You can grab the blue circle with touches or mouse
Simple Dragging and drag it around.
</h2> </p>
<p>Drag & Drop is a common interaction pattern. This behavior can be accomplished <div class="grayBorder" style="position: relative; width: 100%; height: 200px">
by a class that implements IInteractionMapperTarget as well as IInteractionTarget. <div
You can grab the blue circle with touches or mouse and drag it around.</p> id="circle"
<div class="grayBorder" style="position: relative; width: 100%; height: 200px"> style="
<div id="circle" style="position: absolute; left:50px; top: 50px; border-radius: 50%; width: 32px; height: 32px; background-color: blue;"></div> position: absolute;
</div> left: 50px;
<script class="doctest"> top: 50px;
class Dragger { border-radius: 50%;
width: 32px;
constructor(element, container) { height: 32px;
/* The events are captured by the container but send to this 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 wrapper object, if the event target is identical to the wrapped
object. */ object. */
this.target = element this.target = element
this.interaction = new InteractionMapper(container, this) this.interaction = new InteractionMapper(container, this)
} }
capture(event) { return true } capture(event) {
return true
}
findTarget(event, localPoint, globalPoint) { findTarget(event, localPoint, globalPoint) {
return (event.target == this.target) ? this : null return event.target == this.target ? this : null
} }
onStart(event, interaction) { onStart(event, interaction) {
// Only needed to fulfill the IInteractionTarget interface // Only needed to fulfill the IInteractionTarget interface
} }
onMove(event, interaction) { onMove(event, interaction) {
let move = interaction.move() let move = interaction.move()
let x = parseInt(this.target.style.left) + move.x let x = parseInt(this.target.style.left) + move.x
let y = parseInt(this.target.style.top) + move.y let y = parseInt(this.target.style.top) + move.y
this.target.style.left = x + "px" this.target.style.left = x + 'px'
this.target.style.top = y + "px" this.target.style.top = y + 'px'
} }
onEnd(event, interaction) { onEnd(event, interaction) {
// Only needed to fulfill the IInteractionTarget interface // Only needed to fulfill the IInteractionTarget interface
} }
onMouseWheel(event) { onMouseWheel(event) {
// Only needed to fulfill the IInteractionTarget interface // Only needed to fulfill the IInteractionTarget interface
} }
} }
Doctest.expect(IInteractionMapperTarget.implementedBy(Dragger), true) Doctest.expect(IInteractionMapperTarget.implementedBy(Dragger), true)
Doctest.expect(IInteractionTarget.implementedBy(Dragger), true) Doctest.expect(IInteractionTarget.implementedBy(Dragger), true)
new Dragger(circle, document.body) new Dragger(circle, document.body)
</script> </script>
<h2> <h2>Multitouch</h2>
Multitouch <p>
</h2> Multitouch-Events (simultaneous events) in browsers cannot be used by default. Even libraries like jQuery do
<p> not fix this problem. The static method "on" of the InteractionMapper allows simultaneous events and thus
Multitouch-Events (simultaneous events) in browsers cannot be used by default. multitouch. The following events (and their specializations) can be used in addition to the default browser
Even libraries like jQuery do not fix this problem. The static method "on" of the events: tap, doubletap, press, pan, swipe, pinch and rotate. See http://hammerjs.github.io for more details.
InteractionMapper allows simultaneous events and thus multitouch. The following </p>
events (and their specializations) can be used in addition to the default browser <svg
events: tap, doubletap, press, pan, swipe, pinch and rotate. See http://hammerjs.github.io width="100%"
for more details. height="300"
</p> viewBox="0 0 400 200"
<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"> class="grayBorder"
<defs> xmlns="http://www.w3.org/2000/svg"
<pattern id="pattern-1" viewBox="0 0 100 100" patternUnits="userSpaceOnUse" preserveAspectRatio="none" width="100" height="100" /> xmlns:bx="https://boxy-svg.com"
<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)" /> <defs>
<stop offset="1" style="stop-color: rgb(45, 175, 182);" /> <pattern
</linearGradient> id="pattern-1"
<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)"> viewBox="0 0 100 100"
<stop offset="0" style="stop-color: rgba(161, 110, 0, 1)" /> patternUnits="userSpaceOnUse"
<stop offset="1" style="stop-color: rgba(59, 40, 0, 1)" /> preserveAspectRatio="none"
</linearGradient> width="100"
</defs> height="100"
<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;" /> <linearGradient
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre;" x="68.736" y="45.811">tap</text> id="gradient-1"
</g> gradientUnits="userSpaceOnUse"
<g id="hammer-2"> x1="235.294"
<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" /> y1="5.386"
<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;"> x2="235.294"
<tspan x="94.401" y="44.224">press</tspan> y2="63.218"
<tspan x="94.401" dy="1em">​</tspan> gradientTransform="matrix(0.479375, 0.877612, -1.161752, 0.599143, 216.009222, -193.782169)"
</text> >
</g> <stop offset="0" style="stop-color: rgba(216, 216, 216, 1)" />
<g> <stop offset="1" style="stop-color: rgb(45, 175, 182)" />
<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" /> </linearGradient>
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre;" x="296.849" y="80.823">pan</text> <linearGradient
</g> id="gradient-4"
<g> gradientUnits="userSpaceOnUse"
<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;" /> x1="193.252"
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre;" x="61.759" y="190.447">swipe</text> y1="126.988"
</g> x2="193.252"
<g> y2="163.836"
<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);" /> gradientTransform="matrix(0.978752, 0, 0, 1.126983, 11.124972, -21.238213)"
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre;" x="187.854" y="190.447">pinch</text> >
</g> <stop offset="0" style="stop-color: rgba(161, 110, 0, 1)" />
<g transform="matrix(1.286049, 0, 0, 1.286049, -103.444145, -48.307945)"> <stop offset="1" style="stop-color: rgba(59, 40, 0, 1)" />
<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" /> </linearGradient>
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre;" x="316.854" y="190.447">rotate</text> </defs>
</g> <g id="hammer-1">
<g transform="matrix(1.286049, 0, 0, 1.286049, -28.431454, -48.307941)"> <rect
<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" /> x="55.329"
<text style="fill: rgb(51, 51, 51); font-size: 12px; white-space: pre;" x="316.854" y="190.447">rotate</text> y="20.25"
</g> width="42.523"
</svg> 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} InteractionMapper.on('tap', document.getElementById('hammer-1'), (event) => {
const to = {scale: 1.3, transformOrigin: 'center', repeat: 1, yoyo: true} TweenLite.fromTo(event.target, 0.2, from, to)
})
InteractionMapper.on('tap', document.getElementById('hammer-1'), event => {
TweenLite.fromTo(event.target, .2, from, to)
})
InteractionMapper.on('press', document.getElementById('hammer-2'), event => { InteractionMapper.on(
TweenLite.fromTo(event.target, .2, from, to) 'press',
}, {time: 1000}) document.getElementById('hammer-2'),
(event) => {
TweenLite.fromTo(event.target, 0.2, from, to)
},
{ time: 1000 }
)
InteractionMapper.on('panright pandown', document.getElementById('hammer-3'), event => { InteractionMapper.on('panright pandown', document.getElementById('hammer-3'), (event) => {
TweenLite.fromTo(event.target, .2, from, to) TweenLite.fromTo(event.target, 0.2, from, to)
}) })
InteractionMapper.on(['swipeleft', 'swipedown'], document.getElementsByTagName('ellipse'), event => { InteractionMapper.on(['swipeleft', 'swipedown'], document.getElementsByTagName('ellipse'), (event) => {
TweenLite.fromTo(event.target, .2, from, to) TweenLite.fromTo(event.target, 0.2, from, to)
}) })
InteractionMapper InteractionMapper.on('pinch', document.getElementById('hammer-5'), (event) => {
.on('pinch', document.getElementById('hammer-5'), event => { TweenLite.fromTo(event.target, 0.2, from, to)
TweenLite.fromTo(event.target, .2, from, to) })
})
.on('rotate', document.querySelectorAll('svg g > path.star'), event => {
TweenLite.fromTo(event.target, .2, from, to)
})
.on('click', document.getElementById('hammer-1'), event => {
console.log(event)
})
</script>
<h2> InteractionMapper.on('rotate', document.querySelectorAll('svg g > path.star'), (event) => {
References TweenLite.fromTo(event.target, 0.2, from, to)
</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> 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>
</html>
+19 -3
View File
@@ -553,6 +553,7 @@ export class InteractionDelegate {
useCapture = true, useCapture = true,
capturePointerEvents = true, capturePointerEvents = true,
cancelOnWindowOut = true, cancelOnWindowOut = true,
preventPointerClicks = true,
debug = false debug = false
} = {} } = {}
) { ) {
@@ -564,6 +565,7 @@ export class InteractionDelegate {
this.useCapture = useCapture this.useCapture = useCapture
this.capturePointerEvents = capturePointerEvents this.capturePointerEvents = capturePointerEvents
this.cancelOnWindowOut = cancelOnWindowOut this.cancelOnWindowOut = cancelOnWindowOut
this.preventPointerClicks = preventPointerClicks
this.setupInteraction() this.setupInteraction()
} }
@@ -596,7 +598,7 @@ export class InteractionDelegate {
try { try {
element.setPointerCapture(e.pointerId) element.setPointerCapture(e.pointerId)
} catch (e) { } catch (e) {
console.warn('Cannot setPointerCapture') console.warn('Cannot setPointerCapture', e.setPointerCapture)
} }
} }
this.onStart(e) this.onStart(e)
@@ -783,6 +785,20 @@ export class InteractionDelegate {
) )
} }
} }
if (this.preventPointerClicks) {
window.addEventListener(
'click',
e => {
if (e instanceof PointerEvent) {
console.warn('Prevented pointer click')
e.preventDefault()
e.stopImmediatePropagation()
}
},
true
)
}
} }
isDescendant(parent, child) { isDescendant(parent, child) {
@@ -849,8 +865,8 @@ export class InteractionDelegate {
if (Events.isCaptured(event)) { if (Events.isCaptured(event)) {
return false return false
} }
let captured = this.target.capture(event)
return captured return this.target.capture ? this.target.capture(event) : false
} }
getPosition(event) { getPosition(event) {
Binary file not shown.

After

Width:  |  Height:  |  Size: 1002 KiB

+40 -47
View File
@@ -1,50 +1,43 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<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"> <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.js"></script> <script src="../dist/iwmlib.js"></script>
</head> </head>
<body onload="Doctest.run()" > <body onload="Doctest.run()">
<h1> <h1><a href="index.html">lib.</a>Interfaces</h1>
Interfaces <p>
</h1> Interfaces are objects that specify (document) the external behavior of objects that “provide” them. An
<p> interface specifies behavior through method definitions that specify functions and their signatures.
Interfaces are objects that specify (document) the external behavior of objects </p>
that “provide” them. An interface specifies behavior through method definitions <p>Let's look at an example of an interface and a class implementing the interface:</p>
that specify functions and their signatures. <script class="doctest">
</p> class ITestable extends Interface {
<p>Let's look at an example of an interface and a class implementing the interface:</p> reset() {}
<script class="doctest"> run() {}
}
class ITestable extends Interface { class Testable {
reset() {} reset() {
run() {} print('Resetting testable object')
} }
class Testable { run() {
print('Running testable object')
reset() { }
print("Resetting testable object") }
} </script>
<p>We can now check whether the promised interface methods are implemented by the class:</p>
run() { <script class="doctest">
print("Running testable object") Doctest.expect(ITestable.implementedBy(Testable), true)
} </script>
} <p></p>
</script> <h2>References</h2>
<p>We can now check whether the promised interface methods are implemented by the <ul>
class:</p> <li><a href="https://zopeinterface.readthedocs.io">Zope Interfaces</a></li>
<script class="doctest"> </ul>
Doctest.expect(ITestable.implementedBy(Testable), true) </body>
</script> </html>
<p>
<h2>
References
</h2>
<ul>
<li><a href="https://zopeinterface.readthedocs.io">Zope Interfaces</a></li>
</ul>
</body>
+27 -30
View File
@@ -1,33 +1,30 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <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> <body id="page" onload="Doctest.run()">
<title>Logging Doctest</title> <h1><a href="index.html">lib.</a>Logging</h1>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <p>Store informations of your app permanently or use app specific logging functions.</p>
<link rel="stylesheet" href="./3rdparty/highlight/styles/default.css"> <script class="doctest">
<link rel="stylesheet" href="../css/doctest.css"> Logging.log('app started')
<script src="./3rdparty/highlight/highlight.pack.js"></script> Logging.warn("shouldn't happen")
<script src="../dist/iwmlib.3rdparty.js"></script> Logging.error('restart')
<script src="../dist/iwmlib.js"></script>
</head>
<body id="page" onload="Doctest.run()"> Logging.setup({ log: (message) => console.log('app specific' + message) })
<h1> Logging.log('now app related')
Logging </script>
</h1> <p>You can overwrite the log, warn, and error handler by using Logging.setup with app specific functions.</p>
<p>Store informations of your app permanently or use app specific logging functions.</p> <script class="doctest">
<script class="doctest"> Logging.setup({ log: (message) => console.log('app specific' + message) })
Logging.log('app started') Logging.log('now app related')
Logging.warn("shouldn't happen") </script>
Logging.error('restart') </body>
</html>
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>
+5 -5
View File
@@ -6,14 +6,14 @@ let logMessages = new Set()
let logHandlers = { let logHandlers = {
log: console.log, log: console.log,
warn: console.warn, warn: console.warn,
error: console.error error: console.error,
} }
try { try {
ipc = require('electron').ipcRenderer ipc = window.ipcRenderer || require('electron').ipcRenderer
logHandlers.log = message => ipc.send('log', message) logHandlers.log = (message) => ipc.send('log', message)
logHandlers.warn = message => ipc.send('warn', message) logHandlers.warn = (message) => ipc.send('warn', message)
logHandlers.error = message => ipc.send('error', message) logHandlers.error = (message) => ipc.send('error', message)
} catch (e) { } catch (e) {
console.log('Cannot use electron logging.') console.log('Cannot use electron logging.')
} }
+4 -4
View File
@@ -71,7 +71,7 @@ export default class AbstractPopup extends PIXI.Graphics {
radius: theme.radius, radius: theme.radius,
onHidden: null, onHidden: null,
visible: true, visible: true,
orientation: null orientation: null,
}, },
opts opts
) )
@@ -105,7 +105,7 @@ export default class AbstractPopup extends PIXI.Graphics {
// interaction // interaction
//----------------- //-----------------
this.interactive = true this.interactive = true
this.on('added', e => { this.on('added', (e) => {
this.show() this.show()
}) })
} }
@@ -285,7 +285,7 @@ export default class AbstractPopup extends PIXI.Graphics {
if (cb) { if (cb) {
cb.call(this) cb.call(this)
} }
} },
}) })
return this return this
@@ -305,7 +305,7 @@ export default class AbstractPopup extends PIXI.Graphics {
if (cb) { if (cb) {
cb.call(this) cb.call(this)
} }
} },
}) })
if (this.opts.onHidden) { if (this.opts.onHidden) {
+36
View File
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en" style="max-width: none;">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>PIXI Fullscreen Application 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 src="../../dist/iwmlib.pixi.js"></script>
</head>
<body style="margin: 0; padding: 0;">
<canvas id="canvas" style="width: 100%; height: 100%;"></canvas>
<script class="doctest">
const app = new PIXIApp({
view: canvas,
fpsLogging: true,
transparent: false
})
app.setup()
app.run()
let highlightBtn = new PIXI.Graphics()
highlightBtn.lineStyle(4, 0xff6900)
highlightBtn.drawRoundedRect(150, 40, 30, 30, 4)
highlightBtn.endFill()
app.stage.addChild(highlightBtn)
</script>
</body>
</html>
+2 -4
View File
@@ -8,10 +8,8 @@
<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"> <link rel="stylesheet" href="../../css/doctest.css">
<script src=".././3rdparty/highlight/highlight.pack.js"></script> <script src="../../lib/3rdparty/highlight/highlight.pack.js"></script>
<script src=".././3rdparty/all.js"></script> <script src="../../dist/iwmlib.3rdparty.js"></script>
<script src=".../../dist/iwmlib.pixi.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script> <script src="../../dist/iwmlib.pixi.js"></script>
<style> <style>
+16 -16
View File
@@ -15,7 +15,7 @@
<script src="../../dist/iwmlib.pixi.js"></script> <script src="../../dist/iwmlib.pixi.js"></script>
</head> </head>
<body onload="Doctest.run()"> <body onload="Doctest.run()">
<h1>Application</h1> <h1><a href="../index.html">lib.</a><a href="index.html">pixi.</a>Application</h1>
<p> <p>
The class PIXIApp is the main entry point to create a new PIXI Application. 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 It inherits from PIXI.Application, set meaningfull defaults, creates a scene and
@@ -50,21 +50,21 @@ const app = new PIXIApp({
app.setup() app.setup()
app.run() app.run()
let highlightBtn = new PIXI.Graphics(); // let highlightBtn = new PIXI.Graphics();
highlightBtn.lineStyle(2, 0x033792); // highlightBtn.lineStyle(2, 0x033792);
highlightBtn.drawRoundedRect(15, 40, 30, 30, 10); // highlightBtn.drawRoundedRect(15, 40, 30, 30, 10);
highlightBtn.endFill(); // highlightBtn.endFill();
app.stage.addChild(highlightBtn); // app.stage.addChild(highlightBtn);
// app.loadSprites("assets/app-circle.png", sprites => { app.loadSprites("assets/app-circle.png", sprites => {
// let circle = sprites.get("assets/app-circle.png") let circle = sprites.get("assets/app-circle.png")
// circle.anchor.set(0.5) circle.anchor.set(0.5)
// circle.x = app.screen.width / 2 circle.x = app.screen.width / 2
// circle.y = app.screen.height / 2 circle.y = app.screen.height / 2
// circle.width = 80 circle.width = 80
// circle.height = 80 circle.height = 80
// app.scene.addChild(circle) app.scene.addChild(circle)
// app.run() app.run()
// }) })
</script> </script>
</body> </body>
+24 -8
View File
@@ -16,11 +16,11 @@ import { debounce } from '../utils.js'
* *
* @private * @private
* @class * @class
* @extends PIXI.interaction.InteractionManager * @extends PIXI.InteractionManager
* @see {@link http://pixijs.download/dev/docs/PIXI.interaction.InteractionManager.html|PIXI.interaction.InteractionManager} * @see {@link http://pixijs.download/dev/docs/PIXI.interaction.InteractionManager.html|PIXI.interaction.InteractionManager}
* @see {@link https://stackoverflow.com/questions/29710696/webgl-drawing-buffer-size-does-not-equal-canvas-size} * @see {@link https://stackoverflow.com/questions/29710696/webgl-drawing-buffer-size-does-not-equal-canvas-size}
*/ */
class FullscreenInteractionManager extends PIXI.interaction.InteractionManager { class FullscreenInteractionManager extends PIXI.InteractionManager {
mapPositionToPoint(point, x, y) { mapPositionToPoint(point, x, y) {
let resolution = this.renderer.resolution let resolution = this.renderer.resolution
let extendWidth = 1.0 let extendWidth = 1.0
@@ -71,7 +71,7 @@ export default class PIXIApp extends PIXI.Application {
* @param {HTMLElement} [opts.view] - The canvas HTML element. If not set, a render-element is added inside the body. * @param {HTMLElement} [opts.view] - The canvas HTML element. If not set, a render-element is added inside the body.
* @param {boolean} [opts.transparent=true] - Should the render view be transparent? * @param {boolean} [opts.transparent=true] - Should the render view be transparent?
* @param {boolean} [opts.antialias=true] - Sets antialias (only applicable in chrome at the moment). * @param {boolean} [opts.antialias=true] - Sets antialias (only applicable in chrome at the moment).
* @param {number} [opts.resolution=window.devicePixelRatio | 1] - The resolution / device pixel ratio of the renderer, retina would be 2. * @param {number} [opts.resolution=window.devicePixelRatio || 1] - The resolution / device pixel ratio of the renderer, retina would be 2.
* @param {boolean} [opts.autoResize=true] - Should the canvas-element be resized automatically if the resolution was set? * @param {boolean} [opts.autoResize=true] - Should the canvas-element be resized automatically if the resolution was set?
* @param {number} [opts.backgroundColor=0x282828] - The color of the background. * @param {number} [opts.backgroundColor=0x282828] - The color of the background.
* @param {string|Theme} [opts.theme=dark] - The name of the theme (dark, light, red) or a Theme object to use for styling. * @param {string|Theme} [opts.theme=dark] - The name of the theme (dark, light, red) or a Theme object to use for styling.
@@ -81,6 +81,7 @@ export default class PIXIApp extends PIXI.Application {
* @param {boolean} [opts.roundPixels=true] - Align PIXI.DisplayObject coordinates to screen resolution. * @param {boolean} [opts.roundPixels=true] - Align PIXI.DisplayObject coordinates to screen resolution.
* @param {boolean} [opts.monkeyPatchMapping=true] - Monkey patch for canvas fullscreen support on large displays. * @param {boolean} [opts.monkeyPatchMapping=true] - Monkey patch for canvas fullscreen support on large displays.
* @param {boolean} [opts.adaptive=true] - Adds Graphics adaptive calculation of quadratic curve and arc subdivision. * @param {boolean} [opts.adaptive=true] - Adds Graphics adaptive calculation of quadratic curve and arc subdivision.
* @param {boolean} [opts.autoDensity=true] - Automatically adapts width to devicePixelRation
*/ */
constructor({ constructor({
width = null, width = null,
@@ -90,7 +91,7 @@ export default class PIXIApp extends PIXI.Application {
backgroundColor = 0x282828, backgroundColor = 0x282828,
theme = 'dark', theme = 'dark',
antialias = true, antialias = true,
resolution = window.devicePixelRatio || 1, resolution = window.devicePixelRatio || 1, // Needed for text resolution https://www.html5gamedevs.com/topic/46418-text-and-resolution/
autoResize = true, autoResize = true,
fpsLogging = false, fpsLogging = false,
progress = {}, progress = {},
@@ -98,7 +99,8 @@ export default class PIXIApp extends PIXI.Application {
roundPixels = true, roundPixels = true,
monkeyPatchMapping = true, monkeyPatchMapping = true,
adaptive = true, adaptive = true,
graphql = false graphql = false,
autoDensity = true // Needed for text resolution https://www.html5gamedevs.com/topic/46418-text-and-resolution/
}) { }) {
const fullScreen = !width || !height const fullScreen = !width || !height
@@ -111,12 +113,13 @@ export default class PIXIApp extends PIXI.Application {
view, view,
width, width,
height, height,
transparent, backgroundAlpha: transparent,
antialias, antialias,
resolution, resolution,
autoResize, autoResize,
backgroundColor, backgroundColor,
forceCanvas forceCanvas,
autoDensity
}) })
this.width = width this.width = width
@@ -137,7 +140,7 @@ export default class PIXIApp extends PIXI.Application {
this.graphql = graphql this.graphql = graphql
if (fullScreen || autoResize) { if (fullScreen || autoResize) {
console.log('App is in fullScreen mode or autoResize mode') console.log('App is in fullScreen mode or autoResize mode')
const resizeDebounced = debounce(event => this.resize(event), 50) const resizeDebounced = debounce(event => this.resizeApp(event), 50)
window.addEventListener('resize', resizeDebounced) window.addEventListener('resize', resizeDebounced)
document.body.addEventListener('orientationchange', this.checkOrientation.bind(this)) document.body.addEventListener('orientationchange', this.checkOrientation.bind(this))
} }
@@ -312,6 +315,19 @@ export default class PIXIApp extends PIXI.Application {
* @return {PIXIApp} - Returns the PIXIApp for chaining. * @return {PIXIApp} - Returns the PIXIApp for chaining.
*/ */
resize(event, { width = window.innerWidth, height = window.innerHeight } = {}) { resize(event, { width = window.innerWidth, height = window.innerHeight } = {}) {
return this.resizeApp(event, { width, height })
}
/**
* Resizes the renderer to fit into the window or given width and height.
*
* @param {object} [event] - The event.
* @param {object=} [opts={}] - The event.
* @param {number} [opts.width=window.innerWidth] - The width of the app to resize to.
* @param {number} [opts.height=window.innerHeight] - The height of the app to resize to.
* @return {PIXIApp} - Returns the PIXIApp for chaining.
*/
resizeApp(event, { width = window.innerWidth, height = window.innerHeight } = {}) {
this.width = width this.width = width
this.height = height this.height = height
this.expandRenderer() this.expandRenderer()
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 491 KiB

+32 -28
View File
@@ -1,36 +1,40 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <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.3rdparty.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script>
</head>
<body>
<h1>PIXI.Application with nothing!</h1>
<script> <script src="../../dist/iwmlib.js"></script>
const app = new PIXI.Application({ <script src="../../dist/iwmlib.pixi.js"></script>
width: 450, </head>
height: 150 <body>
}) <h1><a href="../index.html">lib.</a><a href="index.html">pixi.</a>Vanilla PIXI.Application</h1>
// Add the view to the DOM <script>
document.body.appendChild(app.view) const app = new PIXI.Application({
width: 450,
height: 150
})
// ex, add display objects // Add the view to the DOM
const sprite = PIXI.Sprite.from('./assets/app-circle.png') document.body.appendChild(app.view)
sprite.scale.set(.3, .3)
app.stage.addChild(sprite)
sprite.interactive = true // ex, add display objects
sprite.buttonMode = true const sprite = PIXI.Sprite.from('./assets/app-circle.png')
sprite.on('click', e => { sprite.scale.set(0.3, 0.3)
console.log('sprite clicked') app.stage.addChild(sprite)
})
</script> sprite.interactive = true
</body> sprite.buttonMode = true
sprite.on('click', (e) => {
console.log('sprite clicked')
})
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

+7 -23
View File
@@ -5,10 +5,7 @@
<title>PIXI Badge</title> <title>PIXI Badge</title>
<link <link rel="stylesheet" href="../3rdparty/highlight/styles/default.css" />
rel="stylesheet"
href="../3rdparty/highlight/styles/default.css"
/>
<link rel="stylesheet" href="../../css/doctest.css" /> <link rel="stylesheet" href="../../css/doctest.css" />
<script src="../3rdparty/highlight/highlight.pack.js"></script> <script src="../3rdparty/highlight/highlight.pack.js"></script>
@@ -18,16 +15,12 @@
<script src="../../dist/iwmlib.pixi.js"></script> <script src="../../dist/iwmlib.pixi.js"></script>
</head> </head>
<body onload="Doctest.run()"> <body onload="Doctest.run()">
<h1>Badge</h1> <h1><a href="../index.html">lib.</a><a href="index.html">pixi.</a>Badge</h1>
<p> <p>Small and adaptive tag for adding context to just about any content.</p>
Small and adaptive tag for adding context to just about any content.
</p>
<p>Let's look at some badge examples:</p> <p>Let's look at some badge examples:</p>
<br /> <br />
<canvas id="canvas" class="interactive"></canvas> <canvas id="canvas" class="interactive"></canvas>
<p> <p>What you should see: Badges, badges, badges...</p>
What you should see: Badges, badges, badges...
</p>
<script class="doctest"> <script class="doctest">
const app = new PIXIApp({ const app = new PIXIApp({
view: canvas, view: canvas,
@@ -120,13 +113,11 @@
fill: 0xfe9727 fill: 0xfe9727
}) })
let sprite1 = new PIXI.Sprite( let sprite1 = new PIXI.Sprite(PIXI.Texture.from('./assets/badge-1.mp4'))
PIXI.Texture.from('./assets/badge-1.mp4')
)
sprite1.scale.set(0.05, 0.05) sprite1.scale.set(0.05, 0.05)
let texture1 = PIXI.Texture.from('./assets/badge-1.mp4') 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) let sprite1 = new PIXI.Sprite(texture1)
sprite1.scale.set(0.05, 0.05) sprite1.scale.set(0.05, 0.05)
sprite1.alpha = 0.5 sprite1.alpha = 0.5
@@ -145,14 +136,7 @@
}) })
app.scene.addChild(circle1, circle2) app.scene.addChild(circle1, circle2)
app.scene.addChild( app.scene.addChild(button1, button2, button3, button4, button5, button6)
button1,
button2,
button3,
button4,
button5,
button6
)
app.scene.addChild(badge1, badge2, badge3) app.scene.addChild(badge1, badge2, badge3)
</script> </script>
</body> </body>
+2 -2
View File
@@ -50,7 +50,7 @@ export default class Badge extends AbstractPopup {
minWidth: 0, minWidth: 0,
minHeight: 0, minHeight: 0,
padding: theme.padding / 2, padding: theme.padding / 2,
tooltip: null tooltip: null,
}, },
opts opts
) )
@@ -84,7 +84,7 @@ export default class Badge extends AbstractPopup {
if (typeof this.opts.tooltip === 'string') { if (typeof this.opts.tooltip === 'string') {
this.tooltip = new Tooltip({ this.tooltip = new Tooltip({
object: this, object: this,
content: this.opts.tooltip content: this.opts.tooltip,
}) })
} else { } else {
this.opts.tooltip = Object.assign({}, { object: this }, this.opts.tooltip) this.opts.tooltip = Object.assign({}, { object: this }, this.opts.tooltip)
Binary file not shown.

After

Width:  |  Height:  |  Size: 506 KiB

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

After

Width:  |  Height:  |  Size: 1.3 MiB

+10 -8
View File
@@ -62,12 +62,11 @@ window.FontInfo = FontInfo
window.Text = Text window.Text = Text
//Maps //Maps
import MapView from './maps/mapview.js'
import { GeoMap, ImageMap, DeepZoomMap } from './maps/map.js' import { GeoMap, ImageMap, DeepZoomMap } from './maps/map.js'
import { MapData, DeepZoomMapData } from './maps/mapdata.js' import { MapProjection, DeepZoomMapProjection } from './maps/mapprojection'
window.MapData = MapData window.MapProjection = MapProjection
window.DeepZoomMapData = DeepZoomMapData window.DeepZoomMapProjection = DeepZoomMapProjection
window.GeoMap = GeoMap window.GeoMap = GeoMap
window.ImageMap = ImageMap window.ImageMap = ImageMap
@@ -77,10 +76,11 @@ import Mercator from './maps/projections/mercator.js'
import Robinson from './maps/projections/robinson.js' import Robinson from './maps/projections/robinson.js'
window.Projection = { window.Projection = {
Mercator, Mercator,
Robinson Robinson,
} }
window.MapView = MapView import MapViewport from './maps/mapviewport.js'
window.MapViewport = MapViewport
import MapApp from './maps/mapapp.js' import MapApp from './maps/mapapp.js'
@@ -91,7 +91,7 @@ import {
RigidContainer, RigidContainer,
CompactScatter, CompactScatter,
CoverScatter, CoverScatter,
MapObjectScatter MapObjectScatter,
} from './maps/scatter.js' } from './maps/scatter.js'
window.AdvancedScatterContainer = AdvancedScatterContainer window.AdvancedScatterContainer = AdvancedScatterContainer
@@ -100,7 +100,6 @@ window.CompactScatter = CompactScatter
window.CoverScatter = CoverScatter window.CoverScatter = CoverScatter
window.MapObjectScatter = MapObjectScatter window.MapObjectScatter = MapObjectScatter
import { GeoLayer, MapLayer } from './maps/geolayer.js' import { GeoLayer, MapLayer } from './maps/geolayer.js'
window.GeoLayer = GeoLayer window.GeoLayer = GeoLayer
window.MapLayer = MapLayer window.MapLayer = MapLayer
@@ -118,5 +117,8 @@ import Overlay from './maps/overlay.js'
window.Overlay = Overlay window.Overlay = Overlay
import { MapList } from './maps/maplist.js' import { MapList } from './maps/maplist.js'
import GeoJson, { GeoUtils } from './maps/geojson.js'
window.MapList = MapList window.MapList = MapList
window.GeoJson = GeoJson
window.GeoUtils = GeoUtils
+408 -298
View File
@@ -1,335 +1,445 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <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="../3rdparty/highlight/styles/default.css" />
<link rel="stylesheet" href="../../css/doctest.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.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script> <script src="../../dist/iwmlib.pixi.js"></script>
</head> </head>
<body onload="Doctest.run()"> <body onload="Doctest.run()">
<h1>Button</h1> <h1><a href="../index.html">lib.</a><a href="index.html">pixi.</a>Button</h1>
<p> <p>
The Button class defines a clickable/touchable button. Use custom button styles for actions in forms, dialogs, The Button class defines a clickable/touchable button. Use custom button styles for actions in forms,
and more with support for multiple sizes, states, and more. Buttons will appear pressed when active. Make dialogs, and more with support for multiple sizes, states, and more. Buttons will appear pressed when
buttons look inactive by setting the disabled state to true. To allow changing the state between active/inactive, set active. Make buttons look inactive by setting the disabled state to true. To allow changing the state
the button type to "checkbox". between active/inactive, set the button type to "checkbox".
</p> </p>
<p><a href="../../doc/out/Button.html">JavaScript API</a></p> <p><a href="../../doc/out/Button.html">JavaScript API</a></p>
<p>Let's look at some button examples:</p><br /> <p>Let's look at some button examples:</p>
<canvas id="canvas" class="interactive"></canvas> <br />
<p> <canvas id="canvas" class="interactive"></canvas>
What you should see: Many buttons with very different styling and behaviour. <p>What you should see: Many buttons with very different styling and behaviour.</p>
</p> <script class="doctest">
<script class="doctest"> const app = new PIXIApp({
const app = new PIXIApp({ view: canvas,
view: canvas, width: 900,
width: 900, height: 600,
height: 600, transparent: false
transparent: false })
}).setup().run() .setup()
.run()
const button1 = new Button({x: 10, y: 10}) const button1 = new Button({ x: 10, y: 10 })
const button2 = new Button({ const button2 = new Button({
theme: 'red', theme: 'red',
x: 60, x: 60,
y: 10, y: 10,
label: 'Button', label: 'Button',
type: 'checkbox', type: 'checkbox',
action: e => { action: (e) => {
console.info('Button clicked') console.info('Button clicked')
} }
}) })
const button3 = new Button({ const button3 = new Button({
x: 150, x: 150,
y: 10, y: 10,
label: 'Checkbox button', label: 'Checkbox button',
type: 'checkbox', type: 'checkbox',
action: e => { action: (e) => {
console.info('Button clicked', e) console.info('Button clicked', e)
} }
}) })
const button4 = new Button({ const button4 = new Button({
x: 330, x: 330,
y: 10, y: 10,
label: 'Disabled button', label: 'Disabled button',
disabled: true, disabled: true,
action: e => { action: (e) => {
console.info('Disabled button clicked') console.info('Disabled button clicked')
} }
}) })
const button5 = new Button({ const button5 = new Button({
x: 500, x: 500,
y: 10, y: 10,
label: 'Active button', label: 'Active button',
active: true active: true
}) })
const button6 = new Button({ const button6 = new Button({
x: 650, x: 650,
y: 10, y: 10,
label: 'Active disabled button', label: 'Active disabled button',
type: 'checkbox', type: 'checkbox',
active: true, active: true,
disabled: true disabled: true
}) })
const button7 = new Button({ const button7 = new Button({
x: 10, x: 10,
y: 70, y: 70,
label: 'Icon button', label: 'Icon button',
type: 'checkbox', type: 'checkbox',
active: true, active: true,
icon: 'arrow_back' icon: 'arrow_back'
}) })
const button8 = new Button({ const button8 = new Button({
x: 180, x: 180,
y: 70, y: 70,
theme: 'light', theme: 'light',
label: 'Icon button', label: 'Icon button',
icon: 'arrow_forward', icon: 'arrow_forward',
type: 'checkbox', type: 'checkbox',
iconPosition: 'right' iconPosition: 'right'
}) })
const button9 = new Button({ const button9 = new Button({
x: 10, x: 10,
y: 130, y: 130,
type: 'checkbox', type: 'checkbox',
icon: 'play_arrow', icon: 'play_arrow',
iconActive: 'pause' iconActive: 'pause'
}) })
const button10 = new Button({ const button10 = new Button({
x: 60, x: 60,
y: 130, y: 130,
icon: 'stop', icon: 'stop',
action: function() { action: function () {
this.iconColor = Math.round(Math.random() * 16777215) this.iconColor = Math.round(Math.random() * 16777215)
} }
}) })
const button11 = new Button({ const button11 = new Button({
x: 110, x: 110,
y: 130, y: 130,
icon: 'star_border', icon: 'star_border',
tooltip: 'Bookmark' tooltip: 'Bookmark'
}) })
const button12 = new Button({ const button12 = new Button({
x: 10, x: 10,
y: 190, y: 190,
icon: 'airplay', icon: 'airplay',
fillAlpha: 0, fillAlpha: 0,
strokeAlpha: 0, strokeAlpha: 0,
iconColor: 0xdd0000, iconColor: 0xdd0000,
iconColorActive: 0x00dd00, iconColorActive: 0x00dd00,
fillActiveAlpha: 0, fillActiveAlpha: 0,
strokeActiveAlpha: 0, strokeActiveAlpha: 0,
type: 'checkbox' type: 'checkbox'
}) })
const button13 = new Button({ const button13 = new Button({
x: 50, x: 50,
y: 190, y: 190,
label: 'Button', label: 'Button',
fillAlpha: 0, fillAlpha: 0,
strokeAlpha: 0, strokeAlpha: 0,
fillActiveAlpha: 0, fillActiveAlpha: 0,
strokeActiveAlpha: 0, strokeActiveAlpha: 0,
textStyle: { textStyle: {
fontSize: 20, fontSize: 20,
stroke: 'brown', stroke: 'brown',
fill: 'orange', fill: 'orange',
strokeThickness: 4, strokeThickness: 4,
miterLimit: 1, miterLimit: 1,
letterSpacing: 6 letterSpacing: 6
}, },
textStyleActive: { textStyleActive: {
fontSize: 20, fontSize: 20,
stroke: 'orange', stroke: 'orange',
fill: 'brown', fill: 'brown',
strokeThickness: 4, strokeThickness: 4,
fontWeight: 'bold', fontWeight: 'bold',
miterLimit: 1, miterLimit: 1,
letterSpacing: 5 letterSpacing: 5
}, },
type: 'checkbox' type: 'checkbox'
}) })
const button14 = new Button({ const button14 = new Button({
x: 10, x: 10,
y: 250, y: 250,
label: 'Button', label: 'Button',
type: 'checkbox', type: 'checkbox',
icon: null, icon: null,
iconActive: 'add_circle' iconActive: 'add_circle'
}) })
const button15 = new Button({ const button15 = new Button({
x: 200, x: 200,
y: 250, y: 250,
label: 'Button', label: 'Button',
type: 'checkbox', type: 'checkbox',
icon: 'add_circle', icon: 'add_circle',
iconActive: null iconActive: null
}) })
const button16 = new Button({ const button16 = new Button({
x: 400, x: 400,
y: 250, y: 250,
label: 'Button', label: 'Button',
type: 'checkbox', type: 'checkbox',
icon: null, icon: null,
iconActive: 'add_circle', iconActive: 'add_circle',
active: true active: true
}) })
const button17 = new Button({ const button17 = new Button({
x: 600, x: 600,
y: 250, y: 250,
label: 'Button', label: 'Button',
type: 'checkbox', type: 'checkbox',
icon: 'add_circle', icon: 'add_circle',
iconActive: null, iconActive: null,
active: true active: true
}) })
let graphic1 = new PIXI.Graphics() let graphic1 = new PIXI.Graphics()
graphic1.beginFill(0xd7a3f9) graphic1.beginFill(0xd7a3f9)
graphic1.drawCircle(10, 10, 10) graphic1.drawCircle(10, 10, 10)
let graphic2 = new PIXI.Graphics() let graphic2 = new PIXI.Graphics()
graphic2.beginFill(0x40c3f2) graphic2.beginFill(0x40c3f2)
graphic2.drawCircle(30, 30, 30) graphic2.drawCircle(30, 30, 30)
const button18 = new Button({ const button18 = new Button({
x: 10, x: 10,
y: 310, y: 310,
label: 'Button', label: 'Button',
type: 'checkbox', type: 'checkbox',
icon: graphic1, icon: graphic1,
iconActive: graphic2 iconActive: graphic2
}) })
let graphic3 = new PIXI.Graphics() let graphic3 = new PIXI.Graphics()
graphic3.beginFill(0xfd6b6a) graphic3.beginFill(0xfd6b6a)
graphic3.drawCircle(2, 2, 2) graphic3.drawCircle(2, 2, 2)
let graphic4 = new PIXI.Graphics() let graphic4 = new PIXI.Graphics()
graphic4.beginFill(0xf8ce2d) graphic4.beginFill(0xf8ce2d)
graphic4.drawCircle(40, 40, 40) graphic4.drawCircle(40, 40, 40)
const button19 = new Button({ const button19 = new Button({
x: 200, x: 200,
y: 310, y: 310,
label: 'Button', label: 'Button',
type: 'checkbox', type: 'checkbox',
icon: graphic3, icon: graphic3,
iconActive: graphic4, iconActive: graphic4,
active: true, active: true,
iconPosition: 'right' iconPosition: 'right'
}) })
const button20 = new Button({ const button20 = new Button({
x: 400, x: 400,
y: 310, y: 310,
label: 'Link Button', label: 'Link Button',
type: 'checkbox', type: 'checkbox',
style: 'link' style: 'link',
}) action: (event) => {
console.log('Link button clicked')
}
})
const button21 = new Button({ const button21 = new Button({
x: 600, x: 600,
y: 310, y: 310,
minWidth: 70, minWidth: 70,
minHeight: 70, minHeight: 70,
icon: 'loop', icon: 'loop',
type: 'checkbox', type: 'checkbox',
style: 'link' style: 'link'
}) })
const button22 = new Button({ const button22 = new Button({
x: 10, x: 10,
y: 440, y: 440,
icon: 'play_arrow', icon: 'play_arrow',
badge: '19' badge: '19'
}) })
const button23 = new Button({ const button23 = new Button({
x: 100, x: 100,
y: 440, y: 440,
icon: 'stop', icon: 'stop',
badge: 'Stop' badge: 'Stop'
}) })
const button24 = new Button({ const button24 = new Button({
x: 200, x: 200,
y: 440, y: 440,
icon: 'star_border', icon: 'star_border',
badge: { badge: {
content: 'Bookmark', content: 'Bookmark',
align: 'center', align: 'center',
verticalAlign: 'bottom', verticalAlign: 'bottom',
offsetTop: 8, offsetTop: 8,
radius: 14, radius: 14,
fill: 0xfe832d fill: 0xfe832d
} }
}) })
const button25 = new Button({ const button25 = new Button({
x: 300, x: 300,
y: 460, y: 460,
icon: 'add', icon: 'add',
badge: { badge: {
content: 'Sweden', content: 'Sweden',
align: 'center', align: 'center',
verticalAlign: 'top', verticalAlign: 'top',
offsetTop: -20, offsetTop: -20,
radius: 12, radius: 12,
fill: 0x5856d6 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'] const countries = [
setInterval(() => { 'Tajikistan',
button25.badge.content = countries[Math.floor(Math.random() * countries.length)] 'Zambia',
button25.layout() 'Dominica',
}, 1000) '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({ const button26 = new Button({
x: 10, x: 10,
y: 520, y: 520,
label: 'add', label: 'add',
type: 'checkbox', type: 'checkbox',
strokeActive: 0x28a745, strokeActive: 0x28a745,
textStyleActive: { textStyleActive: {
fill: 0x28a745 fill: 0x28a745
}, },
textAlpha: .2, textAlpha: 0.2,
textActiveAlpha: .6 textActiveAlpha: 0.6
}) })
app.scene.addChild(button1, button2, button3, button4, button5, button6) app.scene.addChild(button1, button2, button3, button4, button5, button6)
app.scene.addChild(button7, button8) app.scene.addChild(button7, button8)
app.scene.addChild(button9, button10, button11) app.scene.addChild(button9, button10, button11)
app.scene.addChild(button12, button13) app.scene.addChild(button12, button13)
app.scene.addChild(button14, button15, button16, button17) app.scene.addChild(button14, button15, button16, button17)
app.scene.addChild(button18, button19, button20, button21) app.scene.addChild(button18, button19, button20, button21)
app.scene.addChild(button22, button23, button24, button25) app.scene.addChild(button22, button23, button24, button25)
app.scene.addChild(button26) app.scene.addChild(button26)
</script> </script>
</body> </body>
</html> </html>
+23 -21
View File
@@ -113,6 +113,8 @@ export default class Button extends PIXI.Container {
const theme = Theme.fromString(opts.theme) const theme = Theme.fromString(opts.theme)
this.theme = theme this.theme = theme
this.HIDDEN_ALPHA = 0.000000001 // WORKAROUND: See https://github.com/pixijs/pixi.js/wiki/v5-Migration-Guide#graphics-interaction
this.opts = Object.assign( this.opts = Object.assign(
{}, {},
{ {
@@ -154,7 +156,7 @@ export default class Button extends PIXI.Container {
verticalAlign: 'middle', verticalAlign: 'middle',
tooltip: null, tooltip: null,
badge: null, badge: null,
visible: true visible: true,
}, },
opts opts
) )
@@ -172,10 +174,10 @@ export default class Button extends PIXI.Container {
if (this.opts.style === 'link') { if (this.opts.style === 'link') {
Object.assign(this.opts, { Object.assign(this.opts, {
strokeAlpha: 0, strokeAlpha: this.HIDDEN_ALPHA,
strokeActiveAlpha: 0, strokeActiveAlpha: this.HIDDEN_ALPHA,
fillAlpha: 0, fillAlpha: this.HIDDEN_ALPHA,
fillActiveAlpha: 0 fillActiveAlpha: this.HIDDEN_ALPHA,
}) })
} }
@@ -247,15 +249,15 @@ export default class Button extends PIXI.Container {
// interaction // interaction
//----------------- //-----------------
this.button.on('pointerover', e => { this.button.on('pointerover', (e) => {
this.capture(e) this.capture(e)
TweenLite.to([this.button, this.content], this.theme.fast, { TweenLite.to([this.button, this.content], this.theme.fast, {
alpha: 0.83, alpha: 0.83,
overwrite: 'none' overwrite: 'none',
}) })
}) })
this.button.on('pointermove', e => { this.button.on('pointermove', (e) => {
this.capture(e) this.capture(e)
}) })
@@ -266,17 +268,17 @@ export default class Button extends PIXI.Container {
this.button.on('scroll', this.onEnd.bind(this)) this.button.on('scroll', this.onEnd.bind(this))
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
this.button.on('pointerdown', e => { this.button.on('pointerdown', (e) => {
//this.capture(e) //this.capture(e)
this.__start.x = e.data.global.x this.__start.x = e.data.global.x
this.__start.y = e.data.global.y this.__start.y = e.data.global.y
TweenLite.to([this.button, this.content], this.theme.fast, { TweenLite.to([this.button, this.content], this.theme.fast, {
alpha: 0.7, alpha: 0.7,
overwrite: 'none' overwrite: 'none',
}) })
}) })
this.button.on('pointerup', e => { this.button.on('pointerup', (e) => {
this.capture(e) this.capture(e)
const distance = Points.distance(e.data.global, this.__start) const distance = Points.distance(e.data.global, this.__start)
@@ -286,19 +288,19 @@ export default class Button extends PIXI.Container {
this.opts.beforeAction.call(this, e, this) this.opts.beforeAction.call(this, e, this)
} }
if (this.opts.action) {
this.opts.action.call(this, e, this)
}
TweenLite.to([this.button, this.content], this.theme.fast, { TweenLite.to([this.button, this.content], this.theme.fast, {
alpha: 0.83, alpha: 0.83,
overwrite: 'none' overwrite: 'none',
}) })
if (this.opts.type === 'checkbox') { if (this.opts.type === 'checkbox') {
this.active = !this.active this.active = !this.active
} }
if (this.opts.action) {
this.opts.action.call(this, e, this)
}
if (this.opts.afterAction) { if (this.opts.afterAction) {
this.opts.afterAction.call(this, e, this) this.opts.afterAction.call(this, e, this)
} }
@@ -319,7 +321,7 @@ export default class Button extends PIXI.Container {
if (typeof this.opts.tooltip === 'string') { if (typeof this.opts.tooltip === 'string') {
this.tooltip = new Tooltip({ this.tooltip = new Tooltip({
object: this, object: this,
content: this.opts.tooltip content: this.opts.tooltip,
}) })
} else { } else {
this.opts.tooltip = Object.assign({}, { object: this }, this.opts.tooltip) this.opts.tooltip = Object.assign({}, { object: this }, this.opts.tooltip)
@@ -336,7 +338,7 @@ export default class Button extends PIXI.Container {
align: 'right', align: 'right',
verticalAlign: 'top', verticalAlign: 'top',
offsetLeft: 0, offsetLeft: 0,
offsetTop: 0 offsetTop: 0,
} }
) )
if (typeof this.opts.badge === 'string') { if (typeof this.opts.badge === 'string') {
@@ -640,8 +642,8 @@ export default class Button extends PIXI.Container {
hide() { hide() {
this.opts.strokeAlpha = 0.0 this.opts.strokeAlpha = 0.0
this.opts.strokeActiveAlpha = 0.0 this.opts.strokeActiveAlpha = 0.0
this.opts.fillAlpha = 0.0000000001 // WORKAROUND: See https://github.com/pixijs/pixi.js/wiki/v5-Migration-Guide#graphics-interaction this.opts.fillAlpha = this.HIDDEN_ALPHA
this.opts.fillActiveAlpha = 0.0000000001 this.opts.fillActiveAlpha = this.HIDDEN_ALPHA
this.layout() this.layout()
@@ -714,7 +716,7 @@ export default class Button extends PIXI.Container {
this.capture(event) this.capture(event)
TweenLite.to([this.button, this.content], this.theme.fast, { TweenLite.to([this.button, this.content], this.theme.fast, {
alpha: 1, alpha: 1,
overwrite: 'none' overwrite: 'none',
}) })
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

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

After

Width:  |  Height:  |  Size: 508 KiB

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

After

Width:  |  Height:  |  Size: 688 KiB

+195 -188
View File
@@ -1,219 +1,226 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<head> <title>DeepZoomImage Doctests</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<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"> <script src="../../3rdparty/highlight/highlight.pack.js"></script>
<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.3rdparty.js"></script> <script src="../../../dist/iwmlib.js"></script>
<script src="../../../dist/iwmlib.pixi.js"></script>
<script src="../../../dist/iwmlib.js"></script> <style>
<script src="../../../dist/iwmlib.pixi.js"></script> #app {
display: table;
margin: 0 auto;
}
<style> #app > * {
#app { margin-bottom: 5px;
display: table; }
margin: 0 auto; </style>
} </head>
#app > * { <body onload="Doctest.run()">
margin-bottom: 5px; <h1>
} <a href="../../index.html">lib.</a><a href="../index.html">pixi.</a><a href="index.html">deepzoom.</a>
</style> DeepZoom
</head> </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()"> // app
<h1>Double DeepZoomImage</h1> //--------------------
<p> window.app = new PIXIApp({
The main class of a deeply zoomable image that is represented by a hierarchy of tile layers for each zoom level. This gives width: 400,
the user the impression that even huge pictures (up to gigapixel-images) can be zoomed instantaneously, since the height: 500,
tiles at smaller levels are scaled immediately and overloaded by more detailed tiles at the larger level as fast backgroundColor: 0xffcccccc
as possible. })
</p> .setup()
<br /> .run()
<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 div1.appendChild(app.view)
//--------------------
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}"
// })
// app // create the ScatterContainer
//-------------------- //--------------------
window.app = new PIXIApp({ const scatterContainer1 = new ScatterContainer(app.renderer, { showBounds: true, app: app })
width: 400, app.scene.addChild(scatterContainer1)
height: 500,
backgroundColor: 0xFFCCCCCC
}).setup().run()
div1.appendChild(app.view)
// create the ScatterContainer // Create the DeepZoomImage
//-------------------- //--------------------
const scatterContainer1 = new ScatterContainer(app.renderer, {showBounds: true, app: app}) setTimeout(() => {
app.scene.addChild(scatterContainer1) 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 scatterContainer1.addChild(deepZoomImage1)
//-------------------- }, 1000)
setTimeout(() => {
const deepZoomImage1 = new DeepZoomImage(deepZoomInfo, {app, world: scatterContainer1}) // app2
deepZoomImage1.scatter = new DisplayObjectScatter(deepZoomImage1, app.renderer, { //--------------------
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, minScale: 0,
maxScale: 50, maxScale: 100,
onTransform: event => { onTransform: (event) => {
//console.log('currentLevel', deepZoomImage1.currentLevel) deepZoomImage2.transformed(event)
deepZoomImage1.transformed(event)
} }
}) })
scatterContainer1.addChild(deepZoomImage1) scatterContainer2.addChild(deepZoomImage2)
}, 1000) </script>
// app2
//--------------------
const app2 = new PIXIApp({
width: 400,
height: 500,
backgroundColor: 0xFFCCCCCC
}).setup().run()
div2.appendChild(app2.view)
// create the ScatterContainer <h1>DeepZoomImage in DeepZoomImage</h1>
//-------------------- <p>
const scatterContainer2 = new ScatterContainer(app2.renderer, {showBounds: true, app: app2}) The main class of a deeply zoomable image that is represented by a hierarchy of tile layers for each zoom
app2.scene.addChild(scatterContainer2) 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 window.app3 = app3
//--------------------
const deepZoomImage2 = new DeepZoomImage(deepZoomInfo, {app: app2})
deepZoomImage2.scatter = new DisplayObjectScatter(deepZoomImage2, app2.renderer, {
minScale: 0,
maxScale: 100,
onTransform: (event) => {
deepZoomImage2.transformed(event)
}
})
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> // Create the DeepZoomImage
<p> //--------------------
The main class of a deeply zoomable image that is represented by a hierarchy of tile layers for each zoom level. This gives const deepZoomImage3 = new DeepZoomImage(deepZoomInfo, { app: app3 })
the user the impression that even huge pictures (up to gigapixel-images) can be zoomed instantaneously, since the deepZoomImage3.scatter = new DisplayObjectScatter(deepZoomImage3, app3.renderer, {
tiles at smaller levels are scaled immediately and overloaded by more detailed tiles at the larger level as fast minScale: 0,
as possible. maxScale: 100,
</p> startScale: 2,
<br /> autoBringToFront: false,
<div id="div3"></div> onTransform: (event) => {
<script class="doctest"> deepZoomImage3.transformed(event)
}
})
// app3 app3._deepZoomImage3 = deepZoomImage3
//--------------------
const app3 = new PIXIApp({
width: 900,
height: 500,
backgroundColor: 0xFFCCCCCC
}).setup().run()
window.app3 = app3 scatterContainer3.addChild(deepZoomImage3)
div3.appendChild(app3.view)
// create the ScatterContainer // Create the second DeepZoomImage
//-------------------- //--------------------
const scatterContainer3 = new ScatterContainer(app3.renderer, {app: app3, showBounds: true, claimEvent: false, stopEvents: false}) const border = new PIXI.Graphics()
app3.scene.addChild(scatterContainer3) border.beginFill(0x282828, 1)
border.drawRect(0, 0, 264, 244)
scatterContainer3.addChild(border)
// Create the DeepZoomImage const mask = new PIXI.Graphics()
//-------------------- mask.beginFill(0x282828, 1)
const deepZoomImage3 = new DeepZoomImage(deepZoomInfo, {app: app3}) mask.drawRect(0, 0, 260, 240)
deepZoomImage3.scatter = new DisplayObjectScatter(deepZoomImage3, app3.renderer, { scatterContainer3.addChild(mask)
minScale: 0,
maxScale: 100,
startScale: 2,
autoBringToFront: false,
onTransform: (event) => {
deepZoomImage3.transformed(event)
}
})
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) app3._deepZoomImage4 = deepZoomImage4
// 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>
scatterContainer3.addChild(deepZoomImage4)
</script>
</body>
</html> </html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

+119 -127
View File
@@ -1,143 +1,135 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<head> <title>DeepZoomImage Doctests</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<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"> <script src="../../3rdparty/highlight/highlight.pack.js"></script>
<link rel="stylesheet" href="../../../css/doctest.css"> <script src="../../../dist/iwmlib.3rdparty.js"></script>
<script src="../../3rdparty/highlight/highlight.pack.js"></script> <script src="../../../dist/iwmlib.js"></script>
<script src="../../../dist/iwmlib.3rdparty.js"></script> <script src="../../../dist/iwmlib.pixi.js"></script>
<script src="../../../dist/iwmlib.js"></script> <style>
<script src="../../../dist/iwmlib.pixi.js"></script> #app {
display: table;
<style> margin: 0 auto;
#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)
} }
#app > * {
margin-bottom: 5px;
}
</style>
</head>
//A new canvas has to be created <body onload="Doctest.run()">
//for the new view. <h1>
var canvas = document.createElement("canvas") <a href="../../index.html">lib.</a><a href="../index.html">pixi.</a
canvas_container.appendChild(canvas); ><a href="index.html">deepzoom.</a>DeepZoomImage
</h1>
app = new ScatterApp({ <p>
resolution: state + 1, 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
//Default parameters instantaneously, since the tiles at smaller levels are scaled immediately and overloaded by more detailed
view: canvas, tiles at the larger level as fast as possible.
autoResize: false, </p>
width: 128, <br />
height: 128, <div id="app">
backgroundColor: 0xFFCCCCCC <button id="change_dpr">Change Pixel Ratio</button>
}).setup().run() <div id="canvas_container"></div>
<div id="info"></div>
</div>
// To create a DeepZoomImage, a DeepZoomInfo has to <script class="doctest">
// be provided. It contains all the necessary informations // When an element is added, the ScatterApp wrapps it in it's own Scatter Container.
// for the DeepZoomImage, to behave as intended. // Just as in the doctest: scatter.html
// (E.g. that it displays the right level of tiles for the current view distance.) class ScatterApp extends PIXIApp {
sceneFactory() {
deepZoomInfo = new DeepZoomInfo( return new ScatterContainer(this.renderer, { showBounds: true, app: this })
{
"tileSize": 128,
"format": "jpg",
"overlap": 0,
"type": "map",
"height": 4096,
"width": 4096,
"path": "../assets/maps/test",
"urlTileTemplate": "{path}/{level}/{column}/{row}.{format}"
});
// Create the DeepZoomImage
deepZoomImage = new DeepZoomImage(deepZoomInfo, {
highResolution: !!state,
app
});
deepZoomImage.scatter = new DisplayObjectScatter(deepZoomImage, app.renderer, {
// Allow more flexible scaling for debugging purposes.
minScale: 0,
maxScale: 100,
// Notify the DeepZoomImage, when it's container has
// been transformed (translated, scaled, rotated, ...)
onTransform: (event) => {
deepZoomImage.transformed(event)
} }
}); }
let app
let state = 0
// Add the DeepZoomImage to the scene. //Destroys the PIXIApp element and the corresponding canvas,
app.scene.addChild(deepZoomImage) //to reinstantiate the entire application.
//Set info text.
info.innerHTML = "resolution: " + app.renderer.resolution +
"<br>high resolution: " + !!state;
}
// Add functionality to the button.
change_dpr.addEventListener("click", (event) => {
state = (state + 1) % 2
changePIXI() changePIXI()
})
</script> function changePIXI() {
</body> 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)
}
//A new canvas has to be created
//for the new view.
var canvas = document.createElement('canvas')
canvas_container.appendChild(canvas)
app = new ScatterApp({
resolution: state + 1,
//Default parameters
view: canvas,
autoResize: false,
width: 128,
height: 128,
backgroundColor: 0xffcccccc
})
.setup()
.run()
// To create a DeepZoomImage, a DeepZoomInfo has to
// be provided. It contains all the necessary informations
// for the DeepZoomImage, to behave as intended.
// (E.g. that it displays the right level of tiles for the current view distance.)
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}'
})
// Create the DeepZoomImage
deepZoomImage = new DeepZoomImage(deepZoomInfo, {
highResolution: !!state,
app
})
deepZoomImage.scatter = new DisplayObjectScatter(deepZoomImage, app.renderer, {
// Allow more flexible scaling for debugging purposes.
minScale: 0,
maxScale: 100,
// Notify the DeepZoomImage, when it's container has
// been transformed (translated, scaled, rotated, ...)
onTransform: (event) => {
deepZoomImage.transformed(event)
}
})
// Add the DeepZoomImage to the scene.
app.scene.addChild(deepZoomImage)
//Set info text.
info.innerHTML = 'resolution: ' + app.renderer.resolution + '<br>high resolution: ' + !!state
}
// Add functionality to the button.
change_dpr.addEventListener('click', (event) => {
state = (state + 1) % 2
changePIXI()
})
</script>
</body>
</html> </html>
+20
View File
@@ -887,6 +887,26 @@ export class DeepZoomImage extends PIXI.Container {
}) })
} }
/** Destroys all tiles and laywer above the current level to ensure that the memory can
* be reused. This must be called from time to time to free load worker which otherwise would
* consume memory and threads permanentely.
*/
destroyUnusedTilesAndLayers() {
this.destroyTilesAboveLevel(this.currentLevel)
let candidates = []
Object.keys(this.tileLayers).forEach(key => {
let tiles = this.tileLayers[key]
if (tiles.level > this.currentLevel) {
candidates.push(key)
}
})
for (let key of candidates) {
let tiles = this.tileLayers[key]
tiles.destroy()
delete this.tileLayers[key]
}
}
/** /**
* Tint tiles in all layers that are no longer needed * Tint tiles in all layers that are no longer needed
* *
Binary file not shown.

After

Width:  |  Height:  |  Size: 650 KiB

+40 -33
View File
@@ -1,38 +1,45 @@
<html> <html>
<head> <head>
<title>PIXI Lib Doctests</title> <title>PIXI Lib Doctests</title>
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0" /> <meta
<link rel="stylesheet" href="../../../css/index.css"> name="viewport"
content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0"
/>
<link rel="stylesheet" href="../../../css/index.css" />
<script src="../../../dist/iwmlib.js"></script> <script src="../../../dist/iwmlib.js"></script>
<template id="itemTemplate"> <template id="itemTemplate">
<a class="wrapper" href=""> <a class="wrapper" href="">
<div class="preview"> <div class="preview">
<div class="thumbnail-container"> <div class="thumbnail-container">
<div class="thumbnail"> <div class="thumbnail">
<img class="icon" > <img class="icon" />
<!-- <iframe src="" frameborder="0"></iframe> --> <!-- <iframe src="" frameborder="0"></iframe> -->
</div> </div>
</div>
<div class="title"></div>
</div> </div>
<div class="title"></div> </a>
</div> </template>
</a> </head>
</template> <body>
</head> <h2>
<body> <a href="../../index.html">lib.</a><a href="../index.html">pixi.</a
<div id="container" class="container"> ><a href="../index.html">deepzoom.</a>Doctests
<a style="position: absolute; left: 22px; top: 12px;" target="_blank" href="http://www.iwm-tuebingen.de">IWM</a> </h2>
</div> <div id="container" class="container"></div>
<script> <script>
const index = new Index(itemTemplate, [ const index = new Index(
['Deepzoom', 'deepzoom.html'], itemTemplate,
['Image', 'image.html'], [
['Deepzoom', 'deepzoom.html'],
], ['Image', 'image.html']
null) ],
index.load() null
</script> )
</body> index.load()
</script>
</body>
</html> </html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

+5 -3
View File
@@ -85,8 +85,8 @@ export class PIXITileLoader extends TileLoader {
super(tiles) super(tiles)
this.destroyed = false this.destroyed = false
this.loader = new PIXI.Loader() this.loader = new PIXI.Loader()
this.loader.on('load', this._onLoaded.bind(this)) this.loader.onLoad.add(this._onLoaded.bind(this))
this.loader.on('error', this._onError.bind(this)) this.loader.onError.add(this._onError.bind(this))
if (compression) { if (compression) {
this.loader.use(PIXI.compressedTextures.ImageParser.use) this.loader.use(PIXI.compressedTextures.ImageParser.use)
} }
@@ -317,7 +317,7 @@ export class RequestTileLoader extends TileLoader {
export class WorkerTileLoader extends TileLoader { export class WorkerTileLoader extends TileLoader {
constructor(tiles, workerPath) { constructor(tiles, workerPath) {
super(tiles) super(tiles)
this.debug = false
let worker = (this.worker = new Worker(workerPath)) let worker = (this.worker = new Worker(workerPath))
worker.onmessage = event => { worker.onmessage = event => {
@@ -353,11 +353,13 @@ export class WorkerTileLoader extends TileLoader {
} }
cancel() { cancel() {
if (this.debug) console.log('canceling worker')
super.cancel() super.cancel()
this.worker.postMessage({ command: 'abort' }) this.worker.postMessage({ command: 'abort' })
} }
destroy() { destroy() {
if (this.debug) console.log('destroying worker')
this.worker.postMessage({ command: 'abort' }) this.worker.postMessage({ command: 'abort' })
this.worker.terminate() this.worker.terminate()
this.worker = null this.worker = null
Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 148 KiB

+31 -6
View File
@@ -1,31 +1,54 @@
let loadQueue = [] let loadQueue = []
let requestPool = []
let pendingRequests = new Map() let pendingRequests = new Map()
let requestCount = 0
const batchSize = 8 const batchSize = 8
const debug = false const debug = false
function recycledXMLHttpRequest() {
// https://nullprogram.com/blog/2013/02/08/
if (requestPool.length > 0) {
return requestPool.pop()
}
requestCount += 1
if (debug) console.log('create XMLHttpRequest', requestCount)
let xhr = new XMLHttpRequest()
return xhr
}
function load() { function load() {
while (loadQueue.length > 0 && pendingRequests.size < batchSize) { while (loadQueue.length > 0 && pendingRequests.size < batchSize) {
let tile = loadQueue.shift() let tile = loadQueue.shift()
let [col, row, url] = tile let [col, row, url] = tile
let xhr = new XMLHttpRequest() let xhr = recycledXMLHttpRequest()
xhr.responseType = 'arraybuffer'
xhr.onload = event => { xhr.onload = event => {
pendingRequests.delete(url)
let buffer = xhr.response let buffer = xhr.response
postMessage({ success: true, url, col, row, buffer }, [buffer]) postMessage({ success: true, url, col, row, buffer }, [buffer])
pendingRequests.delete(url)
} }
xhr.onerror = event => { xhr.onerror = event => {
pendingRequests.delete(url)
let buffer = null let buffer = null
postMessage({ success: false, url, col, row, buffer }) postMessage({ success: false, url, col, row, buffer })
pendingRequests.delete(url)
} }
xhr.onreadystatechange = () => {
// In local files, status is 0 upon success in Mozilla Firefox
if (xhr.readyState === XMLHttpRequest.DONE) {
pendingRequests.delete(url)
requestPool.push(xhr)
if (debug) console.log('resuse XMLHttpRequest')
}
}
if (debug) console.log('open XMLHttpRequest')
xhr.open('GET', url, true) xhr.open('GET', url, true)
xhr.responseType = 'arraybuffer'
if (debug) console.log('send XMLHttpRequest')
xhr.send() xhr.send()
pendingRequests.set(url, xhr) pendingRequests.set(url, xhr)
} }
if (loadQueue.length > 0) setTimeout(load, 1000 / 120) if (loadQueue.length > 0) setTimeout(load, 1000 / 120)
else { else {
if (debug) console.log('Ready') if (debug) console.log('tileloader ready')
} }
} }
@@ -40,10 +63,12 @@ self.onmessage = event => {
break break
case 'abort': case 'abort':
loadQueue = [] loadQueue = []
requestPool = []
for (let xhr of pendingRequests.values()) { for (let xhr of pendingRequests.values()) {
xhr.abort() xhr.abort()
} }
if (debug) console.log('Abort') pendingRequests.clear()
if (debug) console.log('tileloader aborted')
break break
default: default:
console.warn('Unknown worker command: ' + msg.command) console.warn('Unknown worker command: ' + msg.command)
+3 -3
View File
@@ -51,7 +51,7 @@ export class Tiles extends PIXI.Container {
this.infoColor = Colors.random() this.infoColor = Colors.random()
this.pprint() this.pprint()
this.destroyed = false //this.destroyed = false
} }
/** Tests whether all tiles are loaded. **/ /** Tests whether all tiles are loaded. **/
@@ -169,7 +169,7 @@ export class Tiles extends PIXI.Container {
if (this.showGrid) { if (this.showGrid) {
this.highlightTile(refCol, refRow) this.highlightTile(refCol, refRow)
} }
urlpos.forEach(d => { urlpos.forEach((d) => {
let [url, col, row] = d let [url, col, row] = d
if (this.loader.schedule(url, col, row)) { if (this.loader.schedule(url, col, row)) {
if (onlyone) { if (onlyone) {
@@ -229,7 +229,7 @@ export class Tiles extends PIXI.Container {
* cancelled. * cancelled.
**/ **/
destroy() { destroy() {
this.destroyed = true //this.destroyed = true
this.loader.destroy() this.loader.destroy()
super.destroy({ children: true }) // Calls destroyChildren super.destroy({ children: true }) // Calls destroyChildren
this.available.clear() this.available.clear()
+5 -9
View File
@@ -16,12 +16,12 @@
<div class="flipWrapper"> <div class="flipWrapper">
<div class="flipCard"> <div class="flipCard">
<img class="flipFace front" src="" /> <img class="flipFace front" src="" />
<div class="flipFace back" style="visibility:hidden;"></div> <div class="flipFace back" style="visibility: hidden"></div>
</div> </div>
<!-- Very tricky problem to scale svgs: see https://css-tricks.com/scale-svg/ --> <!-- Very tricky problem to scale svgs: see https://css-tricks.com/scale-svg/ -->
<svg <svg
class="flipButton backBtn" class="flipButton backBtn"
style="visibility:hidden;" style="visibility: hidden"
viewBox="0 0 100 100" viewBox="0 0 100 100"
preserveAspectRatio="xMidYMid meet" preserveAspectRatio="xMidYMid meet"
> >
@@ -41,9 +41,7 @@
</template> </template>
</head> </head>
<body onload="Doctest.run()"> <body onload="Doctest.run()">
<h1> <h1><a href="../index.html">lib.</a><a href="index.html">pixi.</a>Flip Effect</h1>
Flip Effect
</h1>
<p> <p>
The flip effect, which simulates a flip between a front and back view of an object by means of a 3D The flip effect, which simulates a flip between a front and back view of an object by means of a 3D
rotation, is an interaction between a PIXI scatter object and a DOM Flip effect. The PIXI scatter is used as rotation, is an interaction between a PIXI scatter object and a DOM Flip effect. The PIXI scatter is used as
@@ -63,10 +61,8 @@
</li> </li>
<li>If the back card is closed, the DOM nodes are removed and the PIXI scatter is shown again.</li> <li>If the back card is closed, the DOM nodes are removed and the PIXI scatter is shown again.</li>
</ul> </ul>
<h3> <h3>Example</h3>
Example <main id="main" style="border: 1px solid red; position: relative">
</h3>
<main id="main" style="border: 1px solid red; position: relative;">
<canvas id="canvas" class="grayBorder interactive">Canvas not supported</canvas> <canvas id="canvas" class="grayBorder interactive">Canvas not supported</canvas>
</main> </main>
<script class="doctest"> <script class="doctest">
+11 -11
View File
@@ -47,7 +47,7 @@ export class ScatterLoader extends CardLoader {
width: w, width: w,
height: h, height: h,
view: canvas, view: canvas,
resolution: resolution resolution: resolution,
}) })
let displayObject = this.scatter.displayObject let displayObject = this.scatter.displayObject
@@ -88,7 +88,7 @@ export class ScatterLoader extends CardLoader {
let image = isImage ? domNode : document.createElement('img') let image = isImage ? domNode : document.createElement('img')
let [x, y, w, h, cloneURL] = this.cloneScatterImage() let [x, y, w, h, cloneURL] = this.cloneScatterImage()
let [ww, hh] = this.unscaledSize() let [ww, hh] = this.unscaledSize()
image.onload = e => { image.onload = (e) => {
if (!isImage) domNode.appendChild(image) if (!isImage) domNode.appendChild(image)
this.x = x this.x = x
this.y = y this.y = y
@@ -98,7 +98,7 @@ export class ScatterLoader extends CardLoader {
this.rotation = this.scatter.rotation this.rotation = this.scatter.rotation
resolve(this) resolve(this)
} }
image.onerror = e => { image.onerror = (e) => {
reject(this) reject(this)
} }
image.src = cloneURL image.src = cloneURL
@@ -113,7 +113,7 @@ export default class FlipEffect {
this.backLoader = backLoader this.backLoader = backLoader
this.scatterLoader = new ScatterLoader(scatter) this.scatterLoader = new ScatterLoader(scatter)
this.domFlip = new DOMFlip(domScatterContainer, flipTemplate, this.scatterLoader, backLoader, { this.domFlip = new DOMFlip(domScatterContainer, flipTemplate, this.scatterLoader, backLoader, {
onBack: this.backCardClosed.bind(this) onBack: this.backCardClosed.bind(this),
}) })
this.setupInfoButton() this.setupInfoButton()
} }
@@ -121,7 +121,7 @@ export default class FlipEffect {
startFlip() { startFlip() {
let center = this.flipCenter() let center = this.flipCenter()
let loader = this.backLoader let loader = this.backLoader
this.domFlip.load().then(domFlip => { this.domFlip.load().then((domFlip) => {
this.scatter.displayObject.visible = false this.scatter.displayObject.visible = false
domFlip.centerAt(center) domFlip.centerAt(center)
domFlip.zoom(this.scatter.scale) domFlip.zoom(this.scatter.scale)
@@ -176,10 +176,10 @@ export default class FlipEffect {
this.infoBtn.lineTo(0, 14) this.infoBtn.lineTo(0, 14)
this.infoBtn.endFill() this.infoBtn.endFill()
this.infoBtn.on('click', e => { this.infoBtn.on('click', (e) => {
this.infoSelected() this.infoSelected()
}) })
this.infoBtn.on('tap', e => { this.infoBtn.on('tap', (e) => {
this.infoSelected() this.infoSelected()
}) })
@@ -200,7 +200,7 @@ export default class FlipEffect {
displayObject.addChild(this.infoBtn) displayObject.addChild(this.infoBtn)
} }
this.scatter.addTransformEventCallback(e => { this.scatter.addTransformEventCallback((e) => {
let displayObject = this.scatter.displayObject let displayObject = this.scatter.displayObject
if (displayObject.foreground) { if (displayObject.foreground) {
if (e.scale) { if (e.scale) {
@@ -217,7 +217,7 @@ export default class FlipEffect {
let canvas = document.createElement('canvas') let canvas = document.createElement('canvas')
canvas.width = 88 * 4 canvas.width = 88 * 4
canvas.height = 44 * 4 canvas.height = 44 * 4
svgImage.onload = e => { svgImage.onload = (e) => {
let displayObject = this.scatter.displayObject let displayObject = this.scatter.displayObject
canvas.getContext('2d').drawImage(svgImage, 0, 0, canvas.width, canvas.height) canvas.getContext('2d').drawImage(svgImage, 0, 0, canvas.width, canvas.height)
let texure = new PIXI.Texture(new PIXI.BaseTexture(canvas)) let texure = new PIXI.Texture(new PIXI.BaseTexture(canvas))
@@ -234,10 +234,10 @@ export default class FlipEffect {
this.infoBtn.position = new PIXI.Point(w, h) this.infoBtn.position = new PIXI.Point(w, h)
this.infoBtn.interactive = true this.infoBtn.interactive = true
this.infoBtn.updateTransform() this.infoBtn.updateTransform()
this.infoBtn.on('click', e => { this.infoBtn.on('click', (e) => {
this.infoSelected() this.infoSelected()
}) })
this.infoBtn.on('tap', e => { this.infoBtn.on('tap', (e) => {
this.infoSelected() this.infoSelected()
}) })
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 739 KiB

+159 -155
View File
@@ -1,176 +1,180 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>PIXI Flippable</title> <title>PIXI Flippable</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"> <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.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script> <script src="../../dist/iwmlib.pixi.js"></script>
<script src="../3rdparty/gsap/src/minified/TweenMax.min.js"></script> <script src="../3rdparty/gsap/src/minified/TweenMax.min.js"></script>
</head> </head>
<body onload="Doctest.run()"> <body onload="Doctest.run()">
<h1>Flippable</h1> <h1><a href="../index.html">lib.</a><a href="index.html">pixi.</a>Flippable</h1>
<p> <p>
Using the Flippable class, any PIXI element (PIXI.DisplayObject) can be back-mounted Using the Flippable class, any PIXI element (PIXI.DisplayObject) can be back-mounted (which can include
(which can include another PIXI.DisplayObject), and turning it over to the back can another PIXI.DisplayObject), and turning it over to the back can be adjusted by many parameters in speed and
be adjusted by many parameters in speed and behavior. behavior.
</p> </p>
<p><a href="../../doc/out/Flippable.html">JavaScript API</a></p> <p><a href="../../doc/out/Flippable.html">JavaScript API</a></p>
<p>Let's look at some flippable examples:</p><br /> <p>Let's look at some flippable examples:</p>
<canvas id="canvas" class="interactive"></canvas> <br />
<p> <canvas id="canvas" class="interactive"></canvas>
What you should see: Six flippable elements and one button. <p>What you should see: Six flippable elements and one button.</p>
</p> <script class="doctest">
<script class="doctest"> const app = new PIXIApp({
const app = new PIXIApp({ view: canvas,
view: canvas, width: 900,
width: 900, height: 420,
height: 420, transparent: false
transparent: false })
}).setup().run() .setup()
.run()
app.loadTextures([ app.loadTextures(
'./assets/fruit-1.jpg', [
'./assets/fruit-2.jpg', './assets/fruit-1.jpg',
'./assets/fruit-3.jpg', './assets/fruit-2.jpg',
'./assets/fruit-4.jpg', './assets/fruit-3.jpg',
'./assets/fruit-5.jpg', './assets/fruit-4.jpg',
'./assets/fruit-6.jpg', './assets/fruit-5.jpg',
'./assets/fruit-7.jpg', './assets/fruit-6.jpg',
'./assets/fruit-8.jpg', './assets/fruit-7.jpg',
'./assets/fruit-9.jpg', './assets/fruit-8.jpg',
'./assets/fruit-10.jpg' './assets/fruit-9.jpg',
], textures => { './assets/fruit-10.jpg'
],
(textures) => {
// Example 1
//--------------------
const sprite1 = new PIXI.Sprite(textures.get('./assets/fruit-1.jpg'))
sprite1.position.set(10, 10)
sprite1.scale.set(0.2, 0.2)
const sprite2 = new PIXI.Sprite(textures.get('./assets/fruit-2.jpg'))
// Example 1 const flippable1 = new Flippable(sprite1, sprite2, app.renderer)
//--------------------
const sprite1 = new PIXI.Sprite(textures.get('./assets/fruit-1.jpg'))
sprite1.position.set(10, 10)
sprite1.scale.set(.2, .2)
const sprite2 = new PIXI.Sprite(textures.get('./assets/fruit-2.jpg'))
const flippable1 = new Flippable(sprite1, sprite2, app.renderer) // Example 2
//--------------------
const sprite3 = new PIXI.Sprite(textures.get('./assets/fruit-3.jpg'))
sprite3.position.set(10, 120)
sprite3.scale.set(0.2, 0.2)
const sprite4 = new PIXI.Sprite(textures.get('./assets/fruit-4.jpg'))
// Example 2 const flippable2 = new Flippable(sprite3, sprite4, app.renderer, {
//-------------------- duration: 6,
const sprite3 = new PIXI.Sprite(textures.get('./assets/fruit-3.jpg')) ease: Bounce.easeOut,
sprite3.position.set(10, 120) shadow: true,
sprite3.scale.set(.2, .2) eulerX: 0.8
const sprite4 = new PIXI.Sprite(textures.get('./assets/fruit-4.jpg')) })
const flippable2 = new Flippable(sprite3, sprite4, app.renderer, { // Example 3
duration: 6, //--------------------
ease: Bounce.easeOut, const sprite5 = new PIXI.Sprite(textures.get('./assets/fruit-5.jpg'))
shadow: true, sprite5.position.set(10, 240)
eulerX: .8 sprite5.scale.set(0.2, 0.2)
}) const sprite6 = new PIXI.Sprite(textures.get('./assets/fruit-6.jpg'))
sprite6.position.set(400, 180)
sprite6.scale.set(0.3, 0.3)
sprite6.rotation = 0.3
// Example 3 const flippable3 = new Flippable(sprite5, sprite6, app.renderer, {
//-------------------- duration: 4,
const sprite5 = new PIXI.Sprite(textures.get('./assets/fruit-5.jpg')) ease: Circ.easeInOut,
sprite5.position.set(10, 240) shadow: true,
sprite5.scale.set(.2, .2) useBackTransforms: true
const sprite6 = new PIXI.Sprite(textures.get('./assets/fruit-6.jpg')) })
sprite6.position.set(400, 180)
sprite6.scale.set(.3, .3)
sprite6.rotation = .3
const flippable3 = new Flippable(sprite5, sprite6, app.renderer, { // Example 4
duration: 4, //--------------------
ease: Circ.easeInOut, const sprite7 = new PIXI.Sprite(textures.get('./assets/fruit-7.jpg'))
shadow: true, sprite7.position.set(700, 10)
useBackTransforms: true sprite7.scale.set(0.2, 0.2)
}) const sprite8 = new PIXI.Sprite(textures.get('./assets/fruit-8.jpg'))
sprite8.position.set(550, 180)
sprite8.scale.set(0.15, 0.15)
sprite8.skew.set(0, 0.1)
// Example 4 const flippable4 = new Flippable(sprite7, sprite8, app.renderer, {
//-------------------- duration: 5,
const sprite7 = new PIXI.Sprite(textures.get('./assets/fruit-7.jpg')) ease: Elastic.easeOut.config(1, 0.3),
sprite7.position.set(700, 10) transformEase: Elastic.easeOut.config(1, 0.3),
sprite7.scale.set(.2, .2) shadow: true,
const sprite8 = new PIXI.Sprite(textures.get('./assets/fruit-8.jpg')) focus: 600,
sprite8.position.set(550, 180) near: 50,
sprite8.scale.set(.15, .15) far: 10000,
sprite8.skew.set(0, .1) useBackTransforms: true
})
const flippable4 = new Flippable(sprite7, sprite8, app.renderer, { // Example 5
duration: 5, //--------------------
ease: Elastic.easeOut.config(1, 0.3), const sprite9 = new PIXI.Sprite(textures.get('./assets/fruit-9.jpg'))
transformEase: Elastic.easeOut.config(1, 0.3), sprite9.position.set(700, 130)
shadow: true, sprite9.scale.set(0.2, 0.2)
focus: 600, sprite9.skew.set(0.2, 0.2)
near: 50, const sprite10 = new PIXI.Sprite(textures.get('./assets/fruit-10.jpg'))
far: 10000, sprite10.position.set(700, 130)
useBackTransforms: true sprite10.scale.set(0.2, 0.2)
}) sprite10.skew.set(-0.2, -0.2)
// Example 5 const flippable5 = new Flippable(sprite9, sprite10, app.renderer, {
//-------------------- duration: 2,
const sprite9 = new PIXI.Sprite(textures.get('./assets/fruit-9.jpg')) useBackTransforms: true
sprite9.position.set(700, 130) })
sprite9.scale.set(.2, .2)
sprite9.skew.set(.2, .2)
const sprite10 = new PIXI.Sprite(textures.get('./assets/fruit-10.jpg'))
sprite10.position.set(700, 130)
sprite10.scale.set(.2, .2)
sprite10.skew.set(-.2, -.2)
const flippable5 = new Flippable(sprite9, sprite10, app.renderer, { // Example 6
duration: 2, //--------------------
useBackTransforms: true const graphic1 = new PIXI.Graphics()
}) graphic1.position.set(700, 280)
graphic1.beginFill(0x6b6acf, 1)
graphic1.drawRect(0, 0, 160, 100)
graphic1.lineStyle(1, 0xe7bc51, 1)
for (let i = 0; i < 100; i++) {
graphic1.drawCircle(Math.random() * 160, Math.random() * 100, 2)
}
// Example 6 const graphic2 = new PIXI.Graphics()
//-------------------- graphic2.position.set(700, 280)
const graphic1 = new PIXI.Graphics() graphic2.beginFill(0xcedc9c, 1)
graphic1.position.set(700, 280) graphic2.drawRect(0, 0, 160, 100)
graphic1.beginFill(0x6b6acf, 1) for (let i = 0; i < 1000; i++) {
graphic1.drawRect(0, 0, 160, 100) graphic2.beginFill((Math.random() * 0xffffff) << 0, 1)
graphic1.lineStyle(1, 0xe7bc51, 1) graphic2.lineStyle(1, (Math.random() * 0xffffff) << 0, 1)
for (let i = 0; i < 100; i++) { graphic2.drawCircle(Math.random() * 160, Math.random() * 100, 2)
graphic1.drawCircle(Math.random() * 160, Math.random() * 100, 2) }
}
const graphic2 = new PIXI.Graphics() const flippable6 = new Flippable(graphic1, graphic2, app.renderer, {
graphic2.position.set(700, 280) eulerX: -0.3
graphic2.beginFill(0xcedc9c, 1) })
graphic2.drawRect(0, 0, 160, 100)
for (let i = 0; i < 1000; i++) {
graphic2.beginFill(Math.random() * 0xffffff << 0, 1)
graphic2.lineStyle(1, Math.random() * 0xffffff << 0, 1)
graphic2.drawCircle(Math.random() * 160, Math.random() * 100, 2)
}
const flippable6 = new Flippable(graphic1, graphic2, app.renderer, { // Button
eulerX: -.3 //--------------------
}) const button = new Button({
x: 400,
y: 10,
label: 'Toggle',
type: 'checkbox',
action: (e) => {
flippable1.toggle()
flippable2.toggle()
flippable3.toggle()
flippable4.toggle()
flippable5.toggle()
flippable6.toggle()
}
})
// Button app.scene.addChild(sprite1, sprite3, sprite5, sprite7, sprite9, graphic1, button)
//-------------------- },
const button = new Button({ { resolutionDependent: false }
x: 400, )
y: 10, </script>
label: 'Toggle', </body>
type: 'checkbox', </html>
action: e => {
flippable1.toggle()
flippable2.toggle()
flippable3.toggle()
flippable4.toggle()
flippable5.toggle()
flippable6.toggle()
}
})
app.scene.addChild(sprite1, sprite3, sprite5, sprite7, sprite9, graphic1, button)
}, {resolutionDependent: false})
</script>
</body>
+11 -11
View File
@@ -86,7 +86,7 @@ export default class Flippable extends PIXI.projection.Camera3d {
focus: 800, focus: 800,
near: 10, near: 10,
far: 10000, far: 10000,
orthographic: false orthographic: false,
}, },
opts opts
) )
@@ -228,7 +228,7 @@ export default class Flippable extends PIXI.projection.Camera3d {
height: this.opts.useBackTransforms ? toObject.height * 2 : fromObject.height * 2, height: this.opts.useBackTransforms ? toObject.height * 2 : fromObject.height * 2,
rotation: this.opts.useBackTransforms ? toObject.rotation : fromObject.rotation, rotation: this.opts.useBackTransforms ? toObject.rotation : fromObject.rotation,
skewX: this.opts.useBackTransforms ? toObject.skew.x : fromObject.skew.x, skewX: this.opts.useBackTransforms ? toObject.skew.x : fromObject.skew.x,
skewY: this.opts.useBackTransforms ? toObject.skew.y : fromObject.skew.y skewY: this.opts.useBackTransforms ? toObject.skew.y : fromObject.skew.y,
} }
// set toObject end values // set toObject end values
@@ -265,7 +265,7 @@ export default class Flippable extends PIXI.projection.Camera3d {
if (this.opts.onComplete) { if (this.opts.onComplete) {
this.opts.onComplete(this, this) this.opts.onComplete(this, this)
} }
} },
}) })
// x & y // x & y
@@ -273,7 +273,7 @@ export default class Flippable extends PIXI.projection.Camera3d {
TweenLite.to(this, this.opts.duration, { TweenLite.to(this, this.opts.duration, {
x: to.x, x: to.x,
y: to.y, y: to.y,
ease: this.opts.transformEase ease: this.opts.transformEase,
}) })
// width & height // width & height
@@ -281,7 +281,7 @@ export default class Flippable extends PIXI.projection.Camera3d {
TweenLite.to([front, back], this.opts.duration, { TweenLite.to([front, back], this.opts.duration, {
width: to.width, width: to.width,
height: to.height, height: to.height,
ease: this.opts.transformEase ease: this.opts.transformEase,
}) })
// rotation // rotation
@@ -289,9 +289,9 @@ export default class Flippable extends PIXI.projection.Camera3d {
TweenLite.to(this, this.opts.duration, { TweenLite.to(this, this.opts.duration, {
directionalRotation: { directionalRotation: {
rotation: `${to.rotation}_short`, rotation: `${to.rotation}_short`,
useRadians: true useRadians: true,
}, },
ease: this.opts.transformEase ease: this.opts.transformEase,
}) })
// skewX & skewY // skewX & skewY
@@ -299,7 +299,7 @@ export default class Flippable extends PIXI.projection.Camera3d {
TweenLite.to(this.skew, this.opts.duration, { TweenLite.to(this.skew, this.opts.duration, {
x: to.skewX, x: to.skewX,
y: to.skewY, y: to.skewY,
ease: this.opts.transformEase ease: this.opts.transformEase,
}) })
// camera // camera
@@ -308,7 +308,7 @@ export default class Flippable extends PIXI.projection.Camera3d {
.to(this.euler, half, { .to(this.euler, half, {
x: this.opts.eulerX, x: this.opts.eulerX,
y: this.opts.eulerY, y: this.opts.eulerY,
ease ease,
}) })
.to(this.euler, half, { x: 0, y: 0, ease }) .to(this.euler, half, { x: 0, y: 0, ease })
@@ -385,7 +385,7 @@ export default class Flippable extends PIXI.projection.Camera3d {
const bounds = displayObject.getBounds() const bounds = displayObject.getBounds()
return { return {
x: bounds.x + bounds.width / 2, x: bounds.x + bounds.width / 2,
y: bounds.y + bounds.height / 2 y: bounds.y + bounds.height / 2,
} }
} }
@@ -411,7 +411,7 @@ export default class Flippable extends PIXI.projection.Camera3d {
displayObject.skew.x, displayObject.skew.x,
displayObject.skew.y, displayObject.skew.y,
displayObject.pivot.x, displayObject.pivot.x,
displayObject.pivot.y displayObject.pivot.y,
] ]
displayObject.position.set(0, 0) displayObject.position.set(0, 0)
Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 KiB

+1 -1
View File
@@ -13,7 +13,7 @@
</head> </head>
<body onload="Doctest.run();" > <body onload="Doctest.run();" >
<h1> <h1>
Functional Tests <a href="../index.html">lib.</a><a href="index.html">pixi.</a>Functional Tests
</h1> </h1>
<p> <p>
If you want to test the UI in all functional aspects, it'is important to be able If you want to test the UI in all functional aspects, it'is important to be able
+162 -146
View File
@@ -1,164 +1,180 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>PIXI ForceLayout</title> <title>PIXI ForceLayout</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"> <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.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script> <script src="../../dist/iwmlib.pixi.js"></script>
<script src="./lib/graphology.min.js"></script> <script src="./lib/graphology.min.js"></script>
<script src="./lib/graphology-layout-forceatlas2.js"></script> <script src="./lib/graphology-layout-forceatlas2.js"></script>
</head> </head>
<body onload="Doctest.run()"> <body onload="Doctest.run()">
<h1>ForceLayout</h1> <h1><a href="../index.html">lib.</a><a href="index.html">pixi.</a>ForceLayout</h1>
<p> <p>
Comparison of a <a href="https://github.com/d3/d3-force/blob/master/README.md">D3 ForceLayout</a> with a <a href="https://github.com/graphology/graphology-layout-forceatlas2">ForceAtlas 2</a> forcelayout. Comparison of a <a href="https://github.com/d3/d3-force/blob/master/README.md">D3 ForceLayout</a> with a
</p> <a href="https://github.com/graphology/graphology-layout-forceatlas2">ForceAtlas 2</a> forcelayout.
<h2>Let's look at the D3 ForceLayout:</h2><br /> </p>
<canvas id="canvas" class="interactive"></canvas> <h2>Let's look at the D3 ForceLayout:</h2>
<script class="doctest"> <br />
<canvas id="canvas" class="interactive"></canvas>
<script class="doctest">
const app = new PIXIApp({
view: canvas,
width: 900,
height: 420,
transparent: false
})
.setup()
.run()
const app = new PIXIApp({ app.loadTextures(
view: canvas, ['./assets/circle.png'],
width: 900, (textures) => {
height: 420, // add sprites
transparent: false //--------------------
}).setup().run() const sprites = []
app.loadTextures([ for (let i = 0; i < 100; i++) {
'./assets/circle.png' const sprite = new PIXI.Sprite(textures.get('./assets/circle.png'))
], textures => { sprite.x = randomInt(0, app.size.width)
sprite.y = randomInt(0, app.size.height)
sprite.width = sprite.height = 2
sprite.anchor.set(0.5, 0.5)
sprite.__random = Math.random()
sprites.push(sprite)
}
// add sprites app.scene.addChild(...sprites)
//--------------------
const sprites = []
for (let i = 0; i < 100; i++) { // force simulation
const sprite = new PIXI.Sprite(textures.get('./assets/circle.png')) //--------------------
sprite.x = randomInt(0, app.size.width) const forceCollide = d3.forceCollide().radius((d) => d.width / 2 + 1)
sprite.y = randomInt(0, app.size.height) const simulation = d3
sprite.width = sprite.height = 2 .forceSimulation(sprites)
sprite.anchor.set(.5, .5) .on('tick', () => {
sprite.__random = Math.random() //forceCollide.radius(d => d.radius)
sprites.push(sprite) })
} .force('collide', forceCollide)
.force('x', d3.forceX(app.center.x))
.force('y', d3.forceY(app.center.y))
.stop()
.force(
'radial',
d3.forceRadial(
(d) => {
return d.__random < 0.5 ? 60 : 160
},
app.center.x,
app.center.y
)
)
app.scene.addChild(...sprites) d3.timeout(() => {
simulation.restart()
}, 500)
},
{ resolutionDependent: false }
)
</script>
// force simulation <h2>D3 ForceLayout with links:</h2>
//-------------------- <br />
const forceCollide = d3.forceCollide().radius(d => d.width / 2 + 1) <canvas id="canvas2" class="interactive"></canvas>
const simulation = d3.forceSimulation(sprites) <script class="doctest">
.on('tick', () => { const app2 = new PIXIApp({
//forceCollide.radius(d => d.radius) view: canvas2,
}) width: 900,
.force('collide', forceCollide) height: 420,
.force('x', d3.forceX(app.center.x)) transparent: false
.force('y', d3.forceY(app.center.y)) })
.stop() .setup()
.force('radial', d3.forceRadial(d => { .run()
return d.__random < .5 ? 60 : 160
}, app.center.x, app.center.y))
d3.timeout(() => { app2.loadTextures(
simulation.restart() ['./assets/circle2.png'],
}, 500) (textures) => {
// add sprites
//--------------------
const sprites = []
}, {resolutionDependent: false}) for (let i = 0; i < 100; i++) {
const sprite = new PIXI.Sprite(textures.get('./assets/circle2.png'))
sprite.id = i
sprite.x = randomInt(0, app2.size.width)
sprite.y = randomInt(0, app2.size.height)
sprite.width = sprite.height = 16
sprite.anchor.set(0.5, 0.5)
sprites.push(sprite)
}
</script> app2.scene.addChild(...sprites)
<h2>D3 ForceLayout with links:</h2><br /> const links = new PIXI.Graphics()
<canvas id="canvas2" class="interactive"></canvas> app2.scene.addChildAt(links, 0)
<script class="doctest">
const app2 = new PIXIApp({ // force simulation
view: canvas2, //--------------------
width: 900, const forceCollide = d3.forceCollide().radius((d) => d.width / 2 + 4)
height: 420, const forceLink = d3
transparent: false .forceLink([
}).setup().run() { source: 88, target: 8 },
{ source: 47, target: 55 },
{ source: 70, target: 1 },
{ source: 12, target: 12 },
{ source: 26, target: 33 },
{ source: 48, target: 53 },
{ source: 10, target: 70 },
{ source: 68, target: 61 },
{ source: 28, target: 65 },
{ source: 12, target: 34 },
{ source: 6, target: 55 },
{ source: 9, target: 16 },
{ source: 87, target: 96 },
{ source: 64, target: 24 },
{ source: 98, target: 14 },
{ source: 18, target: 23 },
{ source: 53, target: 62 },
{ source: 11, target: 53 },
{ source: 43, target: 23 },
{ source: 80, target: 9 },
{ source: 72, target: 88 },
{ source: 62, target: 3 },
{ source: 72, target: 15 },
{ source: 84, target: 25 },
{ source: 57, target: 58 },
{ source: 87, target: 19 }
])
.id((d) => d.id)
.strength(0.05)
const simulation = d3
.forceSimulation(sprites)
.on('tick', () => {
links.clear()
links.lineStyle(1, 0xfdc02f)
for (let link of forceLink.links()) {
links.moveTo(link.source.x, link.source.y)
links.lineTo(link.target.x, link.target.y)
}
})
.force('collide', forceCollide)
.force('link', forceLink)
.stop()
app2.loadTextures([ d3.timeout(() => {
'./assets/circle2.png' simulation.restart()
], textures => { }, 1000)
},
// add sprites { resolutionDependent: false }
//-------------------- )
const sprites = [] </script>
</body>
for (let i = 0; i < 100; i++) { </html>
const sprite = new PIXI.Sprite(textures.get('./assets/circle2.png'))
sprite.id = i
sprite.x = randomInt(0, app2.size.width)
sprite.y = randomInt(0, app2.size.height)
sprite.width = sprite.height = 16
sprite.anchor.set(.5, .5)
sprites.push(sprite)
}
app2.scene.addChild(...sprites)
const links = new PIXI.Graphics()
app2.scene.addChildAt(links, 0)
// force simulation
//--------------------
const forceCollide = d3.forceCollide().radius(d => d.width / 2 + 4)
const forceLink = d3.forceLink([
{source: 88, target: 8},
{source: 47, target: 55},
{source: 70, target: 1},
{source: 12, target: 12},
{source: 26, target: 33},
{source: 48, target: 53},
{source: 10, target: 70},
{source: 68, target: 61},
{source: 28, target: 65},
{source: 12, target: 34},
{source: 6, target: 55},
{source: 9, target: 16},
{source: 87, target: 96},
{source: 64, target: 24},
{source: 98, target: 14},
{source: 18, target: 23},
{source: 53, target: 62},
{source: 11, target: 53},
{source: 43, target: 23},
{source: 80, target: 9},
{source: 72, target: 88},
{source: 62, target: 3},
{source: 72, target: 15},
{source: 84, target: 25},
{source: 57, target: 58},
{source: 87, target: 19}
]).id(d => d.id).strength(.05)
const simulation = d3.forceSimulation(sprites)
.on('tick', () => {
links.clear()
links.lineStyle(1, 0xfdc02f)
for (let link of forceLink.links()) {
links.moveTo(link.source.x, link.source.y)
links.lineTo(link.target.x, link.target.y)
}
})
.force('collide', forceCollide)
.force('link', forceLink)
.stop()
d3.timeout(() => {
simulation.restart()
}, 1000)
}, {resolutionDependent: false})
</script>
</body>
+63 -57
View File
@@ -1,62 +1,68 @@
<html> <html>
<head> <head>
<title>PIXI Lib Doctests</title> <title>PIXI Lib Doctests</title>
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0" /> <meta
<link rel="stylesheet" href="../../css/index.css"> name="viewport"
content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0"
/>
<link rel="stylesheet" href="../../css/index.css" />
<script src="../../dist/iwmlib.js"></script> <script src="../../dist/iwmlib.js"></script>
<template id="itemTemplate"> <template id="itemTemplate">
<a class="wrapper" href=""> <a class="wrapper" href="">
<div class="preview"> <div class="preview">
<div class="thumbnail-container"> <div class="thumbnail-container">
<div class="thumbnail"> <div class="thumbnail">
<img class="icon" src="thumbnails/notfound.png"> <img class="icon" src="thumbnails/notfound.png" />
<!-- <iframe src="" frameborder="0"></iframe> --> <!-- <iframe src="" frameborder="0"></iframe> -->
</div> </div>
</div>
<div class="title"></div>
</div> </div>
<div class="title"></div> </a>
</div> </template>
</a> </head>
</template> <body>
</head> <h2><a href="../index.html">lib.</a><a href="index.html">pixi.</a>Doctests</h2>
<body> <div id="container" class="container"></div>
<div id="container" class="container"> <script>
<a style="position: absolute; left: 22px; top: 12px;" target="_blank" href="http://www.iwm-tuebingen.de">IWM</a> const index = new Index(
</div> itemTemplate,
<script> [
const index = new Index(itemTemplate, [ ['PIXI.Application', 'application.html'],
['PIXI.Application', 'application.html'], ['Application', 'app.html'],
['Application', 'app.html'], ['Badge', 'badge.html'],
['Badge', 'badge.html'], ['Button', 'button.html'],
['Button', 'button.html'], ['ButtonGroup', 'buttongroup.html'],
['ButtonGroup', 'buttongroup.html'], ['Coordinates', 'coordinates.html'],
['Coordinates', 'coordinates.html'], ['DeepZoom', 'deepzoom/index.html'],
['DeepZoom', 'deepzoom/index.html'], ['Flippable', 'flippable.html'],
['Flippable', 'flippable.html'], ['LabeledGraphics', 'labeledgraphics.html'],
['LabeledGraphics', 'labeledgraphics.html'], ['List', 'list.html'],
['List', 'list.html'], ['Maps', 'maps/index.html'],
['Message', 'message.html'], ['Message', 'message.html'],
['Modal', 'modal.html'], ['Modal', 'modal.html'],
['Tooltip', 'tooltip.html'], ['Tooltip', 'tooltip.html'],
['Popover', 'popover.html'], ['Popover', 'popover.html'],
['Popup', 'popup.html'], ['Popup', 'popup.html'],
['PopupMenu', 'popupmenu.html'], ['PopupMenu', 'popupmenu.html'],
['Progress', 'progress.html'], ['Progress', 'progress.html'],
['Slider', 'slider.html'], ['Slider', 'slider.html'],
['Switch', 'switch.html'], ['Switch', 'switch.html'],
['Volatile', 'volatile.html'], ['Volatile', 'volatile.html'],
['Scatter', 'scatter.html'], ['Scatter', 'scatter.html'],
['Flip Effect', 'flipeffect.html'], ['Flip Effect', 'flipeffect.html'],
['Blur Filter', 'blurfilter.html'], ['Blur Filter', 'blurfilter.html'],
['Text', 'text.html'], ['Text', 'text.html'],
['Text Transform', 'text-transform.html'], ['Text Transform', 'text-transform.html'],
['Scrollview', 'scrollview.html'], ['Scrollview', 'scrollview.html'],
['Stylus', 'stylus.html'] ['Stylus', 'stylus.html']
], ],
null) null
index.load() )
</script> index.load()
</body> </script>
</body>
</html> </html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

+77 -69
View File
@@ -1,84 +1,92 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>PIXI LabeledGraphics</title> <title>PIXI LabeledGraphics</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"> <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.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script> <script src="../../dist/iwmlib.pixi.js"></script>
</head> </head>
<body onload="Doctest.run()"> <body onload="Doctest.run()">
<h1>LabeledGraphics</h1> <h1><a href="../index.html">lib.</a><a href="index.html">pixi.</a>LabeledGraphics</h1>
<p> <p>
A labeled graphics extends the PIXI.Graphics class with an ensureLabel method that allows A labeled graphics extends the PIXI.Graphics class with an ensureLabel method that allows to place and reuse
to place and reuse labels, i.e. PIXI.Text objects. The labels are cached by a given key labels, i.e. PIXI.Text objects. The labels are cached by a given key and only rendered anew if necessary.
and only rendered anew if necessary. </p>
</p> <p><a href="../../doc/out/LabeledGraphics.html">JavaScript API</a></p>
<p><a href="../../doc/out/LabeledGraphics.html">JavaScript API</a></p> <p>Let's look at an example:</p>
<p>Let's look at an example:</p><br /> <br />
<canvas id="canvas1" class="interactive"></canvas> <canvas id="canvas1" class="interactive"></canvas>
<p> <p>What you should see: A box with a label.</p>
What you should see: A box with a label. <script class="doctest">
</p> const app = new PIXIApp({
<script class="doctest"> view: canvas1,
const app = new PIXIApp({ width: 900,
view: canvas1, height: 150
width: 900, })
height: 150 .setup()
}).setup().run() .run()
const labeledBox = new LabeledGraphics() const labeledBox = new LabeledGraphics()
labeledBox.ensureLabel('key', 'This is a label in a box', { justify: 'top'}) labeledBox.ensureLabel('key', 'This is a label in a box', { justify: 'top' })
labeledBox.beginFill(0x333333) labeledBox.beginFill(0x333333)
labeledBox.drawRect(0, 0, 300, 100) labeledBox.drawRect(0, 0, 300, 100)
labeledBox.endFill() labeledBox.endFill()
app.scene.addChild(labeledBox) app.scene.addChild(labeledBox)
</script> </script>
<h2>Zoomable Labels</h2> <h2>Zoomable Labels</h2>
<p>Labeled graphics can also be used to represent zoomable text columns in more complex layouts. <p>
Use the mousewheel to zoom the following text column: Labeled graphics can also be used to represent zoomable text columns in more complex layouts. Use the
</p> mousewheel to zoom the following text column:
</p>
<canvas id="canvas2" class="interactive"></canvas> <canvas id="canvas2" class="interactive"></canvas>
<script class="doctest"> <script class="doctest">
const app2 = new PIXIApp({ const app2 = new PIXIApp({
view: canvas2, view: canvas2,
width: 900, width: 900,
height: 150 height: 150
}).setup().run() })
.setup()
.run()
const loremIpsum = `Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. const loremIpsum = `Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
At vero eos et accusam et justo duo dolores et ea rebum.` At vero eos et accusam et justo duo dolores et ea rebum.`
const column = new LabeledGraphics() const column = new LabeledGraphics()
const text = column.ensureLabel('key', loremIpsum, { maxWidth: 250, zoomFonts: true, justify: 'top', align: 'align'}) const text = column.ensureLabel('key', loremIpsum, {
column.beginFill(0x333333) maxWidth: 250,
column.drawRect(0, 0, 900, 150) zoomFonts: true,
column.endFill() justify: 'top',
align: 'align'
})
column.beginFill(0x333333)
column.drawRect(0, 0, 900, 150)
column.endFill()
column.interactive = true column.interactive = true
text.interactive = true text.interactive = true
text.getLocalBounds() text.getLocalBounds()
text.on('zoom', (event) => { text.on('zoom', (event) => {
let factor = 1.0 + event.wheelDelta / 1000 let factor = 1.0 + event.wheelDelta / 1000
text.zoom(factor) text.zoom(factor)
}) })
app2.view.addEventListener('mousewheel', (event) => { app2.view.addEventListener('mousewheel', (event) => {
text.emit('zoom', event) text.emit('zoom', event)
event.preventDefault() event.preventDefault()
}) })
app2.scene.addChild(column) app2.scene.addChild(column)
</script> </script>
</body>
</body> </html>
+2 -2
View File
@@ -30,7 +30,7 @@ export class Hypenate {
result.push(p + '-') result.push(p + '-')
} }
result.push(last) result.push(last)
return result.filter(p => p.length > 0) return result.filter((p) => p.length > 0)
} }
static splitWord(word) { static splitWord(word) {
@@ -288,7 +288,7 @@ export class LabeledGraphics extends PIXI.Graphics {
} }
return { return {
text: `${data.text}${i > 0 ? ' ' : ''}${words[i]}`, text: `${data.text}${i > 0 ? ' ' : ''}${words[i]}`,
length: data.length + wordLength + spaceLength length: data.length + wordLength + spaceLength,
} }
}, },
{ text: '', length: dotsLength } { text: '', length: dotsLength }
Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

+166 -166
View File
@@ -1,191 +1,191 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>PIXI List</title> <title>PIXI List</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"> <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="../3rdparty/gsap/src/uncompressed/plugins/ThrowPropsPlugin.js"></script> <script src="../3rdparty/gsap/src/uncompressed/plugins/ThrowPropsPlugin.js"></script>
<script src="../../dist/iwmlib.pixi.js"></script> <script src="../../dist/iwmlib.pixi.js"></script>
<script src="../../dist/iwmlib.js"></script> <script src="../../dist/iwmlib.js"></script>
</head> </head>
<body onload="Doctest.run()"> <body onload="Doctest.run()">
<h1>List</h1> <h1><a href="../index.html">lib.</a><a href="index.html">pixi.</a>List</h1>
<p> <p>Using the List class, any PIXI elements (PIXI.DisplayObject) can be included in a scrollable list.</p>
Using the List class, any PIXI elements (PIXI.DisplayObject) can be included <p><a href="../../doc/out/List.html">JavaScript API</a></p>
in a scrollable list. <p>Let's look at some list examples:</p>
</p> <br />
<p><a href="../../doc/out/List.html">JavaScript API</a></p> <canvas id="canvas" class="interactive"></canvas>
<p>Let's look at some list examples:</p><br /> <p>What you should see: Three lists, one horizontal and two vertical.</p>
<canvas id="canvas" class="interactive"></canvas> <script class="doctest">
<p> const app = new PIXIApp({
What you should see: Three lists, one horizontal and two vertical. view: canvas,
</p> width: 900,
<script class="doctest"> height: 420,
const app = new PIXIApp({ transparent: false
view: canvas, })
width: 900, .setup()
height: 420, .run()
transparent: false
}).setup().run()
app.loadTextures([ app.loadTextures(
'./assets/elephant-1.jpg', [
'./assets/elephant-2.jpg', './assets/elephant-1.jpg',
'./assets/elephant-3.jpg', './assets/elephant-2.jpg',
'./assets/elephant-4.jpg', './assets/elephant-3.jpg',
'./assets/elephant-5.jpg', './assets/elephant-4.jpg',
'./assets/bamboo-1.jpg', './assets/elephant-5.jpg',
'./assets/bamboo-2.jpg', './assets/bamboo-1.jpg',
'./assets/bamboo-3.jpg', './assets/bamboo-2.jpg',
'./assets/bamboo-4.jpg', './assets/bamboo-3.jpg',
'./assets/bamboo-5.jpg' './assets/bamboo-4.jpg',
], textures => { './assets/bamboo-5.jpg'
],
(textures) => {
// Example 1
//--------------------
const elephant1 = new PIXI.Sprite(textures.get('./assets/elephant-1.jpg'))
const elephant2 = new PIXI.Sprite(textures.get('./assets/elephant-2.jpg'))
const elephant3 = new PIXI.Sprite(textures.get('./assets/elephant-3.jpg'))
const elephant4 = new PIXI.Sprite(textures.get('./assets/elephant-4.jpg'))
const elephant5 = new PIXI.Sprite(textures.get('./assets/elephant-5.jpg'))
// Example 1 const elephants = [elephant1, elephant2, elephant3, elephant4, elephant5]
//-------------------- elephants.forEach((it) => it.scale.set(0.1, 0.1))
const elephant1 = new PIXI.Sprite(textures.get('./assets/elephant-1.jpg'))
const elephant2 = new PIXI.Sprite(textures.get('./assets/elephant-2.jpg'))
const elephant3 = new PIXI.Sprite(textures.get('./assets/elephant-3.jpg'))
const elephant4 = new PIXI.Sprite(textures.get('./assets/elephant-4.jpg'))
const elephant5 = new PIXI.Sprite(textures.get('./assets/elephant-5.jpg'))
const elephants = [elephant1, elephant2, elephant3, elephant4, elephant5] const elephantList = new List(elephants)
elephants.forEach(it => it.scale.set(.1, .1)) elephantList.x = 10
elephantList.y = 10
const elephantList = new List(elephants) // Example 2
elephantList.x = 10 //--------------------
elephantList.y = 10 const bamboo1 = new PIXI.Sprite(textures.get('./assets/bamboo-1.jpg'))
const bamboo2 = new PIXI.Sprite(textures.get('./assets/bamboo-2.jpg'))
const bamboo3 = new PIXI.Sprite(textures.get('./assets/bamboo-3.jpg'))
const bamboo4 = new PIXI.Sprite(textures.get('./assets/bamboo-4.jpg'))
const bamboo5 = new PIXI.Sprite(textures.get('./assets/bamboo-5.jpg'))
// Example 2 const bamboos = [bamboo1, bamboo2, bamboo3, bamboo4, bamboo5]
//-------------------- bamboos.forEach((it) => it.scale.set(0.15, 0.15))
const bamboo1 = new PIXI.Sprite(textures.get('./assets/bamboo-1.jpg'))
const bamboo2 = new PIXI.Sprite(textures.get('./assets/bamboo-2.jpg'))
const bamboo3 = new PIXI.Sprite(textures.get('./assets/bamboo-3.jpg'))
const bamboo4 = new PIXI.Sprite(textures.get('./assets/bamboo-4.jpg'))
const bamboo5 = new PIXI.Sprite(textures.get('./assets/bamboo-5.jpg'))
const bamboos = [bamboo1, bamboo2, bamboo3, bamboo4, bamboo5] const bambooList = new List(bamboos, {
bamboos.forEach(it => it.scale.set(.15, .15)) orientation: 'horizontal',
width: 300,
app
})
bambooList.x = 200
bambooList.y = 10
const bambooList = new List(bamboos, { window.bambooList = bambooList
orientation: 'horizontal',
width: 300,
app
})
bambooList.x = 200
bambooList.y = 10
window.bambooList = bambooList // Example 3
//--------------------
const style = { fontSize: 14, fill: 0xe7bc51 }
// Example 3 const texts = []
//-------------------- for (let i = 0; i < 1000; i++) {
const style = {fontSize: 14, fill : 0xe7bc51} const text = new PIXI.Text(`Example Text ${i}`, style)
text.interactive = true
text.on('tap', (event) => console.log(`tap text ${i}`))
text.on('click', (event) => console.log(`click text ${i}`))
texts.push(text)
}
const texts = [] const textList = new List(texts, {
for (let i = 0; i < 1000; i++) { orientation: 'vertical',
const text = new PIXI.Text(`Example Text ${i}`, style) height: 200,
text.interactive = true padding: 2,
text.on('tap', event => console.log(`tap text ${i}`)) app
text.on('click', event => console.log(`click text ${i}`)) })
texts.push(text) textList.x = 200
} textList.y = 200
const textList = new List(texts, { // Add to scene
orientation: 'vertical', //--------------------
height: 200, app.scene.addChild(elephantList, bambooList, textList)
padding: 2, },
app { resolutionDependent: false }
}) )
textList.x = 200 </script>
textList.y = 200
// Add to scene <h1>List in a Scatter</h1>
//-------------------- <p>A PixiJS UI List is displayed inside a ScatterObject.</p>
app.scene.addChild(elephantList, bambooList, textList) <canvas id="canvas2" class="interactive"></canvas>
<p>What you should see: A ScatterObject with a list in it.</p>
<script class="doctest">
const app2 = new PIXIApp({
view: canvas2,
width: 900,
height: 420,
transparent: false
})
.setup()
.run()
}, {resolutionDependent: false}) app2.loadTextures(
</script> [
'./assets/jungle.jpg',
'./assets/elephant-1.jpg',
'./assets/elephant-2.jpg',
'./assets/elephant-3.jpg',
'./assets/elephant-4.jpg',
'./assets/elephant-5.jpg'
],
(textures) => {
// ScatterContainer
//--------------------
const scatterContainer = new ScatterContainer(app2.renderer, {
showBounds: true,
showPolygon: true,
app: app2,
stopEvents: true,
claimEvents: true
})
<h1>List in a Scatter</h1> app2.scene.addChild(scatterContainer)
<p>
A PixiJS UI List is displayed inside a ScatterObject.
</p>
<canvas id="canvas2" class="interactive"></canvas>
<p>
What you should see: A ScatterObject with a list in it.
</p>
<script class="doctest">
const app2 = new PIXIApp({ // ScatterObject
view: canvas2, //--------------------
width: 900, const jungle = new PIXI.Sprite(textures.get('./assets/jungle.jpg'))
height: 420, jungle.interactive = true
transparent: false const scatter = new DisplayObjectScatter(jungle, app2.renderer, {
}).setup().run() x: 200,
y: 20,
startScale: 1,
minScale: 0.25,
maxScale: 1
})
app2.loadTextures([ scatterContainer.addChild(jungle)
'./assets/jungle.jpg',
'./assets/elephant-1.jpg',
'./assets/elephant-2.jpg',
'./assets/elephant-3.jpg',
'./assets/elephant-4.jpg',
'./assets/elephant-5.jpg'
], textures => {
// ScatterContainer // Example 1
//-------------------- //--------------------
const scatterContainer = new ScatterContainer(app2.renderer, { const elephant1 = new PIXI.Sprite(textures.get('./assets/elephant-1.jpg'))
showBounds: true, const elephant2 = new PIXI.Sprite(textures.get('./assets/elephant-2.jpg'))
showPolygon: true, const elephant3 = new PIXI.Sprite(textures.get('./assets/elephant-3.jpg'))
app: app2, const elephant4 = new PIXI.Sprite(textures.get('./assets/elephant-4.jpg'))
stopEvents: true, const elephant5 = new PIXI.Sprite(textures.get('./assets/elephant-5.jpg'))
claimEvents: true
})
app2.scene.addChild(scatterContainer) const elephants = [elephant1, elephant2, elephant3, elephant4, elephant5]
elephants.forEach((it) => it.scale.set(0.15, 0.15))
// ScatterObject const elephantList = new List(elephants, {
//-------------------- height: 200
const jungle = new PIXI.Sprite(textures.get('./assets/jungle.jpg')) })
jungle.interactive = true elephantList.x = 10
const scatter = new DisplayObjectScatter(jungle, app2.renderer, { elephantList.y = 10
x: 200,
y: 20,
startScale: 1,
minScale: 0.25,
maxScale: 1
})
scatterContainer.addChild(jungle) // Add to scene
//--------------------
// Example 1 jungle.addChild(elephantList)
//-------------------- },
const elephant1 = new PIXI.Sprite(textures.get('./assets/elephant-1.jpg')) { resolutionDependent: false }
const elephant2 = new PIXI.Sprite(textures.get('./assets/elephant-2.jpg')) )
const elephant3 = new PIXI.Sprite(textures.get('./assets/elephant-3.jpg')) </script>
const elephant4 = new PIXI.Sprite(textures.get('./assets/elephant-4.jpg')) </body>
const elephant5 = new PIXI.Sprite(textures.get('./assets/elephant-5.jpg')) </html>
const elephants = [elephant1, elephant2, elephant3, elephant4, elephant5]
elephants.forEach(it => it.scale.set(.15, .15))
const elephantList = new List(elephants, {
height: 200
})
elephantList.x = 10
elephantList.y = 10
// Add to scene
//--------------------
jungle.addChild(elephantList)
}, {resolutionDependent: false})
</script>
</body>

Some files were not shown because too many files have changed in this diff Show More