Skip to content

Commit

Permalink
chore(Canvas): Improve look and feel
Browse files Browse the repository at this point in the history
This commit removes the usage of `DefaultNode` and replaces it with a
custom implementation.

It makes the nodes wider, and removes the border to make more room for
the text.

Temp: Add img border, make icon bigger and restore shadow

Temp: Highlight only the image and add proper borders

Temp: Extract context menu logic to hooks

Temp: Add toolbar menu on hover

Temp: AddStep and AddStepSpecial

Temp: Make the toolbar fixed when the node is selected
  • Loading branch information
lordrip committed Oct 22, 2024
1 parent 2cd46db commit e4f945c
Show file tree
Hide file tree
Showing 21 changed files with 524 additions and 226 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { LayoutType } from './canvas.models';
export class CanvasDefaults {
static readonly DEFAULT_LAYOUT = LayoutType.DagreVertical;
static readonly DEFAULT_NODE_SHAPE = NodeShape.rect;
static readonly DEFAULT_NODE_DIAMETER = 75;
static readonly DEFAULT_NODE_WIDTH = 150;
static readonly DEFAULT_NODE_HEIGHT = 85;
static readonly DEFAULT_GROUP_PADDING = 65;
static readonly DEFAULT_SIDEBAR_WIDTH = 500;
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ export class FlowService {
type: 'node',
parentNode: options.parentNode,
data: options.data,
width: CanvasDefaults.DEFAULT_NODE_DIAMETER,
height: CanvasDefaults.DEFAULT_NODE_DIAMETER,
width: CanvasDefaults.DEFAULT_NODE_WIDTH,
height: CanvasDefaults.DEFAULT_NODE_HEIGHT,
shape: CanvasDefaults.DEFAULT_NODE_SHAPE,
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,19 @@
import { TrashIcon } from '@patternfly/react-icons';
import { ContextMenuItem } from '@patternfly/react-topology';
import { FunctionComponent, PropsWithChildren, useCallback, useContext } from 'react';
import { FunctionComponent, PropsWithChildren } from 'react';
import { IDataTestID } from '../../../../models';
import { IVisualizationNode } from '../../../../models/visualization/base-visual-entity';
import {
ACTION_ID_CONFIRM,
ActionConfirmationModalContext,
} from '../../../../providers/action-confirmation-modal.provider';
import { EntitiesContext } from '../../../../providers/entities.provider';
import { useDeleteGroup } from '../hooks/delete-group.hook';

interface ItemDeleteGroupProps extends PropsWithChildren<IDataTestID> {
vizNode: IVisualizationNode;
}

export const ItemDeleteGroup: FunctionComponent<ItemDeleteGroupProps> = (props) => {
const entitiesContext = useContext(EntitiesContext);
const deleteModalContext = useContext(ActionConfirmationModalContext);
const flowId = props.vizNode?.getId();

const onRemoveGroup = useCallback(async () => {
/** Open delete confirm modal, get the confirmation */
const isDeleteConfirmed = await deleteModalContext?.actionConfirmation({
title: 'Permanently delete flow?',
text: 'All steps will be lost.',
});

if (isDeleteConfirmed !== ACTION_ID_CONFIRM) return;

entitiesContext?.camelResource.removeEntity(flowId);
entitiesContext?.updateEntitiesFromCamelResource();
}, [deleteModalContext, entitiesContext, flowId]);
const { onDeleteGroup } = useDeleteGroup(props.vizNode);

return (
<ContextMenuItem onClick={onRemoveGroup} data-testid={props['data-testid']}>
<ContextMenuItem onClick={onDeleteGroup} data-testid={props['data-testid']}>
<TrashIcon /> Delete
</ContextMenuItem>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,19 @@
import { TrashIcon } from '@patternfly/react-icons';
import { ContextMenuItem } from '@patternfly/react-topology';
import { FunctionComponent, PropsWithChildren, useCallback, useContext } from 'react';
import { FunctionComponent, PropsWithChildren } from 'react';
import { IDataTestID } from '../../../../models';
import { IVisualizationNode } from '../../../../models/visualization/base-visual-entity';
import { EntitiesContext } from '../../../../providers/entities.provider';
import {
ACTION_ID_CONFIRM,
ActionConfirmationModalContext,
} from '../../../../providers/action-confirmation-modal.provider';
import { useDeleteStep } from '../hooks/delete-step.hook';

interface ItemDeleteStepProps extends PropsWithChildren<IDataTestID> {
vizNode: IVisualizationNode;
loadActionConfirmationModal: boolean;
}

export const ItemDeleteStep: FunctionComponent<ItemDeleteStepProps> = (props) => {
const entitiesContext = useContext(EntitiesContext);
const deleteModalContext = useContext(ActionConfirmationModalContext);

const onRemoveNode = useCallback(async () => {
if (props.loadActionConfirmationModal) {
/** Open delete confirm modal, get the confirmation */
const isDeleteConfirmed = await deleteModalContext?.actionConfirmation({
title: 'Permanently delete step?',
text: 'Step and its children will be lost.',
});

if (isDeleteConfirmed !== ACTION_ID_CONFIRM) return;
}

props.vizNode?.removeChild();
entitiesContext?.updateEntitiesFromCamelResource();
}, [deleteModalContext, entitiesContext, props.loadActionConfirmationModal, props.vizNode]);
const { onDeleteStep } = useDeleteStep(props.vizNode);

return (
<ContextMenuItem onClick={onRemoveNode} data-testid={props['data-testid']}>
<ContextMenuItem onClick={onDeleteStep} data-testid={props['data-testid']}>
<TrashIcon /> Delete
</ContextMenuItem>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,16 @@
import { BanIcon, CheckIcon } from '@patternfly/react-icons';
import { ContextMenuItem } from '@patternfly/react-topology';
import { FunctionComponent, PropsWithChildren, useCallback, useContext } from 'react';
import { FunctionComponent, PropsWithChildren } from 'react';
import { IDataTestID } from '../../../../models';
import { IVisualizationNode } from '../../../../models/visualization/base-visual-entity';
import { EntitiesContext } from '../../../../providers/entities.provider';
import { setValue } from '../../../../utils/set-value';
import { useDisableStep } from '../hooks/disable-step.hook';

interface ItemDisableStepProps extends PropsWithChildren<IDataTestID> {
vizNode: IVisualizationNode;
}

export const ItemDisableStep: FunctionComponent<ItemDisableStepProps> = (props) => {
const entitiesContext = useContext(EntitiesContext);
const isDisabled = !!props.vizNode.getComponentSchema()?.definition?.disabled;

const onToggleDisableNode = useCallback(() => {
const newModel = props.vizNode.getComponentSchema()?.definition || {};
setValue(newModel, 'disabled', !isDisabled);
props.vizNode.updateModel(newModel);

entitiesContext?.updateEntitiesFromCamelResource();
}, [entitiesContext, isDisabled, props.vizNode]);
const { onToggleDisableNode, isDisabled } = useDisableStep(props.vizNode);

return (
<ContextMenuItem onClick={onToggleDisableNode} data-testid={props['data-testid']}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,19 @@
import { ContextMenuItem } from '@patternfly/react-topology';
import { FunctionComponent, PropsWithChildren, useCallback, useContext } from 'react';
import { FunctionComponent, PropsWithChildren } from 'react';
import { IDataTestID } from '../../../../models';
import { AddStepMode, IVisualizationNode } from '../../../../models/visualization/base-visual-entity';
import { CatalogModalContext } from '../../../../providers/catalog-modal.provider';
import { EntitiesContext } from '../../../../providers/entities.provider';
import { useInsertStep } from '../hooks/insert-step.hook';

interface ItemInsertStepProps extends PropsWithChildren<IDataTestID> {
mode: AddStepMode.InsertChildStep | AddStepMode.InsertSpecialChildStep;
vizNode: IVisualizationNode;
}

export const ItemInsertStep: FunctionComponent<ItemInsertStepProps> = (props) => {
const entitiesContext = useContext(EntitiesContext);
const catalogModalContext = useContext(CatalogModalContext);

const onInsertNode = useCallback(async () => {
if (!props.vizNode || !entitiesContext) return;

/** Get compatible nodes and the location where can be introduced */
const compatibleNodes = entitiesContext.camelResource.getCompatibleComponents(
props.mode,
props.vizNode.data,
props.vizNode.getComponentSchema()?.definition,
);

/** Open Catalog modal, filtering the compatible nodes */
const definedComponent = await catalogModalContext?.getNewComponent(compatibleNodes);
if (!definedComponent) return;
const targetProperty = props.mode === AddStepMode.InsertChildStep ? 'steps' : undefined;

/** Add new node to the entities */
props.vizNode.addBaseEntityStep(definedComponent, props.mode, targetProperty);

/** Update entity */
entitiesContext.updateEntitiesFromCamelResource();
}, [catalogModalContext, entitiesContext, props.mode, props.vizNode]);
const { onInsertStep } = useInsertStep(props.vizNode, props.mode);

return (
<ContextMenuItem onClick={onInsertNode} data-testid={props['data-testid']}>
<ContextMenuItem onClick={onInsertStep} data-testid={props['data-testid']}>
{props.children}
</ContextMenuItem>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,17 @@
import { SyncAltIcon } from '@patternfly/react-icons';
import { ContextMenuItem } from '@patternfly/react-topology';
import { FunctionComponent, PropsWithChildren, useCallback, useContext } from 'react';
import { FunctionComponent, PropsWithChildren } from 'react';
import { IDataTestID } from '../../../../models';
import { AddStepMode, IVisualizationNode } from '../../../../models/visualization/base-visual-entity';
import { CatalogModalContext } from '../../../../providers/catalog-modal.provider';
import { EntitiesContext } from '../../../../providers/entities.provider';
import {
ACTION_ID_CONFIRM,
ActionConfirmationModalContext,
} from '../../../../providers/action-confirmation-modal.provider';
import { IVisualizationNode } from '../../../../models/visualization/base-visual-entity';
import { useReplaceStep } from '../hooks/replace-step.hook';

interface ItemReplaceStepProps extends PropsWithChildren<IDataTestID> {
vizNode: IVisualizationNode;
loadActionConfirmationModal: boolean;
}

export const ItemReplaceStep: FunctionComponent<ItemReplaceStepProps> = (props) => {
const entitiesContext = useContext(EntitiesContext);
const catalogModalContext = useContext(CatalogModalContext);
const replaceModalContext = useContext(ActionConfirmationModalContext);

const onReplaceNode = useCallback(async () => {
if (!props.vizNode || !entitiesContext) return;

if (props.loadActionConfirmationModal) {
/** Open delete confirm modal, get the confirmation */
const isReplaceConfirmed = await replaceModalContext?.actionConfirmation({
title: 'Replace step?',
text: 'Step and its children will be lost.',
});

if (isReplaceConfirmed !== ACTION_ID_CONFIRM) return;
}

/** Find compatible components */
const catalogFilter = entitiesContext.camelResource.getCompatibleComponents(
AddStepMode.ReplaceStep,
props.vizNode.data,
);

/** Open Catalog modal, filtering the compatible nodes */
const definedComponent = await catalogModalContext?.getNewComponent(catalogFilter);
if (!definedComponent) return;

/** Add new node to the entities */
props.vizNode.addBaseEntityStep(definedComponent, AddStepMode.ReplaceStep);

/** Update entity */
entitiesContext.updateEntitiesFromCamelResource();
}, [props.vizNode, props.loadActionConfirmationModal, entitiesContext, catalogModalContext, replaceModalContext]);
const { onReplaceNode } = useReplaceStep(props.vizNode);

return (
<ContextMenuItem onClick={onReplaceNode} data-testid={props['data-testid']}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,7 @@ export const NodeContextMenuFn = (element: GraphElement<ElementModel, CanvasNode

if (nodeInteractions.canRemoveStep) {
items.push(
<ItemDeleteStep
key="context-menu-item-delete"
data-testid="context-menu-item-delete"
vizNode={vizNode}
loadActionConfirmationModal={isStepWithChildren}
/>,
<ItemDeleteStep key="context-menu-item-delete" data-testid="context-menu-item-delete" vizNode={vizNode} />,
);
}
if (nodeInteractions.canRemoveFlow) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@use '../custom';

.phantom-rect {
stroke: transparent;
fill: transparent;
Expand Down Expand Up @@ -27,6 +29,8 @@
}

.custom-group {
@include custom.container;

--custom-group--BorderColor: var(--pf-v5-global--palette--black-400);
--custom-group--BorderSize: var(--pf-v5-global--BorderWidth--md);
--custom-group--BorderRadius: 15px;
Expand All @@ -37,8 +41,6 @@

display: flex;
flex-flow: column;
height: 100%;
width: 100%;
background-color: var(--custom-group--Background);
color: var(--pf-v5-global--Color--100);
border-radius: var(--custom-group--BorderRadius);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ const CustomGroup: FunctionComponent<ICustomGroup> = observer(({ element, ...res
className="custom-group"
label={label}
collapsible
collapsedWidth={CanvasDefaults.DEFAULT_NODE_DIAMETER}
collapsedHeight={CanvasDefaults.DEFAULT_NODE_DIAMETER}
collapsedWidth={CanvasDefaults.DEFAULT_NODE_WIDTH}
collapsedHeight={CanvasDefaults.DEFAULT_NODE_HEIGHT}
hulledOutline={false}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ export const CustomGroupCollapsed: FunctionComponent<CustomGroupExpandedProps> =
({
className,
children,
collapsedWidth = CanvasDefaults.DEFAULT_NODE_DIAMETER,
collapsedHeight = CanvasDefaults.DEFAULT_NODE_DIAMETER,
collapsedWidth = CanvasDefaults.DEFAULT_NODE_WIDTH,
collapsedHeight = CanvasDefaults.DEFAULT_NODE_HEIGHT,
collapsedShadowOffset = 8,
element,
onSelect,
Expand Down
Loading

0 comments on commit e4f945c

Please sign in to comment.