diff --git a/.github/workflows/ci-checks.yml b/.github/workflows/ci-checks.yml
index 33bef40e..6f43270c 100644
--- a/.github/workflows/ci-checks.yml
+++ b/.github/workflows/ci-checks.yml
@@ -10,7 +10,7 @@ on:
branches: [ "main" ]
env:
- reactodia_workspace_ref: 'v0.34.1'
+ reactodia_workspace_ref: 'v0.35.0'
jobs:
build:
diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml
index ae01fa85..58fed323 100644
--- a/.github/workflows/deploy-pages.yml
+++ b/.github/workflows/deploy-pages.yml
@@ -8,7 +8,7 @@ on:
workflow_dispatch:
env:
- reactodia_workspace_ref: 'v0.34.1'
+ reactodia_workspace_ref: 'v0.35.0'
jobs:
build:
diff --git a/docs/components/form-input.md b/docs/components/form-input.md
index a878d4c4..c7ed90ee 100644
--- a/docs/components/form-input.md
+++ b/docs/components/form-input.md
@@ -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;
@@ -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 (
-
+
-
+
);
}
diff --git a/docs/components/workspace.md b/docs/components/workspace.md
index 0bfdccf8..dcb9725f 100644
--- a/docs/components/workspace.md
+++ b/docs/components/workspace.md
@@ -4,7 +4,9 @@ title:
# Workspace Components
-[``](/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 [``](/docs/api/workspace/functions/WorkspaceProvider.md).
+
+Alternatively, [``](/docs/api/workspace/classes/Workspace) can be used as a top-level utility component which both creates and provides the context.
## Hooks
@@ -12,9 +14,9 @@ title:
[`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
+## `` and built-in layouts
-The UI of the workspace is defined by the child components provided to the ``:
+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 [``](/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).
@@ -27,24 +29,27 @@ When providing a custom workspace layout it is required to use [` 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 (
-
+
-
+
);
}
@@ -52,24 +57,25 @@ function Example() {
## 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 ``:
+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.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).
diff --git a/docs/concepts/event-system.md b/docs/concepts/event-system.md
index c290d8c6..3c9c2136 100644
--- a/docs/concepts/event-system.md
+++ b/docs/concepts/event-system.md
@@ -165,4 +165,4 @@ function OtherComponent() {
}
```
-Each Reactodia [``](/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.
diff --git a/docs/concepts/graph-layout.md b/docs/concepts/graph-layout.md
index 8dfa097b..2b6f9bde 100644
--- a/docs/concepts/graph-layout.md
+++ b/docs/concepts/graph-layout.md
@@ -49,7 +49,7 @@ const onClick = async () => {
## Default layout configuration
-`defaultLayout` is the required prop for the top-level [``](/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 [``](/docs/components/workspace.md) component and Reactodia provides two approaches to get a good default layout function:
### Layout via Web Workers
diff --git a/docs/concepts/graph-model.md b/docs/concepts/graph-model.md
index ee98c2b0..69ea1634 100644
--- a/docs/concepts/graph-model.md
+++ b/docs/concepts/graph-model.md
@@ -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
diff --git a/docs/concepts/i18n.md b/docs/concepts/i18n.md
index a91bb0f9..5dd37aa3 100644
--- a/docs/concepts/i18n.md
+++ b/docs/concepts/i18n.md
@@ -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 (
-
-
-
- );
+ 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 (
+
+
+
+ );
}
```
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 (
-
-
-
- );
+ const {defaultLayout} = Reactodia.useWorker(Layouts);
+ const [workspace] = React.useState(() => Reactodia.createWorkspace({
+ defaultLayout,
+ translation: new Reactodia.DefaultTranslation({
+ bundles: [deTranslation, enUkTranslation],
+ }),
+ }));
+ return (
+
+
+
+ );
}
```
@@ -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 (
- alert('Done')}>
- {t.text('my_component.do_action.label')}
-
- );
+ const t = Reactodia.useTranslation();
+ return (
+ alert('Done')}>
+ {t.text('my_component.do_action.label')}
+
+ );
}
function CustomTranslationKeys() {
- const {defaultLayout} = Reactodia.useWorker(Layouts);
- return (
-
-
- }
- />
-
-
- );
+ 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 (
+
+
+ }
+ />
+
+
+ );
}
render();
```
+
+## 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 [``](/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 (
+
+ {/* app components */}
+
+ );
+}
+```
diff --git a/docs/concepts/workspace-context.md b/docs/concepts/workspace-context.md
index ba030330..a1381cc2 100644
--- a/docs/concepts/workspace-context.md
+++ b/docs/concepts/workspace-context.md
@@ -19,32 +19,38 @@ The [`WorkspaceContext`](/docs/api/workspace/interfaces/WorkspaceContext) contai
## Getting the workspace context
-The [`WorkspaceContext`](/docs/api/workspace/interfaces/WorkspaceContext) instance can be acquired in two ways.
-The first one is from the result of `useLoadedWorkspace()` hook which is used for initialization of the [`Workspace`](/docs/components/workspace.md) component:
+The [`WorkspaceContext`](/docs/api/workspace/interfaces/WorkspaceContext) instance can be acquired in multiple ways.
+
+At the top level it is directly available when creating the workspace with [`createWorkspace()`](/docs/api/workspace/functions/createWorkspace.md):
```tsx
function Example() {
const {defaultLayout} = Reactodia.useWorker(Layouts);
+ const [workspace] = React.useState(() => Reactodia.createWorkspace({
+ defaultLayout,
+ }));
+
+ // `workspace` is a workspace context instance
- const {onMount, getContext} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
- /* ... */
+ const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
+ // The context is also available via `context`
}, []);
const onSomething = () => {
- const {model, editor, /* etc */} = getContext();
+ const {model, editor, /* etc */} = workspace;
// Use workspace context
};
return (
-
+
{/* ... */}
-
+
);
}
```
-The second way is to use [`useWorkspace()`](/docs/api/workspace/functions/useWorkspace) hook inside the [`Workspace`](/docs/components/workspace.md) component itself, e.g. a canvas widget:
+There is also a [`useWorkspace()`](/docs/api/workspace/functions/useWorkspace) hook to get the context from children rendered inside the [`Workspace`](/docs/components/workspace.md) or [`WorkspaceProvider`](/docs/components/workspace.md) components, e.g. a canvas widget:
```tsx
function MyWidget() {
diff --git a/docs/examples/live.md b/docs/examples/live.md
index 6aa59f32..d0ff9db0 100644
--- a/docs/examples/live.md
+++ b/docs/examples/live.md
@@ -12,6 +12,9 @@ function SimpleExample() {
const GRAPH_DATA = 'https://reactodia.github.io/resources/orgOntology.ttl';
const {defaultLayout} = Reactodia.useWorker(Layouts);
+ const [workspace] = React.useState(() => Reactodia.createWorkspace({
+ defaultLayout,
+ }));
const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
const {model, performLayout} = context;
@@ -35,8 +38,8 @@ function SimpleExample() {
return (
-
+
diff --git a/package-lock.json b/package-lock.json
index 9fddfad8..6bfacc6d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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.34.1",
+ "@reactodia/workspace": "^0.35.0",
"@zip.js/zip.js": "^2.8.23",
"clsx": "^2.1.1",
"n3": "^1.17.2",
@@ -4752,9 +4752,9 @@
"license": "MIT"
},
"node_modules/@reactodia/workspace": {
- "version": "0.34.1",
- "resolved": "https://registry.npmjs.org/@reactodia/workspace/-/workspace-0.34.1.tgz",
- "integrity": "sha512-dqZJ/BxKzKGabfQbHDFSdvDW81Vi89c2v0aOs7YFyG/bodqWCQEzuQ4yBd/bB6Z8hKm9uHmybtkFjkCpXAHcJQ==",
+ "version": "0.35.0",
+ "resolved": "https://registry.npmjs.org/@reactodia/workspace/-/workspace-0.35.0.tgz",
+ "integrity": "sha512-3eIjJ567YyzRyluz2etsSh4Wwcfe1oVBnO+KJhTFKX6z1oNzddmZcTWCb2uAsigIzJ269/kAsCswVMwBBvMfOg==",
"license": "LGPL-2.1-or-later",
"dependencies": {
"@reactodia/hashmap": "^0.2.1",
diff --git a/package.json b/package.json
index adad7aa4..fc04b5a7 100644
--- a/package.json
+++ b/package.json
@@ -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.34.1",
+ "@reactodia/workspace": "^0.35.0",
"@zip.js/zip.js": "^2.8.23",
"clsx": "^2.1.1",
"n3": "^1.17.2",
diff --git a/src/examples/PlaygroundBasic.tsx b/src/examples/PlaygroundBasic.tsx
index 0a65dfbf..0a123b11 100644
--- a/src/examples/PlaygroundBasic.tsx
+++ b/src/examples/PlaygroundBasic.tsx
@@ -10,6 +10,9 @@ export function PlaygroundBasic() {
const GRAPH_DATA = 'https://reactodia.github.io/resources/orgOntology.ttl';
const {defaultLayout} = Reactodia.useWorker(Layouts);
+ const [workspace] = React.useState(() => Reactodia.createWorkspace({
+ defaultLayout,
+ }));
const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
const {model, performLayout} = context;
@@ -32,9 +35,9 @@ export function PlaygroundBasic() {
}, []);
return (
-
+
-
+
);
}
diff --git a/src/examples/PlaygroundClassicWorkspace.tsx b/src/examples/PlaygroundClassicWorkspace.tsx
index ff92c17c..1695e5eb 100644
--- a/src/examples/PlaygroundClassicWorkspace.tsx
+++ b/src/examples/PlaygroundClassicWorkspace.tsx
@@ -1,9 +1,12 @@
import * as React from 'react';
import * as Reactodia from '@reactodia/workspace';
-import { SemanticTypeStyles, makeOntologyLinkTemplates } from '@reactodia/workspace/legacy-styles';
+import {
+ SemanticTypeStyles, makeOntologyLinkTemplates,
+} from '@reactodia/workspace/legacy-styles';
import * as N3 from 'n3';
import { ExampleToolbarMenu } from './ExampleCommon';
+import { ExampleMetadataProvider, ExampleValidationProvider } from './ExampleMetadata';
const OntologyLinkTemplates = makeOntologyLinkTemplates(Reactodia);
const Layouts = Reactodia.defineLayoutWorker(() => new Worker(
@@ -16,6 +19,13 @@ type TurtleDataSource =
export function PlaygroundClassicWorkspace() {
const {defaultLayout} = Reactodia.useWorker(Layouts);
+ const [workspace] = React.useState(() => Reactodia.createWorkspace({
+ defaultLayout,
+ metadataProvider: new ExampleMetadataProvider(),
+ validationProvider: new ExampleValidationProvider(),
+ renameLinkProvider: new RenameSubclassOfProvider(),
+ typeStyleResolver: SemanticTypeStyles,
+ }));
const [dataSource, setDataSource] = React.useState({
type: 'url',
@@ -48,9 +58,8 @@ export function PlaygroundClassicWorkspace() {
}, [dataSource]);
return (
-
+
{
@@ -75,10 +84,19 @@ export function PlaygroundClassicWorkspace() {
)
}}
/>
-
+
);
}
+class RenameSubclassOfProvider extends Reactodia.RenameLinkToLinkStateProvider {
+ override canRename(link: Reactodia.Link): boolean {
+ return (
+ link instanceof Reactodia.AnnotationLink ||
+ link.typeId === 'http://www.w3.org/2000/01/rdf-schema#subClassOf'
+ );
+ }
+}
+
function ToolbarActionOpenTurtleGraph(props: {
onOpen: (dataSource: TurtleDataSource) => void;
}) {
diff --git a/src/examples/PlaygroundGraphAuthoring.tsx b/src/examples/PlaygroundGraphAuthoring.tsx
index 5ec07795..30cbc13e 100644
--- a/src/examples/PlaygroundGraphAuthoring.tsx
+++ b/src/examples/PlaygroundGraphAuthoring.tsx
@@ -3,10 +3,10 @@ import * as Reactodia from '@reactodia/workspace';
import * as Forms from '@reactodia/workspace/forms';
import * as N3 from 'n3';
+import { ExampleToolbarMenu } from './ExampleCommon';
import {
ExampleMetadataProvider, ExampleValidationProvider, rdfs, example,
} from './ExampleMetadata';
-import { ExampleToolbarMenu } from './ExampleCommon';
const Layouts = Reactodia.defineLayoutWorker(() => new Worker(
new URL('@reactodia/workspace/layout.worker', import.meta.url)
@@ -18,6 +18,12 @@ type TurtleDataSource =
export function PlaygroundGraphAuthoring() {
const {defaultLayout} = Reactodia.useWorker(Layouts);
+ const [workspace] = React.useState(() => Reactodia.createWorkspace({
+ defaultLayout,
+ metadataProvider: new ExampleMetadataProvider(),
+ validationProvider: new ExampleValidationProvider(),
+ renameLinkProvider: new RenameSubclassOfProvider(),
+ }));
const [dataSource, setDataSource] = React.useState({
type: 'url',
@@ -73,16 +79,9 @@ export function PlaygroundGraphAuthoring() {
}
}, [dataSource]);
- const [metadataProvider] = React.useState(() => new ExampleMetadataProvider());
- const [validationProvider] = React.useState(() => new ExampleValidationProvider());
- const [renameLinkProvider] = React.useState(() => new RenameSubclassOfProvider());
-
return (
-
+
@@ -119,7 +118,7 @@ export function PlaygroundGraphAuthoring() {
),
}}
/>
-
+
);
}
diff --git a/src/examples/PlaygroundRdfExplorer.tsx b/src/examples/PlaygroundRdfExplorer.tsx
index 38ef7571..cd7f43f0 100644
--- a/src/examples/PlaygroundRdfExplorer.tsx
+++ b/src/examples/PlaygroundRdfExplorer.tsx
@@ -14,6 +14,9 @@ type TurtleDataSource =
export function PlaygroundRdfExplorer() {
const {defaultLayout} = Reactodia.useWorker(Layouts);
+ const [workspace] = React.useState(() => Reactodia.createWorkspace({
+ defaultLayout,
+ }));
const [dataSource, setDataSource] = React.useState({
type: 'url',
@@ -45,8 +48,8 @@ export function PlaygroundRdfExplorer() {
}, [dataSource]);
return (
-
+
@@ -67,7 +70,7 @@ export function PlaygroundRdfExplorer() {
{code: 'zh', label: '汉语'},
]}
/>
-
+
);
}
diff --git a/src/examples/PlaygroundSparql.tsx b/src/examples/PlaygroundSparql.tsx
index 417d0616..b5af371f 100644
--- a/src/examples/PlaygroundSparql.tsx
+++ b/src/examples/PlaygroundSparql.tsx
@@ -13,6 +13,9 @@ const Layouts = Reactodia.defineLayoutWorker(() => new Worker(
export function PlaygroundSparql() {
const {defaultLayout} = Reactodia.useWorker(Layouts);
+ const [workspace] = React.useState(() => Reactodia.createWorkspace({
+ defaultLayout,
+ }));
const [connectionSettings, setConnectionSettings] = React.useState(
(): SparqlConnectionSettings | undefined => {
@@ -49,8 +52,8 @@ export function PlaygroundSparql() {
}, [connectionSettings]);
return (
-
+
}
languages={[
@@ -72,6 +75,6 @@ export function PlaygroundSparql() {
/>
-
+
);
}
diff --git a/src/examples/PlaygroundStressTest.tsx b/src/examples/PlaygroundStressTest.tsx
index 092efb61..b62e4245 100644
--- a/src/examples/PlaygroundStressTest.tsx
+++ b/src/examples/PlaygroundStressTest.tsx
@@ -15,6 +15,9 @@ export function PlaygroundStressTest(props: {
const {nodeCount = 500, edgesPerNode = 2, propertiesPerEdge = 0} = props;
const {defaultLayout} = Reactodia.useWorker(Layouts);
+ const [workspace] = React.useState(() => Reactodia.createWorkspace({
+ defaultLayout,
+ }));
const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
const {model, view} = context;
@@ -54,14 +57,15 @@ export function PlaygroundStressTest(props: {
}, []);
return (
-
+
}
search={null}
navigator={{expanded: false}}
/>
-
+
);
}
diff --git a/src/examples/PlaygroundStyleCustomization.tsx b/src/examples/PlaygroundStyleCustomization.tsx
index b0b094db..07edab2a 100644
--- a/src/examples/PlaygroundStyleCustomization.tsx
+++ b/src/examples/PlaygroundStyleCustomization.tsx
@@ -14,6 +14,22 @@ const Layouts = Reactodia.defineLayoutWorker(() => new Worker(
export function PlaygroundStyleCustomization() {
const {defaultLayout} = Reactodia.useWorker(Layouts);
+ 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;
+ }
+ },
+ }));
const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
const {model} = context;
@@ -40,21 +56,8 @@ export function PlaygroundStyleCustomization() {
}, []);
return (
- {
- 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;
- }
- }}>
+
{
@@ -76,7 +79,7 @@ export function PlaygroundStyleCustomization() {
menu={}>
-
+
);
}
diff --git a/src/examples/PlaygroundWikidata.tsx b/src/examples/PlaygroundWikidata.tsx
index 6c44faa8..b38c0ded 100644
--- a/src/examples/PlaygroundWikidata.tsx
+++ b/src/examples/PlaygroundWikidata.tsx
@@ -9,6 +9,9 @@ const Layouts = Reactodia.defineLayoutWorker(() => new Worker(
export function PlaygroundWikidata() {
const {defaultLayout} = Reactodia.useWorker(Layouts);
+ const [workspace] = React.useState(() => Reactodia.createWorkspace({
+ defaultLayout,
+ }));
const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
const {model, getCommandBus} = context;
@@ -53,8 +56,8 @@ export function PlaygroundWikidata() {
}, []);
return (
-
+
@@ -71,7 +74,7 @@ export function PlaygroundWikidata() {
{ code: 'zh', label: '汉语' },
]}
/>
-
+
);
}
diff --git a/src/tools/GenealogicalTree/GenealogicalMetadataProvider.ts b/src/tools/GenealogicalTree/GenealogicalMetadataProvider.ts
index c3463b63..51f1be37 100644
--- a/src/tools/GenealogicalTree/GenealogicalMetadataProvider.ts
+++ b/src/tools/GenealogicalTree/GenealogicalMetadataProvider.ts
@@ -179,6 +179,7 @@ export class GenealogicalMetadataProvider extends Reactodia.BaseMetadataProvider
});
this.schemaProvider = schemaProvider;
this.defaultNamespaceBase = defaultNamespaceBase;
+ this.loadedSchema.catch(() => {/* Silence initial rejected or cancelled task */});
}
private async getSchema(): Promise {
@@ -186,7 +187,7 @@ export class GenealogicalMetadataProvider extends Reactodia.BaseMetadataProvider
}
loadSchema(params: { signal: AbortSignal }) {
- this.loadedSchema.catch(() => {/* Silence initial rejected or cancelled task */});
+ this.loadedSchema.catch(() => {/* Silence previous load operation */});
this.loadedSchema = loadOwlShaclSchema({
schemaProvider: this.schemaProvider,
signal: params.signal,
diff --git a/src/tools/GenealogicalTree/GenealogicalTree.tsx b/src/tools/GenealogicalTree/GenealogicalTree.tsx
index 158d0280..a929d647 100644
--- a/src/tools/GenealogicalTree/GenealogicalTree.tsx
+++ b/src/tools/GenealogicalTree/GenealogicalTree.tsx
@@ -32,8 +32,6 @@ interface DataSource {
export function ToolGenealogicalTree() {
const {defaultLayout} = Reactodia.useWorker(Layouts);
- const [dataSource, setDataSource] = React.useState();
-
const [schemaProvider] = React.useState(() => {
const provider = new (class extends Reactodia.RdfDataProvider {
override async knownElementTypes(params: { signal?: AbortSignal; }): Promise {
@@ -55,17 +53,45 @@ export function ToolGenealogicalTree() {
}
return provider;
});
- const [metadataProvider] = React.useState(() => new GenealogicalMetadataProvider({
- schemaProvider,
- defaultNamespaceBase: GenealogicalPackage.DEFAULT_NAMESPACE_BASE,
+
+ const [workspace] = React.useState(() => Reactodia.createWorkspace({
+ defaultLayout,
+ translation: new Reactodia.DefaultTranslation({
+ bundles: [
+ {
+ ...enTranslation,
+ 'visual_authoring': {
+ 'property.load_error.text': '⚠ Failed to load variants',
+ },
+ },
+ Reactodia.DefaultTranslationBundle,
+ ],
+ }),
+ metadataProvider: new GenealogicalMetadataProvider({
+ schemaProvider,
+ defaultNamespaceBase: GenealogicalPackage.DEFAULT_NAMESPACE_BASE,
+ }),
+ validationProvider: new GenealogicalValidationProvider(),
+ renameLinkProvider: new RenameGenealogicalLinksProvider(),
+ typeStyleResolver: types => {
+ if (types.includes(schema.Male)) {
+ return {color: '#01baef', icon: PERSON_ICON, iconMonochrome: true};
+ } else if (types.includes(schema.Female)) {
+ return {color: '#f48da7ff', icon: PERSON_ICON, iconMonochrome: true};
+ } else if (types.includes(schema.Person)) {
+ return {color: '#b5d2cb', icon: PERSON_ICON, iconMonochrome: true};
+ }
+ },
}));
- const [validationProvider] = React.useState(() => new GenealogicalValidationProvider());
- const [renameLinkProvider] = React.useState(() => new RenameGenealogicalLinksProvider());
- const {onMount, getContext} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
+ const [dataSource, setDataSource] = React.useState();
+
+ const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
const {model, editor, overlay, translation: t, getCommandBus, performLayout} = context;
+ const metadataProvider = editor.metadataProvider as GenealogicalMetadataProvider;
+ const validationProvider = editor.validationProvider as GenealogicalValidationProvider;
- metadataProvider.loadSchema({signal});
+ metadataProvider.loadSchema({signal});
editor.setAuthoringState(Reactodia.AuthoringState.empty);
editor.setAuthoringMode(true);
@@ -75,43 +101,43 @@ export function ToolGenealogicalTree() {
});
signal.addEventListener('abort', () => listener.stopListening());
- let sourcePackage: GenealogicalPackage;
- if (dataSource) {
- try {
- sourcePackage = await GenealogicalPackage.loadFromBytes(dataSource.bytes, {signal});
- } catch (err) {
- throw new Error(t.text('genealogical_tree.init_failed_to_load_package'), {cause: err});
- }
- } else {
- sourcePackage = GenealogicalPackage.createEmpty();
+ let sourcePackage: GenealogicalPackage;
+ if (dataSource) {
+ try {
+ sourcePackage = await GenealogicalPackage.loadFromBytes(dataSource.bytes, {signal});
+ } catch (err) {
+ throw new Error(t.text('genealogical_tree.init_failed_to_load_package'), {cause: err});
}
+ } else {
+ sourcePackage = GenealogicalPackage.createEmpty();
+ }
- const uploader = new Forms.MemoryFileUploader({
- factory: Reactodia.Rdf.DefaultDataFactory,
- disposeSignal: signal,
- });
- const mainProvider = new GenealogicalDataProvider(sourcePackage, {uploader, signal});
- const dataProvider = new Reactodia.CompositeDataProvider({
- providers: [
- {
- provider: schemaProvider,
- origin: schemaProvider.factory.namedNode(genealogy.SchemaOrigin),
- },
- {
- provider: mainProvider,
- origin: mainProvider.factory.namedNode(genealogy.DataOrigin),
- },
- ],
- });
+ const uploader = new Forms.MemoryFileUploader({
+ factory: Reactodia.Rdf.DefaultDataFactory,
+ disposeSignal: signal,
+ });
+ const mainProvider = new GenealogicalDataProvider(sourcePackage, {uploader, signal});
+ const dataProvider = new Reactodia.CompositeDataProvider({
+ providers: [
+ {
+ provider: schemaProvider,
+ origin: schemaProvider.factory.namedNode(genealogy.SchemaOrigin),
+ },
+ {
+ provider: mainProvider,
+ origin: mainProvider.factory.namedNode(genealogy.DataOrigin),
+ },
+ ],
+ });
- await metadataProvider.loadSettings({mainProvider, signal});
- metadataProvider.updateSettings(editor.authoringState);
- validationProvider.setProvider(mainProvider);
+ await metadataProvider.loadSettings({mainProvider, signal});
+ metadataProvider.updateSettings(editor.authoringState);
+ validationProvider.setProvider(mainProvider);
- const initialSettings = metadataProvider.getSettings();
- const defaultLanguage = termAsString(getSinglePropertyValue(
- initialSettings, genealogy.defaultLanguage
- ));
+ const initialSettings = metadataProvider.getSettings();
+ const defaultLanguage = termAsString(getSinglePropertyValue(
+ initialSettings, genealogy.defaultLanguage
+ ));
if (defaultLanguage) {
model.setLanguage(defaultLanguage);
}
@@ -163,51 +189,31 @@ export function ToolGenealogicalTree() {
}, [dataSource]);
return (
- {
- if (types.includes(schema.Male)) {
- return {color: '#01baef', icon: PERSON_ICON, iconMonochrome: true};
- } else if (types.includes(schema.Female)) {
- return {color: '#f48da7ff', icon: PERSON_ICON, iconMonochrome: true};
- } else if (types.includes(schema.Person)) {
- return {color: '#b5d2cb', icon: PERSON_ICON, iconMonochrome: true};
- }
- }}
- translations={[
- {
- ...enTranslation,
- 'visual_authoring': {
- 'property.load_error.text': '⚠ Failed to load variants',
- },
- }
- ]}>
+
setDataSource({bytes})}
- onSave={async () => {
- const {model, editor} = getContext();
- const mainProvider = findGenealogicalProvider(model.dataProvider);
+ onOpen={bytes => setDataSource({bytes})}
+ onSave={async () => {
+ const {model, editor} = workspace;
+ const mainProvider = findGenealogicalProvider(model.dataProvider);
- // Capture authoring state before finalizing the diagram
- const authoringState = editor.authoringState;
- editor.applyAuthoringChanges();
+ // Capture authoring state before finalizing the diagram
+ const authoringState = editor.authoringState;
+ editor.applyAuthoringChanges();
- const updatedPackage = await mainProvider.sourcePackage.exportWith({
- dataProvider: mainProvider,
- authoringState: mainProvider.cleanupAuthoring(authoringState),
- diagram: model.exportLayout(),
- uploader: mainProvider.uploader,
- });
- setDataSource({bytes: await updatedPackage.bytes()});
- return updatedPackage;
- }}
+ const updatedPackage = await mainProvider.sourcePackage.exportWith({
+ dataProvider: mainProvider,
+ authoringState: mainProvider.cleanupAuthoring(authoringState),
+ diagram: model.exportLayout(),
+ uploader: mainProvider.uploader,
+ });
+ setDataSource({bytes: await updatedPackage.bytes()});
+ return updatedPackage;
+ }}
/>
>
}
@@ -252,7 +258,7 @@ export function ToolGenealogicalTree() {
-
+
);
}