Repository navigation
feat(media): let the multi-selection reach the actions that imply it - #500
mostafasadeghidev wants to merge 4 commits into
Conversation
Three reports from one install, all the same shape: an affordance that reads as if it already honours the selection, and does not. RIGHT-CLICK IGNORED THE SELECTION. `openContextMenu` stored only the clicked asset and the delete handler acted on `contextMenu.asset` alone, so right-clicking one of five selected files and choosing Delete trashed exactly one and left four selected — no error, nothing to notice. The fix is the rule the same component already uses forty lines above, in `handleAssetDragStart`: an item inside the selection acts on the whole selection, an item outside acts on itself. The menu now shows a "5 files" header and a "Trash 5 files" label so the scope is visible before the click, and hides Rename for a multi-selection because there is one name field. Deliberately NOT adopting the clicked asset into the selection the way the site explorer does: Media derives its floating windows from the selection during render — viewer at <= 1, bulk edit at >= 2 — so writing the selection from a menu opener would pop a window open underneath the menu. THE TRASH HAD NO BULK DELETE. A selection there offered Restore and nothing else, leaving "empty the trash" a one-file-at-a-time job through the preview window. `runPurgeAll` sits beside its Trash and Restore siblings and loops the same single-id endpoint they do — no server work needed. It confirms first, counting only the trashed members: `purgeAsset` 400s on a live asset, so a mixed selection would otherwise promise more than it does. ESCAPE DID NOT CLOSE THE WINDOWS. Every other overlay in the admin takes it. These windows overlay the grid they were opened from, and the only way out was the header's close button. `useTopmostEscape` carries the settings modal's rule — only the topmost layer reacts, so a confirmation opened from inside a window owns Escape until it closes rather than collapsing the stack in one press. `FloatingWindow` uses it, which covers bulk edit, the upload queue and the agent image preview; the media viewer renders its own shell, so it wires the hook directly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI caught what the stacking check missed. `AgentImagePreview` is a `FloatingWindow` too, and its test opens a context menu inside the preview, presses Escape, and asserts the menu closes while the preview stays open. With the window listening, one press closed both. The check only looked for `dialog` and `alertdialog`, and only for elements that follow the panel in document order. A menu is neither: it carries `role="menu"` and portals to `document.body`, so it can render before the panel it belongs to. An open menu now owns Escape wherever it sits. That is the right rule regardless of the test — closing a window out from under the menu the user just opened loses their place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The conflicts here are history rather than content: the stack took CoreBunch#507's warning through its own commits, combined with CoreBunch#500 and CoreBunch#501, so both sides look new to git. Each file was settled by what CoreBunch#507's commits since then actually changed: - MediaCanvas, MediaViewerWindow, the confirmation test, mediaPrefetch — untouched by those commits, so the stack's copies already hold all of CoreBunch#507 plus the multi-select and minimize work. Kept. - contentUsage.ts and its test — the branch-aware lookup is new, so CoreBunch#507's version is taken, with the stack's four-argument walker (`rootNodeId` scopes hole and loop fragments here). - usageWarning copy + tests, the ref schema, docs — merged cleanly. Before choosing a side, the stack's copies of both test files were checked against CoreBunch#507's pre-sync versions: identical, so nothing stack-only is lost. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ents Review on CoreBunch#501 named two problems in this PR, both real: - The trash view's context menu purged a whole selection with no confirmation. A purge from the menu now always asks first, through the admin's one ConfirmDeleteProvider with `alwaysConfirm` — the `confirmBeforeDelete` preference defaults off and governs reversible trashing, not this. A single file asks too, worded as CoreBunch#497 words it, so whichever of the two lands second resolves to the same code. - The tests asserted on local copies of the logic. They now render MediaCanvas and BulkEditWindow inside the real session and confirm providers and drive the menu, the drag and the dialog. Each was checked against a deliberately broken component: no confirmation, a menu or a drag that ignores the selection, and a bulk purge of the whole selection — every one turns the suite red. Writing those tests found a bug the copies hid: the bulk window's dialog counted only the trashed members of a mixed selection, but the purge loop ran over all of them, firing a `purgeAsset` for every live file — an endpoint that 400s on an asset that was never soft-deleted. The purge now runs on exactly the files the dialog counts. The selection rule itself moves to `utils/actionTargets.ts`, and both the drag and the menu call it, so they cannot drift apart; the test that compared two copies of it is gone with them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e that skips live files Review asked for two things; the stack already had the first and lacked the second. - The menu's permanent delete already confirmed here (CoreBunch#497 + CoreBunch#507's usage warning). Kept that flow, awaiting the usage lookup before the dialog opens, and took CoreBunch#500's wording for a multi-file purge ("Delete 3 files"). - The bulk window's purge looped over the WHOLE selection while its dialog counted only the trashed members, firing a 400-bound purgeAsset for every live file. That bug was live on this branch; it now purges exactly `purgeable`, and the usage lookup reads the same list. The selection rule moves to `utils/actionTargets.ts`. Here it stays behind `useMediaSelection` (the stack's extraction), whose `targetsFor` now delegates to it, so the drag and the menu still share one rule. CoreBunch#500's component tests take the stack's async dialog: the workspace stub gains `lookupUsage`, and the dialog assertions wait for it to open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tests The stack already carried CoreBunch#501's minimize code, identical to CoreBunch#660's; the two conflicts were lines CoreBunch#660 does not have (CoreBunch#500's useTopmostEscape, the viewer's purge-confirmation state from CoreBunch#497/CoreBunch#507), kept as they were, and the merge's second `minimizable` attribute on two windows is dropped. What this adds is CoreBunch#660's docs and its tests, which render the real windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
I've updated this one based on your comments in #501. Deleting from the trash through the context menu now always asks for confirmation, both for a single file and for a selection. I used the same wording as #497 for the single-file case, so the two should fit together fine whichever one goes in first. The tests now render MediaCanvas and BulkEditWindow and go through the menu, the drag and the confirm dialog, instead of checking local copies of the logic. I also broke the components on purpose to make sure the tests actually fail when they should. While doing that I found a real bug: the bulk delete dialog only counted the trashed files, but the purge was sent for every file in the selection, including ones that weren't trashed. It now only deletes what the dialog shows. |
Three reports from one install, all the same shape: an affordance that reads as if it already honours the selection, and does not.
Right-click ignored the selection
openContextMenustored only the clicked asset, and the delete handler acted oncontextMenu.assetalone. Right-clicking one of five selected files and choosing Delete trashed exactly one and left four selected — no error, nothing to notice.The fix is the rule the same component already used in
handleAssetDragStart: an item inside the selection acts on the whole selection, an item outside acts on itself. It now lives inutils/actionTargets.ts, and both the drag and the menu call it, so they can't drift apart. The menu now shows a "5 files" header and a "Trash 5 files" label so the scope is visible before the click, and hides Rename for a multi-selection because there is one name field.Deliberately not adopting the clicked asset into the selection the way
SiteExplorerPaneldoes. Media derives its floating windows from the selection during render — viewer at<= 1, bulk edit at>= 2(MediaPage.tsx) — so writing the selection from a menu opener would pop a window open underneath the menu.The trash had no bulk delete
A selection there offered Restore and nothing else, leaving "empty the trash" a one-file-at-a-time job through the preview window.
runPurgeAllsits beside its Trash and Restore siblings and loops the same single-id endpoint they do, so no server work is needed. It confirms first, and both the count and the purge cover only the trashed members:purgeAsset400s on a live asset.A permanent delete from the menu asks first
In the trash view the menu acts on the whole selection too, so it could purge N files in one click. It now always confirms, through the admin's
ConfirmDeleteProviderwithalwaysConfirm— theconfirmBeforeDeletepreference defaults off and governs reversible trashing, not this. A single file asks as well, worded as #497 words it, so whichever of the two lands second resolves to the same code.Escape did not close the windows
Every other overlay in the admin takes it. These windows overlay the grid they were opened from, and the only way out was the header's close button.
useTopmostEscapecarries the settings modal's rule — only the topmost layer reacts, so a confirmation opened from inside a window owns Escape until it closes rather than collapsing the stack in one press.FloatingWindowuses it, which covers bulk edit, the upload queue and the agent image preview; the media viewer renders its own shell, so it wires the hook directly.Tests
multiSelectActions.test.tsxrendersMediaCanvasandBulkEditWindowinside the real session and confirm providers and drives the menu, the drag and the dialog — no copies of the logic. Each case was checked against a deliberately broken component: no confirmation, a menu or drag that ignores the selection, a bulk purge over the whole selection. Every one turns the suite red.Writing them found a bug the copies hid: the bulk dialog counted only trashed files, but the purge loop ran over the whole selection, firing a request per live file that was bound to 400. Fixed in the same commit.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com