diff --git a/CHANGELOG.md b/CHANGELOG.md
index 957a141a..8da0350a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
#### 🐛 Fixed
- Fix partially or fully hidden outlines for `WorkspaceLayoutItem` headers and `Navigator` toggle button.
+- Fix undo/redo not adding or removing graph elements or links after `DataDiagramModel.discardLayout()` call (e.g. by a reload from `useLoadedWorkspace()`).
#### ⏱ Performance
- Fix canvas panning optimization not being applied due to incorrect `z-index` value.
diff --git a/i18n/i18n.schema.json b/i18n/i18n.schema.json
index 453cadf3..ae543b97 100644
--- a/i18n/i18n.schema.json
+++ b/i18n/i18n.schema.json
@@ -101,12 +101,16 @@
"$ref": "#/$defs/Group",
"additionalProperties": false,
"properties": {
+ "add_element.command": { "$ref": "#/$defs/Value" },
+ "add_link.command": { "$ref": "#/$defs/Value" },
"create_links.command": { "$ref": "#/$defs/Value" },
"group_entities.command": { "$ref": "#/$defs/Value" },
"import_layout.command": { "$ref": "#/$defs/Value" },
"regroup_relations.command": { "$ref": "#/$defs/Value" },
"request_entities.command": { "$ref": "#/$defs/Value" },
"request_relations.command": { "$ref": "#/$defs/Value" },
+ "remove_element.command": { "$ref": "#/$defs/Value" },
+ "remove_link.command": { "$ref": "#/$defs/Value" },
"ungroup_entities.command": { "$ref": "#/$defs/Value" }
}
},
@@ -196,7 +200,8 @@
"edit_relation.title_disabled": { "$ref": "#/$defs/Value" },
"rename_link.title": { "$ref": "#/$defs/Value" },
"move_relation.move_source_title": { "$ref": "#/$defs/Value" },
- "move_relation.move_target_title": { "$ref": "#/$defs/Value" }
+ "move_relation.move_target_title": { "$ref": "#/$defs/Value" },
+ "vertex.remove_title": { "$ref": "#/$defs/Value" }
}
},
"navigator": {
diff --git a/i18n/translations/en.reactodia-translation.json b/i18n/translations/en.reactodia-translation.json
index 1be1f2e6..c1cb0d90 100644
--- a/i18n/translations/en.reactodia-translation.json
+++ b/i18n/translations/en.reactodia-translation.json
@@ -74,12 +74,16 @@
"sort_smart.title": "Smart sort"
},
"data_diagram_model": {
+ "add_element.command": "Add element",
+ "add_link.command": "Add link",
"create_links.command": "Create links",
"group_entities.command": "Group entities",
"import_layout.command": "Import layout",
"regroup_relations.command": "Regroup relations",
"request_entities.command": "Request entity data",
"request_relations.command": "Request relations between entities",
+ "remove_element.command": "Remove element",
+ "remove_link.command": "Remove link",
"ungroup_entities.command": "Ungroup entities"
},
"default_data_locale": {
@@ -137,7 +141,8 @@
"edit_relation.title_disabled": "Cannot edit the relation",
"rename_link.title": "Rename link",
"move_relation.move_source_title": "Move link source",
- "move_relation.move_target_title": "Move link target"
+ "move_relation.move_target_title": "Move link target",
+ "vertex.remove_title": "Remove vertex"
},
"navigator": {
"toggle_collapse.title": "Collapse navigator",
diff --git a/src/diagram/linkLayer.tsx b/src/diagram/linkLayer.tsx
index 449f5a9e..662abce4 100644
--- a/src/diagram/linkLayer.tsx
+++ b/src/diagram/linkLayer.tsx
@@ -4,6 +4,7 @@ import { createPortal, flushSync } from 'react-dom';
import { EventObserver } from '../coreUtils/events';
import { useEventStore, useSyncStore } from '../coreUtils/hooks';
+import { useTranslation } from '../coreUtils/i18n';
import { HtmlPaperLayer, type PaperTransform } from '../paper/paperLayers';
@@ -674,35 +675,34 @@ export function LinkVertices(props: LinkVerticesProps) {
return {elements};
}
-class VertexTools extends React.Component<{
+function VertexTools(props: {
vertexIndex: number;
vertexRadius: number;
x: number;
y: number;
onRemove: (vertexIndex: number) => void;
-}> {
- render() {
- const {vertexRadius, x, y} = this.props;
- const transform = `translate(${x + 2 * vertexRadius},${y - 2 * vertexRadius})scale(${vertexRadius})`;
- return (
-
- Remove vertex
-
-
-
- );
- }
-
- private onRemoveVertex = (e: React.MouseEvent) => {
- if (e.button !== 0 /* left button */) { return; }
+}) {
+ const {vertexIndex, vertexRadius, x, y, onRemove} = props;
+ const t = useTranslation();
+ const transform = `translate(${x + 2 * vertexRadius},${y - 2 * vertexRadius})scale(${vertexRadius})`;
+ const onRemoveVertex = (e: React.MouseEvent) => {
+ if (e.button !== 0 /* left button */) {
+ return;
+ }
e.preventDefault();
e.stopPropagation();
- const {onRemove, vertexIndex} = this.props;
onRemove(vertexIndex);
};
+ return (
+
+ {t.text('link_action.vertex.remove_title')}
+
+
+
+ );
}
function LinkMarkersInner(props: {
diff --git a/src/diagram/model.ts b/src/diagram/model.ts
index 785a0658..fb689373 100644
--- a/src/diagram/model.ts
+++ b/src/diagram/model.ts
@@ -1,6 +1,6 @@
import { moveComparator } from '../coreUtils/collections';
import { EventSource, Events, EventObserver, AnyEvent, PropertyChange } from '../coreUtils/events';
-import { Translation } from '../coreUtils/i18n';
+import { TranslatedText, Translation } from '../coreUtils/i18n';
import { LinkTypeIri } from '../data/model';
import * as Rdf from '../data/rdf/rdfModel';
@@ -121,6 +121,8 @@ export interface DiagramModelOptions {
translation: Translation;
}
+const InternalGetGraph: unique symbol = Symbol('DiagramModel.getGraph');
+
/**
* Stores the diagram content: graph (elements, links);
* maintains selection and the current language to display the data.
@@ -157,6 +159,11 @@ export class DiagramModel implements GraphStructure {
this.history = history;
}
+ /** @hidden */
+ [InternalGetGraph]() {
+ return this.graph;
+ }
+
/**
* Current language for the diagram content.
*
@@ -174,7 +181,7 @@ export class DiagramModel implements GraphStructure {
*/
setLanguage(value: string): void {
if (!value) {
- throw new Error('Cannot set empty language.');
+ throw new Error('Reactodia: cannot set empty language');
}
const previous = this._language;
if (previous === value) { return; }
@@ -351,7 +358,7 @@ export class DiagramModel implements GraphStructure {
*/
addElement(element: Element): void {
this.history.execute(
- new AddElementCommand(this.graph, element, [])
+ new AddElementCommand(this, element, [])
);
}
@@ -366,7 +373,7 @@ export class DiagramModel implements GraphStructure {
const element = this.getElement(elementId);
if (element) {
this.history.execute(
- new RemoveElementCommand(this.graph, element)
+ new RemoveElementCommand(this, element)
);
}
}
@@ -380,7 +387,7 @@ export class DiagramModel implements GraphStructure {
* The operation puts a command to the {@link DiagramModel.history command history}.
*/
addLink(link: Link): void {
- this.history.execute(new AddLinkCommand(this.graph, link));
+ this.history.execute(new AddLinkCommand(this, link));
}
/**
@@ -391,24 +398,25 @@ export class DiagramModel implements GraphStructure {
removeLink(linkId: string): void {
const link = this.graph.getLink(linkId);
if (link) {
- this.history.execute(new RemoveLinkCommand(this.graph, link));
+ this.history.execute(new RemoveLinkCommand(this, link));
}
}
}
class AddElementCommand implements Command {
constructor(
- readonly graph: Graph,
+ readonly model: DiagramModel,
readonly element: Element,
readonly connectedLinks: ReadonlyArray
) {}
- get title(): string {
- return 'Add element';
+ get title(): TranslatedText {
+ return TranslatedText.text('data_diagram_model.add_element.command');
}
invoke(): Command {
- const {graph, element, connectedLinks} = this;
+ const {model, element, connectedLinks} = this;
+ const graph = model[InternalGetGraph]();
graph.addElement(element);
for (const link of connectedLinks) {
const existing = graph.getLink(link.id);
@@ -416,58 +424,61 @@ class AddElementCommand implements Command {
graph.addLink(link);
}
}
- return new RemoveElementCommand(graph, element);
+ return new RemoveElementCommand(model, element);
}
}
class RemoveElementCommand implements Command {
constructor(
- readonly graph: Graph,
+ readonly model: DiagramModel,
readonly element: Element
) {}
- get title(): string {
- return 'Remove element';
+ get title(): TranslatedText {
+ return TranslatedText.text('data_diagram_model.remove_element.command');
}
invoke(): Command {
- const {graph, element} = this;
+ const {model, element} = this;
+ const graph = model[InternalGetGraph]();
const connectedLinks = [...graph.getElementLinks(element)];
graph.removeElement(element.id);
- return new AddElementCommand(graph, element, connectedLinks);
+ return new AddElementCommand(model, element, connectedLinks);
}
}
class AddLinkCommand implements Command {
constructor(
- readonly graph: Graph,
+ readonly model: DiagramModel,
readonly link: Link
) {}
- get title(): string {
- return 'Add link';
+ get title(): TranslatedText {
+ return TranslatedText.text('data_diagram_model.add_link.command');
}
invoke(): Command {
- const {graph, link} = this;
+ const {model, link} = this;
+ const graph = model[InternalGetGraph]();
graph.addLink(link);
- return new RemoveLinkCommand(graph, link);
+ return new RemoveLinkCommand(model, link);
}
}
class RemoveLinkCommand implements Command {
constructor(
- readonly graph: Graph,
+ readonly model: DiagramModel,
readonly link: Link
) {}
- get title(): string {
- return 'Remove link';
+ get title(): TranslatedText {
+ return TranslatedText.text('data_diagram_model.remove_link.command');
}
invoke(): Command {
- const {graph, link} = this;
+ const {model, link} = this;
+ const graph = model[InternalGetGraph]();
graph.removeLink(link.id);
- return new AddLinkCommand(graph, link);
+ return new AddLinkCommand(model, link);
}
}