@statewalker/webrun-files-browser 0.5.0 → 0.7.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.
@@ -1,20 +1,23 @@
1
1
  /**
2
- * Browser implementation of IFilesApi using the File System Access API.
2
+ * Browser implementation of FilesApi using the File System Access API.
3
3
  *
4
4
  * Provides a filesystem-like interface over browser's FileSystemDirectoryHandle.
5
5
  * Works with directories obtained via showDirectoryPicker() or OPFS.
6
6
  */
7
7
 
8
8
  import type {
9
- CopyOptions,
10
- FileHandle,
11
9
  FileInfo,
12
- FileRef,
13
- IFilesApi,
10
+ FileStats,
11
+ FilesApi,
14
12
  ListOptions,
13
+ ReadOptions,
15
14
  } from "@statewalker/webrun-files";
16
- import { basename, dirname, joinPath, resolveFileRef } from "@statewalker/webrun-files";
17
- import { BrowserFileHandle } from "./browser-file-handle.js";
15
+ import { basename, dirname, joinPath, normalizePath } from "@statewalker/webrun-files";
16
+
17
+ /** Chrome 110+ exposes move() on FileSystemHandle; not yet in TS DOM types. */
18
+ interface MovableHandle extends FileSystemHandle {
19
+ move(destination: FileSystemDirectoryHandle, name: string): Promise<void>;
20
+ }
18
21
 
19
22
  export interface BrowserFilesApiOptions {
20
23
  /**
@@ -24,17 +27,20 @@ export interface BrowserFilesApiOptions {
24
27
  rootHandle: FileSystemDirectoryHandle;
25
28
  }
26
29
 
27
- export class BrowserFilesApi implements IFilesApi {
30
+ /**
31
+ * Browser filesystem implementation using the File System Access API.
32
+ *
33
+ * Provides FilesApi interface over browser's FileSystemDirectoryHandle.
34
+ * This enables web applications to read/write files in user-selected directories
35
+ * or the Origin Private File System (OPFS).
36
+ */
37
+ export class BrowserFilesApi implements FilesApi {
28
38
  private rootHandle: FileSystemDirectoryHandle;
29
39
 
30
40
  constructor(options: BrowserFilesApiOptions) {
31
41
  this.rootHandle = options.rootHandle;
32
42
  }
33
43
 
34
- /**
35
- * Navigates to a directory handle from a path.
36
- * Returns null if the directory doesn't exist and create is false.
37
- */
38
44
  private async getDirectoryHandle(
39
45
  path: string,
40
46
  options: { create?: boolean } = {},
@@ -53,15 +59,11 @@ export class BrowserFilesApi implements IFilesApi {
53
59
  return current;
54
60
  }
55
61
 
56
- /**
57
- * Gets a file handle from a path.
58
- * Returns null if the file doesn't exist and create is false.
59
- */
60
62
  private async getFileHandle(
61
63
  path: string,
62
64
  options: { create?: boolean } = {},
63
65
  ): Promise<FileSystemFileHandle | null> {
64
- const normalized = resolveFileRef(path);
66
+ const normalized = normalizePath(path);
65
67
  const parentPath = dirname(normalized);
66
68
  const fileName = basename(normalized);
67
69
 
@@ -75,15 +77,71 @@ export class BrowserFilesApi implements IFilesApi {
75
77
  }
76
78
  }
77
79
 
78
- /**
79
- * Lists entries in a directory.
80
- */
81
- async *list(file: FileRef, options: ListOptions = {}): AsyncGenerator<FileInfo> {
82
- const normalized = resolveFileRef(file);
80
+ async *read(path: string, options?: ReadOptions): AsyncIterable<Uint8Array> {
81
+ const fileHandle = await this.getFileHandle(path);
82
+ if (!fileHandle) return;
83
+
84
+ try {
85
+ const file = await fileHandle.getFile();
86
+ const start = options?.start ?? 0;
87
+ const length = options?.length;
88
+ const end = length !== undefined ? start + length : file.size;
89
+
90
+ if (start >= file.size) return;
91
+
92
+ const bufferSize = 8192;
93
+ let position = start;
94
+ const actualEnd = Math.min(end, file.size);
95
+
96
+ while (position < actualEnd) {
97
+ const remaining = actualEnd - position;
98
+ const toRead = Math.min(bufferSize, remaining);
99
+ const slice = file.slice(position, position + toRead);
100
+ const buffer = await slice.arrayBuffer();
101
+ yield new Uint8Array(buffer);
102
+ position += buffer.byteLength;
103
+ }
104
+ } catch {
105
+ return;
106
+ }
107
+ }
108
+
109
+ async write(
110
+ path: string,
111
+ content: Iterable<Uint8Array> | AsyncIterable<Uint8Array>,
112
+ ): Promise<void> {
113
+ const normalized = normalizePath(path);
114
+ const parentPath = dirname(normalized);
115
+ const fileName = basename(normalized);
116
+
117
+ const parent = await this.getDirectoryHandle(parentPath, { create: true });
118
+ if (!parent) {
119
+ throw new Error(`Cannot create directory: ${parentPath}`);
120
+ }
121
+
122
+ const fileHandle = await parent.getFileHandle(fileName, { create: true });
123
+ const writable = await fileHandle.createWritable();
124
+
125
+ try {
126
+ for await (const chunk of content) {
127
+ await writable.write(chunk as Uint8Array<ArrayBuffer>);
128
+ }
129
+ } finally {
130
+ await writable.close();
131
+ }
132
+ }
133
+
134
+ async mkdir(path: string): Promise<void> {
135
+ const normalized = normalizePath(path);
136
+ await this.getDirectoryHandle(normalized, { create: true });
137
+ }
138
+
139
+ async *list(path: string, options?: ListOptions): AsyncIterable<FileInfo> {
140
+ const normalized = normalizePath(path);
83
141
  const dirHandle = await this.getDirectoryHandle(normalized);
84
142
  if (!dirHandle) return;
85
143
 
86
- const { recursive = false } = options;
144
+ const recursive = options?.recursive ?? false;
87
145
 
88
146
  for await (const [name, handle] of dirHandle.entries()) {
89
147
  const entryPath = joinPath(normalized, name);
@@ -102,7 +160,6 @@ export class BrowserFilesApi implements IFilesApi {
102
160
  const file = await fileHandle.getFile();
103
161
  info.size = file.size;
104
162
  info.lastModified = file.lastModified;
105
- info.type = file.type;
106
163
  } catch {
107
164
  // Ignore errors getting file metadata
108
165
  }
@@ -116,19 +173,12 @@ export class BrowserFilesApi implements IFilesApi {
116
173
  }
117
174
  }
118
175
 
119
- /**
120
- * Gets file or directory stats.
121
- */
122
- async stats(file: FileRef): Promise<FileInfo | undefined> {
123
- const normalized = resolveFileRef(file);
124
- const fileName = basename(normalized);
176
+ async stats(path: string): Promise<FileStats | undefined> {
177
+ const normalized = normalizePath(path);
125
178
 
126
- // Handle root directory
127
179
  if (normalized === "/") {
128
180
  return {
129
181
  kind: "directory",
130
- name: "",
131
- path: "/",
132
182
  lastModified: 0,
133
183
  };
134
184
  }
@@ -140,11 +190,8 @@ export class BrowserFilesApi implements IFilesApi {
140
190
  const f = await fileHandle.getFile();
141
191
  return {
142
192
  kind: "file",
143
- name: fileName,
144
- path: normalized,
145
193
  size: f.size,
146
194
  lastModified: f.lastModified,
147
- type: f.type,
148
195
  };
149
196
  } catch {
150
197
  return undefined;
@@ -156,8 +203,6 @@ export class BrowserFilesApi implements IFilesApi {
156
203
  if (dirHandle) {
157
204
  return {
158
205
  kind: "directory",
159
- name: fileName,
160
- path: normalized,
161
206
  lastModified: 0,
162
207
  };
163
208
  }
@@ -165,11 +210,13 @@ export class BrowserFilesApi implements IFilesApi {
165
210
  return undefined;
166
211
  }
167
212
 
168
- /**
169
- * Removes a file or directory.
170
- */
171
- async remove(file: FileRef): Promise<boolean> {
172
- const normalized = resolveFileRef(file);
213
+ async exists(path: string): Promise<boolean> {
214
+ const stats = await this.stats(path);
215
+ return stats !== undefined;
216
+ }
217
+
218
+ async remove(path: string): Promise<boolean> {
219
+ const normalized = normalizePath(path);
173
220
  const parentPath = dirname(normalized);
174
221
  const name = basename(normalized);
175
222
 
@@ -184,65 +231,52 @@ export class BrowserFilesApi implements IFilesApi {
184
231
  }
185
232
  }
186
233
 
187
- /**
188
- * Opens a file for reading and writing.
189
- * Creates the file and parent directories if they don't exist.
190
- */
191
- async open(file: FileRef): Promise<FileHandle> {
192
- const normalized = resolveFileRef(file);
193
- const parentPath = dirname(normalized);
194
- const fileName = basename(normalized);
195
-
196
- // Ensure parent directory exists
197
- const parent = await this.getDirectoryHandle(parentPath, { create: true });
198
- if (!parent) {
199
- throw new Error(`Cannot create directory: ${parentPath}`);
200
- }
234
+ async move(source: string, target: string): Promise<boolean> {
235
+ const sourcePath = normalizePath(source);
236
+ const targetPath = normalizePath(target);
237
+ const targetParentPath = dirname(targetPath);
238
+ const targetName = basename(targetPath);
201
239
 
202
- // Get or create file
203
- const fileHandle = await parent.getFileHandle(fileName, { create: true });
204
- const f = await fileHandle.getFile();
240
+ // Try native move() (available in Chrome 110+, Edge 110+)
241
+ const sourceStats = await this.stats(sourcePath);
242
+ if (!sourceStats) return false;
205
243
 
206
- return new BrowserFileHandle({ fileHandle, initialFile: f });
207
- }
244
+ const sourceHandle: FileSystemHandle | null =
245
+ sourceStats.kind === "file"
246
+ ? await this.getFileHandle(sourcePath)
247
+ : await this.getDirectoryHandle(sourcePath);
248
+ if (!sourceHandle) return false;
208
249
 
209
- /**
210
- * Creates a directory (and parent directories if needed).
211
- */
212
- async mkdir(file: FileRef): Promise<void> {
213
- const normalized = resolveFileRef(file);
214
- await this.getDirectoryHandle(normalized, { create: true });
215
- }
250
+ if (typeof (sourceHandle as MovableHandle).move === "function") {
251
+ try {
252
+ const targetParent = await this.getDirectoryHandle(targetParentPath, { create: true });
253
+ if (!targetParent) return false;
254
+ await (sourceHandle as MovableHandle).move(targetParent, targetName);
255
+ return true;
256
+ } catch {
257
+ // Fall through to copy-then-delete
258
+ }
259
+ }
216
260
 
217
- /**
218
- * Moves a file or directory using copy + delete.
219
- * The File System Access API doesn't have a native move operation.
220
- */
221
- async move(source: FileRef, target: FileRef): Promise<boolean> {
222
261
  const copied = await this.copy(source, target);
223
262
  if (!copied) return false;
224
263
  return this.remove(source);
225
264
  }
226
265
 
227
- /**
228
- * Copies a file or directory.
229
- */
230
- async copy(source: FileRef, target: FileRef, options: CopyOptions = {}): Promise<boolean> {
231
- const sourcePath = resolveFileRef(source);
232
- const targetPath = resolveFileRef(target);
233
- const { recursive = true } = options;
266
+ async copy(source: string, target: string): Promise<boolean> {
267
+ const sourcePath = normalizePath(source);
268
+ const targetPath = normalizePath(target);
234
269
 
235
- const sourceInfo = await this.stats(source);
236
- if (!sourceInfo) return false;
270
+ const sourceStats = await this.stats(source);
271
+ if (!sourceStats) return false;
237
272
 
238
- if (sourceInfo.kind === "file") {
273
+ if (sourceStats.kind === "file") {
239
274
  // Copy single file
240
275
  const sourceHandle = await this.getFileHandle(sourcePath);
241
276
  if (!sourceHandle) return false;
242
277
 
243
278
  const sourceFile = await sourceHandle.getFile();
244
279
 
245
- // Create target file
246
280
  const targetParentPath = dirname(targetPath);
247
281
  const targetFileName = basename(targetPath);
248
282
 
@@ -253,7 +287,6 @@ export class BrowserFilesApi implements IFilesApi {
253
287
  const writable = await targetHandle.createWritable();
254
288
 
255
289
  try {
256
- // Stream the file content
257
290
  const reader = sourceFile.stream().getReader();
258
291
  while (true) {
259
292
  const { done, value } = await reader.read();
@@ -267,13 +300,9 @@ export class BrowserFilesApi implements IFilesApi {
267
300
  return true;
268
301
  }
269
302
 
270
- // Copy directory
271
- if (!recursive) return false;
272
-
273
- // Create target directory
303
+ // Copy directory recursively
274
304
  await this.getDirectoryHandle(targetPath, { create: true });
275
305
 
276
- // Copy all entries recursively
277
306
  for await (const entry of this.list(source, { recursive: true })) {
278
307
  if (entry.kind === "directory") continue;
279
308
 
@@ -291,7 +320,9 @@ export class BrowserFilesApi implements IFilesApi {
291
320
  const newTargetParent = await this.getDirectoryHandle(newTargetParentPath, { create: true });
292
321
  if (!newTargetParent) continue;
293
322
 
294
- const newTargetHandle = await newTargetParent.getFileHandle(newTargetFileName, { create: true });
323
+ const newTargetHandle = await newTargetParent.getFileHandle(newTargetFileName, {
324
+ create: true,
325
+ });
295
326
  const writable = await newTargetHandle.createWritable();
296
327
 
297
328
  try {
@@ -310,19 +341,8 @@ export class BrowserFilesApi implements IFilesApi {
310
341
  }
311
342
  }
312
343
 
313
- /**
314
- * Opens a browser directory picker and returns a BrowserFilesApi instance.
315
- * Only works in secure contexts (HTTPS) and requires user gesture.
316
- */
317
- export async function openBrowserFilesApi(): Promise<BrowserFilesApi> {
318
- // Cast to any since TypeScript's lib.dom.d.ts may not include showDirectoryPicker
319
- const rootHandle = await (globalThis as unknown as { showDirectoryPicker: () => Promise<FileSystemDirectoryHandle> }).showDirectoryPicker();
320
- return new BrowserFilesApi({ rootHandle });
321
- }
322
-
323
344
  /**
324
345
  * Gets a BrowserFilesApi instance backed by the Origin Private File System (OPFS).
325
- * OPFS provides a sandboxed filesystem that persists between sessions.
326
346
  */
327
347
  export async function getOPFSFilesApi(): Promise<BrowserFilesApi> {
328
348
  const rootHandle = await navigator.storage.getDirectory();
package/src/index.ts CHANGED
@@ -4,9 +4,12 @@
4
4
 
5
5
  export {
6
6
  BrowserFilesApi,
7
- getOPFSFilesApi,
8
- openBrowserFilesApi,
9
7
  type BrowserFilesApiOptions,
8
+ getOPFSFilesApi,
10
9
  } from "./browser-files-api.js";
11
-
12
- export { BrowserFileHandle, type BrowserFileHandleOptions } from "./browser-file-handle.js";
10
+ export {
11
+ isHandlerAccessible,
12
+ type OpenBrowserFilesApiOptions,
13
+ openBrowserFilesApi,
14
+ verifyPermission,
15
+ } from "./open-browser-files-api.js";
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Utilities for opening and managing browser file system access.
3
+ *
4
+ * Provides functions for permission verification, handle accessibility checking,
5
+ * and opening directory pickers with optional persistent storage support.
6
+ */
7
+
8
+ import { BrowserFilesApi } from "./browser-files-api.js";
9
+
10
+ /**
11
+ * Extended FileSystemHandle interface with permission methods.
12
+ * These are part of the File System Access API but not fully typed in TypeScript's lib.dom.d.ts.
13
+ */
14
+ interface FileSystemHandleWithPermissions {
15
+ queryPermission(descriptor?: { mode?: "read" | "readwrite" }): Promise<PermissionState>;
16
+ requestPermission(descriptor?: { mode?: "read" | "readwrite" }): Promise<PermissionState>;
17
+ }
18
+
19
+ /**
20
+ * Options for opening a browser files API with persistent storage support.
21
+ */
22
+ export interface OpenBrowserFilesApiOptions {
23
+ /**
24
+ * Key used to store the directory handler in persistent storage.
25
+ * @default "root-dir"
26
+ */
27
+ handlerKey?: string;
28
+ /**
29
+ * Whether to request read-write access (true) or read-only (false).
30
+ * @default true
31
+ */
32
+ readwrite?: boolean;
33
+ /**
34
+ * Gets a stored directory handler by key.
35
+ * @default Returns undefined (no persistence)
36
+ */
37
+ get?: (key: string) => Promise<FileSystemDirectoryHandle | undefined>;
38
+ /**
39
+ * Stores a directory handler with a key.
40
+ * @default No-op (no persistence)
41
+ */
42
+ set?: (key: string, handler: FileSystemDirectoryHandle) => Promise<void>;
43
+ /**
44
+ * Deletes a stored directory handler by key.
45
+ * @default No-op (no persistence)
46
+ */
47
+ del?: (key: string) => Promise<void>;
48
+ }
49
+
50
+ /**
51
+ * Checks if a file system handle is still accessible by performing a real read operation.
52
+ * Handles may become inaccessible if the underlying file/directory was moved or deleted.
53
+ *
54
+ * @param fileHandle - The file or directory handle to check
55
+ * @returns True if the handle is accessible, false otherwise
56
+ */
57
+ export async function isHandlerAccessible(
58
+ fileHandle: FileSystemFileHandle | FileSystemDirectoryHandle,
59
+ ): Promise<boolean> {
60
+ try {
61
+ if (fileHandle.kind === "file") {
62
+ await (fileHandle as FileSystemFileHandle).getFile();
63
+ } else {
64
+ // For directories, iterate to check accessibility
65
+ for await (const _item of (fileHandle as FileSystemDirectoryHandle).values()) {
66
+ break;
67
+ }
68
+ }
69
+ return true;
70
+ } catch {
71
+ return false;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Verifies and requests file system permissions for a handle.
77
+ * First checks if permission was already granted, then requests if needed.
78
+ *
79
+ * @param fileHandle - The file or directory handle to verify permissions for
80
+ * @param readWrite - Whether to request read-write access (true) or read-only (false)
81
+ * @returns True if permission is granted, false otherwise
82
+ */
83
+ export async function verifyPermission(
84
+ fileHandle: FileSystemFileHandle | FileSystemDirectoryHandle,
85
+ readWrite: boolean = false,
86
+ ): Promise<boolean> {
87
+ const handle = fileHandle as unknown as FileSystemHandleWithPermissions;
88
+ const options: { mode?: "read" | "readwrite" } = {};
89
+ if (readWrite) {
90
+ options.mode = "readwrite";
91
+ }
92
+
93
+ // Check if permission was already granted
94
+ if ((await handle.queryPermission(options)) === "granted") {
95
+ return true;
96
+ }
97
+
98
+ // Request permission if not already granted
99
+ if ((await handle.requestPermission(options)) === "granted") {
100
+ return true;
101
+ }
102
+
103
+ return false;
104
+ }
105
+
106
+ /**
107
+ * Opens a browser directory picker and returns a BrowserFilesApi instance.
108
+ * Only works in secure contexts (HTTPS) and requires user gesture.
109
+ *
110
+ * Supports persistent storage of directory handles via optional get/set/del functions.
111
+ * When persistence is configured, will attempt to reuse a previously selected directory.
112
+ *
113
+ * @param options - Configuration options for opening the files API
114
+ * @returns A BrowserFilesApi instance for the selected directory
115
+ * @throws Error if access is not granted or the directory is not accessible
116
+ */
117
+ export async function openBrowserFilesApi(
118
+ options: OpenBrowserFilesApiOptions = {},
119
+ ): Promise<BrowserFilesApi> {
120
+ const {
121
+ handlerKey = "root-dir",
122
+ readwrite = true,
123
+ get = async () => undefined,
124
+ set = async () => {},
125
+ del = async () => {},
126
+ } = options;
127
+
128
+ let rootHandle = await get(handlerKey);
129
+
130
+ if (!rootHandle) {
131
+ // Cast to any since TypeScript's lib.dom.d.ts may not include showDirectoryPicker
132
+ rootHandle = await (
133
+ globalThis as unknown as { showDirectoryPicker: () => Promise<FileSystemDirectoryHandle> }
134
+ ).showDirectoryPicker();
135
+ await set(handlerKey, rootHandle);
136
+ }
137
+
138
+ if (!(await verifyPermission(rootHandle, readwrite))) {
139
+ throw new Error("Access was not granted");
140
+ }
141
+
142
+ if (!(await isHandlerAccessible(rootHandle))) {
143
+ await del(handlerKey);
144
+ throw new Error("Cannot access the folder. Please try again.");
145
+ }
146
+
147
+ return new BrowserFilesApi({ rootHandle });
148
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2022 statewalker
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,42 +0,0 @@
1
- /**
2
- * Browser FileHandle implementation using File System Access API.
3
- *
4
- * Provides random access read/write operations for browser files.
5
- * Uses File.slice() for efficient partial reads and FileSystemWritableFileStream
6
- * for writes.
7
- */
8
- import type { AppendOptions, BinaryStream, FileHandle, ReadStreamOptions, WriteStreamOptions } from "@statewalker/webrun-files";
9
- export interface BrowserFileHandleOptions {
10
- fileHandle: FileSystemFileHandle;
11
- initialFile: File;
12
- }
13
- export declare class BrowserFileHandle implements FileHandle {
14
- private fileHandle;
15
- private file;
16
- private _size;
17
- private closed;
18
- constructor(options: BrowserFileHandleOptions);
19
- get size(): number;
20
- close(): Promise<void>;
21
- /**
22
- * Appends data to the end of the file.
23
- */
24
- appendFile(data: BinaryStream, options?: AppendOptions): Promise<number>;
25
- /**
26
- * Creates a read stream for the file.
27
- * Uses File.slice() for efficient partial reads.
28
- */
29
- createReadStream(options?: ReadStreamOptions): AsyncGenerator<Uint8Array>;
30
- /**
31
- * Writes data to the file starting at the specified position.
32
- * If start > 0, preserves content before start position.
33
- * Truncates file at start position before writing.
34
- */
35
- createWriteStream(data: BinaryStream, options?: WriteStreamOptions): Promise<number>;
36
- /**
37
- * Random access read from file at a specific position.
38
- * Uses File.slice() for efficient partial reads.
39
- */
40
- read(buffer: Uint8Array, offset: number, length: number, position: number): Promise<number>;
41
- }
42
- //# sourceMappingURL=browser-file-handle.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"browser-file-handle.d.ts","sourceRoot":"","sources":["../src/browser-file-handle.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,2BAA2B,CAAC;AAGnC,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,oBAAoB,CAAC;IACjC,WAAW,EAAE,IAAI,CAAC;CACnB;AAED,qBAAa,iBAAkB,YAAW,UAAU;IAClD,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,EAAE,wBAAwB;IAM7C,IAAI,IAAI,IAAI,MAAM,CAEjB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,MAAM,CAAC;IAgClF;;;OAGG;IACI,gBAAgB,CAAC,OAAO,GAAE,iBAAsB,GAAG,cAAc,CAAC,UAAU,CAAC;IAmCpF;;;;OAIG;IACG,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC;IAyC9F;;;OAGG;IACG,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CA0BlG"}