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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 6 additions & 1 deletion i18n/i18n.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
}
},
Expand Down Expand Up @@ -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": {
Expand Down
7 changes: 6 additions & 1 deletion i18n/translations/en.reactodia-translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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",
Expand Down
40 changes: 20 additions & 20 deletions src/diagram/linkLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -674,35 +675,34 @@ export function LinkVertices(props: LinkVerticesProps) {
return <g className={LINK_VERTICES_CLASS}>{elements}</g>;
}

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 (
<g className={`${LINK_VERTICES_CLASS}__handle`}
data-reactodia-no-export='true'
transform={transform}
onPointerDown={this.onRemoveVertex}>
<title>Remove vertex</title>
<circle r={1} />
<path d='M-0.5,-0.5 L0.5,0.5 M0.5,-0.5 L-0.5,0.5' strokeWidth={2 / vertexRadius} />
</g>
);
}

private onRemoveVertex = (e: React.MouseEvent<SVGElement>) => {
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<SVGElement>) => {
if (e.button !== 0 /* left button */) {
return;
}
e.preventDefault();
e.stopPropagation();
const {onRemove, vertexIndex} = this.props;
onRemove(vertexIndex);
};
return (
<g className={`${LINK_VERTICES_CLASS}__handle`}
data-reactodia-no-export='true'
transform={transform}
onPointerDown={onRemoveVertex}>
<title>{t.text('link_action.vertex.remove_title')}</title>
<circle r={1} />
<path d='M-0.5,-0.5 L0.5,0.5 M0.5,-0.5 L-0.5,0.5' strokeWidth={2 / vertexRadius} />
</g>
);
}

function LinkMarkersInner(props: {
Expand Down
63 changes: 37 additions & 26 deletions src/diagram/model.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -157,6 +159,11 @@ export class DiagramModel implements GraphStructure {
this.history = history;
}

/** @hidden */
[InternalGetGraph]() {
return this.graph;
}

/**
* Current language for the diagram content.
*
Expand All @@ -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; }
Expand Down Expand Up @@ -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, [])
);
}

Expand All @@ -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)
);
}
}
Expand All @@ -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));
}

/**
Expand All @@ -391,83 +398,87 @@ 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<Link>
) {}

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);
if (!existing) {
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);
}
}
Loading