mirror of
https://github.com/mathuo/dockview
synced 2025-09-06 09:26:38 +00:00
feat: draggable panels work
This commit is contained in:
parent
b0cf543d44
commit
29771cace1
@ -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 {
|
||||
|
@ -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>;
|
||||
},
|
||||
});
|
5
packages/dockview-demo/src/services/widgets.scss
Normal file
5
packages/dockview-demo/src/services/widgets.scss
Normal file
@ -0,0 +1,5 @@
|
||||
.activity-bar-space {
|
||||
&.activity-bar-space-dragover {
|
||||
background-color: green !important;
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user