Skip to content

Commit

Permalink
refactor: Extract range selection funtionality as an utility
Browse files Browse the repository at this point in the history
  • Loading branch information
miyanokomiya committed Sep 22, 2024
1 parent 328569d commit 8512982
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 20 deletions.
21 changes: 2 additions & 19 deletions src/components/shapeTreePanel/ShapeTreePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { isGroupShape } from "../../shapes/group";
import { isAlignBoxShape } from "../../shapes/align/alignBox";
import { isCtrlOrMeta } from "../../utils/devices";
import { ToggleInput } from "../atoms/inputs/ToggleInput";
import { selectShapesInRange } from "../../composables/states/appCanvas/commons";

type DropOperation = "group" | "above" | "below";

Expand Down Expand Up @@ -38,25 +39,7 @@ export const ShapeTreePanel: React.FC = () => {
if (multi) {
ctx.multiSelectShapes([id], true);
} else if (range) {
const lastId = ctx.getLastSelectedShapeId();
if (!lastId) return;

const composite = ctx.getShapeComposite();
const lastSelected = composite.shapeMap[lastId];
const siblings =
composite.mergedShapeTreeMap[lastSelected.parentId ?? ""]?.children ?? composite.mergedShapeTree;
const siblingIds = siblings.map((s) => s.id);
const lastIndex = siblingIds.findIndex((sid) => sid === lastSelected.id);
const targetIndex = siblingIds.findIndex((sid) => sid === id);
if (lastIndex < targetIndex) {
const ids = siblingIds.slice(lastIndex, targetIndex);
ids.push(id);
ctx.multiSelectShapes(ids, true);
} else if (targetIndex < lastIndex) {
const ids = siblingIds.slice(targetIndex + 1, lastIndex + 1);
ids.push(id);
ctx.multiSelectShapes(ids, true);
}
selectShapesInRange(ctx, id);
} else {
ctx.selectShape(id);
handleEvent({
Expand Down
63 changes: 62 additions & 1 deletion src/composables/states/appCanvas/commons.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { expect, test, describe, vi } from "vitest";
import { getCommonAcceptableEvents, handleCommonWheel, handleHistoryEvent, handleStateEvent } from "./commons";
import {
getCommonAcceptableEvents,
handleCommonWheel,
handleHistoryEvent,
handleStateEvent,
selectShapesInRange,
} from "./commons";
import { createShape, getCommonStruct } from "../../../shapes";
import { RectangleShape } from "../../../shapes/rectangle";
import { UserSetting } from "../../../models";
import { createInitialAppCanvasStateContext } from "../../../contexts/AppCanvasContext";
import { createStyleScheme } from "../../../models/factories";
import { newShapeComposite } from "../../shapeComposite";

function getMockCtx() {
return {
Expand Down Expand Up @@ -96,3 +103,57 @@ describe("handleCommonWheel", () => {
expect(ctx2.scrollView).not.toHaveBeenCalled();
});
});

describe("selectShapesInRange", () => {
function getCtx() {
return {
getLastSelectedShapeId: vi.fn(),
multiSelectShapes: vi.fn(),
getShapeComposite: () =>
newShapeComposite({
shapes: [
createShape(getCommonStruct, "group", { id: "group" }),
createShape(getCommonStruct, "rectangle", { id: "child0", parentId: "group" }),
createShape(getCommonStruct, "rectangle", { id: "child1", parentId: "group" }),
createShape(getCommonStruct, "rectangle", { id: "child2", parentId: "group" }),
createShape(getCommonStruct, "rectangle", { id: "root1" }),
createShape(getCommonStruct, "rectangle", { id: "root2" }),
],
getStruct: getCommonStruct,
}),
};
}

test("should select shapes in the range and set the target shape the latest", () => {
const ctx0 = getCtx();
ctx0.getLastSelectedShapeId.mockReturnValue("child0");
selectShapesInRange(ctx0, "child2");
expect(ctx0.multiSelectShapes).toHaveBeenCalledWith(["child0", "child1", "child2"], true);

const ctx1 = getCtx();
ctx1.getLastSelectedShapeId.mockReturnValue("child2");
selectShapesInRange(ctx1, "child0");
expect(ctx1.multiSelectShapes).toHaveBeenCalledWith(["child1", "child2", "child0"], true);
});

test("should select shapes in the range: for root shapes", () => {
const ctx0 = getCtx();
ctx0.getLastSelectedShapeId.mockReturnValue("group");
selectShapesInRange(ctx0, "root2");
expect(ctx0.multiSelectShapes).toHaveBeenCalledWith(["group", "root1", "root2"], true);
});

test("should not change selection when target shape isn't in the same scope of selected shapes", () => {
const ctx0 = getCtx();
ctx0.getLastSelectedShapeId.mockReturnValue("child0");
selectShapesInRange(ctx0, "root2");
expect(ctx0.multiSelectShapes).not.toHaveBeenCalled();
});

test("should select the target when no shape is selected", () => {
const ctx0 = getCtx();
ctx0.getLastSelectedShapeId.mockReturnValue(undefined);
selectShapesInRange(ctx0, "child2");
expect(ctx0.multiSelectShapes).toHaveBeenCalledWith(["child2"], true);
});
});
30 changes: 30 additions & 0 deletions src/composables/states/appCanvas/commons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -703,3 +703,33 @@ export function panViewToShape(
scale: ctx.getScale(),
});
}

export function selectShapesInRange(
ctx: Pick<AppCanvasStateContext, "getLastSelectedShapeId" | "multiSelectShapes" | "getShapeComposite">,
targetId: string,
) {
const lastId = ctx.getLastSelectedShapeId();
if (!lastId) {
ctx.multiSelectShapes([targetId], true);
return;
}

const shapeComposite = ctx.getShapeComposite();
const lastSelected = shapeComposite.shapeMap[lastId];
const siblings =
shapeComposite.mergedShapeTreeMap[lastSelected.parentId ?? ""]?.children ?? shapeComposite.mergedShapeTree;
const siblingIds = siblings.map((s) => s.id);
const lastIndex = siblingIds.findIndex((id) => id === lastSelected.id);
const targetIndex = siblingIds.findIndex((id) => id === targetId);
if (targetIndex === -1) return;

if (lastIndex < targetIndex) {
const ids = siblingIds.slice(lastIndex, targetIndex);
ids.push(targetId);
ctx.multiSelectShapes(ids, true);
} else if (targetIndex < lastIndex) {
const ids = siblingIds.slice(targetIndex + 1, lastIndex + 1);
ids.push(targetId);
ctx.multiSelectShapes(ids, true);
}
}

0 comments on commit 8512982

Please sign in to comment.