mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
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:
parent
8eeed77edb
commit
d01f7d4d93
62
package-lock.json
generated
62
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.194.0",
|
||||
"version": "0.195.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "n8n",
|
||||
"version": "0.194.0",
|
||||
"version": "0.195.3",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"packages/@n8n_io/*"
|
||||
|
@ -52142,7 +52142,7 @@
|
|||
},
|
||||
"packages/cli": {
|
||||
"name": "n8n",
|
||||
"version": "0.194.0",
|
||||
"version": "0.195.3",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"@oclif/command": "^1.5.18",
|
||||
|
@ -52186,10 +52186,10 @@
|
|||
"lodash.split": "^4.4.2",
|
||||
"lodash.unset": "^4.5.2",
|
||||
"mysql2": "~2.3.0",
|
||||
"n8n-core": "~0.134.0",
|
||||
"n8n-editor-ui": "~0.160.0",
|
||||
"n8n-nodes-base": "~0.192.0",
|
||||
"n8n-workflow": "~0.116.0",
|
||||
"n8n-core": "~0.135.0",
|
||||
"n8n-editor-ui": "~0.161.1",
|
||||
"n8n-nodes-base": "~0.193.1",
|
||||
"n8n-workflow": "~0.117.0",
|
||||
"nodemailer": "^6.7.1",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"open": "^7.0.0",
|
||||
|
@ -52268,7 +52268,7 @@
|
|||
},
|
||||
"packages/core": {
|
||||
"name": "n8n-core",
|
||||
"version": "0.134.0",
|
||||
"version": "0.135.0",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
|
@ -52280,7 +52280,7 @@
|
|||
"form-data": "^4.0.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mime-types": "^2.1.27",
|
||||
"n8n-workflow": "~0.116.0",
|
||||
"n8n-workflow": "~0.117.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"qs": "^6.10.1",
|
||||
|
@ -52367,7 +52367,7 @@
|
|||
},
|
||||
"packages/design-system": {
|
||||
"name": "n8n-design-system",
|
||||
"version": "0.34.0",
|
||||
"version": "0.35.0",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"element-ui": "~2.15.7",
|
||||
|
@ -52434,14 +52434,14 @@
|
|||
},
|
||||
"packages/editor-ui": {
|
||||
"name": "n8n-editor-ui",
|
||||
"version": "0.160.0",
|
||||
"version": "0.161.1",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"@fontsource/open-sans": "^4.5.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||
"luxon": "^2.3.0",
|
||||
"monaco-editor": "^0.30.1",
|
||||
"n8n-design-system": "~0.34.0",
|
||||
"n8n-design-system": "~0.35.0",
|
||||
"timeago.js": "^4.0.2",
|
||||
"v-click-outside": "^3.1.2",
|
||||
"vue-fragment": "1.5.1",
|
||||
|
@ -52487,7 +52487,7 @@
|
|||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"monaco-editor-webpack-plugin": "^5.0.0",
|
||||
"n8n-workflow": "~0.116.0",
|
||||
"n8n-workflow": "~0.117.0",
|
||||
"normalize-wheel": "^1.0.1",
|
||||
"prismjs": "^1.17.1",
|
||||
"quill": "^2.0.0-dev.3",
|
||||
|
@ -52513,7 +52513,7 @@
|
|||
},
|
||||
"packages/node-dev": {
|
||||
"name": "n8n-node-dev",
|
||||
"version": "0.73.0",
|
||||
"version": "0.74.0",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"@oclif/command": "^1.5.18",
|
||||
|
@ -52521,8 +52521,8 @@
|
|||
"change-case": "^4.1.1",
|
||||
"fast-glob": "^3.2.5",
|
||||
"inquirer": "^7.0.1",
|
||||
"n8n-core": "~0.134.0",
|
||||
"n8n-workflow": "~0.116.0",
|
||||
"n8n-core": "~0.135.0",
|
||||
"n8n-workflow": "~0.117.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"replace-in-file": "^6.0.0",
|
||||
"request": "^2.88.2",
|
||||
|
@ -52544,7 +52544,7 @@
|
|||
},
|
||||
"packages/nodes-base": {
|
||||
"name": "n8n-nodes-base",
|
||||
"version": "0.192.0",
|
||||
"version": "0.193.1",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"@kafkajs/confluent-schema-registry": "1.0.6",
|
||||
|
@ -52579,7 +52579,7 @@
|
|||
"mqtt": "4.2.6",
|
||||
"mssql": "^8.1.2",
|
||||
"mysql2": "~2.3.0",
|
||||
"n8n-core": "~0.134.0",
|
||||
"n8n-core": "~0.135.0",
|
||||
"node-html-markdown": "^1.1.3",
|
||||
"node-ssh": "^12.0.0",
|
||||
"nodemailer": "^6.7.1",
|
||||
|
@ -52636,7 +52636,7 @@
|
|||
"eslint-plugin-n8n-nodes-base": "^1.9.3",
|
||||
"gulp": "^4.0.0",
|
||||
"jest": "^27.4.7",
|
||||
"n8n-workflow": "~0.116.0",
|
||||
"n8n-workflow": "~0.117.0",
|
||||
"ts-jest": "^27.1.3",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~4.6.0"
|
||||
|
@ -52686,7 +52686,7 @@
|
|||
},
|
||||
"packages/workflow": {
|
||||
"name": "n8n-workflow",
|
||||
"version": "0.116.0",
|
||||
"version": "0.117.0",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"@n8n_io/riot-tmpl": "^1.0.1",
|
||||
|
@ -81496,10 +81496,10 @@
|
|||
"lodash.split": "^4.4.2",
|
||||
"lodash.unset": "^4.5.2",
|
||||
"mysql2": "~2.3.0",
|
||||
"n8n-core": "~0.134.0",
|
||||
"n8n-editor-ui": "~0.160.0",
|
||||
"n8n-nodes-base": "~0.192.0",
|
||||
"n8n-workflow": "~0.116.0",
|
||||
"n8n-core": "~0.135.0",
|
||||
"n8n-editor-ui": "~0.161.1",
|
||||
"n8n-nodes-base": "~0.193.1",
|
||||
"n8n-workflow": "~0.117.0",
|
||||
"nodemailer": "^6.7.1",
|
||||
"nodemon": "^2.0.2",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
|
@ -81553,7 +81553,7 @@
|
|||
"jest": "^27.4.7",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mime-types": "^2.1.27",
|
||||
"n8n-workflow": "~0.116.0",
|
||||
"n8n-workflow": "~0.117.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"qs": "^6.10.1",
|
||||
|
@ -81699,8 +81699,8 @@
|
|||
"luxon": "^2.3.0",
|
||||
"monaco-editor": "^0.30.1",
|
||||
"monaco-editor-webpack-plugin": "^5.0.0",
|
||||
"n8n-design-system": "~0.34.0",
|
||||
"n8n-workflow": "~0.116.0",
|
||||
"n8n-design-system": "~0.35.0",
|
||||
"n8n-workflow": "~0.117.0",
|
||||
"normalize-wheel": "^1.0.1",
|
||||
"prismjs": "^1.17.1",
|
||||
"quill": "^2.0.0-dev.3",
|
||||
|
@ -81746,8 +81746,8 @@
|
|||
"change-case": "^4.1.1",
|
||||
"fast-glob": "^3.2.5",
|
||||
"inquirer": "^7.0.1",
|
||||
"n8n-core": "~0.134.0",
|
||||
"n8n-workflow": "~0.116.0",
|
||||
"n8n-core": "~0.135.0",
|
||||
"n8n-workflow": "~0.117.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"replace-in-file": "^6.0.0",
|
||||
"request": "^2.88.2",
|
||||
|
@ -81824,8 +81824,8 @@
|
|||
"mqtt": "4.2.6",
|
||||
"mssql": "^8.1.2",
|
||||
"mysql2": "~2.3.0",
|
||||
"n8n-core": "~0.134.0",
|
||||
"n8n-workflow": "~0.116.0",
|
||||
"n8n-core": "~0.135.0",
|
||||
"n8n-workflow": "~0.117.0",
|
||||
"node-html-markdown": "^1.1.3",
|
||||
"node-ssh": "^12.0.0",
|
||||
"nodemailer": "^6.7.1",
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
};
|
|
@ -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>
|
|
@ -0,0 +1,3 @@
|
|||
import ResizeWrapper from './ResizeWrapper.vue';
|
||||
|
||||
export default ResizeWrapper;
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -934,6 +934,7 @@ export interface IUiState {
|
|||
modals: {
|
||||
[key: string]: IModalState;
|
||||
};
|
||||
mainPanelDimensions: {[key: string]: {[key: string]: number}};
|
||||
isPageLoading: boolean;
|
||||
currentView: string;
|
||||
ndv: {
|
||||
|
|
|
@ -7,31 +7,61 @@
|
|||
<slot name="output"></slot>
|
||||
</div>
|
||||
<div :class="$style.mainPanel" :style="mainPanelStyles">
|
||||
<div :class="$style.dragButtonContainer" @click="close">
|
||||
<PanelDragButton
|
||||
:class="{ [$style.draggable]: true, [$style.visible]: isDragging }"
|
||||
v-if="!hideInputAndOutput && isDraggable"
|
||||
:canMoveLeft="canMoveLeft"
|
||||
:canMoveRight="canMoveRight"
|
||||
@dragstart="onDragStart"
|
||||
@drag="onDrag"
|
||||
@dragend="onDragEnd"
|
||||
/>
|
||||
</div>
|
||||
<slot name="main"></slot>
|
||||
<n8n-resize-wrapper
|
||||
:isResizingEnabled="currentNodePaneType !== 'unknown'"
|
||||
:width="relativeWidthToPx(mainPanelDimensions.relativeWidth)"
|
||||
:minWidth="MIN_PANEL_WIDTH"
|
||||
:gridSize="20"
|
||||
@resize="onResize"
|
||||
@resizestart="onResizeStart"
|
||||
@resizeend="onResizeEnd"
|
||||
:supportedDirections="supportedResizeDirections"
|
||||
>
|
||||
<div :class="$style.dragButtonContainer">
|
||||
<PanelDragButton
|
||||
:class="{ [$style.draggable]: true, [$style.visible]: isDragging }"
|
||||
:canMoveLeft="canMoveLeft"
|
||||
:canMoveRight="canMoveRight"
|
||||
v-if="!hideInputAndOutput && isDraggable"
|
||||
@dragstart="onDragStart"
|
||||
@drag="onDrag"
|
||||
@dragend="onDragEnd"
|
||||
/>
|
||||
</div>
|
||||
<div :class="{ [$style.mainPanelInner]: true, [$style.dragging]: isDragging }">
|
||||
<slot name="main" />
|
||||
</div>
|
||||
</n8n-resize-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import Vue, { PropType } from 'vue';
|
||||
import { get } from 'lodash';
|
||||
|
||||
import { INodeTypeDescription } from 'n8n-workflow';
|
||||
import PanelDragButton from './PanelDragButton.vue';
|
||||
|
||||
const MAIN_PANEL_WIDTH = 360;
|
||||
import {
|
||||
LOCAL_STORAGE_MAIN_PANEL_RELATIVE_WIDTH,
|
||||
MAIN_NODE_PANEL_WIDTH,
|
||||
} from '@/constants';
|
||||
|
||||
|
||||
const SIDE_MARGIN = 24;
|
||||
const FIXED_PANEL_WIDTH = 320;
|
||||
const FIXED_PANEL_WIDTH_LARGE = 420;
|
||||
const MINIMUM_INPUT_PANEL_WIDTH = 320;
|
||||
const SIDE_PANELS_MARGIN = 80;
|
||||
const MIN_PANEL_WIDTH = 280;
|
||||
const PANEL_WIDTH = 320;
|
||||
const PANEL_WIDTH_LARGE = 420;
|
||||
|
||||
const initialMainPanelWidth:{ [key: string]: number } = {
|
||||
regular: MAIN_NODE_PANEL_WIDTH,
|
||||
dragless: MAIN_NODE_PANEL_WIDTH,
|
||||
unknown: MAIN_NODE_PANEL_WIDTH,
|
||||
inputless: MAIN_NODE_PANEL_WIDTH,
|
||||
wide: MAIN_NODE_PANEL_WIDTH * 2,
|
||||
};
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'NDVDraggablePanels',
|
||||
|
@ -48,124 +78,271 @@ export default Vue.extend({
|
|||
position: {
|
||||
type: Number,
|
||||
},
|
||||
nodeType: {
|
||||
type: Object as PropType<INodeTypeDescription>,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
data(): { windowWidth: number, isDragging: boolean, MIN_PANEL_WIDTH: number} {
|
||||
return {
|
||||
windowWidth: 0,
|
||||
windowWidth: 1,
|
||||
isDragging: false,
|
||||
MIN_PANEL_WIDTH,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.setTotalWidth();
|
||||
|
||||
/*
|
||||
Only set(or restore) initial position if `mainPanelDimensions`
|
||||
is at the default state({relativeLeft:1, relativeRight: 1, relativeWidth: 1}) to make sure we use store values if they are set
|
||||
*/
|
||||
if(this.mainPanelDimensions.relativeLeft === 1 && this.mainPanelDimensions.relativeRight === 1) {
|
||||
this.setMainPanelWidth();
|
||||
this.setPositions(this.getInitialLeftPosition(this.mainPanelDimensions.relativeWidth));
|
||||
this.restorePositionData();
|
||||
}
|
||||
|
||||
window.addEventListener('resize', this.setTotalWidth);
|
||||
this.$emit('init', { position: this.getRelativePosition() });
|
||||
this.$emit('init', { position: this.mainPanelDimensions.relativeLeft });
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener('resize', this.setTotalWidth);
|
||||
},
|
||||
computed: {
|
||||
fixedPanelWidth() {
|
||||
if (this.windowWidth > 1700) {
|
||||
return FIXED_PANEL_WIDTH_LARGE;
|
||||
}
|
||||
|
||||
return FIXED_PANEL_WIDTH;
|
||||
mainPanelDimensions(): {
|
||||
relativeWidth: number,
|
||||
relativeLeft: number,
|
||||
relativeRight: number
|
||||
} {
|
||||
return this.$store.getters['ui/mainPanelDimensions'](this.currentNodePaneType);
|
||||
},
|
||||
mainPanelPosition(): number {
|
||||
if (typeof this.position === 'number') {
|
||||
return this.position;
|
||||
}
|
||||
supportedResizeDirections() {
|
||||
const supportedDirections = ['right'];
|
||||
|
||||
if (!this.isDraggable) {
|
||||
return this.fixedPanelWidth + MAIN_PANEL_WIDTH / 2 + SIDE_MARGIN;
|
||||
}
|
||||
|
||||
const relativePosition = this.$store.getters['ui/mainPanelPosition'] as number;
|
||||
|
||||
return relativePosition * this.windowWidth;
|
||||
if(this.isDraggable) supportedDirections.push('left');
|
||||
return supportedDirections;
|
||||
},
|
||||
currentNodePaneType() {
|
||||
if(!this.hasInputSlot) return 'inputless';
|
||||
if(!this.isDraggable) return 'dragless';
|
||||
if(this.nodeType === null) return 'unknown';
|
||||
return get(this, 'nodeType.parameterPane') || 'regular';
|
||||
},
|
||||
hasInputSlot() {
|
||||
return this.$slots.input !== undefined;
|
||||
},
|
||||
inputPanelMargin(): number {
|
||||
return !this.isDraggable? 0 : 80;
|
||||
return this.pxToRelativeWidth(SIDE_PANELS_MARGIN);
|
||||
},
|
||||
minWindowWidth() {
|
||||
return 2 * (SIDE_MARGIN + SIDE_PANELS_MARGIN) + MIN_PANEL_WIDTH;
|
||||
},
|
||||
minimumLeftPosition(): number {
|
||||
return SIDE_MARGIN + this.inputPanelMargin;
|
||||
if(this.windowWidth < this.minWindowWidth) return this.pxToRelativeWidth(1);
|
||||
|
||||
if(!this.hasInputSlot) return this.pxToRelativeWidth(SIDE_MARGIN);
|
||||
return this.pxToRelativeWidth(SIDE_MARGIN + 20) + this.inputPanelMargin;
|
||||
},
|
||||
maximumRightPosition(): number {
|
||||
return this.windowWidth - MAIN_PANEL_WIDTH - this.minimumLeftPosition;
|
||||
},
|
||||
mainPanelFinalPositionPx(): number {
|
||||
const padding = this.minimumLeftPosition;
|
||||
let pos = this.mainPanelPosition + MAIN_PANEL_WIDTH / 2;
|
||||
pos = Math.max(padding, pos - MAIN_PANEL_WIDTH);
|
||||
pos = Math.min(pos, this.maximumRightPosition);
|
||||
if(this.windowWidth < this.minWindowWidth) return this.pxToRelativeWidth(1);
|
||||
|
||||
return pos;
|
||||
return this.pxToRelativeWidth(SIDE_MARGIN + 20) + this.inputPanelMargin;
|
||||
},
|
||||
canMoveLeft(): boolean {
|
||||
return this.mainPanelFinalPositionPx > this.minimumLeftPosition;
|
||||
return this.mainPanelDimensions.relativeLeft > this.minimumLeftPosition;
|
||||
},
|
||||
canMoveRight(): boolean {
|
||||
return this.mainPanelFinalPositionPx < this.maximumRightPosition;
|
||||
return this.mainPanelDimensions.relativeRight > this.maximumRightPosition;
|
||||
},
|
||||
mainPanelStyles(): { left: string } {
|
||||
mainPanelStyles(): { left: string, right: string } {
|
||||
return {
|
||||
left: `${this.mainPanelFinalPositionPx}px`,
|
||||
'left': `${this.relativeWidthToPx(this.mainPanelDimensions.relativeLeft)}px`,
|
||||
'right': `${this.relativeWidthToPx(this.mainPanelDimensions.relativeRight)}px`,
|
||||
};
|
||||
},
|
||||
inputPanelStyles(): { width: string } {
|
||||
if (!this.isDraggable) {
|
||||
return {
|
||||
width: `${this.fixedPanelWidth}px`,
|
||||
};
|
||||
inputPanelStyles():{ right: string } {
|
||||
return {
|
||||
right: `${this.relativeWidthToPx(this.calculatedPositions.inputPanelRelativeRight)}px`,
|
||||
};
|
||||
},
|
||||
outputPanelStyles(): { left: string, transform: string} {
|
||||
return {
|
||||
left: `${this.relativeWidthToPx(this.calculatedPositions.outputPanelRelativeLeft)}px`,
|
||||
transform: `translateX(-${this.relativeWidthToPx(this.outputPanelRelativeTranslate)}px)`,
|
||||
};
|
||||
},
|
||||
calculatedPositions():{ inputPanelRelativeRight: number, outputPanelRelativeLeft: number } {
|
||||
const hasInput = this.$slots.input !== undefined;
|
||||
const outputPanelRelativeLeft = this.mainPanelDimensions.relativeLeft + this.mainPanelDimensions.relativeWidth;
|
||||
|
||||
const inputPanelRelativeRight = hasInput
|
||||
? 1 - outputPanelRelativeLeft + this.mainPanelDimensions.relativeWidth
|
||||
: (1 - this.pxToRelativeWidth(SIDE_MARGIN));
|
||||
|
||||
return {
|
||||
inputPanelRelativeRight,
|
||||
outputPanelRelativeLeft,
|
||||
};
|
||||
},
|
||||
outputPanelRelativeTranslate():number {
|
||||
const panelMinLeft = 1 - this.pxToRelativeWidth(MIN_PANEL_WIDTH + SIDE_MARGIN);
|
||||
const currentRelativeLeftDelta = this.calculatedPositions.outputPanelRelativeLeft - panelMinLeft;
|
||||
return currentRelativeLeftDelta > 0 ? currentRelativeLeftDelta : 0;
|
||||
},
|
||||
hasDoubleWidth() {
|
||||
return get(this, 'nodeType.parameterPane') === 'wide';
|
||||
},
|
||||
fixedPanelWidth(): number {
|
||||
const multiplier = this.hasDoubleWidth ? 2 : 1;
|
||||
|
||||
if (this.windowWidth > 1700) {
|
||||
return PANEL_WIDTH_LARGE * multiplier;
|
||||
}
|
||||
|
||||
let width = this.mainPanelPosition - MAIN_PANEL_WIDTH / 2 - SIDE_MARGIN;
|
||||
width = Math.min(
|
||||
width,
|
||||
this.windowWidth - SIDE_MARGIN * 2 - this.inputPanelMargin - MAIN_PANEL_WIDTH,
|
||||
);
|
||||
width = Math.max(320, width);
|
||||
return {
|
||||
width: `${width}px`,
|
||||
};
|
||||
return PANEL_WIDTH * multiplier;
|
||||
},
|
||||
outputPanelStyles(): { width: string } {
|
||||
let width = this.windowWidth - this.mainPanelPosition - MAIN_PANEL_WIDTH / 2 - SIDE_MARGIN;
|
||||
width = Math.min(
|
||||
width,
|
||||
this.windowWidth - SIDE_MARGIN * 2 - this.inputPanelMargin - MAIN_PANEL_WIDTH,
|
||||
);
|
||||
width = Math.max(MINIMUM_INPUT_PANEL_WIDTH, width);
|
||||
return {
|
||||
width: `${width}px`,
|
||||
};
|
||||
isBelowMinWidthMainPanel(): boolean {
|
||||
const minRelativeWidth = this.pxToRelativeWidth(MIN_PANEL_WIDTH);
|
||||
return this.mainPanelDimensions.relativeWidth < minRelativeWidth;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
windowWidth(windowWidth) {
|
||||
const minRelativeWidth = this.pxToRelativeWidth(MIN_PANEL_WIDTH);
|
||||
// Prevent the panel resizing below MIN_PANEL_WIDTH whhile maintaing position
|
||||
if(this.isBelowMinWidthMainPanel) {
|
||||
this.setMainPanelWidth(minRelativeWidth);
|
||||
}
|
||||
|
||||
const isBelowMinLeft = this.minimumLeftPosition > this.mainPanelDimensions.relativeLeft;
|
||||
const isMaxRight = this.maximumRightPosition > this.mainPanelDimensions.relativeRight;
|
||||
|
||||
// When user is resizing from non-supported view(sub ~488px) we need to refit the panels
|
||||
if((windowWidth > this.minWindowWidth) && isBelowMinLeft && isMaxRight) {
|
||||
this.setMainPanelWidth(minRelativeWidth);
|
||||
this.setPositions(this.getInitialLeftPosition(this.mainPanelDimensions.relativeWidth));
|
||||
}
|
||||
|
||||
this.setPositions(this.mainPanelDimensions.relativeLeft);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getRelativePosition() {
|
||||
const current = this.mainPanelFinalPositionPx + MAIN_PANEL_WIDTH / 2 - this.windowWidth / 2;
|
||||
getInitialLeftPosition(width: number) {
|
||||
if(this.currentNodePaneType === 'dragless') return this.pxToRelativeWidth(SIDE_MARGIN + 1 + this.fixedPanelWidth);
|
||||
|
||||
const pos = Math.floor(
|
||||
(current / ((this.maximumRightPosition - this.minimumLeftPosition) / 2)) * 100,
|
||||
return this.hasInputSlot
|
||||
? 0.5 - (width / 2)
|
||||
: this.minimumLeftPosition;
|
||||
},
|
||||
setMainPanelWidth(relativeWidth?: number) {
|
||||
const mainPanelRelativeWidth = relativeWidth || this.pxToRelativeWidth(initialMainPanelWidth[this.currentNodePaneType]);
|
||||
|
||||
this.$store.commit('ui/setMainPanelDimensions', {
|
||||
panelType: this.currentNodePaneType,
|
||||
dimensions: {
|
||||
relativeWidth: mainPanelRelativeWidth,
|
||||
},
|
||||
});
|
||||
},
|
||||
setPositions(relativeLeft: number) {
|
||||
const mainPanelRelativeLeft = relativeLeft || 1 - this.calculatedPositions.inputPanelRelativeRight;
|
||||
const mainPanelRelativeRight = 1 - mainPanelRelativeLeft - this.mainPanelDimensions.relativeWidth;
|
||||
|
||||
const isMaxRight = this.maximumRightPosition > mainPanelRelativeRight;
|
||||
const isMinLeft = this.minimumLeftPosition > mainPanelRelativeLeft;
|
||||
const isInputless = this.currentNodePaneType === 'inputless';
|
||||
|
||||
if(isMinLeft) {
|
||||
this.$store.commit('ui/setMainPanelDimensions', {
|
||||
panelType: this.currentNodePaneType,
|
||||
dimensions: {
|
||||
relativeLeft: this.minimumLeftPosition,
|
||||
relativeRight: 1 - this.mainPanelDimensions.relativeWidth - this.minimumLeftPosition,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if(isMaxRight) {
|
||||
this.$store.commit('ui/setMainPanelDimensions', {
|
||||
panelType: this.currentNodePaneType,
|
||||
dimensions: {
|
||||
relativeLeft: 1 - this.mainPanelDimensions.relativeWidth - this.maximumRightPosition,
|
||||
relativeRight: this.maximumRightPosition,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.$store.commit('ui/setMainPanelDimensions', {
|
||||
panelType: this.currentNodePaneType,
|
||||
dimensions: {
|
||||
relativeLeft: isInputless ? this.minimumLeftPosition : mainPanelRelativeLeft,
|
||||
relativeRight: mainPanelRelativeRight,
|
||||
},
|
||||
});
|
||||
},
|
||||
pxToRelativeWidth(px: number) {
|
||||
return px / this.windowWidth;
|
||||
},
|
||||
relativeWidthToPx(relativeWidth: number) {
|
||||
return relativeWidth * this.windowWidth;
|
||||
},
|
||||
onResizeStart() {
|
||||
this.setTotalWidth();
|
||||
},
|
||||
onResizeEnd() {
|
||||
this.storePositionData();
|
||||
},
|
||||
onResize({ direction, x, width }: { direction: string, x: number, width: number}) {
|
||||
const relativeDistance = this.pxToRelativeWidth(x);
|
||||
const relativeWidth = this.pxToRelativeWidth(width);
|
||||
|
||||
if(direction === "left" && relativeDistance <= this.minimumLeftPosition) return;
|
||||
if(direction === "right" && (1 - relativeDistance) <= this.maximumRightPosition) return;
|
||||
if(width <= MIN_PANEL_WIDTH) return;
|
||||
|
||||
this.setMainPanelWidth(relativeWidth);
|
||||
this.setPositions(direction === 'left'
|
||||
? relativeDistance
|
||||
: this.mainPanelDimensions.relativeLeft,
|
||||
);
|
||||
return pos;
|
||||
},
|
||||
restorePositionData() {
|
||||
const storedPanelWidthData = window.localStorage.getItem(`${LOCAL_STORAGE_MAIN_PANEL_RELATIVE_WIDTH}_${this.currentNodePaneType}`);
|
||||
|
||||
if(storedPanelWidthData) {
|
||||
const parsedWidth = parseFloat(storedPanelWidthData);
|
||||
this.setMainPanelWidth(parsedWidth);
|
||||
const initialPosition = this.getInitialLeftPosition(parsedWidth);
|
||||
|
||||
this.setPositions(initialPosition);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
storePositionData() {
|
||||
window.localStorage.setItem(`${LOCAL_STORAGE_MAIN_PANEL_RELATIVE_WIDTH}_${this.currentNodePaneType}`, this.mainPanelDimensions.relativeWidth.toString());
|
||||
},
|
||||
onDragStart() {
|
||||
this.isDragging = true;
|
||||
this.$emit('dragstart', { position: this.getRelativePosition() });
|
||||
this.$emit('dragstart', { position: this.mainPanelDimensions.relativeLeft });
|
||||
},
|
||||
onDrag(e: {x: number, y: number}) {
|
||||
const relativePosition = e.x / this.windowWidth;
|
||||
this.$store.commit('ui/setMainPanelRelativePosition', relativePosition);
|
||||
const relativeLeft = this.pxToRelativeWidth(e.x) - (this.mainPanelDimensions.relativeWidth / 2);
|
||||
|
||||
this.setPositions(relativeLeft);
|
||||
},
|
||||
onDragEnd() {
|
||||
setTimeout(() => {
|
||||
this.isDragging = false;
|
||||
this.$emit('dragend', {
|
||||
windowWidth: this.windowWidth,
|
||||
position: this.getRelativePosition(),
|
||||
position: this.mainPanelDimensions.relativeLeft,
|
||||
});
|
||||
}, 0);
|
||||
this.storePositionData();
|
||||
},
|
||||
setTotalWidth() {
|
||||
this.windowWidth = window.innerWidth;
|
||||
|
@ -178,14 +355,13 @@ export default Vue.extend({
|
|||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
$--main-panel-width: 360px;
|
||||
|
||||
.dataPanel {
|
||||
position: absolute;
|
||||
height: calc(100% - 2 * var(--spacing-l));
|
||||
position: absolute;
|
||||
top: var(--spacing-l);
|
||||
z-index: 0;
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
.inputPanel {
|
||||
|
@ -200,7 +376,6 @@ $--main-panel-width: 360px;
|
|||
.outputPanel {
|
||||
composes: dataPanel;
|
||||
right: var(--spacing-l);
|
||||
width: $--main-panel-width;
|
||||
|
||||
> * {
|
||||
border-radius: 0 var(--border-radius-large) var(--border-radius-large) 0;
|
||||
|
@ -218,18 +393,35 @@ $--main-panel-width: 360px;
|
|||
}
|
||||
}
|
||||
|
||||
.mainPanelInner {
|
||||
height: 100%;
|
||||
border: var(--border-base);
|
||||
border-radius: var(--border-radius-large);
|
||||
box-shadow: 0 4px 16px rgb(50 61 85 / 10%);
|
||||
overflow: hidden;
|
||||
|
||||
&.dragging {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0px 6px 16px rgba(255, 74, 51, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.draggable {
|
||||
position: absolute;
|
||||
left: 40%;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.dragButtonContainer {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
width: $--main-panel-width;
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
|
||||
.draggable {
|
||||
pointer-events: all;
|
||||
}
|
||||
&:hover .draggable {
|
||||
visibility: visible;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
:hideInputAndOutput="activeNodeType === null"
|
||||
:position="isTriggerNode && !showTriggerPanel ? 0 : undefined"
|
||||
:isDraggable="!isTriggerNode"
|
||||
:nodeType="activeNodeType"
|
||||
@close="close"
|
||||
@init="onPanelsInit"
|
||||
@dragstart="onDragStart"
|
||||
|
@ -82,6 +83,7 @@
|
|||
:eventBus="settingsEventBus"
|
||||
:dragging="isDragging"
|
||||
:sessionId="sessionId"
|
||||
:nodeType="activeNodeType"
|
||||
@valueChanged="valueChanged"
|
||||
@execute="onNodeExecute"
|
||||
@activate="onWorkflowActivate"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div :class="{'node-settings': true, 'dragging': dragging}" @keydown.stop>
|
||||
<div :class="{
|
||||
'node-settings': true, 'dragging': dragging }" @keydown.stop>
|
||||
<div :class="$style.header">
|
||||
<div class="header-side-menu">
|
||||
<NodeTitle class="node-name" :value="node && node.name" :nodeType="nodeType" @input="nameChanged" :readOnly="isReadOnly"></NodeTitle>
|
||||
|
@ -96,7 +97,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import Vue, { PropType } from 'vue';
|
||||
import {
|
||||
INodeTypeDescription,
|
||||
INodeParameters,
|
||||
|
@ -113,7 +114,8 @@ import {
|
|||
import {
|
||||
COMMUNITY_NODES_INSTALLATION_DOCS_URL,
|
||||
CUSTOM_NODES_DOCS_URL,
|
||||
} from '../constants';
|
||||
MAIN_NODE_PANEL_WIDTH,
|
||||
} from '@/constants';
|
||||
|
||||
import NodeTitle from '@/components/NodeTitle.vue';
|
||||
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
||||
|
@ -148,13 +150,6 @@ export default mixins(
|
|||
NodeExecuteButton,
|
||||
},
|
||||
computed: {
|
||||
nodeType (): INodeTypeDescription | null {
|
||||
if (this.node) {
|
||||
return this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
nodeTypeName(): string {
|
||||
if (this.nodeType) {
|
||||
const shortNodeType = this.$locale.shortNodeType(this.nodeType.name);
|
||||
|
@ -224,6 +219,9 @@ export default mixins(
|
|||
sessionId: {
|
||||
type: String,
|
||||
},
|
||||
nodeType: {
|
||||
type: Object as PropType<INodeTypeDescription>,
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
@ -336,6 +334,7 @@ export default mixins(
|
|||
] as INodeProperties[],
|
||||
COMMUNITY_NODES_INSTALLATION_DOCS_URL,
|
||||
CUSTOM_NODES_DOCS_URL,
|
||||
MAIN_NODE_PANEL_WIDTH,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
|
@ -641,13 +640,9 @@ export default mixins(
|
|||
<style lang="scss">
|
||||
.node-settings {
|
||||
overflow: hidden;
|
||||
min-width: 360px;
|
||||
max-width: 360px;
|
||||
background-color: var(--color-background-xlight);
|
||||
height: 100%;
|
||||
border: var(--border-base);
|
||||
border-radius: var(--border-radius-large);
|
||||
box-shadow: 0 4px 16px rgb(50 61 85 / 10%);
|
||||
width: 100%;
|
||||
|
||||
.no-parameters {
|
||||
margin-top: var(--spacing-xs);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<Draggable type="panel-resize" @drag="onDrag" @dragstart="onDragStart" @dragend="onDragEnd">
|
||||
<Draggable type="panel-resize" @drag="onDrag" @dragstart="onDragStart" @dragend="onDragEnd" :class="$style.dragContainer">
|
||||
<template v-slot="{ isDragging }">
|
||||
<div
|
||||
:class="{ [$style.dragButton]: true }"
|
||||
|
@ -63,6 +63,9 @@ export default mixins(dragging).extend({
|
|||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.dragContainer {
|
||||
pointer-events: all;
|
||||
}
|
||||
.dragButton {
|
||||
background-color: var(--color-background-base);
|
||||
width: 64px;
|
||||
|
@ -74,6 +77,8 @@ export default mixins(dragging).extend({
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
|
||||
&:hover {
|
||||
.leftArrow, .rightArrow {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="resource-locator">
|
||||
<div class="resource-locator" ref="container">
|
||||
<resource-locator-dropdown
|
||||
:value="value ? value.value: ''"
|
||||
:show="showResourceDropdown"
|
||||
|
@ -10,6 +10,7 @@
|
|||
:filter="searchFilter"
|
||||
:hasMore="currentQueryHasMore"
|
||||
:errorView="currentQueryError"
|
||||
:width="width"
|
||||
@input="onListItemSelected"
|
||||
@hide="onDropdownHide"
|
||||
@filter="onSearchFilter"
|
||||
|
@ -251,10 +252,12 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
mainPanelMutationSubscription: () => {},
|
||||
showResourceDropdown: false,
|
||||
searchFilter: '',
|
||||
cachedResponses: {} as { [key: string]: IResourceLocatorQuery },
|
||||
hasCompletedASearch: false,
|
||||
width: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -423,8 +426,23 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
|
|||
},
|
||||
mounted() {
|
||||
this.$on('refreshList', this.refreshList);
|
||||
this.setWidth();
|
||||
window.addEventListener('resize', this.setWidth);
|
||||
this.mainPanelMutationSubscription = this.$store.subscribe(this.setWidthOnMainPanelResize);
|
||||
},
|
||||
beforeDestroy() {
|
||||
// Unsubscribe
|
||||
this.mainPanelMutationSubscription();
|
||||
window.removeEventListener('resize', this.setWidth);
|
||||
},
|
||||
methods: {
|
||||
setWidth() {
|
||||
this.width = (this.$refs.container as HTMLElement).offsetWidth;
|
||||
},
|
||||
setWidthOnMainPanelResize(mutation: { type: string }) {
|
||||
// Update the width when main panel dimension change
|
||||
if(mutation.type === 'ui/setMainPanelDimensions') this.setWidth();
|
||||
},
|
||||
getLinkAlt(entity: string) {
|
||||
if (this.selectedMode === 'list' && entity) {
|
||||
return this.$locale.baseText('resourceLocator.openSpecificResource', { interpolate: { entity, appName: this.appName } });
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<n8n-popover
|
||||
placement="bottom"
|
||||
width="318"
|
||||
:width="width"
|
||||
:popper-class="$style.popover"
|
||||
:value="show"
|
||||
trigger="manual"
|
||||
|
@ -90,6 +90,9 @@ export default Vue.extend({
|
|||
filterRequired: {
|
||||
type: Boolean,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -254,7 +257,7 @@ export default Vue.extend({
|
|||
--input-font-size: var(--font-size-2xs);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 316px;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -118,3 +118,7 @@ export function isValueExpression (parameter: INodeProperties, paramValue: NodeP
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function convertRemToPixels(rem: string) {
|
||||
return parseInt(rem, 10) * parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||
}
|
||||
|
|
|
@ -233,6 +233,7 @@ export const LOCAL_STORAGE_ACTIVATION_FLAG = 'N8N_HIDE_ACTIVATION_ALERT';
|
|||
export const LOCAL_STORAGE_PIN_DATA_DISCOVERY_NDV_FLAG = 'N8N_PIN_DATA_DISCOVERY_NDV';
|
||||
export const LOCAL_STORAGE_PIN_DATA_DISCOVERY_CANVAS_FLAG = 'N8N_PIN_DATA_DISCOVERY_CANVAS';
|
||||
export const LOCAL_STORAGE_MAPPING_FLAG = 'N8N_MAPPING_ONBOARDED';
|
||||
export const LOCAL_STORAGE_MAIN_PANEL_RELATIVE_WIDTH = 'N8N_MAIN_PANEL_RELATIVE_WIDTH';
|
||||
export const BASE_NODE_SURVEY_URL = 'https://n8n-community.typeform.com/to/BvmzxqYv#nodename=';
|
||||
|
||||
export const HIRING_BANNER = `
|
||||
|
@ -317,3 +318,4 @@ export const DEFAULT_STICKY_WIDTH = 240;
|
|||
export enum EnterpriseEditionFeature {
|
||||
Sharing = 'sharing',
|
||||
}
|
||||
export const MAIN_NODE_PANEL_WIDTH = 360;
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
ONBOARDING_CALL_SIGNUP_MODAL_KEY,
|
||||
FAKE_DOOR_FEATURES,
|
||||
COMMUNITY_PACKAGE_MANAGE_ACTIONS,
|
||||
MAIN_NODE_PANEL_WIDTH,
|
||||
} from '@/constants';
|
||||
import Vue from 'vue';
|
||||
import { ActionContext, Module } from 'vuex';
|
||||
|
@ -109,6 +110,7 @@ const module: Module<IUiState, IRootState> = {
|
|||
sidebarMenuCollapsed: true,
|
||||
isPageLoading: true,
|
||||
currentView: '',
|
||||
mainPanelDimensions: {},
|
||||
ndv: {
|
||||
sessionId: '',
|
||||
input: {
|
||||
|
@ -205,10 +207,22 @@ const module: Module<IUiState, IRootState> = {
|
|||
draggableType: (state: IUiState) => state.draggable.type,
|
||||
draggableData: (state: IUiState) => state.draggable.data,
|
||||
canDraggableDrop: (state: IUiState) => state.draggable.canDrop,
|
||||
mainPanelDimensions: (state: IUiState) => (panelType: string) => {
|
||||
const defaults = { relativeRight: 1, relativeLeft: 1, relativeWidth: 1 };
|
||||
|
||||
return {...defaults, ...state.mainPanelDimensions[panelType]};
|
||||
},
|
||||
draggableStickyPos: (state: IUiState) => state.draggable.stickyPosition,
|
||||
mappingTelemetry: (state: IUiState) => state.ndv.mappingTelemetry,
|
||||
},
|
||||
mutations: {
|
||||
setMainPanelDimensions: (state: IUiState, params: { panelType:string, dimensions: { relativeLeft?: number, relativeRight?: number, relativeWidth?: number }}) => {
|
||||
Vue.set(
|
||||
state.mainPanelDimensions,
|
||||
params.panelType,
|
||||
{...state.mainPanelDimensions[params.panelType], ...params.dimensions },
|
||||
);
|
||||
},
|
||||
setMode: (state: IUiState, params: {name: string, mode: string}) => {
|
||||
const { name, mode } = params;
|
||||
Vue.set(state.modals[name], 'mode', mode);
|
||||
|
@ -259,9 +273,6 @@ const module: Module<IUiState, IRootState> = {
|
|||
setOutputPanelEditModeValue: (state: IUiState, payload: string) => {
|
||||
Vue.set(state.ndv.output.editMode, 'value', payload);
|
||||
},
|
||||
setMainPanelRelativePosition(state: IUiState, relativePosition: number) {
|
||||
state.mainPanelPosition = relativePosition;
|
||||
},
|
||||
setMappableNDVInputFocus(state: IUiState, paramName: string) {
|
||||
Vue.set(state.ndv, 'focusedMappableInput', paramName);
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue