@ume-group/contracts 0.2.1
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/README.md +37 -0
- package/dist/adserving.d.ts +150 -0
- package/dist/adserving.d.ts.map +1 -0
- package/dist/adserving.js +8 -0
- package/dist/campaigns.d.ts +37 -0
- package/dist/campaigns.d.ts.map +1 -0
- package/dist/campaigns.js +8 -0
- package/dist/gausst.d.ts +236 -0
- package/dist/gausst.d.ts.map +1 -0
- package/dist/gausst.js +307 -0
- package/dist/gausst.test.d.ts +2 -0
- package/dist/gausst.test.d.ts.map +1 -0
- package/dist/gausst.test.js +71 -0
- package/dist/index.d.ts +1531 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1112 -0
- package/dist/layer2/index.d.ts +9 -0
- package/dist/layer2/index.d.ts.map +1 -0
- package/dist/layer2/index.js +10 -0
- package/dist/layer2/shaders.d.ts +185 -0
- package/dist/layer2/shaders.d.ts.map +1 -0
- package/dist/layer2/shaders.js +604 -0
- package/dist/layer2/webcam-utils.d.ts +113 -0
- package/dist/layer2/webcam-utils.d.ts.map +1 -0
- package/dist/layer2/webcam-utils.js +147 -0
- package/dist/layer2/webcam-utils.test.d.ts +2 -0
- package/dist/layer2/webcam-utils.test.d.ts.map +1 -0
- package/dist/layer2/webcam-utils.test.js +18 -0
- package/dist/layer2.d.ts +558 -0
- package/dist/layer2.d.ts.map +1 -0
- package/dist/layer2.js +376 -0
- package/dist/layer2.test.d.ts +2 -0
- package/dist/layer2.test.d.ts.map +1 -0
- package/dist/layer2.test.js +65 -0
- package/dist/perspective.d.ts +28 -0
- package/dist/perspective.d.ts.map +1 -0
- package/dist/perspective.js +157 -0
- package/dist/segmentation/MediaPipeSegmenter.d.ts +201 -0
- package/dist/segmentation/MediaPipeSegmenter.d.ts.map +1 -0
- package/dist/segmentation/MediaPipeSegmenter.js +434 -0
- package/dist/segmentation/index.d.ts +5 -0
- package/dist/segmentation/index.d.ts.map +1 -0
- package/dist/segmentation/index.js +4 -0
- package/dist/webcam/GarbageMatteDragManager.d.ts +63 -0
- package/dist/webcam/GarbageMatteDragManager.d.ts.map +1 -0
- package/dist/webcam/GarbageMatteDragManager.js +183 -0
- package/dist/webcam/WebcamStreamManager.d.ts +103 -0
- package/dist/webcam/WebcamStreamManager.d.ts.map +1 -0
- package/dist/webcam/WebcamStreamManager.js +356 -0
- package/dist/webcam/index.d.ts +5 -0
- package/dist/webcam/index.d.ts.map +1 -0
- package/dist/webcam/index.js +2 -0
- package/openapi/admetise.yaml +632 -0
- package/openapi/includu.yaml +621 -0
- package/openapi/integration.yaml +372 -0
- package/openapi/shared/schemas.yaml +227 -0
- package/package.json +53 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebcamStreamManager — Framework-agnostic webcam stream management.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from editor/player WebcamCapture.svelte to eliminate code duplication.
|
|
5
|
+
* Both Svelte components become thin wrappers around this class.
|
|
6
|
+
*
|
|
7
|
+
* Handles:
|
|
8
|
+
* - Device enumeration (video + audio, with Windows role-prefix filtering)
|
|
9
|
+
* - getUserMedia with OverconstrainedError fallback
|
|
10
|
+
* - Orientation-aware aspect ratio (always landscape for 3D viewport)
|
|
11
|
+
* - Track death auto-restart (Android Chrome rotation)
|
|
12
|
+
* - devicechange listener for hot-plug
|
|
13
|
+
*/
|
|
14
|
+
/** Video constraints for webcam capture */
|
|
15
|
+
const VIDEO_CONSTRAINTS_BASE = {
|
|
16
|
+
width: { ideal: 1280, max: 1920 },
|
|
17
|
+
height: { ideal: 720, max: 1080 },
|
|
18
|
+
frameRate: { ideal: 30, max: 60 }
|
|
19
|
+
};
|
|
20
|
+
/** Windows audio endpoint role prefixes (duplicates from same physical device) */
|
|
21
|
+
const WINDOWS_ROLE_PREFIXES = /^(Standard|Default|Kommunikasjon|Communications)\s*-\s*/i;
|
|
22
|
+
/**
|
|
23
|
+
* Deduplicate Windows devices by cleaned label.
|
|
24
|
+
* Windows lists each physical device multiple times with role prefixes
|
|
25
|
+
* (e.g., "Standard - Bose", "Communications - Bose") and different deviceIds
|
|
26
|
+
* per role. Some Bluetooth devices ONLY appear with a prefix.
|
|
27
|
+
* Strip prefixes, deduplicate by cleaned label, keep the first entry.
|
|
28
|
+
*/
|
|
29
|
+
function deduplicateDevices(devices) {
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
const result = [];
|
|
32
|
+
for (const d of devices) {
|
|
33
|
+
const cleanLabel = d.label.replace(WINDOWS_ROLE_PREFIXES, '');
|
|
34
|
+
if (seen.has(cleanLabel))
|
|
35
|
+
continue;
|
|
36
|
+
seen.add(cleanLabel);
|
|
37
|
+
if (cleanLabel === d.label) {
|
|
38
|
+
result.push(d);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// MediaDeviceInfo is read-only — wrap with clean label
|
|
42
|
+
result.push(Object.defineProperty(Object.create(d, {
|
|
43
|
+
deviceId: { value: d.deviceId, enumerable: true },
|
|
44
|
+
groupId: { value: d.groupId, enumerable: true },
|
|
45
|
+
kind: { value: d.kind, enumerable: true },
|
|
46
|
+
toJSON: { value: () => d.toJSON() },
|
|
47
|
+
}), 'label', { value: cleanLabel, enumerable: true }));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
export class WebcamStreamManager {
|
|
53
|
+
callbacks;
|
|
54
|
+
videoElement = null;
|
|
55
|
+
currentStream = null;
|
|
56
|
+
isLoading = false;
|
|
57
|
+
videoReadyNotified = false;
|
|
58
|
+
nativeVideoWidth = 0;
|
|
59
|
+
nativeVideoHeight = 0;
|
|
60
|
+
isAutoRestarting = false;
|
|
61
|
+
isDestroyed = false;
|
|
62
|
+
// Bound listener references for cleanup
|
|
63
|
+
boundDeviceChange;
|
|
64
|
+
boundOrientationChange;
|
|
65
|
+
constructor(callbacks = {}) {
|
|
66
|
+
this.callbacks = callbacks;
|
|
67
|
+
this.boundDeviceChange = () => this.enumerateDevices();
|
|
68
|
+
this.boundOrientationChange = () => this.handleOrientationChange();
|
|
69
|
+
}
|
|
70
|
+
/** Check if webcam API is available (requires secure context) */
|
|
71
|
+
isWebcamApiAvailable() {
|
|
72
|
+
return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
|
|
73
|
+
}
|
|
74
|
+
/** Get current device orientation */
|
|
75
|
+
getCurrentOrientation() {
|
|
76
|
+
if (screen.orientation) {
|
|
77
|
+
return screen.orientation.type.startsWith('portrait') ? 'portrait' : 'landscape';
|
|
78
|
+
}
|
|
79
|
+
return window.innerHeight > window.innerWidth ? 'portrait' : 'landscape';
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Calculate effective aspect ratio for the webcam plane.
|
|
83
|
+
* Always returns landscape (>= 1.0) — the 3D viewport maintains
|
|
84
|
+
* the video content's aspect ratio regardless of device orientation.
|
|
85
|
+
*/
|
|
86
|
+
getEffectiveAspectRatio() {
|
|
87
|
+
if (this.nativeVideoWidth === 0 || this.nativeVideoHeight === 0) {
|
|
88
|
+
return 16 / 9;
|
|
89
|
+
}
|
|
90
|
+
return Math.max(this.nativeVideoWidth, this.nativeVideoHeight) /
|
|
91
|
+
Math.min(this.nativeVideoWidth, this.nativeVideoHeight);
|
|
92
|
+
}
|
|
93
|
+
/** Handle orientation change — recalculate and report new effective aspect ratio */
|
|
94
|
+
handleOrientationChange() {
|
|
95
|
+
if (this.currentStream && this.videoElement && this.nativeVideoWidth > 0) {
|
|
96
|
+
const effectiveAspect = this.getEffectiveAspectRatio();
|
|
97
|
+
const effectiveWidth = Math.max(this.nativeVideoWidth, this.nativeVideoHeight);
|
|
98
|
+
const effectiveHeight = Math.min(this.nativeVideoWidth, this.nativeVideoHeight);
|
|
99
|
+
this.callbacks.onDimensionsReady?.(effectiveWidth, effectiveHeight, effectiveAspect);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Wait for video element to be ready for playback.
|
|
104
|
+
* Resolves immediately if already ready, otherwise waits for canplay event.
|
|
105
|
+
*/
|
|
106
|
+
waitForVideoReady(video) {
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
const onCanPlay = () => {
|
|
109
|
+
video.removeEventListener('canplay', onCanPlay);
|
|
110
|
+
video.removeEventListener('error', onErr);
|
|
111
|
+
resolve();
|
|
112
|
+
};
|
|
113
|
+
const onErr = () => {
|
|
114
|
+
video.removeEventListener('canplay', onCanPlay);
|
|
115
|
+
video.removeEventListener('error', onErr);
|
|
116
|
+
reject(new Error('Video element failed to load'));
|
|
117
|
+
};
|
|
118
|
+
if (video.readyState >= 3) {
|
|
119
|
+
resolve();
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
video.addEventListener('canplay', onCanPlay);
|
|
123
|
+
video.addEventListener('error', onErr);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Attach a stream to the video element, wait for readiness, and notify callbacks.
|
|
129
|
+
* Shared by the primary path and the OverconstrainedError fallback.
|
|
130
|
+
*/
|
|
131
|
+
async attachStream(stream, deviceId) {
|
|
132
|
+
this.currentStream = stream;
|
|
133
|
+
// Monitor tracks for unexpected death (Android Chrome kills tracks on rotation)
|
|
134
|
+
stream.getTracks().forEach((track) => {
|
|
135
|
+
track.addEventListener('ended', () => {
|
|
136
|
+
if (!this.isDestroyed && !this.isAutoRestarting && !this.isLoading) {
|
|
137
|
+
this.isAutoRestarting = true;
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
this.isAutoRestarting = false;
|
|
140
|
+
if (!this.isDestroyed) {
|
|
141
|
+
this.startStream(deviceId);
|
|
142
|
+
}
|
|
143
|
+
}, 500);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
if (this.videoElement) {
|
|
148
|
+
this.videoElement.srcObject = stream;
|
|
149
|
+
await this.waitForVideoReady(this.videoElement);
|
|
150
|
+
await this.videoElement.play();
|
|
151
|
+
this.notifyVideoReady(this.videoElement);
|
|
152
|
+
}
|
|
153
|
+
this.callbacks.onStreamStart?.(stream);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* After stream is attached and video is playing, extract dimensions
|
|
157
|
+
* and notify callbacks.
|
|
158
|
+
*/
|
|
159
|
+
notifyVideoReady(video) {
|
|
160
|
+
this.nativeVideoWidth = video.videoWidth;
|
|
161
|
+
this.nativeVideoHeight = video.videoHeight;
|
|
162
|
+
this.getCurrentOrientation(); // capture initial orientation
|
|
163
|
+
const effectiveAspect = this.getEffectiveAspectRatio();
|
|
164
|
+
if (!this.videoReadyNotified) {
|
|
165
|
+
this.videoReadyNotified = true;
|
|
166
|
+
this.callbacks.onVideoReady?.(video);
|
|
167
|
+
const effectiveWidth = Math.max(this.nativeVideoWidth, this.nativeVideoHeight);
|
|
168
|
+
const effectiveHeight = Math.min(this.nativeVideoWidth, this.nativeVideoHeight);
|
|
169
|
+
this.callbacks.onDimensionsReady?.(effectiveWidth, effectiveHeight, effectiveAspect);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Enumerate available video and audio devices.
|
|
174
|
+
*
|
|
175
|
+
* Never calls getUserMedia({ audio: true }) — that triggers Windows
|
|
176
|
+
* communication mode. Chrome grants labeled audio devices when camera
|
|
177
|
+
* permission alone is given.
|
|
178
|
+
*/
|
|
179
|
+
async enumerateDevices() {
|
|
180
|
+
if (!this.isWebcamApiAvailable()) {
|
|
181
|
+
const message = 'Webcam requires HTTPS. Please use HTTPS or localhost.';
|
|
182
|
+
console.warn('[WebcamStreamManager]', message);
|
|
183
|
+
this.callbacks.onError?.(message);
|
|
184
|
+
this.callbacks.onDevicesEnumerated?.([]);
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
let devices = await navigator.mediaDevices.enumerateDevices();
|
|
189
|
+
const hasLabels = devices.some((d) => d.kind === 'videoinput' && d.label);
|
|
190
|
+
if (!hasLabels) {
|
|
191
|
+
const permissionStream = await navigator.mediaDevices.getUserMedia({ video: true });
|
|
192
|
+
permissionStream.getTracks().forEach((track) => track.stop());
|
|
193
|
+
devices = await navigator.mediaDevices.enumerateDevices();
|
|
194
|
+
}
|
|
195
|
+
const videoDevices = deduplicateDevices(devices.filter((d) => d.kind === 'videoinput'));
|
|
196
|
+
const audioDevices = deduplicateDevices(devices.filter((d) => d.kind === 'audioinput'));
|
|
197
|
+
this.callbacks.onDevicesEnumerated?.(videoDevices);
|
|
198
|
+
this.callbacks.onAudioDevicesEnumerated?.(audioDevices);
|
|
199
|
+
return videoDevices;
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
const message = err instanceof Error ? err.message : 'Failed to enumerate devices';
|
|
203
|
+
console.error('Failed to enumerate webcam devices:', err);
|
|
204
|
+
this.callbacks.onError?.(message);
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Start the webcam stream.
|
|
210
|
+
* @param deviceId - Specific camera device ID, or undefined for default front camera
|
|
211
|
+
*/
|
|
212
|
+
async startStream(deviceId) {
|
|
213
|
+
if (this.isLoading || this.isDestroyed)
|
|
214
|
+
return;
|
|
215
|
+
if (!this.videoElement)
|
|
216
|
+
return;
|
|
217
|
+
if (!this.isWebcamApiAvailable()) {
|
|
218
|
+
const message = 'Webcam requires HTTPS. Please use HTTPS or localhost.';
|
|
219
|
+
this.callbacks.onError?.(message);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
this.isLoading = true;
|
|
223
|
+
try {
|
|
224
|
+
// Stop any existing stream (silent = don't trigger callbacks during restart)
|
|
225
|
+
this.stopStream(true);
|
|
226
|
+
// Build constraints — don't combine facingMode with deviceId (OverconstrainedError)
|
|
227
|
+
const videoConstraints = deviceId
|
|
228
|
+
? { ...VIDEO_CONSTRAINTS_BASE, deviceId: { exact: deviceId } }
|
|
229
|
+
: { ...VIDEO_CONSTRAINTS_BASE, facingMode: 'user' };
|
|
230
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
231
|
+
video: videoConstraints,
|
|
232
|
+
audio: false
|
|
233
|
+
});
|
|
234
|
+
if (this.isDestroyed) {
|
|
235
|
+
stream.getTracks().forEach((t) => t.stop());
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
await this.attachStream(stream, deviceId);
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
// OverconstrainedError fallback — retry with default camera
|
|
242
|
+
if (deviceId && err instanceof DOMException && err.name === 'OverconstrainedError') {
|
|
243
|
+
console.warn('Requested deviceId not available, falling back to default camera');
|
|
244
|
+
try {
|
|
245
|
+
this.stopStream(true);
|
|
246
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
247
|
+
video: { ...VIDEO_CONSTRAINTS_BASE, facingMode: 'user' },
|
|
248
|
+
audio: false
|
|
249
|
+
});
|
|
250
|
+
if (this.isDestroyed) {
|
|
251
|
+
stream.getTracks().forEach((t) => t.stop());
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
await this.attachStream(stream);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
catch (fallbackErr) {
|
|
258
|
+
console.error('Fallback camera also failed:', fallbackErr);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const message = this.getErrorMessage(err);
|
|
262
|
+
console.error('Failed to start webcam:', err);
|
|
263
|
+
this.callbacks.onError?.(message);
|
|
264
|
+
}
|
|
265
|
+
finally {
|
|
266
|
+
this.isLoading = false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/** Map DOMException names to user-friendly error messages */
|
|
270
|
+
getErrorMessage(err) {
|
|
271
|
+
if (err instanceof DOMException) {
|
|
272
|
+
switch (err.name) {
|
|
273
|
+
case 'NotAllowedError':
|
|
274
|
+
return 'Camera permission denied. Please allow camera access.';
|
|
275
|
+
case 'NotFoundError':
|
|
276
|
+
return 'No camera found. Please connect a webcam.';
|
|
277
|
+
case 'NotReadableError':
|
|
278
|
+
return 'Camera is in use. Close the webcam in any other Includu tabs or applications, then try again.';
|
|
279
|
+
case 'OverconstrainedError':
|
|
280
|
+
return 'Camera does not support the requested settings.';
|
|
281
|
+
default:
|
|
282
|
+
return `Camera error: ${err.message}`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (err instanceof Error)
|
|
286
|
+
return err.message;
|
|
287
|
+
return 'Failed to start webcam';
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Stop the webcam stream.
|
|
291
|
+
* @param silent - If true, don't call onStreamStop callback (used during restart)
|
|
292
|
+
*/
|
|
293
|
+
stopStream(silent = false) {
|
|
294
|
+
if (!this.currentStream)
|
|
295
|
+
return;
|
|
296
|
+
this.currentStream.getTracks().forEach((track) => track.stop());
|
|
297
|
+
this.currentStream = null;
|
|
298
|
+
this.videoReadyNotified = false;
|
|
299
|
+
this.nativeVideoWidth = 0;
|
|
300
|
+
this.nativeVideoHeight = 0;
|
|
301
|
+
if (this.videoElement) {
|
|
302
|
+
this.videoElement.srcObject = null;
|
|
303
|
+
}
|
|
304
|
+
if (!silent) {
|
|
305
|
+
this.callbacks.onStreamStop?.();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/** Whether a stream is currently active */
|
|
309
|
+
get hasStream() {
|
|
310
|
+
return this.currentStream !== null;
|
|
311
|
+
}
|
|
312
|
+
/** Whether the manager is currently starting a stream */
|
|
313
|
+
get loading() {
|
|
314
|
+
return this.isLoading;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Bind to a video element. Call this once after the element is created.
|
|
318
|
+
* The video element is used as the srcObject target for the stream.
|
|
319
|
+
*/
|
|
320
|
+
setVideoElement(el) {
|
|
321
|
+
this.videoElement = el;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Initialize event listeners. Call on mount.
|
|
325
|
+
* Enumerates devices and starts listening for device/orientation changes.
|
|
326
|
+
*/
|
|
327
|
+
init() {
|
|
328
|
+
this.enumerateDevices();
|
|
329
|
+
navigator.mediaDevices.addEventListener('devicechange', this.boundDeviceChange);
|
|
330
|
+
if (screen.orientation) {
|
|
331
|
+
screen.orientation.addEventListener('change', this.boundOrientationChange);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
window.addEventListener('orientationchange', this.boundOrientationChange);
|
|
335
|
+
}
|
|
336
|
+
window.addEventListener('resize', this.boundOrientationChange);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Clean up all resources. Call on unmount.
|
|
340
|
+
* @param persistStream - If true, don't stop the stream (parent will manage it)
|
|
341
|
+
*/
|
|
342
|
+
destroy(persistStream = false) {
|
|
343
|
+
this.isDestroyed = true;
|
|
344
|
+
if (!persistStream) {
|
|
345
|
+
this.stopStream();
|
|
346
|
+
}
|
|
347
|
+
navigator.mediaDevices.removeEventListener('devicechange', this.boundDeviceChange);
|
|
348
|
+
if (screen.orientation) {
|
|
349
|
+
screen.orientation.removeEventListener('change', this.boundOrientationChange);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
window.removeEventListener('orientationchange', this.boundOrientationChange);
|
|
353
|
+
}
|
|
354
|
+
window.removeEventListener('resize', this.boundOrientationChange);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { WebcamStreamManager } from './WebcamStreamManager.js';
|
|
2
|
+
export type { WebcamStreamCallbacks } from './WebcamStreamManager.js';
|
|
3
|
+
export { GarbageMatteDragManager } from './GarbageMatteDragManager.js';
|
|
4
|
+
export type { DragHandle, GarbageMatteDragCallbacks, GarbageMatteDragConfig } from './GarbageMatteDragManager.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webcam/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,YAAY,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,YAAY,EAAE,UAAU,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC"}
|