Commit 4b950dc2 authored by Kushal Pandya's avatar Kushal Pandya

Add a delay before expanding epic

Adds a momentary delay before expanding an epic with
children when user tries to drag over an item.
parent 7a19c4a6
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import { GlButton, GlLoadingIcon } from '@gitlab/ui'; import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { ChildType } from '../constants'; import { ChildType, EXPAND_DELAY } from '../constants';
import TreeDragAndDropMixin from '../mixins/tree_dd_mixin'; import TreeDragAndDropMixin from '../mixins/tree_dd_mixin';
export default { export default {
...@@ -24,6 +24,8 @@ export default { ...@@ -24,6 +24,8 @@ export default {
data() { data() {
return { return {
fetchInProgress: false, fetchInProgress: false,
currentClientX: 0,
currentClientY: 0,
}; };
}, },
computed: { computed: {
...@@ -48,13 +50,34 @@ export default { ...@@ -48,13 +50,34 @@ export default {
this.fetchInProgress = false; this.fetchInProgress = false;
}); });
}, },
onMove(e) { onMove(e, originalEvent) {
const item = e.relatedContext.element; const item = e.relatedContext.element;
if (item?.type === ChildType.Epic) const { clientX, clientY } = originalEvent;
this.toggleItem({
parentItem: item, // Cache current cursor position
isDragging: true, this.currentClientX = clientX;
}); this.currentClientY = clientY;
// Check if current item is an Epic, and has any children.
if (item?.type === ChildType.Epic && (item.hasChildren || item.hasIssues)) {
const { top, left } = originalEvent.target.getBoundingClientRect();
// Check if user has paused cursor on top of current item's boundary
if (clientY >= top && clientX >= left) {
// Wait for moment before expanding the epic
this.toggleTimer = setTimeout(() => {
// Ensure that current cursor position is still within item's boundary
if (this.currentClientX === clientX && this.currentClientY === clientY) {
this.toggleItem({
parentItem: item,
isDragging: true,
});
}
}, EXPAND_DELAY);
} else {
clearTimeout(this.toggleTimer);
}
}
}, },
}, },
}; };
......
...@@ -41,6 +41,8 @@ export const OVERFLOW_AFTER = 5; ...@@ -41,6 +41,8 @@ export const OVERFLOW_AFTER = 5;
export const SEARCH_DEBOUNCE = 500; export const SEARCH_DEBOUNCE = 500;
export const EXPAND_DELAY = 1000;
export const itemRemoveModalId = 'item-remove-confirmation'; export const itemRemoveModalId = 'item-remove-confirmation';
export const treeItemChevronBtnClassName = 'btn-tree-item-chevron'; export const treeItemChevronBtnClassName = 'btn-tree-item-chevron';
......
---
title: Add a delay before expanding epic within tree during drag and drop
merge_request: 58282
author:
type: changed
...@@ -377,26 +377,67 @@ describe('RelatedItemsTree', () => { ...@@ -377,26 +377,67 @@ describe('RelatedItemsTree', () => {
}); });
describe('onMove', () => { describe('onMove', () => {
it('calls toggleItem action if move event finds epic element', () => { let mockEvt;
jest.spyOn(wrapper.vm, 'toggleItem').mockImplementation(() => {}); let mockOriginalEvt;
const evt = {
beforeEach(() => {
mockEvt = {
relatedContext: { relatedContext: {
element: mockParentItem, element: mockParentItem,
}, },
}; };
wrapper.vm.onMove(evt); mockOriginalEvt = {
clientX: 10,
clientY: 10,
target: {
getBoundingClientRect() {
return {
top: 5,
left: 5,
};
},
},
};
});
it('calls toggleItem action after a delay if move event finds epic with children and mouse cursor is over it', () => {
jest.spyOn(wrapper.vm, 'toggleItem').mockImplementation(() => {});
wrapper.vm.onMove(mockEvt, mockOriginalEvt);
jest.runAllTimers();
expect(wrapper.vm.toggleItem).toHaveBeenCalled(); expect(wrapper.vm.toggleItem).toHaveBeenCalled();
}); });
it(' does not call toggleItem action if move event does not find epic element', () => { it('does not call toggleItem action if move event does not find epic with children', () => {
jest.spyOn(wrapper.vm, 'toggleItem').mockImplementation(() => {}); jest.spyOn(wrapper.vm, 'toggleItem').mockImplementation(() => {});
const evt = { mockEvt = {
relatedContext: { relatedContext: {
element: mockIssue2, element: mockIssue2,
}, },
}; };
wrapper.vm.onMove(evt); mockOriginalEvt = {
clientX: 10,
clientY: 10,
};
wrapper.vm.onMove(mockEvt, mockOriginalEvt);
expect(wrapper.vm.toggleItem).not.toHaveBeenCalled();
});
it('does not call toggleItem action if move event no longer have cursor over an epic with children', () => {
jest.spyOn(wrapper.vm, 'toggleItem').mockImplementation(() => {});
wrapper.vm.onMove(mockEvt, mockOriginalEvt);
// Simulate cursor movement.
wrapper.setData({
currentClientX: 10,
currentClientY: 20,
});
jest.runAllTimers();
expect(wrapper.vm.toggleItem).not.toHaveBeenCalled(); expect(wrapper.vm.toggleItem).not.toHaveBeenCalled();
}); });
......
...@@ -18,6 +18,8 @@ export const mockParentItem = { ...@@ -18,6 +18,8 @@ export const mockParentItem = {
title: 'Some sample epic', title: 'Some sample epic',
reference: 'gitlab-org&1', reference: 'gitlab-org&1',
type: 'Epic', type: 'Epic',
hasChildren: true,
hasIssues: true,
userPermissions: { userPermissions: {
adminEpic: true, adminEpic: true,
createEpic: true, createEpic: true,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment