Skip to content

Commit

Permalink
feat(editor): Add support for changing sticky notes color in new canv…
Browse files Browse the repository at this point in the history
…as (no-changelog) (#10593)
  • Loading branch information
alexgrozav authored Aug 29, 2024
1 parent 821ca16 commit c988931
Show file tree
Hide file tree
Showing 11 changed files with 340 additions and 17 deletions.
6 changes: 6 additions & 0 deletions packages/editor-ui/src/__tests__/data/canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import { ref } from 'vue';
import type {
CanvasNode,
CanvasNodeData,
CanvasNodeEventBusEvents,
CanvasNodeHandleInjectionData,
CanvasNodeInjectionData,
} from '@/types';
import { CanvasConnectionMode, CanvasNodeRenderType } from '@/types';
import { NodeConnectionType } from 'n8n-workflow';
import type { EventBus } from 'n8n-design-system';
import { createEventBus } from 'n8n-design-system';

export function createCanvasNodeData({
id = 'node',
Expand Down Expand Up @@ -89,11 +92,13 @@ export function createCanvasNodeProvide({
label = 'Test Node',
selected = false,
data = {},
eventBus = createEventBus<CanvasNodeEventBusEvents>(),
}: {
id?: string;
label?: string;
selected?: boolean;
data?: Partial<CanvasNodeData>;
eventBus?: EventBus<CanvasNodeEventBusEvents>;
} = {}) {
const props = createCanvasNodeProps({ id, label, selected, data });
return {
Expand All @@ -102,6 +107,7 @@ export function createCanvasNodeProvide({
label: ref(props.label),
selected: ref(props.selected),
data: ref(props.data),
eventBus: ref(eventBus),
} satisfies CanvasNodeInjectionData,
};
}
Expand Down
26 changes: 18 additions & 8 deletions packages/editor-ui/src/components/canvas/Canvas.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<script lang="ts" setup>
import type { CanvasConnection, CanvasNode, CanvasNodeMoveEvent, ConnectStartEvent } from '@/types';
import type {
CanvasConnection,
CanvasNode,
CanvasNodeMoveEvent,
CanvasEventBusEvents,
ConnectStartEvent,
} from '@/types';
import type {
EdgeMouseEvent,
Connection,
Expand Down Expand Up @@ -65,7 +71,7 @@ const props = withDefaults(
nodes: CanvasNode[];
connections: CanvasConnection[];
controlsPosition?: PanelPosition;
eventBus?: EventBus;
eventBus?: EventBus<CanvasEventBusEvents>;
readOnly?: boolean;
}>(),
{
Expand Down Expand Up @@ -102,8 +108,8 @@ useKeybindings({
ctrl_d: emitWithSelectedNodes((ids) => emit('duplicate:nodes', ids)),
d: emitWithSelectedNodes((ids) => emit('update:nodes:enabled', ids)),
p: emitWithSelectedNodes((ids) => emit('update:nodes:pin', ids, 'keyboard-shortcut')),
enter: () => emitWithLastSelectedNode((id) => emit('update:node:active', id)),
f2: () => emitWithLastSelectedNode((id) => emit('update:node:name', id)),
enter: emitWithLastSelectedNode((id) => onSetNodeActive(id)),
f2: emitWithLastSelectedNode((id) => emit('update:node:name', id)),
tab: () => emit('create:node', 'tab'),
shift_s: () => emit('create:sticky'),
ctrl_alt_n: () => emit('create:workflow'),
Expand Down Expand Up @@ -154,6 +160,7 @@ function onNodesChange(events: NodeChange[]) {
}
function onSetNodeActive(id: string) {
props.eventBus.emit('nodes:action', { ids: [id], action: 'update:node:active' });
emit('update:node:active', id);
}
Expand All @@ -166,7 +173,7 @@ function onSelectNode() {
emit('update:node:selected', lastSelectedNode.value.id);
}
function onSelectNodes(ids: string[]) {
function onSelectNodes({ ids }: CanvasEventBusEvents['nodes:select']) {
clearSelectedNodes();
addSelectedNodes(ids.map(findNode).filter(isPresent));
}
Expand Down Expand Up @@ -358,9 +365,11 @@ function onContextMenuAction(action: ContextMenuAction, nodeIds: string[]) {
case 'toggle_activation':
return emit('update:nodes:enabled', nodeIds);
case 'open':
return emit('update:node:active', nodeIds[0]);
return onSetNodeActive(nodeIds[0]);
case 'rename':
return emit('update:node:name', nodeIds[0]);
case 'change_color':
return props.eventBus.emit('nodes:action', { ids: nodeIds, action: 'update:sticky:color' });
}
}
Expand All @@ -378,12 +387,12 @@ function minimapNodeClassnameFn(node: CanvasNode) {
onMounted(() => {
props.eventBus.on('fitView', onFitView);
props.eventBus.on('selectNodes', onSelectNodes);
props.eventBus.on('nodes:select', onSelectNodes);
});
onUnmounted(() => {
props.eventBus.off('fitView', onFitView);
props.eventBus.off('selectNodes', onSelectNodes);
props.eventBus.off('nodes:select', onSelectNodes);
});
onPaneReady(async () => {
Expand Down Expand Up @@ -431,6 +440,7 @@ provide(CanvasKey, {
<Node
v-bind="canvasNodeProps"
:read-only="readOnly"
:event-bus="eventBus"
@delete="onDeleteNode"
@run="onRunNode"
@select="onSelectNode"
Expand Down
5 changes: 3 additions & 2 deletions packages/editor-ui/src/components/canvas/WorkflowCanvas.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { IWorkflowDb } from '@/Interface';
import { useCanvasMapping } from '@/composables/useCanvasMapping';
import type { EventBus } from 'n8n-design-system';
import { createEventBus } from 'n8n-design-system';
import type { CanvasEventBusEvents } from '@/types';
import { STICKY_NODE_TYPE } from '@/constants';
defineOptions({
Expand All @@ -18,12 +19,12 @@ const props = withDefaults(
workflow: IWorkflowDb;
workflowObject: Workflow;
fallbackNodes?: IWorkflowDb['nodes'];
eventBus?: EventBus;
eventBus?: EventBus<CanvasEventBusEvents>;
readOnly?: boolean;
}>(),
{
id: 'canvas',
eventBus: () => createEventBus(),
eventBus: () => createEventBus<CanvasEventBusEvents>(),
fallbackNodes: () => [],
},
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<script lang="ts" setup>
import { computed, provide, toRef, watch } from 'vue';
import { computed, onBeforeUnmount, onMounted, provide, ref, toRef, watch } from 'vue';
import type {
CanvasConnectionPort,
CanvasElementPortWithRenderData,
CanvasNodeData,
CanvasNodeEventBusEvents,
CanvasEventBusEvents,
} from '@/types';
import { CanvasConnectionMode } from '@/types';
import NodeIcon from '@/components/NodeIcon.vue';
Expand All @@ -18,9 +20,12 @@ import type { NodeProps, XYPosition } from '@vue-flow/core';
import { Position } from '@vue-flow/core';
import { useCanvas } from '@/composables/useCanvas';
import { createCanvasConnectionHandleString } from '@/utils/canvasUtilsV2';
import type { EventBus } from 'n8n-design-system';
import { createEventBus } from 'n8n-design-system';
type Props = NodeProps<CanvasNodeData> & {
readOnly?: boolean;
eventBus?: EventBus<CanvasEventBusEvents>;
};
const emit = defineEmits<{
Expand Down Expand Up @@ -58,6 +63,18 @@ const nodeTypeDescription = computed(() => {
return nodeTypesStore.getNodeType(props.data.type, props.data.typeVersion);
});
/**
* Event bus
*/
const canvasNodeEventBus = ref(createEventBus<CanvasNodeEventBusEvents>());
function emitCanvasNodeEvent(event: CanvasEventBusEvents['nodes:action']) {
if (event.ids.includes(props.id)) {
canvasNodeEventBus.value.emit(event.action, event.payload);
}
}
/**
* Inputs
*/
Expand Down Expand Up @@ -208,6 +225,7 @@ provide(CanvasNodeKey, {
data,
label,
selected,
eventBus: canvasNodeEventBus,
});
const showToolbar = computed(() => {
Expand All @@ -225,6 +243,14 @@ watch(
emit('select', props.id, value);
},
);
onMounted(() => {
props.eventBus?.on('nodes:action', emitCanvasNodeEvent);
});
onBeforeUnmount(() => {
props.eventBus?.off('nodes:action', emitCanvasNodeEvent);
});
</script>

<template>
Expand Down Expand Up @@ -272,6 +298,7 @@ watch(
@delete="onDelete"
@toggle="onDisabledToggle"
@run="onRun"
@update="onUpdate"
@open:contextmenu="onOpenContextMenuFromToolbar"
/>

Expand All @@ -296,6 +323,7 @@ watch(
<style lang="scss" module>
.canvasNode {
&:hover,
&:focus-within,
&.showToolbar {
.canvasNodeToolbar {
opacity: 1;
Expand All @@ -311,5 +339,10 @@ watch(
transform: translate(-50%, -100%);
opacity: 0;
z-index: 1;
&:focus-within,
&:hover {
opacity: 1;
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const emit = defineEmits<{
delete: [];
toggle: [];
run: [];
update: [parameters: Record<string, unknown>];
'open:contextmenu': [event: MouseEvent];
}>();
Expand Down Expand Up @@ -46,6 +47,8 @@ const isDisableNodeVisible = computed(() => {
const isDeleteNodeVisible = computed(() => !props.readOnly);
const isStickyNoteNodeType = computed(() => render.value.type === CanvasNodeRenderType.StickyNote);
function executeNode() {
emit('run');
}
Expand All @@ -58,6 +61,12 @@ function onDeleteNode() {
emit('delete');
}
function onChangeStickyColor(color: number) {
emit('update', {
color,
});
}
function onOpenContextMenu(event: MouseEvent) {
emit('open:contextmenu', event);
}
Expand Down Expand Up @@ -97,6 +106,7 @@ function onOpenContextMenu(event: MouseEvent) {
:title="i18n.baseText('node.delete')"
@click="onDeleteNode"
/>
<CanvasNodeStickyColorSelector v-if="isStickyNoteNodeType" @update="onChangeStickyColor" />
<N8nIconButton
data-test-id="overflow-node-button"
type="tertiary"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* eslint-disable vue/no-multiple-template-root */
import { useCanvasNode } from '@/composables/useCanvasNode';
import type { CanvasNodeStickyNoteRender } from '@/types';
import { ref, computed, useCssModule } from 'vue';
import { ref, computed, useCssModule, onMounted, onBeforeUnmount } from 'vue';
import { NodeResizer } from '@vue-flow/node-resizer';
import type { OnResize } from '@vue-flow/node-resizer/dist/types';
import type { XYPosition } from '@vue-flow/core';
Expand All @@ -15,11 +15,12 @@ const emit = defineEmits<{
update: [parameters: Record<string, unknown>];
move: [position: XYPosition];
dblclick: [event: MouseEvent];
'open:contextmenu': [event: MouseEvent];
}>();
const $style = useCssModule();
const { id, isSelected, render } = useCanvasNode();
const { id, isSelected, render, eventBus } = useCanvasNode();
const renderOptions = computed(() => render.value.options as CanvasNodeStickyNoteRender['options']);
Expand Down Expand Up @@ -63,6 +64,30 @@ function onEdit(edit: boolean) {
function onDoubleClick(event: MouseEvent) {
emit('dblclick', event);
}
function onActivate() {
onEdit(true);
}
/**
* Context menu
*/
function openContextMenu(event: MouseEvent) {
emit('open:contextmenu', event);
}
/**
* Lifecycle
*/
onMounted(() => {
eventBus.value?.on('update:node:active', onActivate);
});
onBeforeUnmount(() => {
eventBus.value?.off('update:node:active', onActivate);
});
</script>
<template>
<NodeResizer
Expand All @@ -80,11 +105,12 @@ function onDoubleClick(event: MouseEvent) {
:height="renderOptions.height"
:width="renderOptions.width"
:model-value="renderOptions.content"
:background="renderOptions.color"
:background-color="renderOptions.color"
:edit-mode="isActive"
@edit="onEdit"
@dblclick="onDoubleClick"
@update:model-value="onInputChange"
@contextmenu="openContextMenu"
/>
</template>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { fireEvent } from '@testing-library/vue';
import CanvasNodeStickyColorSelector from '@/components/canvas/elements/nodes/toolbar/CanvasNodeStickyColorSelector.vue';
import { createComponentRenderer } from '@/__tests__/render';
import { createCanvasNodeProvide } from '@/__tests__/data';

const renderComponent = createComponentRenderer(CanvasNodeStickyColorSelector);

describe('CanvasNodeStickyColorSelector', () => {
it('should render trigger correctly', () => {
const { getByTestId } = renderComponent({
global: {
provide: {
...createCanvasNodeProvide(),
},
},
});
const colorSelector = getByTestId('change-sticky-color');
expect(colorSelector).toBeVisible();
});

it('should render all colors and apply selected color correctly', async () => {
const { getByTestId, getAllByTestId, emitted } = renderComponent({
global: {
provide: {
...createCanvasNodeProvide(),
},
},
});

const colorSelector = getByTestId('change-sticky-color');

await fireEvent.click(colorSelector);

const colorOption = getAllByTestId('color');
const selectedIndex = 2;

await fireEvent.click(colorOption[selectedIndex]);

expect(colorOption).toHaveLength(7);
expect(emitted()).toHaveProperty('update');
expect(emitted().update[0]).toEqual([selectedIndex + 1]);
});
});
Loading

0 comments on commit c988931

Please sign in to comment.