mirror of
https://github.com/n8n-io/n8n.git
synced 2024-09-19 22:37:31 -07:00
Enable drag and drop in code node for runOnceForEachItem mode
This commit is contained in:
parent
fdef6c9f0d
commit
5daf916a2f
|
@ -3,14 +3,14 @@ import { javascript } from '@codemirror/lang-javascript';
|
|||
import { python } from '@codemirror/lang-python';
|
||||
import type { LanguageSupport } from '@codemirror/language';
|
||||
import type { Extension, Line } from '@codemirror/state';
|
||||
import { Compartment, EditorState } from '@codemirror/state';
|
||||
import { Compartment, EditorSelection, EditorState } from '@codemirror/state';
|
||||
import type { ViewUpdate } from '@codemirror/view';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import type { CodeExecutionMode, CodeNodeEditorLanguage } from 'n8n-workflow';
|
||||
import { format } from 'prettier';
|
||||
import jsParser from 'prettier/plugins/babel';
|
||||
import * as estree from 'prettier/plugins/estree';
|
||||
import { type Ref, computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import { type Ref, computed, nextTick, onBeforeUnmount, onMounted, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { CODE_NODE_TYPE } from '@/constants';
|
||||
import { codeNodeEditorEventBus } from '@/event-bus';
|
||||
|
@ -26,6 +26,8 @@ import { useLinter } from './linter';
|
|||
import { codeNodeEditorTheme } from './theme';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { eventToCoord, mappingDropCursor } from '@/plugins/codemirror/dragAndDrop';
|
||||
import { unwrapExpression } from '@/utils/expressions';
|
||||
|
||||
type Props = {
|
||||
mode: CodeExecutionMode;
|
||||
|
@ -51,6 +53,7 @@ const emit = defineEmits<{
|
|||
const message = useMessage();
|
||||
const editor = ref(null) as Ref<EditorView | null>;
|
||||
const languageCompartment = ref(new Compartment());
|
||||
const dragAndDropCompartment = ref(new Compartment());
|
||||
const linterCompartment = ref(new Compartment());
|
||||
const isEditorHovered = ref(false);
|
||||
const isEditorFocused = ref(false);
|
||||
|
@ -95,6 +98,7 @@ onMounted(() => {
|
|||
|
||||
extensions.push(
|
||||
...writableEditorExtensions,
|
||||
dragAndDropCompartment.value.of(dragAndDropExtension.value),
|
||||
EditorView.domEventHandlers({
|
||||
focus: () => {
|
||||
isEditorFocused.value = true;
|
||||
|
@ -151,6 +155,12 @@ const placeholder = computed(() => {
|
|||
return CODE_PLACEHOLDERS[props.language]?.[props.mode] ?? '';
|
||||
});
|
||||
|
||||
const dragAndDropEnabled = computed(() => {
|
||||
return !props.isReadOnly && props.mode === 'runOnceForEachItem';
|
||||
});
|
||||
|
||||
const dragAndDropExtension = computed(() => (dragAndDropEnabled.value ? mappingDropCursor() : []));
|
||||
|
||||
// eslint-disable-next-line vue/return-in-computed-property
|
||||
const languageExtensions = computed<[LanguageSupport, ...Extension[]]>(() => {
|
||||
switch (props.language) {
|
||||
|
@ -188,6 +198,12 @@ watch(
|
|||
},
|
||||
);
|
||||
|
||||
watch(dragAndDropExtension, (extension) => {
|
||||
editor.value?.dispatch({
|
||||
effects: dragAndDropCompartment.value.reconfigure(extension),
|
||||
});
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.language,
|
||||
(_newLanguage, previousLanguage: CodeNodeEditorLanguage) => {
|
||||
|
@ -202,7 +218,6 @@ watch(
|
|||
reloadLinter();
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
aiEnabled,
|
||||
async (isEnabled) => {
|
||||
|
@ -361,6 +376,26 @@ function onAiLoadStart() {
|
|||
function onAiLoadEnd() {
|
||||
isLoadingAIResponse.value = false;
|
||||
}
|
||||
|
||||
async function onDrop(value: string, event: MouseEvent) {
|
||||
if (!editor.value) return;
|
||||
|
||||
const cmEditor = toRaw(editor.value);
|
||||
|
||||
const dropPos = cmEditor.posAtCoords(eventToCoord(event), false);
|
||||
const valueToInsert = unwrapExpression(value);
|
||||
|
||||
const changes = cmEditor.state.changes({ from: dropPos, insert: valueToInsert });
|
||||
const anchor = changes.mapPos(dropPos, -1);
|
||||
const head = changes.mapPos(dropPos, 1);
|
||||
const selection = EditorSelection.single(anchor, head);
|
||||
|
||||
cmEditor.dispatch({
|
||||
changes,
|
||||
selection,
|
||||
userEvent: 'input.drop',
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -382,10 +417,19 @@ function onAiLoadEnd() {
|
|||
name="code"
|
||||
data-test-id="code-node-tab-code"
|
||||
>
|
||||
<div
|
||||
ref="codeNodeEditorRef"
|
||||
:class="['ph-no-capture', 'code-editor-tabs', $style.editorInput]"
|
||||
/>
|
||||
<DraggableTarget type="mapping" :disabled="!dragAndDropEnabled" @drop="onDrop">
|
||||
<template #default="{ activeDrop, droppable }">
|
||||
<div
|
||||
ref="codeNodeEditorRef"
|
||||
:class="[
|
||||
'ph-no-capture',
|
||||
'code-editor-tabs',
|
||||
$style.editorInput,
|
||||
{ [$style.activeDrop]: activeDrop, [$style.droppable]: droppable },
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</DraggableTarget>
|
||||
<slot name="suffix" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane
|
||||
|
@ -405,7 +449,19 @@ function onAiLoadEnd() {
|
|||
</el-tabs>
|
||||
<!-- If AskAi not enabled, there's no point in rendering tabs -->
|
||||
<div v-else :class="$style.fillHeight">
|
||||
<div ref="codeNodeEditorRef" :class="['ph-no-capture', $style.fillHeight]" />
|
||||
<DraggableTarget type="mapping" :disabled="!dragAndDropEnabled" @drop="onDrop">
|
||||
<template #default="{ activeDrop, droppable }">
|
||||
<div
|
||||
ref="codeNodeEditorRef"
|
||||
:class="[
|
||||
'ph-no-capture',
|
||||
$style.fillHeight,
|
||||
$style.editorInput,
|
||||
{ [$style.activeDrop]: activeDrop, [$style.droppable]: droppable },
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</DraggableTarget>
|
||||
<slot name="suffix" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -413,7 +469,7 @@ function onAiLoadEnd() {
|
|||
|
||||
<style scoped lang="scss">
|
||||
:deep(.el-tabs) {
|
||||
.code-editor-tabs .cm-editor {
|
||||
.cm-editor {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
@ -446,4 +502,20 @@ function onAiLoadEnd() {
|
|||
.fillHeight {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.editorInput.droppable {
|
||||
:global(.cm-editor) {
|
||||
border-color: var(--color-ndv-droppable-parameter);
|
||||
border-style: dashed;
|
||||
border-width: 1.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.editorInput.activeDrop {
|
||||
:global(.cm-editor) {
|
||||
border-color: var(--color-success);
|
||||
cursor: grabbing;
|
||||
border-width: 1px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -117,7 +117,7 @@ const drawDropCursor = ViewPlugin.fromClass(
|
|||
},
|
||||
);
|
||||
|
||||
function eventToCoord(event: MouseEvent): { x: number; y: number } {
|
||||
export function eventToCoord(event: MouseEvent): { x: number; y: number } {
|
||||
return { x: event.clientX, y: event.clientY };
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue