feat(editor): a computer's files to and from the person's own files (F8.1)

Beside the dashboard only: Java's import window gets "Aus meinen Dateien"
(D4) — the dashboard is asked over the bridge for a file of at most Java's
150 000 bytes and hands its bytes over — and a file's menu gets "In meine
Dateien speichern" (D5), its bytes handed to the dashboard to save.

- Bridge: chooseFile/chosenFile and saveFile/savedFile, each question with
  its number; an answer from elsewhere, of another version, for no question
  or of the other kind is no answer.
- Whoever reads or finds the project frozen may save, not bring in.
- doc fileBytes: a text as UTF-8, a picture as it is, and "missing" while
  a blob's bytes have not come (read so, the file would be empty).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 16:02:43 +00:00
co-authored by Claude Opus 5.5
parent cc5e07f1a6
commit 8efbc442f1
12 changed files with 430 additions and 19 deletions
+25 -1
View File
@@ -10,7 +10,7 @@
import * as Y from 'yjs';
import type { Id, VirtualFileContent, VirtualFileSystem } from '@mdg-filius/core';
import { VirtualFileSystem as FileSystem } from '@mdg-filius/core';
import { textBytes, VirtualFileSystem as FileSystem } from '@mdg-filius/core';
import { blobSha, blobSideOf } from './blobs.js';
import { fileEntry, fileSystemOf, inOrder, ROOTS } from './projectDoc.js';
@@ -125,6 +125,30 @@ export function fileText(doc: Y.Doc, id: Id, path: string): Y.Text | undefined {
return entry?.get('text') as Y.Text | undefined;
}
/**
* A file's bytes to take out of the project (F8, D5: into the person's own
* files): a text as UTF-8 — as the import reads it —, a picture or other
* file as it is. `missing` while a blob's bytes have not come yet (they read
* as none: saved so, the file would be empty); `notFile` for a folder or
* nothing at the path.
*/
export function fileBytes(doc: Y.Doc, id: Id, path: string): { readonly name: string; readonly bytes: Uint8Array } | 'missing' | 'notFile' {
const files = nodeMap(doc, id)?.get('files') as Y.Map<Y.Map<unknown>> | undefined;
if (files === undefined) return 'notFile';
const lower = path.toLowerCase();
const found = files.has(path) ? ([path, files.get(path)!] as const) : [...files.entries()].find(([key]) => key.toLowerCase() === lower);
if (found === undefined) return 'notFile';
const [key, entry] = found;
const name = key.slice(key.lastIndexOf('/') + 1);
const kind = entry.get('kind');
if (kind === 'text') return { name, bytes: textBytes((entry.get('text') as Y.Text).toString()) };
if (kind !== 'binary' && kind !== 'image') return 'notFile';
const blob = entry.get('blob');
if (typeof blob !== 'string') return { name, bytes: entry.get('data') as Uint8Array };
const held = blobSideOf(doc)?.bytes(blob);
return held === undefined ? 'missing' : { name, bytes: held };
}
/**
* The document's files of a device into a file system (a running engine's):
* what differs only, an entry the document no longer has removed, a new one