Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 166 additions & 20 deletions src/components/EditorCanvas/Canvas.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,13 @@ export default function Canvas() {
newName: "",
});

const [changeCardinalityModal, setChangeCardinalityModal] = useState({
visible: false,
relationshipId: null,
currentCardinality: "",
newCardinality: "",
});

const [areaRenameModal, setAreaRenameModal] = useState({
visible: false,
areaId: null,
Expand Down Expand Up @@ -255,6 +262,7 @@ export default function Canvas() {
const fieldRenameInputRef = useRef(null);
const tableRenameInputRef = useRef(null);
const relationshipRenameInputRef = useRef(null);
const changeCardinalityInputRef = useRef(null);

// Auto-select text when field rename modal opens
useEffect(() => {
Expand Down Expand Up @@ -295,6 +303,19 @@ export default function Canvas() {
}
}, [relationshipRenameModal.visible]);

// Auto select when the change cardinality modal opens
useEffect(() => {
if (changeCardinalityModal.visible && changeCardinalityInputRef.current) {
const timer = setTimeout(() => {
if (changeCardinalityInputRef.current) {
changeCardinalityInputRef.current.focus();
changeCardinalityInputRef.current.select();
}
}, 100);
return () => clearTimeout(timer);
}
}, [changeCardinalityModal.visible]);

// Centralized function to close all context menus
const closeAllContextMenus = () => {
setContextMenu({
Expand Down Expand Up @@ -477,6 +498,46 @@ export default function Canvas() {
});
};

const handleChangeCardinalityConfirm = () => {
const { relationshipId, newCardinality } = changeCardinalityModal;
const relationship = relationships.find((r) => r.id === relationshipId);

if (!relationship) return;

const trimmedCardinality = newCardinality.trim();
if (trimmedCardinality === "") {
handleChangeCardinalityCancel();
return;
}

const validation = validateCustomCardinality(trimmedCardinality);
if (!validation.valid) {
Toast.error(validation.message);
return;
}

const normalizedCardinality = normalizeCardinality(trimmedCardinality);
updateRelationship(relationshipId, { cardinality: normalizedCardinality });

setChangeCardinalityModal({
visible: false,
relationshipId: null,
currentCardinality: "",
newCardinality: "",
});

Toast.success("Cardinality updated.");
};

const handleChangeCardinalityCancel = () => {
setChangeCardinalityModal({
visible: false,
relationshipId: null,
currentCardinality: "",
newCardinality: "",
});
};

const handleAddField = () => {
if (contextMenu.tableId !== null) {
const table = tables.find((t) => t.id === contextMenu.tableId);
Expand Down Expand Up @@ -729,6 +790,7 @@ export default function Canvas() {

const handleRenameRelationship = () => {
if (relationshipContextMenu.relationshipId !== null) {
// Find relationship by id
const relationship = relationships.find(
(r) => r.id === relationshipContextMenu.relationshipId,
);
Expand Down Expand Up @@ -942,27 +1004,71 @@ export default function Canvas() {
if (relationshipContextMenu.relationshipId !== null) {
const relationship =
relationships[relationshipContextMenu.relationshipId];
if (relationship) {
pushUndo({
action: Action.EDIT,
element: ObjectType.RELATIONSHIP,
rid: relationshipContextMenu.relationshipId,
undo: { cardinality: relationship.cardinality },
redo: { cardinality: newCardinality },
message: `Change cardinality for ${relationship.name}`,
if (!relationship) {
handleRelationshipContextMenuClose();
return;
}

if (newCardinality === "Custom...") {
setChangeCardinalityModal({
visible: true,
relationshipId: relationshipContextMenu.relationshipId,
currentCardinality: relationship.cardinality || "",
newCardinality: relationship.cardinality || "",
});
} else {
updateRelationship(relationshipContextMenu.relationshipId, {
cardinality: newCardinality,
});
setRelationships((prev) =>
prev.map((e, idx) =>
idx === relationshipContextMenu.relationshipId
? { ...e, cardinality: newCardinality }
: e,
),
);
}
handleRelationshipContextMenuClose();
}
};

const validateCustomCardinality = (cardinality) => {
const match = cardinality.match(/^\(\s*(\d+)\s*,\s*(\d+|\*)\s*\)$/);
if (!match) {
return {
valid: false,
message: "Invalid format. Use (x,y) where x is 0 or 1, and y is greater than 1 or *",
};
}

const first = parseInt(match[1]);
const second = match[2];

// First coordinate must be 0 or 1
if (first !== 0 && first !== 1) {
return {
valid: false,
message: "First coordinate must be 0 or 1",
};
}

// Second coordinate must be * or a number greater than 1
if (second !== "*") {
const secondNum = parseInt(second);
if (isNaN(secondNum) || secondNum < 2) {
return {
valid: false,
message: "Second coordinate must be greater than 1 or *",
};
}
}

return { valid: true };
};

const normalizeCardinality = (cardinality) => {
const match = cardinality.match(/^\(\s*(\d+)\s*,\s*(\d+|\*)\s*\)$/);
if (!match) return cardinality;

const first = parseInt(match[1]);
const second = match[2] === "*" ? "*" : parseInt(match[2]).toString();

return `(${first},${second})`;
};

const handleDeleteRelationship = () => {
if (relationshipContextMenu.relationshipId !== null) {
deleteRelationship(relationshipContextMenu.relationshipId);
Expand Down Expand Up @@ -1033,7 +1139,6 @@ export default function Canvas() {
}
};

// Area context menu handlers
const handleAreaContextMenu = (e, areaId, x, y) => {
e.preventDefault();
e.stopPropagation();
Expand Down Expand Up @@ -1155,7 +1260,6 @@ export default function Canvas() {
}
};

// Note context menu handlers
const handleNoteContextMenu = (e, noteId, x, y) => {
e.preventDefault();
e.stopPropagation();
Expand Down Expand Up @@ -1376,12 +1480,9 @@ export default function Canvas() {
relatedTableId,
relatedFieldId,
) => {
// Rename the original field
renameFieldOnly(tableId, fieldId, newName);
// Rename the related field
renameFieldOnly(relatedTableId, relatedFieldId, newName);

// Update relationship names to default when foreign key fields are renamed
updateRelationshipNamesAfterFieldRename(
tableId,
fieldId,
Expand Down Expand Up @@ -3261,6 +3362,51 @@ export default function Canvas() {
</div>
</Modal>

<Modal
title={t("change") + " Cardinality"}
visible={changeCardinalityModal.visible}
onOk={handleChangeCardinalityConfirm}
onCancel={handleChangeCardinalityCancel}
okText={t("confirm")}
cancelText={t("cancel")}
>
<div style={{ padding: "20px 0" }}>
<label
style={{
display: "block",
marginBottom: "8px",
fontWeight: "bold",
}}
>
Cardinality:
</label>
<Input
ref={changeCardinalityInputRef}
value={changeCardinalityModal.newCardinality}
onChange={(value) =>
setChangeCardinalityModal((prev) => ({
...prev,
newCardinality: value,
}))
}
placeholder={"e.g., (1,n)"}
onEnterPress={handleChangeCardinalityConfirm}
/>

{/* ADD THIS SECTION FOR THE LEGEND */}
<div
style={{
marginTop: "10px",
fontSize: "12px",
color: "#a0a0a0",
lineHeight: "1.5",
}}
>
<p>Format: (x,y) where x is 0 or 1, and y is n, *, or a number.</p>
</div>
</div>
</Modal>

<Modal
title={t("rename") + " Area"}
visible={areaRenameModal.visible}
Expand Down
1 change: 0 additions & 1 deletion src/components/EditorCanvas/RelationshipContextMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ export default function RelationshipContextMenu({
document.addEventListener("click", handleClickOutside);
document.addEventListener("keydown", handleEscape);

// Add mouse leave event to the menu element
if (currentMenuRef) {
currentMenuRef.addEventListener("mouseleave", handleMouseLeave);
}
Expand Down
Loading