feat: more idiomatic element filters [POC]
This commit is contained in:
parent
cec5232a7a
commit
32da1819f9
@ -68,3 +68,28 @@ export type MaybePromise<T> = T | Promise<T>;
|
|||||||
|
|
||||||
// get union of all keys from the union of types
|
// get union of all keys from the union of types
|
||||||
export type AllPossibleKeys<T> = T extends any ? keyof T : never;
|
export type AllPossibleKeys<T> = T extends any ? keyof T : never;
|
||||||
|
|
||||||
|
// utlity types for filter helper and related data structures
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
export type ReadonlyArrayOrMap<
|
||||||
|
T,
|
||||||
|
K = T extends { id: string } ? T["id"] : string,
|
||||||
|
> = readonly T[] | ReadonlyMap<K, T>;
|
||||||
|
|
||||||
|
export type GenericAccumulator<T = unknown> = Set<T> | Map<T, T> | Array<T>;
|
||||||
|
export type ArrayAccumulator<T = unknown> = Array<T>;
|
||||||
|
export type MapAccumulator<T = unknown, K = unknown> = Map<T, K>;
|
||||||
|
export type SetAccumulator<T = unknown> = Set<T>;
|
||||||
|
export type OutputAccumulator<
|
||||||
|
Accumulator,
|
||||||
|
OutputType,
|
||||||
|
Attr extends keyof OutputType = never,
|
||||||
|
> = Accumulator extends SetAccumulator
|
||||||
|
? Set<[Attr] extends [never] ? OutputType : Attr>
|
||||||
|
: Accumulator extends MapAccumulator
|
||||||
|
? Map<
|
||||||
|
OutputType extends { id: string } ? OutputType["id"] : string,
|
||||||
|
OutputType
|
||||||
|
>
|
||||||
|
: Array<OutputType>;
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
@ -26,6 +26,10 @@ import {
|
|||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
|
|
||||||
|
import { filterElements } from "./utils";
|
||||||
|
|
||||||
|
import { getFrameChildren } from "./frame";
|
||||||
|
|
||||||
import type Scene from "./Scene";
|
import type Scene from "./Scene";
|
||||||
|
|
||||||
import type { Bounds } from "./bounds";
|
import type { Bounds } from "./bounds";
|
||||||
@ -65,23 +69,13 @@ export const dragSelectedElements = (
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// we do not want a frame and its elements to be selected at the same time
|
// update frames and their children (use a set to make sure we avoid
|
||||||
// but when it happens (due to some bug), we want to avoid updating element
|
// duplicates in case the user already selected the frame's children)
|
||||||
// in the frame twice, hence the use of set
|
const elementsToUpdate = getFrameChildren(
|
||||||
const elementsToUpdate = new Set<NonDeletedExcalidrawElement>(
|
scene.getNonDeletedElements(),
|
||||||
selectedElements,
|
filterElements(selectedElements, isFrameLikeElement, new Set(), "id"),
|
||||||
|
new Set(selectedElements),
|
||||||
);
|
);
|
||||||
const frames = selectedElements
|
|
||||||
.filter((e) => isFrameLikeElement(e))
|
|
||||||
.map((f) => f.id);
|
|
||||||
|
|
||||||
if (frames.length > 0) {
|
|
||||||
for (const element of scene.getNonDeletedElements()) {
|
|
||||||
if (element.frameId !== null && frames.includes(element.frameId)) {
|
|
||||||
elementsToUpdate.add(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const origElements: ExcalidrawElement[] = [];
|
const origElements: ExcalidrawElement[] = [];
|
||||||
|
|
||||||
|
@ -9,7 +9,13 @@ import type {
|
|||||||
StaticCanvasAppState,
|
StaticCanvasAppState,
|
||||||
} from "@excalidraw/excalidraw/types";
|
} from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
import type { ReadonlySetLike } from "@excalidraw/common/utility-types";
|
import type {
|
||||||
|
ArrayAccumulator,
|
||||||
|
GenericAccumulator,
|
||||||
|
ReadonlyArrayOrMap,
|
||||||
|
ReadonlySetLike,
|
||||||
|
OutputAccumulator,
|
||||||
|
} from "@excalidraw/common/utility-types";
|
||||||
|
|
||||||
import { getElementsWithinSelection, getSelectedElements } from "./selection";
|
import { getElementsWithinSelection, getSelectedElements } from "./selection";
|
||||||
import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups";
|
import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups";
|
||||||
@ -27,6 +33,8 @@ import {
|
|||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
|
|
||||||
|
import { filterElements } from "./utils";
|
||||||
|
|
||||||
import type { ExcalidrawElementsIncludingDeleted } from "./Scene";
|
import type { ExcalidrawElementsIncludingDeleted } from "./Scene";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -230,17 +238,30 @@ export const groupByFrameLikes = (elements: readonly ExcalidrawElement[]) => {
|
|||||||
return frameElementsMap;
|
return frameElementsMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFrameChildren = (
|
export const getFrameChildren = <
|
||||||
allElements: ElementsMapOrArray,
|
K extends ExcalidrawElement,
|
||||||
frameId: string,
|
O extends GenericAccumulator = ArrayAccumulator,
|
||||||
) => {
|
>(
|
||||||
const frameChildren: ExcalidrawElement[] = [];
|
allElements: ReadonlyArrayOrMap<K>,
|
||||||
for (const element of allElements.values()) {
|
frameId: K["id"] | Set<string>,
|
||||||
if (element.frameId === frameId) {
|
output?: O,
|
||||||
frameChildren.push(element);
|
): OutputAccumulator<O, K> => {
|
||||||
}
|
if (frameId instanceof Set && frameId.size === 0) {
|
||||||
|
return (output || []) as any as OutputAccumulator<O, K>;
|
||||||
}
|
}
|
||||||
return frameChildren;
|
|
||||||
|
return filterElements(
|
||||||
|
allElements,
|
||||||
|
(element): element is K => {
|
||||||
|
if (!element.frameId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return typeof frameId === "string"
|
||||||
|
? element.frameId === frameId
|
||||||
|
: frameId.has(element.frameId);
|
||||||
|
},
|
||||||
|
output || [],
|
||||||
|
) as any as OutputAccumulator<O, K>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFrameLikeElements = (
|
export const getFrameLikeElements = (
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
type GlobalPoint,
|
type GlobalPoint,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import { elementCenterPoint } from "@excalidraw/common";
|
import { elementCenterPoint, isReadonlyArray } from "@excalidraw/common";
|
||||||
|
|
||||||
import type { Curve, LineSegment } from "@excalidraw/math";
|
import type { Curve, LineSegment } from "@excalidraw/math";
|
||||||
|
|
||||||
@ -18,8 +18,15 @@ import { getCornerRadius } from "./shapes";
|
|||||||
|
|
||||||
import { getDiamondPoints } from "./bounds";
|
import { getDiamondPoints } from "./bounds";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
GenericAccumulator,
|
||||||
|
OutputAccumulator,
|
||||||
|
ReadonlyArrayOrMap,
|
||||||
|
} from "../../common/src/utility-types";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawDiamondElement,
|
ExcalidrawDiamondElement,
|
||||||
|
ExcalidrawElement,
|
||||||
ExcalidrawRectanguloidElement,
|
ExcalidrawRectanguloidElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
@ -353,3 +360,35 @@ export function deconstructDiamondElement(
|
|||||||
|
|
||||||
return [sides, corners];
|
return [sides, corners];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const filterElements = <
|
||||||
|
InputType extends ExcalidrawElement,
|
||||||
|
PredicateOutputType extends InputType,
|
||||||
|
AccumulatorType extends GenericAccumulator,
|
||||||
|
Attr extends keyof PredicateOutputType = never,
|
||||||
|
>(
|
||||||
|
elements: ReadonlyArrayOrMap<InputType>,
|
||||||
|
predicate: (elem: InputType) => elem is PredicateOutputType,
|
||||||
|
accumulator: AccumulatorType,
|
||||||
|
attr?: Attr,
|
||||||
|
): OutputAccumulator<AccumulatorType, PredicateOutputType, Attr> => {
|
||||||
|
for (const element of isReadonlyArray(elements)
|
||||||
|
? elements
|
||||||
|
: elements.values()) {
|
||||||
|
if (predicate(element)) {
|
||||||
|
if (accumulator instanceof Set) {
|
||||||
|
accumulator.add(attr ? element[attr] : element);
|
||||||
|
} else if (accumulator instanceof Map) {
|
||||||
|
accumulator.set(element.id, attr ? element[attr] : element);
|
||||||
|
} else {
|
||||||
|
accumulator.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return accumulator as any as OutputAccumulator<
|
||||||
|
AccumulatorType,
|
||||||
|
PredicateOutputType,
|
||||||
|
Attr
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user