Skip to content

Commit

Permalink
refactor(editor): Convert Draggable components to composition API (no…
Browse files Browse the repository at this point in the history
…-changelog) (#9889)

Co-authored-by: Ricardo Espinoza <ricardo@n8n.io>
  • Loading branch information
elsmr and RicardoE105 authored Jul 2, 2024
1 parent 8debac7 commit ae67d6b
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 271 deletions.
19 changes: 6 additions & 13 deletions cypress/e2e/14-mapping.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,19 +317,12 @@ describe('Data mapping', () => {
workflowPage.actions.zoomToFit();

workflowPage.actions.openNode('Set');
ndv.actions.clearParameterInput('value');
cy.get('body').type('{esc}');

ndv.getters.parameterInput('includeOtherFields').find('input[type="checkbox"]').should('exist');
ndv.getters.parameterInput('includeOtherFields').find('input[type="text"]').should('not.exist');
ndv.getters
.inputDataContainer()
.should('exist')
.find('span')
.contains('count')
.realMouseDown()
.realMouseMove(100, 100);
cy.wait(50);
const pill = ndv.getters.inputDataContainer().find('span').contains('count');
pill.should('be.visible');
pill.realMouseDown();
pill.realMouseMove(100, 100);

ndv.getters
.parameterInput('includeOtherFields')
Expand All @@ -340,13 +333,13 @@ describe('Data mapping', () => {
.find('input[type="text"]')
.should('exist')
.invoke('css', 'border')
.then((border) => expect(border).to.include('dashed rgb(90, 76, 194)'));
.should('include', 'dashed rgb(90, 76, 194)');

ndv.getters
.parameterInput('value')
.find('input[type="text"]')
.should('exist')
.invoke('css', 'border')
.then((border) => expect(border).to.include('dashed rgb(90, 76, 194)'));
.should('include', 'dashed rgb(90, 76, 194)');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ describe('AssignmentCollection.vue', () => {
setActivePinia(pinia);

const { getByTestId, findAllByTestId } = renderComponent({ pinia });
const dropArea = getByTestId('assignment-collection-drop-area');
const dropArea = getByTestId('drop-area');

await dropAssignment({ key: 'boolKey', value: true, dropArea });
await dropAssignment({ key: 'stringKey', value: 'stringValue', dropArea });
Expand Down
260 changes: 119 additions & 141 deletions packages/editor-ui/src/components/Draggable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,154 +9,131 @@

<Teleport to="body">
<div v-show="isDragging" ref="draggable" :class="$style.draggable" :style="draggableStyle">
<slot name="preview" :can-drop="canDrop" :el="draggingEl"></slot>
<slot name="preview" :can-drop="canDrop" :el="draggingElement"></slot>
</div>
</Teleport>
</component>
</template>

<script lang="ts">
<script setup lang="ts">
import type { XYPosition } from '@/Interface';
import { useNDVStore } from '@/stores/ndv.store';
import { mapStores } from 'pinia';
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Draggable',
props: {
disabled: {
type: Boolean,
},
type: {
type: String,
required: true,
},
data: {
type: String,
},
tag: {
type: String,
default: 'div',
},
targetDataKey: {
type: String,
},
},
data() {
const draggablePosition = {
x: -100,
y: -100,
};
return {
isDragging: false,
draggablePosition,
draggingEl: null as null | HTMLElement,
draggableStyle: {
transform: `translate(${draggablePosition.x}px, ${draggablePosition.y}px)`,
},
animationFrameId: 0,
};
},
computed: {
...mapStores(useNDVStore),
canDrop(): boolean {
return this.ndvStore.canDraggableDrop;
},
stickyPosition(): XYPosition | null {
return this.ndvStore.draggableStickyPos;
},
},
methods: {
setDraggableStyle() {
this.draggableStyle = {
transform: `translate(${this.draggablePosition.x}px, ${this.draggablePosition.y}px)`,
};
},
onDragStart(e: MouseEvent) {
if (this.disabled) {
return;
}
this.draggingEl = e.target as HTMLElement;
if (this.targetDataKey && this.draggingEl.dataset?.target !== this.targetDataKey) {
this.draggingEl = this.draggingEl.closest(
`[data-target="${this.targetDataKey}"]`,
) as HTMLElement;
}
if (this.targetDataKey && this.draggingEl?.dataset?.target !== this.targetDataKey) {
return;
}
e.preventDefault();
e.stopPropagation();
this.isDragging = false;
this.draggablePosition = { x: e.pageX, y: e.pageY };
this.setDraggableStyle();
window.addEventListener('mousemove', this.onDrag);
window.addEventListener('mouseup', this.onDragEnd);
// blur so that any focused inputs update value
const activeElement = document.activeElement as HTMLElement;
if (activeElement) {
activeElement.blur();
}
},
onDrag(e: MouseEvent) {
e.preventDefault();
e.stopPropagation();
if (this.disabled) {
return;
}
if (!this.isDragging) {
this.isDragging = true;
const data =
this.targetDataKey && this.draggingEl ? this.draggingEl.dataset.value : this.data || '';
this.ndvStore.draggableStartDragging({
type: this.type,
data: data || '',
dimensions: this.draggingEl?.getBoundingClientRect() ?? null,
});
this.$emit('dragstart', this.draggingEl);
document.body.style.cursor = 'grabbing';
}
this.animationFrameId = window.requestAnimationFrame(() => {
if (this.canDrop && this.stickyPosition) {
this.draggablePosition = { x: this.stickyPosition[0], y: this.stickyPosition[1] };
} else {
this.draggablePosition = { x: e.pageX, y: e.pageY };
}
this.setDraggableStyle();
this.$emit('drag', this.draggablePosition);
});
},
onDragEnd() {
if (this.disabled) {
return;
}
document.body.style.cursor = 'unset';
window.removeEventListener('mousemove', this.onDrag);
window.removeEventListener('mouseup', this.onDragEnd);
window.cancelAnimationFrame(this.animationFrameId);
setTimeout(() => {
this.$emit('dragend', this.draggingEl);
this.isDragging = false;
this.draggingEl = null;
this.ndvStore.draggableStopDragging();
}, 0);
},
},
});
import { isPresent } from '@/utils/typesUtils';
import { type StyleValue, computed, ref } from 'vue';
type Props = {
type: string;
data?: string;
tag?: string;
targetDataKey?: string;
disabled?: boolean;
};
const props = withDefaults(defineProps<Props>(), { tag: 'div', disabled: false });
const emit = defineEmits<{
(event: 'drag', value: XYPosition): void;
(event: 'dragstart', value: HTMLElement): void;
(event: 'dragend', value: HTMLElement): void;
}>();
const isDragging = ref(false);
const draggingElement = ref<HTMLElement>();
const draggablePosition = ref<XYPosition>([0, 0]);
const animationFrameId = ref<number>();
const ndvStore = useNDVStore();
const draggableStyle = computed<StyleValue>(() => ({
transform: `translate(${draggablePosition.value[0]}px, ${draggablePosition.value[1]}px)`,
}));
const canDrop = computed(() => ndvStore.canDraggableDrop);
const stickyPosition = computed(() => ndvStore.draggableStickyPos);
const onDragStart = (event: MouseEvent) => {
if (props.disabled) {
return;
}
draggingElement.value = event.target as HTMLElement;
if (props.targetDataKey && draggingElement.value.dataset?.target !== props.targetDataKey) {
draggingElement.value = draggingElement.value.closest(
`[data-target="${props.targetDataKey}"]`,
) as HTMLElement;
}
if (props.targetDataKey && draggingElement.value?.dataset?.target !== props.targetDataKey) {
return;
}
event.preventDefault();
event.stopPropagation();
isDragging.value = false;
draggablePosition.value = [event.pageX, event.pageY];
window.addEventListener('mousemove', onDrag);
window.addEventListener('mouseup', onDragEnd);
// blur so that any focused inputs update value
const activeElement = document.activeElement as HTMLElement;
if (activeElement) {
activeElement.blur();
}
};
const onDrag = (event: MouseEvent) => {
event.preventDefault();
event.stopPropagation();
if (props.disabled) {
return;
}
if (!isDragging.value && draggingElement.value) {
isDragging.value = true;
const data = props.targetDataKey ? draggingElement.value.dataset.value : props.data ?? '';
ndvStore.draggableStartDragging({
type: props.type,
data: data ?? '',
dimensions: draggingElement.value?.getBoundingClientRect() ?? null,
});
emit('dragstart', draggingElement.value);
document.body.style.cursor = 'grabbing';
}
animationFrameId.value = window.requestAnimationFrame(() => {
if (canDrop.value && stickyPosition.value) {
draggablePosition.value = stickyPosition.value;
} else {
draggablePosition.value = [event.pageX, event.pageY];
}
emit('drag', draggablePosition.value);
});
};
const onDragEnd = () => {
if (props.disabled) {
return;
}
document.body.style.cursor = 'unset';
window.removeEventListener('mousemove', onDrag);
window.removeEventListener('mouseup', onDragEnd);
if (isPresent(animationFrameId.value)) {
window.cancelAnimationFrame(animationFrameId.value);
}
setTimeout(() => {
if (draggingElement.value) emit('dragend', draggingElement.value);
isDragging.value = false;
draggingElement.value = undefined;
ndvStore.draggableStopDragging();
}, 0);
};
</script>

<style lang="scss" module>
Expand All @@ -166,6 +143,7 @@ export default defineComponent({
}
.draggable {
pointer-events: none;
position: fixed;
z-index: 9999999;
top: 0;
Expand Down
Loading

0 comments on commit ae67d6b

Please sign in to comment.