feat(editor-ui): Resizable main panel (#3980)

* Introduce node deprecation (#3930)

 Introduce node deprecation

* 🚧 Scaffold out Code node

* 👕 Fix lint

* 📘 Create types file

* 🚚 Rename theme

* 🔥 Remove unneeded prop

*  Override keybindings

*  Expand lintings

*  Create editor content getter

* 🚚 Ensure all helpers use `$`

*  Add autocompletion

* ♻️ Refactore Resize UI lib component, allow to use it in different than n8n-sticky context

* 🚧 Use variable width for node settings and allow for resizing

*  Use store to keep track of wide and regular main panel widths

* ♻️ Extract Resize wrapper from the Sticky and create a story for it

* 🐛 Fixed cherry-pick conflicts

*  Filter out welcome note node

*  Convey error line number

*  Highlight error line

*  Restore logging from node

*  More autocompletions

*  Streamline completions

* 💄 Fix drag-button border

* ✏️ Update placeholders

*  Update linter to new methods

*  Preserve main panel width in local storage

* 🐛 Fallback to max size size if window is too big

* 🔥 Remove `$nodeItem` completions

*  Re-update placeholders

* 🎨 Fix formatting

* 📦 Update `package-lock.json`

*  Refresh with multi-line empty string

* ♻️ Refactored DraggablePanels to use relative units and implemented independent resizing, cleaned store

* 🐛 Re-implement dragging indicators and move border styles to NDVDraggablePanels component

* 🚨 Fix semis

* 🚨 Remove unsused UI state props

* ♻️ Use only relative left position and calculate right based on it, fix quirks

* 🚨Fix linting error

* ♻️ Store and retrieve main panel dimensions from store to make them persistable in the same app mount session

* 🐛 Prevent resizing of unknown nodes

* ♻️ Add typings for `nodeType` prop, remove unused `convertRemToPixels` import

* 🏷️ Add typings for `nodeType` prop in NodeSettings.vue

* 🐛 Prevent the main panel resize below 280px

* 🐛 Fix inputless panel left position

*  Resize resource locator on main panel size change

* 🐛 Resize resource locator on window resize

Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
This commit is contained in:
OlegIvaniv
2022-09-22 17:41:15 +02:00
committed by GitHub
parent 8eeed77edb
commit d01f7d4d93
16 changed files with 510 additions and 182 deletions

View File

@@ -0,0 +1,77 @@
import { action } from '@storybook/addon-actions';
import N8nResizeWrapper from './ResizeWrapper.vue';
export default {
title: 'Atoms/ResizeWrapper',
component: N8nResizeWrapper,
};
const methods = {
onInput: action('input'),
onResize(resizeData) {
action('resize', resizeData);
this.newHeight = resizeData.height;
this.newWidth = resizeData.width;
},
onResizeEnd: action('resizeend'),
onResizeStart: action('resizestart'),
};
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: {
N8nResizeWrapper,
},
data() {
return {
newWidth: this.width,
newHeight: this.height,
background: "linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%)",
};
},
computed: {
containerStyles() {
return {
width: `${this.newWidth}px`,
height: `${this.newHeight}px`,
background: this.background,
};
},
},
template:
`<div style="width: fit-content; height: fit-content">
<n8n-resize-wrapper
v-bind="$props"
@resize="onResize"
@resizeend="onResizeEnd"
@resizestart="onResizeStart"
@input="onInput"
:width="newWidth"
:height="newHeight"
>
<div :style="containerStyles" />
</n8n-resize-wrapper>
</div>`,
methods,
});
export const Resize = Template.bind({});
Resize.args = {
width: 200,
height: 200,
minWidth: 200,
minHeight: 200,
scale: 1,
gridSize: 20,
isResizingEnabled: true,
supportedDirections: [
"right",
"top",
"bottom",
"left",
"topLeft",
"topRight",
"bottomLeft",
"bottomRight",
],
};

View File

@@ -1,34 +1,24 @@
<template>
<div :class="$style.resize">
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="right" :class="[$style.resizer, $style.right]" />
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="left" :class="[$style.resizer, $style.left]" />
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="top" :class="[$style.resizer, $style.top]" />
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="bottom" :class="[$style.resizer, $style.bottom]" />
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="top-left" :class="[$style.resizer, $style.topLeft]" />
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="top-right" :class="[$style.resizer, $style.topRight]" />
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="bottom-left" :class="[$style.resizer, $style.bottomLeft]" />
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="bottom-right" :class="[$style.resizer, $style.bottomRight]" />
<div
v-for="direction in enabledDirections"
:key="direction"
:data-dir="direction"
:class="[$style.resizer, $style[direction]]"
@mousedown="resizerMove"
/>
<slot></slot>
</div>
</template>
<script lang="ts">
const cursorMap: { [key: string]: string } = {
right: 'ew-resize',
top: 'ns-resize',
bottom: 'ns-resize',
left: 'ew-resize',
'top-left': 'nw-resize',
'top-right' : 'ne-resize',
'bottom-left': 'sw-resize',
'bottom-right': 'se-resize',
};
import Vue from 'vue';
function closestNumber(value: number, divisor: number): number {
let q = value / divisor;
let n1 = divisor * q;
const q = value / divisor;
const n1 = divisor * q;
let n2 = (value * divisor) > 0 ?
const n2 = (value * divisor) > 0 ?
(divisor * (q + 1)) : (divisor * (q - 1));
if (Math.abs(value - n1) < Math.abs(value - n2))
@@ -37,7 +27,7 @@ function closestNumber(value: number, divisor: number): number {
return n2;
}
function getSize(delta: number, min: number, virtual: number, gridSize: number): number {
function getSize(min: number, virtual: number, gridSize: number): number {
const target = closestNumber(virtual, gridSize);
if (target >= min && virtual > 0) {
return target;
@@ -46,7 +36,16 @@ function getSize(delta: number, min: number, virtual: number, gridSize: number):
return min;
};
import Vue from 'vue';
const directionsCursorMaps: { [key: string]: string } = {
right: 'ew-resize',
top: 'ns-resize',
bottom: 'ns-resize',
left: 'ew-resize',
topLeft: 'nw-resize',
topRight : 'ne-resize',
bottomLeft: 'sw-resize',
bottomRight: 'se-resize',
};
export default Vue.extend({
name: 'n8n-resize',
@@ -74,9 +73,14 @@ export default Vue.extend({
gridSize: {
type: Number,
},
supportedDirections: {
type: Array,
default: () => [],
},
},
data() {
return {
directionsCursorMaps,
dir: '',
dHeight: 0,
dWidth: 0,
@@ -86,6 +90,16 @@ export default Vue.extend({
y: 0,
};
},
computed: {
enabledDirections() {
const availableDirections = Object.keys(directionsCursorMaps);
if(this.isResizingEnabled === false) return [];
if(this.supportedDirections.length === 0) return availableDirections;
return this.supportedDirections;
},
},
methods: {
resizerMove(event: MouseEvent) {
event.preventDefault();
@@ -93,10 +107,10 @@ export default Vue.extend({
const targetResizer = event.target as { dataset: { dir: string } } | null;
if (targetResizer) {
this.dir = targetResizer.dataset.dir;
this.dir = targetResizer.dataset.dir.toLocaleLowerCase();
}
document.body.style.cursor = cursorMap[this.dir];
document.body.style.cursor = directionsCursorMaps[this.dir];
this.x = event.pageX;
this.y = event.pageY;
@@ -137,17 +151,20 @@ export default Vue.extend({
this.vHeight = this.vHeight + deltaHeight;
this.vWidth = this.vWidth + deltaWidth;
const height = getSize(deltaHeight, this.minHeight, this.vHeight, this.gridSize);
const width = getSize(deltaWidth, this.minWidth, this.vWidth, this.gridSize);
const height = getSize(this.minHeight, this.vHeight, this.gridSize);
const width = getSize(this.minWidth, this.vWidth, this.gridSize);
const dX = left && width !== this.width ? -1 * (width - this.width) : 0;
const dY = top && height !== this.height ? -1 * (height - this.height): 0;
const x = event.x;
const y = event.y;
const direction = this.dir;
this.$emit('resize', { height, width, dX, dY });
this.$emit('resize', { height, width, dX, dY, x, y, direction });
this.dHeight = dHeight;
this.dWidth = dWidth;
},
mouseUp(event: Event) {
mouseUp(event: MouseEvent) {
event.preventDefault();
event.stopPropagation();
this.$emit('resizeend');
@@ -162,7 +179,7 @@ export default Vue.extend({
<style lang="scss" module>
.resize {
position: absolute;
position: relative;
width: 100%;
height: 100%;
z-index: 2;
@@ -170,7 +187,7 @@ export default Vue.extend({
.resizer {
position: absolute;
z-index: 2;
z-index: 3;
}
.right {
@@ -211,7 +228,6 @@ export default Vue.extend({
top: -3px;
left: -3px;
cursor: nw-resize;
z-index: 3;
}
.topRight {
@@ -220,7 +236,6 @@ export default Vue.extend({
top: -3px;
right: -3px;
cursor: ne-resize;
z-index: 3;
}
.bottomLeft {
@@ -229,7 +244,6 @@ export default Vue.extend({
bottom: -3px;
left: -3px;
cursor: sw-resize;
z-index: 3;
}
.bottomRight {
@@ -238,6 +252,5 @@ export default Vue.extend({
bottom: -3px;
right: -3px;
cursor: se-resize;
z-index: 3;
}
</style>

View File

@@ -0,0 +1,3 @@
import ResizeWrapper from './ResizeWrapper.vue';
export default ResizeWrapper;

View File

@@ -4,7 +4,7 @@
:style="styles"
@keydown.prevent
>
<resize
<n8n-resize-wrapper
:isResizingEnabled="!readOnly"
:height="height"
:width="width"
@@ -60,14 +60,14 @@
</n8n-text>
</div>
</template>
</resize>
</n8n-resize-wrapper>
</div>
</template>
<script lang="ts">
import N8nInput from '../N8nInput';
import N8nMarkdown from '../N8nMarkdown';
import Resize from './Resize.vue';
import N8nResizeWrapper from '../N8nResizeWrapper';
import N8nText from '../N8nText';
import Locale from '../../mixins/locale';
import mixins from 'vue-typed-mixins';
@@ -121,7 +121,7 @@ export default mixins(Locale).extend({
components: {
N8nInput,
N8nMarkdown,
Resize,
N8nResizeWrapper,
N8nText,
},
data() {

View File

@@ -40,6 +40,7 @@ import N8nTree from '../components/N8nTree';
import N8nUserInfo from '../components/N8nUserInfo';
import N8nUserSelect from '../components/N8nUserSelect';
import N8nUsersList from '../components/N8nUsersList';
import N8nResizeWrapper from '../components/N8nResizeWrapper';
export default {
install: (app: typeof Vue, options?: {}) => {
@@ -84,5 +85,6 @@ export default {
app.component('n8n-tree', N8nTree);
app.component('n8n-users-list', N8nUsersList);
app.component('n8n-user-select', N8nUserSelect);
app.component('n8n-resize-wrapper', N8nResizeWrapper);
},
};