mirror of
https://github.com/mathuo/dockview
synced 2025-11-05 06:30:37 +00:00
Merge branch 'dnd-improvements' of https://github.com/mathuo/dockview into dnd-improvements
This commit is contained in:
commit
b0cf543d44
@ -1,7 +1,7 @@
|
||||
<!-- <!DOCTYPE html> -->
|
||||
<html>
|
||||
<head>
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@ -1,34 +1,64 @@
|
||||
export interface SerializedView {
|
||||
id: string;
|
||||
isExpanded: boolean;
|
||||
}
|
||||
|
||||
export type ViewOptions = {
|
||||
id: string;
|
||||
title: string;
|
||||
isExpanded: boolean;
|
||||
isLocationEditable: boolean;
|
||||
icon: string;
|
||||
};
|
||||
|
||||
export interface View {
|
||||
readonly id: string;
|
||||
readonly isExpanded: boolean;
|
||||
readonly title: string;
|
||||
toJSON(): object;
|
||||
readonly isLocationEditable: boolean;
|
||||
readonly icon: string;
|
||||
toJSON(): SerializedView;
|
||||
}
|
||||
|
||||
export class DefaultView implements View {
|
||||
private readonly _id: string;
|
||||
private readonly _title: string;
|
||||
private readonly _isExpanded: boolean;
|
||||
private readonly _isLocationEditable: boolean;
|
||||
private readonly _icon: string;
|
||||
|
||||
get id() {
|
||||
get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get title() {
|
||||
get title(): string {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
get isExpanded() {
|
||||
get isExpanded(): boolean {
|
||||
return this._isExpanded;
|
||||
}
|
||||
|
||||
constructor(id: string, title: string, isExpanded: boolean) {
|
||||
this._id = id;
|
||||
this._title = title;
|
||||
this._isExpanded = isExpanded;
|
||||
get isLocationEditable(): boolean {
|
||||
return this._isLocationEditable;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return { id: this.id, title: this.title, isExpanded: this.isExpanded };
|
||||
get icon(): string {
|
||||
return this._icon;
|
||||
}
|
||||
|
||||
constructor(options: ViewOptions) {
|
||||
this._id = options.id;
|
||||
this._title = options.title;
|
||||
this._isExpanded = options.isExpanded;
|
||||
this._isLocationEditable = options.isLocationEditable;
|
||||
this._icon = options.icon;
|
||||
}
|
||||
|
||||
toJSON(): SerializedView {
|
||||
return {
|
||||
id: this.id,
|
||||
isExpanded: this.isExpanded,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,26 +4,31 @@ import {
|
||||
Event,
|
||||
SerializedPaneview,
|
||||
} from 'dockview';
|
||||
import { DefaultView, View } from './view';
|
||||
import { DefaultView, View, SerializedView } from './view';
|
||||
import { IViewRegistry } from './viewRegistry';
|
||||
|
||||
export interface SerializedViewContainer {
|
||||
readonly id: string;
|
||||
readonly views: SerializedView[];
|
||||
}
|
||||
|
||||
export interface ViewContainer<T = any> {
|
||||
readonly id: string;
|
||||
readonly canDelete: boolean;
|
||||
readonly views: View[];
|
||||
readonly schema: T | undefined;
|
||||
readonly schema: T | any;
|
||||
readonly icon: string;
|
||||
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;
|
||||
toJSON(): SerializedViewContainer;
|
||||
}
|
||||
|
||||
export class PaneviewContainer implements ViewContainer<SerializedPaneview> {
|
||||
private readonly _id: string;
|
||||
private readonly _canDelete: boolean;
|
||||
private readonly _views: View[];
|
||||
private readonly _views: View[] = [];
|
||||
|
||||
private readonly _onDidAddView = new Emitter<View>();
|
||||
readonly onDidAddView = this._onDidAddView.event;
|
||||
@ -40,10 +45,6 @@ export class PaneviewContainer implements ViewContainer<SerializedPaneview> {
|
||||
return this._views;
|
||||
}
|
||||
|
||||
get canDelete() {
|
||||
return this._canDelete;
|
||||
}
|
||||
|
||||
get schema(): SerializedPaneview | undefined {
|
||||
if (!this._schema) {
|
||||
this._schema = JSON.parse(
|
||||
@ -53,21 +54,35 @@ export class PaneviewContainer implements ViewContainer<SerializedPaneview> {
|
||||
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 = [];
|
||||
get icon(): string {
|
||||
const defaultIcon = 'search';
|
||||
if (this.views.length > 0) {
|
||||
return this.views.find((v) => !!v.icon)?.icon || defaultIcon;
|
||||
}
|
||||
// super();
|
||||
return defaultIcon;
|
||||
}
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
viewRegistry: IViewRegistry,
|
||||
views?: SerializedView[]
|
||||
) {
|
||||
this._id = id;
|
||||
|
||||
if (views) {
|
||||
for (const view of views) {
|
||||
const registeredView = viewRegistry.getRegisteredView(view.id);
|
||||
this.addView(
|
||||
new DefaultView({
|
||||
id: view.id,
|
||||
title: registeredView.title,
|
||||
isExpanded: view.isExpanded,
|
||||
isLocationEditable: registeredView.isLocationEditable,
|
||||
icon: registeredView.icon,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
// this.addDisposables(this._onDidAddView, this._onDidRemoveView);
|
||||
}
|
||||
|
||||
@ -106,7 +121,7 @@ export class PaneviewContainer implements ViewContainer<SerializedPaneview> {
|
||||
localStorage.removeItem(`viewcontainer_${this.id}`);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return { id: this.id, views: this.views.map((v) => ({ id: v.id })) };
|
||||
toJSON(): SerializedViewContainer {
|
||||
return { id: this.id, views: this.views.map((v) => v.toJSON()) };
|
||||
}
|
||||
}
|
||||
|
||||
49
packages/dockview-demo/src/services/viewRegistry.ts
Normal file
49
packages/dockview-demo/src/services/viewRegistry.ts
Normal file
@ -0,0 +1,49 @@
|
||||
export interface RegisteredView {
|
||||
id: string;
|
||||
icon: string;
|
||||
title: string;
|
||||
isLocationEditable: boolean;
|
||||
}
|
||||
|
||||
export interface IViewRegistry {
|
||||
getRegisteredView(id: string): RegisteredView | undefined;
|
||||
}
|
||||
|
||||
export class ViewRegistry {
|
||||
private readonly _registry = new Map<string, RegisteredView>();
|
||||
|
||||
register(registeredView: RegisteredView): void {
|
||||
this._registry.set(registeredView.id, registeredView);
|
||||
}
|
||||
|
||||
getRegisteredView(id: string): RegisteredView | undefined {
|
||||
return this._registry.get(id);
|
||||
}
|
||||
}
|
||||
|
||||
export const VIEW_REGISTRY = new ViewRegistry();
|
||||
|
||||
VIEW_REGISTRY.register({
|
||||
id: 'search_widget',
|
||||
title: 'search',
|
||||
icon: 'search',
|
||||
isLocationEditable: false,
|
||||
});
|
||||
VIEW_REGISTRY.register({
|
||||
id: 'home_widget',
|
||||
title: 'Home',
|
||||
icon: 'home',
|
||||
isLocationEditable: true,
|
||||
});
|
||||
VIEW_REGISTRY.register({
|
||||
id: 'account_widget',
|
||||
title: 'Account',
|
||||
icon: 'account_circle',
|
||||
isLocationEditable: true,
|
||||
});
|
||||
VIEW_REGISTRY.register({
|
||||
id: 'settings_widget',
|
||||
title: 'Settings',
|
||||
icon: 'settings',
|
||||
isLocationEditable: true,
|
||||
});
|
||||
@ -1,12 +1,29 @@
|
||||
import { Emitter, Event } from 'dockview';
|
||||
import {
|
||||
CompositeDisposable,
|
||||
Emitter,
|
||||
Event,
|
||||
IDisposable,
|
||||
IView,
|
||||
} from 'dockview';
|
||||
import { DefaultView, View } from './view';
|
||||
import { PaneviewContainer, ViewContainer } from './viewContainer';
|
||||
import {
|
||||
PaneviewContainer,
|
||||
ViewContainer,
|
||||
SerializedViewContainer,
|
||||
} from './viewContainer';
|
||||
import { IViewRegistry } from './viewRegistry';
|
||||
|
||||
export interface IViewService {
|
||||
export interface SerializedViewService {
|
||||
containers: SerializedViewContainer[];
|
||||
activeContainer?: string;
|
||||
}
|
||||
|
||||
export interface IViewService extends IDisposable {
|
||||
readonly containers: ViewContainer[];
|
||||
readonly onDidActiveContainerChange: Event<void>;
|
||||
readonly onDidRemoveContainer: Event<void>;
|
||||
readonly onDidAddContainer: Event<void>;
|
||||
readonly onDidContainersChange: Event<void>;
|
||||
readonly activeContainer: ViewContainer | undefined;
|
||||
addContainer(container: ViewContainer): void;
|
||||
setActiveViewContainer(id: string): void;
|
||||
@ -16,11 +33,13 @@ export interface IViewService {
|
||||
targetViewContainer: ViewContainer,
|
||||
targetLocation: number
|
||||
): void;
|
||||
insertContainerAfter(source: ViewContainer, target: ViewContainer): 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;
|
||||
getViewContainer2(view: View): ViewContainer | undefined;
|
||||
toJSON(): SerializedViewService;
|
||||
load(layout: SerializedViewService): void;
|
||||
}
|
||||
|
||||
export class ViewService implements IViewService {
|
||||
@ -46,21 +65,21 @@ export class ViewService implements IViewService {
|
||||
);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
constructor(private readonly viewRegistry: IViewRegistry) {
|
||||
//
|
||||
}
|
||||
|
||||
load(layout: any): void {
|
||||
load(layout: SerializedViewService): 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)
|
||||
);
|
||||
}
|
||||
const viewContainer = new PaneviewContainer(
|
||||
id,
|
||||
this.viewRegistry,
|
||||
views
|
||||
);
|
||||
|
||||
this.addContainer(viewContainer);
|
||||
}
|
||||
|
||||
@ -166,10 +185,16 @@ export class ViewService implements IViewService {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
toJSON(): SerializedViewService {
|
||||
return {
|
||||
containers: this.containers.map((c) => c.toJSON()),
|
||||
activeContainer: this.activeContainer.id,
|
||||
};
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._onDidActiveContainerChange.dispose();
|
||||
this._onDidAddContainer.dispose();
|
||||
this._onDidRemoveContainer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,36 +13,88 @@ import {
|
||||
import * as React from 'react';
|
||||
import { useLayoutRegistry } from '../layout-grid/registry';
|
||||
import { PaneviewContainer, ViewContainer } from './viewContainer';
|
||||
import { ViewService } from './viewService';
|
||||
import { IViewService, ViewService } from './viewService';
|
||||
import { DefaultView } from './view';
|
||||
import { RegisteredView, VIEW_REGISTRY } from './viewRegistry';
|
||||
|
||||
const viewService = new ViewService();
|
||||
class ViewServiceModel {
|
||||
private readonly viewService: IViewService;
|
||||
|
||||
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));
|
||||
get model() {
|
||||
return this.viewService;
|
||||
}
|
||||
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));
|
||||
|
||||
constructor() {
|
||||
this.viewService = new ViewService(VIEW_REGISTRY);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init(): void {
|
||||
const layout = localStorage.getItem('viewservice');
|
||||
if (layout) {
|
||||
this.viewService.load(JSON.parse(layout));
|
||||
} else {
|
||||
const container1 = new PaneviewContainer(
|
||||
'default_container_1',
|
||||
VIEW_REGISTRY
|
||||
);
|
||||
if (!container1.schema) {
|
||||
this.addView(
|
||||
container1,
|
||||
VIEW_REGISTRY.getRegisteredView('search_widget')
|
||||
);
|
||||
this.addView(
|
||||
container1,
|
||||
VIEW_REGISTRY.getRegisteredView('home_widget')
|
||||
);
|
||||
}
|
||||
const container2 = new PaneviewContainer(
|
||||
'default_container_2',
|
||||
VIEW_REGISTRY
|
||||
);
|
||||
if (!container2.schema) {
|
||||
this.addView(
|
||||
container2,
|
||||
VIEW_REGISTRY.getRegisteredView('account_widget')
|
||||
);
|
||||
this.addView(
|
||||
container2,
|
||||
VIEW_REGISTRY.getRegisteredView('settings_widget')
|
||||
);
|
||||
}
|
||||
this.viewService.addContainer(container1);
|
||||
this.viewService.addContainer(container2);
|
||||
}
|
||||
|
||||
const save = () => {
|
||||
localStorage.setItem(
|
||||
'viewservice',
|
||||
JSON.stringify(this.viewService.toJSON())
|
||||
);
|
||||
};
|
||||
|
||||
this.viewService.onDidActiveContainerChange(save);
|
||||
this.viewService.onDidRemoveContainer(save);
|
||||
this.viewService.onDidAddContainer(save);
|
||||
}
|
||||
|
||||
private addView(
|
||||
container: ViewContainer,
|
||||
registedView: RegisteredView
|
||||
): void {
|
||||
container.addView(
|
||||
new DefaultView({
|
||||
id: registedView.id,
|
||||
title: registedView.title,
|
||||
isExpanded: true,
|
||||
isLocationEditable: registedView.isLocationEditable,
|
||||
icon: registedView.icon,
|
||||
})
|
||||
);
|
||||
}
|
||||
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 viewService = new ViewServiceModel();
|
||||
|
||||
const components: PanelCollection<IPaneviewPanelProps<any>> = {
|
||||
default: (props: IPaneviewPanelProps<{ viewId: string }>) => {
|
||||
@ -56,27 +108,27 @@ const components: PanelCollection<IPaneviewPanelProps<any>> = {
|
||||
|
||||
export const Activitybar = (props: IGridviewPanelProps) => {
|
||||
const [activeContainerid, setActiveContainerId] = React.useState<string>(
|
||||
viewService.activeContainer.id
|
||||
viewService.model.activeContainer.id
|
||||
);
|
||||
const [containers, setContainers] = React.useState<ViewContainer[]>(
|
||||
viewService.containers
|
||||
viewService.model.containers
|
||||
);
|
||||
|
||||
const registry = useLayoutRegistry();
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = new CompositeDisposable(
|
||||
viewService.onDidActiveContainerChange(() => {
|
||||
setActiveContainerId(viewService.activeContainer.id);
|
||||
viewService.model.onDidActiveContainerChange(() => {
|
||||
setActiveContainerId(viewService.model.activeContainer.id);
|
||||
}),
|
||||
viewService.onDidAddContainer(() => {
|
||||
setContainers(viewService.containers);
|
||||
viewService.model.onDidAddContainer(() => {
|
||||
setContainers(viewService.model.containers);
|
||||
}),
|
||||
viewService.onDidRemoveContainer(() => {
|
||||
setContainers(viewService.containers);
|
||||
viewService.model.onDidRemoveContainer(() => {
|
||||
setContainers(viewService.model.containers);
|
||||
}),
|
||||
viewService.onDidContainersChange(() => {
|
||||
setContainers(viewService.containers);
|
||||
viewService.model.onDidContainersChange(() => {
|
||||
setContainers(viewService.model.containers);
|
||||
})
|
||||
);
|
||||
|
||||
@ -103,7 +155,7 @@ export const Activitybar = (props: IGridviewPanelProps) => {
|
||||
sidebarPanel.focus();
|
||||
}
|
||||
|
||||
viewService.setActiveViewContainer(container.id);
|
||||
viewService.model.setActiveViewContainer(container.id);
|
||||
};
|
||||
|
||||
const onDrop = (targetContainer: ViewContainer) => (
|
||||
@ -112,8 +164,13 @@ export const Activitybar = (props: IGridviewPanelProps) => {
|
||||
const data = event.dataTransfer.getData('application/json');
|
||||
if (data) {
|
||||
const { container } = JSON.parse(data);
|
||||
const sourceContainer = viewService.getViewContainer(container);
|
||||
viewService.insertContainerAfter(sourceContainer, targetContainer);
|
||||
const sourceContainer = viewService.model.getViewContainer(
|
||||
container
|
||||
);
|
||||
viewService.model.insertContainerAfter(
|
||||
sourceContainer,
|
||||
targetContainer
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -121,16 +178,16 @@ export const Activitybar = (props: IGridviewPanelProps) => {
|
||||
const data = getPaneData();
|
||||
if (data) {
|
||||
const { paneId } = data;
|
||||
const view = viewService.getView(paneId);
|
||||
const viewContainer = viewService.getViewContainer2(view);
|
||||
viewService.removeViews([view], viewContainer);
|
||||
const view = viewService.model.getView(paneId);
|
||||
const viewContainer = viewService.model.getViewContainer2(view);
|
||||
viewService.model.removeViews([view], viewContainer);
|
||||
// viewContainer.removeView(view);
|
||||
const newContainer = new PaneviewContainer(
|
||||
`t_${Date.now().toString().substr(5)}`,
|
||||
true
|
||||
VIEW_REGISTRY
|
||||
);
|
||||
newContainer.addView(view);
|
||||
viewService.addContainer(newContainer);
|
||||
viewService.model.addContainer(newContainer);
|
||||
}
|
||||
};
|
||||
|
||||
@ -157,6 +214,9 @@ export const Activitybar = (props: IGridviewPanelProps) => {
|
||||
}}
|
||||
onDrop={onDrop(container)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '48px',
|
||||
boxSizing: 'border-box',
|
||||
borderLeft: isActive
|
||||
@ -165,7 +225,13 @@ export const Activitybar = (props: IGridviewPanelProps) => {
|
||||
}}
|
||||
key={i}
|
||||
>
|
||||
{container.id}
|
||||
{/* {container.id} */}
|
||||
<span
|
||||
style={{ fontSize: '30px' }}
|
||||
className="material-icons-outlined"
|
||||
>
|
||||
{container.icon}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@ -185,12 +251,12 @@ export const Activitybar = (props: IGridviewPanelProps) => {
|
||||
|
||||
export const Sidebar = () => {
|
||||
const [sidebarId, setSidebarId] = React.useState<string>(
|
||||
viewService.activeContainer.id
|
||||
viewService.model.activeContainer.id
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = viewService.onDidActiveContainerChange(() => {
|
||||
setSidebarId(viewService.activeContainer.id);
|
||||
const disposable = viewService.model.onDidActiveContainerChange(() => {
|
||||
setSidebarId(viewService.model.activeContainer.id);
|
||||
});
|
||||
|
||||
return () => {
|
||||
@ -211,7 +277,7 @@ export const SidebarPart = (props: { id: string }) => {
|
||||
};
|
||||
}
|
||||
|
||||
const viewContainer = viewService.getViewContainer(props.id);
|
||||
const viewContainer = viewService.model.getViewContainer(props.id);
|
||||
|
||||
const disposables = new CompositeDisposable(
|
||||
api.onDidLayoutChange(() => {
|
||||
@ -271,10 +337,10 @@ export const SidebarPart = (props: { id: string }) => {
|
||||
}
|
||||
|
||||
const viewId = data.paneId;
|
||||
const viewContainer = viewService.getViewContainer(props.id);
|
||||
const view = viewService.getView(viewId);
|
||||
const viewContainer = viewService.model.getViewContainer(props.id);
|
||||
const view = viewService.model.getView(viewId);
|
||||
|
||||
viewService.moveViewToLocation(view, viewContainer, 0);
|
||||
viewService.model.moveViewToLocation(view, viewContainer, 0);
|
||||
};
|
||||
|
||||
if (!props.id) {
|
||||
|
||||
@ -119,7 +119,7 @@ export const PaneviewReact = React.forwardRef(
|
||||
React.useEffect(() => {
|
||||
if (!paneviewRef.current) {
|
||||
return () => {
|
||||
// noop
|
||||
//
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user