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: 1 addition & 1 deletion .github/workflows/ci-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
branches: [ "main" ]

env:
reactodia_workspace_ref: 'v0.33.0'
reactodia_workspace_ref: 'v0.34.0'

jobs:
build:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
workflow_dispatch:

env:
reactodia_workspace_ref: 'v0.33.0'
reactodia_workspace_ref: 'v0.34.0'

jobs:
build:
Expand Down
25 changes: 17 additions & 8 deletions docs/components/form-input.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: <FormInput* />
title: <Forms.Input* />
---

# Form input components
Expand All @@ -8,8 +8,10 @@ Reactodia provides basic built-in components to edit entity or relation properti

| Form input component | Description |
|----------------------|-------------|
| [`<FormInputList />`](/docs/api/workspace/variables/FormInputList.md) | Form input to edit multiple values in a list of specified single value inputs. |
| [`<FormInputText />`](/docs/api/workspace/functions/FormInputText.md) | Form input to edit a single value as a plain string with an optional language. |
| [`<Forms.InputList />`](/docs/api/forms/functions/InputList.md) | Form input to edit multiple values in a list of specified single value inputs. |
| [`<Forms.InputText />`](/docs/api/forms/functions/InputText.md) | Form input to edit a single value as a plain string with an optional language. |
| [`<Forms.InputSelect />`](/docs/api/forms/functions/InputSelect.md) | Form input to select a value from a predefined list of variants. |
| [`<Forms.InputFile />`](/docs/api/forms/functions/InputFile.md) | Form input to upload files and display previously uploaded files. |

:::warning
Currently form input components are considered **unstable** so there might be breaking changes in their API in the future.
Expand Down Expand Up @@ -60,10 +62,17 @@ function Example() {
search={null}
navigator={{expanded: false}}
visualAuthoring={{
inputResolver: (property, inputProps) =>
property === RDF_COMMENT
? <Reactodia.FormInputList {...inputProps} valueInput={MultilineTextInput} />
: undefined,
propertyEditor: options => (
<Reactodia.DefaultPropertyEditor options={options}
resolveInput={(property, inputProps) => (
<Forms.InputList {...inputProps}
valueInput={
property === RDF_COMMENT ? MultilineTextInput : Forms.InputText
}
/>
)}
/>
),
}}
/>
</Reactodia.Workspace>
Expand All @@ -72,7 +81,7 @@ function Example() {
}

function MultilineTextInput(props: Reactodia.FormInputSingleProps) {
return <Reactodia.FormInputText {...props} multiline />;
return <Forms.InputText {...props} multiline />;
}

render(<Example />);
Expand Down
8 changes: 7 additions & 1 deletion docs/components/unified-search.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,15 @@ function SearchWithNpm() {
},
], []);

const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
const {getCommandBus} = context;
getCommandBus(Reactodia.UnifiedSearchTopic)
.trigger('focus', { sectionKey: 'entities' });
}, []);

return (
<div className='reactodia-live-editor'>
<Reactodia.Workspace defaultLayout={defaultLayout}>
<Reactodia.Workspace ref={onMount} defaultLayout={defaultLayout}>
<Reactodia.DefaultWorkspace
mainToolbar={{dock: 'n'}}
navigator={{expanded: false}}
Expand Down
155 changes: 155 additions & 0 deletions docs/concepts/data-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,158 @@ function ExampleRdfProviderProvisionFromJGF() {
);
}
```

## Loading data from a data provider

When exploring the graph data, Reactodia components track which data needs to be loaded and requests to fetch it based on currently displayed [diagram content](/docs/concepts/graph-model#diagram-content). For example, when an [`EntityElement`](/docs/api/workspace/classes/EntityElement.md) is added to the canvas and rendered with the[default template](/docs/components/canvas.md#customization), the library will load corresponding entity types to display correct labels.

The library includes a number of hooks and methods to simplify data loading from a custom component which are listed below.

### Request data for entities and/or relations on the canvas

After adding one or more [`EntityElement`](/docs/api/workspace/classes/EntityElement.md) elements to the canvas e.g. with [`model.createElement()`](/docs/api/workspace/classes/DataDiagramModel.md#createelement) (see [Manipulating the diagram](/docs/concepts/graph-model.md#manipulating-the-diagram)), it is necessary to call one or several of the following methods to initiate loading entity data and relations between them:

| Method | Description |
|-----------------|-------------|
| [`model.requestData()`](/docs/api/workspace/classes/DataDiagramModel.md#requestdata) | Requests to load all non-loaded ([placeholder](/docs/api/workspace/classes/EntityElement.md#isplaceholderdata)) entity elements and links connected to them. |
| [`model.requestElementData()`](/docs/api/workspace/classes/DataDiagramModel.md#requestelementdata) | Requests to load (or reload) data for the specified set of entities. |
| [`model.requestLinks()`](/docs/api/workspace/classes/DataDiagramModel.md#requestlinks) | Requests to load (or reload) all relations connected to the specified sets of entities. |

It is also possible to use [`requestElementData()`](/docs/api/workspace/functions/requestElementData.md) and [`restoreLinksBetweenElements()`](/docs/api/workspace/functions/restoreLinksBetweenElements.md) command effects to re-request the data on [undo/redo](/docs/concepts/command-history.md) if needed.

### Manually request data for entity, relation or property types

In some cases it is easier to manually trigger a request to load data for an entity, relation or property type:

| Method | Description |
|-----------------|-------------|
| [`model.createElementType()`](/docs/api/workspace/classes/DataDiagramModel.md#createelementtype) | Requests to load an entity type if it has not been loaded yet. <br/> [`model.getElementType()`](/docs/api/workspace/classes/DataDiagramModel.md#getelementtype) can be used to get the placeholder or loaded data. |
| [`model.createLinkType()`](/docs/api/workspace/classes/DataDiagramModel.md#createlinktype) | Requests to load a relation type if it has not been loaded yet. <br/> [`model.getLinkType()`](/docs/api/workspace/classes/DataDiagramModel.md#getlinktype) can be used to get the placeholder or loaded data. |
| [`model.createPropertyType()`](/docs/api/workspace/classes/DataDiagramModel.md#createpropertytype) | Requests to load a property type if it has not been loaded yet. <br/> [`model.getPropertyType()`](/docs/api/workspace/classes/DataDiagramModel.md#getpropertytype) can be used to get the placeholder or loaded data. |

#### Example: manual request and subscription for an entity type

```ts
function MyElementTypeBadge(props: { elementTypeIri }) {
const {elementTypeIri} = props;
const {model} = Reactodia.useWorkspace();
const t = Reactodia.useTranslation();
const language = Reactodia.useObservedProperty(
model.events, 'changeLanguage', () => model.language
);

const [elementType, setElementType] = React.useState<Reactodia.ElementType>();
React.useEffect(() => {
setElementType(model.createElementType(elementTypeIri));
}, [elementTypeIri]);

const data = Reactodia.useSyncStore(
Reactodia.useEventStore(elementType?.events, 'changeData'),
() => elementType?.data
);
return (
<div className="my-badge">
{t.formatLabel(data?.label, elementTypeIri, language)}
</div>
);
}
```

:::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 (
<ul>
{properties.map(([iri, values])) => {
// Get property type to display
const property = model.getPropertyType(iri);
return (
<li>
{t.formatLabel(property?.data?.label, iri, language)}{': '}
{values.map(v => v.value).join(', ')}
</li>
);
}}
</ul>
);
}
```

### `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 (
<Forms.InputSelect {...props} variants={variants} />
);
}
```

## 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 (`<a>` 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.
:::
1 change: 1 addition & 0 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reactodia/reactodia.github.io",
"version": "0.33.0",
"version": "0.34.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
Expand All @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ const sidebars: SidebarsConfig = {
label: '@reactodia/workspace',
collapsed: false,
},
{
...findSidebarCategory(typedocItems, 'forms'),
label: '/forms',
},
{
...findSidebarCategory(typedocItems, 'layout-sync'),
label: '/layout-sync',
Expand Down
21 changes: 17 additions & 4 deletions src/examples/ExampleMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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 {
Expand Down Expand Up @@ -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};
Expand All @@ -169,8 +177,13 @@ export class ExampleMetadataProvider extends Reactodia.BaseMetadataProvider {
await Reactodia.delay(SIMULATED_DELAY, {signal});
const properties = new Map<Reactodia.PropertyTypeIri, Reactodia.MetadataPropertyShape>();
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};
Expand Down
Loading
Loading