mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-02 07:01:30 -08:00
feat(editor): Make the left sidebar in Expressions editor draggable (#11838)
This commit is contained in:
parent
1d80225d26
commit
a713b3ed25
|
@ -1,6 +1,8 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { directionsCursorMaps, type Direction, type ResizeData } from 'n8n-design-system/types';
|
||||||
|
|
||||||
function closestNumber(value: number, divisor: number): number {
|
function closestNumber(value: number, divisor: number): number {
|
||||||
const q = value / divisor;
|
const q = value / divisor;
|
||||||
const n1 = divisor * q;
|
const n1 = divisor * q;
|
||||||
|
@ -21,19 +23,6 @@ function getSize(min: number, virtual: number, gridSize: number): number {
|
||||||
return min;
|
return min;
|
||||||
}
|
}
|
||||||
|
|
||||||
const directionsCursorMaps = {
|
|
||||||
right: 'ew-resize',
|
|
||||||
top: 'ns-resize',
|
|
||||||
bottom: 'ns-resize',
|
|
||||||
left: 'ew-resize',
|
|
||||||
topLeft: 'nw-resize',
|
|
||||||
topRight: 'ne-resize',
|
|
||||||
bottomLeft: 'sw-resize',
|
|
||||||
bottomRight: 'se-resize',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
type Direction = keyof typeof directionsCursorMaps;
|
|
||||||
|
|
||||||
interface ResizeProps {
|
interface ResizeProps {
|
||||||
isResizingEnabled?: boolean;
|
isResizingEnabled?: boolean;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
@ -56,16 +45,6 @@ const props = withDefaults(defineProps<ResizeProps>(), {
|
||||||
supportedDirections: () => [],
|
supportedDirections: () => [],
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface ResizeData {
|
|
||||||
height: number;
|
|
||||||
width: number;
|
|
||||||
dX: number;
|
|
||||||
dY: number;
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
direction: Direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
resizestart: [];
|
resizestart: [];
|
||||||
resize: [value: ResizeData];
|
resize: [value: ResizeData];
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, useAttrs } from 'vue';
|
import { computed, ref, useAttrs } from 'vue';
|
||||||
|
|
||||||
import N8nResizeWrapper, { type ResizeData } from '../N8nResizeWrapper/ResizeWrapper.vue';
|
import { type ResizeData } from 'n8n-design-system/types';
|
||||||
|
|
||||||
|
import N8nResizeWrapper from '../N8nResizeWrapper/ResizeWrapper.vue';
|
||||||
import { defaultStickyProps } from '../N8nSticky/constants';
|
import { defaultStickyProps } from '../N8nSticky/constants';
|
||||||
import N8nSticky from '../N8nSticky/Sticky.vue';
|
import N8nSticky from '../N8nSticky/Sticky.vue';
|
||||||
import type { StickyProps } from '../N8nSticky/types';
|
import type { StickyProps } from '../N8nSticky/types';
|
||||||
|
|
|
@ -9,3 +9,4 @@ export * from './select';
|
||||||
export * from './user';
|
export * from './user';
|
||||||
export * from './keyboardshortcut';
|
export * from './keyboardshortcut';
|
||||||
export * from './node-creator-node';
|
export * from './node-creator-node';
|
||||||
|
export * from './resize';
|
||||||
|
|
22
packages/design-system/src/types/resize.ts
Normal file
22
packages/design-system/src/types/resize.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
export const directionsCursorMaps = {
|
||||||
|
right: 'ew-resize',
|
||||||
|
top: 'ns-resize',
|
||||||
|
bottom: 'ns-resize',
|
||||||
|
left: 'ew-resize',
|
||||||
|
topLeft: 'nw-resize',
|
||||||
|
topRight: 'ne-resize',
|
||||||
|
bottomLeft: 'sw-resize',
|
||||||
|
bottomRight: 'se-resize',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type Direction = keyof typeof directionsCursorMaps;
|
||||||
|
|
||||||
|
export interface ResizeData {
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
|
dX: number;
|
||||||
|
dY: number;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
direction: Direction;
|
||||||
|
}
|
|
@ -71,7 +71,7 @@ function onClose() {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SlideTransition>
|
<SlideTransition>
|
||||||
<n8n-resize-wrapper
|
<N8nResizeWrapper
|
||||||
v-show="assistantStore.isAssistantOpen"
|
v-show="assistantStore.isAssistantOpen"
|
||||||
:supported-directions="['left']"
|
:supported-directions="['left']"
|
||||||
:width="assistantStore.chatWidth"
|
:width="assistantStore.chatWidth"
|
||||||
|
@ -97,7 +97,7 @@ function onClose() {
|
||||||
@code-undo="undoCodeDiff"
|
@code-undo="undoCodeDiff"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</n8n-resize-wrapper>
|
</N8nResizeWrapper>
|
||||||
</SlideTransition>
|
</SlideTransition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -240,7 +240,7 @@ watchEffect(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n8n-resize-wrapper
|
<N8nResizeWrapper
|
||||||
v-if="chatTriggerNode"
|
v-if="chatTriggerNode"
|
||||||
:is-resizing-enabled="isChatOpen || isLogsOpen"
|
:is-resizing-enabled="isChatOpen || isLogsOpen"
|
||||||
:supported-directions="['top']"
|
:supported-directions="['top']"
|
||||||
|
@ -282,7 +282,7 @@ watchEffect(() => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n8n-resize-wrapper>
|
</N8nResizeWrapper>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import { ref, computed, onMounted, onBeforeUnmount, watchEffect } from 'vue';
|
import { ref, computed, onMounted, onBeforeUnmount, watchEffect } from 'vue';
|
||||||
import type { ResizeData } from 'n8n-design-system/components/N8nResizeWrapper/ResizeWrapper.vue';
|
|
||||||
import { useDebounce } from '@/composables/useDebounce';
|
import { useDebounce } from '@/composables/useDebounce';
|
||||||
import type { IChatResizeStyles } from '../types/chat';
|
import type { IChatResizeStyles } from '../types/chat';
|
||||||
import { useStorage } from '@/composables/useStorage';
|
import { useStorage } from '@/composables/useStorage';
|
||||||
|
import { type ResizeData } from 'n8n-design-system';
|
||||||
|
|
||||||
const LOCAL_STORAGE_PANEL_HEIGHT = 'N8N_CANVAS_CHAT_HEIGHT';
|
const LOCAL_STORAGE_PANEL_HEIGHT = 'N8N_CANVAS_CHAT_HEIGHT';
|
||||||
const LOCAL_STORAGE_PANEL_WIDTH = 'N8N_CANVAS_CHAT_WIDTH';
|
const LOCAL_STORAGE_PANEL_WIDTH = 'N8N_CANVAS_CHAT_WIDTH';
|
||||||
|
|
|
@ -23,6 +23,10 @@ import { dropInExpressionEditor } from '@/plugins/codemirror/dragAndDrop';
|
||||||
|
|
||||||
import { APP_MODALS_ELEMENT_ID } from '@/constants';
|
import { APP_MODALS_ELEMENT_ID } from '@/constants';
|
||||||
import { N8nInput, N8nText } from 'n8n-design-system';
|
import { N8nInput, N8nText } from 'n8n-design-system';
|
||||||
|
import { N8nResizeWrapper, type ResizeData } from 'n8n-design-system';
|
||||||
|
import { useThrottleFn } from '@vueuse/core';
|
||||||
|
|
||||||
|
const DEFAULT_LEFT_SIDEBAR_WIDTH = 360;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
parameter: INodeProperties;
|
parameter: INodeProperties;
|
||||||
|
@ -56,6 +60,7 @@ const { debounce } = useDebounce();
|
||||||
const segments = ref<Segment[]>([]);
|
const segments = ref<Segment[]>([]);
|
||||||
const search = ref('');
|
const search = ref('');
|
||||||
const appliedSearch = ref('');
|
const appliedSearch = ref('');
|
||||||
|
const sidebarWidth = ref(DEFAULT_LEFT_SIDEBAR_WIDTH);
|
||||||
const expressionInputRef = ref<InstanceType<typeof ExpressionEditorModalInput>>();
|
const expressionInputRef = ref<InstanceType<typeof ExpressionEditorModalInput>>();
|
||||||
const expressionResultRef = ref<InstanceType<typeof ExpressionOutput>>();
|
const expressionResultRef = ref<InstanceType<typeof ExpressionOutput>>();
|
||||||
const theme = outputTheme();
|
const theme = outputTheme();
|
||||||
|
@ -122,6 +127,12 @@ async function onDrop(expression: string, event: MouseEvent) {
|
||||||
|
|
||||||
await dropInExpressionEditor(toRaw(inputEditor.value), event, expression);
|
await dropInExpressionEditor(toRaw(inputEditor.value), event, expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onResize(event: ResizeData) {
|
||||||
|
sidebarWidth.value = event.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onResizeThrottle = useThrottleFn(onResize, 10);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -136,6 +147,14 @@ async function onDrop(expression: string, event: MouseEvent) {
|
||||||
<Close height="18" width="18" />
|
<Close height="18" width="18" />
|
||||||
</button>
|
</button>
|
||||||
<div :class="$style.container">
|
<div :class="$style.container">
|
||||||
|
<N8nResizeWrapper
|
||||||
|
:width="sidebarWidth"
|
||||||
|
:min-width="200"
|
||||||
|
:style="{ width: `${sidebarWidth}px` }"
|
||||||
|
:grid-size="8"
|
||||||
|
:supported-directions="['left', 'right']"
|
||||||
|
@resize="onResizeThrottle"
|
||||||
|
>
|
||||||
<div :class="$style.sidebar">
|
<div :class="$style.sidebar">
|
||||||
<N8nInput
|
<N8nInput
|
||||||
v-model="search"
|
v-model="search"
|
||||||
|
@ -158,6 +177,7 @@ async function onDrop(expression: string, event: MouseEvent) {
|
||||||
context="modal"
|
context="modal"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</N8nResizeWrapper>
|
||||||
|
|
||||||
<div :class="$style.io">
|
<div :class="$style.io">
|
||||||
<div :class="$style.input">
|
<div :class="$style.input">
|
||||||
|
@ -239,7 +259,7 @@ async function onDrop(expression: string, event: MouseEvent) {
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-2xs);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,10 @@ import { LOCAL_STORAGE_MAIN_PANEL_RELATIVE_WIDTH, MAIN_NODE_PANEL_WIDTH } from '
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { ndvEventBus } from '@/event-bus';
|
import { ndvEventBus } from '@/event-bus';
|
||||||
import NDVFloatingNodes from '@/components/NDVFloatingNodes.vue';
|
import NDVFloatingNodes from '@/components/NDVFloatingNodes.vue';
|
||||||
import { useDebounce } from '@/composables/useDebounce';
|
|
||||||
import type { MainPanelType, XYPosition } from '@/Interface';
|
import type { MainPanelType, XYPosition } from '@/Interface';
|
||||||
import { ref, onMounted, onBeforeUnmount, computed, watch } from 'vue';
|
import { ref, onMounted, onBeforeUnmount, computed, watch } from 'vue';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
|
import { useThrottleFn } from '@vueuse/core';
|
||||||
|
|
||||||
const SIDE_MARGIN = 24;
|
const SIDE_MARGIN = 24;
|
||||||
const SIDE_PANELS_MARGIN = 80;
|
const SIDE_PANELS_MARGIN = 80;
|
||||||
|
@ -34,7 +34,8 @@ interface Props {
|
||||||
nodeType: INodeTypeDescription | null;
|
nodeType: INodeTypeDescription | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { callDebounced } = useDebounce();
|
const throttledOnResize = useThrottleFn(onResize, 100);
|
||||||
|
|
||||||
const ndvStore = useNDVStore();
|
const ndvStore = useNDVStore();
|
||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
|
|
||||||
|
@ -292,9 +293,9 @@ function onResizeEnd() {
|
||||||
storePositionData();
|
storePositionData();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onResizeDebounced(data: { direction: string; x: number; width: number }) {
|
function onResizeThrottle(data: { direction: string; x: number; width: number }) {
|
||||||
if (initialized.value) {
|
if (initialized.value) {
|
||||||
void callDebounced(onResize, { debounceTime: 10, trailing: true }, data);
|
void throttledOnResize(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,13 +369,13 @@ function onDragEnd() {
|
||||||
<slot name="output"></slot>
|
<slot name="output"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.mainPanel" :style="mainPanelStyles">
|
<div :class="$style.mainPanel" :style="mainPanelStyles">
|
||||||
<n8n-resize-wrapper
|
<N8nResizeWrapper
|
||||||
:is-resizing-enabled="currentNodePaneType !== 'unknown'"
|
:is-resizing-enabled="currentNodePaneType !== 'unknown'"
|
||||||
:width="relativeWidthToPx(mainPanelDimensions.relativeWidth)"
|
:width="relativeWidthToPx(mainPanelDimensions.relativeWidth)"
|
||||||
:min-width="MIN_PANEL_WIDTH"
|
:min-width="MIN_PANEL_WIDTH"
|
||||||
:grid-size="20"
|
:grid-size="20"
|
||||||
:supported-directions="supportedResizeDirections"
|
:supported-directions="supportedResizeDirections"
|
||||||
@resize="onResizeDebounced"
|
@resize="onResizeThrottle"
|
||||||
@resizeend="onResizeEnd"
|
@resizeend="onResizeEnd"
|
||||||
>
|
>
|
||||||
<div :class="$style.dragButtonContainer">
|
<div :class="$style.dragButtonContainer">
|
||||||
|
@ -391,7 +392,7 @@ function onDragEnd() {
|
||||||
<div :class="{ [$style.mainPanelInner]: true, [$style.dragging]: isDragging }">
|
<div :class="{ [$style.mainPanelInner]: true, [$style.dragging]: isDragging }">
|
||||||
<slot name="main" />
|
<slot name="main" />
|
||||||
</div>
|
</div>
|
||||||
</n8n-resize-wrapper>
|
</N8nResizeWrapper>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Reference in a new issue