Skip to content

Commit 8ab0ea8

Browse files
authored
Update docs for @reactodia/workspace@0.31.0 (#2)
* Update docs for @reactodia/workspace@0.31.0: * Add GitHub Actions configurations; * Fix CI build
1 parent 03e4a5f commit 8ab0ea8

35 files changed

Lines changed: 689 additions & 205 deletions

.github/workflows/ci-checks.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3+
4+
name: Node.js CI
5+
6+
on:
7+
push:
8+
branches: [ "main" ]
9+
pull_request:
10+
branches: [ "main" ]
11+
12+
jobs:
13+
build:
14+
runs-on: ubuntu-latest
15+
16+
strategy:
17+
matrix:
18+
node-version: [22.x]
19+
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
20+
21+
steps:
22+
- uses: actions/checkout@v4
23+
- name: Checkout @reactodia/workspace
24+
uses: actions/checkout@v4
25+
with:
26+
repository: reactodia/reactodia-workspace
27+
path: ./reactodia-workspace
28+
- name: Use Node.js ${{ matrix.node-version }}
29+
uses: actions/setup-node@v4
30+
with:
31+
node-version: ${{ matrix.node-version }}
32+
cache: 'npm'
33+
- name: Install dependencies
34+
run: npm ci
35+
- name: Install dependencies for library
36+
run: cd ./reactodia-workspace && npm ci
37+
- name: Typecheck
38+
run: npm run typecheck
39+
- name: Build
40+
run: npm run build

.github/workflows/deploy-pages.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3+
4+
name: Deploy to GitHub pages
5+
6+
on:
7+
# Allows you to run this workflow manually from the Actions tab
8+
workflow_dispatch:
9+
10+
jobs:
11+
build:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- name: Setup Node.js
16+
uses: actions/setup-node@v4
17+
with:
18+
node-version: 22.x
19+
cache: 'npm'
20+
- run: npm ci
21+
- run: npm run typecheck
22+
- run: npm run build
23+
- name: Archive build artifacts
24+
uses: actions/upload-pages-artifact@v3
25+
with:
26+
path: build
27+
28+
deploy:
29+
# Add a dependency to the build job
30+
needs: build
31+
32+
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
33+
permissions:
34+
pages: write # to deploy to Pages
35+
id-token: write # to verify the deployment originates from an appropriate source
36+
37+
# Deploy to the github-pages environment
38+
environment:
39+
name: github-pages
40+
url: ${{ steps.deployment.outputs.page_url }}
41+
42+
# Specify runner + deployment step
43+
runs-on: ubuntu-latest
44+
steps:
45+
- name: Deploy to GitHub Pages
46+
id: deployment
47+
uses: actions/deploy-pages@v4 # or specific "vX.X.X" version tag for this action
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
---
2+
title: <AnnotationSupport />
3+
---
4+
5+
# Annotation Support
6+
7+
[`<AnnotationSupport />`](/docs/api/workspace/functions/AnnotationSupport.md) component is a [canvas widget](/docs/components/canvas.md#widgets) to provide UI for the [annotation](/docs/concepts/graph-model.md#annotations) elements and links.
8+
9+
:::important
10+
`<AnnotationSupport />` widget must be provided to the canvas to in order to enable annotation UI features such as creating a new annotation with [`<SelectionActionAnnotate />`](/docs/components/selection.md#selecting-elements), linking annotation to an element with [`<SelectionActionEstablishLink />`](/docs/components/selection.md#selecting-elements) or [`<LinkActionMoveEndpoint />`](/docs/components/selection.md#selecting-links).
11+
:::
12+
13+
The component observes [`AnnotationTopic`](/docs/api/workspace/variables/AnnotationTopic.md) [command bus topic](/docs/concepts/event-system.md#command-bus).
14+
15+
### Example: annotation elements and links
16+
17+
```tsx live noInline
18+
function Example() {
19+
const {defaultLayout} = Reactodia.useWorker(Layouts);
20+
21+
const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
22+
const {model, view, performLayout} = context;
23+
const entity = model.createElement('http://example.com/entity1');
24+
const annotation1 = new Reactodia.AnnotationElement({
25+
elementState: Reactodia.TemplateState.empty
26+
.set(Reactodia.TemplateProperties.AnnotationContent, {
27+
type: 'plaintext',
28+
text: 'Double-click to edit\nnote about "entity1"',
29+
}),
30+
});
31+
const annotation2 = new Reactodia.AnnotationElement({
32+
elementState: Reactodia.TemplateState.empty
33+
.set(Reactodia.TemplateProperties.AnnotationContent, {
34+
type: 'plaintext',
35+
text: 'Note about entity AND annotation',
36+
})
37+
.set(Reactodia.TemplateProperties.ColorVariant, 'primary'),
38+
});
39+
model.addElement(annotation1);
40+
model.addElement(annotation2);
41+
model.addLink(new Reactodia.AnnotationLink({
42+
sourceId: annotation1.id,
43+
targetId: entity.id,
44+
}));
45+
model.addLink(new Reactodia.AnnotationLink({
46+
sourceId: annotation2.id,
47+
targetId: entity.id,
48+
}));
49+
model.addLink(new Reactodia.AnnotationLink({
50+
sourceId: annotation2.id,
51+
targetId: annotation1.id,
52+
linkState: Reactodia.TemplateState.empty
53+
.set(
54+
Reactodia.TemplateProperties.CustomLabel,
55+
'note about another note'
56+
),
57+
}));
58+
await performLayout({signal});
59+
}, []);
60+
61+
return (
62+
<div className='reactodia-live-editor'>
63+
<Reactodia.Workspace ref={onMount}
64+
defaultLayout={defaultLayout}>
65+
<Reactodia.WorkspaceRoot>
66+
<Reactodia.Canvas>
67+
<Reactodia.Halo />
68+
<Reactodia.HaloLink />
69+
<Reactodia.Selection />
70+
<Reactodia.AnnotationSupport />
71+
</Reactodia.Canvas>
72+
</Reactodia.WorkspaceRoot>
73+
</Reactodia.Workspace>
74+
</div>
75+
);
76+
}
77+
78+
render(<Example />);
79+
```

docs/components/canvas.md

Lines changed: 157 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ The library provides the following built-in templates:
2323
| [`StandardTemplate`](/docs/api/workspace/variables/StandardTemplate.md) | element | Default (fallback) template for an element; supports single entity elements and [entity groups](/docs/concepts/graph-model.md#data-graph).<br />Uses [`StandardEntity`](/docs/api/workspace/functions/StandardEntity.md) and [`StandardEntityGroup`](/docs/api/workspace/functions/StandardEntityGroup.md) components to render elements. |
2424
| [`ClassicTemplate`](/docs/api/workspace/variables/ClassicTemplate.md) | element | Element template component with classic "look and feel" which was used for elements before v0.8; does not support entity groups.<br />Uses [`ClassicEntity`](/docs/api/workspace/functions/ClassicEntity.md) component to render elements. |
2525
| [`RoundTemplate`](/docs/api/workspace/variables/RoundTemplate.md) | element | Basic element template with an round (elliptical) shape; does not support entity groups.<br />Uses [`RoundEntity`](/docs/api/workspace/functions/RoundEntity.md) component to render elements. |
26-
| [`DefaultLinkTemplate`](/docs/api/workspace/variables/DefaultLinkTemplate.md) | link | Default (fallback) template for a link; supports single relation links and [relation groups](/docs/concepts/graph-model.md#data-graph).<br />Uses [`DefaultLink`](/docs/api/workspace/functions/DefaultLink.md) component to render links which uses [`LinkPath`](/docs/api/workspace/functions/LinkPath.md), [`LinkLabel`](/docs/api/workspace/functions/LinkLabel.md) and [`LinkVertices`](/docs/api/workspace/functions/LinkVertices.md) components inside to display the link connection itself, the labels and vertices (to change link geometry). |
26+
| [`StandardLinkTemplate`](/docs/api/workspace/variables/StandardLinkTemplate.md) | link | Default (fallback) template for a link; supports single relation links and [relation groups](/docs/concepts/graph-model.md#data-graph).<br />Uses [`StandardRelation`](/docs/api/workspace/functions/StandardRelation.md) component to render links which uses [`LinkPath`](/docs/api/workspace/functions/LinkPath.md), [`LinkLabel`](/docs/api/workspace/functions/LinkLabel.md) and [`LinkVertices`](/docs/api/workspace/functions/LinkVertices.md) components inside to display the link connection itself, the labels and vertices (to change link geometry). |
27+
| [`NoteTemplate`](/docs/api/workspace/variables/NoteTemplate.md) | element | Default template for [annotation elements](/docs/concepts/graph-model.md#annotations).<br />Uses [`NoteAnnotation`](/docs/api/workspace/functions/NoteAnnotation.md) component to render a user-resizable note with editable text content and style. |
28+
| [`NoteLinkTemplate`](/docs/api/workspace/variables/NoteLinkTemplate.md) | link | Default template for [annotation links](/docs/concepts/graph-model.md#annotations).<br />Uses [`NoteLink`](/docs/api/workspace/functions/NoteLink.md) component to render a [basic link](/docs/api/workspace/functions/BasicLink.md) with an optional label if the link was [renamed](/docs/api/workspace/interfaces/RenameLinkProvider.md). |
2729

2830
Additionally, it is possible to override how the link are routed (how default path geometry is computed) on the canvas by providing `linkRouter` ([`LinkRouter`](/docs/api/workspace/interfaces/LinkRouter.md)) prop to the `<Canvas />`. By default, the [`DefaultLinkRouter`](/docs/api/workspace/classes/DefaultLinkRouter.md) is used which moves apart multiple links between same elements and displays self-links (where target is equal to source) as loops.
2931

@@ -81,25 +83,63 @@ function NonWidgetComponent {
8183
}
8284
```
8385

84-
## Canvas widgets
86+
## Widgets
8587

86-
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.
88+
Canvas widget is any React component placed as a child to [`<Canvas />`](/docs/api/workspace/functions/Canvas).
8789

8890
There are multiple canvas layers to place widgets on, from top one to the bottom:
8991

90-
| Layer name | [Coordinate type](/docs/concepts/canvas-coordinates.md) | Description |
91-
|----------------|-------------------|-------------|
92-
| `viewport` | client (viewport) | Topmost layer, does not scale or scroll with the diagram. |
93-
| `overElements` | paper | Displayed over both elements and links, scales and scrolls with the diagram. |
94-
| `overLinks` | paper | Displayed under elements but over links, scales and scrolls with the diagram. |
92+
| Layer name | [Coordinate type](/docs/concepts/canvas-coordinates.md) | Is scaled | Is scrolled | Description |
93+
|--------------------|-------------------|---|---|-------------|
94+
| `viewport` | client (viewport) ||| Top layer, placed over all diagram content and other layers. |
95+
| `overElements` | scrollable pane ||| Placed over both elements and links. |
96+
| `overLinks` | scrollable pane ||| Placed under elements but over links (including its geometry and labels). |
97+
| `overLinkGeometry` | scrollable pane ||| Placed under link labels but over link geometry (paths). |
98+
| `underlay` | scrollable pane ||| Bottom layer, placed under all diagram content and other layers. |
9599

96-
### Example: custom viewport widget
100+
By default, every child component (widget) is placed at `viewport` layer over canvas viewport. However, [`<CanvasPlaceAt />`](/docs/api/workspace/functions/CanvasPlaceAt.md) can be used to display its children at a different layer:
101+
102+
```tsx
103+
function MyWidget() {
104+
return (
105+
<>
106+
<div>
107+
{/* This will be displayer over viewport */}
108+
</div>
109+
<Reactodia.CanvasPlaceAt layer='overLinkGeometry'>
110+
{/* ... render additonal link decorations ... */}
111+
</Reactodia.CanvasPlaceAt>
112+
<Reactodia.CanvasPlaceAt layer='underlay'>
113+
{/* ... render additional background content */}
114+
</Reactodia.CanvasPlaceAt>
115+
</>
116+
);
117+
}
118+
119+
...
120+
return (
121+
<Reactodia.Workspace ref={onMount}
122+
defaultLayout={defaultLayout}>
123+
<Reactodia.WorkspaceRoot>
124+
<Reactodia.Canvas>
125+
<MyWidget />
126+
</Reactodia.Canvas>
127+
</Reactodia.WorkspaceRoot>
128+
</Reactodia.Workspace>
129+
);
130+
```
131+
132+
:::warning
133+
`<CanvasPlaceAt />` cannot be nested into itself, otherwise an error will be thrown.
134+
:::
135+
136+
### Example: simple viewport widget
97137

98138
```tsx live noInline
99-
function CustomSelectAllWidget() {
139+
function SelectAllButton() {
100140
const {model} = Reactodia.useWorkspace();
101141
return (
102-
<Reactodia.ViewportDock dock='ne'>
142+
<Reactodia.ViewportDock dock='n'>
103143
<button type='button'
104144
className='reactodia-btn reactodia-btn-default'
105145
onClick={() => model.setSelection([...model.elements])}>
@@ -109,10 +149,109 @@ function CustomSelectAllWidget() {
109149
);
110150
}
111151

112-
Reactodia.defineCanvasWidget(
113-
CustomSelectAllWidget,
114-
element => ({element, attachment: 'viewport'})
115-
);
152+
function Example() {
153+
const {defaultLayout} = Reactodia.useWorker(Layouts);
154+
155+
const {onMount} = Reactodia.useLoadedWorkspace(async ({context, signal}) => {
156+
const {model, view, performLayout} = context;
157+
model.createElement('http://example.com/entity1');
158+
model.createElement('http://example.com/entity2');
159+
model.createLinks({
160+
sourceId: 'http://example.com/entity1',
161+
targetId: 'http://example.com/entity2',
162+
linkTypeId: 'http://example.com/connectedTo',
163+
properties: {},
164+
});
165+
await performLayout({signal});
166+
}, []);
167+
168+
return (
169+
<div className='reactodia-live-editor'>
170+
<Reactodia.Workspace ref={onMount}
171+
defaultLayout={defaultLayout}>
172+
<Reactodia.WorkspaceRoot>
173+
<Reactodia.Canvas>
174+
<Reactodia.Halo />
175+
<Reactodia.HaloLink />
176+
<Reactodia.Selection />
177+
<SelectAllButton />
178+
</Reactodia.Canvas>
179+
</Reactodia.WorkspaceRoot>
180+
</Reactodia.Workspace>
181+
</div>
182+
);
183+
}
184+
185+
render(<Example />);
186+
```
187+
188+
### Example: placing components at [canvas coordinates](/docs/concepts/canvas-coordinates.md)
189+
190+
When placing a component at non-`viewport` layer, it is usually neccessary to position it based on other [diagram cells](/docs/concepts/graph-model.md). In that cases the widget needs to subscribe to canvas content and transform changes (including canvas total size, scale and origin point).
191+
192+
```tsx live noInline
193+
function OverlayAndUnderlay() {
194+
const {canvas, model} = Reactodia.useCanvas();
195+
196+
// Update on canvas transform changes (change scale, size or origin)
197+
Reactodia.useSyncStore(
198+
Reactodia.useLayerDebouncedStore(
199+
Reactodia.useEventStore(canvas.events, 'changeTransform'),
200+
canvas.renderingState
201+
),
202+
() => canvas.metrics.getTransform()
203+
);
204+
205+
// Track average of element centers
206+
const p = Reactodia.useSyncStoreWithComparator(
207+
Reactodia.useLayerDebouncedStore(
208+
useAllElementBoundsStore(),
209+
canvas.renderingState
210+
),
211+
() => Reactodia.calculateAveragePosition(
212+
model.elements,
213+
canvas.renderingState
214+
),
215+
(a, b) => Reactodia.Vector.equals(a, b)
216+
);
217+
218+
const {x, y} = canvas.metrics.paperToScrollablePaneCoords(p.x, p.y);
219+
220+
const style: React.CSSProperties = {
221+
position: 'absolute',
222+
left: x - 50,
223+
top: y - 50,
224+
width: 100,
225+
height: 100,
226+
pointerEvents: 'none',
227+
};
228+
229+
return (
230+
<>
231+
<Reactodia.CanvasPlaceAt layer='underlay'>
232+
<div style={{...style, background: 'cornflowerblue'}} />
233+
</Reactodia.CanvasPlaceAt>
234+
<Reactodia.CanvasPlaceAt layer='overElements'>
235+
<div style={{...style, border: '2px dashed violet'}} />
236+
</Reactodia.CanvasPlaceAt>
237+
</>
238+
);
239+
}
240+
241+
function useAllElementBoundsStore(): Reactodia.SyncStore {
242+
const {canvas, model} = Reactodia.useCanvas();
243+
return React.useCallback<Reactodia.SyncStore>(onChange => {
244+
const listener = new Reactodia.EventObserver();
245+
listener.listen(model.events, 'changeCells', onChange);
246+
listener.listen(model.events, 'elementEvent', ({data}) => {
247+
if (data.changePosition) {
248+
onChange();
249+
}
250+
});
251+
listener.listen(canvas.renderingState.events, 'changeElementSize', onChange);
252+
return () => listener.stopListening();
253+
}, [model.events, canvas.renderingState.events]);
254+
}
116255

117256
function Example() {
118257
const {defaultLayout} = Reactodia.useWorker(Layouts);
@@ -135,9 +274,9 @@ function Example() {
135274
<Reactodia.Workspace ref={onMount}
136275
defaultLayout={defaultLayout}>
137276
<Reactodia.DefaultWorkspace
138-
search={null}
139-
canvasWidgets={[<CustomSelectAllWidget key='select-all' />]}
140-
/>
277+
search={null}>
278+
<OverlayAndUnderlay />
279+
</Reactodia.DefaultWorkspace>
141280
</Reactodia.Workspace>
142281
</div>
143282
);

0 commit comments

Comments
 (0)