diff --git a/docs/components/canvas.md b/docs/components/canvas.md
index e5b17aaa..3155a40a 100644
--- a/docs/components/canvas.md
+++ b/docs/components/canvas.md
@@ -2,17 +2,49 @@
title: Canvas
---
-# Canvas Components
+# Canvas and widgets
[Canvas](/docs/api/workspace/functions/Canvas) is a main component to display a scrollable canvas for the diagram with [elements](/docs/concepts/graph-model.md), [links](/docs/concepts/graph-model.md) and additional widgets.
-## Hooks
+## Getting the canvas instance
-[useCanvas()](/docs/api/workspace/functions/useCanvas) hook called from a canvas widget, [SharedCanvasState.findAnyCanvas()](/docs/api/workspace/classes/SharedCanvasState.md#findanycanvas) or [SharedCanvasState.findAllCanvases()](/docs/api/workspace/classes/SharedCanvasState.md#findallcanvases) methods can be used to get the [CanvasApi](/docs/api/workspace/interfaces/CanvasApi) instance to read or subscribe to the canvas state or perform viewport-related effects.
+[useCanvas()](/docs/api/workspace/functions/useCanvas) hook called from a canvas widget can be used to get the [CanvasApi](/docs/api/workspace/interfaces/CanvasApi) instance from a context to read or subscribe to the canvas state or perform viewport-related effects:
+
+```ts
+function MyWidget() {
+ const {canvas} = Reactodia.useCanvas();
+ // Use canvas here
+}
+```
+
+Alternatively, with [SharedCanvasState.findAnyCanvas()](/docs/api/workspace/classes/SharedCanvasState.md#findanycanvas) or [SharedCanvasState.findAllCanvases()](/docs/api/workspace/classes/SharedCanvasState.md#findallcanvases) methods it is possible to get canvas instance outside the canvas component:
+
+```ts
+function NonWidgetComponent {
+ const {view} = React.useWorkspace();
+
+ const canvas = view.findAnyCanvas();
+ if (canvas) {
+ // Use canvas here (could be any if there are several of them)
+ }
+
+ for (const canvas of view.findAllCanvases()) {
+ // Use each canvas mounted in the workspace
+ }
+}
+```
## Canvas widgets
-To define a custom canvas widget, the target React component should be marked by [defineCanvasWidget()](/docs/api/workspace/functions/defineCanvasWidget) function to define its attachment layer.
+Canvas widget is an instance of any React component type which is marked by [defineCanvasWidget()](/docs/api/workspace/functions/defineCanvasWidget) function with metadata such as its attachment layer i.e. where the component should be displayed in relation to other canvas content.
+
+There are multiple canvas layers to place widgets on, from top one to the bottom:
+
+| Layer name | [Coordinate type](/docs/concepts/canvas-coordinates.md) | Description |
+|----------------|-------------------|-------------|
+| `viewport` | client (viewport) | Topmost layer, does not scale or scroll with the diagram. |
+| `overElements` | paper | Displayed over both elements and links, scales and scrolls with the diagram. |
+| `overLinks` | paper | Displayed under elements but over links, scales and scrolls with the diagram. |
### Example: custom viewport widget
@@ -20,13 +52,13 @@ To define a custom canvas widget, the target React component should be marked by
function CustomSelectAllWidget() {
const {model} = Reactodia.useWorkspace();
return (
-
+
-
+
);
}
@@ -40,10 +72,14 @@ function Example() {
const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
const {model, view, performLayout} = context;
- const dataProvider = new Reactodia.EmptyDataProvider();
- await model.createNewDiagram({dataProvider, signal});
model.createElement('http://example.com/entity1');
model.createElement('http://example.com/entity2');
+ model.createLinks({
+ sourceId: 'http://example.com/entity1',
+ targetId: 'http://example.com/entity2',
+ linkTypeId: 'http://example.com/connectedTo',
+ properties: {},
+ });
await performLayout({signal});
}, []);
@@ -62,3 +98,16 @@ function Example() {
render();
```
+
+## Styles
+
+The component look can be customized using the following CSS properties (see [design system](/docs/concepts/design-system.mdx) for more information):
+
+| Property | Description |
+|----------|-------------|
+| `--reactodia-canvas-background-color` | Background color for the canvas. |
+| `--reactodia-canvas-box-shadow` | Box shadow for the UI components layered on top of the canvas. |
+| `--reactodia-canvas-overlay-color` | Semi-transparent color to place over canvas content when displaying a modal on top. |
+| `--reactodia-canvas-underlay-color` | Semi-transparent color to place under components for improved readability when they are placed on the canvas. |
+| `--reactodia-element-background-color` | Default background color for the graph elements displayed on the canvas. |
+| `--reactodia-link-stroke-color` | Default stroke color for the graph links displayed on the canvas. |
diff --git a/docs/components/class-tree.md b/docs/components/class-tree.md
index 92cd1711..28f71cda 100644
--- a/docs/components/class-tree.md
+++ b/docs/components/class-tree.md
@@ -7,16 +7,12 @@ Element type graph is loaded from [data provider](/docs/concepts/data-provider.m
In [graph authoring](/docs/concepts/graph-authoring.md) mode, the class tree can be used to create entity elements that are instances of the displayed types.
:::tip
-
The same functionality is also available as `SearchSectionElementTypes` [unified search section](/docs/components/unified-search.md).
-
:::
```tsx live
function Example() {
- const GRAPH_DATA =
- 'https://raw.githubusercontent.com/reactodia/reactodia-workspace/' +
- 'master/examples/resources/orgOntology.ttl';
+ const GRAPH_DATA = 'https://reactodia.github.io/resources/orgOntology.ttl';
const {defaultLayout} = Reactodia.useWorker(Layouts);
@@ -41,3 +37,14 @@ function Example() {
);
}
```
+
+## Styles
+
+The component look can be customized using the following CSS properties (see [design system](/docs/concepts/design-system.mdx) for more information):
+
+| Property | Description |
+|----------|-------------|
+| `--reactodia-tree-background-color-active` | Background color for a selected tree item. |
+| `--reactodia-tree-background-color-focus` | Background color for a hovered over tree item. |
+| `--reactodia-tree-border-color-active` | Border color for a selected tree item. |
+| `--reactodia-tree-border-color-focus` | Border color for a hovered over tree item. |
diff --git a/docs/components/connections-menu.md b/docs/components/connections-menu.md
index 407dde6a..1ec2c6d1 100644
--- a/docs/components/connections-menu.md
+++ b/docs/components/connections-menu.md
@@ -1,3 +1,44 @@
# Connections Menu
[ConnectionsMenu](/docs/api/workspace/functions/ConnectionsMenu) component is a [canvas widget](/docs/components/canvas.md) to explore and navigate the graph by adding connected entities to the diagram.
+
+### Example: opening a connections menu on load
+
+```tsx live
+function Example() {
+ const GRAPH_DATA = 'https://reactodia.github.io/resources/orgOntology.ttl';
+
+ const {defaultLayout} = Reactodia.useWorker(Layouts);
+
+ const [connectionsMenuCommands] = React.useState(() =>
+ new Reactodia.EventSource()
+ );
+
+ const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
+ const {model, view, performLayout} = context;
+
+ const response = await fetch(GRAPH_DATA, {signal});
+ const graphData = new N3.Parser().parse(await response.text());
+ const dataProvider = new Reactodia.RdfDataProvider({acceptBlankNodes: false});
+ dataProvider.addGraph(graphData);
+ await model.importLayout({dataProvider, signal});
+
+ const target = model.createElement('http://www.w3.org/ns/org#Organization');
+ await model.requestElementData([target.iri]);
+
+ connectionsMenuCommands.trigger('show', {targets: [target]});
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+}
+```
diff --git a/docs/components/dialog.md b/docs/components/dialog.md
index b8f2e5df..c621b4ee 100644
--- a/docs/components/dialog.md
+++ b/docs/components/dialog.md
@@ -6,6 +6,8 @@ title: Dialog
It is possible to show a dialog either attached to target [element](/docs/concepts/graph-model.md), [link](/docs/concepts/graph-model.md) or as a modal over the canvas viewport itself.
+## Showing a dialog
+
The following methods and properties from [OverlayController](/docs/api/workspace/classes/OverlayController) (accessible from [workspace context](/docs/concepts/workspace-context.md)) provide means to interact with the dialogs:
| Method or property | Description |
@@ -53,3 +55,13 @@ function Example() {
);
}
```
+
+## Styles
+
+The component look can be customized using the following CSS properties (see [design system](/docs/concepts/design-system.mdx) for more information):
+
+| Property | Description |
+|----------|-------------|
+| `--reactodia-dialog-border-color` | Border color for the dialog (uses the base border color if not set). |
+| `--reactodia-dialog-border-radius` | Border radius for the dialog (uses the base border radius if not set). |
+| `--reactodia-dialog-border-width` | Border width for the dialog (uses the base border width if not set). |
diff --git a/docs/components/drop-on-canvas.md b/docs/components/drop-on-canvas.md
index b4a33fd9..df1177f8 100644
--- a/docs/components/drop-on-canvas.md
+++ b/docs/components/drop-on-canvas.md
@@ -1,3 +1,3 @@
# Drop on Canvas
-[DropOnCanvas](/docs/api/workspace/functions/DropOnCanvas) component is a [canvas widget](/docs/components/canvas.md) to allow creating entity elements on the diagram by dragging then dropping a URL (IRI) to the canvas.
+[DropOnCanvas](/docs/api/workspace/functions/DropOnCanvas) component is a [canvas widget](/docs/components/canvas.md) to allow creating entity elements on the diagram by dragging then dropping a [URL (IRI)](/docs/concepts/data-provider.md#iri-and-rdf) to the canvas.
diff --git a/docs/components/instances-search.md b/docs/components/instances-search.md
index 221890e6..0d3ea588 100644
--- a/docs/components/instances-search.md
+++ b/docs/components/instances-search.md
@@ -3,7 +3,45 @@
[InstancesSearch](/docs/api/workspace/functions/InstancesSearch.md) is a component to search for entities by various filter criteria using [data provider lookup](/docs/concepts/data-provider.md) and add them as elements to the diagram.
:::tip
-
The same functionality is also available as `SearchSectionEntities` [unified search section](/docs/components/unified-search.md).
-
:::
+
+```tsx live
+function Example() {
+ const GRAPH_DATA = 'https://reactodia.github.io/resources/orgOntology.ttl';
+
+ const {defaultLayout} = Reactodia.useWorker(Layouts);
+
+ const [instancesSearchCommands] = React.useState(() =>
+ new Reactodia.EventSource()
+ );
+
+ const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
+ const {model, performLayout} = context;
+
+ const response = await fetch(GRAPH_DATA, {signal});
+ const graphData = new N3.Parser().parse(await response.text());
+ const dataProvider = new Reactodia.RdfDataProvider({acceptBlankNodes: false});
+ dataProvider.addGraph(graphData);
+ await model.createNewDiagram({dataProvider, signal});
+
+ instancesSearchCommands.trigger('setCriteria', {
+ criteria: {
+ refElement: 'http://www.w3.org/ns/org#Organization',
+ refElementLink: 'http://www.w3.org/2000/01/rdf-schema#domain',
+ }
+ });
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+ );
+}
+```
diff --git a/docs/components/layout-panels.md b/docs/components/layout-panels.md
index 334cd71c..ead1db35 100644
--- a/docs/components/layout-panels.md
+++ b/docs/components/layout-panels.md
@@ -17,7 +17,7 @@ Reactodia provides layout panel components to display a row or a column of resiz
```tsx live
function SomeLayout() {
return (
-
+
@@ -39,7 +39,7 @@ function SomeLayout() {
-
+
)
}
```
diff --git a/docs/components/link-types-toolbox.md b/docs/components/link-types-toolbox.md
index 5b3b26bc..f925d967 100644
--- a/docs/components/link-types-toolbox.md
+++ b/docs/components/link-types-toolbox.md
@@ -3,7 +3,5 @@
[LinkTypesToolbox](/docs/api/workspace/functions/LinkTypesToolbox.md) is a component to display incoming and outgoing [link types](/docs/api/workspace/type-aliases/LinkTypeIri.md) from selected elements, toggle their visibility and initiate the [lookup](/docs/components/instances-search.md) for connected entities.
:::tip
-
The same functionality is also available as `SearchSectionLinkTypes` [unified search section](/docs/components/unified-search.md).
-
:::
diff --git a/docs/components/navigator.md b/docs/components/navigator.md
index 4049a0b7..035c0a88 100644
--- a/docs/components/navigator.md
+++ b/docs/components/navigator.md
@@ -1,3 +1,63 @@
# Navigator
[Navigator](/docs/api/workspace/functions/Navigator) component is a [canvas widget](/docs/components/canvas.md) to display a minimap of the diagram contents.
+
+```tsx live
+function Example() {
+ const GRAPH_DATA = 'https://reactodia.github.io/resources/orgOntology.ttl';
+
+ const {defaultLayout} = Reactodia.useWorker(Layouts);
+
+ const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
+ const {model, view, performLayout} = context;
+
+ const response = await fetch(GRAPH_DATA, {signal});
+ const graphData = new N3.Parser().parse(await response.text());
+ const dataProvider = new Reactodia.RdfDataProvider({acceptBlankNodes: false});
+ dataProvider.addGraph(graphData);
+ await model.importLayout({dataProvider, signal});
+
+ const first = model.createElement('http://www.w3.org/ns/org#Organization');
+ const second = model.createElement('http://www.w3.org/ns/org#FormalOrganization');
+ await Promise.all([
+ model.requestElementData([first.iri, second.iri]),
+ model.requestLinks(),
+ ]);
+ await performLayout({signal});
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+}
+```
+
+## Styles
+
+The component look can be customized using the following CSS properties (see [design system](/docs/concepts/design-system.mdx) for more information):
+
+| Property | Description |
+|----------|-------------|
+| `--reactodia-navigator-background-fill` | Background color on the minimap outside the scrollable pane area. |
+| `--reactodia-navigator-scrollable-pane-fill` | Background color on the minimap for the scrollable pane area. |
+| `--reactodia-navigator-viewport-fill` | Background color on the minimap for the viewport area. |
+| `--reactodia-navigator-viewport-stroke-color` | Stroke color for the viewport area border. |
+| `--reactodia-navigator-viewport-stroke-width` | Stroke width for the viewport area border. |
+| `--reactodia-navigator-viewport-stroke-dash` | Stroke dash for the viewport area border. |
+| `--reactodia-navigator-overflow-stroke-color` | Stroke color for the viewport area overflow border (displayed when the viewport is cutoff at the minimap border). |
+| `--reactodia-navigator-overflow-stroke-width` | Stroke width for the viewport area overflow border (displayed when the viewport is cutoff at the minimap border). |
+| `--reactodia-navigator-overflow-stroke-dash` | Stroke dash for the viewport area overflow border (displayed when the viewport is cutoff at the minimap border). |
diff --git a/docs/components/selection.md b/docs/components/selection.md
index 1b8b14ff..65a533cc 100644
--- a/docs/components/selection.md
+++ b/docs/components/selection.md
@@ -36,3 +36,13 @@ There are several built-in link actions that can be used:
| [LinkActionDelete](/docs/api/workspace/functions/LinkActionDelete.md) | Deletes [the relation](/docs/concepts/graph-authoring.md). |
| [LinkActionMoveEndpoint](/docs/api/workspace/functions/LinkActionMoveEndpoint.md) | Displays a handle which allows to [change the relation](/docs/concepts/graph-authoring.md) by moving its endpoint (source or target) to another entity. |
| [LinkActionRename](/docs/api/workspace/functions/LinkActionRename.md) | Starts [renaming a link](/docs/api/workspace/interfaces/RenameLinkProvider.md) (change the label on the diagram only). |
+
+## Styles
+
+The component look can be customized using the following CSS properties (see [design system](/docs/concepts/design-system.mdx) for more information):
+
+| Property | Description |
+|----------|-------------|
+| `--reactodia-selection-icon-filter` | [CSS filter](https://developer.mozilla.org/en-US/docs/Web/CSS/filter) for the element selection action icons. |
+| `--reactodia-selection-multiple-box-shadow` | Box shadow for the selection rectangle with multiple elements. |
+| `--reactodia-selection-single-box-shadow` | Box shadow for the selection rectangle with a single element. |
diff --git a/docs/components/toolbar.md b/docs/components/toolbar.md
index 9c94ef3b..bc1493c8 100644
--- a/docs/components/toolbar.md
+++ b/docs/components/toolbar.md
@@ -2,6 +2,8 @@
[Toolbar](/docs/api/workspace/functions/Toolbar) component is a [canvas widget](/docs/components/canvas.md) to display a simple toolbar with a an optional dropdown menu.
+## Toolbar actions
+
There are several built-in toolbar actions that can be displayed as menu items or quick action buttons:
| Action component | Description |
|------------------|-------------|
@@ -14,3 +16,11 @@ There are several built-in toolbar actions that can be displayed as menu items o
| [ToolbarActionRedo](/docs/api/workspace/functions/ToolbarActionRedo.md) | Performs a [redo](/docs/api/workspace/interfaces/CommandHistory.md#redo) for a command from the [command history](/docs/concepts/command-history.md). |
| [ToolbarActionLayout](/docs/api/workspace/functions/ToolbarActionLayout.md) | Performs the default [graph layout algorithm](/docs/concepts/layout-workers.md) on the diagram content. |
| [ToolbarLanguageSelector](/docs/api/workspace/functions/ToolbarLanguageSelector.md) | Displays a [data language](/docs/api/workspace/classes/DiagramModel.md#language) selector for the workspace. |
+
+## Styles
+
+The component look can be customized using the following CSS properties (see [design system](/docs/concepts/design-system.mdx) for more information):
+
+| Property | Description |
+|----------|-------------|
+| `--reactodia-toolbar-height` | Default height for the toolbar and the menu toggle. |
diff --git a/docs/components/unified-search.md b/docs/components/unified-search.md
index 70ac5108..ced6138c 100644
--- a/docs/components/unified-search.md
+++ b/docs/components/unified-search.md
@@ -2,10 +2,6 @@
[UnifiedSearch](/docs/api/workspace/functions/UnifiedSearch.md) is a component to display a search input with a dropdown for results.
-## Hooks
-
-[useUnifiedSearchSection()](/docs/api/workspace/functions/useUnifiedSearchSection.md) hook can be used to implement a custom search section.
-
## Search sections
One or many available search sections (providers) can be specified:
@@ -15,3 +11,97 @@ One or many available search sections (providers) can be specified:
| [SearchSectionElementTypes](/docs/api/workspace/functions/SearchSectionElementTypes.md) | Allows to lookup entity types displayed in the tree form and create new entities in [authoring mode](/docs/concepts/graph-authoring.md). |
| [SearchSectionEntities](/docs/api/workspace/functions/SearchSectionEntities.md) | Allows to lookup entities using [data provider](/docs/concepts/data-provider.md). |
| [SearchSectionLinkTypes](/docs/api/workspace/functions/SearchSectionLinkTypes.md) | Allows to lookup displayed link types and change their [visibility settings](/docs/api/workspace/classes/DiagramModel.md#getlinkvisibility). |
+
+## Implement a custom search section
+
+[useUnifiedSearchSection()](/docs/api/workspace/functions/useUnifiedSearchSection.md) hook can be used to implement a custom search section:
+
+```tsx live noInline
+function NpmPackagesSearchSection() {
+ const {shouldRender, searchStore} = Reactodia.useUnifiedSearchSection();
+
+ const [items, setItems] = React.useState([]);
+ const [selection, setSelection] = React.useState(() => new Set());
+
+ React.useEffect(() => {
+ const listener = new Reactodia.EventObserver();
+ let controller = new AbortController();
+ listener.listen(searchStore.events, 'executeSearch', async ({value}) => {
+ controller.abort();
+ controller = new AbortController();
+ const signal = controller.signal;
+ let items: Reactodia.ElementModel[] = [];
+ try {
+ items = await queryNpmRegistry(value, signal);
+ } catch (err) {
+ if (signal.aborted) { return; }
+ console.error(err);
+ }
+ setItems(items);
+ setSelection(new Set());
+ });
+ listener.listen(searchStore.events, 'clearSearch', ({value}) => {
+ controller.abort();
+ setItems([]);
+ setSelection(new Set());
+ });
+ return () => {
+ controller.abort();
+ listener.stopListening();
+ };
+ }, [searchStore]);
+
+ return shouldRender ? (
+
+ );
+}
+
+render();
+```
diff --git a/docs/components/visual-authoring.md b/docs/components/visual-authoring.md
index 6aa225d3..e76a29ab 100644
--- a/docs/components/visual-authoring.md
+++ b/docs/components/visual-authoring.md
@@ -3,7 +3,5 @@
[VisualAuthoring](/docs/api/workspace/functions/VisualAuthoring) component is a [canvas widget](/docs/components/canvas.md) to provide UI for the [visual graph authoring](/docs/concepts/graph-authoring.md).
:::important
-
`VisualAuthoring` widget must be provided to the canvas to in order to display visual graph authoring UI.
-
:::
diff --git a/docs/components/workspace.md b/docs/components/workspace.md
index 3158e342..9390ef39 100644
--- a/docs/components/workspace.md
+++ b/docs/components/workspace.md
@@ -10,7 +10,7 @@ title: Workspace
[useLoadedWorkspace()](/docs/api/workspace/functions/useLoadedWorkspace) hook should be used to perform an initial initialization for the workspace which correctly reverts the changes and aborts async operations via provided [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) when the workspace component is unmounted.
-[useWorkspace()](/docs/api/workspace/functions/useWorkspace) hook can be used from inside `` child components to access workspace context.
+[useWorkspace()](/docs/api/workspace/functions/useWorkspace) hook can be used from inside `` child components to access workspace context; see [workspace context](/docs/concepts/workspace-context.md) for details.
## Workspace Layout
@@ -20,7 +20,7 @@ Default built-in layout is provided as [DefaultWorkspace](/docs/api/workspace/fu
Alternative (classic) built-in layout is provided as [ClassicWorkspace](/docs/api/workspace/functions/ClassicWorkspace) component which uses [layout panels](/docs/components/layout-panels.md) with [class tree](/docs/components/class-tree.md), [instances search](/docs/components/instances-search.md), [link types toolbox](/docs/components/link-types-toolbox.md), [ClassicToolbar](/docs/api/workspace/functions/ClassicToolbar) and all built-in [canvas widgets](/docs/components/canvas.md).
-When providing a custom workspace layout it is required to use [WorkspaceRoot](/docs/api/workspace/functions/WorkspaceRoot) as a top-level parent component to establish necessary defaults.
+When providing a custom workspace layout it is required to use [WorkspaceRoot](/docs/api/workspace/functions/WorkspaceRoot) as a top-level parent component to establish necessary style defaults, including styles for light or dark [color scheme](/docs/concepts/design-system.mdx).
### Example: canvas-only custom layout
diff --git a/docs/concepts/canvas-coordinates.md b/docs/concepts/canvas-coordinates.md
index fa5fca74..53d4e145 100644
--- a/docs/concepts/canvas-coordinates.md
+++ b/docs/concepts/canvas-coordinates.md
@@ -11,8 +11,8 @@ Reactodia uses different coordinate systems when dealing with infinitely-resizab
| Type | Description |
|-------------------|-------------|
| paper | Main coordinates system for [diagram content](/docs/concepts/graph-model.md) (elements, links, etc) — unlimited in size, always in 1:1 scale with element and link templates. |
-| scrollable pane | Represents scaled but still unlimited in size layer, natively scrolled inside viewport container. It is possible to use [totalPaneSize()](/docs/api/workspace/functions/totalPaneSize.md) and [paneTopLeft()](/docs/api/workspace/functions/paneTopLeft.md) to get current pane bounds with the same coordinates type. |
-| client (viewport) | Represents fixed-size "window" into visible graph area, equal to [DOM client coordinates](https://developer.mozilla.org/en-US/docs/Web/API/Element/clientWidth) for the [Canvas](/docs/components/canvas) component. |
+| scrollable pane | Represents scaled but still unlimited in size layer, natively scrolled inside viewport container. It is possible to use [`totalPaneSize()`](/docs/api/workspace/functions/totalPaneSize.md) and [`paneTopLeft()`](/docs/api/workspace/functions/paneTopLeft.md) to get current pane bounds with the same coordinates type. |
+| client (viewport) | Represents fixed-size "window" into visible graph area, equal to [DOM client coordinates](https://developer.mozilla.org/en-US/docs/Web/API/Element/clientWidth) for the [`Canvas`](/docs/components/canvas) component. |
| page | Same as [DOM page coordinates](https://developer.mozilla.org/en-US/docs/Web/CSS/CSSOM_view/Coordinate_systems#page) — generally used when handling pointer-related events. |

@@ -21,10 +21,10 @@ Reactodia uses different coordinate systems when dealing with infinitely-resizab
| From type | To type | Method |
| ------------------|-------------------|--------|
-| paper | scrollable pane | [paperToScrollablePaneCoords()](/docs/api/workspace/interfaces/CanvasMetrics.md#papertoscrollablepanecoords) |
-| | page | [paperToPageCoords()](/docs/api/workspace/interfaces/CanvasMetrics.md#papertopagecoords) |
-| scrollable pane | paper | [scrollablePaneToPaperCoords()](/docs/api/workspace/interfaces/CanvasMetrics.md#scrollablepanetopapercoords) |
-| | client (viewport) | [scrollablePaneToClientCoords()](/docs/api/workspace/interfaces/CanvasMetrics.md#scrollablepanetoclientcoords) |
-| client (viewport) | paper | [clientToPaperCoords()](/docs/api/workspace/interfaces/CanvasMetrics.md#clienttopapercoords) |
-| | scrollable pane | [clientToScrollablePaneCoords()](/docs/api/workspace/interfaces/CanvasMetrics.md#clienttoscrollablepanecoords) |
-| page | paper | [pageToPaperCoords()](/docs/api/workspace/interfaces/CanvasMetrics.md#pagetopapercoords) |
+| paper | scrollable pane | [`paperToScrollablePaneCoords()`](/docs/api/workspace/interfaces/CanvasMetrics.md#papertoscrollablepanecoords) |
+| | page | [`paperToPageCoords()`](/docs/api/workspace/interfaces/CanvasMetrics.md#papertopagecoords) |
+| scrollable pane | paper | [`scrollablePaneToPaperCoords()`](/docs/api/workspace/interfaces/CanvasMetrics.md#scrollablepanetopapercoords) |
+| | client (viewport) | [`scrollablePaneToClientCoords()`](/docs/api/workspace/interfaces/CanvasMetrics.md#scrollablepanetoclientcoords) |
+| client (viewport) | paper | [`clientToPaperCoords()`](/docs/api/workspace/interfaces/CanvasMetrics.md#clienttopapercoords) |
+| | scrollable pane | [`clientToScrollablePaneCoords()`](/docs/api/workspace/interfaces/CanvasMetrics.md#clienttoscrollablepanecoords) |
+| page | paper | [`pageToPaperCoords()`](/docs/api/workspace/interfaces/CanvasMetrics.md#pagetopapercoords) |
diff --git a/docs/concepts/command-history.md b/docs/concepts/command-history.md
index 9d69ecf9..b4540dcf 100644
--- a/docs/concepts/command-history.md
+++ b/docs/concepts/command-history.md
@@ -4,8 +4,125 @@ sidebar_position: 4
# Command History
-This page describes how command history and undo/redo works in Reactodia.
+Reactodia uses a common pattern to organize changes to the diagram model into "commands" representing different atomic actions to facilitate undo/redo support.
-API references:
- - [Command](/docs/api/workspace/interfaces/Command) and its [utility namespace](/docs/api/workspace/namespaces/Command)
- - [CommandHistory](/docs/api/workspace/interfaces/CommandHistory)
+## Commands and undo/redo history
+
+The [`Command`](/docs/api/workspace/interfaces/Command) interface defines an action which performs a set of changes to the Reactodia state and can be executed and reverted as needed.
+
+:::tip
+The library contains many built-in commands to manipulate the diagram model, graph authoring state or perform other effects, you can find them all at [API > workspace > Commands](/docs/api/workspace/#commands).
+:::
+
+To execute or revert a command, the [`CommandHistory`](/docs/api/workspace/interfaces/CommandHistory) instance from `DiagramModel.history` should be used to be able to undo or redo it later:
+
+```ts
+function Component() {
+ const {model} = Reactodia.useWorkspace();
+
+ const onClick = () => {
+ const command = changeLinkTypeVisibility(
+ model, 'http://example.com/connectedTo', 'hidden'
+ );
+ model.history.execute(command);
+ };
+
+ const onUndo = () => {
+ // Later: undo or redo a command
+ model.history.undo();
+ model.history.redo();
+ };
+
+ // ...
+}
+```
+
+Another way to use command history is to perform state changes and register a "revert" command which is used for diagram geometry updates:
+
+```ts
+function Component() {
+ const {model} = Reactodia.useWorkspace();
+
+ const onMove = () => {
+ const restoreGeometry = Reactodia.RestoreGeometry.capture(model);
+ /* ... make changes to the element positions and link vertices ... */
+ model.history.registerToUndo(command);
+ };
+
+ // ...
+}
+```
+
+## Command batches
+
+When executing multiple commands in a sequence in cases it would be desirable to undo or redo them all at once at though they were a single atomic command. In that case it is possible to start a [`CommandBatch`](/docs/api/workspace/interfaces/CommandBatch.md) via [`CommandHistory.startBatch()`](/docs/api/workspace/interfaces/CommandHistory.md#startbatch), execute the commands and store the batch, so a single command is added to the history:
+
+```ts
+function Component() {
+ const {model} = Reactodia.useWorkspace();
+ const {canvas} = Reactodia.useCanvas();
+
+ const onAddElements = (
+ target: Reactodia.Element,
+ iris: readonly Reactodia.ElementIri[]
+ ) => {
+ const batch = model.history.startBatch('Adding multiple elements');
+
+ for (const iri of iris) {
+ // Some methods implicitly add commands to the history,
+ // i.e. to the active batch if any
+ const element = model.createElement(iri);
+
+ // In other cases the command needs to be executed explicitly
+ batch.history.execute(setElementState(element, {'my:custom:state': 42}));
+ }
+
+ batch.store();
+ };
+}
+```
+
+It is also possible to discard a batch instead of storing it with [`CommandBatch.discard()`](/docs/api/workspace/interfaces/CommandBatch.md#discard) to avoid putting the commands in the history in the first place.
+
+:::note
+Starting a new batch when there is an active command batch already causes the new batch to become nested, which allows to use operations creating command batches as part of a larger operation having its own top-level batch.
+:::
+
+## How to define a new command
+
+While it is possible to implement [`Command`](/docs/api/workspace/interfaces/Command.md) interface directly, the library provides a [utility namespace](/docs/api/workspace/namespaces/Command) with the same name to simplify the process.
+
+[`Command.create()`](/docs/api/workspace/namespaces/Command/functions/create.md) defines a command from a callback which returns the reverse command:
+
+```ts
+function exchangeElementPositions(
+ first: Reactodia.ElementElement,
+ second: Reactodia.ElementElement
+): Command {
+ return Command.create('Exchange element positions', () => {
+ const position = first.position;
+ first.setPosition(second.position);
+ second.setPosition(position);
+ return exchangeElementPositions(first, second);
+ });
+}
+```
+
+[`Command.compound()`](/docs/api/workspace/namespaces/Command/functions/compound.md) defines a command from a sequence of other commands similar to using a command batch:
+
+```ts
+function resetElementStateForAll(
+ elements: readonly Reactodia.ElementElement[]
+): Command {
+ const commands = elements.map(el => Reactodia.setElementState(el, undefined));
+ return Command.compound('Reset state for elements', commands);
+}
+```
+
+[`Command.effect()`](/docs/api/workspace/namespaces/Command/functions/effect.md) defines a command which runs only after it executed in "forward" direction but skipped on revert:
+
+```ts
+function logAsCommand(message: string): Command {
+ return Command.effect('Log a message', () => console.log(message));
+}
+```
diff --git a/docs/concepts/data-provider.md b/docs/concepts/data-provider.md
index 46a66fa1..3044159e 100644
--- a/docs/concepts/data-provider.md
+++ b/docs/concepts/data-provider.md
@@ -4,13 +4,142 @@ sidebar_position: 2
# Data Provider
-This page describes the data provider contract for incremental data loading when exploring the graph.
-
-API references:
- - [DataProvider](/docs/api/workspace/interfaces/DataProvider)
- - [EmptyDataProvider](/docs/api/workspace/classes/EmptyDataProvider)
- - [RdfDataProvider](/docs/api/workspace/classes/RdfDataProvider)
- - [SparqlDataProvider](/docs/api/workspace/classes/SparqlDataProvider)
- - [CompositeDataProvider](/docs/api/workspace/classes/CompositeDataProvider)
- - [DecoratedDataProvider](/docs/api/workspace/classes/DecoratedDataProvider)
- - [IndexedDbCachedProvider](/docs/api/workspace/classes/IndexedDbCachedProvider)
+Reactodia defines a contract ([`DataProvider`](/docs/api/workspace/interfaces/DataProvider) interface) to query a subset of data from external source ([data graph](./graph-model.md#data-graph)) to provide means for incremental data loading when exploring the graph.
+
+## IRI and RDF
+
+Reactodia uses RDF ([Resource Description Framework](https://en.wikipedia.org/wiki/Resource_Description_Framework)) as a representation format for the graph data. The core concepts of RDF are:
+ - **IRI** ([Internationalized Resource Identifier](https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier)) — basically a URI but not limited to ASCII and may contain most unicode characters.
+ - **resource** — a graph node (element) represented by an **IRI** (in which case it is a **named node**) or a anonymous dataset-local identifier (it which case it is a **blank node**).
+ - **literal** — a simple value represented by a string with a *datatype* or a *language* tag.
+ - **triple** — an expressions of the form *subject*–*predicate*–*object* to represent a graph edge of type *predicate* (link type) between *source* **resource** and *target* **resource** or **literal**.
+ - **quad** — a **triple** with an additional associated *graph* **IRI**.
+
+For interoperability with other RDF-based libraries for JavaScript, the property values for entities and relations are stored as either **named node** or **literal** values using commonly used [RDF/JS](https://rdf.js.org/) representation.
+
+To provide improved type-safety with TypeScript when dealing with various kinds of IRIs from the [data graph](./graph-model.md#data-graph), the library uses the following [branded string types](https://www.learningtypescript.com/articles/branded-types):
+
+| Type | Description |
+|-----------------|-------------|
+| [`ElementIri`](/docs/api/workspace/type-aliases/ElementIri.md) | IRI of a entity (**resource**). |
+| [`ElementTypeIri`](/docs/api/workspace/type-aliases/ElementTypeIri.md) | IRI of a entity type (**resource**). |
+| [`LinkTypeIri`](/docs/api/workspace/type-aliases/LinkTypeIri.md) | IRI of a link type, i.e. triple *predicate* when the **object** is a **resource** (the predicate is always a **named node**). |
+| [`PropertyTypeIri`](/docs/api/workspace/type-aliases/PropertyTypeIri.md) | IRI of a property type, i.e. triple **predicate** when the **object** is a **literal** (the predicate is always a **named node**). |
+
+## Data Providers
+
+The library provides a number of built-in [`DataProvider`](/docs/api/workspace/interfaces/DataProvider) interface implementations for various scenarios:
+
+| Provider | Description |
+|----------|-------------|
+| [`EmptyDataProvider`](/docs/api/workspace/classes/EmptyDataProvider) | An empty provider which returns nothing from all query methods. |
+| [`RdfDataProvider`](/docs/api/workspace/classes/RdfDataProvider) | Provides graph data from an in-memory [RDF/JS-compatible](https://rdf.js.org/data-model-spec/) graph dataset. |
+| [`SparqlDataProvider`](/docs/api/workspace/classes/SparqlDataProvider) | Provides graph data by requesting it from a [SPARQL](https://en.wikipedia.org/wiki/SPARQL) endpoint. |
+| [`CompositeDataProvider`](/docs/api/workspace/classes/CompositeDataProvider) | Provides graph data by combining results from multiple other data providers. |
+| [`DecoratedDataProvider`](/docs/api/workspace/classes/DecoratedDataProvider) | Generically wraps over another provider to modify how the requests are made or alter the results. |
+| [`IndexedDbCachedProvider`](/docs/api/workspace/classes/IndexedDbCachedProvider) | Caches graph data returned from another data provider using browser's built-in [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) storage. |
+
+:::tip
+It is recommended to extend [`EmptyDataProvider`](/docs/api/workspace/classes/EmptyDataProvider) when implementing a data provider: this way methods can be implemented one-by-one as needed and no changes will be necessary if `DataProvider` will gain additional methods in the future.
+:::
+
+### Example: provisioning an `RdfDataProvider` from a graph data in JSON Graph Format
+
+In this example Reactodia is initialized with `RdfDataProvider` which is provisioned with graph data in [JSON Graph Format](https://github.com/jsongraph/json-graph-specification).
+
+As a first step, the data in converted into RDF graph (**triples**), next the graph is added to the provider, finally all the nodes are added tot the diagram:
+
+```tsx live
+function ExampleRdfProviderProvisionFromJGF() {
+ const {defaultLayout} = Reactodia.useWorker(Layouts);
+
+ const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
+ const {model, performLayout} = context;
+
+ // Example graph data based on JSON graph documentation:
+ const jsonGraph = {
+ "graph": {
+ "nodes": {
+ "alice": {
+ "label": "Alice",
+ "metadata": {
+ "type": "Person",
+ "birthDate": "1990-01-01"
+ }
+ },
+ "bob": {
+ "label": "Bob",
+ "metadata": {
+ "type": "Person",
+ "birthDate": "1990-02-02"
+ }
+ }
+ },
+ "edges": [
+ {
+ "source": "alice",
+ "relation": "isFriendOf",
+ "target": "bob",
+ "metadata": {
+ "since": "2000-03-03"
+ }
+ }
+ ]
+ }
+ } as const;
+
+ const factory = Reactodia.Rdf.DefaultDataFactory;
+ const hasType = factory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
+ const hasLabel = factory.namedNode('http://www.w3.org/2000/01/rdf-schema#label');
+
+ const triples: Reactodia.Rdf.Quad[] = [];
+ for (const [id, node] of Object.entries(jsonGraph.graph.nodes)) {
+ const iri = factory.namedNode(`graph:node:${id}`);
+ const {type, ...otherProperties} = node.metadata;
+ triples.push(
+ factory.quad(iri, hasType, factory.namedNode(`graph:type:${type}`)),
+ factory.quad(iri, hasLabel, factory.literal(node.label))
+ );
+ for (const [property, value] of Object.entries(otherProperties)) {
+ const propertyIri = factory.namedNode(`graph:property:${property}`);
+ triples.push(factory.quad(iri, propertyIri, factory.literal(value)));
+ }
+ }
+
+ for (const edge of jsonGraph.graph.edges) {
+ const source = factory.namedNode(`graph:node:${edge.source}`);
+ const target = factory.namedNode(`graph:node:${edge.target}`);
+ const predicate = factory.namedNode(`graph:node:${edge.relation}`);
+ const edgeTriple = factory.quad(source, predicate, target);
+ triples.push(edgeTriple);
+ for (const [property, value] of Object.entries(edge.metadata)) {
+ const propertyIri = factory.namedNode(`graph:property:${property}`);
+ triples.push(factory.quad(edgeTriple, propertyIri, factory.literal(value)));
+ }
+ }
+
+ const dataProvider = new Reactodia.RdfDataProvider();
+ dataProvider.addGraph(triples);
+
+ await model.createNewDiagram({dataProvider, signal});
+
+ const elementIris: Reactodia.ElementIri[] = [];
+ for (const {element} of await dataProvider.lookup({elementTypeId: 'graph:type:Person'})) {
+ elementIris.push(model.createElement(element).iri);
+ }
+
+ await model.requestElementData(elementIris);
+ await model.requestLinks();
+ await performLayout({signal});
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+}
+```
diff --git a/docs/concepts/design-system.mdx b/docs/concepts/design-system.mdx
new file mode 100644
index 00000000..b6ebac4c
--- /dev/null
+++ b/docs/concepts/design-system.mdx
@@ -0,0 +1,416 @@
+---
+sidebar_position: 10
+title: Design System
+---
+
+import * as React from 'react';
+import * as Reactodia from '@reactodia/workspace';
+import { ButtonToggle } from '@site/src/components/ButtonToggle';
+import { ColorBoxes } from '@site/src/components/ColorBoxes';
+
+# Basic Design System
+
+Reactodia defines a basic design system inspired by [Infima](https://infima.dev/) styling framework to support both light and dark color schemes and easy customization via [CSS custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascading_variables/Using_CSS_custom_properties).
+
+This page documents base (common) styles for used as defaults for all UI components. Component-specific CSS properties are documented on the corresponding [component pages](/docs/category/components).
+
+See [Reactodia theme styling source](https://github.com/reactodia/reactodia-workspace/tree/master/styles/theme) for the complete list of CSS properties to customize and their defaults for both light and dark color schemes.
+
+## Colors
+
+:::tip
+
+Toggle active color scheme at the top to see how the default colors are defined for either light or dark themes.
+
+:::
+
+### Grays {#color-grays}
+
+
+ `var(--reactodia-color-gray-${i * 100})`)
+ }
+ />
+
+
+Color-scheme independent gray colors gradient from 0 (white) to 1000 (black) in increments of 100:
+```css
+--reactodia-color-gray-{i}
+```
+
+### Emphasis {#color-emphasis}
+
+
+ `var(--reactodia-color-emphasis-${i * 100})`)
+ }
+ />
+
+
+Color-scheme specific gray colors gradient from 0 (same as schema color) to 1000 (opposite to schema color) in increments of 100:
+```css
+--reactodia-color-emphasis-{i}
+```
+
+### Content and Background
+
+
+
+
+
+Color-scheme specific color for a text-like content, pure inverse for the background, and a contrast (which is suitable for content on top of a background of the named color):
+```css
+--reactodia-color-content
+--reactodia-color-content-inverse
+--reactodia-color-content-contrast
+```
+
+
+
+
+
+Color-scheme specific color for a background-like layers, and a surface one which can be used for layers placed on top of the main background:
+```css
+--reactodia-background-color
+--reactodia-background-color-surface
+```
+
+### Named colors
+
+export const NamedColorBoxes = ({color}) => (
+ <>
+
+
+ >
+);
+
+Named colors can be used to show specific action types for buttons, importance for statuses, etc. The primary color can be specifically used to highlight the "main" action or give a color accent to a UI element:
+```css
+--reactodia-color-{color}-lightest
+--reactodia-color-{color}-lighter
+--reactodia-color-{color}-light
+--reactodia-color-{color}
+--reactodia-color-{color}-dark
+--reactodia-color-{color}-darker
+--reactodia-color-{color}-darkest
+```
+
+Additionally, each color has a contrast foreground (to put on the named color) and a contrast background (to put the named color on):
+```css
+--reactodia-color-{color}-contrast-background
+--reactodia-color-{color}-contrast-foreground
+```
+
+#### `primary` color
+
+
+
+
+
+#### `secondary` color
+
+
+
+
+
+#### `success` color
+
+
+
+
+
+#### `info` color
+
+
+
+
+
+#### `warning` color
+
+
+
+
+
+#### `danger` color
+
+
+
+
+
+## Text
+
+The following properties allow to customize font family, base size and color:
+
+```css
+--reactodia-font-family-base
+--reactodia-font-family-monospace
+--reactodia-font-size-base
+--reactodia-line-height-base
+--reactodia-font-color-base
+--reactodia-font-color-base-inverse
+```
+
+```tsx live
+function Text() {
+ return (
+
+
+ Normal: The quick brown fox jumps over the lazy dog.
+
+
+ Monospace: The quick brown fox jumps over the lazy dog.
+
+
+ Inverse: The quick brown fox jumps over the lazy dog.
+
+
+ );
+}
+```
+
+## Borders
+
+The following properties allow to customize base border styling, including default border radius for UI elements such as buttons, inputs, panels, etc:
+```css
+--reactodia-border-width-base
+--reactodia-border-radius-base
+--reactodia-border-color-base
+```
+
+```tsx live
+function Borders() {
+ return
+
+ Panel
+
+
+
+
+}
+```
+
+## Spacing
+
+The following properties allow to customize default spacing between UI elements:
+```css
+--reactodia-spacing-base
+--reactodia-spacing-vertical
+--reactodia-spacing-horizontal
+```
+
+```tsx live
+function Spacing() {
+ return
+