graph-explorer
Version:
Graph Explorer can be used to explore and RDF graphs in SPARQL endpoints or on the web.
230 lines (208 loc) • 6.36 kB
text/typescript
import {
ElementModel,
LinkModel,
ElementIri,
ElementTypeIri,
LinkTypeIri,
PropertyTypeIri,
LinkDirection,
MetadataApi,
ValidationApi,
ValidationEvent,
ElementError,
LinkError,
LiteralProperty,
DirectedLinkType,
CancellationToken,
} from "../../src/graph-explorer/index";
const OWL_PREFIX = "http://www.w3.org/2002/07/owl#";
const RDFS_PREFIX = "http://www.w3.org/2000/01/rdf-schema#";
const owl = {
class: (OWL_PREFIX + "Class") as ElementTypeIri,
objectProperty: (OWL_PREFIX + "ObjectProperty") as ElementTypeIri,
domain: (OWL_PREFIX + "domain") as LinkTypeIri,
range: (OWL_PREFIX + "range") as LinkTypeIri,
};
const rdfs = {
subClassOf: (RDFS_PREFIX + "subClassOf") as LinkTypeIri,
subPropertyOf: (RDFS_PREFIX + "subPropertyOf") as LinkTypeIri,
};
function hasType(model: ElementModel, type: ElementTypeIri) {
return Boolean(model.types.find((t) => t === type));
}
const SIMULATED_DELAY = 500; /* ms */
export class ExampleMetadataApi implements MetadataApi {
async canDropOnCanvas(
source: ElementModel,
ct: CancellationToken
): Promise<boolean> {
await delay(SIMULATED_DELAY, ct);
const elementTypes = await this.typesOfElementsDraggedFrom(source, ct);
CancellationToken.throwIfAborted(ct);
return elementTypes.length > 0;
}
async canDropOnElement(
source: ElementModel,
target: ElementModel,
ct: CancellationToken
): Promise<boolean> {
//await delay(SIMULATED_DELAY, ct);
const linkTypes = await this.possibleLinkTypes(source, target, ct);
CancellationToken.throwIfAborted(ct);
return linkTypes.length > 0;
}
async possibleLinkTypes(
source: ElementModel,
target: ElementModel,
ct: CancellationToken
): Promise<DirectedLinkType[]> {
await delay(SIMULATED_DELAY, ct);
return hasType(source, owl.class) && hasType(target, owl.class)
? mapLinkTypes([rdfs.subClassOf]).concat(
mapLinkTypes([rdfs.subClassOf], LinkDirection.in)
)
: hasType(source, owl.objectProperty) && hasType(target, owl.class)
? mapLinkTypes([owl.domain, owl.range])
: hasType(target, owl.objectProperty) && hasType(source, owl.class)
? mapLinkTypes([owl.domain, owl.range], LinkDirection.in)
: hasType(source, owl.objectProperty) &&
hasType(target, owl.objectProperty)
? mapLinkTypes([rdfs.subPropertyOf]).concat(
mapLinkTypes([rdfs.subPropertyOf], LinkDirection.in)
)
: [];
function mapLinkTypes(
types: LinkTypeIri[],
direction: LinkDirection = LinkDirection.out
): DirectedLinkType[] {
return types.map((linkTypeIri) => ({ linkTypeIri, direction }));
}
}
async typesOfElementsDraggedFrom(
source: ElementModel,
ct: CancellationToken
): Promise<ElementTypeIri[]> {
await delay(SIMULATED_DELAY, ct);
return hasType(source, owl.class)
? [owl.class]
: hasType(source, owl.objectProperty)
? [owl.class, owl.objectProperty]
: [];
}
async propertiesForType(
_type: ElementTypeIri,
_ct: CancellationToken
): Promise<PropertyTypeIri[]> {
//await delay(SIMULATED_DELAY, ct);
return [];
}
async canDeleteElement(
element: ElementModel,
ct: CancellationToken
): Promise<boolean> {
await delay(SIMULATED_DELAY, ct);
return true;
}
async filterConstructibleTypes(
types: ReadonlySet<ElementTypeIri>,
_ct: CancellationToken
): Promise<ReadonlySet<ElementTypeIri>> {
//await delay(SIMULATED_DELAY, ct);
const result = new Set<ElementTypeIri>();
types.forEach((type) => {
if (type === "http://xmlns.com/foaf/0.1/Person") {
result.add(type);
}
});
return result;
}
async canEditElement(
element: ElementModel,
ct: CancellationToken
): Promise<boolean> {
await delay(SIMULATED_DELAY, ct);
return true;
}
async canLinkElement(
_element: ElementModel,
_ct: CancellationToken
): Promise<boolean> {
//await delay(SIMULATED_DELAY, ct);
return true;
}
async canDeleteLink(
_link: LinkModel,
_source: ElementModel,
_target: ElementModel,
_ct: CancellationToken
): Promise<boolean> {
//await delay(SIMULATED_DELAY, ct);
return true;
}
async canEditLink(
_link: LinkModel,
_source: ElementModel,
_target: ElementModel,
_ct: CancellationToken
): Promise<boolean> {
//await delay(SIMULATED_DELAY, ct);
return true;
}
async generateNewElement(
types: readonly ElementTypeIri[],
ct: CancellationToken
): Promise<ElementModel> {
await delay(SIMULATED_DELAY, ct);
const random32BitDigits = Math.floor((1 + Math.random()) * 0x100000000)
.toString(16)
.substring(1);
return {
id: `${types[0]}_${random32BitDigits}` as ElementIri,
types: [...types],
label: { values: [{ value: "New Entity", language: "" }] },
properties: {
"http://xmlns.com/foaf/0.1/name": {
type: "string",
values: [{ language: "", value: "helo" }],
} as LiteralProperty,
},
};
}
}
export class ExampleValidationApi implements ValidationApi {
async validate(
event: ValidationEvent
): Promise<(ElementError | LinkError)[]> {
const errors: (ElementError | LinkError)[] = [];
if (event.target.types.indexOf(owl.class) >= 0) {
event.state.links.forEach((e) => {
if (!e.before && e.after.sourceId === event.target.id) {
errors.push({
type: "link",
target: e.after,
message: "Cannot add any new link from a Class",
});
const linkType = event.model.createLinkType(e.after.linkTypeId);
errors.push({
type: "element",
target: event.target.id,
message: `Cannot create <${linkType.id}> link from a Class`,
});
}
});
}
await delay(SIMULATED_DELAY, event.cancellation);
return errors;
}
}
async function delay(amountMs: number, ct: CancellationToken) {
CancellationToken.throwIfAborted(ct);
await waitTimeout(amountMs);
CancellationToken.throwIfAborted(ct);
}
function waitTimeout(amountMs: number): Promise<void> {
if (amountMs === 0) {
return Promise.resolve();
}
return new Promise((resolve) => setTimeout(resolve, amountMs));
}