feat: F4.6f3 the file explorer and the image viewer

Oracle FileExplorerWindowRun uses Java's explorer and viewer windows as a
person does (the right-click menu, Java's questions answered, the import
window, the viewer's open). Both windows work on the document's files
through the core's operations: the tree of folders and files, the chosen
folder's entries, Java's menu (also as buttons, no right click needed),
copy that stays and cut that goes once pasted, the import; the viewer's
picture and title. Not taken from Java: a file chosen in the tree taken
for a folder, pictures laid over each other, a text file as an empty
picture. writeFiles numbers the entries in the file system's order again
when a rename made a new key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 18:13:22 +00:00
co-authored by Claude Opus 5.5
parent 2c7733b575
commit 3dc2713f81
21 changed files with 2951 additions and 39 deletions
+10 -2
View File
@@ -11,7 +11,7 @@
import * as Y from 'yjs';
import type { Id, VirtualFileContent, VirtualFileSystem } from '@mdg-filius/core';
import { VirtualFileSystem as FileSystem } from '@mdg-filius/core';
import { fileEntry, fileSystemOf, ROOTS } from './projectDoc.js';
import { fileEntry, fileSystemOf, inOrder, ROOTS } from './projectDoc.js';
export function sameBytes(left: Uint8Array, right: Uint8Array): boolean {
return left.length === right.length && left.every((byte, index) => byte === right[index]);
@@ -36,7 +36,11 @@ function holds(entry: Y.Map<unknown>, content: VirtualFileContent): boolean {
return sameBytes(entry.get('data') as Uint8Array, content.data) && entry.get('mediaType') === content.mediaType;
}
/** A node's files as the file system has them: new ones after the others, changed ones in their place, gone ones removed. */
/**
* A node's files as the file system has them: new ones after the others,
* changed ones in their place, gone ones removed; when the order is no
* longer the file system's (a renamed entry), numbered anew.
*/
export function writeFiles(node: Y.Map<unknown>, fileSystem: VirtualFileSystem): void {
let files = node.get('files') as Y.Map<Y.Map<unknown>> | undefined;
const wanted: [string, VirtualFileContent][] = [];
@@ -65,6 +69,10 @@ export function writeFiles(node: Y.Map<unknown>, fileSystem: VirtualFileSystem):
replaceText(entry.get('text') as Y.Text, content.text);
}
}
// An entry renamed is new to the document: the order is the file system's again (a rename keeps the entry's place).
if (inOrder(files).some(([path], index) => path !== wanted[index]![0])) {
for (const [index, [path]] of wanted.entries()) if (files.get(path)!.get('order') !== index) files.get(path)!.set('order', index);
}
}
function nodeMap(doc: Y.Doc, id: Id): Y.Map<unknown> | undefined {
+4 -6
View File
@@ -116,19 +116,17 @@ describe("a device's files in the document", () => {
const order = (fileSystem: VirtualFileSystem) => fileSystem.entriesInCreationOrder('/').map((entry) => entry.path);
expect(order(engine)).toEqual(order(nodeFiles(doc, 'pc')!));
expect(order(engine).indexOf('/Gross.txt')).toBeLessThan(order(engine).indexOf('/NEU.txt'));
// So also in a folder.
// So also in a folder: a file renamed there keeps its place, in the document and in the engine.
changeFiles(doc, 'pc', 'me', (fileSystem) => {
fileSystem.createDirectory('/tief');
fileSystem.writeTextFile('/tief/x', 'x');
fileSystem.writeTextFile('/tief/y.txt', 'y');
});
readFilesInto(doc, 'pc', engine);
changeFiles(doc, 'pc', 'me', (fileSystem) => {
fileSystem.deleteItem('/tief/x');
fileSystem.createDirectory('/tief/x');
});
changeFiles(doc, 'pc', 'me', (fileSystem) => fileSystem.renameInPlace('/tief/x', 'x.txt'));
expect(nodeFiles(doc, 'pc')!.entriesInCreationOrder('/tief').map((entry) => entry.path)).toEqual(['/tief/x.txt', '/tief/y.txt']);
readFilesInto(doc, 'pc', engine);
expect(engine.entriesInCreationOrder('/tief').map((entry) => entry.path)).toEqual(['/tief/x', '/tief/y.txt']);
expect(engine.entriesInCreationOrder('/tief').map((entry) => entry.path)).toEqual(['/tief/x.txt', '/tief/y.txt']);
});
it('leave out what the file system refuses, as the editor draws it', () => {