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: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- Optimize diagram loading time by avoiding unnecessary updates and separating a measuring element sizes step from applying the sizes to the rendering state.

#### 💅 Polish
- Export `EmptyMetadataProvider` as a stable base class to extend from when implementing custom metadata providers.
- Export `BaseMetadataProvider` as a stable base to instantiate or extend when implementing custom metadata providers.
- Re-use and un-deprecate `model.locale` formatting object with `DataLocaleProvider` interface type:
* **[💥Breaking]**: Remove `Translation.formatIri()` in favor of `locale.formatIri()`;
* Deprecate `Translation.formatIri()` in favor of `locale.formatIri()`;
* Replace other deprecated methods of `locale` with: `selectEntityLabel()`, `selectEntityImageUrl()`, `formatEntityLabel()`, `formatEntityTypeList()`;
- Provide gradual customization options for the built-in entity and relation property editor:
* Expose ability to customize property input in authoring forms with `inputResolver` option for `VisualAuthoring` component;
Expand Down
1 change: 0 additions & 1 deletion examples/graphAuthoring.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ function GraphAuthoringExample() {
model.createElement('http://www.w3.org/ns/org#subOrganizationOf'),
model.createElement('http://www.w3.org/ns/org#unitOf'),
];
model.history.execute(Reactodia.setElementExpanded(elements[0], true));
await Promise.all([
model.requestElementData(elements.map(el => el.iri)),
model.requestLinks(),
Expand Down
11 changes: 8 additions & 3 deletions examples/rdfExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,14 @@ function RdfExample() {
}
languages={[
{code: 'de', label: 'Deutsch'},
{code: 'en', label: 'english'},
{code: 'es', label: 'español'},
{code: 'ru', label: 'русский'},
{code: 'en', label: 'English'},
{code: 'es', label: 'Español'},
{code: 'fr', label: 'Français'},
{code: 'hi', label: 'हिन्दी'},
{code: 'it', label: 'Italiano'},
{code: 'ja', label: '日本語'},
{code: 'pt', label: 'português'},
{code: 'ru', label: 'Русский'},
{code: 'zh', label: '汉语'},
]}
/>
Expand Down
302 changes: 133 additions & 169 deletions examples/resources/exampleMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,189 +18,153 @@ const rdfs = vocabulary('http://www.w3.org/2000/01/rdf-schema#', [

const SIMULATED_DELAY: number = 200; /* ms */

export class ExampleMetadataProvider extends Reactodia.EmptyMetadataProvider {
export class ExampleMetadataProvider extends Reactodia.BaseMetadataProvider {
private readonly propertyTypes = [owl.AnnotationProperty, owl.DatatypeProperty, owl.ObjectProperty];
private readonly editableTypes = new Set([owl.Class, ...this.propertyTypes]);
private readonly editableRelations = new Set<Reactodia.LinkTypeIri>([rdfs.domain, rdfs.range]);
private readonly literalLanguages: ReadonlyArray<string> = ['de', 'en', 'es', 'ru', 'zh'];

getLiteralLanguages(): ReadonlyArray<string> {
return this.literalLanguages;
}

async createEntity(
type: Reactodia.ElementTypeIri,
options: { readonly signal?: AbortSignal }
): Promise<Reactodia.ElementModel> {
await Reactodia.delay(SIMULATED_DELAY, {signal: options.signal});
const random32BitDigits = Math.floor((1 + Math.random()) * 0x100000000)
.toString(16).substring(1);
const typeLabel = Reactodia.Rdf.getLocalName(type) ?? 'Entity';
return {
id: `${type}_${random32BitDigits}` as Reactodia.ElementIri,
types: [type],
properties: {
[Reactodia.rdfs.label]: [
Reactodia.Rdf.DefaultDataFactory.literal(`New ${typeLabel}`)
]
constructor() {
super({
getLiteralLanguages: () => this.literalLanguages,
createEntity: async (type, {signal}) => {
await Reactodia.delay(SIMULATED_DELAY, {signal});
const random32BitDigits = Math.floor((1 + Math.random()) * 0x100000000)
.toString(16).substring(1);
const typeLabel = Reactodia.Rdf.getLocalName(type) ?? 'Entity';
return {
id: `${type}_${random32BitDigits}`,
types: [type],
properties: {
[Reactodia.rdfs.label]: [
Reactodia.Rdf.DefaultDataFactory.literal(`New ${typeLabel}`)
]
},
};
},
};
}

async createRelation(
source: Reactodia.ElementModel,
target: Reactodia.ElementModel,
linkType: Reactodia.LinkTypeIri,
options: { readonly signal?: AbortSignal }
): Promise<Reactodia.LinkModel> {
await Reactodia.delay(SIMULATED_DELAY, {signal: options.signal});
return {
sourceId: source.id,
targetId: target.id,
linkTypeId: linkType,
properties: {},
};
}

async canConnect(
source: Reactodia.ElementModel,
target: Reactodia.ElementModel | undefined,
linkType: Reactodia.LinkTypeIri | undefined,
options: { readonly signal?: AbortSignal }
): Promise<Reactodia.MetadataCanConnect[]> {
await Reactodia.delay(SIMULATED_DELAY, {signal: options.signal});

const connections: Reactodia.MetadataCanConnect[] = [];
const addConnections = (
types: readonly Reactodia.ElementTypeIri[],
allOutLinks: readonly Reactodia.LinkTypeIri[],
allInLinks: readonly Reactodia.LinkTypeIri[]
) => {
const outLinks = linkType
? allOutLinks.filter(type => type === linkType)
: allOutLinks;
const inLinks = linkType
? allInLinks.filter(type => type === linkType)
: allInLinks;
if (types.length > 0 && (outLinks.length > 0 || inLinks.length > 0)) {
connections.push({targetTypes: new Set(types), outLinks, inLinks});
}
};

if (hasType(source, owl.Class)) {
if (hasType(target, owl.Class)) {
addConnections([owl.Class], [rdfs.subClassOf], [rdfs.subClassOf]);
}
createRelation: async (source, target, linkType, {signal}) => {
await Reactodia.delay(SIMULATED_DELAY, {signal: signal});
return {
sourceId: source.id,
targetId: target.id,
linkTypeId: linkType,
properties: {},
};
},
canConnect: async (source, target, linkType, {signal}) => {
await Reactodia.delay(SIMULATED_DELAY, {signal});

const connections: Reactodia.MetadataCanConnect[] = [];
const addConnections = (
types: readonly Reactodia.ElementTypeIri[],
allOutLinks: readonly Reactodia.LinkTypeIri[],
allInLinks: readonly Reactodia.LinkTypeIri[]
) => {
const outLinks = linkType
? allOutLinks.filter(type => type === linkType)
: allOutLinks;
const inLinks = linkType
? allInLinks.filter(type => type === linkType)
: allInLinks;
if (types.length > 0 && (outLinks.length > 0 || inLinks.length > 0)) {
connections.push({targetTypes: new Set(types), outLinks, inLinks});
}
};

const targetPropertyTypes = this.propertyTypes.filter(type => hasType(target, type));
if (targetPropertyTypes.length > 0) {
addConnections(targetPropertyTypes, [], [rdfs.domain, rdfs.range]);
}
}
if (hasType(source, owl.Class)) {
if (hasType(target, owl.Class)) {
addConnections([owl.Class], [rdfs.subClassOf], [rdfs.subClassOf]);
}

const sourcePropertyTypes = this.propertyTypes.filter(type => hasType(source, type));
if (sourcePropertyTypes.length > 0) {
for (const type of sourcePropertyTypes) {
if (hasType(target, type)) {
addConnections([type], [rdfs.subPropertyOf], [rdfs.subPropertyOf]);
const targetPropertyTypes = this.propertyTypes.filter(type => hasType(target, type));
if (targetPropertyTypes.length > 0) {
addConnections(targetPropertyTypes, [], [rdfs.domain, rdfs.range]);
}
}
}

if (hasType(target, owl.Class)) {
addConnections([owl.Class], [rdfs.domain, rdfs.range], []);
}
}

return connections;
}

async canModifyEntity(
entity: Reactodia.ElementModel,
options: { readonly signal?: AbortSignal; }
): Promise<Reactodia.MetadataCanModifyEntity> {
await Reactodia.delay(SIMULATED_DELAY, {signal: options.signal});
const editable = entity.types.some(type => this.editableTypes.has(type));
return {
canChangeIri: entity.types.includes(owl.Class),
canEdit: editable,
canDelete: editable,
};
}
const sourcePropertyTypes = this.propertyTypes.filter(type => hasType(source, type));
if (sourcePropertyTypes.length > 0) {
for (const type of sourcePropertyTypes) {
if (hasType(target, type)) {
addConnections([type], [rdfs.subPropertyOf], [rdfs.subPropertyOf]);
}
}

if (hasType(target, owl.Class)) {
addConnections([owl.Class], [rdfs.domain, rdfs.range], []);
}
}

async canModifyRelation(
link: Reactodia.LinkModel,
source: Reactodia.ElementModel,
target: Reactodia.ElementModel,
options: { readonly signal?: AbortSignal; }
): Promise<Reactodia.MetadataCanModifyRelation> {
await Reactodia.delay(SIMULATED_DELAY, {signal: options.signal});
switch (link.linkTypeId) {
case rdfs.domain:
case rdfs.range:
case rdfs.subClassOf:
case rdfs.subPropertyOf: {
return connections;
},
canModifyEntity: async (entity, {signal}) => {
await Reactodia.delay(SIMULATED_DELAY, {signal});
const editable = entity.types.some(type => this.editableTypes.has(type));
return {
canChangeType: true,
canEdit: this.editableRelations.has(link.linkTypeId),
canDelete: true,
canChangeIri: entity.types.includes(owl.Class),
canEdit: editable,
canDelete: editable,
};
}
default: {
return {};
}
}
}

async getEntityShape(
types: ReadonlyArray<Reactodia.ElementTypeIri>,
options: { readonly signal?: AbortSignal; }
): Promise<Reactodia.MetadataEntityShape> {
await Reactodia.delay(SIMULATED_DELAY, {signal: options.signal});
const properties = new Map<Reactodia.PropertyTypeIri, Reactodia.MetadataPropertyShape>();
if (types.some(type => this.editableTypes.has(type))) {
properties.set(rdfs.comment, {
valueShape: {termType: 'Literal'},
});
properties.set(Reactodia.rdfs.label, {
valueShape: {termType: 'Literal'},
});
properties.set(Reactodia.schema.thumbnailUrl, {
valueShape: {termType: 'NamedNode'},
maxCount: 1,
});
properties.set(rdfs.seeAlso, {
valueShape: {termType: 'NamedNode'},
maxCount: 1,
});
}
return {
extraProperty: {
valueShape: {termType: 'Literal'},
},
properties,
};
}

async getRelationShape(
linkType: Reactodia.LinkTypeIri,
options: { readonly signal?: AbortSignal; }
): Promise<Reactodia.MetadataRelationShape> {
await Reactodia.delay(SIMULATED_DELAY, {signal: options.signal});
const properties = new Map<Reactodia.PropertyTypeIri, Reactodia.MetadataPropertyShape>();
if (this.editableRelations.has(linkType)) {
properties.set(rdfs.comment, {
valueShape: {termType: 'Literal'},
});
}
return {properties};
}

async filterConstructibleTypes(
types: ReadonlySet<Reactodia.ElementTypeIri>,
options: { readonly signal?: AbortSignal }
): Promise<ReadonlySet<Reactodia.ElementTypeIri>> {
await Reactodia.delay(SIMULATED_DELAY, {signal: options.signal});
return new Set(Array.from(types).filter(type => this.editableTypes.has(type)));
canModifyRelation: async (link, source, target, {signal}) => {
await Reactodia.delay(SIMULATED_DELAY, {signal});
switch (link.linkTypeId) {
case rdfs.domain:
case rdfs.range:
case rdfs.subClassOf:
case rdfs.subPropertyOf: {
return {
canChangeType: true,
canEdit: this.editableRelations.has(link.linkTypeId),
canDelete: true,
};
}
default: {
return {};
}
}
},
getEntityShape: async (types, {signal}) => {
await Reactodia.delay(SIMULATED_DELAY, {signal});
const properties = new Map<Reactodia.PropertyTypeIri, Reactodia.MetadataPropertyShape>();
if (types.some(type => this.editableTypes.has(type))) {
properties.set(rdfs.comment, {
valueShape: {termType: 'Literal'},
});
properties.set(Reactodia.rdfs.label, {
valueShape: {termType: 'Literal'},
});
properties.set(Reactodia.schema.thumbnailUrl, {
valueShape: {termType: 'NamedNode'},
maxCount: 1,
});
properties.set(rdfs.seeAlso, {
valueShape: {termType: 'NamedNode'},
maxCount: 1,
});
}
return {
extraProperty: {
valueShape: {termType: 'Literal'},
},
properties,
};
},
getRelationShape: async (linkType, {signal}) => {
await Reactodia.delay(SIMULATED_DELAY, {signal: signal});
const properties = new Map<Reactodia.PropertyTypeIri, Reactodia.MetadataPropertyShape>();
if (this.editableRelations.has(linkType)) {
properties.set(rdfs.comment, {
valueShape: {termType: 'Literal'},
});
}
return {properties};
},
filterConstructibleTypes: async (types, {signal}) => {
await Reactodia.delay(SIMULATED_DELAY, {signal: signal});
return new Set(Array.from(types).filter(type => this.editableTypes.has(type)));
}
});
}
}

Expand Down
11 changes: 6 additions & 5 deletions examples/sparql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,14 @@ function SparqlExample() {
]}
languages={[
{code: 'de', label: 'Deutsch'},
{code: 'en', label: 'english'},
{code: 'es', label: 'español'},
{code: 'fr', label: 'français'},
{code: 'ja', label: '日本語'},
{code: 'en', label: 'English'},
{code: 'es', label: 'Español'},
{code: 'fr', label: 'Français'},
{code: 'hi', label: 'हिन्दी'},
{code: 'it', label: 'Italiano'},
{code: 'ja', label: '日本語'},
{code: 'pt', label: 'português'},
{code: 'ru', label: 'русский'},
{code: 'ru', label: 'Русский'},
{code: 'zh', label: '汉语'},
]}
/>
Expand Down
4 changes: 1 addition & 3 deletions examples/stressTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,7 @@ function createLayout(factory: Reactodia.Rdf.DataFactory, options: {
const nodeType = factory.namedNode('urn:test:Node');
const linkType = factory.namedNode('urn:test:link');

const makeNodeIri = (n: number) => factory.namedNode(
`urn:test:n:${n}` as Reactodia.ElementIri
);
const makeNodeIri = (n: number) => factory.namedNode(`urn:test:n:${n}`);

const elementIris: Reactodia.ElementIri[] = [];
const quads: Reactodia.Rdf.Quad[] = [];
Expand Down
Loading