();
+ React.useEffect(() => {
+ setElementType(model.createElementType(elementTypeIri));
+ }, [elementTypeIri]);
+
+ const data = Reactodia.useSyncStore(
+ Reactodia.useEventStore(elementType?.events, 'changeData'),
+ () => elementType?.data
+ );
+ return (
+
+ {t.formatLabel(data?.label, elementTypeIri, language)}
+
+ );
+}
+```
+
+:::note
+When requesting the data manually, make sure to subscribe to created instances to re-render when the data loads via [`useObservedProperty()`](/docs/api/workspace/functions/useObservedProperty.md), [`useEventStore()`](/docs/api/workspace/functions/useEventStore.md) or manual [event subscription](/docs/concepts/event-system.md).
+:::
+
+### `useKeyedSyncStore()`
+
+[`useKeyedSyncStore`](/docs/api/workspace/functions/useKeyedSyncStore.md) hook allows to subscribe to a set of targets and fetch the data for each:
+
+| Store | Description |
+|-----------------|-------------|
+| [`subscribeElementTypes`](/docs/api/workspace/variables/subscribeElementTypes.md) | Subscribe and fetch entity types. |
+| [`subscribeLinkTypes`](/docs/api/workspace/variables/subscribeLinkTypes.md) | Subscribe and fetch relation types. |
+| [`subscribeElementTypes`](/docs/api/workspace/variables/subscribePropertyTypes.md) | Subscribe and fetch property types. |
+
+#### Example: subscribe to property types from an [element template](/docs/components/canvas.md#customization)
+
+```ts
+function MyElement(props: Reactodia.TemplateProps) {
+ const {model} = Reactodia.useWorkspace();
+ const t = Reactodia.useTranslation();
+ const language = Reactodia.useObservedProperty(
+ model.events, 'changeLanguage', () => model.language
+ );
+
+ const data = props.element instanceof Reactodia.EntityElement
+ ? props.element.data : undefined;
+ // Select only properties with at least one value
+ const properties = Object.entries(data?.properties ?? {})
+ .filter(([iri, values]) => values.length > 0);
+ // Subscribe and fetch property types
+ Reactodia.useKeyedSyncStore(
+ Reactodia.subscribePropertyTypes,
+ properties.map(([iri]) => iri),
+ model
+ );
+
+ return (
+
+ {properties.map(([iri, values])) => {
+ // Get property type to display
+ const property = model.getPropertyType(iri);
+ return (
+ -
+ {t.formatLabel(property?.data?.label, iri, language)}{': '}
+ {values.map(v => v.value).join(', ')}
+
+ );
+ }}
+
+ );
+}
+```
+
+### `useProvidedEntities()`
+
+[`useProvidedEntities`](/docs/api/workspace/functions/useProvidedEntities.md) hook allows to loads entity data for a target set of IRIs even when the entities are not displayed on the canvas at all.
+
+#### Example: load entity variants for a [select input](/docs/components/form-input.md)
+
+```ts
+function MyInputForShape(props: Forms.InputSingleProps) {
+ const {factory} = props;
+ const {model} = Reactodia.useWorkspace();
+
+ const {data: entities} = Reactodia.useProvidedEntities(
+ model.dataProvider,
+ [shapes.Square, shapes.Circle, shapes.Triangle]
+ );
+ const language = Reactodia.useObservedProperty(
+ model.events, 'changeLanguage', () => model.language
+ );
+ const variants = React.useMemo(
+ () => Array.from(entities.values(), (item): Forms.InputSelectVariant => ({
+ value: factory.namedNode(item.id),
+ label: model.locale.formatEntityLabel(item, language),
+ })),
+ [entities, language, factory]
+ );
+
+ return (
+
+ );
+}
+```
+
+## Data Locale
+
+It is possible to customize how library components display graph data by supplying a custom [`DataLocaleProvider`](/docs/api/workspace/interfaces/DataLocaleProvider.md) when calling [model.importLayout()](/docs/api/workspace/classes/DataDiagramModel.md#importlayout).
+
+Data locale provider can be used to alter the following behavior:
+ - [locale.selectEntityLabel()](/docs/api/workspace/interfaces/DataLocaleProvider.md#selectentitylabel) and [locale.formatEntityLabel()](/docs/api/workspace/interfaces/DataLocaleProvider.md#formatentitylabel) to select or format default entity label from its properties (by default it looks for `rdfs:label` property values);
+ - [locale.selectEntityImageUrl()](/docs/api/workspace/interfaces/DataLocaleProvider.md#selectentityimageurl) to select default entity thumbnail image IRI from its properties (by default it looks for `schema:thumbnailUrl` property value);
+ - [locale.prepareAnchor()](/docs/api/workspace/interfaces/DataLocaleProvider.md#prepareanchor) to provide props for an anchor (`` link) to a resource IRI;
+ - [locale.resolveAssetUrl()](/docs/api/workspace/interfaces/DataLocaleProvider.md#resolveasseturl) to resolve an IRI/URL to referenced data asset for display or download, e.g. an image (thumbnail) or a downloadable file.
+
+:::tip
+It is possible to extend [`DefaultDataLocaleProvider`](/docs/api/workspace/classes/DefaultDataLocaleProvider.md) to slightly alter its behavior instead of implementing the full [`DataLocaleProvider`](/docs/api/workspace/interfaces/DataLocaleProvider.md) interface.
+:::
diff --git a/docusaurus.config.ts b/docusaurus.config.ts
index 3ee438a2..659bc5ce 100644
--- a/docusaurus.config.ts
+++ b/docusaurus.config.ts
@@ -55,6 +55,7 @@ const config: Config = {
{
entryPoints: [
`${libraryPathPrefix}/reactodia-workspace/src/workspace.ts`,
+ `${libraryPathPrefix}/reactodia-workspace/src/forms/index.ts`,
`${libraryPathPrefix}/reactodia-workspace/src/layout-sync.ts`,
`${libraryPathPrefix}/reactodia-workspace/src/layout.worker.ts`,
`${libraryPathPrefix}/reactodia-workspace/src/legacy-styles.tsx`,
diff --git a/package-lock.json b/package-lock.json
index 922fa374..c8500ff2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@reactodia/reactodia.github.io",
- "version": "0.31.2",
+ "version": "0.33.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@reactodia/reactodia.github.io",
- "version": "0.31.2",
+ "version": "0.33.0",
"dependencies": {
"@docusaurus/core": "3.8.1",
"@docusaurus/preset-classic": "3.8.1",
@@ -14,7 +14,7 @@
"@easyops-cn/docusaurus-search-local": "^0.51.0",
"@mdx-js/react": "^3.1.0",
"@reactodia/hashmap": "^0.2.1",
- "@reactodia/workspace": "^0.33.0",
+ "@reactodia/workspace": "^0.34.0",
"clsx": "^2.1.1",
"n3": "^1.17.2",
"prism-react-renderer": "^2.4.1",
@@ -4751,9 +4751,9 @@
"license": "MIT"
},
"node_modules/@reactodia/workspace": {
- "version": "0.33.0",
- "resolved": "https://registry.npmjs.org/@reactodia/workspace/-/workspace-0.33.0.tgz",
- "integrity": "sha512-VzPkTA7tgPOlv59HSkiHLk2UEeSTr5tqxnJ6oEPHmrTsTbt3UnzC/cAlmeyYSNSbv7RCpZm3g05fQuBC3ZaDvA==",
+ "version": "0.34.0",
+ "resolved": "https://registry.npmjs.org/@reactodia/workspace/-/workspace-0.34.0.tgz",
+ "integrity": "sha512-lzRgEXhZRXcbBdKaAH9imfvAXgLWshLyE2etwcd/3tXCJbm52N6AM5irBv+pDxmwv6QwSSl2P8agpulHAHOp3A==",
"license": "LGPL-2.1-or-later",
"dependencies": {
"@reactodia/hashmap": "^0.2.1",
diff --git a/package.json b/package.json
index b24738ff..0453ebf7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@reactodia/reactodia.github.io",
- "version": "0.33.0",
+ "version": "0.34.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@@ -21,7 +21,7 @@
"@easyops-cn/docusaurus-search-local": "^0.51.0",
"@mdx-js/react": "^3.1.0",
"@reactodia/hashmap": "^0.2.1",
- "@reactodia/workspace": "^0.33.0",
+ "@reactodia/workspace": "^0.34.0",
"clsx": "^2.1.1",
"n3": "^1.17.2",
"prism-react-renderer": "^2.4.1",
diff --git a/sidebars.ts b/sidebars.ts
index 7bc05623..9d6a6a69 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -78,6 +78,10 @@ const sidebars: SidebarsConfig = {
label: '@reactodia/workspace',
collapsed: false,
},
+ {
+ ...findSidebarCategory(typedocItems, 'forms'),
+ label: '/forms',
+ },
{
...findSidebarCategory(typedocItems, 'layout-sync'),
label: '/layout-sync',
diff --git a/src/examples/ExampleMetadata.ts b/src/examples/ExampleMetadata.ts
index 806637bc..7af91f24 100644
--- a/src/examples/ExampleMetadata.ts
+++ b/src/examples/ExampleMetadata.ts
@@ -7,7 +7,7 @@ const owl = vocabulary('http://www.w3.org/2002/07/owl#', [
'ObjectProperty',
]);
-const rdfs = vocabulary('http://www.w3.org/2000/01/rdf-schema#', [
+export const rdfs = vocabulary('http://www.w3.org/2000/01/rdf-schema#', [
'comment',
'domain',
'range',
@@ -16,6 +16,10 @@ const rdfs = vocabulary('http://www.w3.org/2000/01/rdf-schema#', [
'subPropertyOf',
]);
+export const example = vocabulary('http://www.example.com/', [
+ 'workflowStatus',
+]);
+
const SIMULATED_DELAY: number = 50; /* ms */
export class ExampleMetadataProvider extends Reactodia.BaseMetadataProvider {
@@ -149,18 +153,22 @@ export class ExampleMetadataProvider extends Reactodia.BaseMetadataProvider {
valueShape: {termType: 'Literal'},
order: 1,
});
- properties.set(rdfs.comment, {
+ properties.set(example.workflowStatus, {
valueShape: {termType: 'Literal'},
order: 2,
});
+ properties.set(rdfs.comment, {
+ valueShape: {termType: 'Literal'},
+ order: 3,
+ });
properties.set(Reactodia.schema.thumbnailUrl, {
valueShape: {termType: 'NamedNode'},
maxCount: 1,
- order: 3,
+ order: 4,
});
properties.set(rdfs.seeAlso, {
valueShape: {termType: 'NamedNode'},
- order: 4,
+ order: 5,
});
}
return {properties};
@@ -169,8 +177,13 @@ export class ExampleMetadataProvider extends Reactodia.BaseMetadataProvider {
await Reactodia.delay(SIMULATED_DELAY, {signal});
const properties = new Map();
if (this.editableRelations.has(linkType)) {
+ properties.set(example.workflowStatus, {
+ valueShape: {termType: 'Literal'},
+ order: 1,
+ });
properties.set(rdfs.comment, {
valueShape: {termType: 'Literal'},
+ order: 2,
});
}
return {properties};
diff --git a/src/examples/PlaygroundGraphAuthoring.tsx b/src/examples/PlaygroundGraphAuthoring.tsx
index 444273be..5ec07795 100644
--- a/src/examples/PlaygroundGraphAuthoring.tsx
+++ b/src/examples/PlaygroundGraphAuthoring.tsx
@@ -1,8 +1,11 @@
import * as React from 'react';
import * as Reactodia from '@reactodia/workspace';
+import * as Forms from '@reactodia/workspace/forms';
import * as N3 from 'n3';
-import { ExampleMetadataProvider, ExampleValidationProvider } from './ExampleMetadata';
+import {
+ ExampleMetadataProvider, ExampleValidationProvider, rdfs, example,
+} from './ExampleMetadata';
import { ExampleToolbarMenu } from './ExampleCommon';
const Layouts = Reactodia.defineLayoutWorker(() => new Worker(
@@ -22,7 +25,7 @@ export function PlaygroundGraphAuthoring() {
});
const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
- const {model, editor, getCommandBus, performLayout} = context;
+ const {model, editor, translation: t, getCommandBus, performLayout} = context;
editor.setAuthoringMode(true);
let turtleData: string;
@@ -33,14 +36,22 @@ export function PlaygroundGraphAuthoring() {
turtleData = dataSource.data;
}
- const dataProvider = new Reactodia.RdfDataProvider();
+ const uploader = new Forms.MemoryFileUploader({
+ factory: Reactodia.Rdf.DefaultDataFactory,
+ disposeSignal: signal,
+ });
+ const dataProvider = new GraphDataProvider({}, uploader);
try {
dataProvider.addGraph(new N3.Parser().parse(turtleData));
} catch (err) {
throw new Error('Error parsing RDF graph data', {cause: err});
}
- await model.importLayout({dataProvider, signal});
+ await model.importLayout({
+ dataProvider,
+ locale: new GraphLocaleProvider({model, translation: t}, uploader),
+ signal,
+ });
if (dataSource.type === 'url') {
const entities = [
@@ -80,26 +91,113 @@ export function PlaygroundGraphAuthoring() {
>
}
visualAuthoring={{
- inputResolver: (property, inputProps) => property === 'http://www.w3.org/2000/01/rdf-schema#comment'
- ?
- : undefined,
+ propertyEditor: options => (
+ {
+ if (property === Reactodia.schema.thumbnailUrl) {
+ return ;
+ } else if (property === rdfs.comment) {
+ return (
+
+ );
+ } else if (property === example.workflowStatus) {
+ return (
+
+ );
+ }
+ return (
+
+ );
+ }}
+ />
+ ),
}}
/>