diva.js 6.0.1 → 7.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/.clang-format +7 -0
  2. package/.github/workflows/npm-publish.yml +45 -0
  3. package/LICENSE +55 -0
  4. package/Makefile +75 -0
  5. package/README.md +15 -108
  6. package/elm.json +32 -0
  7. package/package.json +12 -59
  8. package/review/elm.json +52 -0
  9. package/review/src/ReviewConfig.elm +87 -0
  10. package/scripts/elm-esm.sh +40 -0
  11. package/scripts/minify-css.mjs +31 -0
  12. package/src/Filters.elm +1044 -0
  13. package/src/Main.elm +1217 -0
  14. package/src/Model.elm +213 -0
  15. package/src/Msg.elm +59 -0
  16. package/src/Utilities.elm +46 -0
  17. package/src/View/CollectionExplorer.elm +172 -0
  18. package/src/View/Helpers.elm +86 -0
  19. package/src/View/HtmlRenderer.elm +136 -0
  20. package/src/View/Icons.elm +159 -0
  21. package/src/View/ManifestInfoModal.elm +363 -0
  22. package/src/View/PageViewModal.elm +1046 -0
  23. package/src/View/Sidebar.elm +786 -0
  24. package/src/View/Toolbar.elm +189 -0
  25. package/src/View.elm +244 -0
  26. package/src/diva.ts +802 -0
  27. package/src/filters.ts +1843 -0
  28. package/src/styles/app.css +328 -0
  29. package/src/styles/collection.css +75 -0
  30. package/src/styles/modal.css +388 -0
  31. package/src/styles/sidebar.css +215 -0
  32. package/src/styles/theme.css +39 -0
  33. package/src/styles/toolbar.css +154 -0
  34. package/src/viewer-element.ts +1307 -0
  35. package/testing/index.html +52 -0
  36. package/testing/testing.html +231 -0
  37. package/tsconfig.json +12 -0
  38. package/AUTHORS +0 -22
  39. package/_site/diva.iml +0 -11
  40. package/build/diva.css +0 -554
  41. package/build/diva.css.map +0 -1
  42. package/build/diva.js +0 -9
  43. package/build/diva.js.map +0 -1
  44. package/build/plugins/download.js +0 -2
  45. package/build/plugins/download.js.map +0 -1
  46. package/build/plugins/manipulation.js +0 -2
  47. package/build/plugins/manipulation.js.map +0 -1
  48. package/build/plugins/metadata.js +0 -2
  49. package/build/plugins/metadata.js.map +0 -1
  50. package/diva.iml +0 -11
  51. package/index.html +0 -28
  52. package/karma.conf.js +0 -87
  53. package/source/css/_mixins.scss +0 -43
  54. package/source/css/_variables.scss +0 -50
  55. package/source/css/_viewer.scss +0 -462
  56. package/source/css/diva.scss +0 -15
  57. package/source/css/plugins/_manipulation.scss +0 -228
  58. package/source/css/plugins/_metadata.scss +0 -31
  59. package/source/img/adjust.svg +0 -11
  60. package/source/img/book-view.svg +0 -6
  61. package/source/img/close.svg +0 -6
  62. package/source/img/download.svg +0 -6
  63. package/source/img/from-fullscreen.svg +0 -8
  64. package/source/img/grid-fewer.svg +0 -6
  65. package/source/img/grid-more.svg +0 -6
  66. package/source/img/grid-view.svg +0 -6
  67. package/source/img/link.svg +0 -6
  68. package/source/img/metadata.svg +0 -9
  69. package/source/img/page-view.svg +0 -6
  70. package/source/img/to-fullscreen.svg +0 -11
  71. package/source/img/zoom-in.svg +0 -6
  72. package/source/img/zoom-out.svg +0 -7
  73. package/source/js/composite-image.js +0 -174
  74. package/source/js/diva-global.js +0 -7
  75. package/source/js/diva.js +0 -1543
  76. package/source/js/document-handler.js +0 -180
  77. package/source/js/document-layout.js +0 -286
  78. package/source/js/exceptions.js +0 -26
  79. package/source/js/gesture-events.js +0 -190
  80. package/source/js/grid-handler.js +0 -122
  81. package/source/js/iiif-source-adapter.js +0 -63
  82. package/source/js/image-cache.js +0 -113
  83. package/source/js/image-manifest.js +0 -157
  84. package/source/js/image-request-handler.js +0 -76
  85. package/source/js/interpolate-animation.js +0 -122
  86. package/source/js/page-layouts/book-layout.js +0 -161
  87. package/source/js/page-layouts/grid-layout.js +0 -97
  88. package/source/js/page-layouts/index.js +0 -38
  89. package/source/js/page-layouts/page-dimensions.js +0 -9
  90. package/source/js/page-layouts/singles-layout.js +0 -27
  91. package/source/js/page-overlay-manager.js +0 -102
  92. package/source/js/page-tools-overlay.js +0 -95
  93. package/source/js/parse-iiif-manifest.js +0 -302
  94. package/source/js/plugins/_filters.js +0 -679
  95. package/source/js/plugins/download.js +0 -83
  96. package/source/js/plugins/manipulation.js +0 -837
  97. package/source/js/plugins/metadata.js +0 -190
  98. package/source/js/renderer.js +0 -584
  99. package/source/js/settings-view.js +0 -30
  100. package/source/js/tile-coverage-map.js +0 -25
  101. package/source/js/toolbar.js +0 -572
  102. package/source/js/utils/dragscroll.js +0 -106
  103. package/source/js/utils/elt.js +0 -94
  104. package/source/js/utils/events.js +0 -190
  105. package/source/js/utils/get-scrollbar-width.js +0 -29
  106. package/source/js/utils/hash-params.js +0 -86
  107. package/source/js/utils/parse-label-value.js +0 -34
  108. package/source/js/utils/vanilla.kinetic.js +0 -527
  109. package/source/js/validation-runner.js +0 -177
  110. package/source/js/viewer-core.js +0 -1505
  111. package/source/js/viewport.js +0 -143
  112. package/test/_setup.js +0 -13
  113. package/test/composite-image_test.js +0 -94
  114. package/test/diva_test.js +0 -43
  115. package/test/hash-params_test.js +0 -221
  116. package/test/image-cache_test.js +0 -106
  117. package/test/main.js +0 -6
  118. package/test/manifests/beromunsterManifest.json +0 -15514
  119. package/test/manifests/iiifv2.json +0 -11032
  120. package/test/manifests/iiifv2pages.json +0 -30437
  121. package/test/manifests/iiifv3.json +0 -10965
  122. package/test/navigation_test.js +0 -355
  123. package/test/parse-iiif-manifest_test.js +0 -68
  124. package/test/public_test.js +0 -881
  125. package/test/settings_test.js +0 -487
  126. package/test/utils/book-layout_test.js +0 -148
  127. package/test/utils/elt_test.js +0 -102
  128. package/test/utils/events_test.js +0 -245
  129. package/test/utils/hash-params_test.js +0 -79
  130. package/test/utils/parse-label-value_test.js +0 -45
  131. package/test/z_plugins_test.js +0 -180
  132. package/webpack.config.js +0 -58
  133. package/webpack.config.test.js +0 -45
@@ -1,180 +0,0 @@
1
- import maxBy from 'lodash.maxby';
2
- import PageToolsOverlay from './page-tools-overlay';
3
-
4
-
5
- export default class DocumentHandler
6
- {
7
- constructor (viewerCore)
8
- {
9
- this._viewerCore = viewerCore;
10
- this._viewerState = viewerCore.getInternalState();
11
- this._overlays = [];
12
-
13
- if (this._viewerCore.getPageTools().length)
14
- {
15
- const numPages = viewerCore.getSettings().numPages;
16
-
17
- for (let i = 0; i < numPages; i++)
18
- {
19
- const overlay = new PageToolsOverlay(i, viewerCore);
20
- this._overlays.push(overlay);
21
- this._viewerCore.addPageOverlay(overlay);
22
-
23
- // create dummy label for width calculation
24
- // this is necessary because the _pageToolsElem is only created on mount
25
- // so there's no other way to get its width before the pages are loaded
26
- // (which we need to avoid their width temporarily being 0 while loading)
27
- let dummyLabel = document.createElement('span');
28
- dummyLabel.innerHTML = viewerCore.settings.manifest.pages[i].l;
29
- dummyLabel.classList.add('diva-page-labels');
30
- dummyLabel.setAttribute('style', 'display: inline-block;');
31
- document.body.appendChild(dummyLabel);
32
- let labelWidth = dummyLabel.clientWidth;
33
- document.body.removeChild(dummyLabel);
34
-
35
- overlay.labelWidth = labelWidth;
36
- }
37
- }
38
- }
39
-
40
- // USER EVENTS
41
- onDoubleClick (event, coords)
42
- {
43
- const settings = this._viewerCore.getSettings();
44
- const newZoomLevel = event.ctrlKey ? settings.zoomLevel - 1 : settings.zoomLevel + 1;
45
-
46
- const position = this._viewerCore.getPagePositionAtViewportOffset(coords);
47
- this._viewerCore.zoom(newZoomLevel, position);
48
- }
49
-
50
- onPinch (event, coords, startDistance, endDistance)
51
- {
52
- // FIXME: Do this check in a way which is less spaghetti code-y
53
- const viewerState = this._viewerCore.getInternalState();
54
- const settings = this._viewerCore.getSettings();
55
-
56
- let newZoomLevel = Math.log(Math.pow(2, settings.zoomLevel) * endDistance / (startDistance * Math.log(2))) / Math.log(2);
57
- newZoomLevel = Math.max(settings.minZoomLevel, newZoomLevel);
58
- newZoomLevel = Math.min(settings.maxZoomLevel, newZoomLevel);
59
-
60
- if (newZoomLevel === settings.zoomLevel)
61
- {
62
- return;
63
- }
64
-
65
- const position = this._viewerCore.getPagePositionAtViewportOffset(coords);
66
-
67
- const layout = this._viewerCore.getCurrentLayout();
68
- const centerOffset = layout.getPageToViewportCenterOffset(position.anchorPage, viewerState.viewport);
69
- const scaleRatio = 1 / Math.pow(2, settings.zoomLevel - newZoomLevel);
70
-
71
- this._viewerCore.reload({
72
- zoomLevel: newZoomLevel,
73
- goDirectlyTo: position.anchorPage,
74
- horizontalOffset: (centerOffset.x - position.offset.left) + position.offset.left * scaleRatio,
75
- verticalOffset: (centerOffset.y - position.offset.top) + position.offset.top * scaleRatio
76
- });
77
- }
78
-
79
- // VIEW EVENTS
80
- onViewWillLoad ()
81
- {
82
- this._viewerCore.publish('DocumentWillLoad', this._viewerCore.getSettings());
83
- }
84
-
85
- onViewDidLoad ()
86
- {
87
- // TODO: Should only be necessary to handle changes on view update, not
88
- // initial load
89
- this._handleZoomLevelChange();
90
-
91
- const currentPageIndex = this._viewerCore.getSettings().activePageIndex;
92
- const fileName = this._viewerCore.getPageName(currentPageIndex);
93
- this._viewerCore.publish("DocumentDidLoad", currentPageIndex, fileName);
94
- }
95
-
96
- onViewDidUpdate (renderedPages, targetPage)
97
- {
98
- const currentPage = (targetPage !== null) ?
99
- targetPage :
100
- getCentermostPage(renderedPages, this._viewerCore.getCurrentLayout(), this._viewerCore.getViewport());
101
-
102
- // calculate the visible pages from the rendered pages
103
- let temp = this._viewerState.viewport.intersectionTolerance;
104
- // without setting to 0, isPageVisible returns true for pages out of viewport by intersectionTolerance
105
- this._viewerState.viewport.intersectionTolerance = 0;
106
- let visiblePages = renderedPages.filter(index => this._viewerState.renderer.isPageVisible(index));
107
- // reset back to original value after getting true visible pages
108
- this._viewerState.viewport.intersectionTolerance = temp;
109
-
110
- // Don't change the current page if there is no page in the viewport
111
- // FIXME: Would be better to fall back to the page closest to the viewport
112
- if (currentPage !== null)
113
- {
114
- this._viewerCore.setCurrentPages(currentPage, visiblePages);
115
- }
116
-
117
- if (targetPage !== null)
118
- {
119
- this._viewerCore.publish("ViewerDidJump", targetPage);
120
- }
121
-
122
- this._handleZoomLevelChange();
123
- }
124
-
125
- _handleZoomLevelChange ()
126
- {
127
- const viewerState = this._viewerState;
128
- const zoomLevel = viewerState.options.zoomLevel;
129
-
130
- // If this is not the initial load, trigger the zoom events
131
- if (viewerState.oldZoomLevel !== zoomLevel && viewerState.oldZoomLevel >= 0)
132
- {
133
- if (viewerState.oldZoomLevel < zoomLevel)
134
- {
135
- this._viewerCore.publish("ViewerDidZoomIn", zoomLevel);
136
- }
137
- else
138
- {
139
- this._viewerCore.publish("ViewerDidZoomOut", zoomLevel);
140
- }
141
-
142
- this._viewerCore.publish("ViewerDidZoom", zoomLevel);
143
- }
144
-
145
- viewerState.oldZoomLevel = zoomLevel;
146
- }
147
-
148
- destroy ()
149
- {
150
- this._overlays.forEach((overlay) =>
151
- {
152
- this._viewerCore.removePageOverlay(overlay);
153
- }, this);
154
- }
155
- }
156
-
157
- function getCentermostPage (renderedPages, layout, viewport)
158
- {
159
- const centerY = viewport.top + (viewport.height / 2);
160
- const centerX = viewport.left + (viewport.width / 2);
161
-
162
- // Find the minimum distance from the viewport center to a page.
163
- // Compute minus the squared distance from viewport center to the page's border.
164
- // http://gamedev.stackexchange.com/questions/44483/how-do-i-calculate-distance-between-a-point-and-an-axis-aligned-rectangle
165
- const centerPage = maxBy(renderedPages, pageIndex =>
166
- {
167
- const dims = layout.getPageDimensions(pageIndex);
168
- const imageOffset = layout.getPageOffset(pageIndex, {includePadding: false});
169
-
170
- const midX = imageOffset.left + (dims.height / 2);
171
- const midY = imageOffset.top + (dims.width / 2);
172
-
173
- const dx = Math.max(Math.abs(centerX - midX) - (dims.width / 2), 0);
174
- const dy = Math.max(Math.abs(centerY - midY) - (dims.height / 2), 0);
175
-
176
- return -(dx * dx + dy * dy);
177
- });
178
-
179
- return centerPage != null ? centerPage : null;
180
- }
@@ -1,286 +0,0 @@
1
- /**
2
- * Translate page layouts, as generated by page-layouts, into an
3
- * object which computes layout information for the document as
4
- * a whole.
5
- */
6
- export default class DocumentLayout
7
- {
8
- constructor (config, zoomLevel)
9
- {
10
- const computedLayout = getComputedLayout(config, zoomLevel);
11
-
12
- this.dimensions = computedLayout.dimensions;
13
- this.pageGroups = computedLayout.pageGroups;
14
- this._pageLookup = getPageLookup(computedLayout.pageGroups);
15
- }
16
-
17
- /**
18
- * @typedef {Object} PageInfo
19
- * @property {number} index
20
- * @property {{index, dimensions, pages, region, padding}} group
21
- * @property {{height: number, width: number}} dimensions
22
- * @property {{top: number, left: number}} groupOffset
23
- */
24
-
25
- /**
26
- * @param pageIndex
27
- * @returns {PageInfo|null}
28
- */
29
- getPageInfo (pageIndex)
30
- {
31
- return this._pageLookup[pageIndex] || null;
32
- }
33
-
34
- /**
35
- * Get the dimensions of a page
36
- *
37
- * @param pageIndex
38
- * @returns {{height: number, width: number}}
39
- */
40
- getPageDimensions (pageIndex)
41
- {
42
- if (!this._pageLookup || !this._pageLookup[pageIndex])
43
- return null;
44
-
45
- const region = getPageRegionFromPageInfo(this._pageLookup[pageIndex]);
46
-
47
- return {
48
- height: region.bottom - region.top,
49
- width: region.right - region.left
50
- };
51
- }
52
-
53
- // TODO(wabain): Get rid of this; it's a subset of the page region, so
54
- // give that instead
55
- /**
56
- * Get the top-left coordinates of a page, including*** padding
57
- *
58
- * @param pageIndex
59
- * @param options
60
- * @returns {{top: number, left: number} | null}
61
- */
62
- getPageOffset (pageIndex, options)
63
- {
64
- const region = this.getPageRegion(pageIndex, options);
65
-
66
- if (!region)
67
- return null;
68
-
69
- return {
70
- top: region.top,
71
- left: region.left
72
- };
73
- }
74
-
75
- getPageRegion (pageIndex, options)
76
- {
77
- const pageInfo = this._pageLookup[pageIndex];
78
-
79
- if (!pageInfo)
80
- return null;
81
-
82
- const region = getPageRegionFromPageInfo(pageInfo);
83
- const padding = pageInfo.group.padding;
84
-
85
- if (options && options.includePadding)
86
- {
87
- return {
88
- top: region.top + padding.top,
89
- left: region.left + padding.left,
90
- bottom: region.bottom,
91
- right: region.right
92
- };
93
- }
94
-
95
- return {
96
- top: region.top,
97
- left: region.left,
98
- // need to account for plugin icons below the page, see
99
- // https://github.com/DDMAL/diva.js/issues/436
100
- bottom: region.bottom + padding.top,
101
- right: region.right
102
- };
103
- }
104
-
105
- /**
106
- * Get the distance from the top-right of the page to the center of the
107
- * specified viewport region
108
- *
109
- * @param pageIndex
110
- * @param viewport {{top: number, left: number, bottom: number, right: number}}
111
- * @returns {{x: number, y: number}}
112
- */
113
- getPageToViewportCenterOffset (pageIndex, viewport)
114
- {
115
- const scrollLeft = viewport.left;
116
- const elementWidth = viewport.right - viewport.left;
117
-
118
- const offset = this.getPageOffset(pageIndex);
119
-
120
- const x = scrollLeft - offset.left + parseInt(elementWidth / 2, 10);
121
-
122
- const scrollTop = viewport.top;
123
- const elementHeight = viewport.bottom - viewport.top;
124
-
125
- const y = scrollTop - offset.top + parseInt(elementHeight / 2, 10);
126
-
127
- return {
128
- x: x,
129
- y: y
130
- };
131
- }
132
- }
133
-
134
- function getPageRegionFromPageInfo (page)
135
- {
136
- const top = page.groupOffset.top + page.group.region.top;
137
- const bottom = top + page.dimensions.height;
138
- const left = page.groupOffset.left + page.group.region.left;
139
- const right = left + page.dimensions.width;
140
-
141
- return {
142
- top: top,
143
- bottom: bottom,
144
- left: left,
145
- right: right
146
- };
147
- }
148
-
149
- function getPageLookup (pageGroups)
150
- {
151
- const pageLookup = {};
152
-
153
- pageGroups.forEach(group => {
154
- group.pages.forEach(page => {
155
- pageLookup[page.index] = {
156
- index: page.index,
157
- group: group,
158
- dimensions: page.dimensions,
159
- groupOffset: page.groupOffset
160
- };
161
- });
162
- });
163
-
164
- return pageLookup;
165
- }
166
-
167
- function getComputedLayout (config, zoomLevel)
168
- {
169
- const scaledLayouts = zoomLevel === null ? config.pageLayouts : getScaledPageLayouts(config, zoomLevel);
170
-
171
- const documentSecondaryExtent = getExtentAlongSecondaryAxis(config, scaledLayouts);
172
-
173
- // The current position in the document along the primary axis
174
- let primaryDocPosition = config.verticallyOriented ?
175
- config.padding.document.top :
176
- config.padding.document.left;
177
-
178
- const pageGroups = [];
179
-
180
- // TODO: Use bottom, right as well
181
- const pagePadding = {
182
- top: config.padding.page.top,
183
- left: config.padding.page.left
184
- };
185
-
186
- scaledLayouts.forEach((layout, index) => {
187
- let top, left;
188
-
189
- if (config.verticallyOriented)
190
- {
191
- top = primaryDocPosition;
192
- left = (documentSecondaryExtent - layout.dimensions.width) / 2;
193
- }
194
- else
195
- {
196
- top = (documentSecondaryExtent - layout.dimensions.height) / 2;
197
- left = primaryDocPosition;
198
- }
199
-
200
- const region = {
201
- top: top,
202
- bottom: top + pagePadding.top + layout.dimensions.height,
203
- left: left,
204
- right: left + pagePadding.left + layout.dimensions.width
205
- };
206
-
207
- pageGroups.push({
208
- index: index,
209
- dimensions: layout.dimensions,
210
- pages: layout.pages,
211
- region: region,
212
- padding: pagePadding
213
- });
214
-
215
- primaryDocPosition = config.verticallyOriented ? region.bottom : region.right;
216
- });
217
-
218
- let height, width;
219
-
220
- if (config.verticallyOriented)
221
- {
222
- height = primaryDocPosition + pagePadding.top;
223
- width = documentSecondaryExtent;
224
- }
225
- else
226
- {
227
- height = documentSecondaryExtent;
228
- width = primaryDocPosition + pagePadding.left;
229
- }
230
-
231
- return {
232
- dimensions: {
233
- height: height,
234
- width: width
235
- },
236
- pageGroups: pageGroups
237
- };
238
- }
239
-
240
- function getScaledPageLayouts (config, zoomLevel)
241
- {
242
- const scaleRatio = Math.pow(2, zoomLevel - config.maxZoomLevel);
243
-
244
- return config.pageLayouts.map(group => ({
245
- dimensions: scaleDimensions(group.dimensions, scaleRatio),
246
-
247
- pages: group.pages.map(page => ({
248
- index: page.index,
249
-
250
- groupOffset: {
251
- top: Math.floor(page.groupOffset.top * scaleRatio),
252
- left: Math.floor(page.groupOffset.left * scaleRatio)
253
- },
254
-
255
- dimensions: scaleDimensions(page.dimensions, scaleRatio)
256
- }))
257
- }));
258
- }
259
-
260
- function scaleDimensions (dimensions, scaleRatio)
261
- {
262
- return {
263
- height: Math.floor(dimensions.height * scaleRatio),
264
- width: Math.floor(dimensions.width * scaleRatio)
265
- };
266
- }
267
-
268
- function getExtentAlongSecondaryAxis (config, scaledLayouts)
269
- {
270
- // Get the extent of the document along the secondary axis
271
- let secondaryDim, secondaryPadding;
272
- const docPadding = config.padding.document;
273
-
274
- if (config.verticallyOriented)
275
- {
276
- secondaryDim = 'width';
277
- secondaryPadding = docPadding.left + docPadding.right;
278
- }
279
- else
280
- {
281
- secondaryDim = 'height';
282
- secondaryPadding = docPadding.top + docPadding.bottom;
283
- }
284
-
285
- return secondaryPadding + scaledLayouts.reduce((maxDim, layout) => Math.max(layout.dimensions[secondaryDim], maxDim), 0);
286
- }
@@ -1,26 +0,0 @@
1
- export function DivaParentElementNotFoundException (message)
2
- {
3
- this.name = "DivaParentElementNotFoundException";
4
- this.message = message;
5
- this.stack = (new Error()).stack;
6
- }
7
-
8
- DivaParentElementNotFoundException.prototype = new Error();
9
-
10
- export function NotAnIIIFManifestException (message)
11
- {
12
- this.name = "NotAnIIIFManifestException";
13
- this.message = message;
14
- this.stack = (new Error()).stack;
15
- }
16
-
17
- NotAnIIIFManifestException.prototype = new Error();
18
-
19
- export function ObjectDataNotSuppliedException (message)
20
- {
21
- this.name = "ObjectDataNotSuppliedException";
22
- this.message = message;
23
- this.stack = (new Error()).stack;
24
- }
25
-
26
- ObjectDataNotSuppliedException.prototype = new Error();
@@ -1,190 +0,0 @@
1
- export default {
2
- onDoubleClick,
3
- onPinch,
4
- onDoubleTap
5
- };
6
-
7
- const DOUBLE_CLICK_TIMEOUT = 500;
8
- const DOUBLE_TAP_DISTANCE_THRESHOLD = 50;
9
- const DOUBLE_TAP_TIMEOUT = 250;
10
-
11
- function onDoubleClick(elem, callback)
12
- {
13
- elem.addEventListener('dblclick', function (event)
14
- {
15
- if (!event.ctrlKey)
16
- {
17
- callback(event, getRelativeOffset(event.currentTarget, event));
18
- }
19
- });
20
-
21
- // Handle the control key for macs (in conjunction with double-clicking)
22
- // FIXME: Does a click get handled with ctrl pressed on non-Macs?
23
- const tracker = createDoubleEventTracker(DOUBLE_CLICK_TIMEOUT);
24
-
25
- elem.addEventListener('contextmenu', function (event)
26
- {
27
- event.preventDefault();
28
-
29
- if (event.ctrlKey)
30
- {
31
- if (tracker.isTriggered())
32
- {
33
- tracker.reset();
34
- callback(event, getRelativeOffset(event.currentTarget, event));
35
- }
36
- else
37
- {
38
- tracker.trigger();
39
- }
40
- }
41
- });
42
- }
43
-
44
- function onPinch(elem, callback)
45
- {
46
- let startDistance = 0;
47
-
48
- elem.addEventListener('touchstart', function (event)
49
- {
50
- // Prevent mouse event from firing
51
- event.preventDefault();
52
-
53
- if (event.originalEvent.touches.length === 2)
54
- {
55
- startDistance = distance(
56
- event.originalEvent.touches[0].clientX,
57
- event.originalEvent.touches[0].clientY,
58
- event.originalEvent.touches[1].clientX,
59
- event.originalEvent.touches[1].clientY
60
- );
61
- }
62
- });
63
-
64
- elem.addEventListener('touchmove', function(event)
65
- {
66
- // Prevent mouse event from firing
67
- event.preventDefault();
68
-
69
- if (event.originalEvent.touches.length === 2)
70
- {
71
- const touches = event.originalEvent.touches;
72
-
73
- const moveDistance = distance(
74
- touches[0].clientX,
75
- touches[0].clientY,
76
- touches[1].clientX,
77
- touches[1].clientY
78
- );
79
-
80
- const zoomDelta = moveDistance - startDistance;
81
-
82
- if (Math.abs(zoomDelta) > 0)
83
- {
84
- const touchCenter = {
85
- pageX: (touches[0].clientX + touches[1].clientX) / 2,
86
- pageY: (touches[0].clientY + touches[1].clientY) / 2
87
- };
88
-
89
- callback(event, getRelativeOffset(event.currentTarget, touchCenter), startDistance, moveDistance);
90
- }
91
- }
92
- });
93
- }
94
-
95
- function onDoubleTap(elem, callback)
96
- {
97
- const tracker = createDoubleEventTracker(DOUBLE_TAP_TIMEOUT);
98
- let firstTap = null;
99
-
100
- elem.addEventListener('touchend', (event) =>
101
- {
102
- // Prevent mouse event from firing
103
- event.preventDefault();
104
-
105
- if (tracker.isTriggered())
106
- {
107
- tracker.reset();
108
-
109
- // Doubletap has occurred
110
- const secondTap = {
111
- pageX: event.originalEvent.changedTouches[0].clientX,
112
- pageY: event.originalEvent.changedTouches[0].clientY
113
- };
114
-
115
- // If first tap is close to second tap (prevents interference with scale event)
116
- const tapDistance = distance(firstTap.pageX, firstTap.pageY, secondTap.pageX, secondTap.pageY);
117
-
118
- // TODO: Could give something higher-level than secondTap to callback
119
- if (tapDistance < DOUBLE_TAP_DISTANCE_THRESHOLD)
120
- callback(event, getRelativeOffset(event.currentTarget, secondTap));
121
-
122
- firstTap = null;
123
- }
124
- else
125
- {
126
- firstTap = {
127
- pageX: event.originalEvent.changedTouches[0].clientX,
128
- pageY: event.originalEvent.changedTouches[0].clientY
129
- };
130
-
131
- tracker.trigger();
132
- }
133
- });
134
- }
135
-
136
- // Pythagorean theorem to get the distance between two points (used for
137
- // calculating finger distance for double-tap and pinch-zoom)
138
- function distance(x1, y1, x2, y2)
139
- {
140
- return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
141
- }
142
-
143
- // Utility to keep track of whether an event has been triggered twice
144
- // during a a given duration
145
- function createDoubleEventTracker(timeoutDuration)
146
- {
147
- let triggered = false;
148
- let timeoutId = null;
149
-
150
- return {
151
- trigger()
152
- {
153
- triggered = true;
154
- resetTimeout();
155
- timeoutId = setTimeout(function ()
156
- {
157
- triggered = false;
158
- timeoutId = null;
159
- }, timeoutDuration);
160
- },
161
- isTriggered()
162
- {
163
- return triggered;
164
- },
165
- reset()
166
- {
167
- triggered = false;
168
- resetTimeout();
169
- }
170
- };
171
-
172
- function resetTimeout()
173
- {
174
- if (timeoutId !== null)
175
- {
176
- clearTimeout(timeoutId);
177
- timeoutId = null;
178
- }
179
- }
180
- }
181
-
182
- function getRelativeOffset(elem, pageCoords)
183
- {
184
- const bounds = elem.getBoundingClientRect();
185
-
186
- return {
187
- left: pageCoords.pageX - bounds.left,
188
- top: pageCoords.pageY - bounds.top
189
- };
190
- }