feat: expose dnd functions

This commit is contained in:
mathuo 2021-08-23 20:53:39 +01:00
parent 7ec824ea01
commit c51597fde3
12 changed files with 700 additions and 147 deletions

View File

@ -49,18 +49,27 @@ export const Activitybar = (props: IGridviewPanelProps) => {
return (
<div className="activity-bar" onClick={onOpenSidebar}>
<DockviewDropTarget
validOverlays={'vertical'}
canDisplayOverlay={true}
>
<div className="activity-bar-item">
<ActivitybarImage
url={
'https://fonts.gstatic.com/s/i/materialicons/search/v7/24px.svg'
}
/>
</div>
</DockviewDropTarget>
<div className="activity-bar-item">
<ActivitybarImage
url={
'https://fonts.gstatic.com/s/i/materialicons/search/v7/24px.svg'
}
/>
</div>
<div className="activity-bar-item">
<ActivitybarImage
url={
'https://fonts.gstatic.com/s/i/materialicons/search/v7/24px.svg'
}
/>
</div>
<div className="activity-bar-item">
<ActivitybarImage
url={
'https://fonts.gstatic.com/s/i/materialicons/search/v7/24px.svg'
}
/>
</div>
</div>
);
};

View File

@ -8,12 +8,12 @@ import {
SerializedGridview,
GridviewApi,
} from 'dockview';
import { Activitybar } from './activitybar';
import { Activitybar } from '../services/widgets';
import { Footer } from './footer';
import { Panel } from './panel';
import { TestGrid } from './layoutGrid';
import { useLayoutRegistry } from './registry';
import { Sidebar } from './sidebar';
import { Sidebar } from '../services/widgets';
const rootcomponents: {
[index: string]: React.FunctionComponent<IGridviewPanelProps>;

View File

@ -5,7 +5,7 @@
"data": {
"id": "1",
"component": "controlCenter",
"headerComponent": "default",
"props": {},
"title": "Control Center",
"state": {}
@ -17,7 +17,7 @@
"data": {
"id": "2",
"component": "default",
"headerComponent": "default",
"props": {},
"title": "Panel 1",
"state": {}
@ -30,7 +30,7 @@
"data": {
"id": "3",
"component": "default",
"headerComponent": "default",
"props": {},
"title": "Panel 2",
"state": {}
@ -43,7 +43,7 @@
"data": {
"id": "4",
"component": "default",
"headerComponent": "default",
"props": {},
"title": "Panel 3",
"state": {}

View File

@ -12,107 +12,6 @@ import { ControlCenter } from './controlCenter';
import { toggleClass } from '../dom';
import './sidebar.scss';
const DefaultHeader = (props: IPaneviewPanelProps) => {
const ref = React.useRef<HTMLDivElement>();
const mouseover = React.useRef<boolean>();
const [url, setUrl] = React.useState<string>(
props.api.isExpanded
? 'https://fonts.gstatic.com/s/i/materialicons/expand_more/v6/24px.svg'
: 'https://fonts.gstatic.com/s/i/materialicons/chevron_right/v7/24px.svg'
);
const toggle = () => {
toggleClass(
ref.current,
'within',
props.api.isExpanded && mouseover.current
);
};
React.useEffect(() => {
const disposable = new CompositeDisposable(
props.api.onDidExpansionChange((event) => {
setUrl(
event.isExpanded
? 'https://fonts.gstatic.com/s/i/materialicons/expand_more/v6/24px.svg'
: 'https://fonts.gstatic.com/s/i/materialicons/chevron_right/v7/24px.svg'
);
toggle();
}),
props.api.onMouseEnter((ev) => {
mouseover.current = true;
toggle();
}),
props.api.onMouseLeave((ev) => {
mouseover.current = false;
toggle();
})
);
return () => {
disposable.dispose();
};
});
const onClick = (event: React.MouseEvent) => {
if (event.defaultPrevented) {
return;
}
props.api.setExpanded(!props.api.isExpanded);
};
const onClickAction = (event: React.MouseEvent) => {
event.preventDefault();
};
return (
<div
className="my-header"
ref={ref}
style={{
display: 'flex',
fontSize: '11px',
textTransform: 'uppercase',
cursor: 'pointer',
}}
onClick={onClick}
>
<div style={{ width: '20px' }}>
<a
style={{
WebkitMask: `url(${url}) 50% 50% / 100% 100% no-repeat`,
height: '100%',
display: 'block',
backgroundColor: 'lightgray',
}}
/>
</div>
<span>{props.title}</span>
<span style={{ flexGrow: 1 }} />
<div className="actions">
<div
onClick={onClickAction}
style={{
height: '100%',
width: '20px',
}}
>
<a
title="Example action"
style={{
WebkitMask: `url(https://fonts.gstatic.com/s/i/materialicons/help_outline/v6/24px.svg) 50% 50% / 80% 80% no-repeat`,
height: '100%',
display: 'block',
backgroundColor: 'lightgray',
}}
/>
</div>
</div>
</div>
);
};
const components = {
default: (props: IPaneviewPanelProps) => {
return <div style={{ height: '100%' }}>This is an example panel</div>;
@ -120,10 +19,6 @@ const components = {
controlCenter: ControlCenter,
};
const headerComponents = {
default: DefaultHeader,
};
export const Sidebar = (props: IGridviewPanelProps) => {
const api = React.useRef<PaneviewApi>();
@ -139,25 +34,21 @@ export const Sidebar = (props: IGridviewPanelProps) => {
event.api.addPanel({
id: '1',
component: 'default',
headerComponent: 'default',
title: 'Control Center',
});
event.api.addPanel({
id: '2',
component: 'default',
headerComponent: 'default',
title: 'Panel 1',
});
event.api.addPanel({
id: '3',
component: 'default',
headerComponent: 'default',
title: 'Panel 2',
});
event.api.addPanel({
id: '4',
component: 'default',
headerComponent: 'default',
title: 'Panel 3',
});
@ -196,7 +87,6 @@ export const Sidebar = (props: IGridviewPanelProps) => {
}}
>
<PaneviewReact
headerComponents={headerComponents}
components={components}
onReady={onReady}
onDidDrop={onDidDrop}

View File

@ -0,0 +1,34 @@
export interface View {
readonly id: string;
readonly isExpanded: boolean;
readonly title: string;
toJSON(): object;
}
export class DefaultView implements View {
private readonly _id: string;
private readonly _title: string;
private readonly _isExpanded: boolean;
get id() {
return this._id;
}
get title() {
return this._title;
}
get isExpanded() {
return this._isExpanded;
}
constructor(id: string, title: string, isExpanded: boolean) {
this._id = id;
this._title = title;
this._isExpanded = isExpanded;
}
toJSON() {
return { id: this.id, title: this.title, isExpanded: this.isExpanded };
}
}

View File

@ -0,0 +1,112 @@
import {
CompositeDisposable,
Emitter,
Event,
SerializedPaneview,
} from 'dockview';
import { DefaultView, View } from './view';
export interface ViewContainer<T = any> {
readonly id: string;
readonly canDelete: boolean;
readonly views: View[];
readonly schema: T | undefined;
readonly onDidAddView: Event<View>;
readonly onDidRemoveView: Event<View>;
addView(view: View, location?: number): void;
layout(schema: T): void;
removeView(view: View): void;
clear(): void;
toJSON(): object;
}
export class PaneviewContainer implements ViewContainer<SerializedPaneview> {
private readonly _id: string;
private readonly _canDelete: boolean;
private readonly _views: View[];
private readonly _onDidAddView = new Emitter<View>();
readonly onDidAddView = this._onDidAddView.event;
private readonly _onDidRemoveView = new Emitter<View>();
readonly onDidRemoveView = this._onDidRemoveView.event;
private _schema: SerializedPaneview | undefined;
get id() {
return this._id;
}
get views() {
return this._views;
}
get canDelete() {
return this._canDelete;
}
get schema(): SerializedPaneview | undefined {
if (!this._schema) {
this._schema = JSON.parse(
localStorage.getItem(`viewcontainer_${this.id}`)
);
}
return this._schema;
}
constructor(id: string, canDelete: boolean) {
this._id = id;
this._canDelete = canDelete;
const schema = this.schema;
if (this.schema) {
this._views = this.schema.views.map((v) => {
return new DefaultView(v.data.id, v.data.title, v.expanded);
});
} else {
this._views = [];
}
// super();
// this.addDisposables(this._onDidAddView, this._onDidRemoveView);
}
layout(schema: SerializedPaneview): void {
this._schema = schema;
localStorage.setItem(
`viewcontainer_${this.id}`,
JSON.stringify(schema)
);
}
addView(view: View, location = 0): void {
this._views.splice(location, 0, view);
this._onDidAddView.fire(view);
}
removeView(view: View): void {
const index = this._views.indexOf(view);
if (index < 0) {
throw new Error('invalid');
}
this._views.splice(index, 1);
if (this._schema) {
this._schema = { ...this._schema };
this._schema.views = this._schema.views.filter(
(v) => v.data.id !== view.id
);
this.layout(this._schema);
}
this._onDidRemoveView.fire(view);
}
clear() {
localStorage.removeItem(`viewcontainer_${this.id}`);
}
toJSON() {
return { id: this.id, views: this.views.map((v) => ({ id: v.id })) };
}
}

View File

@ -0,0 +1,175 @@
import { Emitter, Event } from 'dockview';
import { DefaultView, View } from './view';
import { PaneviewContainer, ViewContainer } from './viewContainer';
export interface IViewService {
readonly containers: ViewContainer[];
readonly onDidActiveContainerChange: Event<void>;
readonly onDidRemoveContainer: Event<void>;
readonly onDidAddContainer: Event<void>;
readonly activeContainer: ViewContainer | undefined;
addContainer(container: ViewContainer): void;
setActiveViewContainer(id: string): void;
getView(id: string): View | undefined;
moveViewToLocation(
view: View,
targetViewContainer: ViewContainer,
targetLocation: number
): void;
addViews(view: View, viewContainer: ViewContainer, location?: number): void;
removeViews(removeViews: View[], viewContainer: ViewContainer): void;
getViewContainer(id: string): ViewContainer | undefined;
toJSON(): object;
load(layout: any): void;
}
export class ViewService implements IViewService {
private _viewContainers: ViewContainer[] = [];
private readonly _onDidActiveContainerChange = new Emitter<void>();
readonly onDidActiveContainerChange = this._onDidActiveContainerChange
.event;
private readonly _onDidRemoveContainer = new Emitter<void>();
readonly onDidRemoveContainer = this._onDidRemoveContainer.event;
private readonly _onDidAddContainer = new Emitter<void>();
readonly onDidAddContainer = this._onDidAddContainer.event;
private readonly _onDidContainersChange = new Emitter<void>();
readonly onDidContainersChange = this._onDidContainersChange.event;
private _activeViewContainerId: string;
get containers(): ViewContainer[] {
return this._viewContainers;
}
get activeContainer(): ViewContainer | undefined {
return this._viewContainers.find(
(c) => c.id === this._activeViewContainerId
);
}
constructor() {
//
}
load(layout: any): void {
const { containers, activeContainer } = layout;
for (const container of containers) {
const { id, views } = container;
const viewContainer = new PaneviewContainer(id, true);
for (const view of views) {
viewContainer.addView(
new DefaultView(view.id, view.title, view.isExpanded)
);
}
this.addContainer(viewContainer);
}
this.setActiveViewContainer(activeContainer);
}
insertContainerAfter(source: ViewContainer, target: ViewContainer): void {
const sourceIndex = this._viewContainers.findIndex(
(c) => c.id === source.id
);
const view = this._viewContainers.splice(sourceIndex, 1)[0];
const targetIndex = this._viewContainers.findIndex(
(c) => c.id === target.id
);
this._viewContainers.splice(targetIndex + 1, 0, view);
this._viewContainers = [...this._viewContainers];
this._onDidContainersChange.fire();
}
addContainer(container: ViewContainer): void {
this._viewContainers = [...this._viewContainers, container];
this._activeViewContainerId = container.id;
this._onDidAddContainer.fire();
}
removeContainer(container: ViewContainer): void {
this._viewContainers = this._viewContainers.filter(
(c) => c.id !== container.id
);
if (this._activeViewContainerId === container.id) {
this._activeViewContainerId =
this._viewContainers.length > 0
? this._viewContainers[0].id
: undefined;
}
this._onDidRemoveContainer.fire();
}
setActiveViewContainer(id: string): void {
if (!this._viewContainers.find((c) => c.id === id)) {
throw new Error(`invalid container ${id}`);
}
this._activeViewContainerId = id;
this._onDidActiveContainerChange.fire();
}
getView(id: string): View | undefined {
for (const container of Array.from(this._viewContainers.values())) {
const view = container.views.find((v) => v.id === id);
if (view) {
return view;
}
}
return undefined;
}
moveViewToLocation(
view: View,
targetViewContainer: ViewContainer,
targetLocation: number
): void {
const sourceViewContainer = this.getViewContainer2(view);
this.removeViews([view], sourceViewContainer);
this.addViews(view, targetViewContainer, targetLocation);
}
addViews(
view: View,
viewContainer: ViewContainer,
location?: number
): void {
viewContainer.addView(view, location);
}
removeViews(removeViews: View[], viewContainer: ViewContainer): void {
for (const view of removeViews) {
viewContainer.removeView(view);
if (viewContainer.views.length === 0) {
viewContainer.clear();
this.removeContainer(viewContainer);
}
}
}
getViewContainer(id: string): ViewContainer | undefined {
return this._viewContainers.find((c) => c.id === id);
}
getViewContainer2(view: View): ViewContainer | undefined {
for (const container of Array.from(this._viewContainers.values())) {
const v = container.views.find((v) => v.id === view.id);
if (v) {
return container;
}
}
return undefined;
}
toJSON() {
return {
containers: this.containers.map((c) => c.toJSON()),
activeContainer: this.activeContainer.id,
};
}
}

View File

@ -0,0 +1,291 @@
import {
CompositeDisposable,
GridviewApi,
IGridviewPanelProps,
IPaneviewPanelProps,
PanelCollection,
PaneviewApi,
PaneviewDropEvent,
PaneviewReact,
PaneviewReadyEvent,
getPaneData,
} from 'dockview';
import * as React from 'react';
import { useLayoutRegistry } from '../layout-grid/registry';
import { PaneviewContainer, ViewContainer } from './viewContainer';
import { ViewService } from './viewService';
import { DefaultView } from './view';
const viewService = new ViewService();
const layout = localStorage.getItem('viewservice');
if (layout) {
viewService.load(JSON.parse(layout));
} else {
const container1 = new PaneviewContainer('c1', true);
if (!container1.schema) {
container1.addView(new DefaultView('panel1', 'Panel 1', true));
container1.addView(new DefaultView('panel2', 'Panel 2', true));
}
const container2 = new PaneviewContainer('c2', true);
if (!container2.schema) {
container2.addView(new DefaultView('panel3', 'Panel 3', true));
container2.addView(new DefaultView('panel4', 'Panel 4', true));
}
viewService.addContainer(container1);
viewService.addContainer(container2);
}
const save = () => {
localStorage.setItem('viewservice', JSON.stringify(viewService.toJSON()));
};
viewService.onDidActiveContainerChange(save);
viewService.onDidRemoveContainer(save);
viewService.onDidAddContainer(save);
const components: PanelCollection<IPaneviewPanelProps<any>> = {
default: (props: IPaneviewPanelProps<{ viewId: string }>) => {
return (
<div style={{ backgroundColor: 'black', height: '100%' }}>
{props.params.viewId}
</div>
);
},
};
export const Activitybar = (props: IGridviewPanelProps) => {
const [activeContainerid, setActiveContainerId] = React.useState<string>(
viewService.activeContainer.id
);
const [containers, setContainers] = React.useState<ViewContainer[]>(
viewService.containers
);
const registry = useLayoutRegistry();
React.useEffect(() => {
const disposable = new CompositeDisposable(
viewService.onDidActiveContainerChange(() => {
setActiveContainerId(viewService.activeContainer.id);
}),
viewService.onDidAddContainer(() => {
setContainers(viewService.containers);
}),
viewService.onDidRemoveContainer(() => {
setContainers(viewService.containers);
}),
viewService.onDidContainersChange(() => {
setContainers(viewService.containers);
})
);
return () => {
disposable.dispose();
};
}, []);
const onClick = (container: ViewContainer, alwaysOpen = false) => (
event: React.MouseEvent
) => {
const api = registry.get<GridviewApi>('gridview');
const selectedActive = container.id === activeContainerid;
const sidebarPanel = api.getPanel('sidebar');
if (sidebarPanel.api.isVisible) {
if (!alwaysOpen && selectedActive) {
api.setVisible(sidebarPanel, false);
}
} else {
event.preventDefault(); // prevent focus
api.setVisible(sidebarPanel, true);
sidebarPanel.focus();
}
viewService.setActiveViewContainer(container.id);
};
const onDrop = (targetContainer: ViewContainer) => (
event: React.DragEvent
) => {
const data = event.dataTransfer.getData('application/json');
if (data) {
const { container } = JSON.parse(data);
const sourceContainer = viewService.getViewContainer(container);
viewService.insertContainerAfter(sourceContainer, targetContainer);
}
};
const onNewContainer = (event: React.DragEvent) => {
const data = getPaneData();
if (data) {
const { paneId } = data;
const view = viewService.getView(paneId);
const viewContainer = viewService.getViewContainer2(view);
viewService.removeViews([view], viewContainer);
// viewContainer.removeView(view);
const newContainer = new PaneviewContainer(
`t_${Date.now().toString().substr(5)}`,
true
);
newContainer.addView(view);
viewService.addContainer(newContainer);
}
};
return (
<div style={{ background: 'rgb(51,51,51)' }}>
{containers.map((container, i) => {
const isActive = activeContainerid === container.id;
return (
<div
onClick={onClick(container)}
onDragOver={(e) => {
e.preventDefault();
onClick(container, true)(e);
}}
onDragEnter={(e) => {
e.preventDefault();
}}
draggable={true}
onDragStart={(e) => {
e.dataTransfer.setData(
'application/json',
JSON.stringify({ container: container.id })
);
}}
onDrop={onDrop(container)}
style={{
height: '48px',
boxSizing: 'border-box',
borderLeft: isActive
? '1px solid white'
: '1px solid transparent',
}}
key={i}
>
{container.id}
</div>
);
})}
<div
onDragOver={(e) => {
e.preventDefault();
}}
onDragEnter={(e) => {
e.preventDefault();
}}
onDrop={onNewContainer}
style={{ height: '100%', backgroundColor: 'red' }}
></div>
</div>
);
};
export const Sidebar = () => {
const [sidebarId, setSidebarId] = React.useState<string>(
viewService.activeContainer.id
);
React.useEffect(() => {
const disposable = viewService.onDidActiveContainerChange(() => {
setSidebarId(viewService.activeContainer.id);
});
return () => {
disposable.dispose();
};
}, []);
return <SidebarPart id={sidebarId} />;
};
export const SidebarPart = (props: { id: string }) => {
const [api, setApi] = React.useState<PaneviewApi>();
React.useEffect(() => {
if (!api) {
return () => {
//
};
}
const viewContainer = viewService.getViewContainer(props.id);
const disposables = new CompositeDisposable(
api.onDidLayoutChange(() => {
viewContainer.layout(api.toJSON());
}),
viewContainer.onDidAddView((view) => {
api.addPanel({
id: view.id,
isExpanded: view.isExpanded,
title: view.title,
component: 'default',
params: {
viewId: view.id,
},
});
}),
viewContainer.onDidRemoveView((view) => {
const panel = api.getPanel(view.id);
api.removePanel(panel);
})
);
const schema = viewContainer.schema;
if (schema) {
api.fromJSON(schema);
} else {
api.getPanels().forEach((p) => {
api.removePanel(p);
});
viewContainer.views.forEach((view) => {
api.addPanel({
id: view.id,
isExpanded: view.isExpanded,
title: view.title,
component: 'default',
params: {
viewId: view.id,
},
});
});
}
return () => {
disposables.dispose();
};
}, [api, props.id]);
const onReady = (event: PaneviewReadyEvent) => {
setApi(event.api);
};
const onDidDrop = (event: PaneviewDropEvent) => {
const data = getPaneData();
if (!data) {
return;
}
const viewId = data.paneId;
const viewContainer = viewService.getViewContainer(props.id);
const view = viewService.getView(viewId);
viewService.moveViewToLocation(view, viewContainer, 0);
};
if (!props.id) {
return null;
}
return (
<PaneviewReact
onDidDrop={onDidDrop}
components={components}
onReady={onReady}
/>
);
};

View File

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

View File

@ -96,6 +96,12 @@ export class Paneview extends CompositeDisposable implements IDisposable {
this.addDisposables(
this.splitview.onDidSashEnd(() => {
this._onDidChange.fire(undefined);
}),
this.splitview.onDidAddView(() => {
this._onDidChange.fire();
}),
this.splitview.onDidRemoveView(() => {
this._onDidChange.fire();
})
);
}

View File

@ -128,9 +128,10 @@ export interface IPaneviewComponent extends IDisposable {
readonly height: number;
readonly minimumSize: number;
readonly maximumSize: number;
readonly onDidDrop: Event<DroptargetEvent>;
readonly onDidLayoutChange: Event<void>;
addPanel(options: AddPaneviewCompponentOptions): IDisposable;
layout(width: number, height: number): void;
onDidLayoutChange: Event<void>;
toJSON(): SerializedPaneview;
fromJSON(
serializedPaneview: SerializedPaneview,
@ -142,6 +143,7 @@ export interface IPaneviewComponent extends IDisposable {
removePanel(panel: IPaneviewPanel): void;
getPanel(id: string): IPaneviewPanel | undefined;
movePanel(from: number, to: number): void;
updateOptions(options: Partial<PaneviewComponentOptions>): void;
}
export class PaneviewComponent
@ -199,12 +201,20 @@ export class PaneviewComponent
: this.paneview.orthogonalSize;
}
private _options: PaneviewComponentOptions;
get options() {
return this._options;
}
constructor(
private element: HTMLElement,
private readonly options: PaneviewComponentOptions
options: PaneviewComponentOptions
) {
super();
this._options = options;
if (!options.components) {
options.components = {};
}
@ -224,6 +234,10 @@ export class PaneviewComponent
//
}
updateOptions(options: Partial<PaneviewComponentOptions>): void {
this._options = { ...this.options, ...options };
}
addPanel(options: AddPaneviewCompponentOptions): IDisposable {
const body = createComponent(
options.id,
@ -269,17 +283,17 @@ export class PaneviewComponent
disableDnd: !!this.options.disableDnd,
});
view.onDidDrop((event) => {
this._onDidDrop.fire(event);
});
const disposable = new CompositeDisposable(
view.onDidDrop((event) => {
this._onDidDrop.fire(event);
})
);
const size: Sizing | number =
typeof options.size === 'number' ? options.size : Sizing.Distribute;
const index =
typeof options.index === 'number' ? options.index : undefined;
this.paneview.addPane(view, size, index);
view.init({
params: options.params || {},
minimumBodySize: options.minimumBodySize,
@ -289,13 +303,11 @@ export class PaneviewComponent
containerApi: new PaneviewApi(this),
});
this.paneview.addPane(view, size, index);
view.orientation = this.paneview.orientation;
return {
dispose: () => {
//
},
};
return disposable;
}
getPanels(): PaneviewPanel[] {

View File

@ -62,6 +62,18 @@ 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,
@ -90,12 +102,6 @@ export const PaneviewReact = React.forwardRef(
const api = new PaneviewApi(paneview);
const disposable = paneview.onDidDrop((event) => {
if (props.onDidDrop) {
props.onDidDrop({ event, api });
}
});
const { clientWidth, clientHeight } = domRef.current!;
paneview.layout(clientWidth, clientHeight);
@ -106,11 +112,28 @@ export const PaneviewReact = React.forwardRef(
paneviewRef.current = paneview;
return () => {
disposable.dispose();
paneview.dispose();
};
}, []);
React.useEffect(() => {
if (!paneviewRef.current) {
return;
}
const paneview = paneviewRef.current;
const disposable = paneview.onDidDrop((event) => {
if (props.onDidDrop) {
props.onDidDrop({ event, api: new PaneviewApi(paneview) });
}
});
return () => {
disposable.dispose();
};
}, [props.onDidDrop]);
return (
<div
className={props.className}