@wolsok/thanos 17.0.16 → 17.0.18

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 (54) hide show
  1. package/esm2022/index.js +10 -0
  2. package/esm2022/index.js.map +1 -0
  3. package/esm2022/lib/animation.state.js +2 -0
  4. package/esm2022/lib/animation.state.js.map +1 -0
  5. package/esm2022/lib/create-ws-thanos-options.js +9 -0
  6. package/esm2022/lib/create-ws-thanos-options.js.map +1 -0
  7. package/esm2022/lib/simplex-noise-options.js +2 -0
  8. package/esm2022/lib/simplex-noise-options.js.map +1 -0
  9. package/esm2022/lib/simplex-noise.js +199 -0
  10. package/esm2022/lib/simplex-noise.js.map +1 -0
  11. package/esm2022/lib/ws-thanos-options.token.js +13 -0
  12. package/esm2022/lib/ws-thanos-options.token.js.map +1 -0
  13. package/esm2022/lib/ws-thanos.directive.js +67 -0
  14. package/esm2022/lib/ws-thanos.directive.js.map +1 -0
  15. package/esm2022/lib/ws-thanos.options.js +2 -0
  16. package/esm2022/lib/ws-thanos.options.js.map +1 -0
  17. package/esm2022/lib/ws-thanos.service.js +252 -0
  18. package/esm2022/lib/ws-thanos.service.js.map +1 -0
  19. package/esm2022/wolsok-thanos.js +5 -0
  20. package/esm2022/wolsok-thanos.js.map +1 -0
  21. package/{src/index.ts → index.d.ts} +0 -3
  22. package/lib/animation.state.d.ts +6 -0
  23. package/lib/create-ws-thanos-options.d.ts +2 -0
  24. package/lib/simplex-noise-options.d.ts +9 -0
  25. package/lib/simplex-noise.d.ts +18 -0
  26. package/lib/ws-thanos-options.token.d.ts +4 -0
  27. package/lib/ws-thanos.directive.d.ts +29 -0
  28. package/lib/ws-thanos.options.d.ts +5 -0
  29. package/lib/ws-thanos.service.d.ts +20 -0
  30. package/package.json +18 -7
  31. package/wolsok-thanos.d.ts +5 -0
  32. package/CHANGELOG.md +0 -59
  33. package/eslint.config.mjs +0 -64
  34. package/jest.config.ts +0 -23
  35. package/ng-package.json +0 -8
  36. package/project.json +0 -40
  37. package/src/assets/how-to-be-funny.png.ts +0 -4
  38. package/src/lib/animation.state.ts +0 -6
  39. package/src/lib/create-ws-thanos-options.ts +0 -12
  40. package/src/lib/random-util.ts +0 -24
  41. package/src/lib/simplex-noise-options.ts +0 -9
  42. package/src/lib/simplex-noise.ts +0 -240
  43. package/src/lib/ws-thanos-options.token.spec.ts +0 -19
  44. package/src/lib/ws-thanos-options.token.ts +0 -20
  45. package/src/lib/ws-thanos.directive.spec.ts +0 -175
  46. package/src/lib/ws-thanos.directive.ts +0 -70
  47. package/src/lib/ws-thanos.options.ts +0 -5
  48. package/src/lib/ws-thanos.service.spec.ts +0 -9
  49. package/src/lib/ws-thanos.service.ts +0 -387
  50. package/src/test-setup.ts +0 -9
  51. package/tsconfig.json +0 -32
  52. package/tsconfig.lib.json +0 -15
  53. package/tsconfig.lib.prod.json +0 -10
  54. package/tsconfig.spec.json +0 -10
@@ -1,387 +0,0 @@
1
- import { Injectable, inject } from '@angular/core';
2
-
3
- import { default as html2canvas } from 'html2canvas';
4
- import {
5
- animationFrameScheduler,
6
- finalize,
7
- from,
8
- interval,
9
- map,
10
- Observable,
11
- switchMap,
12
- takeWhile,
13
- tap,
14
- timeInterval,
15
- } from 'rxjs';
16
- import { AnimationState } from './animation.state';
17
- import { SimplexNoise } from './simplex-noise';
18
- import { WS_THANOS_OPTIONS_TOKEN } from './ws-thanos-options.token';
19
- import type { WsThanosOptions } from './ws-thanos.options';
20
-
21
- const PARTICLE_BYTE_LENGTH = 10;
22
- const MIN_PARTICLE_ALPHA = ~~(255 * 0.01);
23
- const HEIGHT_SCALE = 5;
24
- const WIDTH_SCALE = 2;
25
- const FLOW_FIELD_RES = 0.05;
26
-
27
- interface ParticlesData {
28
- particles: Float32Array;
29
- maxParticleX: number;
30
- minParticleY: number;
31
- }
32
-
33
- interface UpdateParticleParams {
34
- particlesData: ParticlesData;
35
- animationState: AnimationState;
36
- thanosOptions: WsThanosOptions;
37
- noise: SimplexNoise;
38
- seed: number;
39
- }
40
-
41
- interface ParticleIndices {
42
- x: number;
43
- y: number;
44
- dx: number;
45
- dy: number;
46
- ax: number;
47
- ay: number;
48
- r: number;
49
- g: number;
50
- b: number;
51
- a: number;
52
- }
53
-
54
- @Injectable({ providedIn: 'root' })
55
- export class WsThanosService {
56
- private readonly thanosOptions: WsThanosOptions = inject(
57
- WS_THANOS_OPTIONS_TOKEN
58
- );
59
-
60
- private static getParticleIndicesForBase(base: number): ParticleIndices {
61
- return {
62
- x: base,
63
- y: base + 1,
64
- dx: base + 2,
65
- dy: base + 3,
66
- ax: base + 4,
67
- ay: base + 5,
68
- r: base + 6,
69
- g: base + 7,
70
- b: base + 8,
71
- a: base + 9,
72
- };
73
- }
74
-
75
- private static getColorIndicesForCoord(
76
- x: number,
77
- y: number,
78
- width: number
79
- ): { a: number; r: number; b: number; g: number } {
80
- const red = y * (width * 4) + x * 4;
81
- return { r: red, g: red + 1, b: red + 2, a: red + 3 };
82
- }
83
-
84
- private static updateParticles({
85
- seed,
86
- noise,
87
- particlesData,
88
- thanosOptions,
89
- animationState,
90
- }: UpdateParticleParams): void {
91
- const { deltaTSec, animationT, maxWidth, maxHeight } = animationState;
92
- const { particleAcceleration } = thanosOptions;
93
- const { particles, maxParticleX, minParticleY } = particlesData;
94
-
95
- // the time is used to calculate the vaporization front.
96
- const time = Math.sin(animationT * (Math.PI / 2)) * 1.1;
97
-
98
- const startAccelerateX = maxParticleX - time * maxParticleX;
99
- const startAccelerateY = time * (maxHeight - minParticleY) + minParticleY;
100
-
101
- const lengthY = maxHeight - startAccelerateY;
102
- const accelerateRadiusPow =
103
- startAccelerateX * startAccelerateX + lengthY * lengthY;
104
-
105
- for (let i = 0; i < particles.length; i += PARTICLE_BYTE_LENGTH) {
106
- const { x, y, dx, dy, ax, ay, a } =
107
- WsThanosService.getParticleIndicesForBase(i);
108
- const particleX = particles[x];
109
- const particleY = particles[y];
110
-
111
- // only update particles that are inside view and visible
112
- if (
113
- particleX > maxWidth ||
114
- particleX < 0 ||
115
- particleY > maxHeight ||
116
- particleY < 0 ||
117
- particles[a] < MIN_PARTICLE_ALPHA
118
- ) {
119
- continue;
120
- }
121
-
122
- if (!(Math.abs(particles[ax]) > 0 || Math.abs(particles[ay]) > 0)) {
123
- let pYLength = maxHeight - particleY;
124
- let pXLength = particleX;
125
-
126
- // we calculate some random looking numbers and functions to have nice looking vaporizing front of particles
127
- // todo consider to use noise;
128
- pXLength += Math.tan((pXLength / 20.12) * time + seed) * 0.5;
129
- pXLength += (particleX % deltaTSec) * 0.5;
130
- pXLength +=
131
- Math.sin((pXLength / 30 + 723.394) * time + seed * 12.5) * 11;
132
- pYLength += Math.tan((pYLength / 0.45) * time + seed * 1.5) * 0.5;
133
- pYLength +=
134
- Math.cos((pYLength / 100 + 2323.234) * time + seed * 456.1) * 23;
135
-
136
- const pLength = pXLength * pXLength + pYLength * pYLength;
137
- if (pLength > accelerateRadiusPow) {
138
- particles[ax] = Math.random();
139
- particles[ay] = Math.random() * -1;
140
- }
141
- } else {
142
- // use noise to flow along velocity field
143
- const flowFieldAcc = WsThanosService.getXYFromFlowField(
144
- noise,
145
- seed,
146
- particleX,
147
- particleY
148
- );
149
- const accelerationX = flowFieldAcc[0];
150
- const accelerationY = flowFieldAcc[1];
151
- particles[ax] += accelerationX;
152
- particles[ay] += accelerationY;
153
- }
154
-
155
- // * Math.pow(1 + animationT, 4)
156
- particles[dx] += particles[ax] * particleAcceleration * deltaTSec;
157
- particles[dy] += particles[ay] * particleAcceleration * deltaTSec;
158
- particles[x] += particles[dx] * deltaTSec;
159
- particles[y] += particles[dy] * deltaTSec;
160
- // fade particle out ( very late);
161
- particles[a] *= 1 - Math.pow(animationT, 15); // 255 - Math.sqrt(particles[dx] * particles[dx] + particles[dy] * particles[dy]) / 1.2;
162
- }
163
- }
164
-
165
- private static drawParticles(
166
- drawCtx: CanvasRenderingContext2D,
167
- particles: Float32Array
168
- ): void {
169
- const { width, height } = drawCtx.canvas;
170
- drawCtx.clearRect(0, 0, width, height);
171
- const image = drawCtx.getImageData(0, 0, width, height);
172
- const imageData = image.data;
173
- for (let i = 0; i < particles.length; i += PARTICLE_BYTE_LENGTH) {
174
- const particleX = particles[i];
175
- const particleY = particles[i + 1];
176
- const pI = WsThanosService.getParticleIndicesForBase(i);
177
- if (
178
- particleX > width ||
179
- particleX < 0 ||
180
- particleY > height ||
181
- particleY < 0 ||
182
- particles[pI.a] < MIN_PARTICLE_ALPHA
183
- ) {
184
- continue;
185
- }
186
- const { r, g, b, a } = WsThanosService.getColorIndicesForCoord(
187
- ~~particles[pI.x],
188
- ~~particles[pI.y],
189
- width
190
- );
191
- imageData[r] = ~~particles[pI.r];
192
- imageData[g] = ~~particles[pI.g];
193
- imageData[b] = ~~particles[pI.b];
194
- imageData[a] = ~~particles[pI.a];
195
- }
196
- drawCtx.putImageData(image, 0, 0);
197
- }
198
-
199
- private static prepareCanvasForVaporize(
200
- divCanvas: HTMLCanvasElement,
201
- maxParticleCount: number
202
- ): {
203
- particlesData: ParticlesData;
204
- resultCanvas: HTMLCanvasElement;
205
- } {
206
- const { width, height } = divCanvas;
207
-
208
- // scale result canvas to have more room to draw particles while vaporizing;
209
- const resultHeight = height * HEIGHT_SCALE;
210
- const resultWidth = width * WIDTH_SCALE;
211
- const resultCanvas: HTMLCanvasElement = document.createElement('canvas');
212
- resultCanvas.height = resultHeight;
213
- resultCanvas.width = resultWidth;
214
-
215
- const imageData = divCanvas
216
- .getContext('2d', { willReadFrequently: true })
217
- ?.getImageData(0, 0, width, height);
218
- if (imageData == null) {
219
- throw new Error('Could not get image data from canvas');
220
- }
221
- const particlesData = WsThanosService.createParticlesForImageData(
222
- imageData,
223
- maxParticleCount,
224
- resultHeight
225
- );
226
- return { resultCanvas, particlesData };
227
- }
228
-
229
- private static createParticlesForImageData(
230
- imageData: ImageData,
231
- maxParticleCount: number,
232
- resultHeight: number
233
- ): { minParticleY: number; maxParticleX: number; particles: Float32Array } {
234
- const { width, height } = imageData;
235
- let particleCandidates = 0;
236
- let particleCount = 0;
237
-
238
- const particleCandiatesList: { x: number; y: number }[] = [];
239
- for (let y = 0; y < height; y++) {
240
- for (let x = 0; x < width; x++) {
241
- const { a } = WsThanosService.getColorIndicesForCoord(x, y, width);
242
- if (imageData.data[a] >= MIN_PARTICLE_ALPHA) {
243
- particleCandidates++;
244
- particleCandiatesList.push({ x, y });
245
- }
246
- }
247
- }
248
-
249
- const maxNumParticles = Math.min(particleCandidates, maxParticleCount);
250
- const particles = new Float32Array(
251
- Float32Array.BYTES_PER_ELEMENT * maxNumParticles * PARTICLE_BYTE_LENGTH
252
- );
253
-
254
- let maxParticleX = 0;
255
- let minParticleY = resultHeight;
256
- while (particleCount < maxNumParticles) {
257
- // select random index from candiates
258
- const index = ~~(Math.random() * particleCandiatesList.length);
259
- const { x, y } = particleCandiatesList[index];
260
-
261
- // overwrite index with last element to prevent double selection of particles
262
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
263
- particleCandiatesList[index] = particleCandiatesList.pop()!;
264
-
265
- maxParticleX = Math.max(maxParticleX, x);
266
- minParticleY = Math.min(minParticleY, y);
267
- const { dx, dy, ax, ay, r, g, b, a, ...xy } =
268
- WsThanosService.getParticleIndicesForBase(
269
- particleCount * PARTICLE_BYTE_LENGTH
270
- );
271
- particles[xy.x] = x;
272
- particles[xy.y] = y + resultHeight - height;
273
- particles[dx] = 0;
274
- particles[dy] = 0;
275
- particles[ax] = 0;
276
- particles[ay] = 0;
277
-
278
- const indicesImage = WsThanosService.getColorIndicesForCoord(x, y, width);
279
-
280
- particles[r] = imageData.data[indicesImage.r];
281
- particles[g] = imageData.data[indicesImage.g];
282
- particles[b] = imageData.data[indicesImage.b];
283
- particles[a] = imageData.data[indicesImage.a];
284
- particleCount++;
285
- }
286
- return { particles, maxParticleX, minParticleY };
287
- }
288
-
289
- private static getXYFromFlowField(
290
- noise: SimplexNoise,
291
- seed: number,
292
- x: number,
293
- y: number
294
- ) {
295
- const sampleX = noise.scaled3D(x, y, seed + 33.23, FLOW_FIELD_RES);
296
- const sampleY = noise.scaled3D(x, seed / 13.23, y, FLOW_FIELD_RES) * -1;
297
- return [sampleX, sampleY];
298
- }
299
-
300
- /**
301
- * start the vaporize-effect.
302
- */
303
- vaporize(elem: HTMLElement): Observable<AnimationState> {
304
- return this.vaporizeIntern(elem);
305
- }
306
-
307
- private vaporizeIntern(elem: HTMLElement): Observable<AnimationState> {
308
- elem.style.opacity = elem.style.opacity || '1';
309
- elem.style.transition = `opacity ${Math.floor(
310
- this.thanosOptions.animationLength * 0.8
311
- )}ms ease-out`;
312
- const noise = new SimplexNoise({ frequency: 0.01, min: 0 });
313
- const seed = new Date().getTime() * Math.random();
314
- const html2CanvasPromise: Promise<HTMLCanvasElement> = html2canvas(elem, {
315
- backgroundColor: null,
316
- scale: 1,
317
- allowTaint: true,
318
- windowHeight: window.innerHeight,
319
- windowWidth: window.innerWidth,
320
- scrollY: -window.scrollY,
321
- });
322
-
323
- return from(html2CanvasPromise).pipe(
324
- map((canvasFromHtmlElem) => {
325
- const canvasAndParticles = WsThanosService.prepareCanvasForVaporize(
326
- canvasFromHtmlElem,
327
- this.thanosOptions.maxParticleCount
328
- );
329
- const { resultCanvas } = canvasAndParticles;
330
- if (elem.parentElement) {
331
- elem.parentElement.style.position =
332
- elem.parentElement.style.position || 'relative';
333
- }
334
- resultCanvas.style.position = 'absolute';
335
- resultCanvas.style.left = 0 + 'px';
336
- resultCanvas.style.top =
337
- '-' + elem.getBoundingClientRect().height * (HEIGHT_SCALE - 1) + 'px';
338
- resultCanvas.style.zIndex = '2000';
339
- resultCanvas.style.pointerEvents = 'none';
340
-
341
- elem.insertAdjacentElement('beforebegin', resultCanvas);
342
- // this should start the transition above defined
343
- elem.style.opacity = '0';
344
- return canvasAndParticles;
345
- }),
346
- switchMap(({ resultCanvas, particlesData }) => {
347
- let time = 0;
348
- const { animationLength, maxParticleCount, particleAcceleration } =
349
- this.thanosOptions;
350
- return interval(1000 / 60, animationFrameScheduler).pipe(
351
- timeInterval(),
352
- tap((deltaT) => (time += deltaT.interval)),
353
- map(
354
- (deltaT) =>
355
- ({
356
- deltaTSec: deltaT.interval / 1000,
357
- animationT: time / animationLength,
358
- maxWidth: resultCanvas.width,
359
- maxHeight: resultCanvas.height,
360
- }) as AnimationState
361
- ),
362
- tap((animationState) => {
363
- WsThanosService.updateParticles({
364
- particlesData,
365
- animationState,
366
- thanosOptions: {
367
- animationLength,
368
- maxParticleCount,
369
- particleAcceleration,
370
- },
371
- noise,
372
- seed,
373
- });
374
- const context = resultCanvas.getContext('2d', {
375
- willReadFrequently: true,
376
- });
377
- if (context) {
378
- WsThanosService.drawParticles(context, particlesData.particles);
379
- }
380
- }),
381
- takeWhile((animationState) => animationState.animationT <= 1),
382
- finalize(() => resultCanvas.remove())
383
- );
384
- })
385
- );
386
- }
387
- }
package/src/test-setup.ts DELETED
@@ -1,9 +0,0 @@
1
- import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless';
2
-
3
- setupZonelessTestEnv();
4
-
5
- import { ReadableStream } from 'web-streams-polyfill/polyfill';
6
-
7
- if (typeof globalThis.ReadableStream === 'undefined') {
8
- globalThis.ReadableStream = ReadableStream as never;
9
- }
package/tsconfig.json DELETED
@@ -1,32 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.base.json",
3
- "files": [],
4
- "include": [],
5
- "references": [
6
- {
7
- "path": "./tsconfig.lib.json"
8
- },
9
- {
10
- "path": "./tsconfig.lib.prod.json"
11
- },
12
- {
13
- "path": "./tsconfig.spec.json"
14
- },
15
- {
16
- "path": "./tsconfig.spec.json"
17
- }
18
- ],
19
- "compilerOptions": {
20
- "forceConsistentCasingInFileNames": true,
21
- "strict": true,
22
- "noImplicitReturns": true,
23
- "noFallthroughCasesInSwitch": true,
24
- "target": "ES2022"
25
- },
26
- "angularCompilerOptions": {
27
- "enableIvy": true,
28
- "strictInjectionParameters": true,
29
- "strictInputAccessModifiers": true,
30
- "strictTemplates": true
31
- }
32
- }
package/tsconfig.lib.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "../../../dist/out-tsc",
5
- "target": "ES2022",
6
- "declaration": true,
7
- "declarationMap": true,
8
- "inlineSources": true,
9
- "types": [],
10
- "lib": ["dom", "es2018"],
11
- "moduleResolution": "bundler"
12
- },
13
- "exclude": ["src/test-setup.ts", "**/*.spec.ts", "**/*.test.ts"],
14
- "include": ["**/*.ts"]
15
- }
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "./tsconfig.lib.json",
3
- "compilerOptions": {
4
- "declarationMap": false,
5
- "moduleResolution": "bundler"
6
- },
7
- "angularCompilerOptions": {
8
- "compilationMode": "partial"
9
- }
10
- }
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "../../../dist/out-tsc",
5
- "types": ["jest", "node"],
6
- "module": "commonjs"
7
- },
8
- "files": ["src/test-setup.ts"],
9
- "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts"]
10
- }