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.34.1'
reactodia_workspace_ref: 'v0.35.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.34.1'
reactodia_workspace_ref: 'v0.35.0'

jobs:
build:
Expand Down
32 changes: 16 additions & 16 deletions docs/components/form-input.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,21 @@ Currently form input components are considered **unstable** so there might be br
```tsx live noInline
function Example() {
const GRAPH_DATA = 'https://reactodia.github.io/resources/orgOntology.ttl';
const RDF_COMMENT = 'http://www.w3.org/2000/01/rdf-schema#comment';

const {defaultLayout} = Reactodia.useWorker(Layouts);
const [workspace] = React.useState(() => Reactodia.createWorkspace({
defaultLayout,
metadataProvider: new Reactodia.BaseMetadataProvider({
canModifyEntity: () => ({canEdit: true}),
getEntityShape: types => ({
properties: new Map([
[Reactodia.rdfs.label, {valueShape: {termType: 'Literal'}}],
[RDF_COMMENT, {valueShape: {termType: 'Literal'}}],
])
}),
}),
}));

const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
const {model, editor, getCommandBus, performLayout} = context;
Expand All @@ -41,23 +54,10 @@ function Example() {
.trigger('editEntity', {target: element});
}, []);

const RDF_COMMENT = 'http://www.w3.org/2000/01/rdf-schema#comment';

const [metadataProvider] = React.useState(() => new Reactodia.BaseMetadataProvider({
canModifyEntity: () => ({canEdit: true}),
getEntityShape: types => ({
properties: new Map([
[Reactodia.rdfs.label, {valueShape: {termType: 'Literal'}}],
[RDF_COMMENT, {valueShape: {termType: 'Literal'}}],
])
}),
}));

return (
<div className='reactodia-live-editor'>
<Reactodia.Workspace ref={onMount}
metadataProvider={metadataProvider}
defaultLayout={defaultLayout}>
<Reactodia.WorkspaceProvider workspace={workspace}
onMount={onMount}>
<Reactodia.DefaultWorkspace
search={null}
navigator={{expanded: false}}
Expand All @@ -75,7 +75,7 @@ function Example() {
),
}}
/>
</Reactodia.Workspace>
</Reactodia.WorkspaceProvider>
</div>
);
}
Expand Down
58 changes: 32 additions & 26 deletions docs/components/workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ title: <Workspace />

# Workspace Components

[`<Workspace />`](/docs/api/workspace/classes/Workspace) is a top-level component which establishes [workspace context](/docs/api/workspace/interfaces/WorkspaceContext), which stores graph data and provides means to display and interact with the diagram.
The recommended way to display a workspace is to use [`createWorkspace()`](/docs/api/workspace/functions/createWorkspace.md) to create new [workspace context](/docs/api/workspace/interfaces/WorkspaceContext), which stores graph data and provides means to display and interact with the diagram. Then the context can be provided to child components with [`<WorkspaceProvider>`](/docs/api/workspace/functions/WorkspaceProvider.md).

Alternatively, [`<Workspace />`](/docs/api/workspace/classes/Workspace) can be used as a top-level utility component which both creates and provides the context.

## Hooks

[`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 `<Workspace />` child components to access workspace context; see [workspace context](/docs/concepts/workspace-context.md) for details.

## Workspace Layout
## `<WorkspaceRoot>` and built-in layouts

The UI of the workspace is defined by the child components provided to the `<Workspace />`:
The UI of the workspace is composed of various components which use provided [workspace context](/docs/api/workspace/interfaces/WorkspaceContext):

Default built-in layout is provided as [`<DefaultWorkspace />`](/docs/api/workspace/functions/DefaultWorkspace) component which includes the [unified search](/docs/components/unified-search.md), main menu and action [toolbars](/docs/components/toolbar.md) and all built-in [canvas widgets](/docs/components/canvas.md#widgets).

Expand All @@ -27,49 +29,53 @@ When providing a custom workspace layout it is required to use [`<WorkspaceRoot
```tsx live
function Example() {
const {defaultLayout} = Reactodia.useWorker(Layouts);
const [workspace] = React.useState(() => Reactodia.createWorkspace({
defaultLayout,
}));

const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
const {model, view} = context;
const dataProvider = new Reactodia.EmptyDataProvider();
await model.createNewDiagram({dataProvider, signal});
model.createElement('http://example.com/entity');
const canvas = view.findAnyCanvas();
canvas.zoomToFit();
const {model, view} = context;
const dataProvider = new Reactodia.EmptyDataProvider();
await model.createNewDiagram({dataProvider, signal});
model.createElement('http://example.com/entity');
const canvas = view.findAnyCanvas();
canvas.zoomToFit();
}, []);

return (
<div className='reactodia-live-editor'>
<Reactodia.Workspace ref={onMount}
defaultLayout={defaultLayout}>
<Reactodia.WorkspaceProvider workspace={workspace}
onMount={onMount}>
<Reactodia.WorkspaceRoot>
<Reactodia.Canvas />
</Reactodia.WorkspaceRoot>
</Reactodia.Workspace>
</Reactodia.WorkspaceProvider>
</div>
);
}
```

## Customize type styles

Reactodia displays [entities](/docs/concepts/graph-model.md) in different places throughout the workspace components. It is possible to customize overall style based on entity types (accent color and icon) by providing a custom [`TypeStyleResolver`](/docs/api/workspace/type-aliases/TypeStyleResolver.md) to the `<Workspace typeStyleResolver={...} />`:
Reactodia displays [entities](/docs/concepts/graph-model.md) in different places throughout the workspace components. It is possible to customize overall style based on entity types (accent color and icon) by providing a custom [`typeStyleResolver`](/docs/api/workspace/type-aliases/TypeStyleResolver.md) to the [`createWorkspace()`](/docs/api/workspace/functions/createWorkspace.md):

```tsx
<Reactodia.Workspace
const [workspace] = React.useState(() => Reactodia.createWorkspace({
defaultLayout,
typeStyleResolver={types => {
if (types.includes('http://www.w3.org/2000/01/rdf-schema#Class')) {
return {icon: CERTIFICATE_ICON, iconMonochrome: true};
} else if (types.includes('http://www.w3.org/2002/07/owl#Class')) {
return {icon: CERTIFICATE_ICON, iconMonochrome: true};
} else if (types.includes('http://www.w3.org/2002/07/owl#ObjectProperty')) {
return {icon: COG_ICON, iconMonochrome: true};
} else if (types.includes('http://www.w3.org/2002/07/owl#DatatypeProperty')) {
return {color: '#00b9f2'};
} else {
return undefined;
}
if (types.includes('http://www.w3.org/2000/01/rdf-schema#Class')) {
return {icon: CERTIFICATE_ICON, iconMonochrome: true};
} else if (types.includes('http://www.w3.org/2002/07/owl#Class')) {
return {icon: CERTIFICATE_ICON, iconMonochrome: true};
} else if (types.includes('http://www.w3.org/2002/07/owl#ObjectProperty')) {
return {icon: COG_ICON, iconMonochrome: true};
} else if (types.includes('http://www.w3.org/2002/07/owl#DatatypeProperty')) {
return {color: '#00b9f2'};
} else {
return undefined;
}
}}
...>
}));
```

By default, the colors are assigned deterministically based on total hash of the types in the [entity data](/docs/api/workspace/interfaces/ElementModel.md).
2 changes: 1 addition & 1 deletion docs/concepts/event-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,4 @@ function OtherComponent() {
}
```

Each Reactodia [`<Workspace />`](/docs/components/workspace.md) instance maintains its own command buses for each topic not connected to other workspaces in any way.
Each [workspace context](/docs/concepts/workspace-context.md) instance maintains its own command buses for each topic not connected to other workspaces in any way.
2 changes: 1 addition & 1 deletion docs/concepts/graph-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const onClick = async () => {

## Default layout configuration

`defaultLayout` is the required prop for the top-level [`<Workspace>`](/docs/components/workspace.md) component and Reactodia provides two approaches to get a good default layout function:
`defaultLayout` is the required prop for [`createWorkspace()`](/docs/components/workspace.md) or the top-level [`<Workspace>`](/docs/components/workspace.md) component and Reactodia provides two approaches to get a good default layout function:

### Layout via Web Workers

Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/graph-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ The diagram layout (which includes cell positions and states) can be exported (s

```tsx
function WorkingWithImportExport() {
const {model, onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
const {model} = context;

// Import a diagram layout on mount
Expand Down
137 changes: 86 additions & 51 deletions docs/concepts/i18n.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,42 +32,54 @@ This schema is available as [`@reactodia/workspace/i18n/i18n.schema.json`](https
and can be used with external JSON validation tool (e.g. [`ajv-cli`](https://github.com/ajv-validator/ajv-cli))
to check the translations.

To provide customized translation, additional bundles can be passed to
[`Workspace`](/docs/components/workspace.md) component with `translations` property:
To provide customized translation, all bundles with localized strings can be passed to [`DefaultTranslation`](/docs/api/workspace/classes/DefaultTranslation.md) instance provided with `translation` option to [`createWorkspace()`](/docs/api/workspace/functions/createWorkspace.md):
```tsx
function TranslationOverride() {
return (
<Reactodia.Workspace
translations={[
{
'default_workspace': {
'search_section_entity_types.label': 'Class Tree',
'search_section_entity_types.title': 'Class tree hierarchy',
}
}
]}>
<Reactodia.DefaultWorkspace />
</Reactodia.Workspace>
);
const {defaultLayout} = Reactodia.useWorker(Layouts);
const [workspace] = React.useState(() => Reactodia.createWorkspace({
defaultLayout,
translation: new Reactodia.DefaultTranslation({
bundles: [
{
'default_workspace': {
'search_section_entity_types.label': 'Class Tree',
'search_section_entity_types.title': 'Class tree hierarchy',
}
},
// Fallback bundle with built-in "en" translation strings
Reactodia.DefaultTranslationBundle,
],
})
}));
return (
<Reactodia.WorkspaceProvider workspace={workspace}>
<Reactodia.DefaultWorkspace />
</Reactodia.Workspace>
);
}
```

Additional built-in or custom translation can be used the same way by loading the JSON bundle externally and
pass them with `translations` property in th order of the one with the high priority to low priority.
Default `en` translation [`@reactodia/workspace/i18n/translations/en.reactodia-translation.json`](https://github.com/reactodia/reactodia-workspace/blob/master/i18n/translations/en.reactodia-translation.json) is always
enabled as a fallback unless `useDefaultTranslation={false}` is specified:
pass them with `bundles` property in the high priority to low priority order.

It is also possible to exclude default `en` translation [`@reactodia/workspace/i18n/translations/en.reactodia-translation.json`](https://github.com/reactodia/reactodia-workspace/blob/master/i18n/translations/en.reactodia-translation.json) by omitting [`DefaultTranslationBundle`](/docs/api/workspace/variables/DefaultTranslationBundle.md) from `bundles` array:
```tsx
import enUkTranslation from './en-uk.reactodia-translation.json';
import deTranslation from './de.reactodia-translation.json';

function MultipleTranslations() {
return (
<Reactodia.Workspace
translations={[deTranslation, enUkTranslation]}
useDefaultTranslation={false}>
<Reactodia.DefaultWorkspace />
</Reactodia.Workspace>
);
const {defaultLayout} = Reactodia.useWorker(Layouts);
const [workspace] = React.useState(() => Reactodia.createWorkspace({
defaultLayout,
translation: new Reactodia.DefaultTranslation({
bundles: [deTranslation, enUkTranslation],
}),
}));
return (
<Reactodia.WorkspaceProvider workspace={workspace}>
<Reactodia.DefaultWorkspace />
</Reactodia.Workspace>
);
}
```

Expand All @@ -80,36 +92,59 @@ The localization mechanism can be used for a custom component nested inside the
In the following example, additional custom translation keys are added to the workspace to provide localizable component labels:
```tsx live noInline
function MyComponent() {
const t = Reactodia.useTranslation();
return (
<Reactodia.ToolbarAction
title={t.text('my_component.do_action.title')}
onSelect={() => alert('Done')}>
{t.text('my_component.do_action.label')}
</Reactodia.ToolbarAction>
);
const t = Reactodia.useTranslation();
return (
<Reactodia.ToolbarAction
title={t.text('my_component.do_action.title')}
onSelect={() => alert('Done')}>
{t.text('my_component.do_action.label')}
</Reactodia.ToolbarAction>
);
}

function CustomTranslationKeys() {
const {defaultLayout} = Reactodia.useWorker(Layouts);
return (
<div className='reactodia-live-editor'>
<Reactodia.Workspace defaultLayout={defaultLayout}
translations={[
{
'my_component': {
'do_action.label': 'Do the impossible!',
'do_action.title': 'Perform the impossible action',
}
}
]}>
<Reactodia.DefaultWorkspace
actions={<MyComponent />}
/>
</Reactodia.Workspace>
</div>
);
const {defaultLayout} = Reactodia.useWorker(Layouts);
const [workspace] = React.useState(() => Reactodia.createWorkspace({
defaultLayout,
translation: new Reactodia.DefaultTranslation({
bundles: [
{
'my_component': {
'do_action.label': 'Do the impossible!',
'do_action.title': 'Perform the impossible action',
}
},
Reactodia.DefaultTranslationBundle,
],
}),
}));
return (
<div className='reactodia-live-editor'>
<Reactodia.WorkspaceProvider workspace={workspace}>
<Reactodia.DefaultWorkspace
actions={<MyComponent />}
/>
</Reactodia.WorkspaceProvider>
</div>
);
}

render(<CustomTranslationKeys />);
```

## Translation context

Translation mechanism can be used outside the [workspace context](/docs/concepts/workspace-context.md) in other parts of an application when needed by establishing the context with [`<TranslationProvider>`](/docs/api/workspace/functions/TranslationProvider.md) in order to make use of [`useTranslation()`](/docs/api/workspace/functions/useTranslation.md) hook:

```tsx
function App() {
const translation = React.useMemo(() => new Reactodia.DefaultTranslation({
bundles: [/*...*/],
}), []);
return (
<Reactodia.TranslationProvider translation={translation}>
{/* app components */}
</Reactodia.TranslationProvider>
);
}
```
Loading
Loading