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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- Add `changeTransform` event to `CanvasApi.events` which triggers on `CanvasApi.metrics.getTransform()` changes, i.e. when coordinate mapping changes due to scale or canvas size is re-adjusted.
- Add `DiagramModel.cellsVersion` property which updates on every element or link addition/removal/reordering to be able to subscribe to `changeCells` event with `useSyncStore()` hook.
- Deprecate `canvasWidgets` prop on `DefaultWorkspace` and `ClassicWorkspace` in favor of passing widgets directly as children.
- Mark placeholder entity data with `PlaceholderDataProperty` property key to distinguish not-loaded-yet elements with `EntityElement.isPlaceholderData()`:
* Add `DataDiagramModel.requestData()` as a convenient method to load all placeholder entities at once.
- Move expanded element state from distinct property on `Element` to be stored in `Element.elementState` with `TemplateProperties.Expanded` property:
* All existing properties, methods and commands works as before but use element template state as storage for expanded state;
* `changeExpanded` event is removed from element events, use `changeElementState` event instead;
Expand Down
4 changes: 2 additions & 2 deletions examples/basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ function BasicExample() {
await model.createNewDiagram({dataProvider, signal});
const elementTypeId = 'http://www.w3.org/2002/07/owl#Class';
for (const {element} of await dataProvider.lookup({elementTypeId})) {
model.createElement(element);
model.createElement(element.id);
}
await model.requestLinks();
await model.requestData();

// Layout elements on canvas
await performLayout({signal});
Expand Down
22 changes: 11 additions & 11 deletions examples/graphAuthoring.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,18 @@ function GraphAuthoringExample() {
});

if (!diagram) {
const elements = [
model.createElement('http://www.w3.org/ns/org#Organization'),
model.createElement('http://www.w3.org/ns/org#FormalOrganization'),
model.createElement('http://www.w3.org/ns/org#hasMember'),
model.createElement('http://www.w3.org/ns/org#hasSubOrganization'),
model.createElement('http://www.w3.org/ns/org#subOrganizationOf'),
model.createElement('http://www.w3.org/ns/org#unitOf'),
const entities = [
'http://www.w3.org/ns/org#Organization',
'http://www.w3.org/ns/org#FormalOrganization',
'http://www.w3.org/ns/org#hasMember',
'http://www.w3.org/ns/org#hasSubOrganization',
'http://www.w3.org/ns/org#subOrganizationOf',
'http://www.w3.org/ns/org#unitOf',
];
await Promise.all([
model.requestElementData(elements.map(el => el.iri)),
model.requestLinks(),
]);
for (const entity of entities) {
model.createElement(entity);
}
await model.requestData();
await performLayout({signal});
}
}, []);
Expand Down
5 changes: 1 addition & 4 deletions examples/stressTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,7 @@ function StressTestExample() {
}));
}
batch.store();
await Promise.all([
model.requestElementData(nodes),
model.requestLinks(),
]);
await model.requestData();
model.history.reset();

const canvas = view.findAnyCanvas();
Expand Down
9 changes: 8 additions & 1 deletion src/data/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ElementTypeIri, LinkTypeIri } from './model';
import type { ElementTypeIri, LinkTypeIri, PropertyTypeIri } from './model';

/**
* [JSON-LD](https://json-ld.org/) context IRI (`@context` value) for the
Expand All @@ -7,6 +7,13 @@ import type { ElementTypeIri, LinkTypeIri } from './model';
* @category Constants
*/
export const DiagramContextV1 = 'https://ontodia.org/context/v1.json';
/**
* Property type to mark placeholder (i.e. not loaded yet) entity data.
*
* @category Constants
* @see {@link EntityElement.placeholderData}
*/
export const PlaceholderDataProperty: PropertyTypeIri = 'urn:reactodia:isPlaceholder';
/**
* Type for a newly created temporary entity in graph authoring mode.
*
Expand Down
31 changes: 28 additions & 3 deletions src/editor/dataDiagramModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -578,11 +578,36 @@ export class DataDiagramModel extends DiagramModel implements DataGraphStructure
}
}

/**
* Requests to load all {@link EntityElement entities} with
* {@link EntityElement.isPlaceholderData placeholder data} and all
* {@link RelationLink relations} connected to them.
*
* @see {@link DataDiagramModel.requestElementData}
* @see {@link DataDiagramModel.requestLinks}
*/
async requestData(): Promise<void> {
const targets = new Set<ElementIri>();
for (const element of this.elements) {
for (const entity of iterateEntitiesOf(element)) {
if (EntityElement.isPlaceholderData(entity)) {
targets.add(entity.id);
}
}
}

const elements = Array.from(targets);
await Promise.all([
this.fetcher.fetchElementData(targets),
this.requestLinks({addedElements: elements}),
]);
}

/**
* Requests to fetch the data for the specified elements from a data provider.
*/
requestElementData(elementIris: ReadonlyArray<ElementIri>): Promise<void> {
return this.fetcher.fetchElementData(elementIris);
return this.fetcher.fetchElementData(new Set<ElementIri>(elementIris));
}

/**
Expand Down Expand Up @@ -618,8 +643,8 @@ export class DataDiagramModel extends DiagramModel implements DataGraphStructure
/**
* Creates or gets an existing entity element on the diagram.
*
* If element is specified as an IRI only, then the placeholder data will
* be used.
* If element is specified as an IRI only, then the
* {@link EntityElement.placeholderData placeholder data} will be used.
*
* If multiple entity elements with the same IRI is on the diagram,
* the first one in the order will be returned.
Expand Down
23 changes: 22 additions & 1 deletion src/editor/dataElements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
PropertyTypeIri, PropertyTypeModel,
equalLinks, hashLink,
} from '../data/model';
import { PlaceholderDataProperty } from '../data/schema';

import {
Element, ElementEvents, ElementProps, ElementTemplateState,
Expand Down Expand Up @@ -64,15 +65,35 @@ export class EntityElement extends Element {
*
* This data can be used to display an entity in the UI
* until the actual data is loaded from a data provider.
*
* @see {@link PlaceholderDataProperty}
*/
static placeholderData(iri: ElementIri): ElementModel {
return {
id: iri,
types: [],
properties: {},
properties: {
[PlaceholderDataProperty]: [],
},
};
}

/**
* Returns `true` if the `data` is an empty placeholder (not yet loaded) data,
* otherwise `false`.
*
* The entity data is considered to be a placeholder data if `data.properties`
* contains `PlaceholderDataProperty` key with a empty or non-empty values.
*
* @see {@link PlaceholderDataProperty}
*/
static isPlaceholderData(data: ElementModel): boolean {
return (
Object.prototype.hasOwnProperty.call(data.properties, PlaceholderDataProperty) &&
data.properties[PlaceholderDataProperty] !== undefined
);
}

protected get entitySource(): EventSource<EntityElementEvents> {
return this.source as EventSource<any>;
}
Expand Down
32 changes: 23 additions & 9 deletions src/editor/dataFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ElementIri, ElementTypeIri, LinkTypeIri, PropertyTypeIri,
} from '../data/model';
import { DataProvider } from '../data/dataProvider';
import { PlaceholderDataProperty } from '../data/schema';

import { Graph } from '../diagram/graph';

Expand Down Expand Up @@ -264,39 +265,44 @@ export class DataFetcher {
return reasons;
}

fetchElementData(elementIris: ReadonlyArray<ElementIri>): Promise<void> {
if (elementIris.length === 0) {
fetchElementData(targets: ReadonlySet<ElementIri>): Promise<void> {
if (targets.size === 0) {
return Promise.resolve();
}
const operation: FetchOperationElement = {
type: 'element',
targets: new Set(elementIris),
targets,
};
const task = this.dataProvider
.elements({elementIds: [...elementIris], signal: this.signal})
.then(this.onElementInfoLoaded);
.elements({elementIds: Array.from(targets), signal: this.signal})
.then(result => this.onElementInfoLoaded(targets, result));
this.addOperation(operation, task);
return task;
}

private onElementInfoLoaded = (elements: Map<ElementIri, ElementModel>) => {
private onElementInfoLoaded(
targets: ReadonlySet<ElementIri>,
elements: Map<ElementIri, ElementModel>
): void {
for (const element of this.graph.getElements()) {
if (element instanceof EntityElement) {
const loadedModel = elements.get(element.iri);
if (loadedModel) {
element.setData(loadedModel);
} else if (targets.has(element.iri)) {
element.setData(unsetPlaceholder(element.data));
}
} else if (element instanceof EntityGroup) {
let hasLoadedModel = false;
for (const item of element.items) {
if (elements.has(item.data.id)) {
if (targets.has(item.data.id)) {
hasLoadedModel = true;
}
}
if (hasLoadedModel) {
const loadedItems = element.items.map((item): EntityGroupItem => {
const loadedData = elements.get(item.data.id);
return loadedData ? {...item, data: loadedData} : item;
const nextData = elements.get(item.data.id) ?? unsetPlaceholder(item.data);
return nextData === item.data ? item : {...item, data: nextData};
});
element.setItems(loadedItems);
}
Expand Down Expand Up @@ -360,3 +366,11 @@ export class DataFetcher {
}
};
}

function unsetPlaceholder(data: ElementModel): ElementModel {
if (EntityElement.isPlaceholderData(data)) {
const {[PlaceholderDataProperty]: _, ...restProperties} = data.properties;
return {...data, properties: restProperties};
}
return data;
}
2 changes: 1 addition & 1 deletion src/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export {
type ValidatedLink, type ValidationSeverity,
} from './data/validationProvider';
export {
DiagramContextV1, PlaceholderEntityType, PlaceholderRelationType,
DiagramContextV1, PlaceholderDataProperty, PlaceholderEntityType, PlaceholderRelationType,
TemplateProperties, type PinnedProperties, type AnnotationContent, type AnnotationTextStyle,
type ColorVariant, setTemplateProperty,
} from './data/schema';
Expand Down