Drag a whole selection and join existing groups in the hierarchy (#241)
Three follow-ups to the hierarchy groups. Dragging a row that belonged to a multi-selection moved only that one row. The group dialog could only create a new group. The group row drew its folder in front of the label, unlike every other row in the hierarchy. - Dragging a selected row moves every selected object. This holds for reordering, dropping into a group or between its members, dragging out of a group and dropping onto an object as new parent. - The moved objects land side by side in hierarchy order, whichever row was grabbed. The whole move is one undo step. - A selected child of a selected object travels with its parent, not on its own. Dropping the selection onto its own rows does nothing. - Dragging a row that is not selected still moves only that row. - hierarchyDrop.moveObjects replaces the single-object rule. One object is the case with one element, so the old drop tests hold unchanged. - The group dialog has a "Group" dropdown: New group or an existing group. Picking a group greys out the name and turns the button into "Add". A group that holds all chosen objects already is not offered. - Rename keeps using the dialog without the dropdown. - ListRow draws an end icon at the very right and the hint left of it. Only the hierarchy uses iconAtEnd, so other lists do not change. - New test hook __gm.hierarchyHint() gives the centre of a row's hint. Not clicked in the browser: dragging a selection in the container tab and onto an object as parent. Only the unit tests cover these. Tests: - ./check.sh: 178 files, 2564 tests, CHECK EXIT 0 - e2e 1 + 52: ALL PASSED, 27 checks - 7 new unit tests in hierarchy-drop.test.ts, 4 new e2e checks in 52i and 52j with real clicks and a real drag - 4 counter-probes bite: drop rule ignores the selection (5 unit tests fail), icon column not kept free, dialog ignores the picked group, panel ignores the selection (only o2 lands in the group) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
893b81f798
commit
2eeabf7c23
10 files changed
+327
-64
No files matched your search
@@ -20,9 +20,12 @@ function grouped() {
|
||||
const group = new GroupObjectsCommand(f.sceneId, [b!, d!], 'Crates');
|
||||
f.stack.execute(group);
|
||||
const row = groupRowId(group.groupId);
|
||||
/** Drops like the panel does, with the given rows unfolded; returns how many commands it made. */
|
||||
const drop = (dragged: string, spot: DropSpot, open: string[] = [row]): number => {
|
||||
const { commands } = hierarchyDrop(f.sceneOf(), dragged, spot, (id) => open.includes(id));
|
||||
/**
|
||||
* Drops like the panel does, with the given rows unfolded and `selected` as the selection;
|
||||
* returns how many commands it made.
|
||||
*/
|
||||
const drop = (dragged: string, spot: DropSpot, open: string[] = [row], selected: string[] = []): number => {
|
||||
const { commands } = hierarchyDrop(f.sceneOf(), dragged, spot, (id) => open.includes(id), selected);
|
||||
if (commands.length > 0) f.stack.execute(oneStep('Move object', commands));
|
||||
return commands.length;
|
||||
};
|
||||
@@ -112,3 +115,62 @@ describe('dropping rows in the hierarchy with groups (#241)', () => {
|
||||
expect(f.listed()).toBe('a .k [Crates] .b .d c e');
|
||||
});
|
||||
});
|
||||
|
||||
describe('dragging several selected rows at once', () => {
|
||||
it('puts every selected object into the group it is dropped onto, as one undo step', () => {
|
||||
const f = grouped();
|
||||
f.drop(f.e, { at: 'onto', targetId: f.row }, [f.row], [f.c, f.e]);
|
||||
expect(f.members()).toEqual(['b', 'd', 'c', 'e']);
|
||||
expect(f.listed()).toBe('a .k [Crates] .b .d .c .e');
|
||||
f.stack.undo();
|
||||
expect(f.listed()).toBe('a .k [Crates] .b .d c e');
|
||||
});
|
||||
|
||||
it('moves the selection side by side into a gap, in the order it was listed', () => {
|
||||
const f = grouped();
|
||||
// Dragged by its last row, still c comes before e.
|
||||
f.drop(f.e, { at: 'before', targetId: f.a }, [f.row], [f.e, f.c]);
|
||||
expect(f.listed()).toBe('c e a .k [Crates] .b .d');
|
||||
});
|
||||
|
||||
it('takes members out of the group and joins others to it in the same drop', () => {
|
||||
const f = grouped();
|
||||
f.drop(f.b, { at: 'after', targetId: f.e }, [f.row], [f.b, f.a]);
|
||||
expect(f.members()).toEqual(['d']);
|
||||
// The group now stands where d, its first member left, is stored – behind c.
|
||||
expect(f.listed()).toBe('c [Crates] .d e a .k b');
|
||||
f.drop(f.a, { at: 'after', targetId: f.d }, [f.row], [f.a, f.c]);
|
||||
expect(f.members()).toEqual(['d', 'c', 'a']);
|
||||
});
|
||||
|
||||
it('parents every selected object to the row they are dropped onto', () => {
|
||||
const f = grouped();
|
||||
f.drop(f.c, { at: 'onto', targetId: f.a }, [f.row], [f.c, f.e]);
|
||||
expect(findObject(f.sceneOf(), f.c)!.parentId).toBe(f.a);
|
||||
expect(findObject(f.sceneOf(), f.e)!.parentId).toBe(f.a);
|
||||
});
|
||||
|
||||
it('carries a selected child with its selected parent instead of on its own', () => {
|
||||
const f = grouped();
|
||||
f.drop(f.a, { at: 'after', targetId: f.e }, [f.row], [f.a, f.k]);
|
||||
expect(findObject(f.sceneOf(), f.k)!.parentId).toBe(f.a);
|
||||
expect(f.listed()).toBe('[Crates] .b .d c e a .k');
|
||||
});
|
||||
|
||||
it('moves only the dragged row when it is not part of the selection', () => {
|
||||
const f = grouped();
|
||||
f.drop(f.e, { at: 'before', targetId: f.a }, [f.row], [f.c]);
|
||||
expect(f.listed()).toBe('e a .k [Crates] .b .d c');
|
||||
});
|
||||
|
||||
it('makes nothing of a drop onto or between the selected rows themselves', () => {
|
||||
const f = grouped();
|
||||
expect(f.drop(f.c, { at: 'onto', targetId: f.e }, [f.row], [f.c, f.e])).toBe(0);
|
||||
expect(f.drop(f.a, { at: 'before', targetId: f.k }, [f.row], [f.a, f.c])).toBe(0);
|
||||
expect(f.drop(f.c, { at: 'after', targetId: f.d }, [f.row], [f.c, f.e])).toBeGreaterThan(0);
|
||||
f.stack.undo();
|
||||
// Already side by side at the end of the top level: nothing to do.
|
||||
expect(f.drop(f.c, { at: 'end' }, [f.row], [f.c, f.e])).toBe(0);
|
||||
expect(f.listed()).toBe('a .k [Crates] .b .d c e');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user