mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat(editor): Zoom into a node to open experimental embedded NDV (no-changelog) (#16912)
Co-authored-by: Milorad FIlipović <milorad@n8n.io>
This commit is contained in:
@@ -66,6 +66,7 @@ import IconLucideCode from '~icons/lucide/code';
|
|||||||
import IconLucideCog from '~icons/lucide/cog';
|
import IconLucideCog from '~icons/lucide/cog';
|
||||||
import IconLucideContrast from '~icons/lucide/contrast';
|
import IconLucideContrast from '~icons/lucide/contrast';
|
||||||
import IconLucideCopy from '~icons/lucide/copy';
|
import IconLucideCopy from '~icons/lucide/copy';
|
||||||
|
import IconLucideCrosshair from '~icons/lucide/crosshair';
|
||||||
import IconLucideDatabase from '~icons/lucide/database';
|
import IconLucideDatabase from '~icons/lucide/database';
|
||||||
import IconLucideEarth from '~icons/lucide/earth';
|
import IconLucideEarth from '~icons/lucide/earth';
|
||||||
import IconLucideEllipsis from '~icons/lucide/ellipsis';
|
import IconLucideEllipsis from '~icons/lucide/ellipsis';
|
||||||
@@ -460,6 +461,7 @@ export const updatedIconSet = {
|
|||||||
cog: IconLucideCog,
|
cog: IconLucideCog,
|
||||||
contrast: IconLucideContrast,
|
contrast: IconLucideContrast,
|
||||||
copy: IconLucideCopy,
|
copy: IconLucideCopy,
|
||||||
|
crosshair: IconLucideCrosshair,
|
||||||
database: IconLucideDatabase,
|
database: IconLucideDatabase,
|
||||||
earth: IconLucideEarth,
|
earth: IconLucideEarth,
|
||||||
ellipsis: IconLucideEllipsis,
|
ellipsis: IconLucideEllipsis,
|
||||||
|
|||||||
@@ -1556,6 +1556,8 @@
|
|||||||
"nodeView.zoomToFit": "Zoom to Fit",
|
"nodeView.zoomToFit": "Zoom to Fit",
|
||||||
"nodeView.replaceMe": "Replace Me",
|
"nodeView.replaceMe": "Replace Me",
|
||||||
"nodeView.setupTemplate": "Set up template",
|
"nodeView.setupTemplate": "Set up template",
|
||||||
|
"nodeView.expandAllNodes": "Expand all nodes",
|
||||||
|
"nodeView.collapseAllNodes": "Collapse all nodes",
|
||||||
"nodeViewV2.showError.editingNotAllowed": "Editing is not allowed",
|
"nodeViewV2.showError.editingNotAllowed": "Editing is not allowed",
|
||||||
"nodeViewV2.showError.failedToCreateNode": "Failed to create node",
|
"nodeViewV2.showError.failedToCreateNode": "Failed to create node",
|
||||||
"contextMenu.node": "node | nodes",
|
"contextMenu.node": "node | nodes",
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import type {
|
|||||||
import { useActions } from './NodeCreator/composables/useActions';
|
import { useActions } from './NodeCreator/composables/useActions';
|
||||||
import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue';
|
import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue';
|
||||||
import { useI18n } from '@n8n/i18n';
|
import { useI18n } from '@n8n/i18n';
|
||||||
import { useExperimentalNdvStore } from '../canvas/experimental/experimentalNdv.store';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
nodeViewScale: number;
|
nodeViewScale: number;
|
||||||
@@ -45,7 +44,6 @@ const uiStore = useUIStore();
|
|||||||
const focusPanelStore = useFocusPanelStore();
|
const focusPanelStore = useFocusPanelStore();
|
||||||
const posthogStore = usePostHog();
|
const posthogStore = usePostHog();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const experimentalNdvStore = useExperimentalNdvStore();
|
|
||||||
|
|
||||||
const { getAddedNodesAndConnections } = useActions();
|
const { getAddedNodesAndConnections } = useActions();
|
||||||
|
|
||||||
@@ -127,20 +125,6 @@ function nodeTypeSelected(value: NodeTypeSelectedPayload[]) {
|
|||||||
@click="focusPanelStore.toggleFocusPanel"
|
@click="focusPanelStore.toggleFocusPanel"
|
||||||
/>
|
/>
|
||||||
</KeyboardShortcutTooltip>
|
</KeyboardShortcutTooltip>
|
||||||
<n8n-icon-button
|
|
||||||
v-if="experimentalNdvStore.isEnabled"
|
|
||||||
type="tertiary"
|
|
||||||
size="large"
|
|
||||||
icon="maximize-2"
|
|
||||||
@click="experimentalNdvStore.expandAllNodes"
|
|
||||||
/>
|
|
||||||
<n8n-icon-button
|
|
||||||
v-if="experimentalNdvStore.isEnabled"
|
|
||||||
type="tertiary"
|
|
||||||
size="large"
|
|
||||||
icon="minimize-2"
|
|
||||||
@click="experimentalNdvStore.collapseAllNodes"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<LazyNodeCreator
|
<LazyNodeCreator
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import TidyUpIcon from '@/components/TidyUpIcon.vue';
|
|||||||
import { useI18n } from '@n8n/i18n';
|
import { useI18n } from '@n8n/i18n';
|
||||||
import { Controls } from '@vue-flow/controls';
|
import { Controls } from '@vue-flow/controls';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import { useExperimentalNdvStore } from '../../experimental/experimentalNdv.store';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -26,6 +27,8 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
|
const experimentalNdvStore = useExperimentalNdvStore();
|
||||||
|
|
||||||
const isResetZoomVisible = computed(() => props.zoom !== 1);
|
const isResetZoomVisible = computed(() => props.zoom !== 1);
|
||||||
|
|
||||||
function onResetZoom() {
|
function onResetZoom() {
|
||||||
@@ -109,6 +112,30 @@ function onTidyUp() {
|
|||||||
<TidyUpIcon />
|
<TidyUpIcon />
|
||||||
</N8nButton>
|
</N8nButton>
|
||||||
</KeyboardShortcutTooltip>
|
</KeyboardShortcutTooltip>
|
||||||
|
<N8nTooltip
|
||||||
|
v-if="experimentalNdvStore.isActive(props.zoom)"
|
||||||
|
placement="top"
|
||||||
|
:content="i18n.baseText('nodeView.expandAllNodes')"
|
||||||
|
>
|
||||||
|
<N8nIconButton
|
||||||
|
type="tertiary"
|
||||||
|
size="large"
|
||||||
|
icon="maximize-2"
|
||||||
|
@click="experimentalNdvStore.expandAllNodes"
|
||||||
|
/>
|
||||||
|
</N8nTooltip>
|
||||||
|
<N8nTooltip
|
||||||
|
v-if="experimentalNdvStore.isActive(props.zoom)"
|
||||||
|
placement="top"
|
||||||
|
:content="i18n.baseText('nodeView.collapseAllNodes')"
|
||||||
|
>
|
||||||
|
<N8nIconButton
|
||||||
|
type="tertiary"
|
||||||
|
size="large"
|
||||||
|
icon="minimize-2"
|
||||||
|
@click="experimentalNdvStore.collapseAllNodes"
|
||||||
|
/>
|
||||||
|
</N8nTooltip>
|
||||||
</Controls>
|
</Controls>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -21,5 +21,7 @@ exports[`CanvasControlButtons > should render correctly 1`] = `
|
|||||||
<n8n-button-stub block="false" element="button" label="" square="true" active="false" disabled="false" loading="false" outline="false" size="large" text="false" type="tertiary" data-test-id="tidy-up-button" class="iconButton el-tooltip__trigger el-tooltip__trigger"></n8n-button-stub>
|
<n8n-button-stub block="false" element="button" label="" square="true" active="false" disabled="false" loading="false" outline="false" size="large" text="false" type="tertiary" data-test-id="tidy-up-button" class="iconButton el-tooltip__trigger el-tooltip__trigger"></n8n-button-stub>
|
||||||
<!--teleport start-->
|
<!--teleport start-->
|
||||||
<!--teleport end-->
|
<!--teleport end-->
|
||||||
|
<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
</div>"
|
</div>"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import { createEventBus } from '@n8n/utils/event-bus';
|
|||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
import CanvasNodeTrigger from '@/components/canvas/elements/nodes/render-types/parts/CanvasNodeTrigger.vue';
|
import CanvasNodeTrigger from '@/components/canvas/elements/nodes/render-types/parts/CanvasNodeTrigger.vue';
|
||||||
import { CONFIGURATION_NODE_OFFSET, GRID_SIZE } from '@/utils/nodeViewUtils';
|
import { CONFIGURATION_NODE_OFFSET, GRID_SIZE } from '@/utils/nodeViewUtils';
|
||||||
|
import { useExperimentalNdvStore } from '../../experimental/experimentalNdv.store';
|
||||||
|
|
||||||
type Props = NodeProps<CanvasNodeData> & {
|
type Props = NodeProps<CanvasNodeData> & {
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
@@ -72,7 +73,9 @@ const props = defineProps<Props>();
|
|||||||
|
|
||||||
const contextMenu = useContextMenu();
|
const contextMenu = useContextMenu();
|
||||||
|
|
||||||
const { connectingHandle } = useCanvas();
|
const { connectingHandle, viewport } = useCanvas();
|
||||||
|
|
||||||
|
const experimentalNdvStore = useExperimentalNdvStore();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Toolbar slot classes
|
Toolbar slot classes
|
||||||
@@ -96,6 +99,10 @@ const {
|
|||||||
|
|
||||||
const isDisabled = computed(() => props.data.disabled);
|
const isDisabled = computed(() => props.data.disabled);
|
||||||
|
|
||||||
|
const isExperimentalEmbeddedNdvShown = computed(() =>
|
||||||
|
experimentalNdvStore.isActive(viewport.value.zoom),
|
||||||
|
);
|
||||||
|
|
||||||
const classes = computed(() => ({
|
const classes = computed(() => ({
|
||||||
[style.canvasNode]: true,
|
[style.canvasNode]: true,
|
||||||
[style.showToolbar]: showToolbar.value,
|
[style.showToolbar]: showToolbar.value,
|
||||||
@@ -187,7 +194,9 @@ const createEndpointMappingFn =
|
|||||||
const offsetValue =
|
const offsetValue =
|
||||||
position === Position.Bottom
|
position === Position.Bottom
|
||||||
? `${GRID_SIZE * 2 * (1 + index * 2) + CONFIGURATION_NODE_OFFSET}px`
|
? `${GRID_SIZE * 2 * (1 + index * 2) + CONFIGURATION_NODE_OFFSET}px`
|
||||||
: `${(100 / (endpoints.length + 1)) * (index + 1)}%`;
|
: isExperimentalEmbeddedNdvShown.value && endpoints.length === 1
|
||||||
|
? `${(1 + index) * (GRID_SIZE * 2)}px`
|
||||||
|
: `${(100 / (endpoints.length + 1)) * (index + 1)}%`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...endpoint,
|
...endpoint,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { CanvasNodeRenderType } from '@/types';
|
|||||||
import { useCanvas } from '@/composables/useCanvas';
|
import { useCanvas } from '@/composables/useCanvas';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
|
import { useExperimentalNdvStore } from '../../experimental/experimentalNdv.store';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
delete: [];
|
delete: [];
|
||||||
@@ -27,8 +28,9 @@ const { isDisabled, render, name } = useCanvasNode();
|
|||||||
|
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
const nodeTypesStore = useNodeTypesStore();
|
const nodeTypesStore = useNodeTypesStore();
|
||||||
|
const experimentalNdvStore = useExperimentalNdvStore();
|
||||||
|
|
||||||
const node = computed(() => !!name.value && workflowsStore.getNodeByName(name.value));
|
const node = computed(() => (name.value ? workflowsStore.getNodeByName(name.value) : null));
|
||||||
const isToolNode = computed(() => !!node.value && nodeTypesStore.isToolNode(node.value.type));
|
const isToolNode = computed(() => !!node.value && nodeTypesStore.isToolNode(node.value.type));
|
||||||
|
|
||||||
const nodeDisabledTitle = computed(() => {
|
const nodeDisabledTitle = computed(() => {
|
||||||
@@ -59,6 +61,13 @@ const isDisableNodeVisible = computed(() => {
|
|||||||
|
|
||||||
const isDeleteNodeVisible = computed(() => !props.readOnly);
|
const isDeleteNodeVisible = computed(() => !props.readOnly);
|
||||||
|
|
||||||
|
const isFocusNodeVisible = computed(
|
||||||
|
() =>
|
||||||
|
experimentalNdvStore.isEnabled &&
|
||||||
|
node.value !== null &&
|
||||||
|
experimentalNdvStore.collapsedNodes[node.value.id] !== false,
|
||||||
|
);
|
||||||
|
|
||||||
const isStickyNoteChangeColorVisible = computed(
|
const isStickyNoteChangeColorVisible = computed(
|
||||||
() => !props.readOnly && render.value.type === CanvasNodeRenderType.StickyNote,
|
() => !props.readOnly && render.value.type === CanvasNodeRenderType.StickyNote,
|
||||||
);
|
);
|
||||||
@@ -92,6 +101,12 @@ function onMouseEnter() {
|
|||||||
function onMouseLeave() {
|
function onMouseLeave() {
|
||||||
isHovered.value = false;
|
isHovered.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onFocusNode() {
|
||||||
|
if (node.value) {
|
||||||
|
experimentalNdvStore.focusNode(node.value.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -139,6 +154,14 @@ function onMouseLeave() {
|
|||||||
:title="i18n.baseText('node.delete')"
|
:title="i18n.baseText('node.delete')"
|
||||||
@click="onDeleteNode"
|
@click="onDeleteNode"
|
||||||
/>
|
/>
|
||||||
|
<N8nIconButton
|
||||||
|
v-if="isFocusNodeVisible"
|
||||||
|
type="tertiary"
|
||||||
|
size="small"
|
||||||
|
text
|
||||||
|
icon="crosshair"
|
||||||
|
@click="onFocusNode"
|
||||||
|
/>
|
||||||
<CanvasNodeStickyColorSelector
|
<CanvasNodeStickyColorSelector
|
||||||
v-if="isStickyNoteChangeColorVisible"
|
v-if="isStickyNoteChangeColorVisible"
|
||||||
v-model:visible="isStickyColorSelectorOpen"
|
v-model:visible="isStickyColorSelectorOpen"
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const {
|
|||||||
outputs,
|
outputs,
|
||||||
connections,
|
connections,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
|
isReadOnly,
|
||||||
isSelected,
|
isSelected,
|
||||||
hasPinnedData,
|
hasPinnedData,
|
||||||
executionStatus,
|
executionStatus,
|
||||||
@@ -136,6 +137,8 @@ function onActivate(event: MouseEvent) {
|
|||||||
:node-id="id"
|
:node-id="id"
|
||||||
:class="classes"
|
:class="classes"
|
||||||
:style="styles"
|
:style="styles"
|
||||||
|
:is-read-only="isReadOnly"
|
||||||
|
:is-configurable="renderOptions.configurable ?? false"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
|
|||||||
import { createEventBus } from '@n8n/utils/event-bus';
|
import { createEventBus } from '@n8n/utils/event-bus';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
const { nodeId, noWheel } = defineProps<{ nodeId: string; noWheel?: boolean }>();
|
const { nodeId, noWheel, isReadOnly } = defineProps<{
|
||||||
|
nodeId: string;
|
||||||
|
noWheel?: boolean;
|
||||||
|
isReadOnly?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
defineSlots<{ actions?: {} }>();
|
defineSlots<{ actions?: {} }>();
|
||||||
|
|
||||||
@@ -30,7 +34,7 @@ function handleValueChanged(parameterData: IUpdateInformation) {
|
|||||||
:active-node="activeNode"
|
:active-node="activeNode"
|
||||||
push-ref=""
|
push-ref=""
|
||||||
:foreign-credentials="[]"
|
:foreign-credentials="[]"
|
||||||
:read-only="false"
|
:read-only="isReadOnly"
|
||||||
:block-u-i="false"
|
:block-u-i="false"
|
||||||
:executable="false"
|
:executable="false"
|
||||||
:input-size="0"
|
:input-size="0"
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import { N8nIcon, N8nIconButton } from '@n8n/design-system';
|
|||||||
import { useVueFlow } from '@vue-flow/core';
|
import { useVueFlow } from '@vue-flow/core';
|
||||||
import { watchOnce } from '@vueuse/core';
|
import { watchOnce } from '@vueuse/core';
|
||||||
|
|
||||||
const { nodeId } = defineProps<{ nodeId: string }>();
|
const { nodeId, isReadOnly, isConfigurable } = defineProps<{
|
||||||
|
nodeId: string;
|
||||||
|
isReadOnly?: boolean;
|
||||||
|
isConfigurable: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
const experimentalNdvStore = useExperimentalNdvStore();
|
const experimentalNdvStore = useExperimentalNdvStore();
|
||||||
const isExpanded = computed(() => !experimentalNdvStore.collapsedNodes[nodeId]);
|
const isExpanded = computed(() => !experimentalNdvStore.collapsedNodes[nodeId]);
|
||||||
@@ -22,7 +26,7 @@ const nodeType = computed(() => {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
const vf = useVueFlow(workflowsStore.workflowId);
|
const vf = useVueFlow();
|
||||||
|
|
||||||
const isMoving = ref(false);
|
const isMoving = ref(false);
|
||||||
|
|
||||||
@@ -65,7 +69,10 @@ function handleToggleExpand() {
|
|||||||
<div
|
<div
|
||||||
ref="container"
|
ref="container"
|
||||||
:class="[$style.component, isExpanded ? $style.expanded : $style.collapsed]"
|
:class="[$style.component, isExpanded ? $style.expanded : $style.collapsed]"
|
||||||
:style="{ '--zoom': `${1 / experimentalNdvStore.maxCanvasZoom}` }"
|
:style="{
|
||||||
|
'--zoom': `${1 / experimentalNdvStore.maxCanvasZoom}`,
|
||||||
|
'--node-width-scaler': isConfigurable ? 1 : 1.5,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<template v-if="isOnceVisible">
|
<template v-if="isOnceVisible">
|
||||||
<ExperimentalCanvasNodeSettings
|
<ExperimentalCanvasNodeSettings
|
||||||
@@ -75,6 +82,7 @@ function handleToggleExpand() {
|
|||||||
:no-wheel="
|
:no-wheel="
|
||||||
!isMoving /* to not interrupt panning while allowing scroll of the settings pane, allow wheel event while panning */
|
!isMoving /* to not interrupt panning while allowing scroll of the settings pane, allow wheel event while panning */
|
||||||
"
|
"
|
||||||
|
:is-read-only="isReadOnly"
|
||||||
>
|
>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<N8nIconButton
|
<N8nIconButton
|
||||||
@@ -107,20 +115,17 @@ function handleToggleExpand() {
|
|||||||
position: relative;
|
position: relative;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: stretch;
|
justify-content: stretch;
|
||||||
overflow: visible;
|
overflow: hidden;
|
||||||
border-width: 0 !important;
|
border-width: 1px !important;
|
||||||
outline: none;
|
border-radius: var(--border-radius-base) !important;
|
||||||
box-shadow: none !important;
|
width: calc(var(--canvas-node--width) * var(--node-width-scaler));
|
||||||
background-color: transparent;
|
|
||||||
width: calc(var(--canvas-node--width) * 1.5);
|
|
||||||
|
|
||||||
&.expanded {
|
&.expanded {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
height: 50px;
|
height: calc(16px * 4);
|
||||||
margin-block: calc(var(--canvas-node--width) * 0.25);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,28 +139,12 @@ function handleToggleExpand() {
|
|||||||
|
|
||||||
:root .collapsedContent,
|
:root .collapsedContent,
|
||||||
:root .settingsView {
|
:root .settingsView {
|
||||||
border-radius: var(--border-radius-base);
|
|
||||||
border: 1px solid var(--canvas-node--border-color, var(--color-foreground-xdark));
|
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
:global(.selected) & {
|
|
||||||
box-shadow: 0 0 0 4px var(--color-canvas-selected-transparent);
|
|
||||||
z-index: 1001;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
zoom: var(--zoom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:root .settingsView {
|
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: min(200%, 300px);
|
max-height: min(calc(var(--canvas-node--height) * 2), 300px);
|
||||||
top: -10%;
|
min-height: var(--spacing-3xl); // should be multiple of GRID_SIZE
|
||||||
min-height: 120%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsedContent {
|
.collapsedContent {
|
||||||
@@ -169,5 +158,15 @@ function handleToggleExpand() {
|
|||||||
background-color: var(--color-background-xlight);
|
background-color: var(--color-background-xlight);
|
||||||
color: var(--color-text-base);
|
color: var(--color-text-base);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
zoom: calc(var(--zoom) * 1.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingsView {
|
||||||
|
& > * {
|
||||||
|
zoom: var(--zoom);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { computed, shallowRef } from 'vue';
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
|
import { useVueFlow } from '@vue-flow/core';
|
||||||
|
import { calculateNodeSize } from '@/utils/nodeViewUtils';
|
||||||
|
|
||||||
export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
|
export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
|
||||||
const workflowStore = useWorkflowsStore();
|
const workflowStore = useWorkflowsStore();
|
||||||
@@ -20,7 +22,7 @@ export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
|
|||||||
function setNodeExpanded(nodeId: string, isExpanded?: boolean) {
|
function setNodeExpanded(nodeId: string, isExpanded?: boolean) {
|
||||||
collapsedNodes.value = {
|
collapsedNodes.value = {
|
||||||
...collapsedNodes.value,
|
...collapsedNodes.value,
|
||||||
[nodeId]: isExpanded ?? !collapsedNodes.value[nodeId],
|
[nodeId]: isExpanded === undefined ? !collapsedNodes.value[nodeId] : !isExpanded,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +44,40 @@ export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
|
|||||||
return isEnabled.value && canvasZoom === maxCanvasZoom.value;
|
return isEnabled.value && canvasZoom === maxCanvasZoom.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function focusNode(nodeId: string) {
|
||||||
|
const nodeToFocus = workflowStore.getNodeById(nodeId);
|
||||||
|
|
||||||
|
if (!nodeToFocus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call useVueFlow() here because having it in setup fn scope seem to cause initialization problem
|
||||||
|
const vueFlow = useVueFlow(workflowStore.workflow.id);
|
||||||
|
|
||||||
|
collapsedNodes.value = workflowStore.allNodes.reduce<Partial<Record<string, boolean>>>(
|
||||||
|
(acc, node) => {
|
||||||
|
acc[node.id] = node.id !== nodeId;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
const workflow = workflowStore.getCurrentWorkflow();
|
||||||
|
const nodeSize = calculateNodeSize(
|
||||||
|
workflow.getChildNodes(nodeToFocus.name, 'ALL_NON_MAIN').length > 0,
|
||||||
|
workflow.getParentNodes(nodeToFocus.name, 'ALL_NON_MAIN').length > 0,
|
||||||
|
workflow.getParentNodes(nodeToFocus.name, 'main').length,
|
||||||
|
workflow.getChildNodes(nodeToFocus.name, 'main').length,
|
||||||
|
workflow.getParentNodes(nodeToFocus.name, 'ALL_NON_MAIN').length,
|
||||||
|
);
|
||||||
|
|
||||||
|
void vueFlow.setCenter(
|
||||||
|
nodeToFocus.position[0] + (nodeSize.width * 1.5) / 2,
|
||||||
|
nodeToFocus.position[1] + 80,
|
||||||
|
{ duration: 200, zoom: maxCanvasZoom.value },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isEnabled,
|
isEnabled,
|
||||||
maxCanvasZoom,
|
maxCanvasZoom,
|
||||||
@@ -50,5 +86,6 @@ export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
|
|||||||
setNodeExpanded,
|
setNodeExpanded,
|
||||||
expandAllNodes,
|
expandAllNodes,
|
||||||
collapseAllNodes,
|
collapseAllNodes,
|
||||||
|
focusNode,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { useUIStore } from '@/stores/ui.store';
|
|||||||
import { shallowRef, watch } from 'vue';
|
import { shallowRef, watch } from 'vue';
|
||||||
import { computed, type ComputedRef } from 'vue';
|
import { computed, type ComputedRef } from 'vue';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
|
import { useExperimentalNdvStore } from '@/components/canvas/experimental/experimentalNdv.store';
|
||||||
|
|
||||||
export function useLogsSelection(
|
export function useLogsSelection(
|
||||||
execution: ComputedRef<IExecutionResponse | undefined>,
|
execution: ComputedRef<IExecutionResponse | undefined>,
|
||||||
@@ -33,13 +34,19 @@ export function useLogsSelection(
|
|||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
const canvasStore = useCanvasStore();
|
const canvasStore = useCanvasStore();
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
|
const experimentalNdvStore = useExperimentalNdvStore();
|
||||||
|
|
||||||
function syncSelectionToCanvasIfEnabled(value: LogEntry) {
|
function syncSelectionToCanvasIfEnabled(value: LogEntry) {
|
||||||
if (!logsStore.isLogSelectionSyncedWithCanvas) {
|
if (!logsStore.isLogSelectionSyncedWithCanvas) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvasEventBus.emit('nodes:select', { ids: [value.node.id], panIntoView: true });
|
if (experimentalNdvStore.isEnabled) {
|
||||||
|
canvasEventBus.emit('nodes:select', { ids: [value.node.id], panIntoView: false });
|
||||||
|
experimentalNdvStore.focusNode(value.node.id);
|
||||||
|
} else {
|
||||||
|
canvasEventBus.emit('nodes:select', { ids: [value.node.id], panIntoView: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function select(value: LogEntry | undefined) {
|
function select(value: LogEntry | undefined) {
|
||||||
|
|||||||
Reference in New Issue
Block a user