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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
* `DataDiagramModel.importLayout()` will accept known cell types via `elementCellTypes` and `linkCellTypes` to import.
- Add `AnnotationElement` and `AnnotationLink` diagram cell types representing diagram-only elements and links which exports and imports with the diagram but does not exists in the data graph:
* Rendered by default with new built-in templates `NoteTemplate` and `NoteLinkTemplate` which use `NoteAnnotation`, `NoteEntity` and `NoteLink` template components;
* Add `DefaultRenameLinkProvider` and use it by default to allow to change annotation link labels;
* Support annotation elements in `SelectionActionEstablishLink` and new `SelectionActionAnnotate` components;
* Support annotation links in `LinkActionDelete`, `LinkActionMoveEndpoint` components.
- Support user-resizable element templates with `ElementSize` template state property:
Expand All @@ -30,6 +31,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
* Subscribe to canvas `resize` event to track viewport size;
* Subscribe to `changeCells` event from `DiagramModel` to track graph content changes.
- Add `TemplateProps.onlySelected` flag to use in the element templates to track if the element is the only one selected without performance penalty.
- Avoid eager link type creation for relation links, only create and fetch them on first render.

#### 💅 Polish
- Make dialogs fill the available viewport when the viewport width is small:
Expand All @@ -45,6 +47,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
* `changeExpanded` event is removed from element events, use `changeElementState` event instead;
* When exporting the diagram the expanded state is serialized only with `elementState` while using `isExpanded` property when importing the diagram for backward compatibility.
- Introduce `ElementTemplate.supports` property for templates to tell its capabilities such as ability to expand/collapse or resized by user.
- Deprecate `DefaultLinkTemplate` and `DefaultLink` and alias them to `StandardLinkTemplate` and `StandardRelation`:
* Change CSS class for default link template from `reactodia-default-link` to `reactodia-standard-link`.
- Move "expand/collapse on double click" global element behavior to `StandardEntity` and `ClassicEntity` implementation only.
- Add `setTemplateProperty()` utility function to easily set or unset template state property.

Expand Down
5 changes: 4 additions & 1 deletion examples/classicWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ function ClassicWorkspaceExample() {

class RenameSubclassOfProvider extends Reactodia.RenameLinkToLinkStateProvider {
override canRename(link: Reactodia.Link): boolean {
return link.typeId === 'http://www.w3.org/2000/01/rdf-schema#subClassOf';
return (
link instanceof Reactodia.AnnotationLink ||
link.typeId === 'http://www.w3.org/2000/01/rdf-schema#subClassOf'
);
}
}

Expand Down
5 changes: 4 additions & 1 deletion examples/graphAuthoring.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ function GraphAuthoringExample() {

class RenameSubclassOfProvider extends Reactodia.RenameLinkToLinkStateProvider {
override canRename(link: Reactodia.Link): boolean {
return link.typeId === 'http://www.w3.org/2000/01/rdf-schema#subClassOf';
return (
link instanceof Reactodia.AnnotationLink ||
link.typeId === 'http://www.w3.org/2000/01/rdf-schema#subClassOf'
);
}
}

Expand Down
2 changes: 1 addition & 1 deletion examples/styleCustomization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ const DoubleArrowLinkTemplate: Reactodia.LinkTemplate = {
height: 12,
},
renderLink: props => (
<Reactodia.DefaultLink {...props}
<Reactodia.StandardRelation {...props}
pathProps={{stroke: '#747da8', strokeWidth: 2}}
primaryLabelProps={{
style: {color: '#747da8'},
Expand Down
118 changes: 54 additions & 64 deletions src/diagram/linkLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -676,83 +676,73 @@ class VertexTools extends React.Component<{
};
}

export interface LinkMarkersProps {
function LinkMarkersInner(props: {
model: DiagramModel;
renderingState: MutableRenderingState;
}

export class LinkMarkers extends React.Component<LinkMarkersProps> {
private readonly listener = new EventObserver();
private readonly delayedUpdate = new Debouncer();

render() {
const {model, renderingState} = this.props;
}) {
const {model, renderingState} = props;

const sourceMarkers = new Set<LinkMarkerStyle>();
const targetMarkers = new Set<LinkMarkerStyle>();
const [cellsVersion, setCellsVersion] = React.useState(model.cellsVersion);

for (const link of model.links) {
const template = renderingState.getLinkTemplate(link);
const defaultTemplate = renderingState.shared.defaultLinkResolver(link);

const markerSource = template.markerSource ?? defaultTemplate.markerSource;
if (markerSource) {
sourceMarkers.add(markerSource);
}

const markerTarget = template.markerTarget ?? defaultTemplate.markerTarget;
if (markerTarget) {
targetMarkers.add(markerTarget);
}
}

return (
<defs>
{Array.from(sourceMarkers, marker => {
const index = renderingState.ensureLinkMarkerIndex(marker);
return (
<LinkMarker key={index}
markerIndex={index}
isStartMarker={true}
style={marker}
/>
);
})}
{Array.from(targetMarkers, marker => {
const index = renderingState.ensureLinkMarkerIndex(marker);
return (
<LinkMarker key={index}
markerIndex={index}
isStartMarker={false}
style={marker}
/>
);
})}
</defs>
);
}
React.useEffect(() => {
const listener = new EventObserver();
const delayedUpdate = new Debouncer();

componentDidMount() {
const {renderingState} = this.props;
this.listener.listen(renderingState.events, 'syncUpdate', ({layer}) => {
listener.listen(renderingState.events, 'syncUpdate', ({layer}) => {
if (layer !== RenderingLayer.Link) { return; }
this.delayedUpdate.runSynchronously();
delayedUpdate.runSynchronously();
});
this.listener.listen(renderingState.events, 'changeLinkTemplates', () => {
this.delayedUpdate.call(() => this.forceUpdate());
listener.listen(renderingState.events, 'changeLinkTemplates', () => {
delayedUpdate.call(() => setCellsVersion(model.cellsVersion));
});
}
}, []);

shouldComponentUpdate() {
return false;
}
const sourceMarkers = new Set<LinkMarkerStyle>();
const targetMarkers = new Set<LinkMarkerStyle>();

componentWillUnmount() {
this.listener.stopListening();
this.delayedUpdate.dispose();
for (const link of model.links) {
const template = renderingState.getLinkTemplate(link);
const defaultTemplate = renderingState.shared.defaultLinkResolver(link);

const markerSource = template.markerSource ?? defaultTemplate.markerSource;
if (markerSource) {
sourceMarkers.add(markerSource);
}

const markerTarget = template.markerTarget ?? defaultTemplate.markerTarget;
if (markerTarget) {
targetMarkers.add(markerTarget);
}
}

return (
<defs>
{Array.from(sourceMarkers, marker => {
const index = renderingState.ensureLinkMarkerIndex(marker);
return (
<LinkMarker key={index}
markerIndex={index}
isStartMarker={true}
style={marker}
/>
);
})}
{Array.from(targetMarkers, marker => {
const index = renderingState.ensureLinkMarkerIndex(marker);
return (
<LinkMarker key={index}
markerIndex={index}
isStartMarker={false}
style={marker}
/>
);
})}
</defs>
);
}

export const LinkMarkers = React.memo(LinkMarkersInner, () => true);

interface LinkMarkerProps {
markerIndex: number;
isStartMarker: boolean;
Expand Down
26 changes: 11 additions & 15 deletions src/editor/dataDiagramModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,11 @@ export class DataDiagramModel extends DiagramModel implements DataGraphStructure
* @see {@link importLayout}
*/
exportLayout(): SerializedDiagram {
const knownLinkTypes = new Set(this.graph.getLinks().map(link => link.typeId));
const knownLinkTypes = new Set(
this.graph.getLinks()
.filter(link => link instanceof RelationLink || link instanceof RelationGroup)
.map(link => link.typeId)
);
const linkTypeVisibility = new Map<LinkTypeIri, LinkTypeVisibility>();
for (const linkTypeIri of knownLinkTypes) {
linkTypeVisibility.set(linkTypeIri, this.getLinkVisibility(linkTypeIri));
Expand Down Expand Up @@ -543,10 +547,10 @@ export class DataDiagramModel extends DiagramModel implements DataGraphStructure
this.setLinkVisibility(linkTypeIri, visibility);
}

const usedLinkTypes = new Set<LinkTypeIri>();
for (const link of links) {
const linkType = this.createLinkType(link.typeId);
usedLinkTypes.add(linkType.id);
if (link instanceof RelationLink || link instanceof RelationGroup) {
this.createLinkType(link.typeId);
}
}

for (const element of elements) {
Expand All @@ -562,7 +566,9 @@ export class DataDiagramModel extends DiagramModel implements DataGraphStructure
private hideUnusedLinkTypes(knownLinkTypes: ReadonlyArray<LinkType>): void {
const usedTypes = new Set<LinkTypeIri>();
for (const link of this.graph.getLinks()) {
usedTypes.add(link.typeId);
if (link instanceof RelationLink || link instanceof RelationGroup) {
usedTypes.add(link.typeId);
}
}

for (const linkType of knownLinkTypes) {
Expand Down Expand Up @@ -641,22 +647,12 @@ export class DataDiagramModel extends DiagramModel implements DataGraphStructure
return element;
}

override addLink(link: Link): void {
// TODO: postpone creating link type until first render
// the same way as with element types
this.createLinkType(link.typeId);
super.addLink(link);
}

private onLinkInfoLoaded(links: LinkModel[]) {
let allowToCreate: boolean;
const cancel = () => { allowToCreate = false; };

const batch = this.history.startBatch();
for (const linkModel of links) {
// TODO: postpone creating link type until first render
// the same way as with element types
this.createLinkType(linkModel.linkTypeId);
allowToCreate = true;
this.extendedSource.trigger('createLoadedLink', {source: this, model: linkModel, cancel});
if (allowToCreate) {
Expand Down
32 changes: 21 additions & 11 deletions src/forms/renameLinkForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as React from 'react';
import { useEventStore, useSyncStore } from '../coreUtils/hooks';

import { Link } from '../diagram/elements';

import { RelationLink, RelationGroup } from '../editor/dataElements';
import { useWorkspace } from '../workspace/workspaceContext';

const FORM_CLASS = 'reactodia-form';
Expand All @@ -16,17 +16,9 @@ export interface RenameLinkFormProps {
export function RenameLinkForm(props: RenameLinkFormProps) {
const {link, onFinish} = props;

const {model, view: {renameLinkProvider}, translation: t} = useWorkspace();

const linkType = model.getLinkType(link.typeId);
const linkTypeChangeStore = useEventStore(linkType?.events, 'changeData');
const linkTypeLabel = useSyncStore(linkTypeChangeStore, () => linkType?.data?.label);

const defaultLabel = React.useMemo(
() => t.formatLabel(linkTypeLabel, link.typeId, model.language),
[link, linkTypeLabel, model.language]
);
const {view: {renameLinkProvider}, translation: t} = useWorkspace();

const defaultLabel = useDefaultLinkLabel(link);
const [customLabel, setCustomLabel] = React.useState(
renameLinkProvider?.getLabel(link) ?? defaultLabel
);
Expand Down Expand Up @@ -66,3 +58,21 @@ export function RenameLinkForm(props: RenameLinkFormProps) {
</div>
);
}

function useDefaultLinkLabel(link: Link): string {
const {model, translation: t} = useWorkspace();
const linkType = link instanceof RelationLink || link instanceof RelationGroup
? model.getLinkType(link.typeId) : undefined;
const linkTypeChangeStore = useEventStore(linkType?.events, 'changeData');
const linkTypeLabel = useSyncStore(linkTypeChangeStore, () => linkType?.data?.label);
return React.useMemo(
() => {
if (link instanceof RelationLink || link instanceof RelationGroup) {
return t.formatLabel(linkTypeLabel, link.typeId, model.language);
} else {
return '';
}
},
[link, linkTypeLabel, model.language]
);
}
Loading