@shapediver/viewer.shared.services 1.15.5 → 2.0.0
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.
- package/dist/converter/Converter.d.ts.map +1 -1
- package/dist/converter/Converter.js +2 -2
- package/dist/converter/Converter.js.map +1 -1
- package/dist/dom-event-engine/DomEventEngine.d.ts +1 -2
- package/dist/dom-event-engine/DomEventEngine.d.ts.map +1 -1
- package/dist/dom-event-engine/DomEventEngine.js +1 -2
- package/dist/dom-event-engine/DomEventEngine.js.map +1 -1
- package/dist/event-engine/EventEngine.d.ts +3 -3
- package/dist/event-engine/EventEngine.d.ts.map +1 -1
- package/dist/event-engine/EventEngine.js +2 -2
- package/dist/event-engine/EventEngine.js.map +1 -1
- package/dist/event-engine/EventTypes.d.ts +20 -24
- package/dist/event-engine/EventTypes.d.ts.map +1 -1
- package/dist/event-engine/EventTypes.js +55 -85
- package/dist/event-engine/EventTypes.js.map +1 -1
- package/dist/http-client/HttpClient.d.ts +11 -8
- package/dist/http-client/HttpClient.d.ts.map +1 -1
- package/dist/http-client/HttpClient.js +102 -27
- package/dist/http-client/HttpClient.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/input-validator/InputValidator.d.ts +3 -3
- package/dist/input-validator/InputValidator.d.ts.map +1 -1
- package/dist/input-validator/InputValidator.js +6 -2
- package/dist/input-validator/InputValidator.js.map +1 -1
- package/dist/logger/Logger.d.ts +16 -16
- package/dist/logger/Logger.d.ts.map +1 -1
- package/dist/logger/Logger.js +77 -76
- package/dist/logger/Logger.js.map +1 -1
- package/dist/logger/ShapeDiverError.d.ts +1 -1
- package/dist/logger/ShapeDiverError.d.ts.map +1 -1
- package/dist/logger/ShapeDiverError.js +1 -1
- package/dist/logger/ShapeDiverError.js.map +1 -1
- package/dist/logger/ShapeDiverViewerErrors.d.ts +24 -24
- package/dist/logger/ShapeDiverViewerErrors.d.ts.map +1 -1
- package/dist/logger/ShapeDiverViewerErrors.js +1 -1
- package/dist/logger/ShapeDiverViewerErrors.js.map +1 -1
- package/dist/performance-evaluator/PerformanceEvaluator.js +1 -1
- package/dist/performance-evaluator/PerformanceEvaluator.js.map +1 -1
- package/dist/settings-engine/SettingsEngine.d.ts +1 -1
- package/dist/settings-engine/SettingsEngine.d.ts.map +1 -1
- package/dist/settings-engine/SettingsEngine.js +16 -25
- package/dist/settings-engine/SettingsEngine.js.map +1 -1
- package/dist/state-engine/StateEngine.d.ts +7 -16
- package/dist/state-engine/StateEngine.d.ts.map +1 -1
- package/dist/state-engine/StateEngine.js +8 -21
- package/dist/state-engine/StateEngine.js.map +1 -1
- package/dist/system-info/SystemInfo.js +1 -1
- package/dist/system-info/SystemInfo.js.map +1 -1
- package/dist/type-check/TypeChecker.js +1 -1
- package/dist/type-check/TypeChecker.js.map +1 -1
- package/dist/uuid-generator/UuidGenerator.js +5 -5
- package/dist/uuid-generator/UuidGenerator.js.map +1 -1
- package/package.json +12 -9
- package/src/converter/Converter.ts +299 -0
- package/src/dom-event-engine/DomEventEngine.ts +334 -0
- package/src/dom-event-engine/IDomEventListener.ts +14 -0
- package/src/event-engine/EventEngine.ts +116 -0
- package/src/event-engine/EventTypes.ts +66 -0
- package/src/event-engine/interfaces/ICallback.ts +5 -0
- package/src/event-engine/interfaces/IEvent.ts +1 -0
- package/src/event-engine/interfaces/IListener.ts +8 -0
- package/src/http-client/HttpClient.ts +177 -0
- package/src/http-client/HttpResponse.ts +4 -0
- package/src/index.ts +82 -0
- package/src/input-validator/InputValidator.ts +100 -0
- package/src/logger/Logger.ts +297 -0
- package/src/logger/ShapeDiverError.ts +48 -0
- package/src/logger/ShapeDiverViewerErrors.ts +115 -0
- package/src/performance-evaluator/PerformanceEvaluator.ts +102 -0
- package/src/settings-engine/SettingsEngine.ts +176 -0
- package/src/state-engine/StateEngine.ts +73 -0
- package/src/state-engine/StatePromise.ts +54 -0
- package/src/system-info/SystemInfo.ts +117 -0
- package/src/type-check/TypeChecker.ts +13 -0
- package/src/uuid-generator/UuidGenerator.ts +41 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { vec3, vec4 } from 'gl-matrix'
|
|
2
|
+
import { TinyColor } from '@ctrl/tinycolor'
|
|
3
|
+
import { container, singleton } from 'tsyringe'
|
|
4
|
+
import { HttpClient } from '../http-client/HttpClient';
|
|
5
|
+
import { HttpResponse } from '../http-client/HttpResponse';
|
|
6
|
+
|
|
7
|
+
@singleton()
|
|
8
|
+
export class Converter {
|
|
9
|
+
private readonly _httpClient: HttpClient = <HttpClient>container.resolve(HttpClient);
|
|
10
|
+
|
|
11
|
+
private tinyColorToString(color: TinyColor): string {
|
|
12
|
+
return color.toHex8String();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param color
|
|
17
|
+
* @param defColor
|
|
18
|
+
*/
|
|
19
|
+
public toHex8Color(color: any, defColorString: string = '#00fff7'): string {
|
|
20
|
+
const c = this.toColor(color, defColorString);
|
|
21
|
+
const tColor = new TinyColor(c);
|
|
22
|
+
const cH8 = tColor.toHex8String();
|
|
23
|
+
return cH8.replace('#', '0x');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public toColorArray(color: string): number[] {
|
|
27
|
+
const tColor = new TinyColor(color);
|
|
28
|
+
const rgb = tColor.toRgb()
|
|
29
|
+
return [rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public toAlpha(color: string): number {
|
|
33
|
+
const c = this.toColor(color);
|
|
34
|
+
if (c.length <= 8) return 1;
|
|
35
|
+
return parseInt(c.slice(c.length - 2, c.length), 16) / 255;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public toThreeJsColorInput(color: string): string {
|
|
39
|
+
const c = this.toColor(color);
|
|
40
|
+
return c.slice(0, c.length - 2);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
public async processSVG(blob: Blob): Promise<HTMLImageElement> {
|
|
45
|
+
let data = <string>await new Promise((resolve, _) => {
|
|
46
|
+
const reader = new FileReader();
|
|
47
|
+
reader.onloadend = () => resolve(<string>reader.result);
|
|
48
|
+
reader.readAsDataURL(blob);
|
|
49
|
+
});
|
|
50
|
+
data = data.replace('data:image/svg+xml;base64,', '')
|
|
51
|
+
data = atob(data);
|
|
52
|
+
|
|
53
|
+
let svgC = document.createElement('DIV');
|
|
54
|
+
svgC.id = 'svgc';
|
|
55
|
+
svgC.innerHTML = <string>data;
|
|
56
|
+
|
|
57
|
+
// now we can access the svg element as a DOM object
|
|
58
|
+
let svgE = svgC.getElementsByTagName('svg');
|
|
59
|
+
let childImageURIs: string[] = [];
|
|
60
|
+
let styleURIs: string[] = [];
|
|
61
|
+
|
|
62
|
+
// collect image urls
|
|
63
|
+
for (let i = 0; i < svgE.length; ++i) {
|
|
64
|
+
for (let j = 0; j < 2; ++j) {
|
|
65
|
+
let childImages = <HTMLCollectionOf<SVGImageElement>>svgE[i].getElementsByTagName(['image', 'img'][j]);
|
|
66
|
+
for (let k = 0; k < childImages.length; ++k) {
|
|
67
|
+
if (childImages[k].href.baseVal.substring(0, 5) != 'data:') {
|
|
68
|
+
childImageURIs.push(childImages[k].href.baseVal);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// collect potential font definitions
|
|
73
|
+
// we assume styles are imported using the following syntax:
|
|
74
|
+
// @import url(CSS_URL);
|
|
75
|
+
let styleElements = <HTMLCollectionOf<HTMLStyleElement>>svgE[i].getElementsByTagName('style');
|
|
76
|
+
for (let j = 0; j < styleElements.length; ++j) {
|
|
77
|
+
let regex = /@import\x20url\(\s*(.*?)\s*\);/g;
|
|
78
|
+
let m;
|
|
79
|
+
while ((m = regex.exec(styleElements[j].innerHTML)) !== null) {
|
|
80
|
+
styleURIs.push(m[1]);
|
|
81
|
+
}
|
|
82
|
+
// make unique
|
|
83
|
+
styleURIs = styleURIs.filter(
|
|
84
|
+
function (value, index, self) {
|
|
85
|
+
return self.indexOf(value) === index;
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// creating a promise for each image which needs to be converted to a data URI
|
|
92
|
+
let replacementPromises = [];
|
|
93
|
+
let createImagePromise = async (uri: string) => {
|
|
94
|
+
if (uri.length > 0) {
|
|
95
|
+
const response = await this._httpClient.loadTexture(uri);
|
|
96
|
+
let uInt8Array = new Uint8Array(response.data), i = uInt8Array.length;
|
|
97
|
+
let biStr = []; //new Array(i);
|
|
98
|
+
while (i--)
|
|
99
|
+
biStr[i] = String.fromCharCode(uInt8Array[i]);
|
|
100
|
+
|
|
101
|
+
let base64Data = window.btoa(biStr.join(''));
|
|
102
|
+
let imgDataUrl = 'data:' + response.headers['content-type'] + ';base64,' + base64Data;
|
|
103
|
+
|
|
104
|
+
// replace url in SVG string
|
|
105
|
+
// CAUTION theoretically this could cause unwanted replacements
|
|
106
|
+
data = data.replace(uri, imgDataUrl);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < childImageURIs.length; ++i)
|
|
111
|
+
replacementPromises.push(createImagePromise(childImageURIs[i]));
|
|
112
|
+
|
|
113
|
+
// now we create promises for the google fonts to be imported
|
|
114
|
+
let createStylePromise = async (styleUrl: string) => {
|
|
115
|
+
const response = await this._httpClient.get(
|
|
116
|
+
styleUrl,
|
|
117
|
+
{ responseType: 'text' }
|
|
118
|
+
);
|
|
119
|
+
let cssString = response.data;
|
|
120
|
+
// we assume that fonts are imported using the following syntax:
|
|
121
|
+
// url(FONT_URI);
|
|
122
|
+
let fontURLs = [];
|
|
123
|
+
let regex = /url\(\s*(.*?)\s*\)/g;
|
|
124
|
+
let m;
|
|
125
|
+
while ((m = regex.exec(cssString)) !== null) {
|
|
126
|
+
fontURLs.push(m[1]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let fontPromises = [];
|
|
130
|
+
let createFontPromise = async (fUrl: string) => {
|
|
131
|
+
const response = await this._httpClient.get(
|
|
132
|
+
fUrl,
|
|
133
|
+
{ responseType: 'arraybuffer' }
|
|
134
|
+
);
|
|
135
|
+
let uInt8Array = new Uint8Array(response.data), i = uInt8Array.length;
|
|
136
|
+
let biStr = []; //new Array(i);
|
|
137
|
+
while (i--)
|
|
138
|
+
biStr[i] = String.fromCharCode(uInt8Array[i]);
|
|
139
|
+
|
|
140
|
+
let base64Data = window.btoa(biStr.join(''));
|
|
141
|
+
let fontDataUrl = 'data:' + response.headers['content-type'] + ';base64,' + base64Data;
|
|
142
|
+
if (fUrl.length > 0)
|
|
143
|
+
cssString = cssString.replace(fUrl, fontDataUrl);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
for (let j = 0; j < fontURLs.length; ++j)
|
|
147
|
+
fontPromises.push(createFontPromise(fontURLs[j]));
|
|
148
|
+
|
|
149
|
+
await Promise.all(fontPromises);
|
|
150
|
+
data = data.replace('@import url(' + styleUrl + ');', cssString);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
for (let i = 0; i < styleURIs.length; ++i)
|
|
154
|
+
replacementPromises.push(createStylePromise(styleURIs[i]));
|
|
155
|
+
|
|
156
|
+
await Promise.all(replacementPromises);
|
|
157
|
+
|
|
158
|
+
let du = 'data:image/svg+xml,' + encodeURIComponent(data);
|
|
159
|
+
let img = new Image(); // same as document.createElement('img')
|
|
160
|
+
img.crossOrigin = 'Anonymous';
|
|
161
|
+
const promise = new Promise<void>(resolve => {
|
|
162
|
+
img.onload = () => resolve();
|
|
163
|
+
})
|
|
164
|
+
img.src = du;
|
|
165
|
+
await promise;
|
|
166
|
+
return img;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* This color converter is mostly left 'as-is' from viewer v2.
|
|
171
|
+
* I didn't want to break something that works.
|
|
172
|
+
*
|
|
173
|
+
* @param color
|
|
174
|
+
* @param defColor
|
|
175
|
+
*/
|
|
176
|
+
public toColor(color: any, defColorString: string = '#00fff7'): string {
|
|
177
|
+
if (!color || color === 'default') return defColorString;
|
|
178
|
+
|
|
179
|
+
if (color.constructor === Float32Array)
|
|
180
|
+
color = Array.from(color);
|
|
181
|
+
|
|
182
|
+
const tColor = new TinyColor(color);
|
|
183
|
+
|
|
184
|
+
if (color instanceof TinyColor)
|
|
185
|
+
return this.tinyColorToString(tColor);
|
|
186
|
+
|
|
187
|
+
// check if we got a number
|
|
188
|
+
if (typeof color === 'number') {
|
|
189
|
+
let cs = color.toString(16);
|
|
190
|
+
let cl = cs.length;
|
|
191
|
+
if (cl < 3) cs = cs.padStart(3, '0');
|
|
192
|
+
else if (cl < 6) cs = cs.padStart(6, '0');
|
|
193
|
+
else if (cl < 8) cs = cs.padEnd(8, '0');
|
|
194
|
+
let tc = new TinyColor(cs);
|
|
195
|
+
return tc.isValid ? this.tinyColorToString(tc) : defColorString;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// check if the input is a THREE.Color
|
|
199
|
+
if (color.isColor && typeof color.getHexString == 'function') {
|
|
200
|
+
let tc = new TinyColor(color.getHexString());
|
|
201
|
+
return tc.isValid ? this.tinyColorToString(tc) : defColorString;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// check for array of numbers
|
|
205
|
+
if (Array.isArray(color) && (color.length == 3 || color.length == 4)) {
|
|
206
|
+
let isRGBArray = true;
|
|
207
|
+
for (let i = 0; i < 3; ++i) {
|
|
208
|
+
color[i] = parseFloat(color[i]);
|
|
209
|
+
if (isNaN(color[i])) {
|
|
210
|
+
isRGBArray = false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (!isRGBArray)
|
|
214
|
+
return defColorString;
|
|
215
|
+
|
|
216
|
+
let tc = new TinyColor({
|
|
217
|
+
r: Math.max(0, Math.min(color[0], 255)),
|
|
218
|
+
g: Math.max(0, Math.min(color[1], 255)),
|
|
219
|
+
b: Math.max(0, Math.min(color[2], 255))
|
|
220
|
+
});
|
|
221
|
+
if (color.length == 4) {
|
|
222
|
+
let a = parseFloat(color[3]);
|
|
223
|
+
if (!isNaN(a)) {
|
|
224
|
+
tc.setAlpha(Math.max(0, Math.min(a, 255)) / 255);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return tc.isValid ? this.tinyColorToString(tc) : defColorString;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// if we got something other than a string, check if
|
|
231
|
+
// tinycolor can work with it
|
|
232
|
+
if (typeof color !== 'string') {
|
|
233
|
+
let tc = new TinyColor(color);
|
|
234
|
+
return tc.isValid ? this.tinyColorToString(tc) : defColorString;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// tinycolor doesn't like 0x
|
|
238
|
+
let tmpColor = color.replace('0x', '#');
|
|
239
|
+
|
|
240
|
+
// if we got no alpha value, add full opacity
|
|
241
|
+
if (tmpColor.match(/^#[a-f0-9]{6}$/i) !== null) {
|
|
242
|
+
let tc = new TinyColor(tmpColor + 'ff');
|
|
243
|
+
return tc.isValid ? this.tinyColorToString(tc) : defColorString;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// standard case
|
|
247
|
+
if (tmpColor.match(/^#[a-f0-9]{8}$/i) !== null) {
|
|
248
|
+
let tc = new TinyColor(tmpColor);
|
|
249
|
+
return tc.isValid ? this.tinyColorToString(tc) : defColorString;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// correct number which have the alpha value defined as a single hex digit
|
|
253
|
+
if (tmpColor.match(/^#[a-f0-9]{7}$/i) !== null) {
|
|
254
|
+
let tc = new TinyColor(tmpColor.slice(0, 7) + '0' + tmpColor.slice(-1));
|
|
255
|
+
return tc.isValid ? this.tinyColorToString(tc) : defColorString;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// check if tinycolor understands the string
|
|
259
|
+
let tc = new TinyColor(tmpColor);
|
|
260
|
+
return tc.isValid ? this.tinyColorToString(tc) : defColorString;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
public toVec3(point: any): vec3 {
|
|
264
|
+
if (Array.isArray(point) && point.length >= 3 && typeof point[0] === 'number' && typeof point[1] === 'number' && typeof point[2] === 'number')
|
|
265
|
+
return vec3.fromValues(point[0], point[1], point[2]);
|
|
266
|
+
|
|
267
|
+
if (((point.x || point.x === 0) && typeof point.x === 'number') && ((point.y || point.y === 0) && typeof point.y === 'number') && ((point.z || point.z === 0) && typeof point.z === 'number'))
|
|
268
|
+
return vec3.fromValues(point.x, point.y, point.z);
|
|
269
|
+
|
|
270
|
+
if (((point.X || point.X === 0) && typeof point.X === 'number') && ((point.Y || point.Y === 0) && typeof point.Y === 'number') && ((point.Z || point.Z === 0) && typeof point.Z === 'number'))
|
|
271
|
+
return vec3.fromValues(point.X, point.Y, point.Z);
|
|
272
|
+
|
|
273
|
+
return vec3.create();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
public async responseToImage(response: HttpResponse<ArrayBuffer>): Promise<HTMLImageElement> {
|
|
277
|
+
const arrayBufferView = new Uint8Array( response.data );
|
|
278
|
+
const blob = new Blob([ arrayBufferView ], { type: response.headers['content-type'] } );
|
|
279
|
+
if (response.headers['content-type'] === 'image/svg+xml') {
|
|
280
|
+
const img = await this.processSVG(blob);
|
|
281
|
+
return img;
|
|
282
|
+
} else {
|
|
283
|
+
const img = new Image();
|
|
284
|
+
const promise = new Promise<void>(resolve => {
|
|
285
|
+
img.onload = () => resolve();
|
|
286
|
+
})
|
|
287
|
+
img.crossOrigin = "anonymous";
|
|
288
|
+
|
|
289
|
+
let data = <string>await new Promise((resolve, _) => {
|
|
290
|
+
const reader = new FileReader();
|
|
291
|
+
reader.onloadend = () => resolve(<string>reader.result);
|
|
292
|
+
reader.readAsDataURL(blob);
|
|
293
|
+
});
|
|
294
|
+
img.src = data;
|
|
295
|
+
await promise;
|
|
296
|
+
return img;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { container } from 'tsyringe'
|
|
2
|
+
import { UuidGenerator } from '../uuid-generator/UuidGenerator';
|
|
3
|
+
|
|
4
|
+
import { IDomEventListener } from './IDomEventListener'
|
|
5
|
+
|
|
6
|
+
export class DomEventEngine {
|
|
7
|
+
// #region Properties (5)
|
|
8
|
+
|
|
9
|
+
private readonly _domEventListeners: {
|
|
10
|
+
[key: string]: IDomEventListener
|
|
11
|
+
} = {};
|
|
12
|
+
private readonly _uuidGenerator: UuidGenerator = <UuidGenerator>container.resolve(UuidGenerator);
|
|
13
|
+
|
|
14
|
+
private _allowListeners = {
|
|
15
|
+
mousewheel: true,
|
|
16
|
+
mousedown: true,
|
|
17
|
+
mousemove: true,
|
|
18
|
+
mouseup: true,
|
|
19
|
+
mouseout: true,
|
|
20
|
+
touchstart: true,
|
|
21
|
+
touchmove: true,
|
|
22
|
+
touchend: true,
|
|
23
|
+
touchcancel: true,
|
|
24
|
+
keydown: true,
|
|
25
|
+
contextmenu: true,
|
|
26
|
+
};
|
|
27
|
+
private _canvas: HTMLCanvasElement;
|
|
28
|
+
private _currentMousePosition: { x: number, y: number } = { x: 0, y: 0 };
|
|
29
|
+
private _onMouseWheel: (event: Event) => void;
|
|
30
|
+
private _onMouseDown: (event: MouseEvent) => void;
|
|
31
|
+
private _onMouseMove: (event: MouseEvent) => void;
|
|
32
|
+
private _onKeyDownMousePositionHelper: (event: MouseEvent) => void;
|
|
33
|
+
private _onMouseUp: (event: MouseEvent) => void;
|
|
34
|
+
private _onMouseOut: (event: MouseEvent) => void;
|
|
35
|
+
private _onTouchStart: (event: TouchEvent) => void;
|
|
36
|
+
private _onTouchMove: (event: TouchEvent) => void;
|
|
37
|
+
private _onTouchUp: (event: TouchEvent) => void;
|
|
38
|
+
private _onTouchCancel: (event: TouchEvent) => void;
|
|
39
|
+
private _onKeyDown: (event: KeyboardEvent) => void;
|
|
40
|
+
private _onContextMenu: (event: MouseEvent) => void;
|
|
41
|
+
|
|
42
|
+
// #endregion Properties (5)
|
|
43
|
+
|
|
44
|
+
// #region Constructors (1)
|
|
45
|
+
|
|
46
|
+
constructor(canvas: HTMLCanvasElement) {
|
|
47
|
+
this._canvas = canvas;
|
|
48
|
+
this._onMouseWheel = this.onMouseWheel.bind(this);
|
|
49
|
+
this._onMouseDown = this.onMouseDown.bind(this);
|
|
50
|
+
this._onMouseMove = this.onMouseMove.bind(this)
|
|
51
|
+
this._onKeyDownMousePositionHelper = this.onKeyDownMousePositionHelper.bind(this)
|
|
52
|
+
this._onMouseUp = this.onMouseUp.bind(this)
|
|
53
|
+
this._onMouseOut = this.onMouseOut.bind(this)
|
|
54
|
+
this._onTouchStart = this.onTouchStart.bind(this)
|
|
55
|
+
this._onTouchMove = this.onTouchMove.bind(this)
|
|
56
|
+
this._onTouchUp = this.onTouchUp.bind(this)
|
|
57
|
+
this._onTouchCancel = this.onTouchCancel.bind(this)
|
|
58
|
+
this._onKeyDown = this.onKeyDown.bind(this)
|
|
59
|
+
this._onContextMenu = this.onContextMenu.bind(this)
|
|
60
|
+
|
|
61
|
+
this.addEventListeners();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// #endregion Constructors (1)
|
|
65
|
+
|
|
66
|
+
// #region Public Methods (5)
|
|
67
|
+
|
|
68
|
+
public addDomEventListener(listener: IDomEventListener): string {
|
|
69
|
+
const id = this._uuidGenerator.create();
|
|
70
|
+
this._domEventListeners[id] = listener;
|
|
71
|
+
return id;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Allow / disallow events.
|
|
76
|
+
* This can be used to disable events for a specific viewer.
|
|
77
|
+
*
|
|
78
|
+
* Example use case: If you don't want to allow mouse wheel events for a specific viewer so that users can scroll past the viewer.
|
|
79
|
+
*
|
|
80
|
+
* Be aware that this might cause some issues with the the camera controls if the mouse / touch events are disabled only partially.
|
|
81
|
+
*
|
|
82
|
+
* @param allowedListeners
|
|
83
|
+
*/
|
|
84
|
+
public allowEventListeners(allowedListeners: {
|
|
85
|
+
mousewheel?: boolean,
|
|
86
|
+
mousedown?: boolean,
|
|
87
|
+
mousemove?: boolean,
|
|
88
|
+
mouseup?: boolean,
|
|
89
|
+
mouseout?: boolean,
|
|
90
|
+
touchstart?: boolean,
|
|
91
|
+
touchmove?: boolean,
|
|
92
|
+
touchend?: boolean,
|
|
93
|
+
touchcancel?: boolean,
|
|
94
|
+
keydown?: boolean,
|
|
95
|
+
contextmenu?: boolean,
|
|
96
|
+
}): void {
|
|
97
|
+
if (allowedListeners.mousewheel !== undefined && this._allowListeners.mousewheel !== allowedListeners.mousewheel) {
|
|
98
|
+
if (allowedListeners.mousewheel) {
|
|
99
|
+
this._canvas.addEventListener("mousewheel", this._onMouseWheel);
|
|
100
|
+
this._canvas.addEventListener("MozMousePixelScroll", this._onMouseWheel); // firefox
|
|
101
|
+
} else {
|
|
102
|
+
this._canvas.removeEventListener("mousewheel", this._onMouseWheel);
|
|
103
|
+
this._canvas.removeEventListener("MozMousePixelScroll", this._onMouseWheel); // firefox
|
|
104
|
+
}
|
|
105
|
+
this._allowListeners.mousewheel = allowedListeners.mousewheel;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (allowedListeners.mousedown !== undefined && this._allowListeners.mousedown !== allowedListeners.mousedown) {
|
|
109
|
+
if (allowedListeners.mousedown) {
|
|
110
|
+
this._canvas.addEventListener("mousedown", this._onMouseDown);
|
|
111
|
+
} else {
|
|
112
|
+
this._canvas.removeEventListener("mousedown", this._onMouseDown);
|
|
113
|
+
}
|
|
114
|
+
this._allowListeners.mousedown = allowedListeners.mousedown;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (allowedListeners.mousemove !== undefined && this._allowListeners.mousemove !== allowedListeners.mousemove) {
|
|
118
|
+
if (allowedListeners.mousemove) {
|
|
119
|
+
this._canvas.addEventListener("mousemove", this._onMouseMove);
|
|
120
|
+
window.addEventListener("mousemove", this._onKeyDownMousePositionHelper);
|
|
121
|
+
} else {
|
|
122
|
+
this._canvas.removeEventListener("mousemove", this._onMouseMove);
|
|
123
|
+
window.removeEventListener("mousemove", this._onKeyDownMousePositionHelper);
|
|
124
|
+
}
|
|
125
|
+
this._allowListeners.mousemove = allowedListeners.mousemove;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (allowedListeners.mouseup !== undefined && this._allowListeners.mouseup !== allowedListeners.mouseup) {
|
|
129
|
+
if (allowedListeners.mouseup) {
|
|
130
|
+
this._canvas.addEventListener("mouseup", this._onMouseUp);
|
|
131
|
+
} else {
|
|
132
|
+
this._canvas.removeEventListener("mouseup", this._onMouseUp);
|
|
133
|
+
}
|
|
134
|
+
this._allowListeners.mouseup = allowedListeners.mouseup;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (allowedListeners.mouseout !== undefined && this._allowListeners.mouseout !== allowedListeners.mouseout) {
|
|
138
|
+
if (allowedListeners.mouseout) {
|
|
139
|
+
this._canvas.addEventListener("mouseout", this._onMouseOut);
|
|
140
|
+
} else {
|
|
141
|
+
this._canvas.removeEventListener("mouseout", this._onMouseOut);
|
|
142
|
+
}
|
|
143
|
+
this._allowListeners.mouseout = allowedListeners.mouseout;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (allowedListeners.touchstart !== undefined && this._allowListeners.touchstart !== allowedListeners.touchstart) {
|
|
147
|
+
if (allowedListeners.touchstart) {
|
|
148
|
+
window.addEventListener("touchstart", this._onTouchStart);
|
|
149
|
+
} else {
|
|
150
|
+
window.removeEventListener("touchstart", this._onTouchStart);
|
|
151
|
+
}
|
|
152
|
+
this._allowListeners.touchstart = allowedListeners.touchstart;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (allowedListeners.touchmove !== undefined && this._allowListeners.touchmove !== allowedListeners.touchmove) {
|
|
156
|
+
if (allowedListeners.touchmove) {
|
|
157
|
+
window.addEventListener("touchmove", this._onTouchMove);
|
|
158
|
+
} else {
|
|
159
|
+
window.removeEventListener("touchmove", this._onTouchMove);
|
|
160
|
+
}
|
|
161
|
+
this._allowListeners.touchmove = allowedListeners.touchmove;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (allowedListeners.touchend !== undefined && this._allowListeners.touchend !== allowedListeners.touchend) {
|
|
165
|
+
if (allowedListeners.touchend) {
|
|
166
|
+
window.addEventListener("touchend", this._onTouchUp);
|
|
167
|
+
} else {
|
|
168
|
+
window.removeEventListener("touchend", this._onTouchUp);
|
|
169
|
+
}
|
|
170
|
+
this._allowListeners.touchend = allowedListeners.touchend;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (allowedListeners.touchcancel !== undefined && this._allowListeners.touchcancel !== allowedListeners.touchcancel) {
|
|
174
|
+
if (allowedListeners.touchcancel) {
|
|
175
|
+
window.addEventListener("touchcancel", this._onTouchCancel);
|
|
176
|
+
} else {
|
|
177
|
+
window.removeEventListener("touchcancel", this._onTouchCancel);
|
|
178
|
+
}
|
|
179
|
+
this._allowListeners.touchcancel = allowedListeners.touchcancel;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (allowedListeners.keydown !== undefined && this._allowListeners.keydown !== allowedListeners.keydown) {
|
|
183
|
+
if (allowedListeners.keydown) {
|
|
184
|
+
window.addEventListener("keydown", this._onKeyDown);
|
|
185
|
+
} else {
|
|
186
|
+
window.removeEventListener("keydown", this._onKeyDown);
|
|
187
|
+
}
|
|
188
|
+
this._allowListeners.keydown = allowedListeners.keydown;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (allowedListeners.contextmenu !== undefined && this._allowListeners.contextmenu !== allowedListeners.contextmenu) {
|
|
192
|
+
if (allowedListeners.contextmenu) {
|
|
193
|
+
this._canvas.addEventListener("contextmenu", this._onContextMenu);
|
|
194
|
+
} else {
|
|
195
|
+
this._canvas.removeEventListener("contextmenu", this._onContextMenu);
|
|
196
|
+
}
|
|
197
|
+
this._allowListeners.contextmenu = allowedListeners.contextmenu;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public dispose() {
|
|
202
|
+
this.removeEventListeners();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public removeAllDomEventListener(): void {
|
|
206
|
+
for(let id in this._domEventListeners)
|
|
207
|
+
delete this._domEventListeners[id];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
public removeDomEventListener(id: string): boolean {
|
|
211
|
+
if(this._domEventListeners[id]) {
|
|
212
|
+
delete this._domEventListeners[id];
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// #endregion Public Methods (5)
|
|
219
|
+
|
|
220
|
+
// #region Private Methods (12)
|
|
221
|
+
|
|
222
|
+
private addEventListeners() {
|
|
223
|
+
this._canvas.addEventListener("mousewheel", this._onMouseWheel);
|
|
224
|
+
this._canvas.addEventListener("MozMousePixelScroll", this._onMouseWheel); // firefox
|
|
225
|
+
|
|
226
|
+
this._canvas.addEventListener("mousedown", this._onMouseDown);
|
|
227
|
+
this._canvas.addEventListener("mousemove", this._onMouseMove);
|
|
228
|
+
this._canvas.addEventListener("mouseup", this._onMouseUp);
|
|
229
|
+
this._canvas.addEventListener("mouseout", this._onMouseOut);
|
|
230
|
+
|
|
231
|
+
window.addEventListener("touchstart", this._onTouchStart);
|
|
232
|
+
window.addEventListener("touchmove", this._onTouchMove);
|
|
233
|
+
window.addEventListener("touchend", this._onTouchUp);
|
|
234
|
+
window.addEventListener("touchcancel", this._onTouchCancel);
|
|
235
|
+
|
|
236
|
+
window.addEventListener("keydown", this._onKeyDown);
|
|
237
|
+
window.addEventListener("mousemove", this._onKeyDownMousePositionHelper);
|
|
238
|
+
|
|
239
|
+
// just prevent right click menu
|
|
240
|
+
this._canvas.addEventListener("contextmenu", this._onContextMenu);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private onContextMenu(event: MouseEvent): void {
|
|
244
|
+
event.preventDefault();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private onKeyDown(event: KeyboardEvent): void {
|
|
248
|
+
if (this._canvas === document.elementFromPoint(this._currentMousePosition.x, this._currentMousePosition.y))
|
|
249
|
+
Object.values(this._domEventListeners).forEach(e => e.onKeyDown(event));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private onKeyDownMousePositionHelper(event: MouseEvent): void {
|
|
253
|
+
this._currentMousePosition = { x: event.pageX, y: event.pageY };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private onMouseDown(event: MouseEvent): void {
|
|
257
|
+
event.preventDefault();
|
|
258
|
+
Object.values(this._domEventListeners).forEach(e => e.onMouseDown(event));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private onMouseMove(event: MouseEvent): void {
|
|
262
|
+
event.preventDefault();
|
|
263
|
+
Object.values(this._domEventListeners).forEach(e => e.onMouseMove(event));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private onMouseUp(event: MouseEvent): void {
|
|
267
|
+
event.preventDefault();
|
|
268
|
+
Object.values(this._domEventListeners).forEach(e => e.onMouseUp(event));
|
|
269
|
+
Object.values(this._domEventListeners).forEach(e => e.onMouseEnd(event));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private onMouseOut(event: MouseEvent): void {
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
Object.values(this._domEventListeners).forEach(e => e.onMouseOut(event));
|
|
275
|
+
Object.values(this._domEventListeners).forEach(e => e.onMouseEnd(event));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private onMouseWheel(event: Event): void {
|
|
279
|
+
event.preventDefault();
|
|
280
|
+
event.stopPropagation();
|
|
281
|
+
Object.values(this._domEventListeners).forEach(e => e.onMouseWheel(<WheelEvent>event));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private onTouchUp(event: TouchEvent): void {
|
|
285
|
+
if (event.composedPath().includes(this._canvas)) {
|
|
286
|
+
event.preventDefault();
|
|
287
|
+
Object.values(this._domEventListeners).forEach(e => e.onTouchUp(event));
|
|
288
|
+
Object.values(this._domEventListeners).forEach(e => e.onTouchEnd(event));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private onTouchCancel(event: TouchEvent): void {
|
|
293
|
+
if (event.composedPath().includes(this._canvas)) {
|
|
294
|
+
event.preventDefault();
|
|
295
|
+
Object.values(this._domEventListeners).forEach(e => e.onTouchCancel(event));
|
|
296
|
+
Object.values(this._domEventListeners).forEach(e => e.onTouchEnd(event));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private onTouchMove(event: TouchEvent): void {
|
|
301
|
+
if (event.composedPath().includes(this._canvas)) {
|
|
302
|
+
event.preventDefault();
|
|
303
|
+
Object.values(this._domEventListeners).forEach(e => e.onTouchMove(event))
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private onTouchStart(event: TouchEvent): void {
|
|
308
|
+
if (event.composedPath().includes(this._canvas)) {
|
|
309
|
+
event.preventDefault();
|
|
310
|
+
Object.values(this._domEventListeners).forEach(e => e.onTouchStart(event));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private removeEventListeners() {
|
|
315
|
+
this._canvas.removeEventListener("mousewheel", this._onMouseWheel);
|
|
316
|
+
this._canvas.removeEventListener("MozMousePixelScroll", this._onMouseWheel); // firefox
|
|
317
|
+
|
|
318
|
+
this._canvas.removeEventListener("mousedown", this._onMouseDown);
|
|
319
|
+
this._canvas.removeEventListener("mousemove", this._onMouseMove);
|
|
320
|
+
this._canvas.removeEventListener("mouseup", this._onMouseUp);
|
|
321
|
+
this._canvas.removeEventListener("mouseout", this._onMouseOut);
|
|
322
|
+
|
|
323
|
+
window.removeEventListener("touchstart", this._onTouchStart);
|
|
324
|
+
window.removeEventListener("touchmove", this._onTouchMove);
|
|
325
|
+
window.removeEventListener("touchend", this._onTouchUp);
|
|
326
|
+
window.removeEventListener("touchcancel", this._onTouchCancel);
|
|
327
|
+
|
|
328
|
+
window.removeEventListener("keydown", this._onKeyDown);
|
|
329
|
+
window.removeEventListener("mousemove", this._onKeyDownMousePositionHelper);
|
|
330
|
+
this._canvas.removeEventListener("contextmenu", this._onContextMenu);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// #endregion Private Methods (12)
|
|
334
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface IDomEventListener {
|
|
2
|
+
onKeyDown(event: KeyboardEvent): void;
|
|
3
|
+
onMouseDown(event: MouseEvent): void;
|
|
4
|
+
onMouseMove(event: MouseEvent): void;
|
|
5
|
+
onMouseEnd(event: MouseEvent): void;
|
|
6
|
+
onMouseUp(event: MouseEvent): void;
|
|
7
|
+
onMouseOut(event: MouseEvent): void;
|
|
8
|
+
onMouseWheel(event: WheelEvent): void;
|
|
9
|
+
onTouchEnd(event: TouchEvent): void;
|
|
10
|
+
onTouchUp(event: TouchEvent): void;
|
|
11
|
+
onTouchCancel(event: TouchEvent): void;
|
|
12
|
+
onTouchMove(event: TouchEvent): void;
|
|
13
|
+
onTouchStart(event: TouchEvent): void;
|
|
14
|
+
}
|