Media bin: controls pinned at the top, Delete ends Select mode
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
1b4870c499
commit
4cf2f58bf6
@@ -408,6 +408,9 @@
|
||||
.cx-stack { display: flex; flex-direction: column; min-inline-size: 0; }
|
||||
.cx-stack > * { min-inline-size: 0; }
|
||||
.cx-stack-none { gap: 0; }
|
||||
/* Pinned to the top of the scrolling panel body: the controls stay in reach while the list under them scrolls.
|
||||
The body pads its top, so the stack reaches back over that padding and gives it back inside. */
|
||||
.cx-stack-sticky { position: sticky; inset-block-start: calc(-1 * var(--sp-7)); z-index: 1; margin-block-start: calc(-1 * var(--sp-7)); padding-block: var(--sp-7) var(--sp-4); background: var(--elev-1); }
|
||||
.cx-stack-sm { gap: var(--sp-5); --cx-gap: var(--sp-5); }
|
||||
.cx-stack-md { gap: var(--sp-8); --cx-gap: var(--sp-8); }
|
||||
.cx-stack-lg { gap: var(--sp-10); }
|
||||
|
||||
@@ -126,8 +126,8 @@ define("panel", "zone", panel("zone"));
|
||||
// Without it the shell reaches for the document to make a div of its own, and a
|
||||
// div the shell made is a div the shell has to lay out -- which is the one thing
|
||||
// that is supposed to live only here.
|
||||
define("region", "stack", ({ children = [], gap = "md", label, fill = false } = {}) => {
|
||||
const node = el("div", { cls: ["cx-stack", `cx-stack-${gap}`, fill ? "cx-stack-fill" : null],
|
||||
define("region", "stack", ({ children = [], gap = "md", label, fill = false, sticky = false } = {}) => {
|
||||
const node = el("div", { cls: ["cx-stack", `cx-stack-${gap}`, fill ? "cx-stack-fill" : null, sticky ? "cx-stack-sticky" : null],
|
||||
attrs: { "aria-label": label } });
|
||||
const set = (next = []) => {
|
||||
// A redraw replaces the field under the caret (an arrow press commits, and the panel redraws): the caret moves to
|
||||
|
||||
@@ -218,3 +218,25 @@ menuTest("an already pinned picture is unpinned, scenes keep it, new scenes no l
|
||||
menuTest("reference for all scenes skips scenes that have it or are full, pinned counts as having it", cat,
|
||||
{ fixed_references: { z: 1 }, scenes: [{ id: "s1" }, { id: "s2", references: ["m1"] }, { id: "s3", references: Array.from({ length: 15 }, (_, i) => `r${i}`) }, { id: "s4", references: ["a"] }] },
|
||||
"reference for all scenes", (d) => assert.deepEqual(d.scenes.map((s) => s.references), [["m1"], ["m1"], Array.from({ length: 15 }, (_, i) => `r${i}`), ["a", "m1"]]));
|
||||
|
||||
test("Delete after picking files ends Select mode, and the bin's controls are pinned at the top", async () => {
|
||||
const project = { selected: null, project: { fixed_references: {} }, setScene: () => {}, edit: () => {} };
|
||||
let list = [{ id: "m1", name: "cat.png", kind: "image" }, { id: "m2", name: "dog.png", kind: "image" }];
|
||||
const api = { media: async () => ({ media: list }), deleteMedia: async (id) => { list = list.filter((m) => m.id !== id); } };
|
||||
const host = document.createElement("div");
|
||||
hub.setup({ host, app: { project, api, mediaPeek: {}, say: () => {}, on: () => () => {} } });
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
const btn = (t) => [...document.querySelectorAll("button")].find((b) => b.textContent.trim().startsWith(t));
|
||||
assert.ok(host.querySelector(".cx-stack-sticky button"), "the controls sit in the pinned stack");
|
||||
document.body.append(host);
|
||||
btn("Select").click();
|
||||
host.querySelector(".cx-cell").click();
|
||||
btn("Delete (1)").click();
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
[...document.querySelectorAll("button")].filter((b) => b.textContent.trim() === "Delete").pop().click();
|
||||
await new Promise((r) => setTimeout(r, 30));
|
||||
assert.equal(host.querySelectorAll(".cx-cell").length, 1);
|
||||
assert.ok(btn("Select"), "back to Select, not Done");
|
||||
assert.equal(btn("Delete (0)").hidden, true);
|
||||
host.remove();
|
||||
});
|
||||
|
||||
@@ -146,12 +146,13 @@ export default {
|
||||
const ids = [...chosen];
|
||||
if (!ids.length || !(await c.modal.dialogue({ title: "Delete media", message: `Delete ${ids.length} file${ids.length > 1 ? "s" : ""} from the bin for good? Scenes in other projects that use them will lose them too.`, tone: "danger", confirmLabel: "Delete" }).result)) return;
|
||||
await gone(ids);
|
||||
selecting = false; chosen.clear(); draw(true); // Delete ends Select mode
|
||||
} });
|
||||
const controls = { sort, view: view, cols: tileSize };
|
||||
bulk.node.hidden = true;
|
||||
const clearRefs = c.button.sm({ label: "Clear references", tone: "ghost", title: "Stop using every reference picture in every scene (pinned ones stay)", onClick: () => p.edit(clearReferences) });
|
||||
host.append(c.toolbar.default({ items: [c.text.sm({ text: "Media" })], trailing: [clearRefs, selectBtn, bulk] }).node, drop.node, note.node, seg("Show", FILTERS, "filter").node, sort.node,
|
||||
view.node, tileSize.node, shelf.node);
|
||||
const bar = c.region.stack({ gap: "sm", sticky: true, children: [c.toolbar.default({ items: [c.text.sm({ text: "Media" })], trailing: [clearRefs, selectBtn, bulk] }), drop, note, seg("Show", FILTERS, "filter"), sort, view, tileSize] });
|
||||
host.append(bar.node, shelf.node);
|
||||
|
||||
let drawn = "";
|
||||
function adopt() {
|
||||
|
||||
Reference in New Issue
Block a user