From dda62626016bda864be3c84c2a878169af0ccb36 Mon Sep 17 00:00:00 2001 From: Uwe Oestermeier Date: Thu, 4 Jul 2019 16:00:56 +0200 Subject: [PATCH 01/17] Added maxRotation parameter. --- dist/iwmlib.js | 15 ++++++++++++--- dist/iwmlib.pixi.js | 15 ++++++++++++--- lib/scatter.js | 15 ++++++++++++--- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/dist/iwmlib.js b/dist/iwmlib.js index c858c55..0cbf467 100644 --- a/dist/iwmlib.js +++ b/dist/iwmlib.js @@ -3135,7 +3135,8 @@ onThrowFinished = null, scaleAutoClose = false, scaleCloseThreshold = 0.10, - scaleCloseBuffer = 0.05 + scaleCloseBuffer = 0.05, + maxRotation = 5 } = {}) { if (rotationDegrees != null && rotation != null) { throw new Error('Use rotationDegrees or rotation but not both') @@ -3167,6 +3168,7 @@ this.startScale = startScale; // Needed to reset object this.minScale = minScale; this.maxScale = maxScale; + this.maxRotation = maxRotation; this.overdoScaling = overdoScaling; this.translatable = translatable; if (!translatable) { @@ -3178,6 +3180,7 @@ this.resizable = resizable; this.mouseZoomFactor = mouseZoomFactor; this.autoBringToFront = autoBringToFront; + this.dragging = false; this.onTransform = onTransform != null ? [onTransform] : null; this.onClose = onClose != null ? [onClose] : null; @@ -3212,10 +3215,16 @@ gesture(interaction) { let delta = interaction.delta(); - //console.log("gesture", delta) + console.log("gesture", delta.rotate); if (delta != null) { this.addVelocity(delta); - this.transform(delta, delta.zoom, delta.rotate, delta.about); + let alpha = delta.rotate; + if (this.maxRotationPerStep != null) { + if (Math.abs(alpha) > this.maxRotationPerStep) { + alpha = 0; + } + } + this.transform(delta, delta.zoom, alpha, delta.about); if (delta.zoom != 1) this.interactionAnchor = delta.about; } } diff --git a/dist/iwmlib.pixi.js b/dist/iwmlib.pixi.js index 706334d..bc18fb1 100644 --- a/dist/iwmlib.pixi.js +++ b/dist/iwmlib.pixi.js @@ -6302,7 +6302,8 @@ onThrowFinished = null, scaleAutoClose = false, scaleCloseThreshold = 0.10, - scaleCloseBuffer = 0.05 + scaleCloseBuffer = 0.05, + maxRotation = 5 } = {}) { if (rotationDegrees != null && rotation != null) { throw new Error('Use rotationDegrees or rotation but not both') @@ -6334,6 +6335,7 @@ this.startScale = startScale; // Needed to reset object this.minScale = minScale; this.maxScale = maxScale; + this.maxRotation = maxRotation; this.overdoScaling = overdoScaling; this.translatable = translatable; if (!translatable) { @@ -6345,6 +6347,7 @@ this.resizable = resizable; this.mouseZoomFactor = mouseZoomFactor; this.autoBringToFront = autoBringToFront; + this.dragging = false; this.onTransform = onTransform != null ? [onTransform] : null; this.onClose = onClose != null ? [onClose] : null; @@ -6379,10 +6382,16 @@ gesture(interaction) { let delta = interaction.delta(); - //console.log("gesture", delta) + console.log("gesture", delta.rotate); if (delta != null) { this.addVelocity(delta); - this.transform(delta, delta.zoom, delta.rotate, delta.about); + let alpha = delta.rotate; + if (this.maxRotationPerStep != null) { + if (Math.abs(alpha) > this.maxRotationPerStep) { + alpha = 0; + } + } + this.transform(delta, delta.zoom, alpha, delta.about); if (delta.zoom != 1) this.interactionAnchor = delta.about; } } diff --git a/lib/scatter.js b/lib/scatter.js index 107d3db..76de55e 100644 --- a/lib/scatter.js +++ b/lib/scatter.js @@ -268,7 +268,8 @@ export class AbstractScatter extends Throwable { onThrowFinished = null, scaleAutoClose = false, scaleCloseThreshold = 0.10, - scaleCloseBuffer = 0.05 + scaleCloseBuffer = 0.05, + maxRotation = 5 } = {}) { if (rotationDegrees != null && rotation != null) { throw new Error('Use rotationDegrees or rotation but not both') @@ -300,6 +301,7 @@ export class AbstractScatter extends Throwable { this.startScale = startScale // Needed to reset object this.minScale = minScale this.maxScale = maxScale + this.maxRotation = maxRotation this.overdoScaling = overdoScaling this.translatable = translatable if (!translatable) { @@ -311,6 +313,7 @@ export class AbstractScatter extends Throwable { this.resizable = resizable this.mouseZoomFactor = mouseZoomFactor this.autoBringToFront = autoBringToFront + this.dragging = false this.onTransform = onTransform != null ? [onTransform] : null this.onClose = onClose != null ? [onClose] : null @@ -345,10 +348,16 @@ export class AbstractScatter extends Throwable { gesture(interaction) { let delta = interaction.delta() - //console.log("gesture", delta) + console.log("gesture", delta.rotate) if (delta != null) { this.addVelocity(delta) - this.transform(delta, delta.zoom, delta.rotate, delta.about) + let alpha = delta.rotate + if (this.maxRotationPerStep != null) { + if (Math.abs(alpha) > this.maxRotationPerStep) { + alpha = 0 + } + } + this.transform(delta, delta.zoom, alpha, delta.about) if (delta.zoom != 1) this.interactionAnchor = delta.about } } From 9b9988569ffdc870a06473c143230a7831f1dfb9 Mon Sep 17 00:00:00 2001 From: uoestermeier Date: Thu, 4 Jul 2019 16:08:23 +0200 Subject: [PATCH 02/17] Added limit to scatter rotation to avoid flicker. --- dist/iwmlib.js | 3 +- dist/iwmlib.pixi.js | 3 +- lib/scatter.js | 3 +- package-lock.json | 85 ++++++++++++++++++++++++++------------------- package.json | 8 ++--- 5 files changed, 57 insertions(+), 45 deletions(-) diff --git a/dist/iwmlib.js b/dist/iwmlib.js index 0cbf467..448f3fb 100644 --- a/dist/iwmlib.js +++ b/dist/iwmlib.js @@ -3136,7 +3136,7 @@ scaleAutoClose = false, scaleCloseThreshold = 0.10, scaleCloseBuffer = 0.05, - maxRotation = 5 + maxRotation = Angle.degree2radian(5) } = {}) { if (rotationDegrees != null && rotation != null) { throw new Error('Use rotationDegrees or rotation but not both') @@ -3215,7 +3215,6 @@ gesture(interaction) { let delta = interaction.delta(); - console.log("gesture", delta.rotate); if (delta != null) { this.addVelocity(delta); let alpha = delta.rotate; diff --git a/dist/iwmlib.pixi.js b/dist/iwmlib.pixi.js index bc18fb1..7ca6108 100644 --- a/dist/iwmlib.pixi.js +++ b/dist/iwmlib.pixi.js @@ -6303,7 +6303,7 @@ scaleAutoClose = false, scaleCloseThreshold = 0.10, scaleCloseBuffer = 0.05, - maxRotation = 5 + maxRotation = Angle.degree2radian(5) } = {}) { if (rotationDegrees != null && rotation != null) { throw new Error('Use rotationDegrees or rotation but not both') @@ -6382,7 +6382,6 @@ gesture(interaction) { let delta = interaction.delta(); - console.log("gesture", delta.rotate); if (delta != null) { this.addVelocity(delta); let alpha = delta.rotate; diff --git a/lib/scatter.js b/lib/scatter.js index 76de55e..5788272 100644 --- a/lib/scatter.js +++ b/lib/scatter.js @@ -269,7 +269,7 @@ export class AbstractScatter extends Throwable { scaleAutoClose = false, scaleCloseThreshold = 0.10, scaleCloseBuffer = 0.05, - maxRotation = 5 + maxRotation = Angle.degree2radian(5) } = {}) { if (rotationDegrees != null && rotation != null) { throw new Error('Use rotationDegrees or rotation but not both') @@ -348,7 +348,6 @@ export class AbstractScatter extends Throwable { gesture(interaction) { let delta = interaction.delta() - console.log("gesture", delta.rotate) if (delta != null) { this.addVelocity(delta) let alpha = delta.rotate diff --git a/package-lock.json b/package-lock.json index 1281fd0..a22b15d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "iwmlib", - "version": "1.0.10", + "version": "1.0.15", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -184,9 +184,9 @@ } }, "@types/pixi.js": { - "version": "4.8.7", - "resolved": "https://registry.npmjs.org/@types/pixi.js/-/pixi.js-4.8.7.tgz", - "integrity": "sha512-SuaeAVDWNbvVzg+ipVrNzVxMDZHaa/MRNT/+Y270sYp/qxfB31KC1wEt7KDVNq9Ac/pRdSDrxVDcLDUaYn0aVg==" + "version": "4.8.8", + "resolved": "https://registry.npmjs.org/@types/pixi.js/-/pixi.js-4.8.8.tgz", + "integrity": "sha512-5wmLnmL3foK/rqYMrrEM/3DxEwvwxJaP73RyqY8aMqq8zUm6CBlmc+12RIBH6iR/RHqU76XL238vWWJV1IN/zw==" }, "acorn": { "version": "5.7.3", @@ -194,9 +194,9 @@ "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" }, "agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "requires": { "es6-promisify": "^5.0.0" } @@ -1049,9 +1049,9 @@ } }, "es6-promise": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz", - "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==" + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, "es6-promisify": { "version": "5.0.0", @@ -1430,13 +1430,20 @@ } }, "fs-extra": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.0.1.tgz", - "integrity": "sha512-W+XLrggcDzlle47X/XnS7FXrXu9sDo+Ze9zpndeBxdgv88FHLm1HtmkhEwavruS6koanBjp098rUpHs65EmG7A==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", + "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==" + } } }, "fs-mkdirp-stream": { @@ -2502,9 +2509,9 @@ } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -2770,6 +2777,14 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, "just-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", @@ -2962,9 +2977,9 @@ } }, "mime": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.3.tgz", - "integrity": "sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw==" + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" }, "mini-signals": { "version": "1.2.0", @@ -3416,22 +3431,22 @@ "integrity": "sha1-i0tcQzsx5Bm8N53FZc4bg1qRs3I=" }, "pixi-particles": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/pixi-particles/-/pixi-particles-4.1.0.tgz", - "integrity": "sha512-By5470dTkHCTrM3T2xrFg9YRH6XK9BNpZjTZmWNzsHmyPbWB+4E/fg66b1DKFQ/vgez6xQm8wfHWcOpNrHWWHg==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/pixi-particles/-/pixi-particles-4.1.1.tgz", + "integrity": "sha512-R/vnqXzD2X4v4mSi3zJE81i1vGWaaZSDI/ImaZr8G4E0qBq2+OxB97Kb9WVWd7BlLFj4wR09VGKm7e5sQGQy4Q==" }, "pixi-projection": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/pixi-projection/-/pixi-projection-0.2.7.tgz", - "integrity": "sha512-zrMUs2lDmPQX8AHXr14/MNGKXRlqr/XBjyI4Pm9fwz2sxMkz+QRLCa1weUrHXbfv1e6GjUe4tJ8KgqeNuRkc3w==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/pixi-projection/-/pixi-projection-0.2.8.tgz", + "integrity": "sha512-3K06VHVDNm0rnCd72HhH9cZF/davWZXIMcJMFzwAuIV9Io2ExQrUv3Eauri4A682jsoriM+stHgm+6n75Ps5lg==", "requires": { "@types/pixi.js": "^4.7.1" } }, "pixi.js": { - "version": "4.8.7", - "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-4.8.7.tgz", - "integrity": "sha512-mx7YbHPkkWoj8FT3qBMkieAjBuuJ4yZWU7rq9NnCSUGpNrVlocrW179xrJQPVR2Q7JZ73ZGTwH7NOUZ9wgh7wA==", + "version": "4.8.8", + "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-4.8.8.tgz", + "integrity": "sha512-wQzuLAWSMfV+x2guL5jZBp37pwCmYXHiTXG7ZWXu4E/5IsC9xozwmOfLeCNEyPzlyucOgxAx/HS+tLqxWPYX7Q==", "requires": { "bit-twiddle": "^1.0.2", "earcut": "^2.1.4", @@ -3501,9 +3516,9 @@ } }, "puppeteer": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.17.0.tgz", - "integrity": "sha512-3EXZSximCzxuVKpIHtyec8Wm2dWZn1fc5tQi34qWfiUgubEVYHjUvr0GOJojqf3mifI6oyKnCdrGxaOI+lWReA==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.18.1.tgz", + "integrity": "sha512-luUy0HPSuWPsPZ1wAp6NinE0zgetWtudf5zwZ6dHjMWfYpTQcmKveFRox7VBNhQ98OjNA9PQ9PzQyX8k/KrxTg==", "requires": { "debug": "^4.1.0", "extract-zip": "^1.6.6", @@ -3524,9 +3539,9 @@ } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, diff --git a/package.json b/package.json index cbfce9c..63bca56 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,10 @@ "optimal-select": "^4.0.1", "pixi-compressed-textures": "^1.1.8", "pixi-filters": "^2.7.1", - "pixi-particles": "^4.1.0", - "pixi-projection": "^0.2.7", - "pixi.js": "^4.8.7", + "pixi-particles": "^4.1.1", + "pixi-projection": "^0.2.8", + "pixi.js": "^4.8.8", "propagating-hammerjs": "^1.4.6", - "puppeteer": "^1.16.0" + "puppeteer": "^1.18.1" } } From 5d1408ad9a1bfe16e551871f70379bce6fa95d05 Mon Sep 17 00:00:00 2001 From: uoestermeier Date: Thu, 4 Jul 2019 16:15:12 +0200 Subject: [PATCH 03/17] Fixed minor bug. --- dist/iwmlib.js | 5 +++-- dist/iwmlib.pixi.js | 5 +++-- lib/scatter.js | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/dist/iwmlib.js b/dist/iwmlib.js index 448f3fb..db06854 100644 --- a/dist/iwmlib.js +++ b/dist/iwmlib.js @@ -3218,8 +3218,9 @@ if (delta != null) { this.addVelocity(delta); let alpha = delta.rotate; - if (this.maxRotationPerStep != null) { - if (Math.abs(alpha) > this.maxRotationPerStep) { + if (this.maxRotation != null) { + if (Math.abs(alpha) > this.maxRotation) { + console.log("limited rotation"); alpha = 0; } } diff --git a/dist/iwmlib.pixi.js b/dist/iwmlib.pixi.js index 7ca6108..aba397f 100644 --- a/dist/iwmlib.pixi.js +++ b/dist/iwmlib.pixi.js @@ -6385,8 +6385,9 @@ if (delta != null) { this.addVelocity(delta); let alpha = delta.rotate; - if (this.maxRotationPerStep != null) { - if (Math.abs(alpha) > this.maxRotationPerStep) { + if (this.maxRotation != null) { + if (Math.abs(alpha) > this.maxRotation) { + console.log("limited rotation"); alpha = 0; } } diff --git a/lib/scatter.js b/lib/scatter.js index 5788272..ceb4c23 100644 --- a/lib/scatter.js +++ b/lib/scatter.js @@ -351,8 +351,9 @@ export class AbstractScatter extends Throwable { if (delta != null) { this.addVelocity(delta) let alpha = delta.rotate - if (this.maxRotationPerStep != null) { - if (Math.abs(alpha) > this.maxRotationPerStep) { + if (this.maxRotation != null) { + if (Math.abs(alpha) > this.maxRotation) { + console.log("limited rotation") alpha = 0 } } From 357153b978b21f7bd028c9d549cf4ed1f28ce79d Mon Sep 17 00:00:00 2001 From: Sebastian Kupke Date: Fri, 5 Jul 2019 08:35:32 +0200 Subject: [PATCH 04/17] Added lint files. --- .eslintignore | 2 ++ .eslintrc.json | 35 +++++++++++++++++++++++++++++++++++ .stylelintignore | 3 +++ .stylelintrc.json | 11 +++++++++++ 4 files changed, 51 insertions(+) create mode 100644 .eslintignore create mode 100644 .eslintrc.json create mode 100644 .stylelintignore create mode 100644 .stylelintrc.json diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..1f63996 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +dist/* +doc/out/* diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..10d290e --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,35 @@ +{ + "parserOptions": { + "ecmaVersion": 10, + "sourceType": "module", + "ecmaFeatures": { + "impliedStrict": true + } + }, + "plugins": [ + "mocha" + ], + "env": { + "browser": true, + "es6": true, + "node": true, + "jquery": true + }, + "globals": { + "PIXI": false, + "TweenLite": false, + "TweenMax": false, + "TimelineLite": false, + "TimelineMax": false, + "SystemJS": false + }, + "extends": "eslint:recommended", + "rules": { + "semi": ["error", "never"], + "quotes": ["warn", "single", {"allowTemplateLiterals": true}], + "no-console": "warn", + "no-unused-vars": ["warn", {"argsIgnorePattern": "^(e|event|points|ended)$"}], + "indent": ["warn", 4, {"SwitchCase": 1}], + "mocha/no-exclusive-tests": "error" + } +} diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 0000000..2e03c6e --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,3 @@ +dist/* +doc/* +lib/* diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000..edfefdd --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,11 @@ +{ + "extends": "stylelint-config-standard", + "rules": { + "indentation": 4, + "selector-list-comma-newline-after": "never-multi-line", + "no-eol-whitespace": [true, { + "ignore": ["empty-lines"] + }] + }, + "ignoreFiles": [] +} \ No newline at end of file From 22b0ceaff74c26fbd7b4246e0bbfcc93ee81ecff Mon Sep 17 00:00:00 2001 From: Sebastian Kupke Date: Fri, 5 Jul 2019 08:36:40 +0200 Subject: [PATCH 05/17] Improved gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8a247b2..577a2ac 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,4 @@ typings/ # own *.code-workspace .history/ +.vscode/ From fc6b30f03c52c07248b926d638873f93ffcc82da Mon Sep 17 00:00:00 2001 From: Sebastian Kupke Date: Fri, 5 Jul 2019 08:43:23 +0200 Subject: [PATCH 06/17] Fixed some linter problems. --- css/doctest.css | 48 +++++++++++++------------- css/flipeffect.css | 32 ++++++++--------- css/index.css | 86 +++++++++++++++++++++------------------------- rollup.config.js | 2 +- 4 files changed, 79 insertions(+), 89 deletions(-) diff --git a/css/doctest.css b/css/doctest.css index db2f0eb..eb904d3 100644 --- a/css/doctest.css +++ b/css/doctest.css @@ -1,12 +1,11 @@ -html -{ - padding: 0px; - font-size: 16px; - background: white; - font-family: Arial,sans-serif; - color: #000; - max-width: 932px; - margin:0 auto; +html { + padding: 0; + font-size: 16px; + background: white; + font-family: Arial, sans-serif; + color: #000; + max-width: 932px; + margin: 0 auto; } .grayBorder { @@ -24,10 +23,10 @@ html } .unselectable { - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; } body { @@ -44,28 +43,27 @@ canvas { margin-left: 8px; } - .intrinsic-container { - position: relative; - height: 0; - overflow: hidden; + position: relative; + height: 0; + overflow: hidden; } /* 16x9 Aspect Ratio */ .intrinsic-container-16x9 { - padding-bottom: 56.25%; + padding-bottom: 56.25%; } /* 4x3 Aspect Ratio */ .intrinsic-container-4x3 { - padding-bottom: 75%; + padding-bottom: 75%; } .intrinsic-container iframe { - position: absolute; - border: 0; - top:0; - left: 0; - width: 100%; - height: 100%; + position: absolute; + border: 0; + top: 0; + left: 0; + width: 100%; + height: 100%; } diff --git a/css/flipeffect.css b/css/flipeffect.css index 200c7df..650b29a 100644 --- a/css/flipeffect.css +++ b/css/flipeffect.css @@ -1,6 +1,5 @@ -.flipWrapper -{ +.flipWrapper { position: absolute; top: 0; left: 0; @@ -16,7 +15,8 @@ width: 100%; height: 100%; - /*** See: https://stackoverflow.com/questions/7439042/css-js-to-prevent-dragging-of-ghost-image ***/ + /*** See: https://stackoverflow.com/questions/7439042/css-js-to-prevent-dragging-of-ghost-image ***/ + /* -webkit-user-drag: none; -khtml-user-drag: none; -moz-user-drag: none; @@ -24,21 +24,21 @@ user-drag: none; */ } -.flipFace{ +.flipFace { box-shadow: 2px 2px 10px #000; visibility: hidden; } -.front{ +.front { width: 100%; height: 100%; - position:absolute; - background-color:#333; + position: absolute; + background-color: #333; } -.back{ - background-color:#333; - position:absolute; +.back { + background-color: #333; + position: absolute; border: 8px solid white; } @@ -48,8 +48,8 @@ width: 44px; height: 44px; padding: 4px; - right: 0px; - top: 0px; + right: 0; + top: 0; } .infoBtn { @@ -58,8 +58,8 @@ width: 44px; height: 44px; padding: 4px; - right: 0px; - bottom: 0px; + right: 0; + bottom: 0; } .backBtn { @@ -68,6 +68,6 @@ width: 44px; height: 44px; padding: 4px; - right: 0px; - bottom: 0px; + right: 0; + bottom: 0; } diff --git a/css/index.css b/css/index.css index e8f5323..10cff7f 100644 --- a/css/index.css +++ b/css/index.css @@ -1,19 +1,18 @@ html { height: 100%; width: 100%; - margin: 0px; + margin: 0; } -body -{ - margin: 0px; - padding: 0px; +body { + margin: 0; + padding: 0; width: 100%; height: 100%; font-family: sans-serif; font-size: 22pt; -webkit-tap-highlight-color: #ccc; - background-color: #DDD; + background-color: #ddd; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; @@ -22,24 +21,22 @@ body user-select: none; -webkit-hyphens: auto; hyphens: auto; + /* https://davidwalsh.name/font-smoothing */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - } -h3 -{ +h3 { color: white; padding: 4px; margin: 2px; - background-color: rgba(0, 0, 15, .5); + background-color: rgba(0, 0, 15, 0.5); } a { text-decoration: none; } -div.wrapper -{ +div.wrapper { overflow: hidden; width: 100%; height: 100%; @@ -49,29 +46,32 @@ div.wrapper /* Color animation from https://www.tjvantoll.com/2012/02/20/css3-color-animations/ */ @-webkit-keyframes color_change { - from { background-color: rgba(0, 0, 0, 0); } - to { background-color: red; } + from { background-color: rgba(0, 0, 0, 0); } + to { background-color: red; } } + @-moz-keyframes color_change { - from { background-color: rgba(0, 0, 0, 0); } - to { background-color: red; } + from { background-color: rgba(0, 0, 0, 0); } + to { background-color: red; } } + @-ms-keyframes color_change { - from { background-color: rgba(0, 0, 0, 0); } - to { background-color: red; } + from { background-color: rgba(0, 0, 0, 0); } + to { background-color: red; } } + @-o-keyframes color_change { - from { background-color: rgba(0, 0, 0, 0); } - to { background-color: red; } + from { background-color: rgba(0, 0, 0, 0); } + to { background-color: red; } } + @keyframes color_change { - from { background-color:rgba(0, 0, 0, 0); } - to { background-color: red; } + from { background-color: rgba(0, 0, 0, 0); } + to { background-color: red; } } /*** CSS taken from https://medium.com/@jamesfuthey/simulating-the-creation-of-website-thumbnail-screenshots-using-iframes-7145269891db#.7v7fshos5 ***/ -.thumbnail -{ +.thumbnail { position: relative; -ms-zoom: 0.25; -moz-transform: scale(0.25); @@ -82,9 +82,7 @@ div.wrapper -webkit-transform-origin: 0 0; } - -.thumbnail:after -{ +.thumbnail::after { content: ""; display: block; position: absolute; @@ -95,11 +93,14 @@ div.wrapper jamesfuthey blog. Otherwise touches would go through on iPad. ***/ } -.thumbnail iframe -{ +iframe { + pointer-events: none; +} + +.thumbnail iframe { width: 1024px; height: 624px; - -webkit-animation-delay: 3s; /* Safari 4.0 - 8.0 */ + -webkit-animation-delay: 3s; /* Safari 4.0 - 8.0 */ animation-delay: 3s; -webkit-animation: color_change 1s infinite alternate; -moz-animation: color_change 1s infinite alternate; @@ -108,22 +109,20 @@ div.wrapper animation: color_change 1s infinite alternate; } -.thumbnail-container -{ +.thumbnail-container { width: calc(1024px * 0.25); height: calc(624px * 0.25); display: inline-block; overflow: hidden; position: relative; box-shadow: 2px 2px 10px #000; - color: #DDD; + color: #ddd; } -div.preview -{ +div.preview { display: inline-block; margin: 22px; - padding: 0px; + padding: 0; color: #333; font-size: 12pt; text-align: center; @@ -131,8 +130,7 @@ div.preview height: 196px; } -div.title -{ +div.title { padding-top: 8px; width: 256px; height: 20px; @@ -140,10 +138,9 @@ div.title overflow: hidden; } -.container -{ - margin: 0px; - padding: 0px; +.container { + margin: 0; + padding: 0; border: 2pt #000; min-height: 100%; min-width: 100%; @@ -156,12 +153,7 @@ div.title align-content: flex-end; } -iframe { - pointer-events: none; -} - /** See https://github.com/electron/electron/issues/4420 */ ::selection { background: transparent; } - diff --git a/rollup.config.js b/rollup.config.js index eac5f49..3ca5fdf 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -19,4 +19,4 @@ export default [{ watch: { clearScreen: false } -}]; +}] From 4f35bfd51fd45f5db2419bb6470b03b0ed393118 Mon Sep 17 00:00:00 2001 From: Uwe Oestermeier Date: Fri, 5 Jul 2019 09:17:52 +0200 Subject: [PATCH 07/17] Fixed eslint problems and cleaned DOMScatter.onTap code --- dist/iwmlib.js | 4417 +++++++++++++++++++++---------------------- dist/iwmlib.pixi.js | 202 +- lib/interaction.js | 87 +- lib/scatter.js | 138 +- 4 files changed, 2359 insertions(+), 2485 deletions(-) diff --git a/dist/iwmlib.js b/dist/iwmlib.js index db06854..50a3b26 100644 --- a/dist/iwmlib.js +++ b/dist/iwmlib.js @@ -1614,7 +1614,7 @@ } } - /* globals Hammer, propagating */ + /* eslint-disable no-unused-vars */ /** Interaction patterns @@ -1622,6 +1622,7 @@ */ class IInteractionTarget extends Interface { + capture(event) { return typeof true } @@ -1825,10 +1826,10 @@ let d1 = Points.subtract(c1, p1); let d2 = Points.subtract(c2, p2); let cm = Points.mean(c1, c2); - + // Using the mean leads to jumps between time slices with 3 and 2 fingers // We use the mean of deltas instead - let delta = Points.mean(d1, d2); + let delta = Points.mean(d1, d2); let zoom = 1.0; let distance1 = Points.distance(p1, p2); let distance2 = Points.distance(c1, c2); @@ -2032,7 +2033,6 @@ } let result = false; if (this.isTap(key)) { - this.registerTap(key, ended); result = this.tapCounts.get(key) == 2; } @@ -2146,7 +2146,9 @@ if (this.capturePointerEvents) { try { element.setPointerCapture(e.pointerId); - } catch (e) { } + } catch (e) { + console.warn('Cannot setPointerCapture'); + } } this.onStart(e); } @@ -2178,7 +2180,9 @@ if (this.capturePointerEvents) { try { element.releasePointerCapture(e.pointerId); - } catch (e) { } + } catch (e) { + console.warn('Cannot release pointer'); + } } }, useCapture @@ -2222,7 +2226,7 @@ e => { if (this.debug) console.log('pointerout', e.pointerId, e.pointerType, e.target); if (e.target == element) { - this.onEnd(e); + this.onEnd(e); } }, useCapture); @@ -2325,9 +2329,8 @@ e => { if (e.target == element) { this.onEnd(e); - console.warn("Shouldn't happen: mouseout ends interaction"); + console.warn('Shouldn\'t happen: mouseout ends interaction'); } - }, useCapture ); @@ -2427,39 +2430,42 @@ // 'targetTouches' let result = {}; switch (event.constructor.name) { - case 'MouseEvent': - let buttons = event.buttons || event.which; - if (buttons) result['mouse'] = this.getPosition(event); - break - case 'PointerEvent': - result[event.pointerId.toString()] = this.getPosition(event); - break - case 'Touch': - let id = + case 'MouseEvent': { + let buttons = event.buttons || event.which; + if (buttons) result['mouse'] = this.getPosition(event); + break + } + case 'PointerEvent': { + result[event.pointerId.toString()] = this.getPosition(event); + break + } + case 'Touch': { + let id = event.touchType === 'stylus' ? 'stylus' : event.identifier.toString(); - result[id] = this.getPosition(event); - break - // case 'TouchEvent': - // // Needs to be observed: Perhaps changedTouches are all we need. If so - // // we can remove the touchEventKey default parameter - // if (touchEventKey == 'all') { - // for(let t of event.targetTouches) { - // result[t.identifier.toString()] = this.getPosition(t) - // } - // for(let t of event.changedTouches) { - // result[t.identifier.toString()] = this.getPosition(t) - // } - // } - // else { - // for(let t of event.changedTouches) { - // result[t.identifier.toString()] = this.getPosition(t) - // } - // } - // break - default: - break + result[id] = this.getPosition(event); + break + } + // case 'TouchEvent': + // // Needs to be observed: Perhaps changedTouches are all we need. If so + // // we can remove the touchEventKey default parameter + // if (touchEventKey == 'all') { + // for(let t of event.targetTouches) { + // result[t.identifier.toString()] = this.getPosition(t) + // } + // for(let t of event.changedTouches) { + // result[t.identifier.toString()] = this.getPosition(t) + // } + // } + // else { + // for(let t of event.changedTouches) { + // result[t.identifier.toString()] = this.getPosition(t) + // } + // } + // break + default: + break } return result } @@ -2487,7 +2493,7 @@ let point = extracted[key]; let updated = this.interaction.update(key, point); if (updated) { - console.warn("new pointer in updateInteraction shouldn't happen", key); + console.warn('new pointer in updateInteraction shouldn\'t happen', key); this.interactionStarted(event, key, point); } } @@ -2872,2193 +2878,6 @@ window.Capabilities = Capabilities; window.CapabilitiesTests = CapabilitiesTests; - /** - * A base class for scatter specific events. - * - * @constructor - * @param {name} String - The name of the event - * @param {target} Object - The target of the event - */ - class BaseEvent { - constructor(name, target) { - this.name = name; - this.target = target; - } - } - - // Event types - const START = 'onStart'; - const UPDATE = 'onUpdate'; - const END = 'onEnd'; - - /** - * A scatter event that describes how the scatter has changed. - * - * @constructor - * @param {target} Object - The target scatter of the event - * @param {optional} Object - Optional parameter - */ - class ScatterEvent extends BaseEvent { - constructor( - target, - { - translate = { x: 0, y: 0 }, - scale = null, - rotate = 0, - about = null, - fast = false, - type = null - } = {} - ) { - super('scatterTransformed', { target: target }); - this.translate = translate; - this.scale = scale; - this.rotate = rotate; - this.about = about; - this.fast = fast; - this.type = type; - } - - toString() { - return ( - "Event('scatterTransformed', scale: " + - this.scale + - ' about: ' + - this.about.x + - ', ' + - this.about.y + - ')' - ) - } - } - - /** - * A scatter resize event that describes how the scatter has changed. - * - * @constructor - * @param {target} Object - The target scatter of the event - * @param {optional} Object - Optional parameter - */ - class ResizeEvent extends BaseEvent { - constructor(target, { width = 0, height = 0 } = {}) { - super('scatterResized', { width: width, height: height }); - this.width = width; - this.height = height; - } - - toString() { - return ( - 'Event(scatterResized width: ' + - this.width + - 'height: ' + - this.height + - ')' - ) - } - } - - /** - * A abstract base class that implements the throwable behavior of a scatter - * object. - * - * @constructor - */ - class Throwable { - constructor({ - movableX = true, - movableY = true, - throwVisibility = 44, - throwDamping = 0.95, - autoThrow = true, - onThrowFinished = null - } = {}) { - this.movableX = movableX; - this.movableY = movableY; - this.throwVisibility = throwVisibility; - this.throwDamping = throwDamping; - this.autoThrow = autoThrow; - this.velocities = []; - this.velocity = null; - this.timestamp = null; - this.onThrowFinished = onThrowFinished; - //console.log("onThrowFinished", onThrowFinished) - } - - observeVelocity() { - this.lastframe = performance.now(); - } - - addVelocity(delta, buffer = 5) { - let t = performance.now(); - let dt = t - this.lastframe; - this.lastframe = t; - if (dt > 0) { - // Avoid division by zero errors later on - // and consider the number of involved pointers sind addVelocity will be called by the - // onMove events - let velocity = { t: t, dt: dt, dx: delta.x / delta.number, dy: delta.y / delta.number}; - this.velocities.push(velocity); - while (this.velocities.length > buffer) { - this.velocities.shift(); - } - } - } - - meanVelocity(milliseconds = 30) { - this.addVelocity({ x: 0, y: 0, number: 1 }); - let sum = { x: 0, y: 0 }; - let count = 0; - let t = 0; - for (let i = this.velocities.length - 1; i > 0; i--) { - let v = this.velocities[i]; - t += v.dt; - let nv = { x: v.dx / v.dt, y: v.dy / v.dt }; - sum = Points.add(sum, nv); - count += 1; - if (t > milliseconds) { - break - } - } - if (count === 0) return sum // empty vector - return Points.multiplyScalar(sum, 1 / count) - } - - killAnimation() { - this.velocity = null; - this.velocities = []; - } - - startThrow() { - this.velocity = this.meanVelocity(); - if (this.velocity != null) { - // Call next velocity to ansure that specializations - // that use keepOnStage are called - this.velocity = this.nextVelocity(this.velocity); - if (this.autoThrow) this.animateThrow(performance.now()); - } else { - this.onDragComplete(); - } - } - - _throwDeltaTime() { - let t = performance.now(); - let dt = t - this.lastframe; - this.lastframe = t; - return dt - } - - animateThrow(time) { - if (this.velocity != null) { - let dt = this._throwDeltaTime(); - // console.log("animateThrow", dt) - let next = this.nextVelocity(this.velocity); - let prevLength = Points.length(this.velocity); - let nextLength = Points.length(next); - if (nextLength > prevLength) { - let factor = nextLength / prevLength; - next = Points.multiplyScalar(next, 1 / factor); - console.log('Prevent acceleration', factor, this.velocity, next); - } - this.velocity = next; - let d = Points.multiplyScalar(this.velocity, dt); - this._move(d); - - this.onDragUpdate(d); - if (dt == 0 || this.needsAnimation()) { - requestAnimationFrame(this.animateThrow.bind(this)); - return - } else { - if (this.isOutside()) { - requestAnimationFrame(this.animateThrow.bind(this)); - return - } - } - } - this.onDragComplete(); - if (this.onThrowFinished != null) { - this.onThrowFinished(); - } - } - - needsAnimation() { - if (this.velocity == null) { - return false - } - return Points.length(this.velocity) > 0.01 - } - - nextVelocity(velocity) { - // Must be overwritten: computes the changed velocity. Implement - // damping, collison detection, etc. here - let next = Points.multiplyScalar(velocity, this.throwDamping); - return { - x: (this.movableX) ? next.x : 0, - y: (this.movableY) ? next.y : 0 - } - } - - _move(delta) { - // Overwrite if necessary - } - - onDragComplete() { - // Overwrite if necessary - } - - onDragUpdate(delta) { - // Overwrite if necessary - } - } - - class AbstractScatter extends Throwable { - constructor({ - minScale = 0.1, - maxScale = 1.0, - startScale = 1.0, - autoBringToFront = true, - autoThrow = true, - translatable = true, - scalable = true, - rotatable = true, - resizable = false, - movableX = true, - movableY = true, - throwVisibility = 44, - throwDamping = 0.95, - overdoScaling = 1, - mouseZoomFactor = 1.1, - rotationDegrees = null, - rotation = null, - onTransform = null, - interactive = true, - onClose = null, - onThrowFinished = null, - scaleAutoClose = false, - scaleCloseThreshold = 0.10, - scaleCloseBuffer = 0.05, - maxRotation = Angle.degree2radian(5) - } = {}) { - if (rotationDegrees != null && rotation != null) { - throw new Error('Use rotationDegrees or rotation but not both') - } else if (rotation != null) { - rotationDegrees = Angle.radian2degree(rotation); - } else if (rotationDegrees == null) { - rotationDegrees = 0; - } - super({ - movableX, - movableY, - throwVisibility, - throwDamping, - autoThrow, - onThrowFinished - }); - - /** - * Closes the card when the minScale is reached and the - * card is released. Card can be saved by scaling it up again. - */ - this.scaleAutoClose = scaleAutoClose; - this.scaleCloseThreshold = scaleCloseThreshold; - this.scaleCloseBuffer = scaleCloseBuffer; - this.scaleAutoCloseTimeout = null; - - this.interactive = interactive; - this.startRotationDegrees = rotationDegrees; - this.startScale = startScale; // Needed to reset object - this.minScale = minScale; - this.maxScale = maxScale; - this.maxRotation = maxRotation; - this.overdoScaling = overdoScaling; - this.translatable = translatable; - if (!translatable) { - this.movableX = false; - this.movableY = false; - } - this.scalable = scalable; - this.rotatable = rotatable; - this.resizable = resizable; - this.mouseZoomFactor = mouseZoomFactor; - this.autoBringToFront = autoBringToFront; - - this.dragging = false; - this.onTransform = onTransform != null ? [onTransform] : null; - this.onClose = onClose != null ? [onClose] : null; - } - - addCloseEventCallback(callback) { - if (this.onClose == null) { - this.onClose = []; - } - this.onClose.push(callback); - } - - addTransformEventCallback(callback) { - if (this.onTransform == null) { - this.onTransform = []; - } - this.onTransform.push(callback); - } - - startGesture(interaction) { - this.bringToFront(); - this.killAnimation(); - this.observeVelocity(); - return true - } - - close() { - if (this.onClose) { - this.onClose.forEach(callback => callback(this)); - } - } - - gesture(interaction) { - let delta = interaction.delta(); - if (delta != null) { - this.addVelocity(delta); - let alpha = delta.rotate; - if (this.maxRotation != null) { - if (Math.abs(alpha) > this.maxRotation) { - console.log("limited rotation"); - alpha = 0; - } - } - this.transform(delta, delta.zoom, alpha, delta.about); - if (delta.zoom != 1) this.interactionAnchor = delta.about; - } - } - - get polygon() { - let w2 = this.width * this.scale / 2; - let h2 = this.height * this.scale / 2; - let center = this.center; - let polygon = new Polygon(center); - polygon.addPoint({ x: -w2, y: -h2 }); - polygon.addPoint({ x: w2, y: -h2 }); - polygon.addPoint({ x: w2, y: h2 }); - polygon.addPoint({ x: -w2, y: h2 }); - polygon.rotate(this.rotation); - return polygon - } - - isOutside() { - let stagePolygon = this.containerPolygon; - if (stagePolygon == null) - return false - let polygon = this.polygon; - if (polygon == null) - return false - let result = stagePolygon.intersectsWith(polygon); - return result === false || result.overlap < this.throwVisibility - } - - recenter() { - // Return a small vector that guarantees that the scatter is moving - // towards the center of the stage - let center = this.center; - let target = this.container.center; - let delta = Points.subtract(target, center); - return Points.normalize(delta) - } - - nextVelocity(velocity) { - return this.keepOnStage(velocity) - } - - bouncing() { - // Implements the bouncing behavior of the scatter. Moves the scatter - // to the center of the stage if the scatter is outside the stage or - // not within the limits of the throwVisibility. - - let stagePolygon = this.containerPolygon; - let polygon = this.polygon; - let result = stagePolygon.intersectsWith(polygon); - if (result === false || result.overlap < this.throwVisibility) { - let cv = this.recenter(); - let recentered = false; - while (result === false || result.overlap < this.throwVisibility) { - polygon.center.x += cv.x; - polygon.center.y += cv.y; - this._move(cv); - result = stagePolygon.intersectsWith(polygon); - recentered = true; - } - return recentered - } - return false - } - - keepOnStage(velocity, collision = 0.5) { - let stagePolygon = this.containerPolygon; - // UO: since keepOnStage is called in nextVelocity we need to - // ensure a return value - if (!stagePolygon) return { x: 0, y: 0} - let polygon = this.polygon; - let bounced = this.bouncing(); - if (bounced) { - let stage = this.containerBounds; - let x = this.center.x; - let y = this.center.y; - let dx = this.movableX ? velocity.x : 0; - let dy = this.movableY ? velocity.y : 0; - let factor = this.throwDamping; - // if (recentered) { - if (x < 0) { - dx = -dx; - factor = collision; - } - if (x > stage.width) { - dx = -dx; - factor = collision; - } - if (y < 0) { - dy = -dy; - factor = collision; - } - if (y > stage.height) { - dy = -dy; - factor = collision; - } - // } - return Points.multiplyScalar({ x: dx, y: dy }, factor) - } - return super.nextVelocity(velocity) - } - - endGesture(interaction) { - this.startThrow(); - this._checkAutoClose(); - } - - _checkAutoClose() { - if (this.scaleAutoClose) - if (this.scale < this.minScale + this.scaleCloseThreshold - this.scaleCloseBuffer) { - this.zoom(this.minScale, { animate: 0.2, onComplete: this.close.bind(this) }); - } else if (this.scale < this.minScale + this.scaleCloseThreshold) { - this.zoom(this.minScale + this.scaleCloseThreshold, { animate: 0.4 }); - } - } - - rotateDegrees(degrees, anchor) { - let rad = Angle.degree2radian(degrees); - this.rotate(rad, anchor); - } - - rotate(rad, anchor) { - this.transform({ x: 0, y: 0 }, 1.0, rad, anchor); - } - - move(d, { animate = 0 } = {}) { - if (this.translatable) { - if (animate > 0) { - let startPos = this.position; - TweenLite.to(this, animate, { - x: '+=' + d.x, - y: '+=' + d.y, - /* scale: scale, uo: not defined, why was this here? */ - onUpdate: e => { - let p = this.position; - let dx = p.x - startPos.x; - let dy = p.x - startPos.y; - this.onMoved(dx, dy); - } - }); - } else { - this._move(d); - this.onMoved(d.x, d.y); - } - } - } - - moveTo(p, { animate = 0 } = {}) { - let c = this.origin; - let delta = Points.subtract(p, c); - this.move(delta, { animate: animate }); - } - - centerAt(p, { animate = 0 } = {}) { - let c = this.center; - let delta = Points.subtract(p, c); - this.move(delta, { animate: animate }); - } - - zoom( - scale, - { - animate = 0, - about = null, - delay = 0, - x = null, - y = null, - onComplete = null - } = {} - ) { - let anchor = about || this.center; - if (scale != this.scale) { - if (animate > 0) { - TweenLite.to(this, animate, { - scale: scale, - delay: delay, - onComplete: onComplete, - onUpdate: this.onZoomed.bind(this) - }); - } else { - this.scale = scale; - this.onZoomed(anchor); - } - } - } - - _move(delta) { - this.x += this.movableX ? delta.x : 0; - this.y += this.movableX ? delta.y : 0; - } - - transform(translate, zoom, rotate, anchor) { - let delta = { - x: this.movableX ? translate.x : 0, - y: this.movableY ? translate.y : 0 - }; - if (this.resizable) var vzoom = zoom; - if (!this.translatable) delta = { x: 0, y: 0 }; - if (!this.rotatable) rotate = 0; - if (!this.scalable) zoom = 1.0; - if (zoom == 1.0 && rotate == 0) { - this._move(delta); - if (this.onTransform != null) { - let event = new ScatterEvent(this, { - translate: delta, - scale: this.scale, - rotate: 0, - about: anchor, - fast: false, - type: UPDATE - }); - this.onTransform.forEach(function (f) { - f(event); - }); - } - return - } - let origin = this.rotationOrigin; - let beta = Points.angle(origin, anchor); - let distance = Points.distance(origin, anchor); - let { scale: newScale, zoom: thresholdedZoom } = this.calculateScale(zoom); - - let newOrigin = Points.arc(anchor, beta + rotate, distance * thresholdedZoom); - let extra = Points.subtract(newOrigin, origin); - let offset = Points.subtract(anchor, origin); - this._move(offset); - this.scale = newScale; - this.rotation += rotate; - offset = Points.negate(offset); - offset = Points.add(offset, extra); - offset = Points.add(offset, translate); - this._move(offset); - - delta.x += extra.x; - delta.y += extra.y; - if (this.onTransform != null) { - let event = new ScatterEvent(this, { - translate: delta, - scale: newScale, - rotate: rotate, - about: anchor - }); - this.onTransform.forEach(function (f) { - f(event); - }); - } - if (this.resizable) { - this.resizeAfterTransform(vzoom); - } - } - - /** - * For a given zoom, a new scale is calculated, taking - * min and max scale into account. - * - * @param {number} zoom - The zoom factor, to scale the object with. - * @returns {object} - Returns an object containing the a value for a valid scale and the corrected zoom factor. - */ - calculateScale(zoom) { - let scale = this.scale * zoom; - - let minScale = this.minScale / this.overdoScaling; - let maxScale = this.maxScale * this.overdoScaling; - if (scale < minScale) { - scale = minScale; - zoom = scale / this.scale; - } - if (scale > maxScale) { - scale = maxScale; - zoom = scale / this.scale; - } - - if (this.scaleAutoClose) - this._updateTransparency(); - - return { zoom, scale } - } - - _updateTransparency() { - if (this.scale < this.minScale + this.scaleCloseThreshold) { - let transparency = this.calculateScaleTransparency(); - this.element.style.opacity = transparency; - } else this.element.style.opacity = 1; - } - - calculateScaleTransparency() { - let transparency = (this.scale - this.minScale) / this.scaleCloseThreshold; - transparency = (transparency > 1) ? 1 : (transparency < 0) ? 0 : transparency; - return transparency - } - - resizeAfterTransform(zoom) { - // Overwrite this in subclasses. - } - - validScale(scale) { - scale = Math.max(scale, this.minScale); - scale = Math.min(scale, this.maxScale); - return scale - } - - animateZoomBounce(dt = 1) { - if (this.zoomAnchor != null) { - let zoom = 1; - let amount = Math.min(0.01, 0.3 * dt / 100000.0); - if (this.scale < this.minScale) zoom = 1 + amount; - if (this.scale > this.maxScale) zoom = 1 - amount; - if (zoom != 1) { - this.transform({ x: 0, y: 0 }, zoom, 0, this.zoomAnchor); - requestAnimationFrame(dt => { - this.animateZoomBounce(dt); - }); - return - } - this.zoomAnchor = null; - } - } - - checkScaling(about, delay = 0) { - this.zoomAnchor = about; - clearTimeout(this.animateZoomBounce.bind(this)); - setTimeout(this.animateZoomBounce.bind(this), delay); - } - - onMouseWheel(event) { - if (event.claimedByScatter) { - if (event.claimedByScatter != this) return - } - this.killAnimation(); - this.targetScale = null; - let direction = event.detail < 0 || event.wheelDelta > 0; - let globalPoint = { x: event.clientX, y: event.clientY }; - let centerPoint = this.mapPositionToContainerPoint(globalPoint); - if (event.shiftKey) { - let degrees = direction ? 5 : -5; - let rad = Angle.degree2radian(degrees); - return this.transform({ x: 0, y: 0 }, 1.0, rad, centerPoint) - } - const zoomFactor = this.mouseZoomFactor; - let zoom = direction ? zoomFactor : 1 / zoomFactor; - this.transform({ x: 0, y: 0 }, zoom, 0, centerPoint); - this.checkScaling(centerPoint, 200); - - if (this.scaleAutoClose) { - if (this.scale <= this.minScale + this.scaleCloseThreshold) { - - if (this.scaleAutoCloseTimeout) clearTimeout(this.scaleAutoCloseTimeout); - this.scaleAutoCloseTimeout = setTimeout(() => { - this._checkAutoClose(); - }, 600); - } - this._updateTransparency(); - } - // - // if (this.onTransform != null) { - // let event = new ScatterEvent(this, { - // translate: {x: 0, y: 0}, - // scale: this.scale, - // rotate: 0, - // about: null, - // fast: false, - // type: ZOOM - // }) - // this.onTransform.forEach(function(f) { - // f(event) - // }) - // } - } - - onStart(event, interaction) { - - if (this.startGesture(interaction)) { - this.dragging = true; - this.interactionAnchor = null; - } - if (this.onTransform != null) { - let event = new ScatterEvent(this, { - translate: { x: 0, y: 0 }, - scale: this.scale, - rotate: 0, - about: null, - fast: false, - type: START - }); - this.onTransform.forEach(function (f) { - f(event); - }); - } - } - - onMove(event, interaction) { - /** As long as mouseout && mouseleave interrupt we cannot be sure that - * dragging remains correct. - */ - if (this.dragging) { - this.gesture(interaction); - } - } - - onEnd(event, interaction) { - //console.log("Scatter.onEnd", this.dragging) - if (interaction.isFinished()) { - this.endGesture(interaction); - this.dragging = false; - for (let key of interaction.ended.keys()) { - if (interaction.isTap(key)) { - let point = interaction.ended.get(key); - this.onTap(event, interaction, point); - } - } - if (this.onTransform != null) { - let event = new ScatterEvent(this, { - translate: { x: 0, y: 0 }, - scale: this.scale, - rotate: 0, - about: null, - fast: false, - type: END - }); - this.onTransform.forEach(function (f) { - f(event); - }); - } - } - let about = this.interactionAnchor; - if (about != null) { - this.checkScaling(about, 100); - } - } - - onTap(event, interaction, point) { } - - onDragUpdate(delta) { - if (this.onTransform != null) { - let event = new ScatterEvent(this, { - fast: true, - translate: delta, - scale: this.scale, - about: this.currentAbout, - type: null - }); - this.onTransform.forEach(function (f) { - f(event); - }); - } - } - - onDragComplete() { - if (this.onTransform) { - let event = new ScatterEvent(this, { - scale: this.scale, - about: this.currentAbout, - fast: false, - type: null - }); - this.onTransform.forEach(function (f) { - f(event); - }); - } - } - - onMoved(dx, dy, about) { - if (this.onTransform != null) { - let event = new ScatterEvent(this, { - translate: { x: dx, y: dy }, - about: about, - fast: true, - type: null - }); - this.onTransform.forEach(function (f) { - f(event); - }); - } - } - - onResizing() { - if (this.onTransform != null) { - let event = new ScatterEvent(this, { - scale: this.scale, - fast: false, - type: null - }); - this.onTransform.forEach(function (f) { - f(event); - }); - } - } - - onZoomed(about) { - - if (this.scaleAutoClose) - this._updateTransparency(); - - if (this.onTransform != null) { - let event = new ScatterEvent(this, { - scale: this.scale, - about: about, - fast: false, - type: null - }); - this.onTransform.forEach(function (f) { - f(event); - }); - } - } - } - - /** A container for scatter objects, which uses a single InteractionMapper - * for all children. This reduces the number of registered event handlers - * and covers the common use case that multiple objects are scattered - * on the same level. - */ - class DOMScatterContainer { - /** - * @constructor - * @param {DOM node} element - DOM element that receives events - * @param {Bool} stopEvents - Whether events should be stopped or propagated - * @param {Bool} claimEvents - Whether events should be marked as claimed - * if findTarget return as non-null value. - * @param {String} [touchAction=none] - CSS to set touch action style, needed to prevent - * pointer cancel events. Use null if the - * the touch action should not be set. - */ - constructor( - element, - { stopEvents = 'auto', claimEvents = true, useCapture = true, touchAction = 'none' } = {} - ) { - this.onCapture = null; - this.element = element; - if (stopEvents === 'auto') { - if (Capabilities.isSafari) { - document.addEventListener( - 'touchmove', - event => this.preventPinch(event), - false - ); - stopEvents = false; - } else { - stopEvents = true; - } - } - this.stopEvents = stopEvents; - this.claimEvents = claimEvents; - if (touchAction !== null) { - Elements$1.setStyle(element, { touchAction }); - } - this.scatter = new Map(); - this.delegate = new InteractionMapper$1(element, this, { - useCapture, - mouseWheelElement: window - }); - - if (typeof debugCanvas !== 'undefined') { - requestAnimationFrame(dt => { - this.showTouches(dt); - }); - } - } - - showTouches(dt) { - let resolution = window.devicePixelRatio; - let canvas = debugCanvas; - let current = this.delegate.interaction.current; - let context = canvas.getContext('2d'); - let radius = 20 * resolution; - context.clearRect(0, 0, canvas.width, canvas.height); - context.fillStyle = 'rgba(0, 0, 0, 0.3)'; - context.lineWidth = 2; - context.strokeStyle = '#003300'; - for (let [key, point] of current.entries()) { - let local = point; - context.beginPath(); - context.arc( - local.x * resolution, - local.y * resolution, - radius, - 0, - 2 * Math.PI, - false - ); - context.fill(); - context.stroke(); - } - requestAnimationFrame(dt => { - this.showTouches(dt); - }); - } - - preventPinch(event) { - event = event.originalEvent || event; - if (event.scale !== 1) { - event.preventDefault(); - } - } - - add(scatter) { - this.scatter.set(scatter.element, scatter); - } - - capture(event) { - if (this.onCapture) { - return this.onCapture(event) - } - if (event.target == this.element && this.stopEvents) { - Events.stop(event); - } - return true - } - - mapPositionToPoint(point) { - return Points.fromPageToNode(this.element, point) - } - - isDescendant(parent, child, clickable = false) { - if (parent == child) return true - let node = child.parentNode; - while (node != null) { - if (!clickable && node.onclick) { - return false - } - if (node == parent) { - return true - } - node = node.parentNode; - } - return false - } - - findTarget(event, local, global) { - /*** Note that elementFromPoint works with clientX, clientY, not pageX, pageY - The important point is that event should not be used, since the TouchEvent - points are hidden in sub objects. - ***/ - let found = document.elementFromPoint(global.x, global.y); - for (let target of this.scatter.values()) { - if (target.interactive && this.isDescendant(target.element, found)) { - if (this.stopEvents) Events.stop(event); - if (this.claimEvents) event.claimedByScatter = target; - return target - } - } - return null - } - - get center() { - let r = this.bounds; - let w2 = r.width / 2; - let h2 = r.height / 2; - return { x: w2, y: h2 } - } - - get bounds() { - return this.element.getBoundingClientRect() - } - - get polygon() { - let r = this.bounds; - let w2 = r.width / 2; - let h2 = r.height / 2; - let center = { x: w2, y: h2 }; - let polygon = new Polygon(center); - polygon.addPoint({ x: -w2, y: -h2 }); - polygon.addPoint({ x: w2, y: -h2 }); - polygon.addPoint({ x: w2, y: h2 }); - polygon.addPoint({ x: -w2, y: h2 }); - return polygon - } - } - - - class DOMScatter extends AbstractScatter { - constructor( - element, - container, - { - startScale = 1.0, - minScale = 0.1, - maxScale = 1.0, - overdoScaling = 1.5, - 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, // required - height = null, // required - resizable = false, - simulateClick = false, - verbose = true, - onResize = null, - touchAction = 'none', - throwVisibility = 44, - throwDamping = 0.95, - autoThrow = true, - scaleAutoClose = false, - onClose = null, - scaleCloseThreshold = 0.10, - scaleCloseBuffer = 0.05 - } = {} - ) { - super({ - minScale, - maxScale, - startScale, - overdoScaling, - autoBringToFront, - translatable, - scalable, - rotatable, - movableX, - movableY, - resizable, - rotationDegrees, - rotation, - onTransform, - throwVisibility, - throwDamping, - autoThrow, - scaleAutoClose, - scaleCloseThreshold, - scaleCloseBuffer, - onClose - }); - if (container == null || width == null || height == null) { - throw new Error('Invalid value: null') - } - element.scatter = this; - this.element = element; - this.x = x; - this.y = y; - this.oldX = 0; - this.oldY = 0; - this.meanX = x; - this.meanY = y; - this.width = width; - this.height = height; - this.throwVisibility = Math.min(width, height, throwVisibility); - this.container = container; - this.simulateClick = simulateClick; - this.scale = startScale; - this.rotationDegrees = this.startRotationDegrees; - this.transformOrigin = transformOrigin; - this.initialValues = { - x: x, - y: y, - width: width, - height: height, - scale: startScale, - rotation: this.startRotationDegrees, - transformOrigin: transformOrigin - }; - - - // For tweenlite we need initial values in _gsTransform - TweenLite.set(element, this.initialValues); - this.onResize = onResize; - this.verbose = verbose; - if (touchAction !== null) { - Elements$1.setStyle(element, { touchAction }); - } - this.resizeButton = null; - if (resizable) { - let button = document.createElement("div"); - button.style.position = "absolute"; - button.style.right = "0px"; - button.style.bottom = "0px"; - button.style.width = "50px"; - button.style.height = "50px"; - // button.style.borderRadius = "100% 0px 0px 0px"; - // button.style.background = this.element.style.backgroundColor - button.className = "interactiveElement"; - this.element.appendChild(button); - - button.addEventListener('pointerdown', (e) => { - this.startResize(e); - }); - - button.addEventListener('pointermove', (e) => { - this.resize(e); - }); - - button.addEventListener('pointerup', (e) => { - this.stopResize(e); - }); - this.resizeButton = button; - } - container.add(this); - } - - /** Returns geometry data as object. **/ - getState() { - return { - scale: this.scale, - x: this.x, - y: this.y, - rotation: this.rotation - } - } - - close() { - super.close(); - let parent = this.element.parentNode; - if (parent) parent.removeChild(this.element); - } - - get rotationOrigin() { - return this.center - } - - get x() { - return this._x - } - - get y() { - return this._y - } - - set x(value) { - this._x = value; - TweenLite.set(this.element, { x: value }); - } - - set y(value) { - this._y = value; - TweenLite.set(this.element, { y: value }); - } - - get position() { - let transform = this.element._gsTransform; - let x = transform.x; - let y = transform.y; - return { x, y } - } - - get origin() { - let p = this.fromNodeToPage(0, 0); - return Points.fromPageToNode(this.container.element, p) - } - - get bounds() { - let stage = this.container.element.getBoundingClientRect(); - let rect = this.element.getBoundingClientRect(); - return { - top: rect.top - stage.top, - left: rect.left - stage.left, - width: rect.width, - height: rect.height - } - } - - get center() { - let r = this.bounds; - let w2 = r.width / 2; - let h2 = r.height / 2; - // if (this.resizable) { - // w2 *= this.scale - // h2 *= this.scale - // } - var x = r.left + w2; - var y = r.top + h2; - return { x, y } - } - - set rotation(radians) { - let rad = radians; // Angle.normalize(radians) - let degrees = Angle.radian2degree(rad); - TweenLite.set(this.element, { rotation: degrees }); - this._rotation = rad; - } - - set rotationDegrees(degrees) { - let deg = degrees; // Angle.normalizeDegree(degrees) - TweenLite.set(this.element, { rotation: deg }); - this._rotation = Angle.degree2radian(deg); - } - - get rotation() { - return this._rotation - } - - get rotationDegrees() { - return this._rotation - } - - set scale(scale) { - TweenLite.set(this.element, { - scale: scale, - transformOrigin: this.transformOrigin - }); - this._scale = scale; - } - - get scale() { - return this._scale - } - - get containerBounds() { - return this.container.bounds - } - - get containerPolygon() { - return this.container.polygon - } - - mapPositionToContainerPoint(point) { - return this.container.mapPositionToPoint(point) - } - - capture(event) { - return true - } - - reset() { - TweenLite.set(this.element, this.initialValues); - } - - hide() { - TweenLite.to(this.element, 0.1, { - display: 'none', - onComplete: e => { - this.element.parentNode.removeChild(this.element); - } - }); - } - - show() { - TweenLite.set(this.element, { display: 'block' }); - } - - showAt(p, rotationDegrees) { - TweenLite.set(this.element, { - display: 'block', - x: p.x, - y: p.y, - rotation: rotationDegrees, - transformOrigin: this.transformOrigin - }); - } - - bringToFront() { - // this.element.parentNode.appendChild(this.element) - // uo: On Chome and Electon appendChild leads to flicker - TweenLite.set(this.element, { zIndex: DOMScatter.zIndex++ }); - } - - toggleVideo(element) { - if (element.paused) { - element.play(); - } else { - element.pause(); - } - } - - onTap(event, interaction, point) { - if (this.simulateClick) { - let p = Points.fromPageToNode(this.element, point); - let iframe = this.element.querySelector('iframe'); - if (iframe) { - let doc = iframe.contentWindow.document; - let element = doc.elementFromPoint(p.x, p.y); - if (element == null) { - return - } - switch (element.tagName) { - case 'VIDEO': - console.log(element.currentSrc); - if (PopupMenu) { - PopupMenu.open( - { - Fullscreen: () => - window.open(element.currentSrc), - Play: () => element.play() - }, - { x, y } - ); - } else { - this.toggleVideo(element); - } - break - default: - element.click(); - } - } - } - } - - isDescendant(parent, child) { - let node = child.parentNode; - while (node != null) { - if (node == parent) { - return true - } - node = node.parentNode; - } - return false - } - - fromPageToNode(x, y) { - return Points.fromPageToNode(this.element, { x, y }) - } - - fromNodeToPage(x, y) { - return Points.fromNodeToPage(this.element, { x, y }) - } - - _move(delta) { - // UO: We need to keep TweenLite's _gsTransform and the private - // _x and _y attributes aligned - let x = this.element._gsTransform.x; - let y = this.element._gsTransform.y; - if (this.movableX) { - x += delta.x; - } - if (this.movableY) { - y += delta.y; - } - this._x = x; - this._y = y; - TweenLite.set(this.element, { x: x, y: y }); - } - - resizeAfterTransform(zoom) { - // let w = this.width * this.scale - // let h = this.height * this.scale - // TweenLite.set(this.element, { width: w, height: h }) - if (this.onResize) { - let event = new ResizeEvent(this, { width: w, height: h }); - this.onResize(event); - } - if (this.resizeButton != null) ; - } - - startResize(e) { - e.preventDefault(); - let event = new CustomEvent('resizeStarted'); - - let oldPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top}; - this.bringToFront(); - - this.element.style.transformOrigin = "0% 0%"; - - let newPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top}; - - let offset = Points.subtract(oldPostition, newPostition); - - this.oldX = e.clientX; - this.oldY = e.clientY; - - e.target.setAttribute('resizing', "true"); - e.target.setPointerCapture(e.pointerId); - - TweenLite.to(this.element, 0, { css: { left: "+=" + offset.x + "px" } }); - TweenLite.to(this.element, 0, { css: { top: "+=" + offset.y + "px" } }); - - this.element.dispatchEvent(event); - } - - resize(e) { - e.preventDefault(); - - let rotation = Angle.radian2degree(this.rotation); - rotation = (rotation + 360) % 360; - let event = new CustomEvent('resized'); - if (e.target.getAttribute('resizing') == "true") { - - let deltaX = (e.clientX - this.oldX); - let deltaY = (e.clientY - this.oldY); - - let r = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); - let phi = Angle.radian2degree(Math.atan2(deltaX, deltaY)); - - phi = ((phi) + 630) % 360; - let rot = ((rotation + 90) + 630) % 360; - - let diffAngle = ((0 + rot) + 360) % 360; - let phiCorrected = (phi + diffAngle + 360) % 360; - - let resizeW = r * Math.cos(Angle.degree2radian(phiCorrected)); - let resizeH = -r * Math.sin(Angle.degree2radian(phiCorrected)); - - if ((this.element.offsetWidth + resizeW) / this.scale > this.width * 0.5 / this.scale && (this.element.offsetHeight + resizeH) / this.scale > this.height * 0.3 / this.scale) TweenLite.to(this.element, 0, { width: this.element.offsetWidth + resizeW / this.scale, height: this.element.offsetHeight + resizeH / this.scale }); - - this.oldX = e.clientX; - this.oldY = e.clientY; - this.onResizing(); - - this.element.dispatchEvent(event); - } - } - - stopResize(e) { - e.preventDefault(); - - let event = new CustomEvent('resizeEnded'); - let oldPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top}; - this.element.style.transformOrigin = "50% 50%"; - let newPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top}; - let offset = Points.subtract(oldPostition, newPostition); - - TweenLite.to(this.element, 0, { css: { left: "+=" + offset.x + "px" } }); - TweenLite.to(this.element, 0, { css: { top: "+=" + offset.y + "px" } }); - - e.target.setAttribute('resizing', "false"); - - this.element.dispatchEvent(event); - } - } - - DOMScatter.zIndex = 1000; - - class CardLoader { - constructor( - src, - { - x = 0, - y = 0, - width = 1000, - height = 800, - maxWidth = null, - maxHeight = null, - scale = 1, - minScale = 0.5, - maxScale = 1.5, - rotation = 0 - } = {} - ) { - this.src = src; - this.x = x; - this.y = y; - this.scale = scale; - this.rotation = 0; - this.maxScale = maxScale; - this.minScale = minScale; - this.wantedWidth = width; - this.wantedHeight = height; - this.maxWidth = maxWidth != null ? maxWidth : window.innerWidth; - this.maxHeight = maxHeight != null ? maxHeight : window.innerHeight; - this.addedNode = null; - console.log({ - - width, - height, - maxWidth, - maxHeight, - - }); - } - - unload() { - if (this.addedNode) { - this.addedNode.remove(); - this.addedNode = null; - } - } - } - - class PDFLoader extends CardLoader { - constructor(src, { width = 1640, height = 800, scale = 1 } = {}) { - super(src, { width, height, scale }); - if (typeof PDFJS == 'undefined') { - alert('PDF.js needed'); - } - } - - load(domNode) { - return new Promise((resolve, reject) => { - PDFJS.getDocument(this.src).then(pdf => { - pdf.getPage(1).then(page => { - let scale = this.scale * app.renderer.resolution; - let invScale = 1 / scale; - let viewport = page.getViewport(scale); - - // Prepare canvas using PDF page dimensions. - let canvas = document.createElement('canvas'); - let context = canvas.getContext('2d'); - canvas.height = viewport.height; - canvas.width = viewport.width; - - // Render PDF page into canvas context. - let renderContext = { - canvasContext: context, - viewport: viewport - }; - page.render(renderContext); - domNode.appendChild(canvas); - this.wantedWidth = canvas.width; - this.wantedHeight = canvas.height; - this.scale = invScale; - this.addedNode = canvas; - resolve(this); - }); - }); - }) - } - } - - class ImageLoader extends CardLoader { - load(domNode) { - return new Promise((resolve, reject) => { - let isImage = domNode instanceof HTMLImageElement; - let image = isImage ? domNode : document.createElement('img'); - image.onload = e => { - if (!isImage) { - domNode.appendChild(image); - this.addedNode = image; - } - this.wantedWidth = image.naturalWidth; - this.wantedHeight = image.naturalHeight; - - let scaleW = this.maxWidth / image.naturalWidth; - let scaleH = this.maxHeight / image.naturalHeight; - this.scale = Math.min(this.maxScale, Math.min(scaleW, scaleH)); - image.setAttribute('draggable', false); - image.width = image.naturalWidth; - image.height = image.naturalHeight; - resolve(this); - }; - image.onerror = e => { - reject(this); - }; - image.src = this.src; - }) - } - } - - class FrameLoader extends CardLoader { - load(domNode) { - return new Promise((resolve, reject) => { - let isFrame = domNode instanceof HTMLIFrameElement; - let iframe = isFrame ? domNode : document.createElement('iframe'); - console.log('FrameLoader.load', isFrame, iframe, this.src); - iframe.frameBorder = 0; - iframe.style.scrolling = false; - iframe.width = this.wantedWidth; - iframe.height = this.wantedHeight; - if (!isFrame) { - // Unlike img onload is only triggered if the iframe is part of the DOM tree - domNode.appendChild(iframe); - this.addedNode = iframe; - } - iframe.onload = e => { - resolve(this); - }; - iframe.onerror = e => { - reject(this); - }; - iframe.src = this.src; - }) - } - } - - class HTMLLoader extends CardLoader { - load(domNode) { - return new Promise((resolve, reject) => { - let xhr = new XMLHttpRequest(); - xhr.open('GET', this.src, false); - xhr.onload = e => { - domNode.innerHTML = xhr.response; - this.addedNode = domNode.firstElementChild; - let { width, height } = this.size(this.addedNode); - console.log("HTMLLoader.load", { added: this.addedNode, width, height }); - if (width) - this.wantedWidth = width || this.wantedWidth; - if (height) - this.wantedHeight = height || this.wantedHeight; - resolve(this); - }; - xhr.onerror = e => { - reject(this); - }; - xhr.send(); - }) - } - - /** - * Tries to determine the size of the addedNode. - * Checks for explicit width and height style attributes. - * - * Overwrite this method if you want to extract values from other infos. - * - * @returns { width: int, height: int } - * @memberof HTMLLoader - */ - size(node) { - let width = parseInt(node.style.width) || null; - let height = parseInt(node.style.height) || null; - return { width, height } - } - } - - class DOMFlip { - constructor( - domScatterContainer, - flipTemplate, - frontLoader, - backLoader, - { - closeOnMinScale = false, - flipDuration = 1, - fadeDuration = 0.2, - overdoScaling = 1, - autoLoad = false, - center = null, - preloadBack = false, - translatable = true, - scalable = true, - rotatable = true, - onFront = null, - onBack = null, - onClose = null, - onUpdate = null, - onRemoved = null, - onLoaded = null - } = {} - ) { - this.domScatterContainer = domScatterContainer; - this.id = getId$1(); - this.flipDuration = flipDuration; - this.fadeDuration = fadeDuration; - this.closeOnMinScale = closeOnMinScale; - this.flipTemplate = flipTemplate; - this.frontLoader = frontLoader; - this.backLoader = backLoader; - this.translatable = translatable; - this.scalable = scalable; - this.rotatable = rotatable; - this.onFrontFlipped = onFront; - this.onBackFlipped = onBack; - this.onClose = onClose; - this.onRemoved = onRemoved; - this.onUpdate = onUpdate; - this.onLoaded = onLoaded; - this.center = center; - this.preloadBack = preloadBack; - this.overdoScaling = overdoScaling; - if (autoLoad) { - this.load(); - } - } - - load() { - return new Promise((resolve, reject) => { - let t = this.flipTemplate; - let dom = this.domScatterContainer.element; - let wrapper = t.content.querySelector('.flipWrapper'); - wrapper.id = this.id; - let clone = document.importNode(t.content, true); - dom.appendChild(clone); - // We cannot use the document fragment itself because it - // is not part of the main dom tree. After the appendChild - // call we can access the new dom element by id - this.cardWrapper = dom.querySelector('#' + this.id); - let front = this.cardWrapper.querySelector('.front'); - this.frontLoader.load(front).then(loader => { - this.frontLoaded(loader).then((obj) => { - if (this.onLoaded) this.onLoaded(); - resolve(this); - }); - }); - }) - } - - frontLoaded(loader) { - return new Promise((resolve, reject) => { - let scatter = new DOMScatter( - this.cardWrapper, - this.domScatterContainer, - { - x: loader.x, - y: loader.y, - startScale: loader.scale, - scale: loader.scale, - maxScale: loader.maxScale, - minScale: loader.minScale, - width: loader.wantedWidth, - height: loader.wantedHeight, - rotation: loader.rotation, - translatable: this.translatable, - scalable: this.scalable, - rotatable: this.rotatable, - overdoScaling: this.overdoScaling - } - ); - - if (this.center) { - scatter.centerAt(this.center); - } - - if (this.closeOnMinScale) { - - const removeOnMinScale = function () { - if (scatter.scale <= scatter.minScale) { - this.flippable.close(); - - // 'Disable' overdoscaling to avoid weird jumps on close. - scatter.minScale /= scatter.overdoScaling; - scatter.overdoScaling = 1; - - //Remove callback - if (scatter.onTransform) { - let callbackIdx = scatter.onTransform.indexOf(removeOnMinScale); - scatter.onTransform.splice(callbackIdx, 1); - } - } - - }.bind(this); - - - - scatter.addTransformEventCallback(removeOnMinScale); - } - - let flippable = new DOMFlippable(this.cardWrapper, scatter, this); - let back = this.cardWrapper.querySelector('.back'); - - if (this.preloadBack) { - this.backLoader.load(back).then(loader => { - this.setupFlippable(flippable, loader); - }); - } - this.flippable = flippable; - resolve(this); - }) - } - - centerAt(p) { - this.center = p; - this.flippable.centerAt(p); - } - - zoom(scale) { - this.flippable.zoom(scale); - } - - setupFlippable(flippable, loader) { - flippable.wantedWidth = loader.wantedWidth; - flippable.wantedHeight = loader.wantedHeight; - flippable.wantedScale = loader.scale; - flippable.minScale = loader.minScale; - flippable.maxScale = loader.maxScale; - flippable.scaleButtons(); - } - - start({ targetCenter = null } = {}) { - console.log('DOMFlip.start', targetCenter); - if (this.preloadBack) this.flippable.start({ duration: this.flipDuration, targetCenter }); - else { - let back = this.cardWrapper.querySelector('.back'); - let flippable = this.flippable; - this.backLoader.load(back).then(loader => { - this.setupFlippable(flippable, loader); - flippable.start({ duration: this.flipDuration, targetCenter }); - }); - } - } - - fadeOutAndRemove() { - TweenLite.to(this.cardWrapper, this.fadeDuration, { - opacity: 0, - onComplete: () => { - this.cardWrapper.remove(); - } - }); - } - - closed() { - this.unload(); - } - - unload() { - if (!this.preloadBack) { - this.backLoader.unload(); - } - } - } - - class DOMFlippable { - constructor(element, scatter, flip) { - // Set log to console.log or a custom log function - // define data structures to store our touchpoints in - - this.element = element; - this.flip = flip; - this.card = element.querySelector('.flipCard'); - this.front = element.querySelector('.front'); - this.back = element.querySelector('.back'); - this.flipped = false; - this.scatter = scatter; - this.onFrontFlipped = flip.onFrontFlipped; - this.onBackFlipped = flip.onBackFlipped; - this.onClose = flip.onClose; - this.onRemoved = flip.onRemoved; - this.onUpdate = flip.onUpdate; - - this.flipDuration = flip.flipDuration; - this.fadeDuration = flip.fadeDuration; - scatter.addTransformEventCallback(this.scatterTransformed.bind(this)); - console.log('lib.DOMFlippable', 5000); - TweenLite.set(this.element, { perspective: 5000 }); - TweenLite.set(this.card, { transformStyle: 'preserve-3d' }); - TweenLite.set(this.back, { rotationY: -180 }); - TweenLite.set([this.back, this.front], { - backfaceVisibility: 'hidden', - perspective: 5000 - }); - TweenLite.set(this.front, { visibility: 'visible' }); - this.infoBtn = element.querySelector('.infoBtn'); - this.backBtn = element.querySelector('.backBtn'); - this.closeBtn = element.querySelector('.closeBtn'); - /* Buttons are not guaranteed to exist. */ - if (this.infoBtn) { - InteractionMapper$1.on('tap', this.infoBtn, event => this.flip.start()); - - this.enable(this.infoBtn); - } - if (this.backBtn) { - InteractionMapper$1.on('tap', this.backBtn, event => this.start()); - } - if (this.closeBtn) { - InteractionMapper$1.on('tap', this.closeBtn, event => this.close()); - this.enable(this.closeBtn); - } - this.scaleButtons(); - this.bringToFront(); - } - - close() { - this.disable(this.infoBtn); - this.disable(this.closeBtn); - if (this.onClose) { - this.onClose(this); - this.flip.closed(); - } else { - this.scatter.zoom(0.1, { - animate: this.fadeDuration, - onComplete: () => { - this.element.remove(); - this.flip.closed(); - if (this.onRemoved) { - this.onRemoved.call(this); - } - } - }); - } - } - - showFront() { - TweenLite.set(this.front, { visibility: 'visible' }); - } - - centerAt(p) { - this.scatter.centerAt(p); - } - - zoom(scale) { - this.scatter.zoom(scale); - } - - get buttonScale() { - let iscale = 1.0; - - if (this.scatter != null) { - let scale = this.scatter.scale || 1; - iscale = 1.0 / scale; - } - return iscale - } - - scaleButtons() { - //This also works for svgs. - // if (this.infoBtn) - // this.infoBtn.style.transform = "scale(" + this.buttonScale + ")" - - // if (this.backBtn) - // this.backBtn.style.transform = "scale(" + this.buttonScale + ")" - - // if (this.closeBtn) - // this.closeBtn.style.transform = "scale(" + this.buttonScale + ")" - - console.log(this.buttonScale); - //// This did not work with svgs! - TweenLite.set([this.infoBtn, this.backBtn, this.closeBtn], { - scale: this.buttonScale - }); - } - - bringToFront() { - this.scatter.bringToFront(); - TweenLite.set(this.element, { zIndex: DOMScatter.zIndex++ }); - } - - clickInfo() { - this.bringToFront(); - this.infoBtn.click(); - } - - scatterTransformed(event) { - this.scaleButtons(); - } - - targetRotation(alpha) { - let ortho = 90; - let rest = alpha % ortho; - let delta = 0.0; - if (rest > ortho / 2.0) { - delta = ortho - rest; - } else { - delta = -rest; - } - return delta - } - - infoValues(info) { - let startX = this.element._gsTransform.x; - let startY = this.element._gsTransform.y; - let startAngle = this.element._gsTransform.rotation; - let startScale = this.element._gsTransform.scaleX; - let w = this.element.style.width; - let h = this.element.style.height; - console.log(info, startX, startY, startAngle, startScale, w, h); - } - - show(element, duration = 0, alpha = 1) { - if (element) { - TweenLite.to(element, duration, { autoAlpha: alpha }); // visibility: 'visible', display: 'initial'}) - } - } - - hide(element, duration = 0, alpha = 0) { - if (element) { - TweenLite.to(element, duration, { autoAlpha: alpha }); // {visibility: 'hidden', display: 'none'}) - } - } - - - - enable(button) { - this.show(button, this.fadeDuration); - if (button) { - TweenLite.set(button, { pointerEvents: 'auto' }); - } - } - - disable(button) { - this.hide(button, this.fadeDuration); - if (button) { - TweenLite.set(button, { pointerEvents: 'none' }); - } - } - - start({ targetCenter = null } = {}) { - this.bringToFront(); - if (!this.flipped) { - this.startX = this.element._gsTransform.x; - this.startY = this.element._gsTransform.y; - this.startAngle = this.element._gsTransform.rotation; - this.startScale = this.element._gsTransform.scaleX; - this.startWidth = this.element.style.width; - this.startHeight = this.element.style.height; - this.scatterStartWidth = this.scatter.width; - this.scatterStartHeight = this.scatter.height; - this.show(this.back); - this.disable(this.infoBtn); - this.disable(this.closeBtn); - } else { - this.show(this.front, this.fadeDuration); - this.disable(this.backBtn); - } - let { scalable, translatable, rotatable } = this.scatter; - this.saved = { scalable, translatable, rotatable }; - this.scatter.scalable = false; - this.scatter.translatable = false; - this.scatter.rotatable = false; - this.scatter.killAnimation(); - - this.flipped = !this.flipped; - let targetY = this.flipped ? 180 : 0; - let targetZ = this.flipped - ? this.startAngle + this.targetRotation(this.startAngle) - : this.startAngle; - let targetScale = this.flipped ? this.wantedScale : this.startScale; - let w = this.flipped ? this.wantedWidth : this.startWidth; - let h = this.flipped ? this.wantedHeight : this.startHeight; - let dw = this.wantedWidth - this.scatter.width; - let dh = this.wantedHeight - this.scatter.height; - let tc = targetCenter; - let xx = tc != null ? tc.x - w / 2 : this.startX - dw / 2; - let yy = tc != null ? tc.y - h / 2 : this.startY - dh / 2; - let x = this.flipped ? xx : this.startX; - let y = this.flipped ? yy : this.startY; - - console.log("DOMFlippable.start", this.flipped, targetCenter, x, y, this.saved); - let onUpdate = this.onUpdate !== null ? () => this.onUpdate(this) : null; - console.log(this.flipDuration); - TweenLite.to(this.card, this.flipDuration, { - rotationY: targetY, - ease: Power1.easeOut, - transformOrigin: '50% 50%', - onUpdate, - onComplete: e => { - if (this.flipped) { - //this.hide(this.front) - this.enable(this.backBtn); - this.show(this.backBtn); - - if (this.onFrontFlipped) { - this.onFrontFlipped(this); - } - } else { - - if (this.onBackFlipped == null) { - this.enable(this.infoBtn, this.fadeDuration); - this.enable(this.closeBtn, this.fadeDuration); - } else { - this.onBackFlipped(this); - } - this.flip.unload(); - } - this.scatter.scale = targetScale; - this.scaleButtons(); - this.scatter.rotationDegrees = targetZ; - this.scatter.width = this.flipped ? w : this.scatterStartWidth; - this.scatter.height = this.flipped ? h : this.scatterStartHeight; - - let { scalable, translatable, rotatable } = this.saved; - this.scatter.scalable = scalable; - this.scatter.translatable = translatable; - this.scatter.rotatable = rotatable; - }, - force3D: true - }); - - // See https://greensock.com/forums/topic/7997-rotate-the-shortest-way/ - TweenLite.to(this.element, this.flipDuration / 2, { - scale: targetScale, - ease: Power1.easeOut, - rotationZ: targetZ + '_short', - transformOrigin: '50% 50%', - width: w, - height: h, - x: x, - y: y, - onComplete: e => { - if (this.flipped) { - this.hide(this.front); - // this.hide(this.infoBtn) - } else { - this.hide(this.back); - // this.show(this.infoBtn) - } - } - }); - } - } - - class Index { - - constructor(template, pages, notfound='thumbnails/notfound.png') { - this.template = template; - this.pages = pages; - this.notfound = notfound; - } - - setup() { - for(let pair of this.pages) { - let [title, src] = pair; - let id = getId(); - pair.push(id); - let t = this.template; - let wrapper = t.content.querySelector('.wrapper'); - wrapper.id = id; - let clone = document.importNode(t.content, true); - container.appendChild(clone); - wrapper = container.querySelector('#'+id); - - let icon = wrapper.querySelector('.icon'); - - icon.onerror = (e) => { - if (this.notfound) - icon.src = this.notfound; - }; - let iconSrc = src.replace('.html', '.png'); - //console.log("iconSrc", iconSrc) - if (iconSrc.endsWith('index.png')) { - icon.src = iconSrc.replace('index.png', 'thumbnail.png'); - } - else { - icon.src = 'thumbnails/' + iconSrc; - } - - // icon.src = 'thumbnails/' + iconSrc - // console.log(iconSrc) - wrapper.href = src; - let titleDiv = wrapper.querySelector('.title'); - titleDiv.innerText = title; - } - } - - frames() { - if (this.pages.length == 0) - return - let [title, src, id] = this.pages.shift(); - let iframe = document.createElement('iframe'); - iframe.frameborder = 0; - let wrapper = document.getElementById(id); - let icon = wrapper.querySelector('.icon'); - - icon.parentNode.replaceChild(iframe, icon); - iframe.onload = (e) => { - this.frames(); - }; - iframe.src = src + window.location.search; - } - - load() { - this.setup(); - if (window.location.search.startsWith('?test')) - this.frames(); - } - - loadAndTest() { - this.setup(); - if (!Capabilities.isMobile) - this.frames(); - } - } - /** Basic class for poppable elements that need to be closed as soon as one poppable is * shown. */ @@ -6097,7 +3916,7 @@ /** A Popup Menu that shows text labels in a vertical row. */ - class PopupMenu$1 extends Popup { + class PopupMenu extends Popup { /** * The constructor. * @constructor @@ -6261,7 +4080,7 @@ } console.log("open", point); let notchPosition = (point.y < 50 && switchPos) ? 'topCenter' : 'bottomCenter'; - let popup = new PopupMenu$1({ + let popup = new PopupMenu({ parent, fontSize, padding, zIndex, spacing, switchPos, notchSize, notchPosition, maxWidth, backgroundColor, normalColor, @@ -6298,6 +4117,2144 @@ } } + /* eslint-disable no-unused-vars */ + /** + * A base class for scatter specific events. + * + * @constructor + * @param {name} String - The name of the event + * @param {target} Object - The target of the event + */ + class BaseEvent { + constructor(name, target) { + this.name = name; + this.target = target; + } + } + + // Event types + const START = 'onStart'; + const UPDATE = 'onUpdate'; + const END = 'onEnd'; + + /** + * A scatter event that describes how the scatter has changed. + * + * @constructor + * @param {target} Object - The target scatter of the event + * @param {optional} Object - Optional parameter + */ + class ScatterEvent extends BaseEvent { + constructor( + target, + { + translate = { x: 0, y: 0 }, + scale = null, + rotate = 0, + about = null, + fast = false, + type = null + } = {} + ) { + super('scatterTransformed', { target: target }); + this.translate = translate; + this.scale = scale; + this.rotate = rotate; + this.about = about; + this.fast = fast; + this.type = type; + } + + toString() { + return ( + 'Event(\'scatterTransformed\', scale: ' + + this.scale + + ' about: ' + + this.about.x + + ', ' + + this.about.y + + ')' + ) + } + } + + /** + * A scatter resize event that describes how the scatter has changed. + * + * @constructor + * @param {target} Object - The target scatter of the event + * @param {optional} Object - Optional parameter + */ + class ResizeEvent extends BaseEvent { + constructor(target, { width = 0, height = 0 } = {}) { + super('scatterResized', { width: width, height: height }); + this.width = width; + this.height = height; + } + + toString() { + return ( + 'Event(scatterResized width: ' + + this.width + + 'height: ' + + this.height + + ')' + ) + } + } + + /** + * A abstract base class that implements the throwable behavior of a scatter + * object. + * + * @constructor + */ + class Throwable { + constructor({ + movableX = true, + movableY = true, + throwVisibility = 44, + throwDamping = 0.95, + autoThrow = true, + onThrowFinished = null + } = {}) { + this.movableX = movableX; + this.movableY = movableY; + this.throwVisibility = throwVisibility; + this.throwDamping = throwDamping; + this.autoThrow = autoThrow; + this.velocities = []; + this.velocity = null; + this.timestamp = null; + this.onThrowFinished = onThrowFinished; + //console.log("onThrowFinished", onThrowFinished) + } + + observeVelocity() { + this.lastframe = performance.now(); + } + + addVelocity(delta, buffer = 5) { + let t = performance.now(); + let dt = t - this.lastframe; + this.lastframe = t; + if (dt > 0) { + // Avoid division by zero errors later on + // and consider the number of involved pointers sind addVelocity will be called by the + // onMove events + let velocity = { t: t, dt: dt, dx: delta.x / delta.number, dy: delta.y / delta.number }; + this.velocities.push(velocity); + while (this.velocities.length > buffer) { + this.velocities.shift(); + } + } + } + + meanVelocity(milliseconds = 30) { + this.addVelocity({ x: 0, y: 0, number: 1 }); + let sum = { x: 0, y: 0 }; + let count = 0; + let t = 0; + for (let i = this.velocities.length - 1; i > 0; i--) { + let v = this.velocities[i]; + t += v.dt; + let nv = { x: v.dx / v.dt, y: v.dy / v.dt }; + sum = Points.add(sum, nv); + count += 1; + if (t > milliseconds) { + break + } + } + if (count === 0) return sum // empty vector + return Points.multiplyScalar(sum, 1 / count) + } + + killAnimation() { + this.velocity = null; + this.velocities = []; + } + + startThrow() { + this.velocity = this.meanVelocity(); + if (this.velocity != null) { + // Call next velocity to ansure that specializations + // that use keepOnStage are called + this.velocity = this.nextVelocity(this.velocity); + if (this.autoThrow) this.animateThrow(performance.now()); + } else { + this.onDragComplete(); + } + } + + _throwDeltaTime() { + let t = performance.now(); + let dt = t - this.lastframe; + this.lastframe = t; + return dt + } + + animateThrow(time) { + if (this.velocity != null) { + let dt = this._throwDeltaTime(); + // console.log("animateThrow", dt) + let next = this.nextVelocity(this.velocity); + let prevLength = Points.length(this.velocity); + let nextLength = Points.length(next); + if (nextLength > prevLength) { + let factor = nextLength / prevLength; + next = Points.multiplyScalar(next, 1 / factor); + console.log('Prevent acceleration', factor, this.velocity, next); + } + this.velocity = next; + let d = Points.multiplyScalar(this.velocity, dt); + this._move(d); + + this.onDragUpdate(d); + if (dt == 0 || this.needsAnimation()) { + requestAnimationFrame(this.animateThrow.bind(this)); + return + } else { + if (this.isOutside()) { + requestAnimationFrame(this.animateThrow.bind(this)); + return + } + } + } + this.onDragComplete(); + if (this.onThrowFinished != null) { + this.onThrowFinished(); + } + } + + needsAnimation() { + if (this.velocity == null) { + return false + } + return Points.length(this.velocity) > 0.01 + } + + nextVelocity(velocity) { + // Must be overwritten: computes the changed velocity. Implement + // damping, collison detection, etc. here + let next = Points.multiplyScalar(velocity, this.throwDamping); + return { + x: (this.movableX) ? next.x : 0, + y: (this.movableY) ? next.y : 0 + } + } + + _move(delta) { + // Overwrite if necessary + } + + onDragComplete() { + // Overwrite if necessary + } + + onDragUpdate(delta) { + // Overwrite if necessary + } + } + + class AbstractScatter extends Throwable { + constructor({ + minScale = 0.1, + maxScale = 1.0, + startScale = 1.0, + autoBringToFront = true, + autoThrow = true, + translatable = true, + scalable = true, + rotatable = true, + resizable = false, + movableX = true, + movableY = true, + throwVisibility = 44, + throwDamping = 0.95, + overdoScaling = 1, + mouseZoomFactor = 1.1, + rotationDegrees = null, + rotation = null, + onTransform = null, + interactive = true, + onClose = null, + onThrowFinished = null, + scaleAutoClose = false, + scaleCloseThreshold = 0.10, + scaleCloseBuffer = 0.05, + maxRotation = Angle.degree2radian(5) + } = {}) { + if (rotationDegrees != null && rotation != null) { + throw new Error('Use rotationDegrees or rotation but not both') + } else if (rotation != null) { + rotationDegrees = Angle.radian2degree(rotation); + } else if (rotationDegrees == null) { + rotationDegrees = 0; + } + super({ + movableX, + movableY, + throwVisibility, + throwDamping, + autoThrow, + onThrowFinished + }); + + /** + * Closes the card when the minScale is reached and the + * card is released. Card can be saved by scaling it up again. + */ + this.scaleAutoClose = scaleAutoClose; + this.scaleCloseThreshold = scaleCloseThreshold; + this.scaleCloseBuffer = scaleCloseBuffer; + this.scaleAutoCloseTimeout = null; + + this.interactive = interactive; + this.startRotationDegrees = rotationDegrees; + this.startScale = startScale; // Needed to reset object + this.minScale = minScale; + this.maxScale = maxScale; + this.maxRotation = maxRotation; + this.overdoScaling = overdoScaling; + this.translatable = translatable; + if (!translatable) { + this.movableX = false; + this.movableY = false; + } + this.scalable = scalable; + this.rotatable = rotatable; + this.resizable = resizable; + this.mouseZoomFactor = mouseZoomFactor; + this.autoBringToFront = autoBringToFront; + + this.dragging = false; + this.onTransform = onTransform != null ? [onTransform] : null; + this.onClose = onClose != null ? [onClose] : null; + } + + addCloseEventCallback(callback) { + if (this.onClose == null) { + this.onClose = []; + } + this.onClose.push(callback); + } + + addTransformEventCallback(callback) { + if (this.onTransform == null) { + this.onTransform = []; + } + this.onTransform.push(callback); + } + + startGesture(interaction) { + this.bringToFront(); + this.killAnimation(); + this.observeVelocity(); + return true + } + + close() { + if (this.onClose) { + this.onClose.forEach(callback => callback(this)); + } + } + + gesture(interaction) { + let delta = interaction.delta(); + if (delta != null) { + this.addVelocity(delta); + let alpha = delta.rotate; + if (this.maxRotation != null) { + if (Math.abs(alpha) > this.maxRotation) { + alpha = 0; + } + } + this.transform(delta, delta.zoom, alpha, delta.about); + if (delta.zoom != 1) this.interactionAnchor = delta.about; + } + } + + get polygon() { + let w2 = this.width * this.scale / 2; + let h2 = this.height * this.scale / 2; + let center = this.center; + let polygon = new Polygon(center); + polygon.addPoint({ x: -w2, y: -h2 }); + polygon.addPoint({ x: w2, y: -h2 }); + polygon.addPoint({ x: w2, y: h2 }); + polygon.addPoint({ x: -w2, y: h2 }); + polygon.rotate(this.rotation); + return polygon + } + + isOutside() { + let stagePolygon = this.containerPolygon; + if (stagePolygon == null) + return false + let polygon = this.polygon; + if (polygon == null) + return false + let result = stagePolygon.intersectsWith(polygon); + return result === false || result.overlap < this.throwVisibility + } + + recenter() { + // Return a small vector that guarantees that the scatter is moving + // towards the center of the stage + let center = this.center; + let target = this.container.center; + let delta = Points.subtract(target, center); + return Points.normalize(delta) + } + + nextVelocity(velocity) { + return this.keepOnStage(velocity) + } + + bouncing() { + // Implements the bouncing behavior of the scatter. Moves the scatter + // to the center of the stage if the scatter is outside the stage or + // not within the limits of the throwVisibility. + + let stagePolygon = this.containerPolygon; + let polygon = this.polygon; + let result = stagePolygon.intersectsWith(polygon); + if (result === false || result.overlap < this.throwVisibility) { + let cv = this.recenter(); + let recentered = false; + while (result === false || result.overlap < this.throwVisibility) { + polygon.center.x += cv.x; + polygon.center.y += cv.y; + this._move(cv); + result = stagePolygon.intersectsWith(polygon); + recentered = true; + } + return recentered + } + return false + } + + keepOnStage(velocity, collision = 0.5) { + let stagePolygon = this.containerPolygon; + // UO: since keepOnStage is called in nextVelocity we need to + // ensure a return value + if (!stagePolygon) return { x: 0, y: 0 } + let polygon = this.polygon; + let bounced = this.bouncing(); + if (bounced) { + let stage = this.containerBounds; + let x = this.center.x; + let y = this.center.y; + let dx = this.movableX ? velocity.x : 0; + let dy = this.movableY ? velocity.y : 0; + let factor = this.throwDamping; + // if (recentered) { + if (x < 0) { + dx = -dx; + factor = collision; + } + if (x > stage.width) { + dx = -dx; + factor = collision; + } + if (y < 0) { + dy = -dy; + factor = collision; + } + if (y > stage.height) { + dy = -dy; + factor = collision; + } + // } + return Points.multiplyScalar({ x: dx, y: dy }, factor) + } + return super.nextVelocity(velocity) + } + + endGesture(interaction) { + this.startThrow(); + this._checkAutoClose(); + } + + _checkAutoClose() { + if (this.scaleAutoClose) + if (this.scale < this.minScale + this.scaleCloseThreshold - this.scaleCloseBuffer) { + this.zoom(this.minScale, { animate: 0.2, onComplete: this.close.bind(this) }); + } else if (this.scale < this.minScale + this.scaleCloseThreshold) { + this.zoom(this.minScale + this.scaleCloseThreshold, { animate: 0.4 }); + } + } + + rotateDegrees(degrees, anchor) { + let rad = Angle.degree2radian(degrees); + this.rotate(rad, anchor); + } + + rotate(rad, anchor) { + this.transform({ x: 0, y: 0 }, 1.0, rad, anchor); + } + + move(d, { animate = 0 } = {}) { + if (this.translatable) { + if (animate > 0) { + let startPos = this.position; + TweenLite.to(this, animate, { + x: '+=' + d.x, + y: '+=' + d.y, + /* scale: scale, uo: not defined, why was this here? */ + onUpdate: e => { + let p = this.position; + let dx = p.x - startPos.x; + let dy = p.x - startPos.y; + this.onMoved(dx, dy); + } + }); + } else { + this._move(d); + this.onMoved(d.x, d.y); + } + } + } + + moveTo(p, { animate = 0 } = {}) { + let c = this.origin; + let delta = Points.subtract(p, c); + this.move(delta, { animate: animate }); + } + + centerAt(p, { animate = 0 } = {}) { + let c = this.center; + let delta = Points.subtract(p, c); + this.move(delta, { animate: animate }); + } + + zoom( + scale, + { + animate = 0, + about = null, + delay = 0, + x = null, + y = null, + onComplete = null + } = {} + ) { + let anchor = about || this.center; + if (scale != this.scale) { + if (animate > 0) { + TweenLite.to(this, animate, { + scale: scale, + delay: delay, + onComplete: onComplete, + onUpdate: this.onZoomed.bind(this) + }); + } else { + this.scale = scale; + this.onZoomed(anchor); + } + } + } + + _move(delta) { + this.x += this.movableX ? delta.x : 0; + this.y += this.movableX ? delta.y : 0; + } + + transform(translate, zoom, rotate, anchor) { + let delta = { + x: this.movableX ? translate.x : 0, + y: this.movableY ? translate.y : 0 + }; + if (this.resizable) var vzoom = zoom; + if (!this.translatable) delta = { x: 0, y: 0 }; + if (!this.rotatable) rotate = 0; + if (!this.scalable) zoom = 1.0; + if (zoom == 1.0 && rotate == 0) { + this._move(delta); + if (this.onTransform != null) { + let event = new ScatterEvent(this, { + translate: delta, + scale: this.scale, + rotate: 0, + about: anchor, + fast: false, + type: UPDATE + }); + this.onTransform.forEach(function (f) { + f(event); + }); + } + return + } + let origin = this.rotationOrigin; + let beta = Points.angle(origin, anchor); + let distance = Points.distance(origin, anchor); + let { scale: newScale, zoom: thresholdedZoom } = this.calculateScale(zoom); + + let newOrigin = Points.arc(anchor, beta + rotate, distance * thresholdedZoom); + let extra = Points.subtract(newOrigin, origin); + let offset = Points.subtract(anchor, origin); + this._move(offset); + this.scale = newScale; + this.rotation += rotate; + offset = Points.negate(offset); + offset = Points.add(offset, extra); + offset = Points.add(offset, translate); + this._move(offset); + + delta.x += extra.x; + delta.y += extra.y; + if (this.onTransform != null) { + let event = new ScatterEvent(this, { + translate: delta, + scale: newScale, + rotate: rotate, + about: anchor + }); + this.onTransform.forEach(function (f) { + f(event); + }); + } + if (this.resizable) { + this.resizeAfterTransform(vzoom); + } + } + + /** + * For a given zoom, a new scale is calculated, taking + * min and max scale into account. + * + * @param {number} zoom - The zoom factor, to scale the object with. + * @returns {object} - Returns an object containing the a value for a valid scale and the corrected zoom factor. + */ + calculateScale(zoom) { + let scale = this.scale * zoom; + + let minScale = this.minScale / this.overdoScaling; + let maxScale = this.maxScale * this.overdoScaling; + if (scale < minScale) { + scale = minScale; + zoom = scale / this.scale; + } + if (scale > maxScale) { + scale = maxScale; + zoom = scale / this.scale; + } + + if (this.scaleAutoClose) + this._updateTransparency(); + + return { zoom, scale } + } + + _updateTransparency() { + if (this.scale < this.minScale + this.scaleCloseThreshold) { + let transparency = this.calculateScaleTransparency(); + this.element.style.opacity = transparency; + } else this.element.style.opacity = 1; + } + + calculateScaleTransparency() { + let transparency = (this.scale - this.minScale) / this.scaleCloseThreshold; + transparency = (transparency > 1) ? 1 : (transparency < 0) ? 0 : transparency; + return transparency + } + + resizeAfterTransform(zoom) { + // Overwrite this in subclasses. + } + + validScale(scale) { + scale = Math.max(scale, this.minScale); + scale = Math.min(scale, this.maxScale); + return scale + } + + animateZoomBounce(dt = 1) { + if (this.zoomAnchor != null) { + let zoom = 1; + let amount = Math.min(0.01, 0.3 * dt / 100000.0); + if (this.scale < this.minScale) zoom = 1 + amount; + if (this.scale > this.maxScale) zoom = 1 - amount; + if (zoom != 1) { + this.transform({ x: 0, y: 0 }, zoom, 0, this.zoomAnchor); + requestAnimationFrame(dt => { + this.animateZoomBounce(dt); + }); + return + } + this.zoomAnchor = null; + } + } + + checkScaling(about, delay = 0) { + this.zoomAnchor = about; + clearTimeout(this.animateZoomBounce.bind(this)); + setTimeout(this.animateZoomBounce.bind(this), delay); + } + + onMouseWheel(event) { + if (event.claimedByScatter) { + if (event.claimedByScatter != this) return + } + this.killAnimation(); + this.targetScale = null; + let direction = event.detail < 0 || event.wheelDelta > 0; + let globalPoint = { x: event.clientX, y: event.clientY }; + let centerPoint = this.mapPositionToContainerPoint(globalPoint); + if (event.shiftKey) { + let degrees = direction ? 5 : -5; + let rad = Angle.degree2radian(degrees); + return this.transform({ x: 0, y: 0 }, 1.0, rad, centerPoint) + } + const zoomFactor = this.mouseZoomFactor; + let zoom = direction ? zoomFactor : 1 / zoomFactor; + this.transform({ x: 0, y: 0 }, zoom, 0, centerPoint); + this.checkScaling(centerPoint, 200); + + if (this.scaleAutoClose) { + if (this.scale <= this.minScale + this.scaleCloseThreshold) { + + if (this.scaleAutoCloseTimeout) clearTimeout(this.scaleAutoCloseTimeout); + this.scaleAutoCloseTimeout = setTimeout(() => { + this._checkAutoClose(); + }, 600); + } + this._updateTransparency(); + } + } + + onStart(event, interaction) { + + if (this.startGesture(interaction)) { + this.dragging = true; + this.interactionAnchor = null; + } + if (this.onTransform != null) { + let event = new ScatterEvent(this, { + translate: { x: 0, y: 0 }, + scale: this.scale, + rotate: 0, + about: null, + fast: false, + type: START + }); + this.onTransform.forEach(function (f) { + f(event); + }); + } + } + + onMove(event, interaction) { + /** As long as mouseout && mouseleave interrupt we cannot be sure that + * dragging remains correct. + */ + if (this.dragging) { + this.gesture(interaction); + } + } + + onEnd(event, interaction) { + //console.log("Scatter.onEnd", this.dragging) + if (interaction.isFinished()) { + this.endGesture(interaction); + this.dragging = false; + for (let key of interaction.ended.keys()) { + if (interaction.isTap(key)) { + let point = interaction.ended.get(key); + this.onTap(event, interaction, point); + } + } + if (this.onTransform != null) { + let event = new ScatterEvent(this, { + translate: { x: 0, y: 0 }, + scale: this.scale, + rotate: 0, + about: null, + fast: false, + type: END + }); + this.onTransform.forEach(function (f) { + f(event); + }); + } + } + let about = this.interactionAnchor; + if (about != null) { + this.checkScaling(about, 100); + } + } + + onTap(event, interaction, point) { } + + onDragUpdate(delta) { + if (this.onTransform != null) { + let event = new ScatterEvent(this, { + fast: true, + translate: delta, + scale: this.scale, + about: this.currentAbout, + type: null + }); + this.onTransform.forEach(function (f) { + f(event); + }); + } + } + + onDragComplete() { + if (this.onTransform) { + let event = new ScatterEvent(this, { + scale: this.scale, + about: this.currentAbout, + fast: false, + type: null + }); + this.onTransform.forEach(function (f) { + f(event); + }); + } + } + + onMoved(dx, dy, about) { + if (this.onTransform != null) { + let event = new ScatterEvent(this, { + translate: { x: dx, y: dy }, + about: about, + fast: true, + type: null + }); + this.onTransform.forEach(function (f) { + f(event); + }); + } + } + + onResizing() { + if (this.onTransform != null) { + let event = new ScatterEvent(this, { + scale: this.scale, + fast: false, + type: null + }); + this.onTransform.forEach(function (f) { + f(event); + }); + } + } + + onZoomed(about) { + + if (this.scaleAutoClose) + this._updateTransparency(); + + if (this.onTransform != null) { + let event = new ScatterEvent(this, { + scale: this.scale, + about: about, + fast: false, + type: null + }); + this.onTransform.forEach(function (f) { + f(event); + }); + } + } + } + + /** A container for scatter objects, which uses a single InteractionMapper + * for all children. This reduces the number of registered event handlers + * and covers the common use case that multiple objects are scattered + * on the same level. + */ + class DOMScatterContainer { + /** + * @constructor + * @param {DOM node} element - DOM element that receives events + * @param {Bool} stopEvents - Whether events should be stopped or propagated + * @param {Bool} claimEvents - Whether events should be marked as claimed + * if findTarget return as non-null value. + * @param {String} [touchAction=none] - CSS to set touch action style, needed to prevent + * pointer cancel events. Use null if the + * the touch action should not be set. + */ + constructor( + element, + { stopEvents = 'auto', claimEvents = true, useCapture = true, touchAction = 'none' } = {} + ) { + this.onCapture = null; + this.element = element; + if (stopEvents === 'auto') { + if (Capabilities.isSafari) { + document.addEventListener( + 'touchmove', + event => this.preventPinch(event), + false + ); + stopEvents = false; + } else { + stopEvents = true; + } + } + this.stopEvents = stopEvents; + this.claimEvents = claimEvents; + if (touchAction !== null) { + Elements$1.setStyle(element, { touchAction }); + } + this.scatter = new Map(); + this.delegate = new InteractionMapper$1(element, this, { + useCapture, + mouseWheelElement: window + }); + + if (typeof debugCanvas !== 'undefined') { + requestAnimationFrame(dt => { + this.showTouches(dt, debugCanvas); + }); + } + } + + showTouches(dt, canvas) { + let resolution = window.devicePixelRatio; + let current = this.delegate.interaction.current; + let context = canvas.getContext('2d'); + let radius = 20 * resolution; + context.clearRect(0, 0, canvas.width, canvas.height); + context.fillStyle = 'rgba(0, 0, 0, 0.3)'; + context.lineWidth = 2; + context.strokeStyle = '#003300'; + for (let [key, point] of current.entries()) { + let local = point; + context.beginPath(); + context.arc( + local.x * resolution, + local.y * resolution, + radius, + 0, + 2 * Math.PI, + false + ); + context.fill(); + context.stroke(); + } + requestAnimationFrame(dt => { + this.showTouches(dt); + }); + } + + preventPinch(event) { + event = event.originalEvent || event; + if (event.scale !== 1) { + event.preventDefault(); + } + } + + add(scatter) { + this.scatter.set(scatter.element, scatter); + } + + capture(event) { + if (this.onCapture) { + return this.onCapture(event) + } + if (event.target == this.element && this.stopEvents) { + Events.stop(event); + } + return true + } + + mapPositionToPoint(point) { + return Points.fromPageToNode(this.element, point) + } + + isDescendant(parent, child, clickable = false) { + if (parent == child) return true + let node = child.parentNode; + while (node != null) { + if (!clickable && node.onclick) { + return false + } + if (node == parent) { + return true + } + node = node.parentNode; + } + return false + } + + findTarget(event, local, global) { + /*** Note that elementFromPoint works with clientX, clientY, not pageX, pageY + The important point is that event should not be used, since the TouchEvent + points are hidden in sub objects. + ***/ + let found = document.elementFromPoint(global.x, global.y); + for (let target of this.scatter.values()) { + if (target.interactive && this.isDescendant(target.element, found)) { + if (this.stopEvents) Events.stop(event); + if (this.claimEvents) event.claimedByScatter = target; + return target + } + } + return null + } + + get center() { + let r = this.bounds; + let w2 = r.width / 2; + let h2 = r.height / 2; + return { x: w2, y: h2 } + } + + get bounds() { + return this.element.getBoundingClientRect() + } + + get polygon() { + let r = this.bounds; + let w2 = r.width / 2; + let h2 = r.height / 2; + let center = { x: w2, y: h2 }; + let polygon = new Polygon(center); + polygon.addPoint({ x: -w2, y: -h2 }); + polygon.addPoint({ x: w2, y: -h2 }); + polygon.addPoint({ x: w2, y: h2 }); + polygon.addPoint({ x: -w2, y: h2 }); + return polygon + } + } + + + class DOMScatter extends AbstractScatter { + constructor( + element, + container, + { + startScale = 1.0, + minScale = 0.1, + maxScale = 1.0, + overdoScaling = 1.5, + 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, // required + height = null, // required + resizable = false, + simulateClick = false, + verbose = true, + onResize = null, + touchAction = 'none', + throwVisibility = 44, + throwDamping = 0.95, + autoThrow = true, + scaleAutoClose = false, + onClose = null, + scaleCloseThreshold = 0.10, + scaleCloseBuffer = 0.05 + } = {} + ) { + super({ + minScale, + maxScale, + startScale, + overdoScaling, + autoBringToFront, + translatable, + scalable, + rotatable, + movableX, + movableY, + resizable, + rotationDegrees, + rotation, + onTransform, + throwVisibility, + throwDamping, + autoThrow, + scaleAutoClose, + scaleCloseThreshold, + scaleCloseBuffer, + onClose + }); + if (container == null || width == null || height == null) { + throw new Error('Invalid value: null') + } + element.scatter = this; + this.element = element; + this.x = x; + this.y = y; + this.oldX = 0; + this.oldY = 0; + this.meanX = x; + this.meanY = y; + this.width = width; + this.height = height; + this.throwVisibility = Math.min(width, height, throwVisibility); + this.container = container; + this.simulateClick = simulateClick; + this.scale = startScale; + this.rotationDegrees = this.startRotationDegrees; + this.transformOrigin = transformOrigin; + this.initialValues = { + x: x, + y: y, + width: width, + height: height, + scale: startScale, + rotation: this.startRotationDegrees, + transformOrigin: transformOrigin + }; + + + // For tweenlite we need initial values in _gsTransform + TweenLite.set(element, this.initialValues); + this.onResize = onResize; + this.verbose = verbose; + if (touchAction !== null) { + Elements$1.setStyle(element, { touchAction }); + } + this.resizeButton = null; + if (resizable) { + let button = document.createElement('div'); + button.style.position = 'absolute'; + button.style.right = '0px'; + button.style.bottom = '0px'; + button.style.width = '50px'; + button.style.height = '50px'; + button.className = 'interactiveElement'; + this.element.appendChild(button); + + button.addEventListener('pointerdown', (e) => { + this.startResize(e); + }); + + button.addEventListener('pointermove', (e) => { + this.resize(e); + }); + + button.addEventListener('pointerup', (e) => { + this.stopResize(e); + }); + this.resizeButton = button; + } + container.add(this); + } + + /** Returns geometry data as object. **/ + getState() { + return { + scale: this.scale, + x: this.x, + y: this.y, + rotation: this.rotation + } + } + + close() { + super.close(); + let parent = this.element.parentNode; + if (parent) parent.removeChild(this.element); + } + + get rotationOrigin() { + return this.center + } + + get x() { + return this._x + } + + get y() { + return this._y + } + + set x(value) { + this._x = value; + TweenLite.set(this.element, { x: value }); + } + + set y(value) { + this._y = value; + TweenLite.set(this.element, { y: value }); + } + + get position() { + let transform = this.element._gsTransform; + let x = transform.x; + let y = transform.y; + return { x, y } + } + + get origin() { + let p = this.fromNodeToPage(0, 0); + return Points.fromPageToNode(this.container.element, p) + } + + get bounds() { + let stage = this.container.element.getBoundingClientRect(); + let rect = this.element.getBoundingClientRect(); + return { + top: rect.top - stage.top, + left: rect.left - stage.left, + width: rect.width, + height: rect.height + } + } + + get center() { + let r = this.bounds; + let w2 = r.width / 2; + let h2 = r.height / 2; + // if (this.resizable) { + // w2 *= this.scale + // h2 *= this.scale + // } + var x = r.left + w2; + var y = r.top + h2; + return { x, y } + } + + set rotation(radians) { + let rad = radians; // Angle.normalize(radians) + let degrees = Angle.radian2degree(rad); + TweenLite.set(this.element, { rotation: degrees }); + this._rotation = rad; + } + + set rotationDegrees(degrees) { + let deg = degrees; // Angle.normalizeDegree(degrees) + TweenLite.set(this.element, { rotation: deg }); + this._rotation = Angle.degree2radian(deg); + } + + get rotation() { + return this._rotation + } + + get rotationDegrees() { + return this._rotation + } + + set scale(scale) { + TweenLite.set(this.element, { + scale: scale, + transformOrigin: this.transformOrigin + }); + this._scale = scale; + } + + get scale() { + return this._scale + } + + get containerBounds() { + return this.container.bounds + } + + get containerPolygon() { + return this.container.polygon + } + + mapPositionToContainerPoint(point) { + return this.container.mapPositionToPoint(point) + } + + capture(event) { + return true + } + + reset() { + TweenLite.set(this.element, this.initialValues); + } + + hide() { + TweenLite.to(this.element, 0.1, { + display: 'none', + onComplete: e => { + this.element.parentNode.removeChild(this.element); + } + }); + } + + show() { + TweenLite.set(this.element, { display: 'block' }); + } + + showAt(p, rotationDegrees) { + TweenLite.set(this.element, { + display: 'block', + x: p.x, + y: p.y, + rotation: rotationDegrees, + transformOrigin: this.transformOrigin + }); + } + + bringToFront() { + // this.element.parentNode.appendChild(this.element) + // uo: On Chome and Electon appendChild leads to flicker + TweenLite.set(this.element, { zIndex: DOMScatter.zIndex++ }); + } + + onTap(event, interaction, point) { + if (this.simulateClick) { + let p = Points.fromPageToNode(this.element, point); + let element = document.elementFromPoint(p.x, p.y); + if (element != null) { + console.log('tap simulates click'); + element.click(); + } + } + } + + isDescendant(parent, child) { + let node = child.parentNode; + while (node != null) { + if (node == parent) { + return true + } + node = node.parentNode; + } + return false + } + + fromPageToNode(x, y) { + return Points.fromPageToNode(this.element, { x, y }) + } + + fromNodeToPage(x, y) { + return Points.fromNodeToPage(this.element, { x, y }) + } + + _move(delta) { + // UO: We need to keep TweenLite's _gsTransform and the private + // _x and _y attributes aligned + let x = this.element._gsTransform.x; + let y = this.element._gsTransform.y; + if (this.movableX) { + x += delta.x; + } + if (this.movableY) { + y += delta.y; + } + this._x = x; + this._y = y; + TweenLite.set(this.element, { x: x, y: y }); + } + + resizeAfterTransform(zoom) { + if (this.onResize) { + let w = this.width * this.scale; + let h = this.height * this.scale; + let event = new ResizeEvent(this, { width: w, height: h }); + this.onResize(event); + } + } + + startResize(e) { + e.preventDefault(); + let event = new CustomEvent('resizeStarted'); + + let oldPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top }; + this.bringToFront(); + + this.element.style.transformOrigin = '0% 0%'; + + let newPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top }; + + let offset = Points.subtract(oldPostition, newPostition); + + this.oldX = e.clientX; + this.oldY = e.clientY; + + e.target.setAttribute('resizing', 'true'); + e.target.setPointerCapture(e.pointerId); + + TweenLite.to(this.element, 0, { css: { left: '+=' + offset.x + 'px' } }); + TweenLite.to(this.element, 0, { css: { top: '+=' + offset.y + 'px' } }); + + this.element.dispatchEvent(event); + } + + resize(e) { + e.preventDefault(); + + let rotation = Angle.radian2degree(this.rotation); + rotation = (rotation + 360) % 360; + let event = new CustomEvent('resized'); + if (e.target.getAttribute('resizing') == 'true') { + + let deltaX = (e.clientX - this.oldX); + let deltaY = (e.clientY - this.oldY); + + let r = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); + let phi = Angle.radian2degree(Math.atan2(deltaX, deltaY)); + + phi = ((phi) + 630) % 360; + let rot = ((rotation + 90) + 630) % 360; + + let diffAngle = ((0 + rot) + 360) % 360; + let phiCorrected = (phi + diffAngle + 360) % 360; + + let resizeW = r * Math.cos(Angle.degree2radian(phiCorrected)); + let resizeH = -r * Math.sin(Angle.degree2radian(phiCorrected)); + + if ((this.element.offsetWidth + resizeW) / this.scale > this.width * 0.5 / this.scale && (this.element.offsetHeight + resizeH) / this.scale > this.height * 0.3 / this.scale) TweenLite.to(this.element, 0, { width: this.element.offsetWidth + resizeW / this.scale, height: this.element.offsetHeight + resizeH / this.scale }); + + this.oldX = e.clientX; + this.oldY = e.clientY; + this.onResizing(); + + this.element.dispatchEvent(event); + } + } + + stopResize(e) { + e.preventDefault(); + + let event = new CustomEvent('resizeEnded'); + let oldPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top }; + this.element.style.transformOrigin = '50% 50%'; + let newPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top }; + let offset = Points.subtract(oldPostition, newPostition); + + TweenLite.to(this.element, 0, { css: { left: '+=' + offset.x + 'px' } }); + TweenLite.to(this.element, 0, { css: { top: '+=' + offset.y + 'px' } }); + + e.target.setAttribute('resizing', 'false'); + + this.element.dispatchEvent(event); + } + } + + DOMScatter.zIndex = 1000; + + class CardLoader { + constructor( + src, + { + x = 0, + y = 0, + width = 1000, + height = 800, + maxWidth = null, + maxHeight = null, + scale = 1, + minScale = 0.5, + maxScale = 1.5, + rotation = 0 + } = {} + ) { + this.src = src; + this.x = x; + this.y = y; + this.scale = scale; + this.rotation = 0; + this.maxScale = maxScale; + this.minScale = minScale; + this.wantedWidth = width; + this.wantedHeight = height; + this.maxWidth = maxWidth != null ? maxWidth : window.innerWidth; + this.maxHeight = maxHeight != null ? maxHeight : window.innerHeight; + this.addedNode = null; + console.log({ + + width, + height, + maxWidth, + maxHeight, + + }); + } + + unload() { + if (this.addedNode) { + this.addedNode.remove(); + this.addedNode = null; + } + } + } + + class PDFLoader extends CardLoader { + constructor(src, { width = 1640, height = 800, scale = 1 } = {}) { + super(src, { width, height, scale }); + if (typeof PDFJS == 'undefined') { + alert('PDF.js needed'); + } + } + + load(domNode) { + return new Promise((resolve, reject) => { + PDFJS.getDocument(this.src).then(pdf => { + pdf.getPage(1).then(page => { + let scale = this.scale * app.renderer.resolution; + let invScale = 1 / scale; + let viewport = page.getViewport(scale); + + // Prepare canvas using PDF page dimensions. + let canvas = document.createElement('canvas'); + let context = canvas.getContext('2d'); + canvas.height = viewport.height; + canvas.width = viewport.width; + + // Render PDF page into canvas context. + let renderContext = { + canvasContext: context, + viewport: viewport + }; + page.render(renderContext); + domNode.appendChild(canvas); + this.wantedWidth = canvas.width; + this.wantedHeight = canvas.height; + this.scale = invScale; + this.addedNode = canvas; + resolve(this); + }); + }); + }) + } + } + + class ImageLoader extends CardLoader { + load(domNode) { + return new Promise((resolve, reject) => { + let isImage = domNode instanceof HTMLImageElement; + let image = isImage ? domNode : document.createElement('img'); + image.onload = e => { + if (!isImage) { + domNode.appendChild(image); + this.addedNode = image; + } + this.wantedWidth = image.naturalWidth; + this.wantedHeight = image.naturalHeight; + + let scaleW = this.maxWidth / image.naturalWidth; + let scaleH = this.maxHeight / image.naturalHeight; + this.scale = Math.min(this.maxScale, Math.min(scaleW, scaleH)); + image.setAttribute('draggable', false); + image.width = image.naturalWidth; + image.height = image.naturalHeight; + resolve(this); + }; + image.onerror = e => { + reject(this); + }; + image.src = this.src; + }) + } + } + + class FrameLoader extends CardLoader { + load(domNode) { + return new Promise((resolve, reject) => { + let isFrame = domNode instanceof HTMLIFrameElement; + let iframe = isFrame ? domNode : document.createElement('iframe'); + console.log('FrameLoader.load', isFrame, iframe, this.src); + iframe.frameBorder = 0; + iframe.style.scrolling = false; + iframe.width = this.wantedWidth; + iframe.height = this.wantedHeight; + if (!isFrame) { + // Unlike img onload is only triggered if the iframe is part of the DOM tree + domNode.appendChild(iframe); + this.addedNode = iframe; + } + iframe.onload = e => { + resolve(this); + }; + iframe.onerror = e => { + reject(this); + }; + iframe.src = this.src; + }) + } + } + + class HTMLLoader extends CardLoader { + load(domNode) { + return new Promise((resolve, reject) => { + let xhr = new XMLHttpRequest(); + xhr.open('GET', this.src, false); + xhr.onload = e => { + domNode.innerHTML = xhr.response; + this.addedNode = domNode.firstElementChild; + let { width, height } = this.size(this.addedNode); + console.log("HTMLLoader.load", { added: this.addedNode, width, height }); + if (width) + this.wantedWidth = width || this.wantedWidth; + if (height) + this.wantedHeight = height || this.wantedHeight; + resolve(this); + }; + xhr.onerror = e => { + reject(this); + }; + xhr.send(); + }) + } + + /** + * Tries to determine the size of the addedNode. + * Checks for explicit width and height style attributes. + * + * Overwrite this method if you want to extract values from other infos. + * + * @returns { width: int, height: int } + * @memberof HTMLLoader + */ + size(node) { + let width = parseInt(node.style.width) || null; + let height = parseInt(node.style.height) || null; + return { width, height } + } + } + + class DOMFlip { + constructor( + domScatterContainer, + flipTemplate, + frontLoader, + backLoader, + { + closeOnMinScale = false, + flipDuration = 1, + fadeDuration = 0.2, + overdoScaling = 1, + autoLoad = false, + center = null, + preloadBack = false, + translatable = true, + scalable = true, + rotatable = true, + onFront = null, + onBack = null, + onClose = null, + onUpdate = null, + onRemoved = null, + onLoaded = null + } = {} + ) { + this.domScatterContainer = domScatterContainer; + this.id = getId$1(); + this.flipDuration = flipDuration; + this.fadeDuration = fadeDuration; + this.closeOnMinScale = closeOnMinScale; + this.flipTemplate = flipTemplate; + this.frontLoader = frontLoader; + this.backLoader = backLoader; + this.translatable = translatable; + this.scalable = scalable; + this.rotatable = rotatable; + this.onFrontFlipped = onFront; + this.onBackFlipped = onBack; + this.onClose = onClose; + this.onRemoved = onRemoved; + this.onUpdate = onUpdate; + this.onLoaded = onLoaded; + this.center = center; + this.preloadBack = preloadBack; + this.overdoScaling = overdoScaling; + if (autoLoad) { + this.load(); + } + } + + load() { + return new Promise((resolve, reject) => { + let t = this.flipTemplate; + let dom = this.domScatterContainer.element; + let wrapper = t.content.querySelector('.flipWrapper'); + wrapper.id = this.id; + let clone = document.importNode(t.content, true); + dom.appendChild(clone); + // We cannot use the document fragment itself because it + // is not part of the main dom tree. After the appendChild + // call we can access the new dom element by id + this.cardWrapper = dom.querySelector('#' + this.id); + let front = this.cardWrapper.querySelector('.front'); + this.frontLoader.load(front).then(loader => { + this.frontLoaded(loader).then((obj) => { + if (this.onLoaded) this.onLoaded(); + resolve(this); + }); + }); + }) + } + + frontLoaded(loader) { + return new Promise((resolve, reject) => { + let scatter = new DOMScatter( + this.cardWrapper, + this.domScatterContainer, + { + x: loader.x, + y: loader.y, + startScale: loader.scale, + scale: loader.scale, + maxScale: loader.maxScale, + minScale: loader.minScale, + width: loader.wantedWidth, + height: loader.wantedHeight, + rotation: loader.rotation, + translatable: this.translatable, + scalable: this.scalable, + rotatable: this.rotatable, + overdoScaling: this.overdoScaling + } + ); + + if (this.center) { + scatter.centerAt(this.center); + } + + if (this.closeOnMinScale) { + + const removeOnMinScale = function () { + if (scatter.scale <= scatter.minScale) { + this.flippable.close(); + + // 'Disable' overdoscaling to avoid weird jumps on close. + scatter.minScale /= scatter.overdoScaling; + scatter.overdoScaling = 1; + + //Remove callback + if (scatter.onTransform) { + let callbackIdx = scatter.onTransform.indexOf(removeOnMinScale); + scatter.onTransform.splice(callbackIdx, 1); + } + } + + }.bind(this); + + + + scatter.addTransformEventCallback(removeOnMinScale); + } + + let flippable = new DOMFlippable(this.cardWrapper, scatter, this); + let back = this.cardWrapper.querySelector('.back'); + + if (this.preloadBack) { + this.backLoader.load(back).then(loader => { + this.setupFlippable(flippable, loader); + }); + } + this.flippable = flippable; + resolve(this); + }) + } + + centerAt(p) { + this.center = p; + this.flippable.centerAt(p); + } + + zoom(scale) { + this.flippable.zoom(scale); + } + + setupFlippable(flippable, loader) { + flippable.wantedWidth = loader.wantedWidth; + flippable.wantedHeight = loader.wantedHeight; + flippable.wantedScale = loader.scale; + flippable.minScale = loader.minScale; + flippable.maxScale = loader.maxScale; + flippable.scaleButtons(); + } + + start({ targetCenter = null } = {}) { + console.log('DOMFlip.start', targetCenter); + if (this.preloadBack) this.flippable.start({ duration: this.flipDuration, targetCenter }); + else { + let back = this.cardWrapper.querySelector('.back'); + let flippable = this.flippable; + this.backLoader.load(back).then(loader => { + this.setupFlippable(flippable, loader); + flippable.start({ duration: this.flipDuration, targetCenter }); + }); + } + } + + fadeOutAndRemove() { + TweenLite.to(this.cardWrapper, this.fadeDuration, { + opacity: 0, + onComplete: () => { + this.cardWrapper.remove(); + } + }); + } + + closed() { + this.unload(); + } + + unload() { + if (!this.preloadBack) { + this.backLoader.unload(); + } + } + } + + class DOMFlippable { + constructor(element, scatter, flip) { + // Set log to console.log or a custom log function + // define data structures to store our touchpoints in + + this.element = element; + this.flip = flip; + this.card = element.querySelector('.flipCard'); + this.front = element.querySelector('.front'); + this.back = element.querySelector('.back'); + this.flipped = false; + this.scatter = scatter; + this.onFrontFlipped = flip.onFrontFlipped; + this.onBackFlipped = flip.onBackFlipped; + this.onClose = flip.onClose; + this.onRemoved = flip.onRemoved; + this.onUpdate = flip.onUpdate; + + this.flipDuration = flip.flipDuration; + this.fadeDuration = flip.fadeDuration; + scatter.addTransformEventCallback(this.scatterTransformed.bind(this)); + console.log('lib.DOMFlippable', 5000); + TweenLite.set(this.element, { perspective: 5000 }); + TweenLite.set(this.card, { transformStyle: 'preserve-3d' }); + TweenLite.set(this.back, { rotationY: -180 }); + TweenLite.set([this.back, this.front], { + backfaceVisibility: 'hidden', + perspective: 5000 + }); + TweenLite.set(this.front, { visibility: 'visible' }); + this.infoBtn = element.querySelector('.infoBtn'); + this.backBtn = element.querySelector('.backBtn'); + this.closeBtn = element.querySelector('.closeBtn'); + /* Buttons are not guaranteed to exist. */ + if (this.infoBtn) { + InteractionMapper$1.on('tap', this.infoBtn, event => this.flip.start()); + + this.enable(this.infoBtn); + } + if (this.backBtn) { + InteractionMapper$1.on('tap', this.backBtn, event => this.start()); + } + if (this.closeBtn) { + InteractionMapper$1.on('tap', this.closeBtn, event => this.close()); + this.enable(this.closeBtn); + } + this.scaleButtons(); + this.bringToFront(); + } + + close() { + this.disable(this.infoBtn); + this.disable(this.closeBtn); + if (this.onClose) { + this.onClose(this); + this.flip.closed(); + } else { + this.scatter.zoom(0.1, { + animate: this.fadeDuration, + onComplete: () => { + this.element.remove(); + this.flip.closed(); + if (this.onRemoved) { + this.onRemoved.call(this); + } + } + }); + } + } + + showFront() { + TweenLite.set(this.front, { visibility: 'visible' }); + } + + centerAt(p) { + this.scatter.centerAt(p); + } + + zoom(scale) { + this.scatter.zoom(scale); + } + + get buttonScale() { + let iscale = 1.0; + + if (this.scatter != null) { + let scale = this.scatter.scale || 1; + iscale = 1.0 / scale; + } + return iscale + } + + scaleButtons() { + //This also works for svgs. + // if (this.infoBtn) + // this.infoBtn.style.transform = "scale(" + this.buttonScale + ")" + + // if (this.backBtn) + // this.backBtn.style.transform = "scale(" + this.buttonScale + ")" + + // if (this.closeBtn) + // this.closeBtn.style.transform = "scale(" + this.buttonScale + ")" + + console.log(this.buttonScale); + //// This did not work with svgs! + TweenLite.set([this.infoBtn, this.backBtn, this.closeBtn], { + scale: this.buttonScale + }); + } + + bringToFront() { + this.scatter.bringToFront(); + TweenLite.set(this.element, { zIndex: DOMScatter.zIndex++ }); + } + + clickInfo() { + this.bringToFront(); + this.infoBtn.click(); + } + + scatterTransformed(event) { + this.scaleButtons(); + } + + targetRotation(alpha) { + let ortho = 90; + let rest = alpha % ortho; + let delta = 0.0; + if (rest > ortho / 2.0) { + delta = ortho - rest; + } else { + delta = -rest; + } + return delta + } + + infoValues(info) { + let startX = this.element._gsTransform.x; + let startY = this.element._gsTransform.y; + let startAngle = this.element._gsTransform.rotation; + let startScale = this.element._gsTransform.scaleX; + let w = this.element.style.width; + let h = this.element.style.height; + console.log(info, startX, startY, startAngle, startScale, w, h); + } + + show(element, duration = 0, alpha = 1) { + if (element) { + TweenLite.to(element, duration, { autoAlpha: alpha }); // visibility: 'visible', display: 'initial'}) + } + } + + hide(element, duration = 0, alpha = 0) { + if (element) { + TweenLite.to(element, duration, { autoAlpha: alpha }); // {visibility: 'hidden', display: 'none'}) + } + } + + + + enable(button) { + this.show(button, this.fadeDuration); + if (button) { + TweenLite.set(button, { pointerEvents: 'auto' }); + } + } + + disable(button) { + this.hide(button, this.fadeDuration); + if (button) { + TweenLite.set(button, { pointerEvents: 'none' }); + } + } + + start({ targetCenter = null } = {}) { + this.bringToFront(); + if (!this.flipped) { + this.startX = this.element._gsTransform.x; + this.startY = this.element._gsTransform.y; + this.startAngle = this.element._gsTransform.rotation; + this.startScale = this.element._gsTransform.scaleX; + this.startWidth = this.element.style.width; + this.startHeight = this.element.style.height; + this.scatterStartWidth = this.scatter.width; + this.scatterStartHeight = this.scatter.height; + this.show(this.back); + this.disable(this.infoBtn); + this.disable(this.closeBtn); + } else { + this.show(this.front, this.fadeDuration); + this.disable(this.backBtn); + } + let { scalable, translatable, rotatable } = this.scatter; + this.saved = { scalable, translatable, rotatable }; + this.scatter.scalable = false; + this.scatter.translatable = false; + this.scatter.rotatable = false; + this.scatter.killAnimation(); + + this.flipped = !this.flipped; + let targetY = this.flipped ? 180 : 0; + let targetZ = this.flipped + ? this.startAngle + this.targetRotation(this.startAngle) + : this.startAngle; + let targetScale = this.flipped ? this.wantedScale : this.startScale; + let w = this.flipped ? this.wantedWidth : this.startWidth; + let h = this.flipped ? this.wantedHeight : this.startHeight; + let dw = this.wantedWidth - this.scatter.width; + let dh = this.wantedHeight - this.scatter.height; + let tc = targetCenter; + let xx = tc != null ? tc.x - w / 2 : this.startX - dw / 2; + let yy = tc != null ? tc.y - h / 2 : this.startY - dh / 2; + let x = this.flipped ? xx : this.startX; + let y = this.flipped ? yy : this.startY; + + console.log("DOMFlippable.start", this.flipped, targetCenter, x, y, this.saved); + let onUpdate = this.onUpdate !== null ? () => this.onUpdate(this) : null; + console.log(this.flipDuration); + TweenLite.to(this.card, this.flipDuration, { + rotationY: targetY, + ease: Power1.easeOut, + transformOrigin: '50% 50%', + onUpdate, + onComplete: e => { + if (this.flipped) { + //this.hide(this.front) + this.enable(this.backBtn); + this.show(this.backBtn); + + if (this.onFrontFlipped) { + this.onFrontFlipped(this); + } + } else { + + if (this.onBackFlipped == null) { + this.enable(this.infoBtn, this.fadeDuration); + this.enable(this.closeBtn, this.fadeDuration); + } else { + this.onBackFlipped(this); + } + this.flip.unload(); + } + this.scatter.scale = targetScale; + this.scaleButtons(); + this.scatter.rotationDegrees = targetZ; + this.scatter.width = this.flipped ? w : this.scatterStartWidth; + this.scatter.height = this.flipped ? h : this.scatterStartHeight; + + let { scalable, translatable, rotatable } = this.saved; + this.scatter.scalable = scalable; + this.scatter.translatable = translatable; + this.scatter.rotatable = rotatable; + }, + force3D: true + }); + + // See https://greensock.com/forums/topic/7997-rotate-the-shortest-way/ + TweenLite.to(this.element, this.flipDuration / 2, { + scale: targetScale, + ease: Power1.easeOut, + rotationZ: targetZ + '_short', + transformOrigin: '50% 50%', + width: w, + height: h, + x: x, + y: y, + onComplete: e => { + if (this.flipped) { + this.hide(this.front); + // this.hide(this.infoBtn) + } else { + this.hide(this.back); + // this.show(this.infoBtn) + } + } + }); + } + } + + class Index { + + constructor(template, pages, notfound='thumbnails/notfound.png') { + this.template = template; + this.pages = pages; + this.notfound = notfound; + } + + setup() { + for(let pair of this.pages) { + let [title, src] = pair; + let id = getId(); + pair.push(id); + let t = this.template; + let wrapper = t.content.querySelector('.wrapper'); + wrapper.id = id; + let clone = document.importNode(t.content, true); + container.appendChild(clone); + wrapper = container.querySelector('#'+id); + + let icon = wrapper.querySelector('.icon'); + + icon.onerror = (e) => { + if (this.notfound) + icon.src = this.notfound; + }; + let iconSrc = src.replace('.html', '.png'); + //console.log("iconSrc", iconSrc) + if (iconSrc.endsWith('index.png')) { + icon.src = iconSrc.replace('index.png', 'thumbnail.png'); + } + else { + icon.src = 'thumbnails/' + iconSrc; + } + + // icon.src = 'thumbnails/' + iconSrc + // console.log(iconSrc) + wrapper.href = src; + let titleDiv = wrapper.querySelector('.title'); + titleDiv.innerText = title; + } + } + + frames() { + if (this.pages.length == 0) + return + let [title, src, id] = this.pages.shift(); + let iframe = document.createElement('iframe'); + iframe.frameborder = 0; + let wrapper = document.getElementById(id); + let icon = wrapper.querySelector('.icon'); + + icon.parentNode.replaceChild(iframe, icon); + iframe.onload = (e) => { + this.frames(); + }; + iframe.src = src + window.location.search; + } + + load() { + this.setup(); + if (window.location.search.startsWith('?test')) + this.frames(); + } + + loadAndTest() { + this.setup(); + if (!Capabilities.isMobile) + this.frames(); + } + } + class FrameContainer { constructor(element) { @@ -7485,7 +7442,7 @@ window.Polygon = Polygon; window.Poppable = Poppable; window.Popup = Popup; - window.PopupMenu = PopupMenu$1; + window.PopupMenu = PopupMenu; window.ResizeEvent = ResizeEvent; window.ScatterEvent = ScatterEvent; window.Sets = Sets; diff --git a/dist/iwmlib.pixi.js b/dist/iwmlib.pixi.js index aba397f..16d53ae 100644 --- a/dist/iwmlib.pixi.js +++ b/dist/iwmlib.pixi.js @@ -4781,7 +4781,7 @@ } } - /* globals Hammer, propagating */ + /* eslint-disable no-unused-vars */ /** Interaction patterns @@ -4789,6 +4789,7 @@ */ class IInteractionTarget extends Interface { + capture(event) { return typeof true } @@ -4992,10 +4993,10 @@ let d1 = Points.subtract(c1, p1); let d2 = Points.subtract(c2, p2); let cm = Points.mean(c1, c2); - + // Using the mean leads to jumps between time slices with 3 and 2 fingers // We use the mean of deltas instead - let delta = Points.mean(d1, d2); + let delta = Points.mean(d1, d2); let zoom = 1.0; let distance1 = Points.distance(p1, p2); let distance2 = Points.distance(c1, c2); @@ -5199,7 +5200,6 @@ } let result = false; if (this.isTap(key)) { - this.registerTap(key, ended); result = this.tapCounts.get(key) == 2; } @@ -5313,7 +5313,9 @@ if (this.capturePointerEvents) { try { element.setPointerCapture(e.pointerId); - } catch (e) { } + } catch (e) { + console.warn('Cannot setPointerCapture'); + } } this.onStart(e); } @@ -5345,7 +5347,9 @@ if (this.capturePointerEvents) { try { element.releasePointerCapture(e.pointerId); - } catch (e) { } + } catch (e) { + console.warn('Cannot release pointer'); + } } }, useCapture @@ -5389,7 +5393,7 @@ e => { if (this.debug) console.log('pointerout', e.pointerId, e.pointerType, e.target); if (e.target == element) { - this.onEnd(e); + this.onEnd(e); } }, useCapture); @@ -5492,9 +5496,8 @@ e => { if (e.target == element) { this.onEnd(e); - console.warn("Shouldn't happen: mouseout ends interaction"); + console.warn('Shouldn\'t happen: mouseout ends interaction'); } - }, useCapture ); @@ -5594,39 +5597,42 @@ // 'targetTouches' let result = {}; switch (event.constructor.name) { - case 'MouseEvent': - let buttons = event.buttons || event.which; - if (buttons) result['mouse'] = this.getPosition(event); - break - case 'PointerEvent': - result[event.pointerId.toString()] = this.getPosition(event); - break - case 'Touch': - let id = + case 'MouseEvent': { + let buttons = event.buttons || event.which; + if (buttons) result['mouse'] = this.getPosition(event); + break + } + case 'PointerEvent': { + result[event.pointerId.toString()] = this.getPosition(event); + break + } + case 'Touch': { + let id = event.touchType === 'stylus' ? 'stylus' : event.identifier.toString(); - result[id] = this.getPosition(event); - break - // case 'TouchEvent': - // // Needs to be observed: Perhaps changedTouches are all we need. If so - // // we can remove the touchEventKey default parameter - // if (touchEventKey == 'all') { - // for(let t of event.targetTouches) { - // result[t.identifier.toString()] = this.getPosition(t) - // } - // for(let t of event.changedTouches) { - // result[t.identifier.toString()] = this.getPosition(t) - // } - // } - // else { - // for(let t of event.changedTouches) { - // result[t.identifier.toString()] = this.getPosition(t) - // } - // } - // break - default: - break + result[id] = this.getPosition(event); + break + } + // case 'TouchEvent': + // // Needs to be observed: Perhaps changedTouches are all we need. If so + // // we can remove the touchEventKey default parameter + // if (touchEventKey == 'all') { + // for(let t of event.targetTouches) { + // result[t.identifier.toString()] = this.getPosition(t) + // } + // for(let t of event.changedTouches) { + // result[t.identifier.toString()] = this.getPosition(t) + // } + // } + // else { + // for(let t of event.changedTouches) { + // result[t.identifier.toString()] = this.getPosition(t) + // } + // } + // break + default: + break } return result } @@ -5654,7 +5660,7 @@ let point = extracted[key]; let updated = this.interaction.update(key, point); if (updated) { - console.warn("new pointer in updateInteraction shouldn't happen", key); + console.warn('new pointer in updateInteraction shouldn\'t happen', key); this.interactionStarted(event, key, point); } } @@ -6039,6 +6045,11 @@ window.Capabilities = Capabilities; window.CapabilitiesTests = CapabilitiesTests; + /** Basic class for poppable elements that need to be closed as soon as one poppable is + * shown. + */ + + /* eslint-disable no-unused-vars */ /** * A base class for scatter specific events. * @@ -6088,7 +6099,7 @@ toString() { return ( - "Event('scatterTransformed', scale: " + + 'Event(\'scatterTransformed\', scale: ' + this.scale + ' about: ' + this.about.x + @@ -6163,7 +6174,7 @@ // Avoid division by zero errors later on // and consider the number of involved pointers sind addVelocity will be called by the // onMove events - let velocity = { t: t, dt: dt, dx: delta.x / delta.number, dy: delta.y / delta.number}; + let velocity = { t: t, dt: dt, dx: delta.x / delta.number, dy: delta.y / delta.number }; this.velocities.push(velocity); while (this.velocities.length > buffer) { this.velocities.shift(); @@ -6347,7 +6358,7 @@ this.resizable = resizable; this.mouseZoomFactor = mouseZoomFactor; this.autoBringToFront = autoBringToFront; - + this.dragging = false; this.onTransform = onTransform != null ? [onTransform] : null; this.onClose = onClose != null ? [onClose] : null; @@ -6387,7 +6398,6 @@ let alpha = delta.rotate; if (this.maxRotation != null) { if (Math.abs(alpha) > this.maxRotation) { - console.log("limited rotation"); alpha = 0; } } @@ -6460,7 +6470,7 @@ let stagePolygon = this.containerPolygon; // UO: since keepOnStage is called in nextVelocity we need to // ensure a return value - if (!stagePolygon) return { x: 0, y: 0} + if (!stagePolygon) return { x: 0, y: 0 } let polygon = this.polygon; let bounced = this.bouncing(); if (bounced) { @@ -6744,20 +6754,6 @@ } this._updateTransparency(); } - // - // if (this.onTransform != null) { - // let event = new ScatterEvent(this, { - // translate: {x: 0, y: 0}, - // scale: this.scale, - // rotate: 0, - // about: null, - // fast: false, - // type: ZOOM - // }) - // this.onTransform.forEach(function(f) { - // f(event) - // }) - // } } onStart(event, interaction) { @@ -6999,15 +6995,13 @@ } this.resizeButton = null; if (resizable) { - let button = document.createElement("div"); - button.style.position = "absolute"; - button.style.right = "0px"; - button.style.bottom = "0px"; - button.style.width = "50px"; - button.style.height = "50px"; - // button.style.borderRadius = "100% 0px 0px 0px"; - // button.style.background = this.element.style.backgroundColor - button.className = "interactiveElement"; + let button = document.createElement('div'); + button.style.position = 'absolute'; + button.style.right = '0px'; + button.style.bottom = '0px'; + button.style.width = '50px'; + button.style.height = '50px'; + button.className = 'interactiveElement'; this.element.appendChild(button); button.addEventListener('pointerdown', (e) => { @@ -7182,43 +7176,13 @@ TweenLite.set(this.element, { zIndex: DOMScatter.zIndex++ }); } - toggleVideo(element) { - if (element.paused) { - element.play(); - } else { - element.pause(); - } - } - onTap(event, interaction, point) { if (this.simulateClick) { let p = Points.fromPageToNode(this.element, point); - let iframe = this.element.querySelector('iframe'); - if (iframe) { - let doc = iframe.contentWindow.document; - let element = doc.elementFromPoint(p.x, p.y); - if (element == null) { - return - } - switch (element.tagName) { - case 'VIDEO': - console.log(element.currentSrc); - if (PopupMenu) { - PopupMenu.open( - { - Fullscreen: () => - window.open(element.currentSrc), - Play: () => element.play() - }, - { x, y } - ); - } else { - this.toggleVideo(element); - } - break - default: - element.click(); - } + let element = document.elementFromPoint(p.x, p.y); + if (element != null) { + console.log('tap simulates click'); + element.click(); } } } @@ -7259,37 +7223,35 @@ } resizeAfterTransform(zoom) { - // let w = this.width * this.scale - // let h = this.height * this.scale - // TweenLite.set(this.element, { width: w, height: h }) if (this.onResize) { + let w = this.width * this.scale; + let h = this.height * this.scale; let event = new ResizeEvent(this, { width: w, height: h }); this.onResize(event); } - if (this.resizeButton != null) ; } startResize(e) { e.preventDefault(); let event = new CustomEvent('resizeStarted'); - let oldPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top}; + let oldPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top }; this.bringToFront(); - this.element.style.transformOrigin = "0% 0%"; + this.element.style.transformOrigin = '0% 0%'; - let newPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top}; + let newPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top }; let offset = Points.subtract(oldPostition, newPostition); this.oldX = e.clientX; this.oldY = e.clientY; - e.target.setAttribute('resizing', "true"); + e.target.setAttribute('resizing', 'true'); e.target.setPointerCapture(e.pointerId); - TweenLite.to(this.element, 0, { css: { left: "+=" + offset.x + "px" } }); - TweenLite.to(this.element, 0, { css: { top: "+=" + offset.y + "px" } }); + TweenLite.to(this.element, 0, { css: { left: '+=' + offset.x + 'px' } }); + TweenLite.to(this.element, 0, { css: { top: '+=' + offset.y + 'px' } }); this.element.dispatchEvent(event); } @@ -7300,7 +7262,7 @@ let rotation = Angle.radian2degree(this.rotation); rotation = (rotation + 360) % 360; let event = new CustomEvent('resized'); - if (e.target.getAttribute('resizing') == "true") { + if (e.target.getAttribute('resizing') == 'true') { let deltaX = (e.clientX - this.oldX); let deltaY = (e.clientY - this.oldY); @@ -7317,7 +7279,7 @@ let resizeW = r * Math.cos(Angle.degree2radian(phiCorrected)); let resizeH = -r * Math.sin(Angle.degree2radian(phiCorrected)); - if ((this.element.offsetWidth + resizeW) / this.scale > this.width * 0.5 / this.scale && (this.element.offsetHeight + resizeH) / this.scale > this.height * 0.3 / this.scale) TweenLite.to(this.element, 0, { width: this.element.offsetWidth + resizeW / this.scale, height: this.element.offsetHeight + resizeH / this.scale }); + if ((this.element.offsetWidth + resizeW) / this.scale > this.width * 0.5 / this.scale && (this.element.offsetHeight + resizeH) / this.scale > this.height * 0.3 / this.scale) TweenLite.to(this.element, 0, { width: this.element.offsetWidth + resizeW / this.scale, height: this.element.offsetHeight + resizeH / this.scale }); this.oldX = e.clientX; this.oldY = e.clientY; @@ -7331,15 +7293,15 @@ e.preventDefault(); let event = new CustomEvent('resizeEnded'); - let oldPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top}; - this.element.style.transformOrigin = "50% 50%"; - let newPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top}; + let oldPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top }; + this.element.style.transformOrigin = '50% 50%'; + let newPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top }; let offset = Points.subtract(oldPostition, newPostition); - TweenLite.to(this.element, 0, { css: { left: "+=" + offset.x + "px" } }); - TweenLite.to(this.element, 0, { css: { top: "+=" + offset.y + "px" } }); + TweenLite.to(this.element, 0, { css: { left: '+=' + offset.x + 'px' } }); + TweenLite.to(this.element, 0, { css: { top: '+=' + offset.y + 'px' } }); - e.target.setAttribute('resizing', "false"); + e.target.setAttribute('resizing', 'false'); this.element.dispatchEvent(event); } diff --git a/lib/interaction.js b/lib/interaction.js index a63c6d9..2b86c89 100755 --- a/lib/interaction.js +++ b/lib/interaction.js @@ -1,8 +1,9 @@ +/* eslint-disable no-unused-vars */ /* globals Hammer, propagating */ /*eslint no-console: ["error", { allow: ["log", "warn", "info", "error"] }]*/ import Interface from './interface.js' -import { Points, Angle, MapProxy } from './utils.js' +import { Points, MapProxy } from './utils.js' import Events from './events.js' import Logging from './logging.js' @@ -12,6 +13,7 @@ import Logging from './logging.js' */ export class IInteractionTarget extends Interface { + capture(event) { return typeof true } @@ -215,10 +217,10 @@ export class InteractionPoints { let d1 = Points.subtract(c1, p1) let d2 = Points.subtract(c2, p2) let cm = Points.mean(c1, c2) - + // Using the mean leads to jumps between time slices with 3 and 2 fingers // We use the mean of deltas instead - let delta = Points.mean(d1, d2) + let delta = Points.mean(d1, d2) let zoom = 1.0 let distance1 = Points.distance(p1, p2) let distance2 = Points.distance(c1, c2) @@ -422,7 +424,6 @@ export class Interaction extends InteractionPoints { } let result = false if (this.isTap(key)) { - this.registerTap(key, ended) result = this.tapCounts.get(key) == 2 } @@ -536,7 +537,9 @@ export class InteractionDelegate { if (this.capturePointerEvents) { try { element.setPointerCapture(e.pointerId) - } catch (e) { } + } catch (e) { + console.warn('Cannot setPointerCapture') + } } this.onStart(e) } @@ -568,7 +571,9 @@ export class InteractionDelegate { if (this.capturePointerEvents) { try { element.releasePointerCapture(e.pointerId) - } catch (e) { } + } catch (e) { + console.warn('Cannot release pointer') + } } }, useCapture @@ -612,7 +617,7 @@ export class InteractionDelegate { e => { if (this.debug) console.log('pointerout', e.pointerId, e.pointerType, e.target) if (e.target == element) { - this.onEnd(e) + this.onEnd(e) } }, useCapture) @@ -715,9 +720,8 @@ export class InteractionDelegate { e => { if (e.target == element) { this.onEnd(e) - console.warn("Shouldn't happen: mouseout ends interaction") + console.warn('Shouldn\'t happen: mouseout ends interaction') } - }, useCapture ) @@ -819,39 +823,42 @@ export class InteractionDelegate { // 'targetTouches' let result = {} switch (event.constructor.name) { - case 'MouseEvent': - let buttons = event.buttons || event.which - if (buttons) result['mouse'] = this.getPosition(event) - break - case 'PointerEvent': - result[event.pointerId.toString()] = this.getPosition(event) - break - case 'Touch': - let id = + case 'MouseEvent': { + let buttons = event.buttons || event.which + if (buttons) result['mouse'] = this.getPosition(event) + break + } + case 'PointerEvent': { + result[event.pointerId.toString()] = this.getPosition(event) + break + } + case 'Touch': { + let id = event.touchType === 'stylus' ? 'stylus' : event.identifier.toString() - result[id] = this.getPosition(event) - break - // case 'TouchEvent': - // // Needs to be observed: Perhaps changedTouches are all we need. If so - // // we can remove the touchEventKey default parameter - // if (touchEventKey == 'all') { - // for(let t of event.targetTouches) { - // result[t.identifier.toString()] = this.getPosition(t) - // } - // for(let t of event.changedTouches) { - // result[t.identifier.toString()] = this.getPosition(t) - // } - // } - // else { - // for(let t of event.changedTouches) { - // result[t.identifier.toString()] = this.getPosition(t) - // } - // } - // break - default: - break + result[id] = this.getPosition(event) + break + } + // case 'TouchEvent': + // // Needs to be observed: Perhaps changedTouches are all we need. If so + // // we can remove the touchEventKey default parameter + // if (touchEventKey == 'all') { + // for(let t of event.targetTouches) { + // result[t.identifier.toString()] = this.getPosition(t) + // } + // for(let t of event.changedTouches) { + // result[t.identifier.toString()] = this.getPosition(t) + // } + // } + // else { + // for(let t of event.changedTouches) { + // result[t.identifier.toString()] = this.getPosition(t) + // } + // } + // break + default: + break } return result } @@ -879,7 +886,7 @@ export class InteractionDelegate { let point = extracted[key] let updated = this.interaction.update(key, point) if (updated) { - console.warn("new pointer in updateInteraction shouldn't happen", key) + console.warn('new pointer in updateInteraction shouldn\'t happen', key) this.interactionStarted(event, key, point) } } diff --git a/lib/scatter.js b/lib/scatter.js index ceb4c23..02a65ac 100644 --- a/lib/scatter.js +++ b/lib/scatter.js @@ -1,8 +1,11 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable no-console */ +/* globals TweenLite debugCanvas */ import { Points, Polygon, Angle, Elements } from './utils.js' import Events from './events.js' import { InteractionMapper } from './interaction.js' import { Capabilities } from './capabilities.js' - +import PopupMenu from './popupmenu.js' /** * A base class for scatter specific events. * @@ -21,8 +24,6 @@ export class BaseEvent { const START = 'onStart' const UPDATE = 'onUpdate' const END = 'onEnd' -const ZOOM = 'onZoom' -const MOVE = 'onMove' /** * A scatter event that describes how the scatter has changed. @@ -54,7 +55,7 @@ export class ScatterEvent extends BaseEvent { toString() { return ( - "Event('scatterTransformed', scale: " + + 'Event(\'scatterTransformed\', scale: ' + this.scale + ' about: ' + this.about.x + @@ -129,7 +130,7 @@ class Throwable { // Avoid division by zero errors later on // and consider the number of involved pointers sind addVelocity will be called by the // onMove events - let velocity = { t: t, dt: dt, dx: delta.x / delta.number, dy: delta.y / delta.number} + let velocity = { t: t, dt: dt, dx: delta.x / delta.number, dy: delta.y / delta.number } this.velocities.push(velocity) while (this.velocities.length > buffer) { this.velocities.shift() @@ -313,7 +314,7 @@ export class AbstractScatter extends Throwable { this.resizable = resizable this.mouseZoomFactor = mouseZoomFactor this.autoBringToFront = autoBringToFront - + this.dragging = false this.onTransform = onTransform != null ? [onTransform] : null this.onClose = onClose != null ? [onClose] : null @@ -353,7 +354,6 @@ export class AbstractScatter extends Throwable { let alpha = delta.rotate if (this.maxRotation != null) { if (Math.abs(alpha) > this.maxRotation) { - console.log("limited rotation") alpha = 0 } } @@ -426,7 +426,7 @@ export class AbstractScatter extends Throwable { let stagePolygon = this.containerPolygon // UO: since keepOnStage is called in nextVelocity we need to // ensure a return value - if (!stagePolygon) return { x: 0, y: 0} + if (!stagePolygon) return { x: 0, y: 0 } let polygon = this.polygon let bounced = this.bouncing() if (bounced) { @@ -710,20 +710,6 @@ export class AbstractScatter extends Throwable { } this._updateTransparency() } - // - // if (this.onTransform != null) { - // let event = new ScatterEvent(this, { - // translate: {x: 0, y: 0}, - // scale: this.scale, - // rotate: 0, - // about: null, - // fast: false, - // type: ZOOM - // }) - // this.onTransform.forEach(function(f) { - // f(event) - // }) - // } } onStart(event, interaction) { @@ -838,10 +824,10 @@ export class AbstractScatter extends Throwable { scale: this.scale, fast: false, type: null - }); + }) this.onTransform.forEach(function (f) { - f(event); - }); + f(event) + }) } } @@ -911,14 +897,13 @@ export class DOMScatterContainer { if (typeof debugCanvas !== 'undefined') { requestAnimationFrame(dt => { - this.showTouches(dt) + this.showTouches(dt, debugCanvas) }) } } - showTouches(dt) { + showTouches(dt, canvas) { let resolution = window.devicePixelRatio - let canvas = debugCanvas let current = this.delegate.interaction.current let context = canvas.getContext('2d') let radius = 20 * resolution @@ -1127,15 +1112,13 @@ export class DOMScatter extends AbstractScatter { } this.resizeButton = null if (resizable) { - let button = document.createElement("div") - button.style.position = "absolute" - button.style.right = "0px" - button.style.bottom = "0px" - button.style.width = "50px"; - button.style.height = "50px"; - // button.style.borderRadius = "100% 0px 0px 0px"; - // button.style.background = this.element.style.backgroundColor - button.className = "interactiveElement" + let button = document.createElement('div') + button.style.position = 'absolute' + button.style.right = '0px' + button.style.bottom = '0px' + button.style.width = '50px' + button.style.height = '50px' + button.className = 'interactiveElement' this.element.appendChild(button) button.addEventListener('pointerdown', (e) => { @@ -1310,43 +1293,13 @@ export class DOMScatter extends AbstractScatter { TweenLite.set(this.element, { zIndex: DOMScatter.zIndex++ }) } - toggleVideo(element) { - if (element.paused) { - element.play() - } else { - element.pause() - } - } - onTap(event, interaction, point) { if (this.simulateClick) { let p = Points.fromPageToNode(this.element, point) - let iframe = this.element.querySelector('iframe') - if (iframe) { - let doc = iframe.contentWindow.document - let element = doc.elementFromPoint(p.x, p.y) - if (element == null) { - return - } - switch (element.tagName) { - case 'VIDEO': - console.log(element.currentSrc) - if (PopupMenu) { - PopupMenu.open( - { - Fullscreen: () => - window.open(element.currentSrc), - Play: () => element.play() - }, - { x, y } - ) - } else { - this.toggleVideo(element) - } - break - default: - element.click() - } + let element = document.elementFromPoint(p.x, p.y) + if (element != null) { + console.log('tap simulates click') + element.click() } } } @@ -1387,42 +1340,37 @@ export class DOMScatter extends AbstractScatter { } resizeAfterTransform(zoom) { - // let w = this.width * this.scale - // let h = this.height * this.scale - // TweenLite.set(this.element, { width: w, height: h }) if (this.onResize) { + let w = this.width * this.scale + let h = this.height * this.scale let event = new ResizeEvent(this, { width: w, height: h }) this.onResize(event) } - if (this.resizeButton != null) { - // this.resizeButton.style.width = 50/this.scale+"px" - // this.resizeButton.style.height = 50/this.scale+"px" - } } startResize(e) { e.preventDefault() let event = new CustomEvent('resizeStarted') - let oldPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top}; + let oldPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top } this.bringToFront() - this.element.style.transformOrigin = "0% 0%" + this.element.style.transformOrigin = '0% 0%' - let newPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top}; + let newPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top } let offset = Points.subtract(oldPostition, newPostition) this.oldX = e.clientX this.oldY = e.clientY - e.target.setAttribute('resizing', "true") + e.target.setAttribute('resizing', 'true') e.target.setPointerCapture(e.pointerId) - TweenLite.to(this.element, 0, { css: { left: "+=" + offset.x + "px" } }) - TweenLite.to(this.element, 0, { css: { top: "+=" + offset.y + "px" } }) + TweenLite.to(this.element, 0, { css: { left: '+=' + offset.x + 'px' } }) + TweenLite.to(this.element, 0, { css: { top: '+=' + offset.y + 'px' } }) - this.element.dispatchEvent(event); + this.element.dispatchEvent(event) } resize(e) { @@ -1431,7 +1379,7 @@ export class DOMScatter extends AbstractScatter { let rotation = Angle.radian2degree(this.rotation) rotation = (rotation + 360) % 360 let event = new CustomEvent('resized') - if (e.target.getAttribute('resizing') == "true") { + if (e.target.getAttribute('resizing') == 'true') { let deltaX = (e.clientX - this.oldX) let deltaY = (e.clientY - this.oldY) @@ -1448,13 +1396,13 @@ export class DOMScatter extends AbstractScatter { let resizeW = r * Math.cos(Angle.degree2radian(phiCorrected)) let resizeH = -r * Math.sin(Angle.degree2radian(phiCorrected)) - if ((this.element.offsetWidth + resizeW) / this.scale > this.width * 0.5 / this.scale && (this.element.offsetHeight + resizeH) / this.scale > this.height * 0.3 / this.scale) TweenLite.to(this.element, 0, { width: this.element.offsetWidth + resizeW / this.scale, height: this.element.offsetHeight + resizeH / this.scale }); + if ((this.element.offsetWidth + resizeW) / this.scale > this.width * 0.5 / this.scale && (this.element.offsetHeight + resizeH) / this.scale > this.height * 0.3 / this.scale) TweenLite.to(this.element, 0, { width: this.element.offsetWidth + resizeW / this.scale, height: this.element.offsetHeight + resizeH / this.scale }) this.oldX = e.clientX this.oldY = e.clientY this.onResizing() - this.element.dispatchEvent(event); + this.element.dispatchEvent(event) } } @@ -1462,17 +1410,17 @@ export class DOMScatter extends AbstractScatter { e.preventDefault() let event = new CustomEvent('resizeEnded') - let oldPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top}; - this.element.style.transformOrigin = "50% 50%" - let newPostition = {x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top}; + let oldPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top } + this.element.style.transformOrigin = '50% 50%' + let newPostition = { x: this.element.getBoundingClientRect().left, y: this.element.getBoundingClientRect().top } let offset = Points.subtract(oldPostition, newPostition) - TweenLite.to(this.element, 0, { css: { left: "+=" + offset.x + "px" } }) - TweenLite.to(this.element, 0, { css: { top: "+=" + offset.y + "px" } }) + TweenLite.to(this.element, 0, { css: { left: '+=' + offset.x + 'px' } }) + TweenLite.to(this.element, 0, { css: { top: '+=' + offset.y + 'px' } }) - e.target.setAttribute('resizing', "false") + e.target.setAttribute('resizing', 'false') - this.element.dispatchEvent(event); + this.element.dispatchEvent(event) } } From 0c190f1f769c581752d4783a000c8657eb0ba02b Mon Sep 17 00:00:00 2001 From: Sebastian Kupke Date: Fri, 5 Jul 2019 09:34:22 +0200 Subject: [PATCH 08/17] Fixed flippable doctest. --- dist/iwmlib.js | 7 ++++++- dist/iwmlib.pixi.js | 2 +- lib/flippable.html | 7 ++++++- lib/flippable.js | 1 - lib/interaction.js | 1 + lib/scatter.js | 5 +++++ 6 files changed, 19 insertions(+), 4 deletions(-) diff --git a/dist/iwmlib.js b/dist/iwmlib.js index db06854..8244e1f 100644 --- a/dist/iwmlib.js +++ b/dist/iwmlib.js @@ -2638,6 +2638,7 @@ * @param {object} [opts] - An options object. See the hammer documentation for more details. */ static on(types, elements, cb, opts = {}) { + opts = Object.assign({}, { }, opts); @@ -3754,6 +3755,11 @@ this.onCapture = null; this.element = element; if (stopEvents === 'auto') { + /* + The events have to be stopped in Safari, otherwise the whole page will be zoomed with + a pinch gesture (preventDefault in method preventPinch). In order to enable the + movement of scatter objects, the touchmove event has to be bound again. + */ if (Capabilities.isSafari) { document.addEventListener( 'touchmove', @@ -4744,7 +4750,6 @@ /* Buttons are not guaranteed to exist. */ if (this.infoBtn) { InteractionMapper$1.on('tap', this.infoBtn, event => this.flip.start()); - this.enable(this.infoBtn); } if (this.backBtn) { diff --git a/dist/iwmlib.pixi.js b/dist/iwmlib.pixi.js index aba397f..169c18d 100644 --- a/dist/iwmlib.pixi.js +++ b/dist/iwmlib.pixi.js @@ -5805,6 +5805,7 @@ * @param {object} [opts] - An options object. See the hammer documentation for more details. */ static on(types, elements, cb, opts = {}) { + opts = Object.assign({}, { }, opts); @@ -7615,7 +7616,6 @@ /* Buttons are not guaranteed to exist. */ if (this.infoBtn) { InteractionMapper$1.on('tap', this.infoBtn, event => this.flip.start()); - this.enable(this.infoBtn); } if (this.backBtn) { diff --git a/lib/flippable.html b/lib/flippable.html index e656c34..ed7d0b2 100644 --- a/lib/flippable.html +++ b/lib/flippable.html @@ -66,7 +66,7 @@ templates. diff --git a/lib/flippable.js b/lib/flippable.js index c85adb8..836c40c 100644 --- a/lib/flippable.js +++ b/lib/flippable.js @@ -404,7 +404,6 @@ export class DOMFlippable { /* Buttons are not guaranteed to exist. */ if (this.infoBtn) { InteractionMapper.on('tap', this.infoBtn, event => this.flip.start()) - this.enable(this.infoBtn) } if (this.backBtn) { diff --git a/lib/interaction.js b/lib/interaction.js index a63c6d9..6d23405 100755 --- a/lib/interaction.js +++ b/lib/interaction.js @@ -1032,6 +1032,7 @@ export class InteractionMapper extends InteractionDelegate { * @param {object} [opts] - An options object. See the hammer documentation for more details. */ static on(types, elements, cb, opts = {}) { + opts = Object.assign({}, { }, opts) diff --git a/lib/scatter.js b/lib/scatter.js index ceb4c23..dc141bf 100644 --- a/lib/scatter.js +++ b/lib/scatter.js @@ -887,6 +887,11 @@ export class DOMScatterContainer { this.onCapture = null this.element = element if (stopEvents === 'auto') { + /* + The events have to be stopped in Safari, otherwise the whole page will be zoomed with + a pinch gesture (preventDefault in method preventPinch). In order to enable the + movement of scatter objects, the touchmove event has to be bound again. + */ if (Capabilities.isSafari) { document.addEventListener( 'touchmove', From e87c8c9e1e19c1e21cb6c5e102cc90780bb022ca Mon Sep 17 00:00:00 2001 From: Uwe Oestermeier Date: Fri, 5 Jul 2019 09:39:01 +0200 Subject: [PATCH 09/17] Renamed simulateClick to clickOnTap --- dist/iwmlib.js | 6 +++--- dist/iwmlib.pixi.js | 6 +++--- lib/scatter.js | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dist/iwmlib.js b/dist/iwmlib.js index 50a3b26..3c9f519 100644 --- a/dist/iwmlib.js +++ b/dist/iwmlib.js @@ -5149,7 +5149,7 @@ width = null, // required height = null, // required resizable = false, - simulateClick = false, + clickOnTap = false, verbose = true, onResize = null, touchAction = 'none', @@ -5200,7 +5200,7 @@ this.height = height; this.throwVisibility = Math.min(width, height, throwVisibility); this.container = container; - this.simulateClick = simulateClick; + this.clickOnTap = clickOnTap; this.scale = startScale; this.rotationDegrees = this.startRotationDegrees; this.transformOrigin = transformOrigin; @@ -5406,7 +5406,7 @@ } onTap(event, interaction, point) { - if (this.simulateClick) { + if (this.clickOnTap) { let p = Points.fromPageToNode(this.element, point); let element = document.elementFromPoint(p.x, p.y); if (element != null) { diff --git a/dist/iwmlib.pixi.js b/dist/iwmlib.pixi.js index 16d53ae..8216093 100644 --- a/dist/iwmlib.pixi.js +++ b/dist/iwmlib.pixi.js @@ -6920,7 +6920,7 @@ width = null, // required height = null, // required resizable = false, - simulateClick = false, + clickOnTap = false, verbose = true, onResize = null, touchAction = 'none', @@ -6971,7 +6971,7 @@ this.height = height; this.throwVisibility = Math.min(width, height, throwVisibility); this.container = container; - this.simulateClick = simulateClick; + this.clickOnTap = clickOnTap; this.scale = startScale; this.rotationDegrees = this.startRotationDegrees; this.transformOrigin = transformOrigin; @@ -7177,7 +7177,7 @@ } onTap(event, interaction, point) { - if (this.simulateClick) { + if (this.clickOnTap) { let p = Points.fromPageToNode(this.element, point); let element = document.elementFromPoint(p.x, p.y); if (element != null) { diff --git a/lib/scatter.js b/lib/scatter.js index 02a65ac..d5d1c25 100644 --- a/lib/scatter.js +++ b/lib/scatter.js @@ -1037,7 +1037,7 @@ export class DOMScatter extends AbstractScatter { width = null, // required height = null, // required resizable = false, - simulateClick = false, + clickOnTap = false, verbose = true, onResize = null, touchAction = 'none', @@ -1088,7 +1088,7 @@ export class DOMScatter extends AbstractScatter { this.height = height this.throwVisibility = Math.min(width, height, throwVisibility) this.container = container - this.simulateClick = simulateClick + this.clickOnTap = clickOnTap this.scale = startScale this.rotationDegrees = this.startRotationDegrees this.transformOrigin = transformOrigin @@ -1294,7 +1294,7 @@ export class DOMScatter extends AbstractScatter { } onTap(event, interaction, point) { - if (this.simulateClick) { + if (this.clickOnTap) { let p = Points.fromPageToNode(this.element, point) let element = document.elementFromPoint(p.x, p.y) if (element != null) { From 20ac5c387ef50977d0670879e71ef4eaebdca1b9 Mon Sep 17 00:00:00 2001 From: Uwe Oestermeier Date: Fri, 5 Jul 2019 13:43:39 +0200 Subject: [PATCH 10/17] Working on tap behavior of scatter and flippables. --- dist/iwmlib.js | 248 +++++++++++++++++++++++--------------------- dist/iwmlib.pixi.js | 161 +++++++++++++++++++++------- lib/flippable.html | 7 +- lib/flippable.js | 56 +++++----- lib/scatter.html | 41 +++++++- lib/scatter.js | 99 ++++++++++++++++-- lib/utils.js | 7 ++ 7 files changed, 416 insertions(+), 203 deletions(-) diff --git a/dist/iwmlib.js b/dist/iwmlib.js index 42752a8..286c758 100644 --- a/dist/iwmlib.js +++ b/dist/iwmlib.js @@ -987,6 +987,13 @@ return Math.sqrt(dx * dx + dy * dy) } + // Distance == 0.0 indicates an inside relation. + static distanceToRect(p, r) { + var cx = Math.max(Math.min(p.x, r.x + r.width), r.x); + var cy = Math.max(Math.min(p.y, r.y + r.height), r.y); + return Math.sqrt((p.x - cx) * (p.x - cx) + (p.y - cy) * (p.y - cy)) + } + static fromPageToNode(element, p) { // if (window.webkitConvertPointFromPageToNode) { // return window.webkitConvertPointFromPageToNode(element, @@ -3815,38 +3822,6 @@ onMove = null } = {} ) { -<<<<<<< HEAD - this.onCapture = null; - this.element = element; - if (stopEvents === 'auto') { - /* - The events have to be stopped in Safari, otherwise the whole page will be zoomed with - a pinch gesture (preventDefault in method preventPinch). In order to enable the - movement of scatter objects, the touchmove event has to be bound again. - */ - if (Capabilities.isSafari) { - document.addEventListener( - 'touchmove', - event => this.preventPinch(event), - false - ); - stopEvents = false; - } else { - stopEvents = true; - } - } - this.stopEvents = stopEvents; - this.claimEvents = claimEvents; - if (touchAction !== null) { - Elements$1.setStyle(element, { touchAction }); - } - this.scatter = new Map(); - this.delegate = new InteractionMapper$1(element, this, { - useCapture, - mouseWheelElement: window - }); -======= ->>>>>>> a3f7eb0b3cc9f48cfee3ce6c45887bf25617d1bc let notchPosition = (switchPos && point.y < 50) ? "topCenter" : "bottomCenter"; @@ -4753,52 +4728,6 @@ } } -<<<<<<< HEAD - class DOMFlippable { - constructor(element, scatter, flip) { - // Set log to console.log or a custom log function - // define data structures to store our touchpoints in - - this.element = element; - this.flip = flip; - this.card = element.querySelector('.flipCard'); - this.front = element.querySelector('.front'); - this.back = element.querySelector('.back'); - this.flipped = false; - this.scatter = scatter; - this.onFrontFlipped = flip.onFrontFlipped; - this.onBackFlipped = flip.onBackFlipped; - this.onClose = flip.onClose; - this.onRemoved = flip.onRemoved; - this.onUpdate = flip.onUpdate; - - this.flipDuration = flip.flipDuration; - this.fadeDuration = flip.fadeDuration; - scatter.addTransformEventCallback(this.scatterTransformed.bind(this)); - console.log('lib.DOMFlippable', 5000); - TweenLite.set(this.element, { perspective: 5000 }); - TweenLite.set(this.card, { transformStyle: 'preserve-3d' }); - TweenLite.set(this.back, { rotationY: -180 }); - TweenLite.set([this.back, this.front], { - backfaceVisibility: 'hidden', - perspective: 5000 - }); - TweenLite.set(this.front, { visibility: 'visible' }); - this.infoBtn = element.querySelector('.infoBtn'); - this.backBtn = element.querySelector('.backBtn'); - this.closeBtn = element.querySelector('.closeBtn'); - /* Buttons are not guaranteed to exist. */ - if (this.infoBtn) { - InteractionMapper$1.on('tap', this.infoBtn, event => this.flip.start()); - this.enable(this.infoBtn); - } - if (this.backBtn) { - InteractionMapper$1.on('tap', this.backBtn, event => this.start()); - } - if (this.closeBtn) { - InteractionMapper$1.on('tap', this.closeBtn, event => this.close()); - this.enable(this.closeBtn); -======= /** * For a given zoom, a new scale is calculated, taking * min and max scale into account. @@ -4814,7 +4743,6 @@ if (scale < minScale) { scale = minScale; zoom = scale / this.scale; ->>>>>>> a3f7eb0b3cc9f48cfee3ce6c45887bf25617d1bc } if (scale > maxScale) { scale = maxScale; @@ -5065,6 +4993,11 @@ this.onCapture = null; this.element = element; if (stopEvents === 'auto') { + /* + The events have to be stopped in Safari, otherwise the whole page will be zoomed with + a pinch gesture (preventDefault in method preventPinch). In order to enable the + movement of scatter objects, the touchmove event has to be bound again. + */ if (Capabilities.isSafari) { document.addEventListener( 'touchmove', @@ -5118,7 +5051,7 @@ context.stroke(); } requestAnimationFrame(dt => { - this.showTouches(dt); + this.showTouches(dt, canvas); }); } @@ -5230,6 +5163,7 @@ height = null, // required resizable = false, clickOnTap = false, + allowClickDistance = 44, verbose = true, onResize = null, touchAction = 'none', @@ -5293,7 +5227,8 @@ rotation: this.startRotationDegrees, transformOrigin: transformOrigin }; - + this.tapNodes = new Map(); + this.allowClickDistance = allowClickDistance; // For tweenlite we need initial values in _gsTransform TweenLite.set(element, this.initialValues); @@ -5486,16 +5421,99 @@ } onTap(event, interaction, point) { + if (this.clickOnTap) { - let p = Points.fromPageToNode(this.element, point); - let element = document.elementFromPoint(p.x, p.y); - if (element != null) { - console.log('tap simulates click'); - element.click(); + let directNode = document.elementFromPoint(point.x, point.y); + let nearestNode = this.nearestClickable(event); + + console.log("onTap", directNode, nearestNode.tagName); + if (directNode != null && this.isClickable(directNode)) { + directNode.click(); + } + else { + if (nearestNode.tagName == 'svg' && this.isClickable(nearestNode)) { + let handler = this.tapNodes.get(nearestNode); + console.log("Clicking beneath SVG: to be done", handler); + Events.stop(event); + //nearestNode.click() + } } } } + /** + * Adds a click or tap behavior to the node. Uses + * either the scatter clickOnTap version which requires click handlers + * or uses the hammer.js driven tap handler. + * + * @param {*} node + * @param {*} handler + * @memberof DOMScatter + */ + + addTapListener(node, handler) { + if (this.clickOnTap) { + node.addEventListener('click', handler); + this.tapNodes.set(node, handler); + } + else { + InteractionMapper$1.on('tap', node, handler); + } + } + + isClickable(node) { + if (node.tagName == 'A') + return true + if (this.tapNodes.has(node)) + return true + return false + } + + /** + * Returns an array of all clickable nodes. + * Unfortunately we cannot search for all nodes with an attached 'click' event listener + * See https://stackoverflow.com/questions/11455515/how-to-check-whether-dynamically-attached-event-listener-exists-or-not + * Therefore we can only detect the following standard cases: + * I. All clickable objects like clickables + * II. Objects that have been attached a click handler by the scatter itself via + */ + clickableNodes() { + let result = []; + for (let node of this.element.querySelectorAll("*")) { + if (this.isClickable(node)) + result.push(node); + } + return result + } + + nearestClickable(event) { + let element = this.element; + let clickables = this.clickableNodes(); + let globalClick = (event.center) ? event.center : { x: event.x, y: event.y }; + let localClick = Points.fromPageToNode(element, globalClick); + + let clickRects = clickables.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 = []; + clickRects.forEach(rect => { + let distance = Points.distanceToRect(localClick, rect); + distances.push(parseInt(distance)); + }); + + let closestClickIndex = distances.indexOf(Math.min(...distances)); + let closestClickable = clickables[closestClickIndex]; + if (distances[closestClickIndex] < this.allowClickDistance) { + console.log("found closest clickables", closestClickable); + return closestClickable + } + return null + } + isDescendant(parent, child) { let node = child.parentNode; while (node != null) { @@ -5647,12 +5665,12 @@ this.maxHeight = maxHeight != null ? maxHeight : window.innerHeight; this.addedNode = null; console.log({ - + width, height, maxWidth, maxHeight, - + }); } @@ -5815,6 +5833,7 @@ translatable = true, scalable = true, rotatable = true, + clickOnTap = false, onFront = null, onBack = null, onClose = null, @@ -5834,6 +5853,7 @@ this.translatable = translatable; this.scalable = scalable; this.rotatable = rotatable; + this.clickOnTap = clickOnTap; this.onFrontFlipped = onFront; this.onBackFlipped = onBack; this.onClose = onClose; @@ -5888,7 +5908,8 @@ translatable: this.translatable, scalable: this.scalable, rotatable: this.rotatable, - overdoScaling: this.overdoScaling + overdoScaling: this.overdoScaling, + clickOnTap: this.clickOnTap } ); @@ -5897,7 +5918,6 @@ } if (this.closeOnMinScale) { - const removeOnMinScale = function () { if (scatter.scale <= scatter.minScale) { this.flippable.close(); @@ -5912,11 +5932,7 @@ scatter.onTransform.splice(callbackIdx, 1); } } - }.bind(this); - - - scatter.addTransformEventCallback(removeOnMinScale); } @@ -5952,8 +5968,11 @@ } start({ targetCenter = null } = {}) { - console.log('DOMFlip.start', targetCenter); - if (this.preloadBack) this.flippable.start({ duration: this.flipDuration, targetCenter }); + console.log("DOMFlip.start", targetCenter); + if (this.preloadBack) { + + this.flippable.start({ duration: this.flipDuration, targetCenter }); + } else { let back = this.cardWrapper.querySelector('.back'); let flippable = this.flippable; @@ -6005,7 +6024,7 @@ this.flipDuration = flip.flipDuration; this.fadeDuration = flip.fadeDuration; scatter.addTransformEventCallback(this.scatterTransformed.bind(this)); - console.log('lib.DOMFlippable', 5000); + TweenLite.set(this.element, { perspective: 5000 }); TweenLite.set(this.card, { transformStyle: 'preserve-3d' }); TweenLite.set(this.back, { rotationY: -180 }); @@ -6018,16 +6037,24 @@ this.backBtn = element.querySelector('.backBtn'); this.closeBtn = element.querySelector('.closeBtn'); /* Buttons are not guaranteed to exist. */ - if (this.infoBtn) { - InteractionMapper$1.on('tap', this.infoBtn, event => this.flip.start()); + if (this.infoBtn) { + scatter.addTapListener(this.infoBtn, event => { + console.log("within click handler", this); + this.flip.start(); + }); this.enable(this.infoBtn); } if (this.backBtn) { - InteractionMapper$1.on('tap', this.backBtn, event => this.start()); + scatter.addTapListener(this.backBtn, event => { + console.log("within click handler", this); + this.start(); + }); } if (this.closeBtn) { - InteractionMapper$1.on('tap', this.closeBtn, event => this.close()); + scatter.addTapListener(this.closeBtn, event => { + this.close(); + }); this.enable(this.closeBtn); } this.scaleButtons(); @@ -6077,18 +6104,6 @@ } scaleButtons() { - //This also works for svgs. - // if (this.infoBtn) - // this.infoBtn.style.transform = "scale(" + this.buttonScale + ")" - - // if (this.backBtn) - // this.backBtn.style.transform = "scale(" + this.buttonScale + ")" - - // if (this.closeBtn) - // this.closeBtn.style.transform = "scale(" + this.buttonScale + ")" - - console.log(this.buttonScale); - //// This did not work with svgs! TweenLite.set([this.infoBtn, this.backBtn, this.closeBtn], { scale: this.buttonScale }); @@ -6101,6 +6116,7 @@ clickInfo() { this.bringToFront(); + console.log("clickInfo"); this.infoBtn.click(); } @@ -6142,8 +6158,6 @@ } } - - enable(button) { this.show(button, this.fadeDuration); if (button) { @@ -6153,9 +6167,6 @@ disable(button) { this.hide(button, this.fadeDuration); - if (button) { - TweenLite.set(button, { pointerEvents: 'none' }); - } } start({ targetCenter = null } = {}) { @@ -6199,7 +6210,6 @@ let x = this.flipped ? xx : this.startX; let y = this.flipped ? yy : this.startY; - console.log("DOMFlippable.start", this.flipped, targetCenter, x, y, this.saved); let onUpdate = this.onUpdate !== null ? () => this.onUpdate(this) : null; console.log(this.flipDuration); TweenLite.to(this.card, this.flipDuration, { diff --git a/dist/iwmlib.pixi.js b/dist/iwmlib.pixi.js index b0fc860..ef8ed40 100644 --- a/dist/iwmlib.pixi.js +++ b/dist/iwmlib.pixi.js @@ -3263,6 +3263,13 @@ return Math.sqrt(dx * dx + dy * dy) } + // Distance == 0.0 indicates an inside relation. + static distanceToRect(p, r) { + var cx = Math.max(Math.min(p.x, r.x + r.width), r.x); + var cy = Math.max(Math.min(p.y, r.y + r.height), r.y); + return Math.sqrt((p.x - cx) * (p.x - cx) + (p.y - cy) * (p.y - cy)) + } + static fromPageToNode(element, p) { // if (window.webkitConvertPointFromPageToNode) { // return window.webkitConvertPointFromPageToNode(element, @@ -6922,6 +6929,7 @@ height = null, // required resizable = false, clickOnTap = false, + allowClickDistance = 44, verbose = true, onResize = null, touchAction = 'none', @@ -6985,7 +6993,8 @@ rotation: this.startRotationDegrees, transformOrigin: transformOrigin }; - + this.tapNodes = new Map(); + this.allowClickDistance = allowClickDistance; // For tweenlite we need initial values in _gsTransform TweenLite.set(element, this.initialValues); @@ -7178,16 +7187,99 @@ } onTap(event, interaction, point) { + if (this.clickOnTap) { - let p = Points.fromPageToNode(this.element, point); - let element = document.elementFromPoint(p.x, p.y); - if (element != null) { - console.log('tap simulates click'); - element.click(); + let directNode = document.elementFromPoint(point.x, point.y); + let nearestNode = this.nearestClickable(event); + + console.log("onTap", directNode, nearestNode.tagName); + if (directNode != null && this.isClickable(directNode)) { + directNode.click(); + } + else { + if (nearestNode.tagName == 'svg' && this.isClickable(nearestNode)) { + let handler = this.tapNodes.get(nearestNode); + console.log("Clicking beneath SVG: to be done", handler); + Events$1.stop(event); + //nearestNode.click() + } } } } + /** + * Adds a click or tap behavior to the node. Uses + * either the scatter clickOnTap version which requires click handlers + * or uses the hammer.js driven tap handler. + * + * @param {*} node + * @param {*} handler + * @memberof DOMScatter + */ + + addTapListener(node, handler) { + if (this.clickOnTap) { + node.addEventListener('click', handler); + this.tapNodes.set(node, handler); + } + else { + InteractionMapper$1.on('tap', node, handler); + } + } + + isClickable(node) { + if (node.tagName == 'A') + return true + if (this.tapNodes.has(node)) + return true + return false + } + + /** + * Returns an array of all clickable nodes. + * Unfortunately we cannot search for all nodes with an attached 'click' event listener + * See https://stackoverflow.com/questions/11455515/how-to-check-whether-dynamically-attached-event-listener-exists-or-not + * Therefore we can only detect the following standard cases: + * I. All clickable objects like clickables + * II. Objects that have been attached a click handler by the scatter itself via + */ + clickableNodes() { + let result = []; + for (let node of this.element.querySelectorAll("*")) { + if (this.isClickable(node)) + result.push(node); + } + return result + } + + nearestClickable(event) { + let element = this.element; + let clickables = this.clickableNodes(); + let globalClick = (event.center) ? event.center : { x: event.x, y: event.y }; + let localClick = Points.fromPageToNode(element, globalClick); + + let clickRects = clickables.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 = []; + clickRects.forEach(rect => { + let distance = Points.distanceToRect(localClick, rect); + distances.push(parseInt(distance)); + }); + + let closestClickIndex = distances.indexOf(Math.min(...distances)); + let closestClickable = clickables[closestClickIndex]; + if (distances[closestClickIndex] < this.allowClickDistance) { + console.log("found closest clickables", closestClickable); + return closestClickable + } + return null + } + isDescendant(parent, child) { let node = child.parentNode; while (node != null) { @@ -7339,12 +7431,12 @@ this.maxHeight = maxHeight != null ? maxHeight : window.innerHeight; this.addedNode = null; console.log({ - + width, height, maxWidth, maxHeight, - + }); } @@ -7373,6 +7465,7 @@ translatable = true, scalable = true, rotatable = true, + clickOnTap = false, onFront = null, onBack = null, onClose = null, @@ -7392,6 +7485,7 @@ this.translatable = translatable; this.scalable = scalable; this.rotatable = rotatable; + this.clickOnTap = clickOnTap; this.onFrontFlipped = onFront; this.onBackFlipped = onBack; this.onClose = onClose; @@ -7446,7 +7540,8 @@ translatable: this.translatable, scalable: this.scalable, rotatable: this.rotatable, - overdoScaling: this.overdoScaling + overdoScaling: this.overdoScaling, + clickOnTap: this.clickOnTap } ); @@ -7455,7 +7550,6 @@ } if (this.closeOnMinScale) { - const removeOnMinScale = function () { if (scatter.scale <= scatter.minScale) { this.flippable.close(); @@ -7470,11 +7564,7 @@ scatter.onTransform.splice(callbackIdx, 1); } } - }.bind(this); - - - scatter.addTransformEventCallback(removeOnMinScale); } @@ -7510,8 +7600,11 @@ } start({ targetCenter = null } = {}) { - console.log('DOMFlip.start', targetCenter); - if (this.preloadBack) this.flippable.start({ duration: this.flipDuration, targetCenter }); + console.log("DOMFlip.start", targetCenter); + if (this.preloadBack) { + + this.flippable.start({ duration: this.flipDuration, targetCenter }); + } else { let back = this.cardWrapper.querySelector('.back'); let flippable = this.flippable; @@ -7563,7 +7656,7 @@ this.flipDuration = flip.flipDuration; this.fadeDuration = flip.fadeDuration; scatter.addTransformEventCallback(this.scatterTransformed.bind(this)); - console.log('lib.DOMFlippable', 5000); + TweenLite.set(this.element, { perspective: 5000 }); TweenLite.set(this.card, { transformStyle: 'preserve-3d' }); TweenLite.set(this.back, { rotationY: -180 }); @@ -7576,15 +7669,24 @@ this.backBtn = element.querySelector('.backBtn'); this.closeBtn = element.querySelector('.closeBtn'); /* Buttons are not guaranteed to exist. */ + if (this.infoBtn) { - InteractionMapper$1.on('tap', this.infoBtn, event => this.flip.start()); + scatter.addTapListener(this.infoBtn, event => { + console.log("within click handler", this); + this.flip.start(); + }); this.enable(this.infoBtn); } if (this.backBtn) { - InteractionMapper$1.on('tap', this.backBtn, event => this.start()); + scatter.addTapListener(this.backBtn, event => { + console.log("within click handler", this); + this.start(); + }); } if (this.closeBtn) { - InteractionMapper$1.on('tap', this.closeBtn, event => this.close()); + scatter.addTapListener(this.closeBtn, event => { + this.close(); + }); this.enable(this.closeBtn); } this.scaleButtons(); @@ -7634,18 +7736,6 @@ } scaleButtons() { - //This also works for svgs. - // if (this.infoBtn) - // this.infoBtn.style.transform = "scale(" + this.buttonScale + ")" - - // if (this.backBtn) - // this.backBtn.style.transform = "scale(" + this.buttonScale + ")" - - // if (this.closeBtn) - // this.closeBtn.style.transform = "scale(" + this.buttonScale + ")" - - console.log(this.buttonScale); - //// This did not work with svgs! TweenLite.set([this.infoBtn, this.backBtn, this.closeBtn], { scale: this.buttonScale }); @@ -7658,6 +7748,7 @@ clickInfo() { this.bringToFront(); + console.log("clickInfo"); this.infoBtn.click(); } @@ -7699,8 +7790,6 @@ } } - - enable(button) { this.show(button, this.fadeDuration); if (button) { @@ -7710,9 +7799,6 @@ disable(button) { this.hide(button, this.fadeDuration); - if (button) { - TweenLite.set(button, { pointerEvents: 'none' }); - } } start({ targetCenter = null } = {}) { @@ -7756,7 +7842,6 @@ let x = this.flipped ? xx : this.startX; let y = this.flipped ? yy : this.startY; - console.log("DOMFlippable.start", this.flipped, targetCenter, x, y, this.saved); let onUpdate = this.onUpdate !== null ? () => this.onUpdate(this) : null; console.log(this.flipDuration); TweenLite.to(this.card, this.flipDuration, { diff --git a/lib/flippable.html b/lib/flippable.html index ed7d0b2..83cb984 100644 --- a/lib/flippable.html +++ b/lib/flippable.html @@ -72,7 +72,7 @@ if (Capabilities.supportsTemplate()) { flipTemplate, new ImageLoader('./examples/king.jpeg'), new ImageLoader('./examples/women.jpeg'), - { onUpdate: e => console.log(e)}) + { clickOnTap: true}) flip.load().then((flip) => { flip.centerAt({ x: 150, y: 120}) }) @@ -80,10 +80,5 @@ if (Capabilities.supportsTemplate()) { else { alert("Templates not supported, use Edge, Chrome, Safari or Firefox.") } - -setTimeout(function() { - const infoBtn = document.querySelector('.infoBtn') - InteractionMapper.on('tap', infoBtn, event => console.log('go')) -}, 2000) diff --git a/lib/flippable.js b/lib/flippable.js index 836c40c..826aee9 100644 --- a/lib/flippable.js +++ b/lib/flippable.js @@ -31,12 +31,12 @@ export class CardLoader { this.maxHeight = maxHeight != null ? maxHeight : window.innerHeight this.addedNode = null console.log({ - + width, height, maxWidth, maxHeight, - + }) } @@ -199,6 +199,7 @@ export class DOMFlip { translatable = true, scalable = true, rotatable = true, + clickOnTap = false, onFront = null, onBack = null, onClose = null, @@ -218,6 +219,7 @@ export class DOMFlip { this.translatable = translatable this.scalable = scalable this.rotatable = rotatable + this.clickOnTap = clickOnTap this.onFrontFlipped = onFront this.onBackFlipped = onBack this.onClose = onClose @@ -272,7 +274,8 @@ export class DOMFlip { translatable: this.translatable, scalable: this.scalable, rotatable: this.rotatable, - overdoScaling: this.overdoScaling + overdoScaling: this.overdoScaling, + clickOnTap: this.clickOnTap } ) @@ -281,7 +284,6 @@ export class DOMFlip { } if (this.closeOnMinScale) { - const removeOnMinScale = function () { if (scatter.scale <= scatter.minScale) { this.flippable.close() @@ -296,11 +298,7 @@ export class DOMFlip { scatter.onTransform.splice(callbackIdx, 1) } } - }.bind(this) - - - scatter.addTransformEventCallback(removeOnMinScale) } @@ -336,8 +334,11 @@ export class DOMFlip { } start({ targetCenter = null } = {}) { - console.log('DOMFlip.start', targetCenter) - if (this.preloadBack) this.flippable.start({ duration: this.flipDuration, targetCenter }) + console.log("DOMFlip.start", targetCenter) + if (this.preloadBack) { + + this.flippable.start({ duration: this.flipDuration, targetCenter }) + } else { let back = this.cardWrapper.querySelector('.back') let flippable = this.flippable @@ -389,7 +390,7 @@ export class DOMFlippable { this.flipDuration = flip.flipDuration this.fadeDuration = flip.fadeDuration scatter.addTransformEventCallback(this.scatterTransformed.bind(this)) - console.log('lib.DOMFlippable', 5000) + TweenLite.set(this.element, { perspective: 5000 }) TweenLite.set(this.card, { transformStyle: 'preserve-3d' }) TweenLite.set(this.back, { rotationY: -180 }) @@ -402,15 +403,24 @@ export class DOMFlippable { this.backBtn = element.querySelector('.backBtn') this.closeBtn = element.querySelector('.closeBtn') /* Buttons are not guaranteed to exist. */ + if (this.infoBtn) { - InteractionMapper.on('tap', this.infoBtn, event => this.flip.start()) + scatter.addTapListener(this.infoBtn, event => { + console.log("within click handler", this) + this.flip.start() + }) this.enable(this.infoBtn) } if (this.backBtn) { - InteractionMapper.on('tap', this.backBtn, event => this.start()) + scatter.addTapListener(this.backBtn, event => { + console.log("within click handler", this) + this.start() + }) } if (this.closeBtn) { - InteractionMapper.on('tap', this.closeBtn, event => this.close()) + scatter.addTapListener(this.closeBtn, event => { + this.close() + }) this.enable(this.closeBtn) } this.scaleButtons() @@ -460,18 +470,6 @@ export class DOMFlippable { } scaleButtons() { - //This also works for svgs. - // if (this.infoBtn) - // this.infoBtn.style.transform = "scale(" + this.buttonScale + ")" - - // if (this.backBtn) - // this.backBtn.style.transform = "scale(" + this.buttonScale + ")" - - // if (this.closeBtn) - // this.closeBtn.style.transform = "scale(" + this.buttonScale + ")" - - console.log(this.buttonScale) - //// This did not work with svgs! TweenLite.set([this.infoBtn, this.backBtn, this.closeBtn], { scale: this.buttonScale }) @@ -484,6 +482,7 @@ export class DOMFlippable { clickInfo() { this.bringToFront() + console.log("clickInfo") this.infoBtn.click() } @@ -525,8 +524,6 @@ export class DOMFlippable { } } - - enable(button) { this.show(button, this.fadeDuration) if (button) { @@ -537,7 +534,7 @@ export class DOMFlippable { disable(button) { this.hide(button, this.fadeDuration) if (button) { - TweenLite.set(button, { pointerEvents: 'none' }) + // TweenLite.set(button, { pointerEvents: 'none' }) } } @@ -582,7 +579,6 @@ export class DOMFlippable { let x = this.flipped ? xx : this.startX let y = this.flipped ? yy : this.startY - console.log("DOMFlippable.start", this.flipped, targetCenter, x, y, this.saved) let onUpdate = this.onUpdate !== null ? () => this.onUpdate(this) : null console.log(this.flipDuration) TweenLite.to(this.card, this.flipDuration, { diff --git a/lib/scatter.html b/lib/scatter.html index fe2c595..4a3ad99 100644 --- a/lib/scatter.html +++ b/lib/scatter.html @@ -15,10 +15,10 @@ context.clearRect(0, 0, debugCanvas.width, debugCanvas.height) let stage = scatterContainer.polygon - stage.draw(context, { stroke: '#FF0000'}) + stage.draw(context, { stroke: '#0000FF'}) for(let scatter of scatterContainer.scatter.values()) { let polygon = scatter.polygon - polygon.draw(context, { stroke: '#FF0000'}) + polygon.draw(context, { stroke: '#0000FF'}) } } @@ -48,7 +48,7 @@ we describe the more basic DOM scatter. - + Canvas not supported. @@ -81,4 +81,39 @@ app.run() animatePolygons() +

+ Interactive Content +

+

+Scatter objects may contain interactive HTML structures. There is one major flag that allows +to simulate click events by using taps. If the scatter detects a tap it looks for clickable +elements under or nearby the event position and calls the click handler. Thus gestures +can be disambiguated as moves, zooms. or taps. +

+ +
+ +
+ + A Link +
A Div with click handler
+
+
+ + + diff --git a/lib/scatter.js b/lib/scatter.js index 02824e9..b947225 100644 --- a/lib/scatter.js +++ b/lib/scatter.js @@ -931,7 +931,7 @@ export class DOMScatterContainer { context.stroke() } requestAnimationFrame(dt => { - this.showTouches(dt) + this.showTouches(dt, canvas) }) } @@ -1043,6 +1043,7 @@ export class DOMScatter extends AbstractScatter { height = null, // required resizable = false, clickOnTap = false, + allowClickDistance = 44, verbose = true, onResize = null, touchAction = 'none', @@ -1106,7 +1107,8 @@ export class DOMScatter extends AbstractScatter { rotation: this.startRotationDegrees, transformOrigin: transformOrigin } - + this.tapNodes = new Map() + this.allowClickDistance = allowClickDistance // For tweenlite we need initial values in _gsTransform TweenLite.set(element, this.initialValues) @@ -1299,16 +1301,99 @@ export class DOMScatter extends AbstractScatter { } onTap(event, interaction, point) { + if (this.clickOnTap) { - let p = Points.fromPageToNode(this.element, point) - let element = document.elementFromPoint(p.x, p.y) - if (element != null) { - console.log('tap simulates click') - element.click() + let directNode = document.elementFromPoint(point.x, point.y) + let nearestNode = this.nearestClickable(event) + + console.log("onTap", directNode, nearestNode.tagName) + if (directNode != null && this.isClickable(directNode)) { + directNode.click() + } + else { + if (nearestNode.tagName == 'svg' && this.isClickable(nearestNode)) { + let handler = this.tapNodes.get(nearestNode) + console.log("Clicking beneath SVG: to be done", handler) + Events.stop(event) + //nearestNode.click() + } } } } + /** + * Adds a click or tap behavior to the node. Uses + * either the scatter clickOnTap version which requires click handlers + * or uses the hammer.js driven tap handler. + * + * @param {*} node + * @param {*} handler + * @memberof DOMScatter + */ + + addTapListener(node, handler) { + if (this.clickOnTap) { + node.addEventListener('click', handler) + this.tapNodes.set(node, handler) + } + else { + InteractionMapper.on('tap', node, handler) + } + } + + isClickable(node) { + if (node.tagName == 'A') + return true + if (this.tapNodes.has(node)) + return true + return false + } + + /** + * Returns an array of all clickable nodes. + * Unfortunately we cannot search for all nodes with an attached 'click' event listener + * See https://stackoverflow.com/questions/11455515/how-to-check-whether-dynamically-attached-event-listener-exists-or-not + * Therefore we can only detect the following standard cases: + * I. All clickable objects like clickables + * II. Objects that have been attached a click handler by the scatter itself via + */ + clickableNodes() { + let result = [] + for (let node of this.element.querySelectorAll("*")) { + if (this.isClickable(node)) + result.push(node) + } + return result + } + + nearestClickable(event) { + let element = this.element + let clickables = this.clickableNodes() + let globalClick = (event.center) ? event.center : { x: event.x, y: event.y } + let localClick = Points.fromPageToNode(element, globalClick) + + let clickRects = clickables.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 = [] + clickRects.forEach(rect => { + let distance = Points.distanceToRect(localClick, rect) + distances.push(parseInt(distance)) + }) + + let closestClickIndex = distances.indexOf(Math.min(...distances)) + let closestClickable = clickables[closestClickIndex] + if (distances[closestClickIndex] < this.allowClickDistance) { + console.log("found closest clickables", closestClickable) + return closestClickable + } + return null + } + isDescendant(parent, child) { let node = child.parentNode while (node != null) { diff --git a/lib/utils.js b/lib/utils.js index 0c9ef66..5b550a1 100755 --- a/lib/utils.js +++ b/lib/utils.js @@ -402,6 +402,13 @@ export class Points { return Math.sqrt(dx * dx + dy * dy) } + // Distance == 0.0 indicates an inside relation. + static distanceToRect(p, r) { + var cx = Math.max(Math.min(p.x, r.x + r.width), r.x) + var cy = Math.max(Math.min(p.y, r.y + r.height), r.y) + return Math.sqrt((p.x - cx) * (p.x - cx) + (p.y - cy) * (p.y - cy)) + } + static fromPageToNode(element, p) { // if (window.webkitConvertPointFromPageToNode) { // return window.webkitConvertPointFromPageToNode(element, From 6794c5eedd7481658f14e6a64e5edbb6e1a2f16d Mon Sep 17 00:00:00 2001 From: Sebastian Kupke Date: Fri, 5 Jul 2019 13:57:52 +0200 Subject: [PATCH 11/17] Fixed conflict. --- dist/iwmlib.js | 85 +++------------------------------------------ dist/iwmlib.pixi.js | 8 ++--- 2 files changed, 7 insertions(+), 86 deletions(-) diff --git a/dist/iwmlib.js b/dist/iwmlib.js index f323825..ae99164 100644 --- a/dist/iwmlib.js +++ b/dist/iwmlib.js @@ -3815,38 +3815,6 @@ onMove = null } = {} ) { -<<<<<<< HEAD - this.onCapture = null; - this.element = element; - if (stopEvents === 'auto') { - /* - The events have to be stopped in Safari, otherwise the whole page will be zoomed with - a pinch gesture (preventDefault in method preventPinch). In order to enable the - movement of scatter objects, the touchmove event has to be bound again. - */ - if (Capabilities.isSafari) { - document.addEventListener( - 'touchmove', - event => this.preventPinch(event), - false - ); - stopEvents = false; - } else { - stopEvents = true; - } - } - this.stopEvents = stopEvents; - this.claimEvents = claimEvents; - if (touchAction !== null) { - Elements$1.setStyle(element, { touchAction }); - } - this.scatter = new Map(); - this.delegate = new InteractionMapper$1(element, this, { - useCapture, - mouseWheelElement: window - }); -======= ->>>>>>> a3f7eb0b3cc9f48cfee3ce6c45887bf25617d1bc let notchPosition = (switchPos && point.y < 50) ? "topCenter" : "bottomCenter"; @@ -4753,52 +4721,6 @@ } } -<<<<<<< HEAD - class DOMFlippable { - constructor(element, scatter, flip) { - // Set log to console.log or a custom log function - // define data structures to store our touchpoints in - - this.element = element; - this.flip = flip; - this.card = element.querySelector('.flipCard'); - this.front = element.querySelector('.front'); - this.back = element.querySelector('.back'); - this.flipped = false; - this.scatter = scatter; - this.onFrontFlipped = flip.onFrontFlipped; - this.onBackFlipped = flip.onBackFlipped; - this.onClose = flip.onClose; - this.onRemoved = flip.onRemoved; - this.onUpdate = flip.onUpdate; - - this.flipDuration = flip.flipDuration; - this.fadeDuration = flip.fadeDuration; - scatter.addTransformEventCallback(this.scatterTransformed.bind(this)); - console.log('lib.DOMFlippable', 5000); - TweenLite.set(this.element, { perspective: 5000 }); - TweenLite.set(this.card, { transformStyle: 'preserve-3d' }); - TweenLite.set(this.back, { rotationY: -180 }); - TweenLite.set([this.back, this.front], { - backfaceVisibility: 'hidden', - perspective: 5000 - }); - TweenLite.set(this.front, { visibility: 'visible' }); - this.infoBtn = element.querySelector('.infoBtn'); - this.backBtn = element.querySelector('.backBtn'); - this.closeBtn = element.querySelector('.closeBtn'); - /* Buttons are not guaranteed to exist. */ - if (this.infoBtn) { - InteractionMapper$1.on('tap', this.infoBtn, event => this.flip.start()); - this.enable(this.infoBtn); - } - if (this.backBtn) { - InteractionMapper$1.on('tap', this.backBtn, event => this.start()); - } - if (this.closeBtn) { - InteractionMapper$1.on('tap', this.closeBtn, event => this.close()); - this.enable(this.closeBtn); -======= /** * For a given zoom, a new scale is calculated, taking * min and max scale into account. @@ -4814,7 +4736,6 @@ if (scale < minScale) { scale = minScale; zoom = scale / this.scale; ->>>>>>> a3f7eb0b3cc9f48cfee3ce6c45887bf25617d1bc } if (scale > maxScale) { scale = maxScale; @@ -5065,6 +4986,11 @@ this.onCapture = null; this.element = element; if (stopEvents === 'auto') { + /* + The events have to be stopped in Safari, otherwise the whole page will be zoomed with + a pinch gesture (preventDefault in method preventPinch). In order to enable the + movement of scatter objects, the touchmove event has to be bound again. + */ if (Capabilities.isSafari) { document.addEventListener( 'touchmove', @@ -6020,7 +5946,6 @@ /* Buttons are not guaranteed to exist. */ if (this.infoBtn) { InteractionMapper$1.on('tap', this.infoBtn, event => this.flip.start()); - this.enable(this.infoBtn); } if (this.backBtn) { diff --git a/dist/iwmlib.pixi.js b/dist/iwmlib.pixi.js index 1ebca3c..be3c34c 100644 --- a/dist/iwmlib.pixi.js +++ b/dist/iwmlib.pixi.js @@ -6046,10 +6046,6 @@ window.Capabilities = Capabilities; window.CapabilitiesTests = CapabilitiesTests; - /** Basic class for poppable elements that need to be closed as soon as one poppable is - * shown. - */ - /* eslint-disable no-unused-vars */ /** * A base class for scatter specific events. @@ -14748,7 +14744,7 @@ * @extends Popup * @see {@link https://www.iwm-tuebingen.de/iwmbrowser/lib/pixi/popupmenu.html|DocTest} */ - class PopupMenu$1 extends Popup { + class PopupMenu extends Popup { /** * Creates an instance of a PopupMenu. @@ -15397,7 +15393,7 @@ window.Stylus = Stylus; window.Switch = Switch; window.Popup = Popup; - window.PopupMenu = PopupMenu$1; + window.PopupMenu = PopupMenu; window.Modal = Modal; window.Volatile = Volatile; window.Message = Message; From 8b4c6c201440d331b633ed9b032e4cc06ab56bc9 Mon Sep 17 00:00:00 2001 From: Uwe Oestermeier Date: Fri, 5 Jul 2019 14:11:43 +0200 Subject: [PATCH 12/17] Fixed doctest problems. --- dist/iwmlib.js | 5 +++-- dist/iwmlib.pixi.js | 8 ++++++-- lib/scatter.html | 22 +++++++++++----------- lib/scatter.js | 5 +++-- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/dist/iwmlib.js b/dist/iwmlib.js index 286c758..5a17a14 100644 --- a/dist/iwmlib.js +++ b/dist/iwmlib.js @@ -4985,10 +4985,11 @@ * @param {String} [touchAction=none] - CSS to set touch action style, needed to prevent * pointer cancel events. Use null if the * the touch action should not be set. + * @param {DOM node} debugCanvas - Shows debug infos about touches if not null */ constructor( element, - { stopEvents = 'auto', claimEvents = true, useCapture = true, touchAction = 'none' } = {} + { stopEvents = 'auto', claimEvents = true, useCapture = true, touchAction = 'none', debugCanvas = null } = {} ) { this.onCapture = null; this.element = element; @@ -5020,7 +5021,7 @@ mouseWheelElement: window }); - if (typeof debugCanvas !== 'undefined') { + if (debugCanvas !== null) { requestAnimationFrame(dt => { this.showTouches(dt, debugCanvas); }); diff --git a/dist/iwmlib.pixi.js b/dist/iwmlib.pixi.js index eb0f1d2..ef8ed40 100644 --- a/dist/iwmlib.pixi.js +++ b/dist/iwmlib.pixi.js @@ -6053,6 +6053,10 @@ window.Capabilities = Capabilities; window.CapabilitiesTests = CapabilitiesTests; + /** Basic class for poppable elements that need to be closed as soon as one poppable is + * shown. + */ + /* eslint-disable no-unused-vars */ /** * A base class for scatter specific events. @@ -14829,7 +14833,7 @@ * @extends Popup * @see {@link https://www.iwm-tuebingen.de/iwmbrowser/lib/pixi/popupmenu.html|DocTest} */ - class PopupMenu extends Popup { + class PopupMenu$1 extends Popup { /** * Creates an instance of a PopupMenu. @@ -15478,7 +15482,7 @@ window.Stylus = Stylus; window.Switch = Switch; window.Popup = Popup; - window.PopupMenu = PopupMenu; + window.PopupMenu = PopupMenu$1; window.Modal = Modal; window.Volatile = Volatile; window.Message = Message; diff --git a/lib/scatter.html b/lib/scatter.html index 4a3ad99..f3e04d7 100644 --- a/lib/scatter.html +++ b/lib/scatter.html @@ -13,9 +13,8 @@ debugCanvas.width = main.getBoundingClientRect().width let context = debugCanvas.getContext('2d') context.clearRect(0, 0, debugCanvas.width, debugCanvas.height) - let stage = scatterContainer.polygon - stage.draw(context, { stroke: '#0000FF'}) + stage.draw(context, { stroke: "#0000FF"}) for(let scatter of scatterContainer.scatter.values()) { let polygon = scatter.polygon polygon.draw(context, { stroke: '#0000FF'}) @@ -26,6 +25,7 @@ requestAnimationFrame((dt) => { drawPolygons() animatePolygons() + }) } @@ -81,6 +81,8 @@ app.run() animatePolygons() + +

Interactive Content

@@ -91,21 +93,19 @@ elements under or nearby the event position and calls the click handler. Thus ge can be disambiguated as moves, zooms. or taps.

-
- +
+
- A Link -
A Div with click handler
+ A Link +
A Div with click handler
diff --git a/lib/scatter.js b/lib/scatter.js index b947225..c0942cd 100644 --- a/lib/scatter.js +++ b/lib/scatter.js @@ -865,10 +865,11 @@ export class DOMScatterContainer { * @param {String} [touchAction=none] - CSS to set touch action style, needed to prevent * pointer cancel events. Use null if the * the touch action should not be set. + * @param {DOM node} debugCanvas - Shows debug infos about touches if not null */ constructor( element, - { stopEvents = 'auto', claimEvents = true, useCapture = true, touchAction = 'none' } = {} + { stopEvents = 'auto', claimEvents = true, useCapture = true, touchAction = 'none', debugCanvas = null } = {} ) { this.onCapture = null this.element = element @@ -900,7 +901,7 @@ export class DOMScatterContainer { mouseWheelElement: window }) - if (typeof debugCanvas !== 'undefined') { + if (debugCanvas !== null) { requestAnimationFrame(dt => { this.showTouches(dt, debugCanvas) }) From a77226e42b4a304cd2bc4d894fb62a182a9797dc Mon Sep 17 00:00:00 2001 From: uoestermeier Date: Fri, 5 Jul 2019 14:12:49 +0200 Subject: [PATCH 13/17] Working on scatter doctest --- lib/scatter.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/scatter.html b/lib/scatter.html index 4a3ad99..833d6cb 100644 --- a/lib/scatter.html +++ b/lib/scatter.html @@ -104,12 +104,13 @@ can be disambiguated as moves, zooms. or taps. - - -

Interactive Content

@@ -102,6 +99,11 @@ can be configured by allowClickDistance. The default value is 44px. A Link
A Div with click handler
+ + + + +
@@ -115,6 +117,7 @@ can be configured by allowClickDistance. The default value is 44px. height: 184, clickOnTap: true, throwVisibility: 88, + triggerSVGClicks: true, minScale: 0.5, maxScale: 1.5}) diff --git a/lib/scatter.js b/lib/scatter.js index 268dd5f..86c31e3 100644 --- a/lib/scatter.js +++ b/lib/scatter.js @@ -1044,6 +1044,7 @@ export class DOMScatter extends AbstractScatter { height = null, // required resizable = false, clickOnTap = false, + triggerSVGClicks = false, allowClickDistance = 44, verbose = true, onResize = null, @@ -1096,6 +1097,7 @@ export class DOMScatter extends AbstractScatter { this.throwVisibility = Math.min(width, height, throwVisibility) this.container = container this.clickOnTap = clickOnTap + this.triggerSVGClicks = triggerSVGClicks this.scale = startScale this.rotationDegrees = this.startRotationDegrees this.transformOrigin = transformOrigin @@ -1152,9 +1154,11 @@ export class DOMScatter extends AbstractScatter { Therefore we make an exception and let the original click event through. */ if (event.target.ownerSVGElement) { - return + if (this.triggerSVGClicks) { + return + } } - if (event.isTrusted) { + else if (event.isTrusted) { Events.stop(event) } }, true) @@ -1336,7 +1340,8 @@ export class DOMScatter extends AbstractScatter { if (nearestNode.tagName == 'svg') { let handler = this.tapNodes.get(nearestNode) console.log("Clicking beneath SVG: to be done", handler) - // nearestNode.dispatchEvent(new Event('click')) + if (this.triggerSVGClicks) + nearestNode.dispatchEvent(new Event('click')) return } console.log("nearestNode clicked")