mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
feat(editor): Make focus panel resizable (no-changelog) (#17289)
This commit is contained in:
@@ -14,13 +14,21 @@ function closestNumber(value: number, divisor: number): number {
|
|||||||
return n2;
|
return n2;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSize(min: number, virtual: number, gridSize: number): number {
|
function getSize(min: number, virtual: number, gridSize: number, max: number): number {
|
||||||
const target = closestNumber(virtual, gridSize);
|
if (virtual <= 0) {
|
||||||
if (target >= min && virtual > 0) {
|
return min;
|
||||||
return target;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const target = closestNumber(virtual, gridSize);
|
||||||
|
|
||||||
|
if (target <= min) {
|
||||||
return min;
|
return min;
|
||||||
|
}
|
||||||
|
if (target >= max) {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ResizeProps {
|
interface ResizeProps {
|
||||||
@@ -28,7 +36,9 @@ interface ResizeProps {
|
|||||||
height?: number;
|
height?: number;
|
||||||
width?: number;
|
width?: number;
|
||||||
minHeight?: number;
|
minHeight?: number;
|
||||||
|
maxHeight?: number;
|
||||||
minWidth?: number;
|
minWidth?: number;
|
||||||
|
maxWidth?: number;
|
||||||
scale?: number;
|
scale?: number;
|
||||||
gridSize?: number;
|
gridSize?: number;
|
||||||
supportedDirections?: Direction[];
|
supportedDirections?: Direction[];
|
||||||
@@ -41,7 +51,9 @@ const props = withDefaults(defineProps<ResizeProps>(), {
|
|||||||
height: 0,
|
height: 0,
|
||||||
width: 0,
|
width: 0,
|
||||||
minHeight: 0,
|
minHeight: 0,
|
||||||
|
maxHeight: Number.POSITIVE_INFINITY,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
|
maxWidth: Number.POSITIVE_INFINITY,
|
||||||
scale: 1,
|
scale: 1,
|
||||||
gridSize: 20,
|
gridSize: 20,
|
||||||
outset: false,
|
outset: false,
|
||||||
@@ -109,8 +121,8 @@ const mouseMove = (event: MouseEvent) => {
|
|||||||
|
|
||||||
state.vHeight.value = state.vHeight.value + deltaHeight;
|
state.vHeight.value = state.vHeight.value + deltaHeight;
|
||||||
state.vWidth.value = state.vWidth.value + deltaWidth;
|
state.vWidth.value = state.vWidth.value + deltaWidth;
|
||||||
const height = getSize(props.minHeight, state.vHeight.value, props.gridSize);
|
const height = getSize(props.minHeight, state.vHeight.value, props.gridSize, props.maxHeight);
|
||||||
const width = getSize(props.minWidth, state.vWidth.value, props.gridSize);
|
const width = getSize(props.minWidth, state.vWidth.value, props.gridSize, props.maxWidth);
|
||||||
|
|
||||||
const dX = left && width !== props.width ? -1 * (width - props.width) : 0;
|
const dX = left && width !== props.width ? -1 * (width - props.width) : 0;
|
||||||
const dY = top && height !== props.height ? -1 * (height - props.height) : 0;
|
const dY = top && height !== props.height ? -1 * (height - props.height) : 0;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useFocusPanelStore } from '@/stores/focusPanel.store';
|
import { useFocusPanelStore } from '@/stores/focusPanel.store';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import { N8nText, N8nInput } from '@n8n/design-system';
|
import { N8nText, N8nInput, N8nResizeWrapper } from '@n8n/design-system';
|
||||||
import { computed, nextTick, ref } from 'vue';
|
import { computed, nextTick, ref } from 'vue';
|
||||||
import { useI18n } from '@n8n/i18n';
|
import { useI18n } from '@n8n/i18n';
|
||||||
import {
|
import {
|
||||||
@@ -26,7 +26,8 @@ import { useEnvironmentsStore } from '@/stores/environments.ee.store';
|
|||||||
import { useDebounce } from '@/composables/useDebounce';
|
import { useDebounce } from '@/composables/useDebounce';
|
||||||
import { htmlEditorEventBus } from '@/event-bus';
|
import { htmlEditorEventBus } from '@/event-bus';
|
||||||
import { hasFocusOnInput, isFocusableEl } from '@/utils/typesUtils';
|
import { hasFocusOnInput, isFocusableEl } from '@/utils/typesUtils';
|
||||||
import type { TargetNodeParameterContext } from '@/Interface';
|
import type { ResizeData, TargetNodeParameterContext } from '@/Interface';
|
||||||
|
import { useThrottleFn } from '@vueuse/core';
|
||||||
|
|
||||||
defineOptions({ name: 'FocusPanel' });
|
defineOptions({ name: 'FocusPanel' });
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ const resolvedParameter = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const focusPanelActive = computed(() => focusPanelStore.focusPanelActive);
|
const focusPanelActive = computed(() => focusPanelStore.focusPanelActive);
|
||||||
|
const focusPanelWidth = computed(() => focusPanelStore.focusPanelWidth);
|
||||||
|
|
||||||
const isDisabled = computed(() => {
|
const isDisabled = computed(() => {
|
||||||
if (!resolvedParameter.value) return false;
|
if (!resolvedParameter.value) return false;
|
||||||
@@ -244,10 +246,26 @@ function optionSelected(command: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const valueChangedDebounced = debounce(valueChanged, { debounceTime: 0 });
|
const valueChangedDebounced = debounce(valueChanged, { debounceTime: 0 });
|
||||||
|
|
||||||
|
function onResize(event: ResizeData) {
|
||||||
|
focusPanelStore.updateWidth(event.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onResizeThrottle = useThrottleFn(onResize, 10);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="focusPanelActive" :class="$style.container" @keydown.stop>
|
<div v-if="focusPanelActive" :class="$style.wrapper" @keydown.stop>
|
||||||
|
<N8nResizeWrapper
|
||||||
|
:width="focusPanelWidth"
|
||||||
|
:supported-directions="['left']"
|
||||||
|
:min-width="300"
|
||||||
|
:max-width="1000"
|
||||||
|
:grid-size="8"
|
||||||
|
:style="{ width: `${focusPanelWidth}px` }"
|
||||||
|
@resize="onResizeThrottle"
|
||||||
|
>
|
||||||
|
<div :class="$style.container">
|
||||||
<div :class="$style.header">
|
<div :class="$style.header">
|
||||||
<N8nText size="small" :bold="true">
|
<N8nText size="small" :bold="true">
|
||||||
{{ locale.baseText('nodeView.focusPanel.title') }}
|
{{ locale.baseText('nodeView.focusPanel.title') }}
|
||||||
@@ -381,16 +399,24 @@ const valueChangedDebounced = debounce(valueChanged, { debounceTime: 0 });
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</N8nResizeWrapper>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.container {
|
.wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row nowrap;
|
||||||
width: 528px;
|
|
||||||
border-left: 1px solid var(--color-foreground-base);
|
border-left: 1px solid var(--color-foreground-base);
|
||||||
background: var(--color-foreground-light);
|
background: var(--color-foreground-light);
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.closeButton:hover {
|
.closeButton:hover {
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import { useWorkflowsStore } from './workflows.store';
|
|||||||
import { LOCAL_STORAGE_FOCUS_PANEL, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants';
|
import { LOCAL_STORAGE_FOCUS_PANEL, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants';
|
||||||
import { useStorage } from '@/composables/useStorage';
|
import { useStorage } from '@/composables/useStorage';
|
||||||
|
|
||||||
|
const DEFAULT_PANEL_WIDTH = 528;
|
||||||
|
|
||||||
type FocusedNodeParameter = {
|
type FocusedNodeParameter = {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
parameter: INodeProperties;
|
parameter: INodeProperties;
|
||||||
@@ -27,6 +29,7 @@ export type RichFocusedNodeParameter = FocusedNodeParameter & {
|
|||||||
type FocusPanelData = {
|
type FocusPanelData = {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
parameters: FocusedNodeParameter[];
|
parameters: FocusedNodeParameter[];
|
||||||
|
width?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FocusPanelDataByWid = Record<string, FocusPanelData>;
|
type FocusPanelDataByWid = Record<string, FocusPanelData>;
|
||||||
@@ -53,6 +56,7 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const focusPanelActive = computed(() => currentFocusPanelData.value.isActive);
|
const focusPanelActive = computed(() => currentFocusPanelData.value.isActive);
|
||||||
|
const focusPanelWidth = computed(() => currentFocusPanelData.value.width ?? DEFAULT_PANEL_WIDTH);
|
||||||
const _focusedNodeParameters = computed(() => currentFocusPanelData.value.parameters);
|
const _focusedNodeParameters = computed(() => currentFocusPanelData.value.parameters);
|
||||||
|
|
||||||
// An unenriched parameter indicates a missing nodeId
|
// An unenriched parameter indicates a missing nodeId
|
||||||
@@ -74,11 +78,13 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
|
|||||||
parameters,
|
parameters,
|
||||||
isActive,
|
isActive,
|
||||||
wid = workflowsStore.workflowId,
|
wid = workflowsStore.workflowId,
|
||||||
|
width = undefined,
|
||||||
removeEmpty = false,
|
removeEmpty = false,
|
||||||
}: {
|
}: {
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
parameters?: FocusedNodeParameter[];
|
parameters?: FocusedNodeParameter[];
|
||||||
wid?: string;
|
wid?: string;
|
||||||
|
width?: number;
|
||||||
removeEmpty?: boolean;
|
removeEmpty?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const focusPanelDataCurrent = focusPanelData.value;
|
const focusPanelDataCurrent = focusPanelData.value;
|
||||||
@@ -92,6 +98,7 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
|
|||||||
[wid]: {
|
[wid]: {
|
||||||
isActive: isActive ?? focusPanelActive.value,
|
isActive: isActive ?? focusPanelActive.value,
|
||||||
parameters: parameters ?? _focusedNodeParameters.value,
|
parameters: parameters ?? _focusedNodeParameters.value,
|
||||||
|
width,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -127,6 +134,10 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
|
|||||||
_setOptions({ isActive: !focusPanelActive.value });
|
_setOptions({ isActive: !focusPanelActive.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateWidth(width: number) {
|
||||||
|
_setOptions({ width });
|
||||||
|
}
|
||||||
|
|
||||||
function isRichParameter(
|
function isRichParameter(
|
||||||
p: RichFocusedNodeParameter | FocusedNodeParameter,
|
p: RichFocusedNodeParameter | FocusedNodeParameter,
|
||||||
): p is RichFocusedNodeParameter {
|
): p is RichFocusedNodeParameter {
|
||||||
@@ -141,5 +152,7 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
|
|||||||
closeFocusPanel,
|
closeFocusPanel,
|
||||||
toggleFocusPanel,
|
toggleFocusPanel,
|
||||||
onNewWorkflowSave,
|
onNewWorkflowSave,
|
||||||
|
updateWidth,
|
||||||
|
focusPanelWidth,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user