Skip to content

Commit

Permalink
Fix eclipse-theia#9203: Drag and drop sections between views
Browse files Browse the repository at this point in the history
Signed-off-by: Esther Perelman <[email protected]>
  • Loading branch information
EstherPerelman committed Sep 9, 2021
1 parent 10d5275 commit 5426df7
Show file tree
Hide file tree
Showing 18 changed files with 647 additions and 122 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [core] added `BreadcrumbsRendererFactory` to constructor arguments of `DockPanelRenderer` and `ToolbarAwareTabBar`. [#9920](https://github.com/eclipse-theia/theia/pull/9920)
- [task] `TaskDefinition.properties.required` is now optional to align with the specification [#10015](https://github.com/eclipse-theia/theia/pull/10015)
- [core] `setTopPanelVisibily` renamed to `setTopPanelVisibility` [#10020](https://github.com/eclipse-theia/theia/pull/10020)
- [view-container] `ViewContainerPart` constructor takes new 2 parameters: `originalContainerId` and `originalContainerTitle`. The existing `viewContainerId` parameter has been renamed to `currentContainerId` to enable drag & drop views. [#9644](https://github.com/eclipse-theia/theia/pull/9644)

## v1.17.2 - 9/1/2021

Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/browser/shell/application-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ export type ApplicationShellLayoutVersion =
/** git history view is replaced by a more generic scm history view, backward compatible to 3.0 */
4.0 |
/** Replace custom/font-awesome icons with codicons */
5.0;
5.0 |
/** added the ability to drag and drop view parts between view containers */
6.0;

/**
* When a version is increased, make sure to introduce a migration (ApplicationShellLayoutMigration) to this version.
Expand Down
96 changes: 96 additions & 0 deletions packages/core/src/browser/shell/tab-bars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { TabBarDecoratorService } from './tab-bar-decorator';
import { IconThemeService } from '../icon-theme-service';
import { BreadcrumbsRenderer, BreadcrumbsRendererFactory } from '../breadcrumbs/breadcrumbs-renderer';
import { NavigatableWidget } from '../navigatable-types';
import { IDragEvent } from '@phosphor/dragdrop';

/** The class name added to hidden content nodes, which are required to render vertical side bars. */
const HIDDEN_CONTENT_CLASS = 'theia-TabBar-hidden-content';
Expand Down Expand Up @@ -772,6 +773,18 @@ export class SideTabBar extends ScrollableTabBar {
protected onAfterAttach(msg: Message): void {
super.onAfterAttach(msg);
this.renderTabBar();
this.node.addEventListener('p-dragenter', this);
this.node.addEventListener('p-dragover', this);
this.node.addEventListener('p-dragleave', this);
document.addEventListener('p-drop', this);
}

protected onAfterDetach(msg: Message): void {
super.onAfterDetach(msg);
this.node.removeEventListener('p-dragenter', this);
this.node.removeEventListener('p-dragover', this);
this.node.removeEventListener('p-dragleave', this);
document.removeEventListener('p-drop', this);
}

protected onUpdateRequest(msg: Message): void {
Expand Down Expand Up @@ -869,6 +882,15 @@ export class SideTabBar extends ScrollableTabBar {
this.onMouseMove(event as MouseEvent);
super.handleEvent(event);
break;
case 'p-dragenter':
this.onDragEnter(event as IDragEvent);
break;
case 'p-dragover':
this.onDragOver(event as IDragEvent);
break;
case 'p-dragleave': case 'p-drop':
this.cancelViewContainerDND();
break;
default:
super.handleEvent(event);
}
Expand Down Expand Up @@ -934,4 +956,78 @@ export class SideTabBar extends ScrollableTabBar {
}
}

toCancelViewContainerDND = new DisposableCollection();
protected cancelViewContainerDND = () => {
this.toCancelViewContainerDND.dispose();
};

/**
* handles `viewContainerPart` drag enter.
*/
protected onDragEnter = (event: IDragEvent) => {
this.cancelViewContainerDND();
if (event.mimeData.getData('application/vnd.phosphor.view-container-factory')) {
event.preventDefault();
event.stopPropagation();
}
};

/**
* Handle `viewContainerPart` drag over,
* Defines the appropriate `drpAction` and opens the tab on which the mouse stands on for more than 800 ms.
*/
protected onDragOver = (event: IDragEvent) => {
const factory = event.mimeData.getData('application/vnd.phosphor.view-container-factory');
const widget = factory && factory();
if (!widget) {
event.dropAction = 'none';
return;
}
event.preventDefault();
event.stopPropagation();
if (!this.toCancelViewContainerDND.disposed) {
event.dropAction = event.proposedAction;
return;
}

const { target, clientX, clientY } = event;
if (target instanceof HTMLElement) {
if (widget.options.disableDraggingToOtherContainers || widget.viewContainer.disableDNDBetweenContainers) {
event.dropAction = 'none';
target.classList.add('theia-cursor-no-drop');
this.toCancelViewContainerDND.push(Disposable.create(() => {
target.classList.remove('theia-cursor-no-drop');
}));
} else {
event.dropAction = event.proposedAction;
}
const { top, bottom, left, right, height } = target.getBoundingClientRect();
const mouseOnTop = (clientY - top) < (height / 2);
const dropTargetClass = `drop-target-${mouseOnTop ? 'top' : 'bottom'}`;
const tabs = this.contentNode.children;
const targetTab = ArrayExt.findFirstValue(tabs, t => ElementExt.hitTest(t, clientX, clientY));
if (!targetTab) {
return;
}
targetTab.classList.add(dropTargetClass);
this.toCancelViewContainerDND.push(Disposable.create(() => {
if (targetTab) {
targetTab.classList.remove(dropTargetClass);
}
}));
const openTabTimer = setTimeout(() => {
const title = this.titles.find(t => (this.renderer as TabBarRenderer).createTabId(t) === targetTab.id);
if (title) {
const mouseStillOnTab = clientX >= left && clientX <= right && clientY >= top && clientY <= bottom;
if (mouseStillOnTab) {
this.currentTitle = title;
}
}
}, 800);
this.toCancelViewContainerDND.push(Disposable.create(() => {
clearTimeout(openTabTimer);
}));
}
};

}
4 changes: 4 additions & 0 deletions packages/core/src/browser/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,10 @@ button.secondary[disabled], .theia-button.secondary[disabled] {
z-index: 999;
}

.theia-cursor-no-drop, .theia-cursor-no-drop:active {
cursor: no-drop;
}

/*-----------------------------------------------------------------------------
| Import children style files
|----------------------------------------------------------------------------*/
Expand Down
14 changes: 14 additions & 0 deletions packages/core/src/browser/style/tabs.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
--theia-tabbar-toolbar-z-index: 1001;
--theia-toolbar-active-transform-scale: 1.272019649;
--theia-horizontal-toolbar-height: calc(var(--theia-private-horizontal-tab-height) + var(--theia-private-horizontal-tab-scrollbar-rail-height) / 2);
--theia-dragover-tab-border-width: 2px;
}

/*-----------------------------------------------------------------------------
Expand Down Expand Up @@ -39,6 +40,19 @@
align-items: center;
}

.p-TabBar[data-orientation='vertical'] .p-TabBar-tab {
border-top: var(--theia-dragover-tab-border-width) solid transparent !important;
border-bottom: var(--theia-dragover-tab-border-width) solid transparent !important;
}

.p-TabBar[data-orientation='vertical'] .p-TabBar-tab.drop-target-top {
border-top-color: var(--theia-activityBar-activeBorder) !important;
}

.p-TabBar[data-orientation='vertical'] .p-TabBar-tab.drop-target-bottom {
border-bottom-color: var(--theia-activityBar-activeBorder) !important;
}

.p-TabBar[data-orientation='horizontal'] .p-TabBar-tab .theia-tab-icon-label,
.p-TabBar-tab.p-mod-drag-image .theia-tab-icon-label {
display: flex;
Expand Down
18 changes: 9 additions & 9 deletions packages/core/src/browser/style/view-container.css
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
height: 100%;
}

.theia-view-container .part > .header {
.theia-view-container-part-header {
cursor: pointer;
display: flex;
align-items: center;
Expand All @@ -61,26 +61,26 @@
font-weight: 700;
}

.theia-view-container .part > .header .theia-ExpansionToggle {
.theia-view-container-part-header .theia-ExpansionToggle {
padding-left: 4px;
}

.theia-view-container > .p-SplitPanel[data-orientation='horizontal'] .part > .header .theia-ExpansionToggle::before {
.theia-view-container > .p-SplitPanel[data-orientation='horizontal'] .part > .theia-header .theia-ExpansionToggle::before {
display: none;
padding-left: 0px;
}

.theia-view-container > .p-SplitPanel[data-orientation='horizontal'] .part > .header .theia-ExpansionToggle {
.theia-view-container > .p-SplitPanel[data-orientation='horizontal'] .part > .theia-header .theia-ExpansionToggle {
padding-left: 0px;
}

.theia-view-container .part > .header .label {
.theia-view-container-part-header .label {
flex: 0;
white-space: nowrap;
text-overflow: ellipsis;
}

.theia-view-container .part > .header .description {
.theia-view-container-part-header .description {
flex: 1;
overflow: hidden;
white-space: nowrap;
Expand All @@ -107,7 +107,7 @@
}

.theia-view-container .part.drop-target {
background: var(--theia-sideBar-dropBackground);
background: var(--theia-list-dropBackground);
border: var(--theia-border-width) dashed var(--theia-contrastActiveBorder);
transition-property: top, left, right, bottom;
transition-duration: 150ms;
Expand Down Expand Up @@ -143,7 +143,7 @@
}

.theia-view-container-part-title.menu-open,
.p-Widget.part:not(.collapsed):hover .header .theia-view-container-part-title,
.p-Widget.part:not(.collapsed):focus-within .header .theia-view-container-part-title {
.p-Widget.part:not(.collapsed):hover .theia-view-container-part-header .theia-view-container-part-title,
.p-Widget.part:not(.collapsed):focus-within .theia-view-container-part-header .theia-view-container-part-title {
display: flex;
}
Loading

0 comments on commit 5426df7

Please sign in to comment.