@wolsok/thanos 16.0.0 → 17.0.15

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 (42) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +54 -2
  3. package/eslint.config.mjs +64 -0
  4. package/jest.config.ts +23 -0
  5. package/ng-package.json +8 -0
  6. package/package.json +10 -23
  7. package/project.json +40 -0
  8. package/src/assets/how-to-be-funny.png.ts +4 -0
  9. package/{index.d.ts → src/index.ts} +4 -0
  10. package/src/lib/animation.state.ts +6 -0
  11. package/src/lib/create-ws-thanos-options.ts +12 -0
  12. package/src/lib/random-util.ts +24 -0
  13. package/src/lib/simplex-noise-options.ts +9 -0
  14. package/src/lib/simplex-noise.ts +240 -0
  15. package/src/lib/ws-thanos-options.token.spec.ts +19 -0
  16. package/src/lib/ws-thanos-options.token.ts +20 -0
  17. package/src/lib/ws-thanos.directive.spec.ts +175 -0
  18. package/src/lib/ws-thanos.directive.ts +70 -0
  19. package/src/lib/ws-thanos.options.ts +5 -0
  20. package/src/lib/ws-thanos.service.spec.ts +9 -0
  21. package/src/lib/ws-thanos.service.ts +387 -0
  22. package/src/test-setup.ts +9 -0
  23. package/tsconfig.json +32 -0
  24. package/tsconfig.lib.json +15 -0
  25. package/tsconfig.lib.prod.json +10 -0
  26. package/tsconfig.spec.json +10 -0
  27. package/esm2022/index.mjs +0 -10
  28. package/esm2022/lib/create-ws-thanos-options.mjs +0 -9
  29. package/esm2022/lib/simplex-noise-options.mjs +0 -2
  30. package/esm2022/lib/simplex-noise.mjs +0 -194
  31. package/esm2022/lib/ws-thanos-options.token.mjs +0 -13
  32. package/esm2022/lib/ws-thanos.directive.mjs +0 -62
  33. package/esm2022/lib/ws-thanos.options.mjs +0 -2
  34. package/esm2022/lib/ws-thanos.service.mjs +0 -257
  35. package/esm2022/wolsok-thanos.mjs +0 -5
  36. package/lib/create-ws-thanos-options.d.ts +0 -2
  37. package/lib/simplex-noise-options.d.ts +0 -9
  38. package/lib/simplex-noise.d.ts +0 -18
  39. package/lib/ws-thanos-options.token.d.ts +0 -4
  40. package/lib/ws-thanos.directive.d.ts +0 -18
  41. package/lib/ws-thanos.options.d.ts +0 -5
  42. package/lib/ws-thanos.service.d.ts +0 -32
@@ -1,6 +1,10 @@
1
+ /*
2
+ * Public API Surface of ws-thanos
3
+ */
1
4
  export * from './lib/ws-thanos.service';
2
5
  export * from './lib/ws-thanos.directive';
3
6
  export * from './lib/ws-thanos-options.token';
4
7
  export * from './lib/ws-thanos.options';
5
8
  export * from './lib/create-ws-thanos-options';
6
9
  export * from './lib/simplex-noise';
10
+ export type { AnimationState } from './lib/animation.state';
@@ -0,0 +1,6 @@
1
+ export interface AnimationState {
2
+ deltaTSec: number;
3
+ animationT: number;
4
+ maxWidth: number;
5
+ maxHeight: number;
6
+ }
@@ -0,0 +1,12 @@
1
+ import { WsThanosOptions } from './ws-thanos.options';
2
+
3
+ export function createWsThanosOptions(
4
+ options?: Partial<WsThanosOptions>
5
+ ): WsThanosOptions {
6
+ return {
7
+ animationLength: 5000,
8
+ maxParticleCount: 400000,
9
+ particleAcceleration: 30,
10
+ ...options,
11
+ };
12
+ }
@@ -0,0 +1,24 @@
1
+ const epsilon = 0.0000000001;
2
+ const two_pi = 2.0 * Math.PI;
3
+ let generate = true;
4
+ let z1: number;
5
+
6
+ export function gaussian(mu: number, sigma: number): number {
7
+ if (!generate) {
8
+ generate = true;
9
+ return z1 * sigma + mu;
10
+ }
11
+
12
+ let u1 = 0;
13
+ let u2 = 0;
14
+ while (u1 <= epsilon) {
15
+ u1 = Math.random();
16
+ u2 = Math.random();
17
+ generate = false;
18
+ }
19
+
20
+ const sqrtLog = Math.sqrt(-2.0 * Math.log(u1));
21
+ const z0 = sqrtLog * Math.cos(two_pi * u2);
22
+ z1 = sqrtLog * Math.sin(two_pi * u2);
23
+ return z0 * sigma + mu;
24
+ }
@@ -0,0 +1,9 @@
1
+ export interface SimplexNoiseOptions {
2
+ amplitude: number; // The base amplitude (default: 1.0)
3
+ frequency: number; // The base frequency (default: 1.0)
4
+ max: number; // The maximum scaled value to return (effective default: 1.0)
5
+ min: number; // The minimum scaled value to return (effective default: -1.0)
6
+ octaves: number; // Integer; the number of octaves to sum for noise generation (default: 1)
7
+ persistence: number; // The persistence of amplitude per octave (default: 0.5)
8
+ random: () => number; // A function that generates random values between 0 and 1 (default: Math.random)
9
+ }
@@ -0,0 +1,240 @@
1
+ import type { SimplexNoiseOptions } from './simplex-noise-options';
2
+
3
+ export class SimplexNoise {
4
+ constructor(options?: Partial<SimplexNoiseOptions>) {
5
+ const { min, max, random, amplitude, frequency, octaves, persistence } =
6
+ createOptionsWithDefaults(options);
7
+
8
+ this.random = random;
9
+ this.amplitude = amplitude;
10
+ this.frequency = frequency;
11
+ this.octaves = octaves;
12
+ this.persistence = persistence;
13
+
14
+ this.scale =
15
+ min === -1 && max === 1
16
+ ? (value) => value
17
+ : (value) => min + ((value + 1) / 2) * (max - min);
18
+
19
+ const p = new Uint8Array(256);
20
+ for (let i = 0; i < 256; i++) {
21
+ p[i] = i;
22
+ }
23
+
24
+ let n: number;
25
+ let q: number;
26
+ for (let i = 255; i > 0; i--) {
27
+ n = Math.floor((i + 1) * this.random());
28
+ q = p[i];
29
+ p[i] = p[n];
30
+ p[n] = q;
31
+ }
32
+
33
+ this.perm = new Uint8Array(512);
34
+ this.permMod12 = new Uint8Array(512);
35
+ for (let i = 0; i < 512; i++) {
36
+ this.perm[i] = p[i & 255];
37
+ this.permMod12[i] = this.perm[i] % 12;
38
+ }
39
+ }
40
+
41
+ static G3 = 1.0 / 6.0;
42
+
43
+ static GRAD3D = [
44
+ [1, 1, 0],
45
+ [-1, 1, 0],
46
+ [1, -1, 0],
47
+ [-1, -1, 0],
48
+ [1, 0, 1],
49
+ [-1, 0, 1],
50
+ [1, 0, -1],
51
+ [-1, 0, -1],
52
+ [0, 1, 1],
53
+ [0, -1, -1],
54
+ [0, 1, -1],
55
+ [0, -1, -1],
56
+ ];
57
+
58
+ readonly amplitude: number;
59
+ readonly frequency: number;
60
+ readonly octaves: number;
61
+ readonly perm: Uint8Array;
62
+ readonly permMod12: Uint8Array;
63
+ readonly persistence: number;
64
+ readonly random: () => number;
65
+ readonly scale: (value: number) => number;
66
+ private memScale3D: { [key: string]: number } = {};
67
+
68
+ dot(gs: number[], coords: number[]): number {
69
+ return gs
70
+ .slice(0, Math.min(gs.length, coords.length))
71
+ .reduce((total, g, i) => total + g * coords[i], 0);
72
+ }
73
+
74
+ raw3D(x: number, y: number, z: number): number {
75
+ // Skew the input space to determine which simplex cell we're in
76
+ const s = (x + y + z) / 3.0; // Very nice and simple skew factor for 3D
77
+ const i = Math.floor(x + s);
78
+ const j = Math.floor(y + s);
79
+ const k = Math.floor(z + s);
80
+ const t = (i + j + k) * SimplexNoise.G3;
81
+ const X0 = i - t; // Unskew the cell origin back to (x,y,z) space
82
+ const Y0 = j - t;
83
+ const Z0 = k - t;
84
+ const x0 = x - X0; // The x,y,z distances from the cell origin
85
+ const y0 = y - Y0;
86
+ const z0 = z - Z0;
87
+
88
+ // Determine which simplex we are in
89
+ let i1: number, j1: number, k1: number; // Offsets for second corner of simplex in (i,j,k) coords
90
+ let i2: number, j2: number, k2: number; // Offsets for third corner of simplex in (i,j,k) coords
91
+ if (x0 >= y0) {
92
+ if (y0 >= z0) {
93
+ i1 = i2 = j2 = 1;
94
+ j1 = k1 = k2 = 0;
95
+ } else if (x0 >= z0) {
96
+ i1 = i2 = k2 = 1;
97
+ j1 = k1 = j2 = 0;
98
+ } else {
99
+ k1 = i2 = k2 = 1;
100
+ i1 = j1 = j2 = 0;
101
+ }
102
+ } else {
103
+ if (y0 < z0) {
104
+ k1 = j2 = k2 = 1;
105
+ i1 = j1 = i2 = 0;
106
+ } else if (x0 < z0) {
107
+ j1 = j2 = k2 = 1;
108
+ i1 = k1 = i2 = 0;
109
+ } else {
110
+ j1 = i2 = j2 = 1;
111
+ i1 = k1 = k2 = 0;
112
+ }
113
+ }
114
+
115
+ const x1 = x0 - i1 + SimplexNoise.G3; // Offsets for second corner in (x,y,z) coords
116
+ const y1 = y0 - j1 + SimplexNoise.G3;
117
+ const z1 = z0 - k1 + SimplexNoise.G3;
118
+ const x2 = x0 - i2 + 2.0 * SimplexNoise.G3; // Offsets for third corner in (x,y,z) coords
119
+ const y2 = y0 - j2 + 2.0 * SimplexNoise.G3;
120
+ const z2 = z0 - k2 + 2.0 * SimplexNoise.G3;
121
+ const x3 = x0 - 1.0 + 3.0 * SimplexNoise.G3; // Offsets for last corner in (x,y,z) coords
122
+ const y3 = y0 - 1.0 + 3.0 * SimplexNoise.G3;
123
+ const z3 = z0 - 1.0 + 3.0 * SimplexNoise.G3;
124
+
125
+ // Work out the hashed gradient indices of the four simplex corners
126
+ const ii = i & 255;
127
+ const jj = j & 255;
128
+ const kk = k & 255;
129
+ const gi0 = this.permMod12[ii + this.perm[jj + this.perm[kk]]];
130
+ const gi1 =
131
+ this.permMod12[ii + i1 + this.perm[jj + j1 + this.perm[kk + k1]]];
132
+ const gi2 =
133
+ this.permMod12[ii + i2 + this.perm[jj + j2 + this.perm[kk + k2]]];
134
+ const gi3 = this.permMod12[ii + 1 + this.perm[jj + 1 + this.perm[kk + 1]]];
135
+
136
+ // Calculate the contribution from the four corners
137
+ const t0 = 0.5 - x0 * x0 - y0 * y0 - z0 * z0;
138
+ const n0 =
139
+ t0 < 0
140
+ ? 0.0
141
+ : Math.pow(t0, 4) * this.dot(SimplexNoise.GRAD3D[gi0], [x0, y0, z0]);
142
+ const t1 = 0.5 - x1 * x1 - y1 * y1 - z1 * z1;
143
+ const n1 =
144
+ t1 < 0
145
+ ? 0.0
146
+ : Math.pow(t1, 4) * this.dot(SimplexNoise.GRAD3D[gi1], [x1, y1, z1]);
147
+ const t2 = 0.5 - x2 * x2 - y2 * y2 - z2 * z2;
148
+ const n2 =
149
+ t2 < 0
150
+ ? 0.0
151
+ : Math.pow(t2, 4) * this.dot(SimplexNoise.GRAD3D[gi2], [x2, y2, z2]);
152
+ const t3 = 0.5 - x3 * x3 - y3 * y3 - z3 * z3;
153
+ const n3 =
154
+ t3 < 0
155
+ ? 0.0
156
+ : Math.pow(t3, 4) * this.dot(SimplexNoise.GRAD3D[gi3], [x3, y3, z3]);
157
+
158
+ // Add contributions from each corner to get the final noise value.
159
+ // The result is scaled to stay just inside [-1,1]
160
+ return 94.68493150681972 * (n0 + n1 + n2 + n3);
161
+ }
162
+
163
+ scaled3D(x: number, y: number, z: number, resolution: number): number {
164
+ const memIndex = `${Math.round(x * resolution)}${Math.round(
165
+ y * resolution
166
+ )}${Math.round(z * resolution)}`;
167
+ const memElem = this.memScale3D[memIndex];
168
+ if (memElem != null) {
169
+ return memElem;
170
+ }
171
+
172
+ let amplitude = this.amplitude;
173
+ let frequency = this.frequency;
174
+ let maxAmplitude = 0;
175
+ let noise = 0;
176
+
177
+ for (let i = 0; i < this.octaves; i++) {
178
+ noise +=
179
+ this.raw3D(x * frequency, y * frequency, z * frequency) * amplitude;
180
+ maxAmplitude += amplitude;
181
+ amplitude *= this.persistence;
182
+ frequency *= 2;
183
+ }
184
+ const result = this.scale(noise / maxAmplitude);
185
+ this.memScale3D[memIndex] = result;
186
+ return result;
187
+ }
188
+ }
189
+
190
+ function createOptionsWithDefaults(options?: Partial<SimplexNoiseOptions>) {
191
+ if (options != null) {
192
+ if (options['amplitude'] && typeof options.amplitude !== 'number') {
193
+ throw new Error('options.amplitude must be a number');
194
+ }
195
+
196
+ if (options['frequency'] && typeof options.frequency !== 'number') {
197
+ throw new Error('options.frequency must be a number');
198
+ }
199
+
200
+ if (
201
+ options['octaves'] &&
202
+ (typeof options.octaves !== 'number' ||
203
+ !isFinite(options.octaves) ||
204
+ Math.floor(options.octaves) !== options.octaves)
205
+ ) {
206
+ throw new Error('options.octaves must be an integer');
207
+ }
208
+
209
+ if (options['persistence'] && typeof options.persistence !== 'number') {
210
+ throw new Error('options.persistence must be a number');
211
+ }
212
+
213
+ if (options['random'] && typeof options.random !== 'function') {
214
+ throw new Error('options.random must be a function');
215
+ }
216
+
217
+ if (options['min'] && typeof options.min !== 'number') {
218
+ throw new Error('options.min must be a number');
219
+ }
220
+
221
+ if (options['max'] && typeof options.max !== 'number') {
222
+ throw new Error('options.max must be a number');
223
+ }
224
+ }
225
+
226
+ const optionsResult = {
227
+ amplitude: 1.0,
228
+ frequency: 1.0,
229
+ max: 1.0,
230
+ min: -1.0,
231
+ octaves: 1,
232
+ persistence: 0.5,
233
+ random: Math.random,
234
+ ...options,
235
+ };
236
+ if (optionsResult.min > optionsResult.max) {
237
+ throw new Error('min must be smaller max');
238
+ }
239
+ return optionsResult;
240
+ }
@@ -0,0 +1,19 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+ import {
3
+ provideWsThanosOptions,
4
+ WS_THANOS_OPTIONS_TOKEN,
5
+ } from './ws-thanos-options.token';
6
+
7
+ describe('WsThanosOptions', () => {
8
+ beforeEach(async () => {
9
+ await TestBed.configureTestingModule({
10
+ providers: [provideWsThanosOptions({ maxParticleCount: 12345 })],
11
+ });
12
+ });
13
+
14
+ it('should override default options', () => {
15
+ expect(TestBed.inject(WS_THANOS_OPTIONS_TOKEN).maxParticleCount).toEqual(
16
+ 12345
17
+ );
18
+ });
19
+ });
@@ -0,0 +1,20 @@
1
+ import { InjectionToken, Provider } from '@angular/core';
2
+ import { createWsThanosOptions } from './create-ws-thanos-options';
3
+ import type { WsThanosOptions } from './ws-thanos.options';
4
+
5
+ export const WS_THANOS_OPTIONS_TOKEN = new InjectionToken<WsThanosOptions>(
6
+ 'thanosOptions',
7
+ {
8
+ providedIn: 'root',
9
+ factory: () => createWsThanosOptions(),
10
+ }
11
+ );
12
+
13
+ export function provideWsThanosOptions(
14
+ options?: Partial<WsThanosOptions>
15
+ ): Provider {
16
+ return {
17
+ provide: WS_THANOS_OPTIONS_TOKEN,
18
+ useValue: createWsThanosOptions(options),
19
+ };
20
+ }
@@ -0,0 +1,175 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ QueryList,
5
+ ViewChildren,
6
+ } from '@angular/core';
7
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
8
+ import { combineLatest, firstValueFrom, of } from 'rxjs';
9
+ import image from '../assets/how-to-be-funny.png';
10
+ import { AnimationState } from './animation.state';
11
+ import { provideWsThanosOptions } from './ws-thanos-options.token';
12
+ import { WsThanosDirective } from './ws-thanos.directive';
13
+ import { WsThanosService } from './ws-thanos.service';
14
+
15
+ describe('Integration Test: WsThanosDirective', () => {
16
+ @Component({
17
+ template: `
18
+ <div
19
+ class="thanos-test-container"
20
+ wsThanos
21
+ (wsThanosComplete)="completed()"
22
+ >
23
+ <h1>My content for the div</h1>
24
+ <img alt="funny-face" style="height: 400px" src="${image}" />
25
+ </div>
26
+ <div class="grid-test">
27
+ <ws-thanos class="div-without-remove" #myThanos="thanos">
28
+ This div should be vaporized when clicked on button and then become
29
+ visible again!
30
+ <div class="inner">Inner container</div>
31
+ </ws-thanos>
32
+ <button (click)="myThanos.vaporize(false)">Vaporize div above</button>
33
+ </div>
34
+ `,
35
+ styles: [
36
+ `
37
+ * {
38
+ box-sizing: border-box;
39
+ }
40
+
41
+ .div-without-remove {
42
+ display: grid;
43
+ grid-template-rows: 1fr 1fr 1fr;
44
+ grid-template-columns: 1fr 1fr 1fr;
45
+ place-content: center;
46
+ padding: 50px;
47
+ border: 10px solid darkorange;
48
+ border-radius: 30px;
49
+ height: 300px;
50
+ background-image: linear-gradient(to right, #cbe7e1, #00ae85);
51
+ }
52
+
53
+ .inner {
54
+ border-radius: 50%;
55
+ grid-column: 2;
56
+ grid-row: 1;
57
+ background-image: linear-gradient(to right, azure, darkorchid);
58
+ text-align: center;
59
+ padding-top: 1rem;
60
+ }
61
+
62
+ .grid-test {
63
+ display: grid;
64
+ grid-template-columns: 1fr;
65
+ grid-template-rows: 1fr auto;
66
+ }
67
+
68
+ .thanos-test-container {
69
+ padding: 14px;
70
+ }
71
+ `,
72
+ ],
73
+ imports: [WsThanosDirective],
74
+ changeDetection: ChangeDetectionStrategy.OnPush,
75
+ })
76
+ class HostComponent {
77
+ wsThanosDirective!: WsThanosDirective;
78
+
79
+ @ViewChildren(WsThanosDirective)
80
+ set thanos(thanos: QueryList<WsThanosDirective>) {
81
+ this.wsThanosDirective = thanos.first;
82
+ }
83
+
84
+ startThanos() {
85
+ return this.wsThanosDirective.vaporize().subscribe();
86
+ }
87
+
88
+ completed() {
89
+ console.log('Completed');
90
+ }
91
+ }
92
+
93
+ let directive: WsThanosDirective;
94
+ let hostFixture: ComponentFixture<HostComponent>;
95
+ let hostComp: HostComponent;
96
+
97
+ beforeEach(async () => {
98
+ await TestBed.configureTestingModule({
99
+ imports: [WsThanosDirective, HostComponent],
100
+ providers: [
101
+ provideWsThanosOptions({
102
+ maxParticleCount: 500,
103
+ animationLength: 1000,
104
+ }),
105
+ ],
106
+ }).compileComponents();
107
+ });
108
+
109
+ beforeEach(() => {
110
+ hostFixture = TestBed.createComponent(HostComponent);
111
+ hostComp = hostFixture.componentInstance;
112
+ hostFixture.detectChanges();
113
+ directive = hostComp.wsThanosDirective;
114
+ });
115
+
116
+ it('should create', () => {
117
+ expect(directive).toBeTruthy();
118
+ });
119
+
120
+ describe('vaporize()', () => {
121
+ it('should call thanosService.vaporize', () => {
122
+ givenStubbedThanosService_VaporizeReturns();
123
+ whenVaporizeIsCalled();
124
+ thenThanosServiceVaporizeWasCalled();
125
+ });
126
+
127
+ it('should emit animationState', (done) => {
128
+ const animationState: AnimationState = {
129
+ animationT: 0,
130
+ deltaTSec: 0,
131
+ maxHeight: 0,
132
+ maxWidth: 0,
133
+ };
134
+ givenStubbedThanosService_VaporizeReturns(of(animationState));
135
+ directive.vaporize$(false).subscribe({
136
+ next: (result) => {
137
+ expect(result).toEqual(animationState);
138
+ done();
139
+ },
140
+ });
141
+ });
142
+
143
+ // SKIPPED: This test requires real browser features (html2canvas, canvas rendering, getComputedStyle)
144
+ // that are not properly simulated in jsdom. See E2E tests in apps/ws-thanos-test-app instead.
145
+ it.skip('should emit complete when vaporize is complete', (done) => {
146
+ combineLatest([
147
+ firstValueFrom(directive.wsThanosComplete),
148
+ directive.vaporize$(),
149
+ ]).subscribe({
150
+ complete: done,
151
+ });
152
+ }, 10000);
153
+ });
154
+
155
+ function givenStubbedThanosService_VaporizeReturns(
156
+ vaporizeReturns: ReturnType<WsThanosService['vaporize']> = of(
157
+ {} as AnimationState
158
+ )
159
+ ): void {
160
+ const thanosService = getWsThanosService();
161
+ jest.spyOn(thanosService, 'vaporize').mockReturnValue(vaporizeReturns);
162
+ }
163
+
164
+ function getWsThanosService(): WsThanosService {
165
+ return TestBed.inject(WsThanosService);
166
+ }
167
+
168
+ function thenThanosServiceVaporizeWasCalled() {
169
+ expect(getWsThanosService().vaporize).toHaveBeenCalled();
170
+ }
171
+
172
+ function whenVaporizeIsCalled(): void {
173
+ directive.vaporize$(false).subscribe();
174
+ }
175
+ });
@@ -0,0 +1,70 @@
1
+ import { Directive, ElementRef, Output, inject } from '@angular/core';
2
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
3
+ import { finalize, Observable, Subject, Subscription, take, tap } from 'rxjs';
4
+ import { AnimationState } from './animation.state';
5
+ import { WS_THANOS_OPTIONS_TOKEN } from './ws-thanos-options.token';
6
+ import type { WsThanosOptions } from './ws-thanos.options';
7
+ import { WsThanosService } from './ws-thanos.service';
8
+
9
+ @Directive({
10
+ selector: '[wsThanos], ws-thanos',
11
+ exportAs: 'thanos',
12
+ })
13
+ export class WsThanosDirective {
14
+ private wsThanosCompleteSubject = new Subject<void>();
15
+ private untilDestroyed = takeUntilDestroyed();
16
+
17
+ @Output()
18
+ public wsThanosComplete: Observable<void> =
19
+ this.wsThanosCompleteSubject.asObservable();
20
+ private subscriptions: Subscription = new Subscription();
21
+
22
+ private readonly vaporizeDomElem: ElementRef<HTMLElement> =
23
+ inject(ElementRef);
24
+ private readonly thanosService: WsThanosService = inject(WsThanosService);
25
+ private readonly thanosOptions: WsThanosOptions = inject(
26
+ WS_THANOS_OPTIONS_TOKEN
27
+ );
28
+
29
+ /**
30
+ *
31
+ * Vaporize the dom element of this directive
32
+ * @param removeElem remove the vaporized dom element or blend back in after a while
33
+ *
34
+ * Subscribe to the returned observable to start vaporization
35
+ *
36
+ */
37
+ public vaporize$(removeElem = true): Observable<AnimationState> {
38
+ const elem = this.vaporizeDomElem.nativeElement;
39
+ return this.thanosService.vaporize(elem).pipe(
40
+ tap({
41
+ error: (error) => {
42
+ console.error('Error vaporizing', error, {
43
+ elemTovaporize: elem,
44
+ removeElem,
45
+ });
46
+ },
47
+ }),
48
+ finalize(() => {
49
+ if (removeElem) {
50
+ elem.remove();
51
+ } else {
52
+ // make visible again
53
+ elem.style.transition = 'opacity 700ms';
54
+ elem.style.opacity = '1';
55
+ }
56
+ this.wsThanosCompleteSubject.next();
57
+ })
58
+ );
59
+ }
60
+
61
+ /**
62
+ * Vaporize the dom element of this directive
63
+ * @param removeElem remove the vaporized dom element or blend back in after a while
64
+ *
65
+ */
66
+ public vaporize(removeElem = true): Observable<void> {
67
+ this.vaporize$(removeElem).pipe(this.untilDestroyed).subscribe();
68
+ return this.wsThanosComplete.pipe(take(1));
69
+ }
70
+ }
@@ -0,0 +1,5 @@
1
+ export interface WsThanosOptions {
2
+ animationLength: number;
3
+ maxParticleCount: number;
4
+ particleAcceleration: number;
5
+ }
@@ -0,0 +1,9 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { WsThanosService } from './ws-thanos.service';
3
+
4
+ describe('WsThanosService', () => {
5
+ it('should be created', () => {
6
+ const service: WsThanosService = TestBed.inject(WsThanosService);
7
+ expect(service).toBeTruthy();
8
+ });
9
+ });