Merge branch 'master' of https://github.com/mathuo/dockview into master

This commit is contained in:
mathuo 2021-10-25 21:22:10 +01:00
commit cfb1b5c38b
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
17 changed files with 246 additions and 79 deletions

View File

@ -2,7 +2,7 @@
"packages": [
"packages/*"
],
"version": "0.0.25",
"version": "0.0.26",
"command": {
"publish": {
"message": "chore(release): publish %s"

View File

@ -1,6 +1,6 @@
{
"name": "dockview-demo",
"version": "0.0.25",
"version": "0.0.26",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -1,7 +1,7 @@
{
"name": "dockview-demo",
"private": true,
"version": "0.0.25",
"version": "0.0.26",
"description": "Demo project for https://github.com/mathuo/dockview",
"scripts": {
"build": "npm run build-webpack && npm run build-storybook",
@ -14,7 +14,7 @@
"author": "https://github.com/mathuo",
"license": "MIT",
"dependencies": {
"dockview": "^0.0.25",
"dockview": "^0.0.26",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"recoil": "^0.4.1"

View File

@ -0,0 +1,135 @@
import { ViewContainer } from './viewContainer';
import * as React from 'react';
import { toggleClass } from '../dom';
export const Container = (props: {
container: ViewContainer;
isActive: boolean;
onDragOver: (e: React.DragEvent) => void;
onDrop: (e: React.DragEvent, direction: 'top' | 'bottom') => void;
onClick: (e: React.MouseEvent) => void;
}) => {
const ref = React.useRef<HTMLDivElement>(null);
const [selection, setSelection] = React.useState<
'top' | 'bottom' | undefined
>(undefined);
const isDragging = React.useRef<boolean>(false);
const [dragEntered, setDragEntered] = React.useState<boolean>(false);
const onDragOver = (e: React.DragEvent) => {
if (isDragging.current) {
return;
}
setDragEntered(true);
e.preventDefault();
const target = e.target as HTMLDivElement;
const width = target.clientWidth;
const height = target.clientHeight;
if (width === 0 || height === 0) {
return; // avoid div!0
}
const x = e.nativeEvent.offsetX;
const y = e.nativeEvent.offsetY;
const xp = (100 * x) / width;
const yp = (100 * y) / height;
const isTop = yp < 50;
const isBottom = yp >= 50;
setSelection(isTop ? 'top' : 'bottom');
props.onDragOver(e);
};
const onDragLeave = (e: React.DragEvent) => {
if (isDragging.current) {
return;
}
setDragEntered(false);
setSelection(undefined);
};
const onDrop = (e: React.DragEvent) => {
if (isDragging.current) {
return;
}
setDragEntered(false);
props.onDrop(e, selection);
setSelection(undefined);
};
const onDragEnter = (e: React.DragEvent) => {
e.preventDefault();
};
const onDragStart = (e: React.DragEvent) => {
isDragging.current = true;
e.dataTransfer.setData(
'application/json',
JSON.stringify({ container: props.container.id })
);
};
const onDragEnd = (e: React.DragEvent) => {
isDragging.current = false;
setDragEntered(false);
};
return (
<div
ref={ref}
draggable={true}
onClick={props.onClick}
onDragOver={onDragOver}
onDragEnter={onDragEnter}
onDragStart={onDragStart}
onDragLeave={onDragLeave}
onDragEnd={onDragEnd}
onDrop={onDrop}
style={{
borderLeft: props.isActive
? '1px solid white'
: '1px solid transparent',
}}
className="container-item"
>
{dragEntered && (
<div
style={{
position: 'absolute',
top: '0px',
left: '0px',
height: '100%',
width: '100%',
backgroundColor: 'transparent',
boxSizing: 'border-box',
borderTop: selection === 'top' ? '2px solid white' : '',
borderBottom:
selection === 'bottom' ? '2px solid white' : '',
pointerEvents: 'none',
}}
/>
)}
<span
style={{ fontSize: '30px' }}
className="material-icons-outlined"
>
{props.container.icon}
</span>
</div>
);
};

View File

@ -45,7 +45,7 @@ export class PaneviewContainer implements ViewContainer<SerializedPaneview> {
}
get views() {
return this._views;
return [...this._views];
}
get schema(): SerializedPaneview | undefined {

View File

@ -34,6 +34,7 @@ export interface IViewService extends IDisposable {
targetLocation: number
): void;
insertContainerAfter(source: ViewContainer, target: ViewContainer): void;
insertContainerBefore(source: ViewContainer, target: ViewContainer): void;
addViews(view: View, viewContainer: ViewContainer, location?: number): void;
removeViews(removeViews: View[], viewContainer: ViewContainer): void;
getViewContainer(id: string): ViewContainer | undefined;
@ -103,6 +104,23 @@ export class ViewService implements IViewService {
this._onDidContainersChange.fire();
}
insertContainerBefore(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(Math.max(targetIndex, 0), 0, view);
this._viewContainers = [...this._viewContainers];
this._onDidContainersChange.fire();
}
addContainer(container: ViewContainer): void {
this._viewContainers = [...this._viewContainers, container];
this._activeViewContainerId = container.id;

View File

@ -3,3 +3,12 @@
background-color: green !important;
}
}
.container-item {
display: flex;
justify-content: center;
align-items: center;
height: 48px;
box-sizing: border-box;
position: relative;
}

View File

@ -18,6 +18,7 @@ import { IViewService, ViewService } from './viewService';
import { DefaultView } from './view';
import { RegisteredView, VIEW_REGISTRY } from './viewRegistry';
import { toggleClass } from '../dom';
import { Container } from './sidebarItem';
import './widgets.scss';
class ViewServiceModel {
@ -172,7 +173,8 @@ export const Activitybar = (props: IGridviewPanelProps) => {
};
const onContainerDrop = (targetContainer: ViewContainer) => (
event: React.DragEvent
event: React.DragEvent,
direction: 'top' | 'bottom'
) => {
const data = event.dataTransfer.getData('application/json');
if (data) {
@ -180,10 +182,21 @@ export const Activitybar = (props: IGridviewPanelProps) => {
const sourceContainer = viewService.model.getViewContainer(
container
);
viewService.model.insertContainerAfter(
sourceContainer,
targetContainer
);
switch (direction) {
case 'bottom':
viewService.model.insertContainerAfter(
sourceContainer,
targetContainer
);
break;
case 'top':
viewService.model.insertContainerBefore(
sourceContainer,
targetContainer
);
break;
}
}
};
@ -204,48 +217,31 @@ export const Activitybar = (props: IGridviewPanelProps) => {
}
};
const onDragOver = (container: ViewContainer) => (e: React.DragEvent) => {
const api = registry.get<GridviewApi>('gridview');
const sidebarPanel = api.getPanel('sidebar');
if (!sidebarPanel.api.isVisible) {
api.setVisible(sidebarPanel, true);
sidebarPanel.focus();
}
viewService.model.setActiveViewContainer(container.id);
};
return (
<div style={{ background: 'rgb(51,51,51)', cursor: 'pointer' }}>
{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={onContainerDrop(container)}
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '48px',
boxSizing: 'border-box',
borderLeft: isActive
? '1px solid white'
: '1px solid transparent',
}}
<Container
key={i}
>
{/* {container.id} */}
<span
style={{ fontSize: '30px' }}
className="material-icons-outlined"
>
{container.icon}
</span>
</div>
container={container}
isActive={isActive}
onDragOver={onDragOver(container)}
onClick={onClick(container)}
onDrop={onContainerDrop(container)}
/>
);
})}
<ExtraSpace onNewContainer={onNewContainer} />
@ -360,25 +356,40 @@ export const SidebarPart = (props: { id: string }) => {
};
const onDidDrop = (event: PaneviewDropEvent) => {
const data = event.event.getData();
const data = event.getData();
const containerData = event.event.dataTransfer.getData(
'application/json'
);
if (containerData) {
const { container } = JSON.parse(containerData);
const sourceContainer = viewService.model.getViewContainer(
container
);
const targetContainer = viewService.model.getViewContainer(
props.id
);
sourceContainer.views.forEach((v) => {
viewService.model.moveViewToLocation(v, targetContainer, 0);
});
return;
}
if (!data) {
return;
}
const targetPanel = event.event.panel;
const targetPanel = 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
event.position === Position.Right ||
event.position === Position.Bottom
) {
toIndex = Math.min(allPanels.length, toIndex + 1);
}

View File

@ -1,6 +1,6 @@
{
"name": "dockview",
"version": "0.0.25",
"version": "0.0.26",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -1,6 +1,6 @@
{
"name": "dockview",
"version": "0.0.25",
"version": "0.0.26",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support",
"main": "./dist/cjs/index.js",
"types": "./dist/cjs/index.d.ts",

View File

@ -1,6 +1,5 @@
import { getElementsByTagName } from '../dom';
import { addDisposableListener, Emitter } from '../events';
import { focusedElement } from '../focusedElement';
import { CompositeDisposable, IDisposable } from '../lifecycle';
export abstract class DragHandler extends CompositeDisposable {

View File

@ -1,11 +0,0 @@
export const focusedElement: { element: Element | null } = { element: null };
window.addEventListener(
'focusin',
() => {
focusedElement.element = document.activeElement;
},
true
);
focusedElement.element = document.activeElement;

View File

@ -17,7 +17,6 @@ import { ContentContainer, IContentContainer } from './panel/content';
import { ITabsContainer, TabsContainer } from './titlebar/tabsContainer';
import { IWatermarkRenderer } from './types';
import { GroupviewPanel } from './groupviewPanel';
import { focusedElement } from '../focusedElement';
import { DockviewDropTargets } from './dnd';
export enum GroupChangeKind2 {
@ -252,11 +251,11 @@ export class Groupview extends CompositeDisposable implements IGroupview {
}
isContentFocused() {
if (!focusedElement.element) {
if (!document.activeElement) {
return false;
}
return isAncestor(
focusedElement.element,
document.activeElement,
this.contentContainer.element
);
}

View File

@ -4,7 +4,6 @@ import { getPanelData, LocalSelectionTransfer } from '../dnd/dataTransfer';
import { getElementsByTagName, toggleClass } from '../dom';
import { IDockviewComponent } from '../dockview/dockviewComponent';
import { ITabRenderer } from './types';
import { focusedElement } from '../focusedElement';
import { IGroupPanel } from './groupPanel';
import { GroupviewPanel } from './groupviewPanel';
import { DroptargetEvent, Droptarget } from '../dnd/droptarget';

View File

@ -10,6 +10,7 @@ import {
PanePanelInitParameter,
PaneviewPanel,
} from './paneviewPanel';
import { addClasses } from '../dom';
interface ViewContainer {
readonly title: string;
@ -56,8 +57,12 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
}
private initDragFeatures() {
if (!this.header) {
return;
}
const id = this.id;
this.header!.draggable = true;
this.header.draggable = true;
this.handler = new (class PaneDragHandler extends DragHandler {
getData(): IDisposable {
@ -74,7 +79,7 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
},
};
}
})(this.header!);
})(this.header);
this.target = new Droptarget(this.element, {
validOverlays: 'vertical',

View File

@ -52,6 +52,10 @@
position: relative;
outline: none;
&.pane-draggable {
cursor: pointer;
}
&:focus,
&:focus-within {
&:before {

View File

@ -22,9 +22,8 @@ export interface IPaneviewPanelProps<T extends {} = Record<string, any>>
title: string;
}
export interface PaneviewDropEvent {
export interface PaneviewDropEvent extends PaneviewDropEvent2 {
api: PaneviewApi;
event: PaneviewDropEvent2;
}
export interface IPaneviewReactProps {
@ -128,7 +127,7 @@ export const PaneviewReact = React.forwardRef(
const disposable = paneview.onDidDrop((event) => {
if (props.onDidDrop) {
props.onDidDrop({
event,
...event,
api: new PaneviewApi(paneview),
});
}