feat: draggable panels work

This commit is contained in:
mathuo 2021-10-04 20:21:05 +01:00 committed by mathuo
parent b0cf543d44
commit 29771cace1
9 changed files with 161 additions and 55 deletions

View File

@ -17,7 +17,7 @@ export interface ViewContainer<T = any> {
readonly views: View[];
readonly schema: T | any;
readonly icon: string;
readonly onDidAddView: Event<View>;
readonly onDidAddView: Event<{ view: View; index?: number }>;
readonly onDidRemoveView: Event<View>;
addView(view: View, location?: number): void;
layout(schema: T): void;
@ -30,7 +30,10 @@ export class PaneviewContainer implements ViewContainer<SerializedPaneview> {
private readonly _id: string;
private readonly _views: View[] = [];
private readonly _onDidAddView = new Emitter<View>();
private readonly _onDidAddView = new Emitter<{
view: View;
index?: number;
}>();
readonly onDidAddView = this._onDidAddView.event;
private readonly _onDidRemoveView = new Emitter<View>();
readonly onDidRemoveView = this._onDidRemoveView.event;
@ -94,9 +97,9 @@ export class PaneviewContainer implements ViewContainer<SerializedPaneview> {
);
}
addView(view: View, location = 0): void {
this._views.splice(location, 0, view);
this._onDidAddView.fire(view);
addView(view: View, index?: number): void {
this._views.splice(index, 0, view);
this._onDidAddView.fire({ view, index });
}
removeView(view: View): void {

View File

@ -1,8 +1,11 @@
import React from 'react';
export interface RegisteredView {
id: string;
icon: string;
title: string;
isLocationEditable: boolean;
component: React.FunctionComponent;
}
export interface IViewRegistry {
@ -28,22 +31,34 @@ VIEW_REGISTRY.register({
title: 'search',
icon: 'search',
isLocationEditable: false,
component: () => {
return <div>This is a search bar component</div>;
},
});
VIEW_REGISTRY.register({
id: 'home_widget',
title: 'Home',
icon: 'home',
isLocationEditable: true,
component: () => {
return <div>Home</div>;
},
});
VIEW_REGISTRY.register({
id: 'account_widget',
title: 'Account',
icon: 'account_circle',
isLocationEditable: true,
component: () => {
return <div>account_circle</div>;
},
});
VIEW_REGISTRY.register({
id: 'settings_widget',
title: 'Settings',
icon: 'settings',
isLocationEditable: true,
component: () => {
return <div>settings</div>;
},
});

View File

@ -0,0 +1,5 @@
.activity-bar-space {
&.activity-bar-space-dragover {
background-color: green !important;
}
}

View File

@ -1,5 +1,6 @@
import {
CompositeDisposable,
getPaneData,
GridviewApi,
IGridviewPanelProps,
IPaneviewPanelProps,
@ -8,7 +9,7 @@ import {
PaneviewDropEvent,
PaneviewReact,
PaneviewReadyEvent,
getPaneData,
Position,
} from 'dockview';
import * as React from 'react';
import { useLayoutRegistry } from '../layout-grid/registry';
@ -16,6 +17,8 @@ import { PaneviewContainer, ViewContainer } from './viewContainer';
import { IViewService, ViewService } from './viewService';
import { DefaultView } from './view';
import { RegisteredView, VIEW_REGISTRY } from './viewRegistry';
import { toggleClass } from '../dom';
import './widgets.scss';
class ViewServiceModel {
private readonly viewService: IViewService;
@ -96,13 +99,23 @@ class ViewServiceModel {
const viewService = new ViewServiceModel();
const colors = {
home_widget: 'red',
account_widget: 'green',
settings_widget: 'yellow',
search_widget: 'orange',
};
const components: PanelCollection<IPaneviewPanelProps<any>> = {
default: (props: IPaneviewPanelProps<{ viewId: string }>) => {
return (
<div style={{ backgroundColor: 'black', height: '100%' }}>
{props.params.viewId}
</div>
);
const Component = React.useMemo(() => {
const registeredView = VIEW_REGISTRY.getRegisteredView(
props.params.viewId
);
return registeredView?.component;
}, [props.params.viewId]);
return Component ? <Component /> : null;
},
};
@ -158,7 +171,7 @@ export const Activitybar = (props: IGridviewPanelProps) => {
viewService.model.setActiveViewContainer(container.id);
};
const onDrop = (targetContainer: ViewContainer) => (
const onContainerDrop = (targetContainer: ViewContainer) => (
event: React.DragEvent
) => {
const data = event.dataTransfer.getData('application/json');
@ -192,7 +205,7 @@ export const Activitybar = (props: IGridviewPanelProps) => {
};
return (
<div style={{ background: 'rgb(51,51,51)' }}>
<div style={{ background: 'rgb(51,51,51)', cursor: 'pointer' }}>
{containers.map((container, i) => {
const isActive = activeContainerid === container.id;
return (
@ -212,7 +225,7 @@ export const Activitybar = (props: IGridviewPanelProps) => {
JSON.stringify({ container: container.id })
);
}}
onDrop={onDrop(container)}
onDrop={onContainerDrop(container)}
style={{
display: 'flex',
justifyContent: 'center',
@ -235,20 +248,36 @@ export const Activitybar = (props: IGridviewPanelProps) => {
</div>
);
})}
<div
onDragOver={(e) => {
e.preventDefault();
}}
onDragEnter={(e) => {
e.preventDefault();
}}
onDrop={onNewContainer}
style={{ height: '100%', backgroundColor: 'red' }}
></div>
<ExtraSpace onNewContainer={onNewContainer} />
</div>
);
};
const ExtraSpace = (props: {
onNewContainer: (event: React.DragEvent) => void;
}) => {
const ref = React.useRef<HTMLDivElement>(null);
return (
<div
ref={ref}
className="activity-bar-space"
onDragOver={(e) => {
e.preventDefault();
}}
onDragEnter={(e) => {
toggleClass(ref.current, 'activity-bar-space-dragover', true);
e.preventDefault();
}}
onDragLeave={(e) => {
toggleClass(ref.current, 'activity-bar-space-dragover', false);
}}
onDrop={props.onNewContainer}
style={{ height: '100%', backgroundColor: 'red' }}
></div>
);
};
export const Sidebar = () => {
const [sidebarId, setSidebarId] = React.useState<string>(
viewService.model.activeContainer.id
@ -283,7 +312,7 @@ export const SidebarPart = (props: { id: string }) => {
api.onDidLayoutChange(() => {
viewContainer.layout(api.toJSON());
}),
viewContainer.onDidAddView((view) => {
viewContainer.onDidAddView(({ view, index }) => {
api.addPanel({
id: view.id,
isExpanded: view.isExpanded,
@ -292,6 +321,7 @@ export const SidebarPart = (props: { id: string }) => {
params: {
viewId: view.id,
},
index,
});
}),
viewContainer.onDidRemoveView((view) => {
@ -330,17 +360,34 @@ export const SidebarPart = (props: { id: string }) => {
};
const onDidDrop = (event: PaneviewDropEvent) => {
const data = getPaneData();
const data = event.event.getData();
if (!data) {
return;
}
const targetPanel = event.event.panel;
const allPanels = event.api.getPanels();
let toIndex = allPanels.indexOf(targetPanel);
// if (
// event.event.position === Position.Left ||
// event.event.position === Position.Top
// ) {
// toIndex = Math.max(0, toIndex - 1);
// }
if (
event.event.position === Position.Right ||
event.event.position === Position.Bottom
) {
toIndex = Math.min(allPanels.length, toIndex + 1);
}
const viewId = data.paneId;
const viewContainer = viewService.model.getViewContainer(props.id);
const view = viewService.model.getView(viewId);
viewService.model.moveViewToLocation(view, viewContainer, 0);
viewService.model.moveViewToLocation(view, viewContainer, toIndex);
};
if (!props.id) {

View File

@ -17,6 +17,7 @@ export * from './dockview/dockviewComponent';
export * from './dockview/options';
export * from './gridview/gridviewComponent';
export * from './dnd/dataTransfer';
export { Position } from './dnd/droptarget';
export * from './react'; // TODO: should be conditional on whether user wants the React wrappers

View File

@ -9,7 +9,11 @@ import {
import { Emitter, Event } from '../events';
import { IDisposable } from '../lifecycle';
import { Orientation } from '../splitview/core/splitview';
import { PanePanelInitParameter, PaneviewPanel } from './paneviewPanel';
import {
IPaneviewPanel,
PanePanelInitParameter,
PaneviewPanel,
} from './paneviewPanel';
interface ViewContainer {
readonly title: string;
@ -28,11 +32,16 @@ interface IViewContainerService {
getViewContainerModel(container: ViewContainer): ViewContainerModel;
}
export interface PaneviewDropEvent2 extends DroptargetEvent {
panel: IPaneviewPanel;
getData: () => PaneTransfer | undefined;
}
export abstract class DraggablePaneviewPanel extends PaneviewPanel {
private handler: DragHandler | undefined;
private target: Droptarget | undefined;
private readonly _onDidDrop = new Emitter<DroptargetEvent>();
private readonly _onDidDrop = new Emitter<PaneviewDropEvent2>();
readonly onDidDrop = this._onDidDrop.event;
constructor(
@ -93,7 +102,11 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
const data = getPaneData();
if (!data) {
this._onDidDrop.fire(event);
this._onDidDrop.fire({
...event,
panel: this,
getData: () => getPaneData(),
});
return;
}
@ -103,20 +116,30 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
const existingPanel = containerApi.getPanel(id);
if (!existingPanel) {
this._onDidDrop.fire(event);
this._onDidDrop.fire({
...event,
panel: this,
getData: () => getPaneData(),
});
return;
}
const fromIndex = containerApi
.getPanels()
.indexOf(existingPanel);
const allPanels = containerApi.getPanels();
const fromIndex = allPanels.indexOf(existingPanel);
let toIndex = containerApi.getPanels().indexOf(this);
if (
event.position === Position.Left ||
event.position === Position.Top
) {
toIndex = Math.max(0, toIndex - 1);
}
if (
event.position === Position.Right ||
event.position === Position.Bottom
) {
toIndex = Math.max(0, toIndex + 1);
toIndex = Math.min(allPanels.length - 1, toIndex + 1);
}
containerApi.movePanel(fromIndex, toIndex);

View File

@ -23,6 +23,11 @@
border-top: 1px solid var(--dv-paneview-header-border-color);
}
}
.default-header {
background-color: var(--dv-group-view-background-color);
color: var(--dv-activegroup-visiblepanel-tab-color);
}
}
&:first-of-type > .pane > .pane-header {

View File

@ -22,8 +22,10 @@ import {
PanePanelInitParameter,
IPaneviewPanel,
} from './paneviewPanel';
import { DraggablePaneviewPanel } from './draggablePaneviewPanel';
import { DroptargetEvent } from '../dnd/droptarget';
import {
DraggablePaneviewPanel,
PaneviewDropEvent2,
} from './draggablePaneviewPanel';
export interface SerializedPaneviewPanel {
snap?: boolean;
@ -58,6 +60,7 @@ class DefaultHeader extends CompositeDisposable implements IPaneHeaderPart {
constructor() {
super();
this._element = document.createElement('div');
this._element.className = 'default-header';
this.addDisposables(
addDisposableListener(this.element, 'click', () => {
@ -128,7 +131,7 @@ export interface IPaneviewComponent extends IDisposable {
readonly height: number;
readonly minimumSize: number;
readonly maximumSize: number;
readonly onDidDrop: Event<DroptargetEvent>;
readonly onDidDrop: Event<PaneviewDropEvent2>;
readonly onDidLayoutChange: Event<void>;
addPanel(options: AddPaneviewCompponentOptions): IDisposable;
layout(width: number, height: number): void;
@ -156,8 +159,8 @@ export class PaneviewComponent
private readonly _onDidLayoutChange = new Emitter<void>();
readonly onDidLayoutChange: Event<void> = this._onDidLayoutChange.event;
private readonly _onDidDrop = new Emitter<DroptargetEvent>();
readonly onDidDrop: Event<DroptargetEvent> = this._onDidDrop.event;
private readonly _onDidDrop = new Emitter<PaneviewDropEvent2>();
readonly onDidDrop: Event<PaneviewDropEvent2> = this._onDidDrop.event;
get onDidAddView() {
return this._paneview.onDidAddView;

View File

@ -9,7 +9,7 @@ import { PaneviewApi } from '../../api/component.api';
import { PanePanelSection } from './view';
import { PanelCollection, PanelParameters } from '../types';
import { watchElementResize } from '../../dom';
import { DroptargetEvent } from '../../dnd/droptarget';
import { PaneviewDropEvent2 } from '../../paneview/draggablePaneviewPanel';
export interface PaneviewReadyEvent {
api: PaneviewApi;
@ -24,7 +24,7 @@ export interface IPaneviewPanelProps<T extends {} = Record<string, any>>
export interface PaneviewDropEvent {
api: PaneviewApi;
event: DroptargetEvent;
event: PaneviewDropEvent2;
}
export interface IPaneviewReactProps {
@ -62,18 +62,6 @@ export const PaneviewReact = React.forwardRef(
};
}, [props.disableAutoResizing]);
React.useEffect(() => {
paneviewRef.current?.updateOptions({
frameworkComponents: props.components,
});
}, [props.components]);
React.useEffect(() => {
paneviewRef.current?.updateOptions({
headerframeworkComponents: props.headerComponents,
});
}, [props.headerComponents]);
React.useEffect(() => {
const createComponent = (
id: string,
@ -116,6 +104,19 @@ export const PaneviewReact = React.forwardRef(
};
}, []);
React.useEffect(() => {
console.log(paneviewRef.current);
paneviewRef.current?.updateOptions({
frameworkComponents: props.components,
});
}, [props.components]);
React.useEffect(() => {
paneviewRef.current?.updateOptions({
headerframeworkComponents: props.headerComponents,
});
}, [props.headerComponents]);
React.useEffect(() => {
if (!paneviewRef.current) {
return () => {
@ -127,7 +128,10 @@ export const PaneviewReact = React.forwardRef(
const disposable = paneview.onDidDrop((event) => {
if (props.onDidDrop) {
props.onDidDrop({ event, api: new PaneviewApi(paneview) });
props.onDidDrop({
event,
api: new PaneviewApi(paneview),
});
}
});