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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to the Reactodia will be documented in this document.
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
#### 🚀 New Features
- 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.

#### 💅 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.

## [0.30.1] - 2025-06-27
#### 🐛 Fixed
Expand Down
3 changes: 3 additions & 0 deletions examples/resources/template.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
:root[data-border-radius-zero] .reactodia-workspace {
--reactodia-border-radius-base: unset;
}
.reactodia-paper-area {
outline: none;
}
</style>
<body>
<select id="reactodia-example-selector"
Expand Down
65 changes: 60 additions & 5 deletions src/editor/overlayController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ export class OverlayController {
private readonly view: SharedCanvasState;
private readonly translation: Translation;

private readonly overlays = new Set<OverlayReader>();

private _openedDialog: OpenedDialog | undefined;
private _tasks = new Set<ExtendedOverlayTask>();
private _taskError: { error: unknown } | undefined;
Expand All @@ -107,6 +109,7 @@ export class OverlayController {
view.setCanvasWidget('selectionHandler', {
element: (
<CanvasOverlayHandler
overlays={this.overlays}
onCanvasPointerUp={this.onAnyCanvasPointerUp}
onCanvasKeydown={this.onAnyCanvasKeydown}
/>
Expand Down Expand Up @@ -324,8 +327,14 @@ export class OverlayController {
onClose,
};

const canvas = this.view.findAnyCanvas();
const breakpoint = readOverlayProperty(this.overlays, reader => reader.getDialogViewportBreakpoint());
const isSmallViewport = Boolean(
canvas && breakpoint !== undefined && canvas.metrics.area.clientWidth <= breakpoint
);

const onHide = () => this.hideDialog();
if (target) {
if (target && !isSmallViewport) {
this.view.setCanvasWidget('dialog', {
element: (
<Dialog {...style}
Expand All @@ -338,12 +347,13 @@ export class OverlayController {
{content}
</Dialog>
),
attachment: 'overElements'
attachment: 'overElements',
});
} else {
this.view.setCanvasWidget('dialog', {
element: (
<ViewportDialog {...style}
mode={isSmallViewport ? 'fillViewport' : 'centered'}
onHide={onHide}>
{content}
</ViewportDialog>
Expand Down Expand Up @@ -446,7 +456,6 @@ function ViewportDialog(props: DialogProps) {
className='reactodia-viewport-dialog-overlay'>
<Dialog {...props}
dock='e'
centered={true}
maxSize={maxSize}
/>
</div>
Expand All @@ -469,19 +478,65 @@ function useViewportSize() {
return size;
}

interface OverlayReader {
getDialogViewportBreakpoint(): number | undefined;
}

function CanvasOverlayHandler(props: {
overlays: Set<OverlayReader>;
onCanvasPointerUp: (event: CanvasPointerUpEvent) => void;
onCanvasKeydown: (event: CanvasKeyboardEvent) => void;
}) {
const {onCanvasPointerUp, onCanvasKeydown} = props;
const {overlays, onCanvasPointerUp, onCanvasKeydown} = props;
const {canvas} = useCanvas();

const ref = React.useRef<HTMLDivElement>(null);
React.useLayoutEffect(() => {
const overlay = ref.current;
if (overlay) {
const reader: OverlayReader = {
getDialogViewportBreakpoint: () => {
const style = getComputedStyle(overlay);
// Use parseFloat to ignore trailing "px" unit at the end
return style.maxWidth ? parseFloat(style.maxWidth) : undefined;
},
};
overlays.add(reader);
return () => {
overlays.delete(reader);
};
}
}, [overlays]);

React.useEffect(() => {
const listener = new EventObserver();
listener.listen(canvas.events, 'pointerUp', onCanvasPointerUp);
listener.listen(canvas.events, 'keydown', onCanvasKeydown);
return () => listener.stopListening();
}, [onCanvasPointerUp, onCanvasKeydown]);
return null;

return (
<div ref={ref}
style={{
display: 'none',
// Use non-custom property to resolve `calc()` and different CSS units
maxWidth: 'var(--reactodia-dialog-viewport-breakpoint-s)',
}}
/>
);
}

function readOverlayProperty<T>(
readers: Iterable<OverlayReader>,
getProperty: (reader: OverlayReader) => T | undefined
): T | undefined {
for (const reader of readers) {
const value = getProperty(reader);
if (value !== undefined) {
return value;
}
}
return undefined;
}

function getErrorMessage(error: unknown): string | undefined {
Expand Down
17 changes: 12 additions & 5 deletions src/widgets/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { DraggableHandle } from './utility/draggableHandle';
export interface DialogProps extends DialogStyleProps {
target?: DialogTarget;
onHide: () => void;
centered?: boolean;
mode?: 'centered' | 'fillViewport';
children: React.ReactNode;
}

Expand Down Expand Up @@ -263,7 +263,8 @@ export class Dialog extends React.Component<DialogProps, State> {
}

private onDragHandle = (e: MouseEvent, dx: number, dy: number) => {
const {dock = DEFAULT_DOCK, centered} = this.props;
const {dock = DEFAULT_DOCK, mode} = this.props;
const centered = mode === 'centered';
let factorX = centered ? 2 : 1;
let factorY = centered ? 2 : 1;
switch (dock) {
Expand All @@ -286,12 +287,14 @@ export class Dialog extends React.Component<DialogProps, State> {

render() {
const {
mode,
dock = DEFAULT_DOCK,
caption,
resizableBy = 'all',
resizableBy: baseResizableBy = 'all',
closable = true,
} = this.props;

const resizableBy = mode === 'fillViewport' ? 'none' : baseResizableBy;
const size = this.getCurrentSize();
const position = this.calculatePosition(size);
const style: React.CSSProperties = {
Expand All @@ -302,10 +305,14 @@ export class Dialog extends React.Component<DialogProps, State> {
};

return (
<div className={CLASS_NAME}
<div
className={cx(
CLASS_NAME,
mode === 'fillViewport' ? 'reactodia-dialog--fill-viewport' : undefined
)}
role='dialog'
aria-labelledby={caption ? 'reactodia-dialog-caption' : undefined}
style={style}>
style={mode === 'fillViewport' ? undefined : style}>
<div className={`${CLASS_NAME}__header`}>
<div id='reactodia-dialog-caption'
className={`${CLASS_NAME}__caption`}
Expand Down
1 change: 1 addition & 0 deletions styles/theme/_common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
--reactodia-dialog-border-color: var(--reactodia-border-color-base);
--reactodia-dialog-border-radius: var(--reactodia-border-radius-base);
--reactodia-dialog-border-width: var(--reactodia-border-width-base);
--reactodia-dialog-viewport-breakpoint-s: 600px;

--reactodia-canvas-background-color: var(--reactodia-background-color);
--reactodia-viewport-dock-margin: 10px;
Expand Down
1 change: 1 addition & 0 deletions styles/theme/_theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ $tree-border-color-focus: var(--reactodia-tree-border-color-focus);
$dialog-border-color: var(--reactodia-dialog-border-color);
$dialog-border-radius: var(--reactodia-dialog-border-radius);
$dialog-border-width: var(--reactodia-dialog-border-width);
$dialog-viewport-breakpoint-s: var(--reactodia-dialog-viewport-breakpoint-s);

/* Canvas */
$canvas-background-color: var(--reactodia-canvas-background-color);
Expand Down
10 changes: 10 additions & 0 deletions styles/widgets/_dialog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@
display: flex;
flex-direction: column;

&--fill-viewport {
top: 0;
left: 0;
bottom: 0;
right: 0;

border: none;
border-radius: none;
}

&__header {
display: flex;
}
Expand Down
2 changes: 1 addition & 1 deletion webpackServe.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,5 @@ app.use(createProxyMiddleware({
}));

// eslint-disable-next-line no-console
console.log(`Running Webpack server at host: "${SERVE_HOST}", port: ${SERVE_PORT}`);
console.log(`Running Webpack server at http://${SERVE_HOST}:${SERVE_PORT}`);
const server = app.listen(SERVE_PORT, SERVE_HOST);
Loading