diff --git a/src/components/EditorCanvas/Area.jsx b/src/components/EditorCanvas/Area.jsx
index 0e1c88c..4af88b6 100644
--- a/src/components/EditorCanvas/Area.jsx
+++ b/src/components/EditorCanvas/Area.jsx
@@ -48,11 +48,47 @@ export default function Area({
});
};
- const lockUnlockArea = () => {
- setBulkSelectedElements((prev) =>
- prev.filter((el) => el.id !== data.id || el.type !== ObjectType.AREA),
- );
- updateArea(data.id, { locked: !data.locked });
+ const lockUnlockArea = (e) => {
+ const locking = !data.locked;
+ updateArea(data.id, { locked: locking });
+
+ const lockArea = () => {
+ setSelectedElement({
+ ...selectedElement,
+ element: ObjectType.NONE,
+ id: -1,
+ open: false,
+ });
+ setBulkSelectedElements((prev) =>
+ prev.filter((el) => el.id !== data.id || el.type !== ObjectType.AREA),
+ );
+ };
+
+ const unlockArea = () => {
+ const elementInBulk = {
+ id: data.id,
+ type: ObjectType.AREA,
+ initialCoords: { x: data.x, y: data.y },
+ currentCoords: { x: data.x, y: data.y },
+ };
+ if (e.ctrlKey || e.metaKey) {
+ setBulkSelectedElements((prev) => [...prev, elementInBulk]);
+ } else {
+ setBulkSelectedElements([elementInBulk]);
+ }
+ setSelectedElement((prev) => ({
+ ...prev,
+ element: ObjectType.AREA,
+ id: data.id,
+ open: false,
+ }));
+ };
+
+ if (locking) {
+ lockArea();
+ } else {
+ unlockArea();
+ }
};
const edit = () => {
diff --git a/src/components/EditorCanvas/Canvas.jsx b/src/components/EditorCanvas/Canvas.jsx
index 6ddffe8..8fbf404 100644
--- a/src/components/EditorCanvas/Canvas.jsx
+++ b/src/components/EditorCanvas/Canvas.jsx
@@ -29,7 +29,7 @@ import { useTranslation } from "react-i18next";
import { useEventListener } from "usehooks-ts";
import { areFieldsCompatible, getTableHeight } from "../../utils/utils";
import { getRectFromEndpoints, isInsideRect } from "../../utils/rect";
-import { noteWidth, State } from "../../data/constants";
+import { State, noteWidth } from "../../data/constants";
export default function Canvas() {
const { t } = useTranslation();
@@ -89,145 +89,170 @@ export default function Canvas() {
width: 0,
height: 0,
});
- const [bulkSelectRectPts, setBulkSelectRectPts] = useState({
+ const [bulkSelectRect, setBulkSelectRect] = useState({
x1: 0,
y1: 0,
x2: 0,
y2: 0,
show: false,
+ ctrlKey: false,
+ metaKey: false,
});
+ // this is used to store the element that is clicked on
+ // at the moment, and shouldn't be a part of the state
+ let elementPointerDown = null;
+
+ const isSameElement = (el1, el2) => {
+ return el1.id === el2.id && el1.type === el2.type;
+ };
const collectSelectedElements = () => {
- const rect = getRectFromEndpoints(bulkSelectRectPts);
-
+ const rect = getRectFromEndpoints(bulkSelectRect);
const elements = [];
+ const shouldAddElement = (elementRect, element) => {
+ // if ctrl key is pressed, only add the elements that are not already selected
+ // can theoretically be optimized later if the selected elements is
+ // a map from id to element (after the ids are made unique)
+ return (
+ isInsideRect(elementRect, rect) &&
+ ((!bulkSelectRect.ctrlKey && !bulkSelectRect.metaKey) ||
+ !bulkSelectedElements.some((el) => isSameElement(el, element)))
+ );
+ };
tables.forEach((table) => {
if (table.locked) return;
- if (
- isInsideRect(
- {
- x: table.x,
- y: table.y,
- width: settings.tableWidth,
- height: getTableHeight(table),
- },
- rect,
- )
- ) {
- elements.push({
- id: table.id,
- type: ObjectType.TABLE,
- currentCoords: { x: table.x, y: table.y },
- initialCoords: { x: table.x, y: table.y },
- });
+ const element = {
+ id: table.id,
+ type: ObjectType.TABLE,
+ currentCoords: { x: table.x, y: table.y },
+ initialCoords: { x: table.x, y: table.y },
+ };
+ const tableRect = {
+ x: table.x,
+ y: table.y,
+ width: settings.tableWidth,
+ height: getTableHeight(table),
+ };
+ if (shouldAddElement(tableRect, element)) {
+ elements.push(element);
}
});
areas.forEach((area) => {
if (area.locked) return;
- if (
- isInsideRect(
- {
- x: area.x,
- y: area.y,
- width: area.width,
- height: area.height,
- },
- rect,
- )
- ) {
- elements.push({
- id: area.id,
- type: ObjectType.AREA,
- currentCoords: { x: area.x, y: area.y },
- initialCoords: { x: area.x, y: area.y },
- });
+ const element = {
+ id: area.id,
+ type: ObjectType.AREA,
+ currentCoords: { x: area.x, y: area.y },
+ initialCoords: { x: area.x, y: area.y },
+ };
+ const areaRect = {
+ x: area.x,
+ y: area.y,
+ width: area.width,
+ height: area.height,
+ };
+ if (shouldAddElement(areaRect, element)) {
+ elements.push(element);
}
});
notes.forEach((note) => {
if (note.locked) return;
- if (
- isInsideRect(
- {
- x: note.x,
- y: note.y,
- width: noteWidth,
- height: note.height,
- },
- rect,
- )
- ) {
- elements.push({
- id: note.id,
- type: ObjectType.NOTE,
- currentCoords: { x: note.x, y: note.y },
- initialCoords: { x: note.x, y: note.y },
- });
+ const element = {
+ id: note.id,
+ type: ObjectType.NOTE,
+ currentCoords: { x: note.x, y: note.y },
+ initialCoords: { x: note.x, y: note.y },
+ };
+ const noteRect = {
+ x: note.x,
+ y: note.y,
+ width: noteWidth,
+ height: note.height,
+ };
+ if (shouldAddElement(noteRect, element)) {
+ elements.push(element);
}
});
- setBulkSelectedElements(elements);
- };
-
- const getElement = (element) => {
- switch (element.type) {
- case ObjectType.TABLE:
- return tables.find((t) => t.id === element.id);
- case ObjectType.AREA:
- return areas[element.id];
- case ObjectType.NOTE:
- return notes[element.id];
- default:
- return { x: 0, y: 0, locked: false };
+ if (bulkSelectRect.ctrlKey || bulkSelectRect.metaKey) {
+ setBulkSelectedElements([...bulkSelectedElements, ...elements]);
+ } else {
+ setBulkSelectedElements(elements);
}
};
- /**
- * @param {PointerEvent} e
- * @param {number} id
- * @param {ObjectType[keyof ObjectType]} type
- */
- const handlePointerDownOnElement = (e, id, type) => {
+ const handlePointerDownOnElement = (e, { element, type }) => {
if (selectedElement.open && !layout.sidebar) return;
if (!e.isPrimary) return;
- const element = getElement({ id, type });
-
- setSelectedElement((prev) => ({
- ...prev,
- element: type,
- id: id,
- open: false,
- }));
+ if (!element.locked || !(e.ctrlKey || e.metaKey)) {
+ setSelectedElement((prev) => ({
+ ...prev,
+ element: type,
+ id: element.id,
+ open: false,
+ }));
+ }
if (element.locked) {
- setBulkSelectedElements([]);
+ if (!(e.ctrlKey || e.metaKey)) {
+ setBulkSelectedElements([]);
+ }
return;
}
- let newBulkSelectedElements;
- if (bulkSelectedElements.some((el) => el.id === id && el.type === type)) {
- newBulkSelectedElements = bulkSelectedElements;
- } else {
- newBulkSelectedElements = [
- {
- id,
- type,
- currentCoords: { x: element.x, y: element.y },
- initialCoords: { x: element.x, y: element.y },
- },
- ];
- setBulkSelectedElements(newBulkSelectedElements);
+ setBulkSelectRect((prev) => ({
+ ...prev,
+ show: false,
+ }));
+
+ // this is the object that will be added to the bulk selected elements
+ // if necessary
+ const elementInBulk = {
+ id: element.id,
+ type,
+ currentCoords: { x: element.x, y: element.y },
+ initialCoords: { x: element.x, y: element.y },
+ };
+
+ const isSelected = bulkSelectedElements.some((el) =>
+ isSameElement(el, elementInBulk),
+ );
+
+ if (e.ctrlKey || e.metaKey) {
+ if (isSelected) {
+ if (bulkSelectedElements.length > 1) {
+ setBulkSelectedElements(
+ bulkSelectedElements.filter(
+ (el) => !isSameElement(el, elementInBulk),
+ ),
+ );
+ setSelectedElement({
+ ...selectedElement,
+ element: ObjectType.NONE,
+ id: -1,
+ open: false,
+ });
+ }
+ } else {
+ setBulkSelectedElements([...bulkSelectedElements, elementInBulk]);
+ }
+ setDragging(notDragging);
+ return;
}
+ if (!isSelected) {
+ setBulkSelectedElements([elementInBulk]);
+ }
setDragging({
- id,
+ id: element.id,
type,
grabOffset: {
x: pointer.spaces.diagram.x - element.x,
@@ -285,8 +310,8 @@ export default function Canvas() {
y: pointer.spaces.diagram.y - dragging.grabOffset.y,
});
- const { currentCoords } = bulkSelectedElements.find(
- (el) => el.id === dragging.id && el.type === dragging.type,
+ const { currentCoords } = bulkSelectedElements.find((el) =>
+ isSameElement(el, dragging),
);
const deltaX = mainElementFinalX - currentCoords.x;
@@ -352,8 +377,8 @@ export default function Canvas() {
return;
}
- if (bulkSelectRectPts.show) {
- setBulkSelectRectPts((prev) => ({
+ if (bulkSelectRect.show) {
+ setBulkSelectRect((prev) => ({
...prev,
x2: pointer.spaces.diagram.x,
y2: pointer.spaces.diagram.y,
@@ -379,13 +404,18 @@ export default function Canvas() {
const isMouseMiddleButton = e.button === 1;
if (isMouseLeftButton) {
- setBulkSelectRectPts({
+ setBulkSelectRect({
x1: pointer.spaces.diagram.x,
y1: pointer.spaces.diagram.y,
x2: pointer.spaces.diagram.x,
y2: pointer.spaces.diagram.y,
- show: true,
+ show: elementPointerDown === null || !elementPointerDown.element.locked,
+ ctrlKey: e.ctrlKey,
+ metaKey: e.metaKey,
});
+ if (elementPointerDown !== null) {
+ handlePointerDownOnElement(e, elementPointerDown);
+ }
pointer.setStyle("crosshair");
} else if (isMouseMiddleButton) {
setPanning({
@@ -459,8 +489,8 @@ export default function Canvas() {
);
}
- if (bulkSelectRectPts.show) {
- setBulkSelectRectPts((prev) => ({
+ if (bulkSelectRect.show) {
+ setBulkSelectRect((prev) => ({
...prev,
x2: pointer.spaces.diagram.x,
y2: pointer.spaces.diagram.y,
@@ -661,11 +691,14 @@ export default function Canvas() {
- handlePointerDownOnElement(e, a.id, ObjectType.AREA)
- }
setResize={setAreaResize}
setInitDimensions={setAreaInitDimensions}
+ onPointerDown={() => {
+ elementPointerDown = {
+ element: a,
+ type: ObjectType.AREA,
+ };
+ }}
/>
))}
{relationships.map((e, i) => (
@@ -678,9 +711,12 @@ export default function Canvas() {
setHoveredTable={setHoveredTable}
handleGripField={handleGripField}
setLinkingLine={setLinkingLine}
- onPointerDown={(e) =>
- handlePointerDownOnElement(e, table.id, ObjectType.TABLE)
- }
+ onPointerDown={() => {
+ elementPointerDown = {
+ element: table,
+ type: ObjectType.TABLE,
+ };
+ }}
/>
))}
{linking && (
@@ -695,14 +731,17 @@ export default function Canvas() {
- handlePointerDownOnElement(e, n.id, ObjectType.NOTE)
- }
+ onPointerDown={() => {
+ elementPointerDown = {
+ element: n,
+ type: ObjectType.NOTE,
+ };
+ }}
/>
))}
- {bulkSelectRectPts.show && (
+ {bulkSelectRect.show && (
{
- setBulkSelectedElements((prev) =>
- prev.filter((el) => el.id !== data.id || el.type !== ObjectType.NOTE),
- );
- updateNote(data.id, { locked: !data.locked });
+ const lockUnlockNote = (e) => {
+ const locking = !data.locked;
+ updateNote(data.id, { locked: locking });
+
+ const lockNote = () => {
+ setSelectedElement({
+ ...selectedElement,
+ element: ObjectType.NONE,
+ id: -1,
+ open: false,
+ });
+ setBulkSelectedElements((prev) =>
+ prev.filter((el) => el.id !== data.id || el.type !== ObjectType.NOTE),
+ );
+ };
+
+ const unlockNote = () => {
+ const elementInBulk = {
+ id: data.id,
+ type: ObjectType.NOTE,
+ initialCoords: { x: data.x, y: data.y },
+ currentCoords: { x: data.x, y: data.y },
+ };
+ if (e.ctrlKey || e.metaKey) {
+ setBulkSelectedElements((prev) => [...prev, elementInBulk]);
+ } else {
+ setBulkSelectedElements([elementInBulk]);
+ }
+ setSelectedElement((prev) => ({
+ ...prev,
+ element: ObjectType.NOTE,
+ id: data.id,
+ open: false,
+ }));
+ };
+
+ if (locking) {
+ lockNote();
+ } else {
+ unlockNote();
+ }
};
const edit = () => {
diff --git a/src/components/EditorCanvas/Table.jsx b/src/components/EditorCanvas/Table.jsx
index 4c51fa1..20caab5 100644
--- a/src/components/EditorCanvas/Table.jsx
+++ b/src/components/EditorCanvas/Table.jsx
@@ -24,16 +24,15 @@ import { isRtl } from "../../i18n/utils/rtl";
import i18n from "../../i18n/i18n";
import { getTableHeight } from "../../utils/utils";
-export default function Table(props) {
+export default function Table({
+ tableData,
+ onPointerDown,
+ setHoveredTable,
+ handleGripField,
+ setLinkingLine,
+}) {
const [hoveredField, setHoveredField] = useState(null);
const { database } = useDiagram();
- const {
- tableData,
- onPointerDown,
- setHoveredTable,
- handleGripField,
- setLinkingLine,
- } = props;
const { layout } = useLayout();
const { deleteTable, deleteField, updateTable } = useDiagram();
const { settings } = useSettings();
@@ -62,11 +61,49 @@ export default function Table(props) {
);
}, [selectedElement, tableData, bulkSelectedElements]);
- const lockUnlockTable = () => {
- setBulkSelectedElements((prev) =>
- prev.filter((el) => el.id !== tableData.id || el.type !== ObjectType.TABLE),
- );
- updateTable(tableData.id, { locked: !tableData.locked });
+ const lockUnlockTable = (e) => {
+ const locking = !tableData.locked;
+ updateTable(tableData.id, { locked: locking });
+
+ const lockTable = () => {
+ setSelectedElement({
+ ...selectedElement,
+ element: ObjectType.NONE,
+ id: -1,
+ open: false,
+ });
+ setBulkSelectedElements((prev) =>
+ prev.filter(
+ (el) => el.id !== tableData.id || el.type !== ObjectType.TABLE,
+ ),
+ );
+ };
+
+ const unlockTable = () => {
+ const elementInBulk = {
+ id: tableData.id,
+ type: ObjectType.TABLE,
+ initialCoords: { x: tableData.x, y: tableData.y },
+ currentCoords: { x: tableData.x, y: tableData.y },
+ };
+ if (e.ctrlKey || e.metaKey) {
+ setBulkSelectedElements((prev) => [...prev, elementInBulk]);
+ } else {
+ setBulkSelectedElements([elementInBulk]);
+ }
+ setSelectedElement((prev) => ({
+ ...prev,
+ element: ObjectType.TABLE,
+ id: tableData.id,
+ open: false,
+ }));
+ };
+
+ if (locking) {
+ lockTable();
+ } else {
+ unlockTable();
+ }
};
const openEditor = () => {