@tutti-os/workspace-file-preview 0.0.181 → 0.0.183

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 CHANGED
@@ -10,7 +10,17 @@ It also exposes a small React preview surface for consumers that want the shared
10
10
  image, text, loading, and readonly rendering shell while keeping host-specific
11
11
  icons and localized copy outside this package.
12
12
 
13
- HTML rendering is an explicit consumer opt-in. File-manager detail panes and
14
- reference pickers intentionally leave it disabled so HTML files are shown as
15
- source text; opening or executing an HTML document belongs to a separate browser
16
- activation flow.
13
+ `createWorkspaceFilePreviewController` owns the shared frontend loading
14
+ lifecycle. Consumers inject a byte reader and entry projection; the controller
15
+ owns readiness checks, stale-request fencing, cancellation, decoding, canonical
16
+ language-neutral state, and media object-URL cleanup. Product controllers keep
17
+ selection, editing, persistence, transport, and localized error presentation.
18
+
19
+ Sources may provide `canReadEntry` as a capability gate. Returning `false`
20
+ blocks reads for every non-directory entry, while returning `true` also allows
21
+ the injected reader to classify file types that local extension detection does
22
+ not recognize. Omitting the callback keeps the default local classification.
23
+
24
+ HTML files are shown as source text. Opening or executing an HTML document
25
+ belongs to a separate browser activation flow rather than the ordinary file
26
+ preview surface.
@@ -316,13 +316,6 @@ function createWorkspaceFilePreviewLoadedState(input) {
316
316
  status: "readonly"
317
317
  };
318
318
  }
319
- if (input.renderHtml && isWorkspaceHtmlFileName(input.target.name)) {
320
- return {
321
- content,
322
- entry: input.target,
323
- status: "html"
324
- };
325
- }
326
319
  return {
327
320
  content,
328
321
  entry: input.target,
@@ -336,11 +329,6 @@ function createWorkspaceFilePreviewLoadedState(input) {
336
329
  };
337
330
  }
338
331
  }
339
- function isWorkspaceHtmlFileName(pathOrName) {
340
- return browserOpenableHtmlExtensions.has(
341
- resolveWorkspaceFileExtension(pathOrName)
342
- );
343
- }
344
332
  function resolveWorkspaceFilePreviewName(entry) {
345
333
  return entry.name?.trim() || entry.displayName?.trim() || entry.path.split("/").pop()?.trim() || entry.path;
346
334
  }
@@ -398,6 +386,291 @@ function formatWorkspacePreviewByteLimit(sizeBytes) {
398
386
  return `${mebibytes.toFixed(1)} MiB`;
399
387
  }
400
388
 
389
+ // src/core/workspaceFilePreviewController.ts
390
+ function createWorkspaceFilePreviewController(input) {
391
+ return new WorkspaceFilePreviewControllerImpl(input);
392
+ }
393
+ var WorkspaceFilePreviewControllerImpl = class {
394
+ activeEntry = null;
395
+ activeEntryKey = null;
396
+ abortController = null;
397
+ disposed = false;
398
+ generation = 0;
399
+ loadPromise = Promise.resolve();
400
+ objectUrl = null;
401
+ listeners = /* @__PURE__ */ new Set();
402
+ input;
403
+ objectUrls;
404
+ state = {
405
+ status: "empty"
406
+ };
407
+ constructor(input) {
408
+ this.input = input;
409
+ this.objectUrls = input.objectUrls ?? browserWorkspaceFilePreviewObjectUrls;
410
+ }
411
+ dispose() {
412
+ if (this.disposed) {
413
+ return;
414
+ }
415
+ this.disposed = true;
416
+ this.cancelLoad();
417
+ this.revokeObjectUrl();
418
+ this.listeners.clear();
419
+ }
420
+ getSnapshot() {
421
+ return this.state;
422
+ }
423
+ reload() {
424
+ if (this.disposed || !this.activeEntry) {
425
+ return Promise.resolve();
426
+ }
427
+ return this.start(this.activeEntry);
428
+ }
429
+ setEntry(entry) {
430
+ if (this.disposed) {
431
+ return Promise.resolve();
432
+ }
433
+ const nextKey = entry ? this.resolveEntryKey(entry) : null;
434
+ if (nextKey === this.activeEntryKey) {
435
+ return this.loadPromise;
436
+ }
437
+ this.activeEntry = entry;
438
+ this.activeEntryKey = nextKey;
439
+ if (!entry) {
440
+ this.cancelLoad();
441
+ this.revokeObjectUrl();
442
+ this.updateState({ status: "empty" });
443
+ this.loadPromise = Promise.resolve();
444
+ return this.loadPromise;
445
+ }
446
+ return this.start(entry);
447
+ }
448
+ subscribe(listener) {
449
+ if (this.disposed) {
450
+ return () => {
451
+ };
452
+ }
453
+ this.listeners.add(listener);
454
+ return () => {
455
+ this.listeners.delete(listener);
456
+ };
457
+ }
458
+ cancelLoad() {
459
+ this.generation += 1;
460
+ this.abortController?.abort();
461
+ this.abortController = null;
462
+ }
463
+ isStale(generation) {
464
+ return this.disposed || generation !== this.generation;
465
+ }
466
+ async load(entry, previewEntry, target, generation, abortController) {
467
+ try {
468
+ const result = await this.input.read?.({
469
+ entry,
470
+ signal: abortController.signal,
471
+ target
472
+ });
473
+ if (this.isStale(generation)) {
474
+ return;
475
+ }
476
+ if (!result) {
477
+ this.updateState({
478
+ entry,
479
+ reason: "file_type",
480
+ status: "unsupported"
481
+ });
482
+ return;
483
+ }
484
+ const loaded = createWorkspaceFilePreviewLoadedState({
485
+ bytes: result instanceof Uint8Array || result instanceof ArrayBuffer ? result : result.bytes,
486
+ contentType: result instanceof Uint8Array || result instanceof ArrayBuffer ? void 0 : result.contentType,
487
+ entry: previewEntry,
488
+ target: result instanceof Uint8Array || result instanceof ArrayBuffer ? target : { ...target, fileKind: result.kind ?? target.fileKind }
489
+ });
490
+ if (this.isStale(generation)) {
491
+ return;
492
+ }
493
+ if (loaded.status === "image" || loaded.status === "video") {
494
+ const previewSizeBytes = loaded.bytes.byteLength;
495
+ const objectUrl = this.objectUrls.create(
496
+ loaded.bytes,
497
+ loaded.contentType
498
+ );
499
+ if (this.isStale(generation)) {
500
+ this.objectUrls.revoke(objectUrl);
501
+ return;
502
+ }
503
+ this.objectUrl = objectUrl;
504
+ this.updateState({
505
+ entry,
506
+ objectUrl,
507
+ previewSizeBytes,
508
+ status: loaded.status
509
+ });
510
+ return;
511
+ }
512
+ if (loaded.status === "text") {
513
+ this.updateState({
514
+ content: loaded.content,
515
+ entry,
516
+ previewSizeBytes: byteLengthOfPreviewReadResult(result),
517
+ status: "text"
518
+ });
519
+ return;
520
+ }
521
+ this.updateState({
522
+ entry,
523
+ previewSizeBytes: byteLengthOfPreviewReadResult(result),
524
+ reason: loaded.reason,
525
+ ...loaded.maxSizeBytes === void 0 ? {} : { maxSizeBytes: loaded.maxSizeBytes },
526
+ status: "readonly"
527
+ });
528
+ } catch (error) {
529
+ if (this.isStale(generation)) {
530
+ return;
531
+ }
532
+ this.updateState({ entry, error, status: "error" });
533
+ } finally {
534
+ if (!this.isStale(generation)) {
535
+ this.abortController = null;
536
+ }
537
+ }
538
+ }
539
+ resolveEntryKey(entry) {
540
+ if (this.input.getEntryKey) {
541
+ return this.input.getEntryKey(entry);
542
+ }
543
+ const previewEntry = this.input.toPreviewEntry(entry);
544
+ return [
545
+ previewEntry.kind,
546
+ previewEntry.path,
547
+ previewEntry.name ?? "",
548
+ previewEntry.displayName ?? "",
549
+ previewEntry.sizeBytes ?? "",
550
+ previewEntry.mtimeMs ?? ""
551
+ ].join("\0");
552
+ }
553
+ revokeObjectUrl() {
554
+ if (!this.objectUrl) {
555
+ return;
556
+ }
557
+ this.objectUrls.revoke(this.objectUrl);
558
+ this.objectUrl = null;
559
+ }
560
+ start(entry) {
561
+ this.cancelLoad();
562
+ this.revokeObjectUrl();
563
+ const previewEntry = this.input.toPreviewEntry(entry);
564
+ const readiness = resolveWorkspaceFilePreviewReadiness(previewEntry);
565
+ if (readiness.status === "directory") {
566
+ this.updateState({ entry, status: "directory" });
567
+ this.loadPromise = Promise.resolve();
568
+ return this.loadPromise;
569
+ }
570
+ const canRead = this.input.canReadEntry?.(entry);
571
+ if (canRead === false) {
572
+ this.updateState({
573
+ entry,
574
+ reason: "reader_unavailable",
575
+ status: "unsupported"
576
+ });
577
+ this.loadPromise = Promise.resolve();
578
+ return this.loadPromise;
579
+ }
580
+ if (readiness.status === "unsupported") {
581
+ if (canRead !== true) {
582
+ this.updateState({
583
+ entry,
584
+ reason: "file_type",
585
+ status: "unsupported"
586
+ });
587
+ this.loadPromise = Promise.resolve();
588
+ return this.loadPromise;
589
+ }
590
+ if (!this.input.read) {
591
+ this.updateState({
592
+ entry,
593
+ reason: "reader_unavailable",
594
+ status: "unsupported"
595
+ });
596
+ this.loadPromise = Promise.resolve();
597
+ return this.loadPromise;
598
+ }
599
+ const target = {
600
+ fileKind: "text",
601
+ name: previewEntry.name ?? previewEntry.displayName ?? previewEntry.path.split("/").pop() ?? previewEntry.path,
602
+ path: previewEntry.path,
603
+ ...previewEntry.mtimeMs === void 0 ? {} : { mtimeMs: previewEntry.mtimeMs },
604
+ ...previewEntry.sizeBytes === void 0 ? {} : { sizeBytes: previewEntry.sizeBytes }
605
+ };
606
+ const abortController2 = new AbortController();
607
+ this.abortController = abortController2;
608
+ const generation2 = this.generation;
609
+ this.updateState({ entry, status: "loading" });
610
+ this.loadPromise = this.load(
611
+ entry,
612
+ previewEntry,
613
+ target,
614
+ generation2,
615
+ abortController2
616
+ );
617
+ return this.loadPromise;
618
+ }
619
+ if (readiness.status === "readonly") {
620
+ this.updateState({
621
+ entry,
622
+ maxSizeBytes: readiness.maxSizeBytes,
623
+ reason: readiness.reason,
624
+ status: "readonly"
625
+ });
626
+ this.loadPromise = Promise.resolve();
627
+ return this.loadPromise;
628
+ }
629
+ if (!this.input.read) {
630
+ this.updateState({
631
+ entry,
632
+ reason: "reader_unavailable",
633
+ status: "unsupported"
634
+ });
635
+ this.loadPromise = Promise.resolve();
636
+ return this.loadPromise;
637
+ }
638
+ const abortController = new AbortController();
639
+ this.abortController = abortController;
640
+ const generation = this.generation;
641
+ this.updateState({ entry, status: "loading" });
642
+ this.loadPromise = this.load(
643
+ entry,
644
+ previewEntry,
645
+ readiness.target,
646
+ generation,
647
+ abortController
648
+ );
649
+ return this.loadPromise;
650
+ }
651
+ updateState(state) {
652
+ if (this.disposed) {
653
+ return;
654
+ }
655
+ this.state = state;
656
+ for (const listener of this.listeners) {
657
+ listener();
658
+ }
659
+ }
660
+ };
661
+ var browserWorkspaceFilePreviewObjectUrls = {
662
+ create(bytes, contentType) {
663
+ return URL.createObjectURL(new Blob([bytes], { type: contentType }));
664
+ },
665
+ revoke(objectUrl) {
666
+ URL.revokeObjectURL(objectUrl);
667
+ }
668
+ };
669
+ function byteLengthOfPreviewReadResult(result) {
670
+ const bytes = result instanceof Uint8Array || result instanceof ArrayBuffer ? result : result.bytes;
671
+ return bytes.byteLength;
672
+ }
673
+
401
674
  export {
402
675
  workspaceFileVideoHandlerCollisionExtensions,
403
676
  workspaceFileTextMaxBytes,
@@ -419,6 +692,7 @@ export {
419
692
  normalizeWorkspaceFilePreviewBytes,
420
693
  copyWorkspaceFilePreviewBytes,
421
694
  looksLikeBinaryText,
422
- formatWorkspacePreviewByteLimit
695
+ formatWorkspacePreviewByteLimit,
696
+ createWorkspaceFilePreviewController
423
697
  };
424
- //# sourceMappingURL=chunk-FYAXIZ3F.js.map
698
+ //# sourceMappingURL=chunk-SUQYO7VX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/workspaceFilePreview.ts","../src/core/workspaceFilePreviewController.ts"],"sourcesContent":["export type WorkspaceFilePreviewEntryKind =\n | \"file\"\n | \"directory\"\n | \"folder\"\n | \"unknown\"\n | (string & {});\n\nexport type WorkspaceFilePreviewKind = \"image\" | \"text\" | \"video\";\n\nexport type WorkspaceFileVisualKind =\n | \"binary\"\n | \"code\"\n | \"directory\"\n | \"document\"\n | \"image\"\n | \"markdown\"\n | \"video\";\n\nexport interface WorkspaceFilePreviewEntry {\n displayName?: string;\n kind: WorkspaceFilePreviewEntryKind;\n mtimeMs?: number | null;\n name?: string;\n path: string;\n sizeBytes?: number | null;\n}\n\nexport interface WorkspaceFilePreviewActivationTarget {\n fileKind: WorkspaceFilePreviewKind;\n mtimeMs?: number | null;\n name: string;\n path: string;\n sizeBytes?: number | null;\n}\n\nexport type WorkspaceFilePreviewReadonlyReason =\n | \"binary\"\n | \"decode_failed\"\n | \"file_too_large\"\n | \"text_too_large\";\n\nexport type WorkspaceFilePreviewReadiness<\n TEntry extends WorkspaceFilePreviewEntry,\n TTarget extends WorkspaceFilePreviewActivationTarget =\n WorkspaceFilePreviewActivationTarget\n> =\n | { entry: TEntry; status: \"directory\" }\n | {\n entry: TEntry;\n maxSizeBytes: number;\n reason: Extract<\n WorkspaceFilePreviewReadonlyReason,\n \"file_too_large\" | \"text_too_large\"\n >;\n status: \"readonly\";\n }\n | { entry: TEntry; status: \"unsupported\" }\n | { entry: TEntry; status: \"ready\"; target: TTarget };\n\nexport type WorkspaceFilePreviewLoadedState<\n TEntry extends WorkspaceFilePreviewEntry,\n TTarget extends WorkspaceFilePreviewActivationTarget\n> =\n | { content: string; entry: TTarget; status: \"text\" }\n | {\n bytes: Uint8Array<ArrayBuffer>;\n contentType: string;\n entry: TTarget;\n status: \"image\";\n }\n | {\n bytes: Uint8Array<ArrayBuffer>;\n contentType: string;\n entry: TTarget;\n status: \"video\";\n }\n | {\n entry: TEntry;\n maxSizeBytes?: number;\n reason: WorkspaceFilePreviewReadonlyReason;\n status: \"readonly\";\n };\n\nconst imageExtensions = new Set([\n \"avif\",\n \"gif\",\n \"jpeg\",\n \"jpg\",\n \"png\",\n \"svg\",\n \"webp\"\n]);\nconst browserOpenableHtmlExtensions = new Set([\n \"htm\",\n \"html\",\n \"shtml\",\n \"xhtml\"\n]);\nconst browserOpenableVideoExtensions = new Set([\"mp4\", \"webm\"]);\n\nconst videoExtensions = new Set([\n \"avi\",\n \"m2ts\",\n \"mkv\",\n \"mov\",\n \"mp4\",\n \"mpeg\",\n \"mpg\",\n \"mts\",\n \"webm\",\n \"wmv\"\n]);\n/**\n * Extensions where macOS Launch Services may register video handlers even when\n * the workspace file is source code (UTI / uniform type collisions).\n */\nexport const workspaceFileVideoHandlerCollisionExtensions = new Set([\"ts\"]);\nconst markdownExtensions = new Set([\"md\", \"mdx\"]);\nconst codeExtensions = new Set([\n \"bash\",\n \"c\",\n \"cc\",\n \"conf\",\n \"cpp\",\n \"cs\",\n \"css\",\n \"go\",\n \"h\",\n \"hpp\",\n \"html\",\n \"java\",\n \"js\",\n \"jsx\",\n \"json\",\n \"lua\",\n \"m\",\n \"mm\",\n \"php\",\n \"plist\",\n \"proto\",\n \"py\",\n \"rb\",\n \"rs\",\n \"sh\",\n \"sql\",\n \"swift\",\n \"toml\",\n \"ts\",\n \"tsx\",\n \"xml\",\n \"yaml\",\n \"yml\",\n \"zsh\"\n]);\nconst documentExtensions = new Set([\n \"csv\",\n \"doc\",\n \"docx\",\n \"log\",\n \"pdf\",\n \"rtf\",\n \"txt\",\n \"xls\",\n \"xlsx\"\n]);\nconst textExtensions = new Set([\n \"bash\",\n \"c\",\n \"cc\",\n \"conf\",\n \"cpp\",\n \"cs\",\n \"css\",\n \"csv\",\n \"env\",\n \"go\",\n \"h\",\n \"hpp\",\n \"html\",\n \"ini\",\n \"java\",\n \"js\",\n \"json\",\n \"jsx\",\n \"log\",\n \"lua\",\n \"m\",\n \"md\",\n \"mdx\",\n \"mm\",\n \"php\",\n \"plist\",\n \"proto\",\n \"py\",\n \"rb\",\n \"rs\",\n \"sh\",\n \"sql\",\n \"swift\",\n \"toml\",\n \"ts\",\n \"tsx\",\n \"txt\",\n \"xml\",\n \"yaml\",\n \"yml\",\n \"zsh\"\n]);\nconst textFileNames = new Set([\n \".gitignore\",\n \".npmrc\",\n \".nvmrc\",\n \"dockerfile\",\n \"makefile\",\n \"readme\"\n]);\n\nexport const workspaceFileTextMaxBytes = 1024 * 1024;\nexport const workspaceFilePreviewMaxBytes = 20 * 1024 * 1024;\n\nexport function resolveWorkspaceFileVisualKind(\n entry: Pick<WorkspaceFilePreviewEntry, \"kind\" | \"name\" | \"path\">\n): WorkspaceFileVisualKind {\n if (entry.kind === \"directory\" || entry.kind === \"folder\") {\n return \"directory\";\n }\n\n const extension = resolveWorkspaceFileExtension(\n entry.path || entry.name || \"\"\n );\n if (imageExtensions.has(extension)) {\n return \"image\";\n }\n if (videoExtensions.has(extension)) {\n return \"video\";\n }\n if (markdownExtensions.has(extension)) {\n return \"markdown\";\n }\n if (codeExtensions.has(extension)) {\n return \"code\";\n }\n if (documentExtensions.has(extension)) {\n return \"document\";\n }\n return \"binary\";\n}\n\nexport function resolveWorkspaceFileExtension(pathOrName: string): string {\n const name = pathOrName.split(\"/\").pop()?.trim().toLowerCase() ?? \"\";\n const dotIndex = name.lastIndexOf(\".\");\n return dotIndex > 0 ? name.slice(dotIndex + 1) : \"\";\n}\n\nexport function isWorkspaceFileBrowserOpenable(\n entry: Pick<WorkspaceFilePreviewEntry, \"kind\" | \"name\" | \"path\">\n): boolean {\n if (entry.kind !== \"file\") {\n return false;\n }\n\n const extension = resolveWorkspaceFileExtension(\n entry.path || entry.name || \"\"\n );\n if (\n extension === \"pdf\" ||\n browserOpenableHtmlExtensions.has(extension) ||\n imageExtensions.has(extension) ||\n browserOpenableVideoExtensions.has(extension)\n ) {\n return true;\n }\n\n return classifyWorkspaceFilePreviewKind(entry) === \"text\";\n}\n\nexport function shouldFilterVideoPlayersForOpenWith(\n entry: Pick<WorkspaceFilePreviewEntry, \"kind\" | \"name\" | \"path\">\n): boolean {\n if (entry.kind !== \"file\") {\n return false;\n }\n\n const visualKind = resolveWorkspaceFileVisualKind(entry);\n if (visualKind === \"video\") {\n return false;\n }\n\n const extension = resolveWorkspaceFileExtension(\n entry.path || entry.name || \"\"\n );\n if (workspaceFileVideoHandlerCollisionExtensions.has(extension)) {\n return true;\n }\n\n if (visualKind === \"code\" || visualKind === \"markdown\") {\n return true;\n }\n\n return classifyWorkspaceFilePreviewKind(entry) === \"text\";\n}\n\nexport function resolveWorkspaceImageMimeType(\n pathOrName: string\n): string | null {\n switch (resolveWorkspaceFileExtension(pathOrName)) {\n case \"avif\":\n return \"image/avif\";\n case \"gif\":\n return \"image/gif\";\n case \"jpeg\":\n case \"jpg\":\n return \"image/jpeg\";\n case \"png\":\n return \"image/png\";\n case \"svg\":\n return \"image/svg+xml\";\n case \"webp\":\n return \"image/webp\";\n default:\n return null;\n }\n}\n\nexport function resolveWorkspaceVideoMimeType(\n pathOrName: string\n): string | null {\n switch (resolveWorkspaceFileExtension(pathOrName)) {\n case \"mp4\":\n return \"video/mp4\";\n case \"webm\":\n return \"video/webm\";\n default:\n return null;\n }\n}\n\nexport function classifyWorkspaceFilePreviewKind(\n entry: Pick<\n WorkspaceFilePreviewEntry,\n \"displayName\" | \"kind\" | \"name\" | \"path\"\n >\n): WorkspaceFilePreviewKind | null {\n if (entry.kind !== \"file\") {\n return null;\n }\n\n const name = resolveWorkspaceFilePreviewName(entry);\n if (resolveWorkspaceImageMimeType(name) !== null) {\n return \"image\";\n }\n if (resolveWorkspaceVideoMimeType(name) !== null) {\n return \"video\";\n }\n\n const normalizedName = name.trim().toLowerCase();\n const extension = resolveWorkspaceFileExtension(name);\n if (textExtensions.has(extension) || textFileNames.has(normalizedName)) {\n return \"text\";\n }\n\n return null;\n}\n\nexport function resolveWorkspaceFileActivationTarget(\n entry: WorkspaceFilePreviewEntry\n): WorkspaceFilePreviewActivationTarget | null {\n const fileKind = classifyWorkspaceFilePreviewKind(entry);\n if (!fileKind) {\n return null;\n }\n\n const target: WorkspaceFilePreviewActivationTarget = {\n fileKind,\n name: resolveWorkspaceFilePreviewName(entry),\n path: entry.path\n };\n if (entry.mtimeMs !== undefined) {\n target.mtimeMs = entry.mtimeMs;\n }\n if (entry.sizeBytes !== undefined) {\n target.sizeBytes = entry.sizeBytes;\n }\n return target;\n}\n\nexport function resolveWorkspaceFilePreviewReadiness<\n TEntry extends WorkspaceFilePreviewEntry\n>(entry: TEntry): WorkspaceFilePreviewReadiness<TEntry> {\n if (entry.kind === \"directory\" || entry.kind === \"folder\") {\n return {\n entry,\n status: \"directory\"\n };\n }\n\n const target = resolveWorkspaceFileActivationTarget(entry);\n if (!target) {\n return {\n entry,\n status: \"unsupported\"\n };\n }\n\n if (\n target.fileKind === \"text\" &&\n isWorkspaceTextFileTooLarge(entry.sizeBytes)\n ) {\n return {\n entry,\n maxSizeBytes: workspaceFileTextMaxBytes,\n reason: \"text_too_large\",\n status: \"readonly\"\n };\n }\n\n if (isWorkspacePreviewFileTooLarge(entry.sizeBytes)) {\n return {\n entry,\n maxSizeBytes: workspaceFilePreviewMaxBytes,\n reason: \"file_too_large\",\n status: \"readonly\"\n };\n }\n\n return {\n entry,\n status: \"ready\",\n target\n };\n}\n\nexport function createWorkspaceFilePreviewLoadedState<\n TEntry extends WorkspaceFilePreviewEntry,\n TTarget extends WorkspaceFilePreviewActivationTarget\n>(input: {\n bytes: Uint8Array | ArrayBuffer;\n contentType?: string | null;\n entry: TEntry;\n target: TTarget;\n}): WorkspaceFilePreviewLoadedState<TEntry, TTarget> {\n if (input.target.fileKind === \"image\") {\n return {\n bytes: copyWorkspaceFilePreviewBytes(input.bytes),\n contentType:\n input.contentType ??\n resolveWorkspaceImageMimeType(input.target.name) ??\n \"application/octet-stream\",\n entry: input.target,\n status: \"image\"\n };\n }\n if (input.target.fileKind === \"video\") {\n return {\n bytes: copyWorkspaceFilePreviewBytes(input.bytes),\n contentType:\n input.contentType ??\n resolveWorkspaceVideoMimeType(input.target.name) ??\n \"application/octet-stream\",\n entry: input.target,\n status: \"video\"\n };\n }\n\n try {\n const content = decodeWorkspaceTextFile(input.bytes);\n if (looksLikeBinaryText(content)) {\n return {\n entry: input.entry,\n reason: \"binary\",\n status: \"readonly\"\n };\n }\n return {\n content,\n entry: input.target,\n status: \"text\"\n };\n } catch {\n return {\n entry: input.entry,\n reason: \"decode_failed\",\n status: \"readonly\"\n };\n }\n}\n\nexport function resolveWorkspaceFilePreviewName(\n entry: Pick<WorkspaceFilePreviewEntry, \"displayName\" | \"name\" | \"path\">\n): string {\n return (\n entry.name?.trim() ||\n entry.displayName?.trim() ||\n entry.path.split(\"/\").pop()?.trim() ||\n entry.path\n );\n}\n\nexport function isWorkspaceTextFileTooLarge(\n sizeBytes?: number | null\n): boolean {\n return (\n typeof sizeBytes === \"number\" &&\n Number.isFinite(sizeBytes) &&\n sizeBytes > workspaceFileTextMaxBytes\n );\n}\n\nexport function isWorkspacePreviewFileTooLarge(\n sizeBytes?: number | null\n): boolean {\n return (\n typeof sizeBytes === \"number\" &&\n Number.isFinite(sizeBytes) &&\n sizeBytes > workspaceFilePreviewMaxBytes\n );\n}\n\nexport function decodeWorkspaceTextFile(\n bytes: Uint8Array | ArrayBuffer\n): string {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(\n normalizeWorkspaceFilePreviewBytes(bytes)\n );\n}\n\nexport function normalizeWorkspaceFilePreviewBytes(\n value: Uint8Array | ArrayBuffer\n): Uint8Array {\n if (value instanceof Uint8Array) {\n return value;\n }\n return new Uint8Array(value);\n}\n\nexport function copyWorkspaceFilePreviewBytes(\n bytes: Uint8Array | ArrayBuffer\n): Uint8Array<ArrayBuffer> {\n const normalized = normalizeWorkspaceFilePreviewBytes(bytes);\n const buffer = new ArrayBuffer(normalized.byteLength);\n const copy = new Uint8Array(buffer);\n copy.set(normalized);\n return copy;\n}\n\nexport function looksLikeBinaryText(content: string): boolean {\n if (content.length === 0) {\n return false;\n }\n\n const sample = content.slice(0, 4096);\n if (sample.includes(\"\\u0000\")) {\n return true;\n }\n\n let suspiciousControlChars = 0;\n for (let index = 0; index < sample.length; index += 1) {\n const code = sample.charCodeAt(index);\n const isAllowedWhitespace = code === 9 || code === 10 || code === 13;\n const isControlChar = code < 32 || (code >= 127 && code <= 159);\n if (isControlChar && !isAllowedWhitespace) {\n suspiciousControlChars += 1;\n }\n }\n\n return suspiciousControlChars / sample.length > 0.12;\n}\n\nexport function formatWorkspacePreviewByteLimit(sizeBytes: number): string {\n if (sizeBytes < 1024) {\n return `${sizeBytes} B`;\n }\n\n const mebibytes = sizeBytes / (1024 * 1024);\n if (Number.isInteger(mebibytes)) {\n return `${mebibytes} MiB`;\n }\n return `${mebibytes.toFixed(1)} MiB`;\n}\n","import {\n createWorkspaceFilePreviewLoadedState,\n resolveWorkspaceFilePreviewReadiness,\n type WorkspaceFilePreviewActivationTarget,\n type WorkspaceFilePreviewEntry,\n type WorkspaceFilePreviewKind,\n type WorkspaceFilePreviewReadonlyReason\n} from \"./workspaceFilePreview.ts\";\n\nexport type WorkspaceFilePreviewUnsupportedReason =\n | \"file_type\"\n | \"reader_unavailable\";\n\nexport type WorkspaceFilePreviewControllerState<TEntry> =\n | { status: \"empty\" }\n | { entry: TEntry; status: \"directory\" }\n | { entry: TEntry; status: \"loading\" }\n | {\n content: string;\n entry: TEntry;\n previewSizeBytes: number;\n status: \"text\";\n }\n | {\n entry: TEntry;\n objectUrl: string;\n previewSizeBytes: number;\n status: \"image\";\n }\n | {\n entry: TEntry;\n objectUrl: string;\n previewSizeBytes: number;\n status: \"video\";\n }\n | {\n entry: TEntry;\n maxSizeBytes?: number;\n previewSizeBytes?: number;\n reason: WorkspaceFilePreviewReadonlyReason;\n status: \"readonly\";\n }\n | {\n entry: TEntry;\n reason: WorkspaceFilePreviewUnsupportedReason;\n status: \"unsupported\";\n }\n | { entry: TEntry; error: unknown; status: \"error\" };\n\nexport interface WorkspaceFilePreviewReadInput<TEntry> {\n entry: TEntry;\n signal: AbortSignal;\n target: WorkspaceFilePreviewActivationTarget;\n}\n\nexport interface WorkspaceFilePreviewReadResult {\n bytes: Uint8Array | ArrayBuffer;\n contentType?: string | null;\n kind?: WorkspaceFilePreviewKind;\n}\n\nexport type WorkspaceFilePreviewReadOutput =\n | WorkspaceFilePreviewReadResult\n | Uint8Array\n | ArrayBuffer;\n\nexport interface WorkspaceFilePreviewObjectUrlFactory {\n create(bytes: Uint8Array<ArrayBuffer>, contentType: string): string;\n revoke(objectUrl: string): void;\n}\n\nexport interface CreateWorkspaceFilePreviewControllerInput<TEntry> {\n /**\n * Optional source capability gate. `false` blocks every non-directory read;\n * `true` also lets the reader classify locally unsupported files. When\n * omitted, the controller relies on local file classification.\n */\n canReadEntry?: (entry: TEntry) => boolean;\n getEntryKey?: (entry: TEntry) => string;\n objectUrls?: WorkspaceFilePreviewObjectUrlFactory;\n read?: (\n input: WorkspaceFilePreviewReadInput<TEntry>\n ) => Promise<WorkspaceFilePreviewReadOutput | null>;\n toPreviewEntry: (entry: TEntry) => WorkspaceFilePreviewEntry;\n}\n\nexport interface WorkspaceFilePreviewController<TEntry> {\n dispose(): void;\n getSnapshot(): WorkspaceFilePreviewControllerState<TEntry>;\n reload(): Promise<void>;\n setEntry(entry: TEntry | null): Promise<void>;\n subscribe(listener: () => void): () => void;\n}\n\nexport function createWorkspaceFilePreviewController<TEntry>(\n input: CreateWorkspaceFilePreviewControllerInput<TEntry>\n): WorkspaceFilePreviewController<TEntry> {\n return new WorkspaceFilePreviewControllerImpl(input);\n}\n\nclass WorkspaceFilePreviewControllerImpl<\n TEntry\n> implements WorkspaceFilePreviewController<TEntry> {\n private activeEntry: TEntry | null = null;\n private activeEntryKey: string | null = null;\n private abortController: AbortController | null = null;\n private disposed = false;\n private generation = 0;\n private loadPromise: Promise<void> = Promise.resolve();\n private objectUrl: string | null = null;\n private readonly listeners = new Set<() => void>();\n private readonly input: CreateWorkspaceFilePreviewControllerInput<TEntry>;\n private readonly objectUrls: WorkspaceFilePreviewObjectUrlFactory;\n private state: WorkspaceFilePreviewControllerState<TEntry> = {\n status: \"empty\"\n };\n\n constructor(input: CreateWorkspaceFilePreviewControllerInput<TEntry>) {\n this.input = input;\n this.objectUrls = input.objectUrls ?? browserWorkspaceFilePreviewObjectUrls;\n }\n\n dispose(): void {\n if (this.disposed) {\n return;\n }\n this.disposed = true;\n this.cancelLoad();\n this.revokeObjectUrl();\n this.listeners.clear();\n }\n\n getSnapshot(): WorkspaceFilePreviewControllerState<TEntry> {\n return this.state;\n }\n\n reload(): Promise<void> {\n if (this.disposed || !this.activeEntry) {\n return Promise.resolve();\n }\n return this.start(this.activeEntry);\n }\n\n setEntry(entry: TEntry | null): Promise<void> {\n if (this.disposed) {\n return Promise.resolve();\n }\n\n const nextKey = entry ? this.resolveEntryKey(entry) : null;\n if (nextKey === this.activeEntryKey) {\n return this.loadPromise;\n }\n\n this.activeEntry = entry;\n this.activeEntryKey = nextKey;\n if (!entry) {\n this.cancelLoad();\n this.revokeObjectUrl();\n this.updateState({ status: \"empty\" });\n this.loadPromise = Promise.resolve();\n return this.loadPromise;\n }\n return this.start(entry);\n }\n\n subscribe(listener: () => void): () => void {\n if (this.disposed) {\n return () => {};\n }\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private cancelLoad(): void {\n this.generation += 1;\n this.abortController?.abort();\n this.abortController = null;\n }\n\n private isStale(generation: number): boolean {\n return this.disposed || generation !== this.generation;\n }\n\n private async load(\n entry: TEntry,\n previewEntry: WorkspaceFilePreviewEntry,\n target: WorkspaceFilePreviewActivationTarget,\n generation: number,\n abortController: AbortController\n ): Promise<void> {\n try {\n const result = await this.input.read?.({\n entry,\n signal: abortController.signal,\n target\n });\n if (this.isStale(generation)) {\n return;\n }\n if (!result) {\n this.updateState({\n entry,\n reason: \"file_type\",\n status: \"unsupported\"\n });\n return;\n }\n\n const loaded = createWorkspaceFilePreviewLoadedState({\n bytes:\n result instanceof Uint8Array || result instanceof ArrayBuffer\n ? result\n : result.bytes,\n contentType:\n result instanceof Uint8Array || result instanceof ArrayBuffer\n ? undefined\n : result.contentType,\n entry: previewEntry,\n target:\n result instanceof Uint8Array || result instanceof ArrayBuffer\n ? target\n : { ...target, fileKind: result.kind ?? target.fileKind }\n });\n if (this.isStale(generation)) {\n return;\n }\n\n if (loaded.status === \"image\" || loaded.status === \"video\") {\n const previewSizeBytes = loaded.bytes.byteLength;\n const objectUrl = this.objectUrls.create(\n loaded.bytes,\n loaded.contentType\n );\n if (this.isStale(generation)) {\n this.objectUrls.revoke(objectUrl);\n return;\n }\n this.objectUrl = objectUrl;\n this.updateState({\n entry,\n objectUrl,\n previewSizeBytes,\n status: loaded.status\n });\n return;\n }\n if (loaded.status === \"text\") {\n this.updateState({\n content: loaded.content,\n entry,\n previewSizeBytes: byteLengthOfPreviewReadResult(result),\n status: \"text\"\n });\n return;\n }\n this.updateState({\n entry,\n previewSizeBytes: byteLengthOfPreviewReadResult(result),\n reason: loaded.reason,\n ...(loaded.maxSizeBytes === undefined\n ? {}\n : { maxSizeBytes: loaded.maxSizeBytes }),\n status: \"readonly\"\n });\n } catch (error) {\n if (this.isStale(generation)) {\n return;\n }\n this.updateState({ entry, error, status: \"error\" });\n } finally {\n if (!this.isStale(generation)) {\n this.abortController = null;\n }\n }\n }\n\n private resolveEntryKey(entry: TEntry): string {\n if (this.input.getEntryKey) {\n return this.input.getEntryKey(entry);\n }\n const previewEntry = this.input.toPreviewEntry(entry);\n return [\n previewEntry.kind,\n previewEntry.path,\n previewEntry.name ?? \"\",\n previewEntry.displayName ?? \"\",\n previewEntry.sizeBytes ?? \"\",\n previewEntry.mtimeMs ?? \"\"\n ].join(\"\\0\");\n }\n\n private revokeObjectUrl(): void {\n if (!this.objectUrl) {\n return;\n }\n this.objectUrls.revoke(this.objectUrl);\n this.objectUrl = null;\n }\n\n private start(entry: TEntry): Promise<void> {\n this.cancelLoad();\n this.revokeObjectUrl();\n\n const previewEntry = this.input.toPreviewEntry(entry);\n const readiness = resolveWorkspaceFilePreviewReadiness(previewEntry);\n\n if (readiness.status === \"directory\") {\n this.updateState({ entry, status: \"directory\" });\n this.loadPromise = Promise.resolve();\n return this.loadPromise;\n }\n\n const canRead = this.input.canReadEntry?.(entry);\n if (canRead === false) {\n this.updateState({\n entry,\n reason: \"reader_unavailable\",\n status: \"unsupported\"\n });\n this.loadPromise = Promise.resolve();\n return this.loadPromise;\n }\n\n if (readiness.status === \"unsupported\") {\n if (canRead !== true) {\n this.updateState({\n entry,\n reason: \"file_type\",\n status: \"unsupported\"\n });\n this.loadPromise = Promise.resolve();\n return this.loadPromise;\n }\n if (!this.input.read) {\n this.updateState({\n entry,\n reason: \"reader_unavailable\",\n status: \"unsupported\"\n });\n this.loadPromise = Promise.resolve();\n return this.loadPromise;\n }\n const target: WorkspaceFilePreviewActivationTarget = {\n fileKind: \"text\",\n name:\n previewEntry.name ??\n previewEntry.displayName ??\n previewEntry.path.split(\"/\").pop() ??\n previewEntry.path,\n path: previewEntry.path,\n ...(previewEntry.mtimeMs === undefined\n ? {}\n : { mtimeMs: previewEntry.mtimeMs }),\n ...(previewEntry.sizeBytes === undefined\n ? {}\n : { sizeBytes: previewEntry.sizeBytes })\n };\n const abortController = new AbortController();\n this.abortController = abortController;\n const generation = this.generation;\n this.updateState({ entry, status: \"loading\" });\n this.loadPromise = this.load(\n entry,\n previewEntry,\n target,\n generation,\n abortController\n );\n return this.loadPromise;\n }\n\n if (readiness.status === \"readonly\") {\n this.updateState({\n entry,\n maxSizeBytes: readiness.maxSizeBytes,\n reason: readiness.reason,\n status: \"readonly\"\n });\n this.loadPromise = Promise.resolve();\n return this.loadPromise;\n }\n\n if (!this.input.read) {\n this.updateState({\n entry,\n reason: \"reader_unavailable\",\n status: \"unsupported\"\n });\n this.loadPromise = Promise.resolve();\n return this.loadPromise;\n }\n\n const abortController = new AbortController();\n this.abortController = abortController;\n const generation = this.generation;\n this.updateState({ entry, status: \"loading\" });\n this.loadPromise = this.load(\n entry,\n previewEntry,\n readiness.target,\n generation,\n abortController\n );\n return this.loadPromise;\n }\n\n private updateState(\n state: WorkspaceFilePreviewControllerState<TEntry>\n ): void {\n if (this.disposed) {\n return;\n }\n this.state = state;\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n\nconst browserWorkspaceFilePreviewObjectUrls: WorkspaceFilePreviewObjectUrlFactory =\n {\n create(bytes, contentType) {\n return URL.createObjectURL(new Blob([bytes], { type: contentType }));\n },\n revoke(objectUrl) {\n URL.revokeObjectURL(objectUrl);\n }\n };\n\nfunction byteLengthOfPreviewReadResult(\n result: WorkspaceFilePreviewReadOutput\n): number {\n const bytes =\n result instanceof Uint8Array || result instanceof ArrayBuffer\n ? result\n : result.bytes;\n return bytes.byteLength;\n}\n"],"mappings":"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","names":["abortController","generation"]}
@@ -37,10 +37,6 @@ type WorkspaceFilePreviewLoadedState<TEntry extends WorkspaceFilePreviewEntry, T
37
37
  content: string;
38
38
  entry: TTarget;
39
39
  status: "text";
40
- } | {
41
- content: string;
42
- entry: TTarget;
43
- status: "html";
44
40
  } | {
45
41
  bytes: Uint8Array<ArrayBuffer>;
46
42
  contentType: string;
@@ -77,7 +73,6 @@ declare function createWorkspaceFilePreviewLoadedState<TEntry extends WorkspaceF
77
73
  bytes: Uint8Array | ArrayBuffer;
78
74
  contentType?: string | null;
79
75
  entry: TEntry;
80
- renderHtml?: boolean;
81
76
  target: TTarget;
82
77
  }): WorkspaceFilePreviewLoadedState<TEntry, TTarget>;
83
78
  declare function resolveWorkspaceFilePreviewName(entry: Pick<WorkspaceFilePreviewEntry, "displayName" | "name" | "path">): string;
@@ -89,4 +84,79 @@ declare function copyWorkspaceFilePreviewBytes(bytes: Uint8Array | ArrayBuffer):
89
84
  declare function looksLikeBinaryText(content: string): boolean;
90
85
  declare function formatWorkspacePreviewByteLimit(sizeBytes: number): string;
91
86
 
92
- export { type WorkspaceFilePreviewActivationTarget, type WorkspaceFilePreviewEntry, type WorkspaceFilePreviewEntryKind, type WorkspaceFilePreviewKind, type WorkspaceFilePreviewLoadedState, type WorkspaceFilePreviewReadiness, type WorkspaceFilePreviewReadonlyReason, type WorkspaceFileVisualKind, classifyWorkspaceFilePreviewKind, copyWorkspaceFilePreviewBytes, createWorkspaceFilePreviewLoadedState, decodeWorkspaceTextFile, formatWorkspacePreviewByteLimit, isWorkspaceFileBrowserOpenable, isWorkspacePreviewFileTooLarge, isWorkspaceTextFileTooLarge, looksLikeBinaryText, normalizeWorkspaceFilePreviewBytes, resolveWorkspaceFileActivationTarget, resolveWorkspaceFileExtension, resolveWorkspaceFilePreviewName, resolveWorkspaceFilePreviewReadiness, resolveWorkspaceFileVisualKind, resolveWorkspaceImageMimeType, resolveWorkspaceVideoMimeType, shouldFilterVideoPlayersForOpenWith, workspaceFilePreviewMaxBytes, workspaceFileTextMaxBytes, workspaceFileVideoHandlerCollisionExtensions };
87
+ type WorkspaceFilePreviewUnsupportedReason = "file_type" | "reader_unavailable";
88
+ type WorkspaceFilePreviewControllerState<TEntry> = {
89
+ status: "empty";
90
+ } | {
91
+ entry: TEntry;
92
+ status: "directory";
93
+ } | {
94
+ entry: TEntry;
95
+ status: "loading";
96
+ } | {
97
+ content: string;
98
+ entry: TEntry;
99
+ previewSizeBytes: number;
100
+ status: "text";
101
+ } | {
102
+ entry: TEntry;
103
+ objectUrl: string;
104
+ previewSizeBytes: number;
105
+ status: "image";
106
+ } | {
107
+ entry: TEntry;
108
+ objectUrl: string;
109
+ previewSizeBytes: number;
110
+ status: "video";
111
+ } | {
112
+ entry: TEntry;
113
+ maxSizeBytes?: number;
114
+ previewSizeBytes?: number;
115
+ reason: WorkspaceFilePreviewReadonlyReason;
116
+ status: "readonly";
117
+ } | {
118
+ entry: TEntry;
119
+ reason: WorkspaceFilePreviewUnsupportedReason;
120
+ status: "unsupported";
121
+ } | {
122
+ entry: TEntry;
123
+ error: unknown;
124
+ status: "error";
125
+ };
126
+ interface WorkspaceFilePreviewReadInput<TEntry> {
127
+ entry: TEntry;
128
+ signal: AbortSignal;
129
+ target: WorkspaceFilePreviewActivationTarget;
130
+ }
131
+ interface WorkspaceFilePreviewReadResult {
132
+ bytes: Uint8Array | ArrayBuffer;
133
+ contentType?: string | null;
134
+ kind?: WorkspaceFilePreviewKind;
135
+ }
136
+ type WorkspaceFilePreviewReadOutput = WorkspaceFilePreviewReadResult | Uint8Array | ArrayBuffer;
137
+ interface WorkspaceFilePreviewObjectUrlFactory {
138
+ create(bytes: Uint8Array<ArrayBuffer>, contentType: string): string;
139
+ revoke(objectUrl: string): void;
140
+ }
141
+ interface CreateWorkspaceFilePreviewControllerInput<TEntry> {
142
+ /**
143
+ * Optional source capability gate. `false` blocks every non-directory read;
144
+ * `true` also lets the reader classify locally unsupported files. When
145
+ * omitted, the controller relies on local file classification.
146
+ */
147
+ canReadEntry?: (entry: TEntry) => boolean;
148
+ getEntryKey?: (entry: TEntry) => string;
149
+ objectUrls?: WorkspaceFilePreviewObjectUrlFactory;
150
+ read?: (input: WorkspaceFilePreviewReadInput<TEntry>) => Promise<WorkspaceFilePreviewReadOutput | null>;
151
+ toPreviewEntry: (entry: TEntry) => WorkspaceFilePreviewEntry;
152
+ }
153
+ interface WorkspaceFilePreviewController<TEntry> {
154
+ dispose(): void;
155
+ getSnapshot(): WorkspaceFilePreviewControllerState<TEntry>;
156
+ reload(): Promise<void>;
157
+ setEntry(entry: TEntry | null): Promise<void>;
158
+ subscribe(listener: () => void): () => void;
159
+ }
160
+ declare function createWorkspaceFilePreviewController<TEntry>(input: CreateWorkspaceFilePreviewControllerInput<TEntry>): WorkspaceFilePreviewController<TEntry>;
161
+
162
+ export { type CreateWorkspaceFilePreviewControllerInput, type WorkspaceFilePreviewActivationTarget, type WorkspaceFilePreviewController, type WorkspaceFilePreviewControllerState, type WorkspaceFilePreviewEntry, type WorkspaceFilePreviewEntryKind, type WorkspaceFilePreviewKind, type WorkspaceFilePreviewLoadedState, type WorkspaceFilePreviewObjectUrlFactory, type WorkspaceFilePreviewReadInput, type WorkspaceFilePreviewReadOutput, type WorkspaceFilePreviewReadResult, type WorkspaceFilePreviewReadiness, type WorkspaceFilePreviewReadonlyReason, type WorkspaceFilePreviewUnsupportedReason, type WorkspaceFileVisualKind, classifyWorkspaceFilePreviewKind, copyWorkspaceFilePreviewBytes, createWorkspaceFilePreviewController, createWorkspaceFilePreviewLoadedState, decodeWorkspaceTextFile, formatWorkspacePreviewByteLimit, isWorkspaceFileBrowserOpenable, isWorkspacePreviewFileTooLarge, isWorkspaceTextFileTooLarge, looksLikeBinaryText, normalizeWorkspaceFilePreviewBytes, resolveWorkspaceFileActivationTarget, resolveWorkspaceFileExtension, resolveWorkspaceFilePreviewName, resolveWorkspaceFilePreviewReadiness, resolveWorkspaceFileVisualKind, resolveWorkspaceImageMimeType, resolveWorkspaceVideoMimeType, shouldFilterVideoPlayersForOpenWith, workspaceFilePreviewMaxBytes, workspaceFileTextMaxBytes, workspaceFileVideoHandlerCollisionExtensions };
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  classifyWorkspaceFilePreviewKind,
3
3
  copyWorkspaceFilePreviewBytes,
4
+ createWorkspaceFilePreviewController,
4
5
  createWorkspaceFilePreviewLoadedState,
5
6
  decodeWorkspaceTextFile,
6
7
  formatWorkspacePreviewByteLimit,
@@ -20,10 +21,11 @@ import {
20
21
  workspaceFilePreviewMaxBytes,
21
22
  workspaceFileTextMaxBytes,
22
23
  workspaceFileVideoHandlerCollisionExtensions
23
- } from "../chunk-FYAXIZ3F.js";
24
+ } from "../chunk-SUQYO7VX.js";
24
25
  export {
25
26
  classifyWorkspaceFilePreviewKind,
26
27
  copyWorkspaceFilePreviewBytes,
28
+ createWorkspaceFilePreviewController,
27
29
  createWorkspaceFilePreviewLoadedState,
28
30
  decodeWorkspaceTextFile,
29
31
  formatWorkspacePreviewByteLimit,
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { WorkspaceFilePreviewActivationTarget, WorkspaceFilePreviewEntry, WorkspaceFilePreviewEntryKind, WorkspaceFilePreviewKind, WorkspaceFilePreviewLoadedState, WorkspaceFilePreviewReadiness, WorkspaceFilePreviewReadonlyReason, WorkspaceFileVisualKind, classifyWorkspaceFilePreviewKind, copyWorkspaceFilePreviewBytes, createWorkspaceFilePreviewLoadedState, decodeWorkspaceTextFile, formatWorkspacePreviewByteLimit, isWorkspaceFileBrowserOpenable, isWorkspacePreviewFileTooLarge, isWorkspaceTextFileTooLarge, looksLikeBinaryText, normalizeWorkspaceFilePreviewBytes, resolveWorkspaceFileActivationTarget, resolveWorkspaceFileExtension, resolveWorkspaceFilePreviewName, resolveWorkspaceFilePreviewReadiness, resolveWorkspaceFileVisualKind, resolveWorkspaceImageMimeType, resolveWorkspaceVideoMimeType, shouldFilterVideoPlayersForOpenWith, workspaceFilePreviewMaxBytes, workspaceFileTextMaxBytes, workspaceFileVideoHandlerCollisionExtensions } from './core/index.js';
1
+ export { CreateWorkspaceFilePreviewControllerInput, WorkspaceFilePreviewActivationTarget, WorkspaceFilePreviewController, WorkspaceFilePreviewControllerState, WorkspaceFilePreviewEntry, WorkspaceFilePreviewEntryKind, WorkspaceFilePreviewKind, WorkspaceFilePreviewLoadedState, WorkspaceFilePreviewObjectUrlFactory, WorkspaceFilePreviewReadInput, WorkspaceFilePreviewReadOutput, WorkspaceFilePreviewReadResult, WorkspaceFilePreviewReadiness, WorkspaceFilePreviewReadonlyReason, WorkspaceFilePreviewUnsupportedReason, WorkspaceFileVisualKind, classifyWorkspaceFilePreviewKind, copyWorkspaceFilePreviewBytes, createWorkspaceFilePreviewController, createWorkspaceFilePreviewLoadedState, decodeWorkspaceTextFile, formatWorkspacePreviewByteLimit, isWorkspaceFileBrowserOpenable, isWorkspacePreviewFileTooLarge, isWorkspaceTextFileTooLarge, looksLikeBinaryText, normalizeWorkspaceFilePreviewBytes, resolveWorkspaceFileActivationTarget, resolveWorkspaceFileExtension, resolveWorkspaceFilePreviewName, resolveWorkspaceFilePreviewReadiness, resolveWorkspaceFileVisualKind, resolveWorkspaceImageMimeType, resolveWorkspaceVideoMimeType, shouldFilterVideoPlayersForOpenWith, workspaceFilePreviewMaxBytes, workspaceFileTextMaxBytes, workspaceFileVideoHandlerCollisionExtensions } from './core/index.js';
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  classifyWorkspaceFilePreviewKind,
3
3
  copyWorkspaceFilePreviewBytes,
4
+ createWorkspaceFilePreviewController,
4
5
  createWorkspaceFilePreviewLoadedState,
5
6
  decodeWorkspaceTextFile,
6
7
  formatWorkspacePreviewByteLimit,
@@ -20,10 +21,11 @@ import {
20
21
  workspaceFilePreviewMaxBytes,
21
22
  workspaceFileTextMaxBytes,
22
23
  workspaceFileVideoHandlerCollisionExtensions
23
- } from "./chunk-FYAXIZ3F.js";
24
+ } from "./chunk-SUQYO7VX.js";
24
25
  export {
25
26
  classifyWorkspaceFilePreviewKind,
26
27
  copyWorkspaceFilePreviewBytes,
28
+ createWorkspaceFilePreviewController,
27
29
  createWorkspaceFilePreviewLoadedState,
28
30
  decodeWorkspaceTextFile,
29
31
  formatWorkspacePreviewByteLimit,
@@ -12,10 +12,6 @@ type WorkspaceFilePreviewSurfaceState<TEntry> = {
12
12
  content: string;
13
13
  entry: TEntry;
14
14
  status: "text";
15
- } | {
16
- content: string;
17
- entry: TEntry;
18
- status: "html";
19
15
  } | {
20
16
  entry: TEntry;
21
17
  objectUrl: string;
@@ -37,26 +33,17 @@ type WorkspaceFilePreviewSurfaceState<TEntry> = {
37
33
  message: string;
38
34
  status: "error";
39
35
  };
36
+ type WorkspaceFilePreviewSurfaceVariant = "canvas" | "compact" | "detail";
40
37
  interface WorkspaceFilePreviewSurfaceProps<TEntry> {
41
38
  directoryMessage: string;
42
39
  emptyMessage: string;
43
- frameClassName: string;
44
- htmlClassName?: string;
45
- htmlFrameClassName?: string;
46
- htmlTitle?: (entry: TEntry) => string;
47
40
  imageAlt: (entry: TEntry) => string;
48
- imageFrameClassName?: string;
49
- imageClassName?: string;
50
41
  loadingIndicator: ReactNode;
51
42
  loadingMessage: string;
52
- messageClassName?: string;
53
43
  renderIcon: (entry: TEntry) => ReactNode;
54
44
  state: WorkspaceFilePreviewSurfaceState<TEntry>;
55
- textClassName?: string;
56
- textFrameClassName?: string;
57
- videoClassName?: string;
58
- videoFrameClassName?: string;
45
+ variant: WorkspaceFilePreviewSurfaceVariant;
59
46
  }
60
- declare function WorkspaceFilePreviewSurface<TEntry>({ directoryMessage, emptyMessage, frameClassName, htmlClassName, htmlFrameClassName, htmlTitle, imageAlt, imageClassName, imageFrameClassName, loadingIndicator, loadingMessage, messageClassName, renderIcon, state, textClassName, textFrameClassName, videoClassName, videoFrameClassName }: WorkspaceFilePreviewSurfaceProps<TEntry>): ReactElement;
47
+ declare function WorkspaceFilePreviewSurface<TEntry>({ directoryMessage, emptyMessage, imageAlt, loadingIndicator, loadingMessage, renderIcon, state, variant }: WorkspaceFilePreviewSurfaceProps<TEntry>): ReactElement;
61
48
 
62
- export { WorkspaceFilePreviewSurface, type WorkspaceFilePreviewSurfaceProps, type WorkspaceFilePreviewSurfaceState };
49
+ export { WorkspaceFilePreviewSurface, type WorkspaceFilePreviewSurfaceProps, type WorkspaceFilePreviewSurfaceState, type WorkspaceFilePreviewSurfaceVariant };
@@ -3,31 +3,22 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  function WorkspaceFilePreviewSurface({
4
4
  directoryMessage,
5
5
  emptyMessage,
6
- frameClassName,
7
- htmlClassName = "h-full w-full border-0 bg-white",
8
- htmlFrameClassName,
9
- htmlTitle,
10
6
  imageAlt,
11
- imageClassName = "max-h-full max-w-full rounded-[6px] object-contain",
12
- imageFrameClassName,
13
7
  loadingIndicator,
14
8
  loadingMessage,
15
- messageClassName = "max-w-[24ch] text-center text-[13px] leading-5 text-[var(--text-tertiary)] [overflow-wrap:anywhere]",
16
9
  renderIcon,
17
10
  state,
18
- textClassName = "h-full overflow-auto p-4 text-[11px] leading-5 whitespace-pre-wrap break-words text-[var(--text-primary)]",
19
- textFrameClassName,
20
- videoClassName = "block max-h-full max-w-full rounded-[6px] object-contain",
21
- videoFrameClassName
11
+ variant
22
12
  }) {
13
+ const styles = workspaceFilePreviewSurfaceStyles[variant];
23
14
  switch (state.status) {
24
15
  case "directory":
25
- return /* @__PURE__ */ jsx(WorkspaceFilePreviewFrame, { className: frameClassName, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-2.5 text-center text-[13px] leading-5 text-[var(--text-tertiary)]", children: [
16
+ return /* @__PURE__ */ jsx(WorkspaceFilePreviewFrame, { className: styles.frame, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-2.5 text-center text-[13px] leading-5 text-[var(--text-tertiary)]", children: [
26
17
  renderIcon(state.entry),
27
18
  /* @__PURE__ */ jsx("span", { children: directoryMessage })
28
19
  ] }) });
29
20
  case "loading":
30
- return /* @__PURE__ */ jsx(WorkspaceFilePreviewFrame, { className: frameClassName, children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-4 text-center text-[13px] text-[var(--text-tertiary)]", children: [
21
+ return /* @__PURE__ */ jsx(WorkspaceFilePreviewFrame, { className: styles.frame, children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-4 text-center text-[13px] text-[var(--text-tertiary)]", children: [
31
22
  loadingIndicator,
32
23
  /* @__PURE__ */ jsx("span", { children: loadingMessage })
33
24
  ] }) });
@@ -35,12 +26,12 @@ function WorkspaceFilePreviewSurface({
35
26
  return /* @__PURE__ */ jsx(
36
27
  WorkspaceFilePreviewFrame,
37
28
  {
38
- className: joinClassNames(frameClassName, imageFrameClassName),
29
+ className: joinClassNames(styles.frame, styles.imageFrame),
39
30
  children: /* @__PURE__ */ jsx(
40
31
  "img",
41
32
  {
42
33
  alt: imageAlt(state.entry),
43
- className: imageClassName,
34
+ className: styles.image,
44
35
  src: state.objectUrl
45
36
  }
46
37
  )
@@ -50,36 +41,20 @@ function WorkspaceFilePreviewSurface({
50
41
  return /* @__PURE__ */ jsx(
51
42
  WorkspaceFilePreviewFrame,
52
43
  {
53
- className: joinClassNames(frameClassName, textFrameClassName),
54
- children: /* @__PURE__ */ jsx("pre", { className: textClassName, children: state.content })
55
- }
56
- );
57
- case "html":
58
- return /* @__PURE__ */ jsx(
59
- WorkspaceFilePreviewFrame,
60
- {
61
- className: joinClassNames(frameClassName, htmlFrameClassName),
62
- children: /* @__PURE__ */ jsx(
63
- "iframe",
64
- {
65
- className: htmlClassName,
66
- sandbox: "allow-forms allow-scripts",
67
- srcDoc: state.content,
68
- title: htmlTitle?.(state.entry) ?? "HTML preview"
69
- }
70
- )
44
+ className: joinClassNames(styles.frame, styles.textFrame),
45
+ children: /* @__PURE__ */ jsx("pre", { className: styles.text, children: state.content })
71
46
  }
72
47
  );
73
48
  case "video":
74
49
  return /* @__PURE__ */ jsx(
75
50
  WorkspaceFilePreviewFrame,
76
51
  {
77
- className: joinClassNames(frameClassName, videoFrameClassName),
52
+ className: joinClassNames(styles.frame, styles.videoFrame),
78
53
  children: /* @__PURE__ */ jsx(
79
54
  "video",
80
55
  {
81
56
  "aria-label": imageAlt(state.entry),
82
- className: videoClassName,
57
+ className: styles.video,
83
58
  muted: true,
84
59
  playsInline: true,
85
60
  preload: "metadata",
@@ -97,14 +72,46 @@ function WorkspaceFilePreviewSurface({
97
72
  case "readonly":
98
73
  case "unsupported":
99
74
  case "error":
100
- return /* @__PURE__ */ jsx(WorkspaceFilePreviewFrame, { className: frameClassName, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-3 px-4 text-center text-[13px] text-[var(--text-tertiary)]", children: [
75
+ return /* @__PURE__ */ jsx(WorkspaceFilePreviewFrame, { className: styles.frame, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-3 px-4 text-center text-[13px] text-[var(--text-tertiary)]", children: [
101
76
  renderIcon(state.entry),
102
- /* @__PURE__ */ jsx("span", { className: messageClassName, children: state.message })
77
+ /* @__PURE__ */ jsx("span", { className: styles.message, children: state.message })
103
78
  ] }) });
104
79
  case "empty":
105
- return /* @__PURE__ */ jsx(WorkspaceFilePreviewFrame, { className: frameClassName, children: /* @__PURE__ */ jsx("span", { className: messageClassName, children: emptyMessage }) });
80
+ return /* @__PURE__ */ jsx(WorkspaceFilePreviewFrame, { className: styles.frame, children: /* @__PURE__ */ jsx("span", { className: styles.message, children: emptyMessage }) });
106
81
  }
107
82
  }
83
+ var workspaceFilePreviewSurfaceStyles = {
84
+ compact: {
85
+ frame: "flex aspect-[3/2] w-full flex-col items-center justify-center overflow-hidden rounded-[8px] border border-[var(--line-2,var(--border-2))] bg-[var(--transparency-block)] p-0 text-center",
86
+ image: "max-h-full max-w-full rounded-[6px] object-contain",
87
+ imageFrame: "p-3",
88
+ message: "mx-auto max-w-[24ch] text-center text-[13px] leading-5 text-[var(--text-secondary)] [overflow-wrap:anywhere]",
89
+ text: "h-full w-full overflow-auto p-3 text-left text-[11px] leading-5 whitespace-pre-wrap break-words text-[var(--text-primary)]",
90
+ textFrame: "items-stretch justify-stretch",
91
+ video: "block max-h-full max-w-full rounded-[6px] object-contain",
92
+ videoFrame: "p-3"
93
+ },
94
+ detail: {
95
+ frame: "flex h-60 min-h-60 max-h-60 items-center justify-center overflow-hidden rounded-lg bg-[var(--transparency-block)]",
96
+ image: "max-h-full max-w-full rounded-[6px] object-contain",
97
+ imageFrame: "p-4",
98
+ message: "max-w-[24ch] text-center text-[13px] leading-5 text-[var(--text-tertiary)] [overflow-wrap:anywhere]",
99
+ text: "h-full overflow-auto p-4 text-[11px] leading-5 whitespace-pre-wrap break-words text-[var(--text-primary)]",
100
+ textFrame: "items-stretch justify-stretch",
101
+ video: "block max-h-full max-w-full rounded-[6px] object-contain",
102
+ videoFrame: "p-4"
103
+ },
104
+ canvas: {
105
+ frame: "flex h-full min-h-0 min-w-0 w-full items-center justify-center overflow-hidden bg-[var(--background-fronted)] text-[var(--text-tertiary)]",
106
+ image: "block max-h-full max-w-full object-contain",
107
+ imageFrame: "overflow-auto p-3",
108
+ message: "max-w-[24ch] text-center text-[13px] leading-5 text-[var(--text-tertiary)] [overflow-wrap:anywhere]",
109
+ text: "m-0 h-full min-h-0 min-w-0 w-full overflow-auto whitespace-pre-wrap break-words p-3 font-[var(--tsh-font-mono)] text-[11px] leading-[18px] text-[var(--text-secondary)]",
110
+ textFrame: "items-stretch justify-stretch",
111
+ video: "block max-h-full max-w-full object-contain",
112
+ videoFrame: "overflow-auto p-3"
113
+ }
114
+ };
108
115
  function WorkspaceFilePreviewFrame({
109
116
  children,
110
117
  className
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/workspaceFilePreviewSurface.tsx"],"sourcesContent":["import type { ReactElement, ReactNode } from \"react\";\n\nexport type WorkspaceFilePreviewSurfaceState<TEntry> =\n | { status: \"empty\" }\n | { entry: TEntry; status: \"directory\" }\n | { entry: TEntry; status: \"loading\" }\n | { content: string; entry: TEntry; status: \"text\" }\n | { content: string; entry: TEntry; status: \"html\" }\n | { entry: TEntry; objectUrl: string; status: \"image\" }\n | { entry: TEntry; objectUrl: string; status: \"video\" }\n | { entry: TEntry; message: string; status: \"readonly\" }\n | { entry: TEntry; message: string; status: \"unsupported\" }\n | { entry: TEntry; message: string; status: \"error\" };\n\nexport interface WorkspaceFilePreviewSurfaceProps<TEntry> {\n directoryMessage: string;\n emptyMessage: string;\n frameClassName: string;\n htmlClassName?: string;\n htmlFrameClassName?: string;\n htmlTitle?: (entry: TEntry) => string;\n imageAlt: (entry: TEntry) => string;\n imageFrameClassName?: string;\n imageClassName?: string;\n loadingIndicator: ReactNode;\n loadingMessage: string;\n messageClassName?: string;\n renderIcon: (entry: TEntry) => ReactNode;\n state: WorkspaceFilePreviewSurfaceState<TEntry>;\n textClassName?: string;\n textFrameClassName?: string;\n videoClassName?: string;\n videoFrameClassName?: string;\n}\n\nexport function WorkspaceFilePreviewSurface<TEntry>({\n directoryMessage,\n emptyMessage,\n frameClassName,\n htmlClassName = \"h-full w-full border-0 bg-white\",\n htmlFrameClassName,\n htmlTitle,\n imageAlt,\n imageClassName = \"max-h-full max-w-full rounded-[6px] object-contain\",\n imageFrameClassName,\n loadingIndicator,\n loadingMessage,\n messageClassName = \"max-w-[24ch] text-center text-[13px] leading-5 text-[var(--text-tertiary)] [overflow-wrap:anywhere]\",\n renderIcon,\n state,\n textClassName = \"h-full overflow-auto p-4 text-[11px] leading-5 whitespace-pre-wrap break-words text-[var(--text-primary)]\",\n textFrameClassName,\n videoClassName = \"block max-h-full max-w-full rounded-[6px] object-contain\",\n videoFrameClassName\n}: WorkspaceFilePreviewSurfaceProps<TEntry>): ReactElement {\n switch (state.status) {\n case \"directory\":\n return (\n <WorkspaceFilePreviewFrame className={frameClassName}>\n <div className=\"flex flex-col items-center justify-center gap-2.5 text-center text-[13px] leading-5 text-[var(--text-tertiary)]\">\n {renderIcon(state.entry)}\n <span>{directoryMessage}</span>\n </div>\n </WorkspaceFilePreviewFrame>\n );\n case \"loading\":\n return (\n <WorkspaceFilePreviewFrame className={frameClassName}>\n <div className=\"space-y-3 px-4 text-center text-[13px] text-[var(--text-tertiary)]\">\n {loadingIndicator}\n <span>{loadingMessage}</span>\n </div>\n </WorkspaceFilePreviewFrame>\n );\n case \"image\":\n return (\n <WorkspaceFilePreviewFrame\n className={joinClassNames(frameClassName, imageFrameClassName)}\n >\n <img\n alt={imageAlt(state.entry)}\n className={imageClassName}\n src={state.objectUrl}\n />\n </WorkspaceFilePreviewFrame>\n );\n case \"text\":\n return (\n <WorkspaceFilePreviewFrame\n className={joinClassNames(frameClassName, textFrameClassName)}\n >\n <pre className={textClassName}>{state.content}</pre>\n </WorkspaceFilePreviewFrame>\n );\n case \"html\":\n return (\n <WorkspaceFilePreviewFrame\n className={joinClassNames(frameClassName, htmlFrameClassName)}\n >\n <iframe\n className={htmlClassName}\n sandbox=\"allow-forms allow-scripts\"\n srcDoc={state.content}\n title={htmlTitle?.(state.entry) ?? \"HTML preview\"}\n />\n </WorkspaceFilePreviewFrame>\n );\n case \"video\":\n return (\n <WorkspaceFilePreviewFrame\n className={joinClassNames(frameClassName, videoFrameClassName)}\n >\n <video\n aria-label={imageAlt(state.entry)}\n className={videoClassName}\n muted\n playsInline\n preload=\"metadata\"\n src={state.objectUrl}\n onLoadedMetadata={(event) => {\n const video = event.currentTarget;\n if (video.duration > 0 && video.currentTime === 0) {\n video.currentTime = Math.min(0.1, video.duration / 2);\n }\n }}\n />\n </WorkspaceFilePreviewFrame>\n );\n case \"readonly\":\n case \"unsupported\":\n case \"error\":\n return (\n <WorkspaceFilePreviewFrame className={frameClassName}>\n <div className=\"flex flex-col items-center justify-center gap-3 px-4 text-center text-[13px] text-[var(--text-tertiary)]\">\n {renderIcon(state.entry)}\n <span className={messageClassName}>{state.message}</span>\n </div>\n </WorkspaceFilePreviewFrame>\n );\n case \"empty\":\n return (\n <WorkspaceFilePreviewFrame className={frameClassName}>\n <span className={messageClassName}>{emptyMessage}</span>\n </WorkspaceFilePreviewFrame>\n );\n }\n}\n\nfunction WorkspaceFilePreviewFrame({\n children,\n className\n}: {\n children: ReactNode;\n className: string;\n}): ReactElement {\n return <div className={className}>{children}</div>;\n}\n\nfunction joinClassNames(\n ...classNames: Array<string | false | null | undefined>\n): string {\n return classNames.filter(Boolean).join(\" \");\n}\n"],"mappings":";AA2DU,SAEE,KAFF;AAxBH,SAAS,4BAAoC;AAAA,EAClD;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA,mBAAmB;AAAA,EACnB;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA,iBAAiB;AAAA,EACjB;AACF,GAA2D;AACzD,UAAQ,MAAM,QAAQ;AAAA,IACpB,KAAK;AACH,aACE,oBAAC,6BAA0B,WAAW,gBACpC,+BAAC,SAAI,WAAU,mHACZ;AAAA,mBAAW,MAAM,KAAK;AAAA,QACvB,oBAAC,UAAM,4BAAiB;AAAA,SAC1B,GACF;AAAA,IAEJ,KAAK;AACH,aACE,oBAAC,6BAA0B,WAAW,gBACpC,+BAAC,SAAI,WAAU,sEACZ;AAAA;AAAA,QACD,oBAAC,UAAM,0BAAe;AAAA,SACxB,GACF;AAAA,IAEJ,KAAK;AACH,aACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,eAAe,gBAAgB,mBAAmB;AAAA,UAE7D;AAAA,YAAC;AAAA;AAAA,cACC,KAAK,SAAS,MAAM,KAAK;AAAA,cACzB,WAAW;AAAA,cACX,KAAK,MAAM;AAAA;AAAA,UACb;AAAA;AAAA,MACF;AAAA,IAEJ,KAAK;AACH,aACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,eAAe,gBAAgB,kBAAkB;AAAA,UAE5D,8BAAC,SAAI,WAAW,eAAgB,gBAAM,SAAQ;AAAA;AAAA,MAChD;AAAA,IAEJ,KAAK;AACH,aACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,eAAe,gBAAgB,kBAAkB;AAAA,UAE5D;AAAA,YAAC;AAAA;AAAA,cACC,WAAW;AAAA,cACX,SAAQ;AAAA,cACR,QAAQ,MAAM;AAAA,cACd,OAAO,YAAY,MAAM,KAAK,KAAK;AAAA;AAAA,UACrC;AAAA;AAAA,MACF;AAAA,IAEJ,KAAK;AACH,aACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,eAAe,gBAAgB,mBAAmB;AAAA,UAE7D;AAAA,YAAC;AAAA;AAAA,cACC,cAAY,SAAS,MAAM,KAAK;AAAA,cAChC,WAAW;AAAA,cACX,OAAK;AAAA,cACL,aAAW;AAAA,cACX,SAAQ;AAAA,cACR,KAAK,MAAM;AAAA,cACX,kBAAkB,CAAC,UAAU;AAC3B,sBAAM,QAAQ,MAAM;AACpB,oBAAI,MAAM,WAAW,KAAK,MAAM,gBAAgB,GAAG;AACjD,wBAAM,cAAc,KAAK,IAAI,KAAK,MAAM,WAAW,CAAC;AAAA,gBACtD;AAAA,cACF;AAAA;AAAA,UACF;AAAA;AAAA,MACF;AAAA,IAEJ,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aACE,oBAAC,6BAA0B,WAAW,gBACpC,+BAAC,SAAI,WAAU,4GACZ;AAAA,mBAAW,MAAM,KAAK;AAAA,QACvB,oBAAC,UAAK,WAAW,kBAAmB,gBAAM,SAAQ;AAAA,SACpD,GACF;AAAA,IAEJ,KAAK;AACH,aACE,oBAAC,6BAA0B,WAAW,gBACpC,8BAAC,UAAK,WAAW,kBAAmB,wBAAa,GACnD;AAAA,EAEN;AACF;AAEA,SAAS,0BAA0B;AAAA,EACjC;AAAA,EACA;AACF,GAGiB;AACf,SAAO,oBAAC,SAAI,WAAuB,UAAS;AAC9C;AAEA,SAAS,kBACJ,YACK;AACR,SAAO,WAAW,OAAO,OAAO,EAAE,KAAK,GAAG;AAC5C;","names":[]}
1
+ {"version":3,"sources":["../../src/react/workspaceFilePreviewSurface.tsx"],"sourcesContent":["import type { ReactElement, ReactNode } from \"react\";\n\nexport type WorkspaceFilePreviewSurfaceState<TEntry> =\n | { status: \"empty\" }\n | { entry: TEntry; status: \"directory\" }\n | { entry: TEntry; status: \"loading\" }\n | { content: string; entry: TEntry; status: \"text\" }\n | { entry: TEntry; objectUrl: string; status: \"image\" }\n | { entry: TEntry; objectUrl: string; status: \"video\" }\n | { entry: TEntry; message: string; status: \"readonly\" }\n | { entry: TEntry; message: string; status: \"unsupported\" }\n | { entry: TEntry; message: string; status: \"error\" };\n\nexport type WorkspaceFilePreviewSurfaceVariant =\n | \"canvas\"\n | \"compact\"\n | \"detail\";\n\nexport interface WorkspaceFilePreviewSurfaceProps<TEntry> {\n directoryMessage: string;\n emptyMessage: string;\n imageAlt: (entry: TEntry) => string;\n loadingIndicator: ReactNode;\n loadingMessage: string;\n renderIcon: (entry: TEntry) => ReactNode;\n state: WorkspaceFilePreviewSurfaceState<TEntry>;\n variant: WorkspaceFilePreviewSurfaceVariant;\n}\n\nexport function WorkspaceFilePreviewSurface<TEntry>({\n directoryMessage,\n emptyMessage,\n imageAlt,\n loadingIndicator,\n loadingMessage,\n renderIcon,\n state,\n variant\n}: WorkspaceFilePreviewSurfaceProps<TEntry>): ReactElement {\n const styles = workspaceFilePreviewSurfaceStyles[variant];\n switch (state.status) {\n case \"directory\":\n return (\n <WorkspaceFilePreviewFrame className={styles.frame}>\n <div className=\"flex flex-col items-center justify-center gap-2.5 text-center text-[13px] leading-5 text-[var(--text-tertiary)]\">\n {renderIcon(state.entry)}\n <span>{directoryMessage}</span>\n </div>\n </WorkspaceFilePreviewFrame>\n );\n case \"loading\":\n return (\n <WorkspaceFilePreviewFrame className={styles.frame}>\n <div className=\"space-y-3 px-4 text-center text-[13px] text-[var(--text-tertiary)]\">\n {loadingIndicator}\n <span>{loadingMessage}</span>\n </div>\n </WorkspaceFilePreviewFrame>\n );\n case \"image\":\n return (\n <WorkspaceFilePreviewFrame\n className={joinClassNames(styles.frame, styles.imageFrame)}\n >\n <img\n alt={imageAlt(state.entry)}\n className={styles.image}\n src={state.objectUrl}\n />\n </WorkspaceFilePreviewFrame>\n );\n case \"text\":\n return (\n <WorkspaceFilePreviewFrame\n className={joinClassNames(styles.frame, styles.textFrame)}\n >\n <pre className={styles.text}>{state.content}</pre>\n </WorkspaceFilePreviewFrame>\n );\n case \"video\":\n return (\n <WorkspaceFilePreviewFrame\n className={joinClassNames(styles.frame, styles.videoFrame)}\n >\n <video\n aria-label={imageAlt(state.entry)}\n className={styles.video}\n muted\n playsInline\n preload=\"metadata\"\n src={state.objectUrl}\n onLoadedMetadata={(event) => {\n const video = event.currentTarget;\n if (video.duration > 0 && video.currentTime === 0) {\n video.currentTime = Math.min(0.1, video.duration / 2);\n }\n }}\n />\n </WorkspaceFilePreviewFrame>\n );\n case \"readonly\":\n case \"unsupported\":\n case \"error\":\n return (\n <WorkspaceFilePreviewFrame className={styles.frame}>\n <div className=\"flex flex-col items-center justify-center gap-3 px-4 text-center text-[13px] text-[var(--text-tertiary)]\">\n {renderIcon(state.entry)}\n <span className={styles.message}>{state.message}</span>\n </div>\n </WorkspaceFilePreviewFrame>\n );\n case \"empty\":\n return (\n <WorkspaceFilePreviewFrame className={styles.frame}>\n <span className={styles.message}>{emptyMessage}</span>\n </WorkspaceFilePreviewFrame>\n );\n }\n}\n\nconst workspaceFilePreviewSurfaceStyles: Record<\n WorkspaceFilePreviewSurfaceVariant,\n {\n frame: string;\n image: string;\n imageFrame: string;\n message: string;\n text: string;\n textFrame: string;\n video: string;\n videoFrame: string;\n }\n> = {\n compact: {\n frame:\n \"flex aspect-[3/2] w-full flex-col items-center justify-center overflow-hidden rounded-[8px] border border-[var(--line-2,var(--border-2))] bg-[var(--transparency-block)] p-0 text-center\",\n image: \"max-h-full max-w-full rounded-[6px] object-contain\",\n imageFrame: \"p-3\",\n message:\n \"mx-auto max-w-[24ch] text-center text-[13px] leading-5 text-[var(--text-secondary)] [overflow-wrap:anywhere]\",\n text: \"h-full w-full overflow-auto p-3 text-left text-[11px] leading-5 whitespace-pre-wrap break-words text-[var(--text-primary)]\",\n textFrame: \"items-stretch justify-stretch\",\n video: \"block max-h-full max-w-full rounded-[6px] object-contain\",\n videoFrame: \"p-3\"\n },\n detail: {\n frame:\n \"flex h-60 min-h-60 max-h-60 items-center justify-center overflow-hidden rounded-lg bg-[var(--transparency-block)]\",\n image: \"max-h-full max-w-full rounded-[6px] object-contain\",\n imageFrame: \"p-4\",\n message:\n \"max-w-[24ch] text-center text-[13px] leading-5 text-[var(--text-tertiary)] [overflow-wrap:anywhere]\",\n text: \"h-full overflow-auto p-4 text-[11px] leading-5 whitespace-pre-wrap break-words text-[var(--text-primary)]\",\n textFrame: \"items-stretch justify-stretch\",\n video: \"block max-h-full max-w-full rounded-[6px] object-contain\",\n videoFrame: \"p-4\"\n },\n canvas: {\n frame:\n \"flex h-full min-h-0 min-w-0 w-full items-center justify-center overflow-hidden bg-[var(--background-fronted)] text-[var(--text-tertiary)]\",\n image: \"block max-h-full max-w-full object-contain\",\n imageFrame: \"overflow-auto p-3\",\n message:\n \"max-w-[24ch] text-center text-[13px] leading-5 text-[var(--text-tertiary)] [overflow-wrap:anywhere]\",\n text: \"m-0 h-full min-h-0 min-w-0 w-full overflow-auto whitespace-pre-wrap break-words p-3 font-[var(--tsh-font-mono)] text-[11px] leading-[18px] text-[var(--text-secondary)]\",\n textFrame: \"items-stretch justify-stretch\",\n video: \"block max-h-full max-w-full object-contain\",\n videoFrame: \"overflow-auto p-3\"\n }\n};\n\nfunction WorkspaceFilePreviewFrame({\n children,\n className\n}: {\n children: ReactNode;\n className: string;\n}): ReactElement {\n return <div className={className}>{children}</div>;\n}\n\nfunction joinClassNames(\n ...classNames: Array<string | false | null | undefined>\n): string {\n return classNames.filter(Boolean).join(\" \");\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tutti-os/workspace-file-preview",
3
- "version": "0.0.181",
3
+ "version": "0.0.183",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/workspaceFilePreview.ts"],"sourcesContent":["export type WorkspaceFilePreviewEntryKind =\n | \"file\"\n | \"directory\"\n | \"folder\"\n | \"unknown\"\n | (string & {});\n\nexport type WorkspaceFilePreviewKind = \"image\" | \"text\" | \"video\";\n\nexport type WorkspaceFileVisualKind =\n | \"binary\"\n | \"code\"\n | \"directory\"\n | \"document\"\n | \"image\"\n | \"markdown\"\n | \"video\";\n\nexport interface WorkspaceFilePreviewEntry {\n displayName?: string;\n kind: WorkspaceFilePreviewEntryKind;\n mtimeMs?: number | null;\n name?: string;\n path: string;\n sizeBytes?: number | null;\n}\n\nexport interface WorkspaceFilePreviewActivationTarget {\n fileKind: WorkspaceFilePreviewKind;\n mtimeMs?: number | null;\n name: string;\n path: string;\n sizeBytes?: number | null;\n}\n\nexport type WorkspaceFilePreviewReadonlyReason =\n | \"binary\"\n | \"decode_failed\"\n | \"file_too_large\"\n | \"text_too_large\";\n\nexport type WorkspaceFilePreviewReadiness<\n TEntry extends WorkspaceFilePreviewEntry,\n TTarget extends WorkspaceFilePreviewActivationTarget =\n WorkspaceFilePreviewActivationTarget\n> =\n | { entry: TEntry; status: \"directory\" }\n | {\n entry: TEntry;\n maxSizeBytes: number;\n reason: Extract<\n WorkspaceFilePreviewReadonlyReason,\n \"file_too_large\" | \"text_too_large\"\n >;\n status: \"readonly\";\n }\n | { entry: TEntry; status: \"unsupported\" }\n | { entry: TEntry; status: \"ready\"; target: TTarget };\n\nexport type WorkspaceFilePreviewLoadedState<\n TEntry extends WorkspaceFilePreviewEntry,\n TTarget extends WorkspaceFilePreviewActivationTarget\n> =\n | { content: string; entry: TTarget; status: \"text\" }\n | { content: string; entry: TTarget; status: \"html\" }\n | {\n bytes: Uint8Array<ArrayBuffer>;\n contentType: string;\n entry: TTarget;\n status: \"image\";\n }\n | {\n bytes: Uint8Array<ArrayBuffer>;\n contentType: string;\n entry: TTarget;\n status: \"video\";\n }\n | {\n entry: TEntry;\n maxSizeBytes?: number;\n reason: WorkspaceFilePreviewReadonlyReason;\n status: \"readonly\";\n };\n\nconst imageExtensions = new Set([\n \"avif\",\n \"gif\",\n \"jpeg\",\n \"jpg\",\n \"png\",\n \"svg\",\n \"webp\"\n]);\nconst browserOpenableHtmlExtensions = new Set([\n \"htm\",\n \"html\",\n \"shtml\",\n \"xhtml\"\n]);\nconst browserOpenableVideoExtensions = new Set([\"mp4\", \"webm\"]);\n\nconst videoExtensions = new Set([\n \"avi\",\n \"m2ts\",\n \"mkv\",\n \"mov\",\n \"mp4\",\n \"mpeg\",\n \"mpg\",\n \"mts\",\n \"webm\",\n \"wmv\"\n]);\n/**\n * Extensions where macOS Launch Services may register video handlers even when\n * the workspace file is source code (UTI / uniform type collisions).\n */\nexport const workspaceFileVideoHandlerCollisionExtensions = new Set([\"ts\"]);\nconst markdownExtensions = new Set([\"md\", \"mdx\"]);\nconst codeExtensions = new Set([\n \"bash\",\n \"c\",\n \"cc\",\n \"conf\",\n \"cpp\",\n \"cs\",\n \"css\",\n \"go\",\n \"h\",\n \"hpp\",\n \"html\",\n \"java\",\n \"js\",\n \"jsx\",\n \"json\",\n \"lua\",\n \"m\",\n \"mm\",\n \"php\",\n \"plist\",\n \"proto\",\n \"py\",\n \"rb\",\n \"rs\",\n \"sh\",\n \"sql\",\n \"swift\",\n \"toml\",\n \"ts\",\n \"tsx\",\n \"xml\",\n \"yaml\",\n \"yml\",\n \"zsh\"\n]);\nconst documentExtensions = new Set([\n \"csv\",\n \"doc\",\n \"docx\",\n \"log\",\n \"pdf\",\n \"rtf\",\n \"txt\",\n \"xls\",\n \"xlsx\"\n]);\nconst textExtensions = new Set([\n \"bash\",\n \"c\",\n \"cc\",\n \"conf\",\n \"cpp\",\n \"cs\",\n \"css\",\n \"csv\",\n \"env\",\n \"go\",\n \"h\",\n \"hpp\",\n \"html\",\n \"ini\",\n \"java\",\n \"js\",\n \"json\",\n \"jsx\",\n \"log\",\n \"lua\",\n \"m\",\n \"md\",\n \"mdx\",\n \"mm\",\n \"php\",\n \"plist\",\n \"proto\",\n \"py\",\n \"rb\",\n \"rs\",\n \"sh\",\n \"sql\",\n \"swift\",\n \"toml\",\n \"ts\",\n \"tsx\",\n \"txt\",\n \"xml\",\n \"yaml\",\n \"yml\",\n \"zsh\"\n]);\nconst textFileNames = new Set([\n \".gitignore\",\n \".npmrc\",\n \".nvmrc\",\n \"dockerfile\",\n \"makefile\",\n \"readme\"\n]);\n\nexport const workspaceFileTextMaxBytes = 1024 * 1024;\nexport const workspaceFilePreviewMaxBytes = 20 * 1024 * 1024;\n\nexport function resolveWorkspaceFileVisualKind(\n entry: Pick<WorkspaceFilePreviewEntry, \"kind\" | \"name\" | \"path\">\n): WorkspaceFileVisualKind {\n if (entry.kind === \"directory\" || entry.kind === \"folder\") {\n return \"directory\";\n }\n\n const extension = resolveWorkspaceFileExtension(\n entry.path || entry.name || \"\"\n );\n if (imageExtensions.has(extension)) {\n return \"image\";\n }\n if (videoExtensions.has(extension)) {\n return \"video\";\n }\n if (markdownExtensions.has(extension)) {\n return \"markdown\";\n }\n if (codeExtensions.has(extension)) {\n return \"code\";\n }\n if (documentExtensions.has(extension)) {\n return \"document\";\n }\n return \"binary\";\n}\n\nexport function resolveWorkspaceFileExtension(pathOrName: string): string {\n const name = pathOrName.split(\"/\").pop()?.trim().toLowerCase() ?? \"\";\n const dotIndex = name.lastIndexOf(\".\");\n return dotIndex > 0 ? name.slice(dotIndex + 1) : \"\";\n}\n\nexport function isWorkspaceFileBrowserOpenable(\n entry: Pick<WorkspaceFilePreviewEntry, \"kind\" | \"name\" | \"path\">\n): boolean {\n if (entry.kind !== \"file\") {\n return false;\n }\n\n const extension = resolveWorkspaceFileExtension(\n entry.path || entry.name || \"\"\n );\n if (\n extension === \"pdf\" ||\n browserOpenableHtmlExtensions.has(extension) ||\n imageExtensions.has(extension) ||\n browserOpenableVideoExtensions.has(extension)\n ) {\n return true;\n }\n\n return classifyWorkspaceFilePreviewKind(entry) === \"text\";\n}\n\nexport function shouldFilterVideoPlayersForOpenWith(\n entry: Pick<WorkspaceFilePreviewEntry, \"kind\" | \"name\" | \"path\">\n): boolean {\n if (entry.kind !== \"file\") {\n return false;\n }\n\n const visualKind = resolveWorkspaceFileVisualKind(entry);\n if (visualKind === \"video\") {\n return false;\n }\n\n const extension = resolveWorkspaceFileExtension(\n entry.path || entry.name || \"\"\n );\n if (workspaceFileVideoHandlerCollisionExtensions.has(extension)) {\n return true;\n }\n\n if (visualKind === \"code\" || visualKind === \"markdown\") {\n return true;\n }\n\n return classifyWorkspaceFilePreviewKind(entry) === \"text\";\n}\n\nexport function resolveWorkspaceImageMimeType(\n pathOrName: string\n): string | null {\n switch (resolveWorkspaceFileExtension(pathOrName)) {\n case \"avif\":\n return \"image/avif\";\n case \"gif\":\n return \"image/gif\";\n case \"jpeg\":\n case \"jpg\":\n return \"image/jpeg\";\n case \"png\":\n return \"image/png\";\n case \"svg\":\n return \"image/svg+xml\";\n case \"webp\":\n return \"image/webp\";\n default:\n return null;\n }\n}\n\nexport function resolveWorkspaceVideoMimeType(\n pathOrName: string\n): string | null {\n switch (resolveWorkspaceFileExtension(pathOrName)) {\n case \"mp4\":\n return \"video/mp4\";\n case \"webm\":\n return \"video/webm\";\n default:\n return null;\n }\n}\n\nexport function classifyWorkspaceFilePreviewKind(\n entry: Pick<\n WorkspaceFilePreviewEntry,\n \"displayName\" | \"kind\" | \"name\" | \"path\"\n >\n): WorkspaceFilePreviewKind | null {\n if (entry.kind !== \"file\") {\n return null;\n }\n\n const name = resolveWorkspaceFilePreviewName(entry);\n if (resolveWorkspaceImageMimeType(name) !== null) {\n return \"image\";\n }\n if (resolveWorkspaceVideoMimeType(name) !== null) {\n return \"video\";\n }\n\n const normalizedName = name.trim().toLowerCase();\n const extension = resolveWorkspaceFileExtension(name);\n if (textExtensions.has(extension) || textFileNames.has(normalizedName)) {\n return \"text\";\n }\n\n return null;\n}\n\nexport function resolveWorkspaceFileActivationTarget(\n entry: WorkspaceFilePreviewEntry\n): WorkspaceFilePreviewActivationTarget | null {\n const fileKind = classifyWorkspaceFilePreviewKind(entry);\n if (!fileKind) {\n return null;\n }\n\n const target: WorkspaceFilePreviewActivationTarget = {\n fileKind,\n name: resolveWorkspaceFilePreviewName(entry),\n path: entry.path\n };\n if (entry.mtimeMs !== undefined) {\n target.mtimeMs = entry.mtimeMs;\n }\n if (entry.sizeBytes !== undefined) {\n target.sizeBytes = entry.sizeBytes;\n }\n return target;\n}\n\nexport function resolveWorkspaceFilePreviewReadiness<\n TEntry extends WorkspaceFilePreviewEntry\n>(entry: TEntry): WorkspaceFilePreviewReadiness<TEntry> {\n if (entry.kind === \"directory\" || entry.kind === \"folder\") {\n return {\n entry,\n status: \"directory\"\n };\n }\n\n const target = resolveWorkspaceFileActivationTarget(entry);\n if (!target) {\n return {\n entry,\n status: \"unsupported\"\n };\n }\n\n if (\n target.fileKind === \"text\" &&\n isWorkspaceTextFileTooLarge(entry.sizeBytes)\n ) {\n return {\n entry,\n maxSizeBytes: workspaceFileTextMaxBytes,\n reason: \"text_too_large\",\n status: \"readonly\"\n };\n }\n\n if (isWorkspacePreviewFileTooLarge(entry.sizeBytes)) {\n return {\n entry,\n maxSizeBytes: workspaceFilePreviewMaxBytes,\n reason: \"file_too_large\",\n status: \"readonly\"\n };\n }\n\n return {\n entry,\n status: \"ready\",\n target\n };\n}\n\nexport function createWorkspaceFilePreviewLoadedState<\n TEntry extends WorkspaceFilePreviewEntry,\n TTarget extends WorkspaceFilePreviewActivationTarget\n>(input: {\n bytes: Uint8Array | ArrayBuffer;\n contentType?: string | null;\n entry: TEntry;\n renderHtml?: boolean;\n target: TTarget;\n}): WorkspaceFilePreviewLoadedState<TEntry, TTarget> {\n if (input.target.fileKind === \"image\") {\n return {\n bytes: copyWorkspaceFilePreviewBytes(input.bytes),\n contentType:\n input.contentType ??\n resolveWorkspaceImageMimeType(input.target.name) ??\n \"application/octet-stream\",\n entry: input.target,\n status: \"image\"\n };\n }\n if (input.target.fileKind === \"video\") {\n return {\n bytes: copyWorkspaceFilePreviewBytes(input.bytes),\n contentType:\n input.contentType ??\n resolveWorkspaceVideoMimeType(input.target.name) ??\n \"application/octet-stream\",\n entry: input.target,\n status: \"video\"\n };\n }\n\n try {\n const content = decodeWorkspaceTextFile(input.bytes);\n if (looksLikeBinaryText(content)) {\n return {\n entry: input.entry,\n reason: \"binary\",\n status: \"readonly\"\n };\n }\n if (input.renderHtml && isWorkspaceHtmlFileName(input.target.name)) {\n return {\n content,\n entry: input.target,\n status: \"html\"\n };\n }\n return {\n content,\n entry: input.target,\n status: \"text\"\n };\n } catch {\n return {\n entry: input.entry,\n reason: \"decode_failed\",\n status: \"readonly\"\n };\n }\n}\n\nfunction isWorkspaceHtmlFileName(pathOrName: string): boolean {\n return browserOpenableHtmlExtensions.has(\n resolveWorkspaceFileExtension(pathOrName)\n );\n}\n\nexport function resolveWorkspaceFilePreviewName(\n entry: Pick<WorkspaceFilePreviewEntry, \"displayName\" | \"name\" | \"path\">\n): string {\n return (\n entry.name?.trim() ||\n entry.displayName?.trim() ||\n entry.path.split(\"/\").pop()?.trim() ||\n entry.path\n );\n}\n\nexport function isWorkspaceTextFileTooLarge(\n sizeBytes?: number | null\n): boolean {\n return (\n typeof sizeBytes === \"number\" &&\n Number.isFinite(sizeBytes) &&\n sizeBytes > workspaceFileTextMaxBytes\n );\n}\n\nexport function isWorkspacePreviewFileTooLarge(\n sizeBytes?: number | null\n): boolean {\n return (\n typeof sizeBytes === \"number\" &&\n Number.isFinite(sizeBytes) &&\n sizeBytes > workspaceFilePreviewMaxBytes\n );\n}\n\nexport function decodeWorkspaceTextFile(\n bytes: Uint8Array | ArrayBuffer\n): string {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(\n normalizeWorkspaceFilePreviewBytes(bytes)\n );\n}\n\nexport function normalizeWorkspaceFilePreviewBytes(\n value: Uint8Array | ArrayBuffer\n): Uint8Array {\n if (value instanceof Uint8Array) {\n return value;\n }\n return new Uint8Array(value);\n}\n\nexport function copyWorkspaceFilePreviewBytes(\n bytes: Uint8Array | ArrayBuffer\n): Uint8Array<ArrayBuffer> {\n const normalized = normalizeWorkspaceFilePreviewBytes(bytes);\n const buffer = new ArrayBuffer(normalized.byteLength);\n const copy = new Uint8Array(buffer);\n copy.set(normalized);\n return copy;\n}\n\nexport function looksLikeBinaryText(content: string): boolean {\n if (content.length === 0) {\n return false;\n }\n\n const sample = content.slice(0, 4096);\n if (sample.includes(\"\\u0000\")) {\n return true;\n }\n\n let suspiciousControlChars = 0;\n for (let index = 0; index < sample.length; index += 1) {\n const code = sample.charCodeAt(index);\n const isAllowedWhitespace = code === 9 || code === 10 || code === 13;\n const isControlChar = code < 32 || (code >= 127 && code <= 159);\n if (isControlChar && !isAllowedWhitespace) {\n suspiciousControlChars += 1;\n }\n }\n\n return suspiciousControlChars / sample.length > 0.12;\n}\n\nexport function formatWorkspacePreviewByteLimit(sizeBytes: number): string {\n if (sizeBytes < 1024) {\n return `${sizeBytes} B`;\n }\n\n const mebibytes = sizeBytes / (1024 * 1024);\n if (Number.isInteger(mebibytes)) {\n return `${mebibytes} MiB`;\n }\n return `${mebibytes.toFixed(1)} MiB`;\n}\n"],"mappings":"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","names":[]}