"+ → Texture" opened the machine's file dialog, so a picture that was uploaded once but lost its asset – an undone import, a deleted asset – was unreachable from the editor while still sitting on the server. Nothing collects those files, by design. It now opens a grid of every picture the server holds, with thumbnails: pick one and "Use image" makes a texture asset on that file, "Upload…" is the way in for bytes the server does not have yet and lands in the same import as a drop. A tile captioned with an asset name is one this project already wears – picking it selects that asset instead of making a second one, since two assets on one file would fight over `filter`. - `GET ?r=textures` now reports `width`/`height`; a texture asset holds the pixel size, and reading it off the GPU texture would tie the asset's correctness to load timing. - `TextureLibrary.load(file)` loads without touching `scaleMode` – a thumbnail must not restage the filter of a picture the scene is drawing with. - The grid geometry lives in `texture/imageGrid.ts` as pure functions; inside the widget no test could reach it. - The part after an upload is now one function shared by the drop import and the picker, so "one file = one asset" is stated in a single place. 12 new E2E checks; the strongest one undoes an import, leaving the file on the server with no asset naming it, and picks exactly that file back out of the dialog.
92 lines
3.1 KiB
TypeScript
92 lines
3.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import {
|
||
CAPTION_HEIGHT,
|
||
containRect,
|
||
gridMetrics,
|
||
TILE_GAP,
|
||
TILE_MAX,
|
||
TILE_MIN,
|
||
tileAt,
|
||
tileRect,
|
||
} from '../../src/editor/texture/imageGrid';
|
||
|
||
/** The width the picker gives its grid. */
|
||
const WIDTH = 560;
|
||
|
||
describe('gridMetrics', () => {
|
||
it('fits as many columns as the width holds, and spreads the leftover across them', () => {
|
||
const m = gridMetrics(WIDTH, 7);
|
||
expect(m.columns).toBe(5);
|
||
expect(m.tile).toBeGreaterThanOrEqual(TILE_MIN);
|
||
expect(m.columns * m.tile + (m.columns - 1) * TILE_GAP).toBeLessThanOrEqual(WIDTH);
|
||
});
|
||
|
||
it('keeps one column in a width too narrow for even that', () => {
|
||
expect(gridMetrics(40, 3).columns).toBe(1);
|
||
});
|
||
|
||
it('caps a lone tile instead of blowing one picture up over the whole dialog', () => {
|
||
expect(gridMetrics(140, 1).tile).toBe(TILE_MAX);
|
||
});
|
||
|
||
it('reports the height the scroll container has to give it', () => {
|
||
const m = gridMetrics(WIDTH, 7);
|
||
expect(m.rows).toBe(2);
|
||
expect(m.height).toBe(2 * (m.tile + CAPTION_HEIGHT) + TILE_GAP);
|
||
expect(gridMetrics(WIDTH, 0).height).toBe(0);
|
||
});
|
||
});
|
||
|
||
describe('tileRect', () => {
|
||
it('lays the tiles out row by row', () => {
|
||
const m = gridMetrics(WIDTH, 7);
|
||
expect(tileRect(0, m)).toEqual({ x: 0, y: 0, w: m.tile, h: m.tile + CAPTION_HEIGHT });
|
||
expect(tileRect(1, m).x).toBe(m.tile + TILE_GAP);
|
||
expect(tileRect(1, m).y).toBe(0);
|
||
expect(tileRect(m.columns, m)).toEqual({
|
||
x: 0,
|
||
y: m.tile + CAPTION_HEIGHT + TILE_GAP,
|
||
w: m.tile,
|
||
h: m.tile + CAPTION_HEIGHT,
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('tileAt', () => {
|
||
const m = gridMetrics(WIDTH, 7);
|
||
|
||
it('finds the tile a point is on', () => {
|
||
expect(tileAt(2, 2, 7, m)).toBe(0);
|
||
expect(tileAt(m.tile + TILE_GAP + 2, 2, 7, m)).toBe(1);
|
||
expect(tileAt(2, m.tile + CAPTION_HEIGHT + TILE_GAP + 2, 7, m)).toBe(m.columns);
|
||
});
|
||
|
||
it('takes nothing in the gaps between tiles', () => {
|
||
expect(tileAt(m.tile + 2, 2, 7, m)).toBe(-1);
|
||
expect(tileAt(2, m.tile + CAPTION_HEIGHT + 2, 7, m)).toBe(-1);
|
||
});
|
||
|
||
it('takes nothing past the last tile – the grid is filled to the box height', () => {
|
||
// Seven pictures in five columns fill the second row up to place 6; 7 is the first empty one.
|
||
expect(tileAt(tileRect(6, m).x + 2, tileRect(6, m).y + 2, 7, m)).toBe(6);
|
||
expect(tileAt(tileRect(7, m).x + 2, tileRect(7, m).y + 2, 7, m)).toBe(-1);
|
||
expect(tileAt(2, 4000, 7, m)).toBe(-1);
|
||
expect(tileAt(-2, 2, 7, m)).toBe(-1);
|
||
});
|
||
});
|
||
|
||
describe('containRect', () => {
|
||
it('centres a picture and keeps its ratio', () => {
|
||
expect(containRect(64, 32, 100, 100)).toEqual({ x: 0, y: 25, w: 100, h: 50 });
|
||
expect(containRect(32, 64, 100, 100)).toEqual({ x: 25, y: 0, w: 50, h: 100 });
|
||
});
|
||
|
||
it('blows a small icon up to the box, so it is recognisable next to a sheet', () => {
|
||
expect(containRect(16, 16, 100, 100)).toEqual({ x: 0, y: 0, w: 100, h: 100 });
|
||
});
|
||
|
||
it('has nothing to draw for a picture the server could not read', () => {
|
||
expect(containRect(0, 0, 100, 100)).toEqual({ x: 0, y: 0, w: 0, h: 0 });
|
||
});
|
||
});
|