Uploads carry the name the file came with (#83)

An upload sends its file name next to the raw bytes, and the server keeps
it in a sidecar next to the content-addressed file. Both pickers caption
every tile and row with it, and an asset made there is named after the
file instead of "Texture" / "Font". Last upload of the same content wins.

The API's own set_error_handler ignores error_reporting(), so @-silenced
warnings still throw: a missing sidecar took the whole listing down with a
500. Existence is checked instead of suppressed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
DeveloperandClaude Opus 5 committed 2026-09-13 20:32:45 +02:00
1 parent 86b5363a01
commit 99c5d2ad78
21 files changed
+347 -61

No files matched your search

+13 -4
View File
@@ -81,6 +81,11 @@ class ImageGrid extends Widget {
return this.items.filter((item) => item.assetName !== null).map((item) => item.file);
}
/** The captions as drawn, read off the tiles themselves rather than recomputed (test hook). */
get captions(): string[] {
return this.tiles.map((tile) => tile.caption.text);
}
select(file: string | null): void {
if (file === this.selectedFile) return;
this.selectedFile = file;
@@ -139,8 +144,10 @@ class ImageGrid extends Widget {
node.sprite.height = fitted.h;
}
// The asset name is the useful caption where there is one; a bare hash tells nobody anything.
const text = item.width > 0 ? (item.assetName ?? `${item.width} × ${item.height}`) : 'unreadable';
// The asset name is the useful caption where there is one, then the name the file was
// uploaded under; a bare hash tells nobody anything, and the size is the last resort.
const text =
item.width > 0 ? (item.assetName ?? (item.name || `${item.width} × ${item.height}`)) : 'unreadable';
node.caption.style = textStyle(item.assetName ? 'accent' : 'muted');
node.caption.text = truncate(text, node.caption.style, side);
node.caption.position.set(0, side + 1);
@@ -210,10 +217,11 @@ export class ImagePicker extends Popup {
}
/** What the dialog offers, which of it the project already wears, and what is picked (test hook). */
debugInfo(): { files: string[]; used: string[]; selected: string | null } {
debugInfo(): { files: string[]; used: string[]; captions: string[]; selected: string | null } {
return {
files: this.grid.fileList,
used: this.grid.usedFiles,
captions: this.grid.captions,
selected: this.grid.selected?.file ?? null,
};
}
@@ -270,7 +278,8 @@ export class ImagePicker extends Popup {
function describe(item: PickerItem): string {
if (item.width <= 0) return 'The server cannot read this file as an image.';
const size = `${item.width} × ${item.height} px, ${formatBytes(item.bytes)}`;
const measurements = `${item.width} × ${item.height} px, ${formatBytes(item.bytes)}`;
const size = item.name ? `${item.name} – ${measurements}` : measurements;
return item.assetName ? `${size} – already in the project as "${item.assetName}"` : size;
}