Enable drag and drop in code node for runOnceForEachItem mode

This commit is contained in:
Elias Meire 2024-09-19 17:48:02 +02:00
parent fdef6c9f0d
commit 5daf916a2f
No known key found for this signature in database
2 changed files with 82 additions and 10 deletions

View file

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

View file

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