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
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,28 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

## [Unreleased]
#### 🚀 New Features
- Simplify canvas widgets placement at one or multiple layers:
* Canvas children are always assumed to be viewport widgets;
* Add `CanvasPlaceAt` component to render its children at specified non-viewport canvas layer instead;
* Support new placement layers: `underlay` layer to place components under all canvas content, `overLinkGeometry` layer to place components above link geometry (connections) but under link labels;
* **[💥Breaking]** Remove `defineCanvasWidget()` and `SharedCanvasState.setCanvasWidget()` (use `CanvasPlaceAt` to display components at canvas layers instead).
- Add `EditorController.applyAuthoringChanges()` method to apply current authoring changes to the diagram (i.e. change entity data, delete relations, etc) and reset the change state to be empty.

#### ⏱ Performance
- **[💥Breaking]** Canvas widgets are not automatically updated when parent canvas is rendered to reduce unnecessary re-renders, and now require explicit subscriptions:
* Subscribe to canvas `changeTransform` event when using `CanvasApi.metrics` to convert between coordinates;
* Subscribe to canvas `resize` event to track viewport size;
* Subscribe to `changeCells` event from `DiagramModel` to track graph content changes.

#### 💅 Polish
- Make dialogs fill the available viewport when the viewport width is small:
* This is controlled by new CSS property `--reactodia-dialog-viewport-breakpoint-s` with default value `600px` which makes dialog fill the viewport if the available width is less or equal to that value.
- Allow to override base z-index level for workspace components with a set z-index value via `--reactodia-z-index-base` CSS property;
- Add `changeTransform` event to `CanvasApi.events` which triggers on `CanvasApi.metrics.getTransform()` changes, i.e. when coordinate mapping changes due to scale or canvas size is re-adjusted.
- Deprecate `canvasWidgets` prop on `DefaultWorkspace` and `ClassicWorkspace` in favor of passing widgets directly as children.

#### 🐛 Fixed
- Fix `HaloLink` and visual authoring link path highlight being rendered on top on elements by placing it onto `overLinkGeometry` widget layer instead.

## [0.30.1] - 2025-06-27
#### 🐛 Fixed
Expand Down
19 changes: 8 additions & 11 deletions examples/sparql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,6 @@ function SparqlExample() {
defaultLayout={defaultLayout}>
<Reactodia.DefaultWorkspace
menu={<ExampleToolbarMenu />}
canvasWidgets={[
<Reactodia.Toolbar key='sparql-settings'
dock='sw'
dockOffsetY={40}>
<SparqlConnectionAction settings={connectionSettings}
applySettings={applyConnectionSettings}
/>
</Reactodia.Toolbar>
]}
languages={[
{code: 'de', label: 'Deutsch'},
{code: 'en', label: 'English'},
Expand All @@ -82,8 +73,14 @@ function SparqlExample() {
{code: 'pt', label: 'português'},
{code: 'ru', label: 'Русский'},
{code: 'zh', label: '汉语'},
]}
/>
]}>
<Reactodia.Toolbar dock='sw'
dockOffsetY={40}>
<SparqlConnectionAction settings={connectionSettings}
applySettings={applyConnectionSettings}
/>
</Reactodia.Toolbar>
</Reactodia.DefaultWorkspace>
</Reactodia.Workspace>
);
}
Expand Down
18 changes: 11 additions & 7 deletions examples/styleCustomization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,24 +61,28 @@ function StyleCustomizationExample() {
},
linkTemplateResolver: type => DoubleArrowLinkTemplate,
}}
canvasWidgets={[
<BookDecorations key='book-decorations' />
]}
menu={<ExampleToolbarMenu />}
/>
menu={<ExampleToolbarMenu />}>
<BookDecorations />
</Reactodia.DefaultWorkspace>
</Reactodia.Workspace>
);
}

function BookDecorations() {
const {model} = Reactodia.useCanvas();

const [, forceUpdate] = React.useState({});
React.useEffect(() => {
const listener = new Reactodia.EventObserver();
listener.listen(model.events, 'changeCells', () => forceUpdate({}));
return () => listener.stopListening();
});

return model.elements
.filter(element => element instanceof Reactodia.EntityElement)
.map(element => <BookDecoration key={element.id} target={element} />);
}

Reactodia.defineCanvasWidget(BookDecorations, element => ({element, attachment: 'viewport'}));

function BookDecoration(props: { target: Reactodia.EntityElement }) {
const {target} = props;

Expand Down
29 changes: 4 additions & 25 deletions src/diagram/canvasApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ export interface CanvasEvents {
* Triggered on {@link CanvasApi.getScale} property change.
*/
changeScale: PropertyChange<CanvasApi, number>;
/**
* Triggered on {@link CanvasApi.metrics.getTransform()} property change.
*/
changeTransform: PropertyChange<CanvasApi, PaperTransform>;
}

/**
Expand Down Expand Up @@ -551,31 +555,6 @@ export interface ExportSvgOptions {
*/
export interface ExportRasterOptions extends ExportSvgOptions, ToDataURLOptions {}

/**
* Canvas widget layer to render widget:
* - `viewport` - topmost layer, uses client (viewport) coordinates and
* does not scale or scroll with the diagram;
* - `overElements` - displayed over both elements and links, uses paper coordinates,
* scales and scrolls with the diagram;
* - `overLinks` - displayed under elements but over links, uses paper coordinates,
* scales and scrolls with the diagram.
*/
export type CanvasWidgetAttachment = 'viewport' | 'overElements' | 'overLinks';

/**
* Describes canvas widget element to render on the specific widget layer.
*/
export interface CanvasWidgetDescription {
/**
* Canvas widget element to render.
*/
element: React.ReactElement;
/**
* Canvas widget layer to render widget on.
*/
attachment: CanvasWidgetAttachment;
}

/**
* Represents a context for everything rendered inside the canvas,
* including diagram content and widgets.
Expand Down
47 changes: 1 addition & 46 deletions src/diagram/canvasWidget.tsx → src/diagram/canvasHotkey.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,9 @@ import * as React from 'react';

import { HotkeyAst, type HotkeyString, parseHotkey, formatHotkey } from '../coreUtils/hotkey';

import { type CanvasWidgetDescription, useCanvas } from './canvasApi';
import { useCanvas } from './canvasApi';
import { type MutableRenderingState } from './renderingState';

const GET_WIDGET_METADATA: unique symbol = Symbol('getWidgetMetadata');

interface WithMetadata {
[GET_WIDGET_METADATA]?: (element: React.ReactElement) => CanvasWidgetDescription;
}

/**
* Defines the React component to be a canvas widget.
*
* A component cannot be rendered by canvas as widget unless explicitly
* defined as such using this function.
*
* **Example**:
* ```jsx
* function MyWidget(props) {
* ...
* }
*
* defineCanvasWidget(MyWidget, element => ({
* element,
* attachment: 'viewport'
* }));
* ```
*
* @category Core
*/
export function defineCanvasWidget<P>(
type: React.ComponentType<P>,
metadataOf: (element: React.ReactElement<P>) => CanvasWidgetDescription
): void {
const typeWithMetadata = type as WithMetadata;
typeWithMetadata[GET_WIDGET_METADATA] = metadataOf as WithMetadata[typeof GET_WIDGET_METADATA];
}

export function extractCanvasWidget(
element: React.ReactElement
): CanvasWidgetDescription | undefined {
const typeWithMetadata = element.type as WithMetadata;
const metadataOf = typeWithMetadata[GET_WIDGET_METADATA];
if (metadataOf) {
return metadataOf(element);
}
return undefined;
}

/**
* Represents a registered canvas hotkey.
*
Expand Down
Loading
Loading