feat(editor): Make the left sidebar in Expressions editor draggable (#11838)

This commit is contained in:
Dana 2024-11-26 20:13:53 +01:00 committed by GitHub
parent 1d80225d26
commit a713b3ed25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 83 additions and 58 deletions

View file

@ -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];

View file

@ -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';

View file

@ -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';

View 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;
}

View file

@ -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>

View file

@ -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>

View file

@ -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';

View file

@ -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,28 +147,37 @@ 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">
<div :class="$style.sidebar"> <N8nResizeWrapper
<N8nInput :width="sidebarWidth"
v-model="search" :min-width="200"
size="small" :style="{ width: `${sidebarWidth}px` }"
:class="$style.search" :grid-size="8"
:placeholder="i18n.baseText('ndv.search.placeholder.input.schema')" :supported-directions="['left', 'right']"
> @resize="onResizeThrottle"
<template #prefix> >
<N8nIcon :class="$style.ioSearchIcon" icon="search" /> <div :class="$style.sidebar">
</template> <N8nInput
</N8nInput> v-model="search"
size="small"
:class="$style.search"
:placeholder="i18n.baseText('ndv.search.placeholder.input.schema')"
>
<template #prefix>
<N8nIcon :class="$style.ioSearchIcon" icon="search" />
</template>
</N8nInput>
<RunDataSchema <RunDataSchema
:class="$style.schema" :class="$style.schema"
:search="appliedSearch" :search="appliedSearch"
:nodes="parentNodes" :nodes="parentNodes"
:mapping-enabled="!isReadOnly" :mapping-enabled="!isReadOnly"
:connection-type="NodeConnectionType.Main" :connection-type="NodeConnectionType.Main"
pane-type="input" pane-type="input"
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%;
} }

View file

@ -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>