Skip to content

Commit cea5965

Browse files
committed
✨ 支持无源多向边
1 parent d4c3873 commit cea5965

File tree

11 files changed

+149
-128
lines changed

11 files changed

+149
-128
lines changed

app/src/components/context-menu-content.tsx

Lines changed: 57 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
import { Button } from "@/components/ui/button";
2-
import {
3-
ContextMenuContent,
4-
ContextMenuItem,
5-
ContextMenuSub,
6-
ContextMenuSubContent,
7-
ContextMenuSubTrigger,
8-
} from "@/components/ui/context-menu";
2+
import { ContextMenuContent, ContextMenuItem } from "@/components/ui/context-menu";
93
import { MouseLocation } from "@/core/service/controlService/MouseLocation";
4+
import { ConnectableEntity } from "@/core/stage/stageObject/abstract/ConnectableEntity";
5+
import { MultiTargetUndirectedEdge } from "@/core/stage/stageObject/association/MutiTargetUndirectedEdge";
106
import { TextNode } from "@/core/stage/stageObject/entity/TextNode";
117
import { activeProjectAtom } from "@/state";
128
import { useAtom } from "jotai";
@@ -23,23 +19,27 @@ import {
2319
Box,
2420
Clipboard,
2521
Copy,
22+
Dot,
2623
Package,
27-
Plus,
2824
Scissors,
25+
SquareRoundCorner,
2926
TextSelect,
3027
Trash,
3128
} from "lucide-react";
29+
import { useTranslation } from "react-i18next";
30+
import { toast } from "sonner";
3231
import KeyTooltip from "./key-tooltip";
3332

3433
const Content = ContextMenuContent;
3534
const Item = ContextMenuItem;
36-
const Sub = ContextMenuSub;
37-
const SubTrigger = ContextMenuSubTrigger;
38-
const SubContent = ContextMenuSubContent;
35+
// const Sub = ContextMenuSub;
36+
// const SubTrigger = ContextMenuSubTrigger;
37+
// const SubContent = ContextMenuSubContent;
3938
// const Separator = ContextMenuSeparator;
4039

4140
export default function MyContextMenuContent() {
4241
const [p] = useAtom(activeProjectAtom);
42+
const { t } = useTranslation("contextMenu");
4343
if (!p) return <></>;
4444

4545
return (
@@ -142,20 +142,41 @@ export default function MyContextMenuContent() {
142142
</Item>
143143
<Item onClick={() => p.stageManager.packEntityToSectionBySelected()}>
144144
<Box />
145-
打包为 Section
145+
{t("packToSection")}
146+
</Item>
147+
<Item
148+
onClick={() => {
149+
const selectedNodes = p.stageManager
150+
.getSelectedEntities()
151+
.filter((node) => node instanceof ConnectableEntity);
152+
if (selectedNodes.length <= 1) {
153+
toast.error("至少选择两个可连接节点");
154+
return;
155+
}
156+
const edge = MultiTargetUndirectedEdge.createFromSomeEntity(p, selectedNodes);
157+
p.stageManager.add(edge);
158+
}}
159+
>
160+
<Asterisk />
161+
{t("createMTUEdgeLine")}
162+
</Item>
163+
<Item
164+
onClick={() => {
165+
const selectedNodes = p.stageManager
166+
.getSelectedEntities()
167+
.filter((node) => node instanceof ConnectableEntity);
168+
if (selectedNodes.length <= 1) {
169+
toast.error("至少选择两个可连接节点");
170+
return;
171+
}
172+
const edge = MultiTargetUndirectedEdge.createFromSomeEntity(p, selectedNodes);
173+
edge.renderType = "convex";
174+
p.stageManager.add(edge);
175+
}}
176+
>
177+
<SquareRoundCorner />
178+
{t("createMTUEdgeConvex")}
146179
</Item>
147-
<Sub>
148-
<SubTrigger>
149-
<Plus />
150-
创建关系
151-
</SubTrigger>
152-
<SubContent>
153-
<Item>
154-
<Asterisk />
155-
无源多向边
156-
</Item>
157-
</SubContent>
158-
</Sub>
159180
</>
160181
)}
161182
{p.stageManager.getSelectedStageObjects().length === 0 && (
@@ -166,36 +187,28 @@ export default function MyContextMenuContent() {
166187
}
167188
>
168189
<TextSelect />
169-
新建文本节点
190+
{t("createTextNode")}
191+
</Item>
192+
<Item
193+
onClick={() => p.controllerUtils.createConnectPoint(p.renderer.transformView2World(MouseLocation.vector()))}
194+
>
195+
<Dot />
196+
{t("createConnectPoint")}
170197
</Item>
171-
<Sub>
172-
<SubTrigger>
173-
<Plus />
174-
新建节点
175-
</SubTrigger>
176-
<SubContent>
177-
{/* <Item
178-
onClick={() =>
179-
p.controllerUtils.createConnectPoint(p.renderer.transformView2World(MouseLocation.vector()))
180-
}
181-
>
182-
<Dot />
183-
质点
184-
</Item> */}
185-
<Item>待完善...</Item>
186-
</SubContent>
187-
</Sub>
188198
</>
189199
)}
190200
{p.stageManager.getSelectedEntities().filter((it) => it instanceof TextNode).length > 0 && (
191201
<>
192202
<Item
193203
onClick={() =>
194-
p.stageManager.getSelectedEntities().map((it) => p.sectionPackManager.targetTextNodeToSection(it))
204+
p.stageManager
205+
.getSelectedEntities()
206+
.filter((it) => it instanceof TextNode)
207+
.map((it) => p.sectionPackManager.targetTextNodeToSection(it))
195208
}
196209
>
197210
<Package />
198-
{p.stageManager.getSelectedEntities().length >= 2 && "分别"}转换为 Section
211+
{t("convertToSection")}
199212
</Item>
200213
</>
201214
)}

app/src/core/render/canvas2d/entityRenderer/multiTargetUndirectedEdge/MultiTargetUndirectedEdgeRenderer.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { Color, Vector } from "@graphif/data-structures";
2-
import { Line } from "@graphif/shapes";
31
import { ConvexHull } from "@/core/algorithm/geometry/convexHull";
42
import { Project, service } from "@/core/Project";
5-
import { MultiTargetUndirectedEdge } from "@/core/stage/stageObject/association/MutiTargetUndirectedEdge";
63
import { Renderer } from "@/core/render/canvas2d/renderer";
4+
import { MultiTargetUndirectedEdge } from "@/core/stage/stageObject/association/MutiTargetUndirectedEdge";
5+
import { Color, Vector } from "@graphif/data-structures";
6+
import { Line } from "@graphif/shapes";
77

88
@service("multiTargetUndirectedEdgeRenderer")
99
export class MultiTargetUndirectedEdgeRenderer {
@@ -16,12 +16,11 @@ export class MultiTargetUndirectedEdgeRenderer {
1616
this.project.stageStyleManager.currentStyle.CollideBoxSelected,
1717
);
1818
}
19-
const targetNodes = this.project.stageManager.getEntitiesByUUIDs(edge.targetUUIDs);
20-
if (targetNodes.length < 2) {
19+
if (edge.associationList.length < 2) {
2120
// 特殊情况,出问题了属于是
22-
if (targetNodes.length === 1) {
21+
if (edge.associationList.length === 1) {
2322
// 画一个圆环
24-
const node = targetNodes[0];
23+
const node = edge.associationList[0];
2524
const center = node.collisionBox.getRectangle().center;
2625
this.project.shapeRenderer.renderCircle(
2726
this.project.renderer.transformWorld2View(center),
@@ -31,7 +30,7 @@ export class MultiTargetUndirectedEdgeRenderer {
3130
2 * this.project.camera.currentScale,
3231
);
3332
}
34-
if (targetNodes.length === 0) {
33+
if (edge.associationList.length === 0) {
3534
// 在0 0 位置画圆
3635
this.project.shapeRenderer.renderCircle(
3736
this.project.renderer.transformWorld2View(Vector.getZero()),
@@ -61,8 +60,8 @@ export class MultiTargetUndirectedEdgeRenderer {
6160
}
6261
if (edge.renderType === "line") {
6362
// 画每一条线
64-
for (let i = 0; i < targetNodes.length; i++) {
65-
const node = targetNodes[i];
63+
for (let i = 0; i < edge.associationList.length; i++) {
64+
const node = edge.associationList[i];
6665
const nodeRectangle = node.collisionBox.getRectangle();
6766
const targetLocation = nodeRectangle.getInnerLocationByRateVector(edge.rectRates[i]);
6867
const line = new Line(centerLocation, targetLocation);
@@ -102,7 +101,7 @@ export class MultiTargetUndirectedEdgeRenderer {
102101
} else if (edge.renderType === "convex") {
103102
// 凸包渲染
104103
let convexPoints: Vector[] = [];
105-
targetNodes.map((node) => {
104+
edge.associationList.map((node) => {
106105
const nodeRectangle = node.collisionBox.getRectangle().expandFromCenter(edge.padding);
107106
convexPoints.push(nodeRectangle.leftTop);
108107
convexPoints.push(nodeRectangle.rightTop);

app/src/core/stage/stageManager/StageManager.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -738,11 +738,11 @@ export class StageManager {
738738
}
739739
}
740740
for (const edge of prepareDeleteUndirectedEdge) {
741-
if (edge.targetUUIDs.length !== 2) {
741+
if (edge.associationList.length !== 2) {
742742
continue;
743743
}
744744

745-
const [fromNode, toNode] = this.getEntitiesByUUIDs(edge.targetUUIDs);
745+
const [fromNode, toNode] = edge.associationList;
746746
if (fromNode && toNode && fromNode instanceof ConnectableEntity && toNode instanceof ConnectableEntity) {
747747
const lineEdge = LineEdge.fromTwoEntity(this.project, fromNode, toNode);
748748
lineEdge.text = edge.text;

app/src/core/stage/stageManager/basicMethods/GraphMethods.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Project, service } from "@/core/Project";
22
import { ConnectableEntity } from "@/core/stage/stageObject/abstract/ConnectableEntity";
33
import { Edge } from "@/core/stage/stageObject/association/Edge";
4+
import { MultiTargetUndirectedEdge } from "../../stageObject/association/MutiTargetUndirectedEdge";
45

56
@service("graphMethods")
67
export class GraphMethods {
@@ -161,4 +162,17 @@ export class GraphMethods {
161162
}
162163
return null;
163164
}
165+
166+
getHyperEdgesByNode(node: ConnectableEntity): MultiTargetUndirectedEdge[] {
167+
const edges: MultiTargetUndirectedEdge[] = [];
168+
const hyperEdges = this.project.stageManager
169+
.getAssociations()
170+
.filter((association) => association instanceof MultiTargetUndirectedEdge);
171+
for (const hyperEdge of hyperEdges) {
172+
if (hyperEdge.associationList.includes(node)) {
173+
edges.push(hyperEdge);
174+
}
175+
}
176+
return edges;
177+
}
164178
}

app/src/core/stage/stageManager/basicMethods/HyperGraphMethods.tsx

Lines changed: 0 additions & 25 deletions
This file was deleted.

app/src/core/stage/stageManager/concreteMethods/StageDeleteManager.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ export class DeleteManager {
153153
visitedAssociations.add(edge.uuid);
154154
}
155155
} else if (edge instanceof MultiTargetUndirectedEdge) {
156-
if (edge.targetUUIDs.includes(entity.uuid) && visitedAssociations.has(edge.uuid) === false) {
156+
if (edge.associationList.includes(entity) && visitedAssociations.has(edge.uuid) === false) {
157157
prepareDeleteAssociation.push(edge);
158158
visitedAssociations.add(edge.uuid);
159159
}

app/src/core/stage/stageManager/concreteMethods/StageMultiTargetEdgeMove.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { Vector } from "@graphif/data-structures";
2-
import { Rectangle } from "@graphif/shapes";
31
import { Project, service } from "@/core/Project";
42
import { MultiTargetUndirectedEdge } from "@/core/stage/stageObject/association/MutiTargetUndirectedEdge";
3+
import { Vector } from "@graphif/data-structures";
4+
import { Rectangle } from "@graphif/shapes";
55

66
/**
77
* 多源无向边移动中心点
@@ -27,7 +27,7 @@ export class MultiTargetEdgeMove {
2727
// const endMouseDragLocation = startMouseDragLocation.add(diffLocation);
2828

2929
const boundingRectangle = Rectangle.getBoundingRectangle(
30-
this.project.stageManager.getEntitiesByUUIDs(association.targetUUIDs).map((n) => n.collisionBox.getRectangle()),
30+
association.associationList.map((n) => n.collisionBox.getRectangle()),
3131
);
3232
// 当前的中心点
3333
const currentCenter = association.centerLocation;

app/src/core/stage/stageObject/abstract/Association.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { serializable } from "@graphif/serializer";
77
* 一切连接关系的抽象
88
*/
99
export abstract class Association extends StageObject {
10+
@serializable
1011
public associationList: StageObject[] = [];
1112

1213
/**

0 commit comments

Comments
 (0)